使用OPENGL绘制一个带轨迹的小球
程序绘制一颗白色小球,通过按下 M/m 键,小球会不断的在窗口中左右移动,并显示出漂亮的尾迹。
因为这是一篇教程,主要为了帮助OPENGL初学者了解一种绘制轨迹/拖影的方法,所以接下来就直接把代码贴上来,大家可以边看注释便敲代码。项目使用 freeglut工具库编写,win32 console应用程序。编译过程中如果出项函数未声明等问题,可以修改包含的头文件路径,因为很可能我们的OPENGL头文件路径不同!!
大概的思路
先来介绍一下大概思路:小球的投影可以简单看作一连串的透明小球组成的,随着小球的移动,队列中靠近末尾的小球会变得更加透明知道消失。为了保存所有拖影及其数据我们使用单链表 tracks,这是链表的数据结构:
- struct tracks{
- int nums; // 节点数量
- tracknode *head; // 头节点,每个新节点插入到头节点之后
- };
- struct tracknode{
- int listnums; //绘制物体的显示列表数量
- GLuint *showlist; // 绘制物体的现实列表
- float color; // 当前颜色值
- point3f poi;
- tracknode *next; // 指向下一个
- };
- struct point3f{
- float val[3];
- };
需要的数据结构定义好之后我们就需要来实现拖影透明的效果了。没错,可以使用混合来实现透明的效果。
代码及其注释
- // 显示小球的运动轨迹
- //
- //
- //
- #include "stdafx.h"
- #include <GL\glew.h>
- #include <GL\GLAUX.H>
- #include <GL\freeglut.h>
- #pragma comment(lib, "glew32.lib")
- #define MAX_RANGE 2.0f // 左右移动范围
- #define SPEED 0.08f // 移动速度
- struct point3f{
- float val[3];
- };
- struct tracknode{
- int listnums; //绘制物体的显示列表数量
- GLuint *showlist; // 绘制物体的现实列表
- float color; // 颜色值
- point3f poi;
- tracknode *next; // 指向下一个
- };
- struct tracks{
- int nums; // 节点数量
- tracknode *head; // 头节点,每个新节点插入到头节点之后
- };
- int time = 0;
- GLuint list; //小球的显示列表
- tracks t; // major data!!!!!!!!!!!!
- BOOL isleft = true; //小球默认向左移动
- // 定义一些材质颜色
- const static float red_material[4] = {1.0f, 1.0, 1.0f, 1.0f};
- void setMaterial(const float diffuseMaterial[4],const float shininessMaterial);
- // 将 src 节点的内容复制给 dst 节点(不包括 this->next)
- void copytracknode(const tracknode *src, tracknode *dst);
- void init();
- void drawtrack();
- void display();
- void reshape(int, int);
- void keyboard(unsigned char, int, int);
- void releaseTracks(tracks *t); // 释放释放
- int _tmain(int argc, _TCHAR* argv[])
- {
- glutInit(&argc, argv);
- glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
- glutInitWindowPosition(300, 200);
- glutInitWindowSize(600, 400);
- glutCreateWindow("轨迹");
- init();
- glutDisplayFunc(display);
- glutReshapeFunc(reshape);
- glutKeyboardFunc(keyboard);
- glutMainLoop();
- releaseTracks(&t);
- return 0;
- }
- void setMaterial(const float diffuseMaterial[4],const float shininessMaterial)
- {
- const float specularMaterial[4] = {0.0f, 0.0f, 0.0f, 1.0f}; // 镜面光
- const float emissionMaterial[4] = {0.0f, 0.0f, 0.0f, 1.0f}; // 发光颜色
- glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuseMaterial);
- glMaterialfv(GL_FRONT, GL_SPECULAR, specularMaterial);
- glMaterialfv(GL_FRONT, GL_EMISSION, emissionMaterial);
- glMaterialfv(GL_FRONT, GL_SHININESS, &shininessMaterial);
- }
- void copytracknode(const tracknode *src, tracknode *dst)
- {
- dst->listnums = src->listnums;
- dst->showlist = new GLuint[dst->listnums];
- memcpy_s(dst->showlist, sizeof(GLuint)*dst->listnums, src->showlist, sizeof(GLuint)*src->listnums);
- dst->color= src->color;
- memcpy_s(dst->poi.val, sizeof(float)*3, src->poi.val, sizeof(float)*3);
- }
- void init()
- {
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glShadeModel(GL_SMOOTH);
- // 启用光照
- float positionFarawayLight[] = {1.0, 1.0f, 3.0f, 0.0f};
- float ambient[] = {0.2f, 0.2f, 0.2f, 1.0f}; // 弱弱的白色环境光
- float diffuseLight[] = {1.0f, 1.0f, 1.0f, 1.0f}; // 散射光
- float specularLight[] = {1.0f, 1.0f, 1.0f, 1.0f};
- glLightfv(GL_LIGHT0, GL_POSITION, positionFarawayLight);
- glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
- glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
- glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
- glEnable(GL_LIGHTING);
- glEnable(GL_LIGHT0);
- // 创建小球的初始化列表
- list = glGenLists(1);
- glNewList(list, GL_COMPILE);
- glutSolidSphere(0.2, 20, 20);
- glEndList();
- // 初始化 struct tracks
- t.head = new tracknode;
- t.nums = 1;
- t.head->listnums = 1;t.head->color= 1.0f;
- t.head->showlist = new GLuint[t.head->listnums];
- memset(t.head->poi.val, 0, sizeof(float)*3);
- t.head->poi.val[0] = -0.1f;
- t.head->showlist[0] = list;
- t.head->next = nullptr;
- // 启用混合
- glEnable(GL_BLEND);
- glEnable(GL_DEPTH_TEST);
- }
- void drawtrack()
- {
- tracknode *ite = nullptr;
- ite = t.head;
- while( ite )
- {
- for(int y=0;y < ite->listnums;++y)
- {
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- glPushMatrix();
- glColor4f(0.0f, 0.0f, 0.0f, ite->color);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glTranslatef(0.0f, 0.0f, -2.0f);
- glTranslatef(ite->poi.val[0], ite->poi.val[1], ite->poi.val[2]);
- glDepthMask(GL_FALSE); // 设置深度缓冲区只读
- if( t.head == ite )
- {
- setMaterial(red_material, 30.0);
- }else{
- float white_material[4] = {ite->color, ite->color, ite->color, 0.5};
- setMaterial(white_material, 30.0);
- }
- glCallList(ite->showlist[y]);
- glPopMatrix();
- glDepthMask(GL_TRUE); // 设置深度缓冲区可读写
- }
- ite = ite->next;
- }
- }
- void display()
- {
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- // 开始绘制小球
- drawtrack();
- glFlush();
- glutSwapBuffers();
- }
- void reshape(int w, int h)
- {
- glViewport(0, 0, w, h);
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- if(w >= h)
- {
- glOrtho(
- -MAX_RANGE*((double)w/(double)h), MAX_RANGE*((double)w/(double)h),
- -MAX_RANGE, MAX_RANGE,
- 0.1, 100.0f
- );
- }else{
- glOrtho(
- -MAX_RANGE, MAX_RANGE,
- -MAX_RANGE*((double)h/(double)w), MAX_RANGE*((double)h/(double)w),
- 0.1, 100.0
- );
- }
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- }
- void keyboard(unsigned char key, int x, int y)
- {
- tracknode *pre = t.head;
- tracknode *current = t.head->next;
- tracknode *newnode1;
- /*tracknode *newnode2;
- tracknode *newnode3;*/
- switch(key)
- {
- case 'm':
- case 'M':
- // 为了让小球动起来,需要调整小球的位置,并将当前位置加入 struct track
- // 第一步加入新的节点
- // 每个节点采用头插法插入头节点之后的位置
- newnode1 = new tracknode;
- copytracknode(t.head, newnode1);
- newnode1->color= t.head->color - 0.1f;
- newnode1->next = t.head->next;
- t.head->next = newnode1;
- ++t.nums;
- // 删除 color为0的节点
- // 不出意外被删除的点都位于最后的位置
- while(current)
- {
- if( current )
- {
- current->color-= 0.03f;
- }
- if( 0.1f >= current->color)
- {
- delete current;
- pre->next = nullptr;
- --t.nums;
- break;
- }
- pre = pre->next;
- current = current->next;
- }
- // 第二部改变头节点的位置
- // 为了方便显示与计算让小球的x坐标在 -MAX_RANGE~+MAX_RANGE范围内不断变化
- if( isleft )
- {
- t.head->poi.val[0] -= (float)SPEED;
- if( t.head->poi.val[0] <= -(float)MAX_RANGE )
- {
- isleft = !isleft;
- }
- }else{
- t.head->poi.val[0] += (float)SPEED;
- if( t.head->poi.val[0] >= (float)MAX_RANGE )
- {
- isleft = !isleft;
- }
- }
- break;
- default:
- break;
- }
- glutPostRedisplay();
- }
- void releaseTracks(tracks *t)
- {
- tracknode *current = t->head;
- tracknode *next = current->next;
- // 删除显示列表(所有节点共用所有显示列表,所以只删除一次)
- for(int x=0;x < current->listnums;++x)
- {
- glDeleteLists(current->showlist[x], 1);
- }
- while( current )
- {
- delete current;
- current = next;
- next = next->next;
- }
- }
以上就是全部绘制代码,在当前代码条件下我们可以根据修改小球移动速度(SPEED)和拖影衰减控制变量(tracknode.color)来达到不同效果。但是细心的话会发现我们还无法绘制出像彗星尾巴那样更加”浓郁“的尾迹(当然要达到这种效果有其他更好的方法,比如使用贴图并总是改变其方向时期朝向观察者或者使用粒子来绘制),为了绘制更加“浓郁”的尾迹可以在相应按键消息的时候一次增加多个位置具有相对微小变化的拖影。可能有些人喜欢在响应绘制函数中做上面的事情,但是出于可控性和效率的考虑不推荐这样做,更好的做法是设置定时器,然后在定时器中进行以上操作,或者干脆另起一个线程来负责以上的任务。