使用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;
- };
ps:可能你会经常在各种库中见到 point3f ,point3F或者point32F之类名字的数据结构,这些都是用来保存三维坐标的数据结构。但是这里我们没有使用那些库(OPENGL里也没有名字叫做point3f的数据结构),因此我们需要自己定义三维点的数据结构,如下- 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;
- 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);
-
- 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();
-
-
- 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;
-
-
- switch(key)
- {
- case 'm':
- case 'M':
-
-
-
- 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;
-
-
- 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;
- }
-
-
- 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)来达到不同效果。但是细心的话会发现我们还无法绘制出像彗星尾巴那样更加”浓郁“的尾迹(当然要达到这种效果有其他更好的方法,比如使用贴图并总是改变其方向时期朝向观察者或者使用粒子来绘制),为了绘制更加“浓郁”的尾迹可以在相应按键消息的时候一次增加多个位置具有相对微小变化的拖影。可能有些人喜欢在响应绘制函数中做上面的事情,但是出于可控性和效率的考虑不推荐这样做,更好的做法是设置定时器,然后在定时器中进行以上操作,或者干脆另起一个线程来负责以上的任务。