使用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; // 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)來達到不同效果。但是細心的話會發現我們還無法繪製出像彗星尾巴那樣更加”濃郁“的尾跡(當然要達到這種效果有其他更好的方法,比如使用貼圖並總是改變其方向時期朝向觀察者或者使用粒子來繪製),爲了繪製更加“濃郁”的尾跡可以在相應按鍵消息的時候一次增加多個位置具有相對微小變化的拖影。可能有些人喜歡在響應繪製函數中做上面的事情,但是出於可控性和效率的考慮不推薦這樣做,更好的做法是設置定時器,然後在定時器中進行以上操作,或者乾脆另起一個線程來負責以上的任務。
本篇結束