作为最后一关,我们将架设自己即时光影的动画,让没有VOODOO的玩家看看OPENGL 这震撼(至少我是这么认为的吧)的效果。
我们前面(好象是第三还是第四次)讲过如何用几何变换实现动画。那时的效果现在看肯定不尽人意,因为频繁的闪烁不是我们需要的。因为那时(到这之前也是)采用的是单缓存模式。对正在显示的部分边计算边修改必然造成速度瓶颈,出现闪烁。一般正规的动画制作在实现上都是通过双缓存实现的(硬件也好,软件也好)大家可以参考《家用电脑与游戏机》的98-2中的一篇文章。当前台显示缓存用于显示时,后台缓存已经进行计算,计算完毕把所有内容通过缓存拷贝一次性完成,防止闪烁的出现。
一 OPENGL帧缓存的实现
1 颜色缓存(Color Buffer)其中内容可以是颜色索引或者RGBA数据,如果用的OPENGL系统支持立体图,则有左右两个缓存。
2 深度缓存(Depth Buffer) 就是Z-BUFFER,用于保存象素Z方向的数值,深度大的被深度小的代替,用以实现消隐
3 模板缓存(Stencil Buffer) 用以保持屏幕上某些位置图形不变,而其他部分重绘。例如大家熟悉的开飞机和赛车的游戏的驾驶舱视角,只有挡风外面的景物变化,舱内仪表等等并不变化。
4 累计缓存(Accumulation Buffer) 只保存RGBA数据,用于合成图象,例如有某缓存中的位图调入这里合成一幅新图。
二 帧缓存的清除
对高分辨率模式清除缓存是工作量巨大的任务,OPENGL一般先寻求硬件同时完成,否则软件依次解决。我们前面每次必用的glClearColor()大家已经不陌生吧。
首先设置清除值
void glClearColor(GLclampf red,GLclampf green,GLclampf blue,GLclampf
alpha);
void glClearIndex(GLfloat index);
void glClearDepth(GLclampd depth);
void glClearStencil(GLint s);
void glClerAccum(GLfloat red,GLfloat green,GLfloat blue,GLfloat alpha);
然后进行清除
void glClear(GLbitfield mask);
mask: GL_COLOR_BUFFER_BIT|
GL_DEPTH_BUFFER_BIT|
GL_STENCIL_BUFFER_BIT|
GL_ACCUM_BUFFER_BIT
三 双缓存动画
你可以把所有的变换工作看成后台缓存的计算,然后把所有结果拷贝到前台即可。 因此我们只需两个新内容:
首先初始化时调用
auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA);
用AUX_DOUBLE代替AUX_SINGLE设置成双缓存模式
然后在绘制完毕(glFlush();后)调用
auxSwapBuffers();
进行缓存拷贝。Easy like sunday morning!!
当然不同系统下,这个函数也许不同(毕竟是辅助库函数么),例如X-WINDOWS
下可以使用glxSwapBuffers(),意思完全一样。
先说说下面这个例子的功能:
有一个兰色的环作为主体,有一个黄色高亮的球表示光源的位置。小球不断从屏幕左方运动到右方,可以看出环状物上光影的变化。
操作:
鼠标左键/右键:开始/停止光源的运动
键盘 上/下/左/右:控制环状物的 前进/后退/旋转
//////////////////////////////////////////////////////////////////
//sample.cpp
#include "glos.h"
#include
#include
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
void CALLBACK stepDisplay(void);
void CALLBACK startIdleFunc(AUX_EVENTREC *event);
void CALLBACK stopIdleFunc(AUX_EVENTREC *event);
//step是表示环状物旋转的参数;z是控制其前后坐标的参数
static GLfloat step=0.0,z=0.0;
//position是控制光源的位置的参数
static GLfloat position[]={-20.0,0.0,-5.0,1.0};
void myinit(void)
{
//初始化注意是双缓存模式
auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glFrontFace(GL_CW);
glEnable(GL_LIGHTING);
glFrontFace(GL_CW);
// glEnable(GL_POLYGON_SMOOTH);
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA,GL_ONE);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
// glShadeModel(GL_FLAT);
}
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
/*if(w<=h*3)
glOrtho(-2.0,2.0,-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
else
glOrtho(-2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w,-2.0,2.0,-10.0,10.0);
*/
//启用立体的视景,具有近大远小的效果
glFrustum(-6.0,6.0,-6.0,6.0,3.0,20.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//首先在当前位置设置光源
glPushMatrix();
GLfloat light_ambient[]={0.3,0.5,0.3};
GLfloat light_diffuse[]={1.0,1.0,1.0};
GLfloat light_specular[]={0.8,0.8,0.0};
glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
glLightfv(GL_LIGHT0,GL_POSITION,position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
//在光源旁边绘制黄色高亮的小球,标志光源位置
//小球和光源位置由position[]决定
glTranslatef(position[0],position[1],position[2]-1.0);
GLfloat mat_ambient[]={1.0,0.0,0.0,1.0};
GLfloat mat_diffuse[]={1.0,1.0,0.0,1.0};
GLfloat mat_specular[]={1.0,1.0,0.0,1.0};
GLfloat mat_shininess[]={50.0};
glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
auxSolidSphere(0.5);
glPopMatrix();
//在当前位置绘制兰色环状物,其位置由step z共同决定
glPushMatrix();
glTranslatef(0.0,0.0,-8.0+z);
glRotatef(135.0+step,0.0,1.0,0.0);
GLfloat mat2_ambient[]={0.0,0.0,1.0,1.0};
GLfloat mat2_diffuse[]={0.2,0.0,0.99,1.0};
GLfloat mat2_specular[]={1.0,1.0,0.0,1.0};
GLfloat mat2_shininess[]={50.0};
glMaterialfv(GL_FRONT,GL_AMBIENT,mat2_ambient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,mat2_diffuse);
glMaterialfv(GL_FRONT,GL_SPECULAR,mat2_specular);
glMaterialfv(GL_FRONT,GL_SHININESS,mat2_shininess);
auxSolidTorus(2.0,3.5);
glPopMatrix();
glFlush();
//绘制完毕,缓存交换
auxSwapBuffers();
}
void CALLBACK Up(void)
{
//键盘“上”的处理
z=z+0.05;
}
void CALLBACK Down(void)
{
//键盘“下”的处理
z=z-0.05;
}
void CALLBACK Left(void)
{
//键盘“左”的处理
step=step+2.0;
}
void CALLBACK Right(void)
{
//键盘“右”的处理
step=step-2.0;
}
void CALLBACK stepDisplay(void)
{
//系统闲时的调用过程
position[0]=position[0]+0.5;
if(position[0]>20.0) position[0]=-20.0;
display();
}
void CALLBACK startFunc(AUX_EVENTREC *event)
{
//鼠标左键的处理
auxIdleFunc(stepDisplay);
}
void CALLBACK stopIdleFunc(AUX_EVENTREC *event)
{
//鼠标右键的处理
auxIdleFunc(0);
}
void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxIdleFunc(stepDisplay);
auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEDOWN,startFunc);
auxMouseFunc(AUX_RIGHTBUTTON,AUX_MOUSEDOWN,stopIdleFunc);
auxKeyFunc(AUX_UP,Up);
auxKeyFunc(AUX_DOWN,Down);
auxKeyFunc(AUX_LEFT,Left);
auxKeyFunc(AUX_RIGHT,Right);
auxMainLoop(display);
}
//end of sample
////////////////////////////////////////////////////////////////////
其中用到大量CALLBACK函数,分别处理不同消息比较前面演示的动画效果,闪烁的现象明显改善乐(你可以把这个程序两个关于双缓存的地方改成单缓存:设置AUX_SINGLE和去掉auxSwapBuffers(),看看闪烁的多么厉害),至于本身的绘图可能的拖尾现象,只能怪自己机器不好乐。
写到这里,终于打穿乐OPENGL!!!实现当初提出的“长长见识”的设想。
今后有什么新内容我还会尽量补充,大家也可以自由补充。