真实感场景绘制(附源码)
首先给出绘制效果图:
说明:
本系统绘制了一个真实感的三维场景,并实现场景漫游。使用按键↑、↓、←、→或W、S、A、D控制运动方向,PgDn和PgUp可以改变观察者的高度,鼠标控制转向,按键‘F’可以打开和关闭“雾气”,Esc退出程序。
本场景中绘制了墙壁与地面、天空、石柱、箱子、玻璃球、雪人、雾等对象,下面将依次分析它们的设计思路,这里将使用相同绘制技术的对象放到一起说明。
1、 墙壁、地面、箱子
它们的基本操作对四边形的纹理映射,将一幅纹理图的四个顶点坐标分别映射到四边形的四个顶点上即可。OpenGL就自动通过插值填充多边形内部的纹理。由于在OpenGL中纹理坐标是一个点的属性,故需要在绘制点之前指定该点的纹理坐标。另外在绘制四边形时要保证绘制顺序的一致,这里统一采用逆时针顺序绘制。纹理坐标范围在[0,1]之间,坐标超过1则采用重复的方式(OpenGL默认处理方式)。这样可以在映射时按四边形的比例设置纹理坐标,从而防止纹理图过度拉伸造成的变形。一个四边形的纹理映射步骤为:
glBindTexture(GL_TEXTURE_2D,texture[0]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(30.0f,0.0f, -170.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(30.0f,0.0f, -150.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(30.0f,20.0f,-150.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(30.0f,20.0f,-170.0f);
glEnd();
实验效果如下:
图1.墙壁、地面、箱子
2、 石柱、雪人
石柱和雪人都属于二次几何体的绘制和纹理映射,其中雪人由圆柱、球体、圆盘、圆锥构成。OpenGL提供了这些基本几何体的绘制函数,而且在纹理映射时我们采用自动生成纹理坐标的方法,所以这一部分工作量其实很少。另外,为了给雪人的身体、鼻子等部位设置颜色时,我们需要关闭纹理映射、并更改材质的属性(物体的颜色是由光源和材质共同作用的结果)。对于石柱,我们加上了绕自身局部坐标Y轴的匀速旋转。实验效果如下:
图2.雪人 图3.石柱
我们使用Glut库提供的如下函数绘制二次几何体:
gluCylinder:绘制圆柱和圆柱
gluSphere:绘制球体
gluDisk:绘制圆盘
使用gluQuadricTexture设置自动计算纹理坐标。
3、 玻璃球
在绘制玻璃球时,为了在球面上反射出周围的场景,我们使用了环境映射。由于我们能够在三维场景中漫游,这就需要球面上反射出的场景是随着视点移动而变化的,这一需求可以利用“渲染到纹理”(RTT)技术实现。其基本思想是:首先绘制出环境中的所有物体(除了该玻璃球),接着将绘制得到的场景图制作成纹理,在绘制该玻璃球时将此纹理图贴在球面上即可。
我们使用的核心函数是glCopyTexImage2D,该函数可以将帧缓存中的颜色值复制到纹理缓存中。实验发现该函数性能较低,会拖慢整个程序的速度,为了平衡绘制质量和速度,可以只拷贝一部分像素值。实验效果如下:
图4.玻璃球
4、 雾气
OpenGL中提供了完整的雾化接口,我们只需要选择合适的雾气的混合因子、密度、颜色、起始位置等。我们的雾气设置如下:
GLfloat fogColor[4]={0.5f, 0.5f, 0.5f, 0.5f};
glFogi(GL_FOG_MODE,GL_EXP2);//模式
glFogfv(GL_FOG_COLOR,fogColor);//颜色
glFogf(GL_FOG_DENSITY, 0.004f);//密度
glFogf(GL_FOG_START, 5.0f);//开始距离
glFogf(GL_FOG_END, 300.0f);//结束距离
glHint(GL_FOG_HINT,GL_NICEST);//雾化效果
实验效果如下:
图5.雾气
5、 天空
天空包含云、星星、闪电三个对象。我们要实现的目标是:云在空中飘动、且能够遮盖住空中的星星,每间隔一段时间都会有闪电和雷声。
图6.云图 图7.二值图
为了实现云的飘动,我们在设置云图纹理坐标时添加一个变量,该变量从0递增至1,超过1时做减1操作,这样就可以模拟云的移动。如下的roll即为该变量:
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 3.0f-roll);glVertex3f(-50.0f, 100.0f, -300.0f);
glTexCoord2f(1.0f, 3.0f-roll);glVertex3f( 50.0f, 100.0f, -300.0f);
glTexCoord2f(1.0f, 0.0f-roll);glVertex3f( 50.0f, 100.0f, 300.0f);
glTexCoord2f(0.0f, 0.0f-roll);glVertex3f(-50.0f, 100.0f, 300.0f);
glEnd();
每绘制一定帧数后,我们将一幅闪电纹理图映射到天空,使用简单的相加混合,且在首次绘制闪电时调用音频播放函数,播放一段雷声,音频调用函数为PlaySound。实验效果如下:
图8.天空