1、本节实现的内容
上一节我们创建一个简单的窗口,本节我们需要了解一下细节内容,同时为了方便观看,我们需要显示一个世界坐标轴,建立一个直观的三维空间。
2、我们的眼睛设定(gluPerspective函数)
上一节课,我们创建了一个简单的opengl窗口,并显示了一个简单的3d模型正方体,这节我们就要开始了解更多的细节内容。嗯,上一节课中,使用了视角的设置函数perspective,第一个参数表示视场角的大小,符合人眼的条件一般设置为40°~70°之间这个函数。在我理解,有点像是你眼睛展开的角度,比如说10度就有点像你能观测的范围只有10度,就像眯着眼睛看世界,那么他看到的物体就范围比较窄,如果你设置的是60度,那么它光视角就会更大一些,看到的范围更多。这一点可以在我们后期,从三维坐标转换到屏幕的二维坐标中体会到,说当前的这个角度是60度,在坐标转换中一个三维坐标点A如果在当前视角的屏幕以外,那么,它A点与当前屏幕正中间点的视线夹角肯定就超过了60度,我们在后期讲解三维坐标转换时会讲到。
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar)
fovy:视角的大小,相当于眼睛张开的角度。当视角为0度时,这就相当于我们的眼睛闭起来了,那就神马也看不到了。所以选择一个合适的视角尤为重要,一般40度至70度最适合,当然具体情况具体分析。
aspect:宽和高的比例,这里我们直接可以取程序窗口的宽度和高度比。
zNear:观察点与近侧裁剪平面的距离,眼睛距离近处的距离,一般选择0.01。
zFar:观察点与远侧裁剪平面的距离,眼睛远处的裁面,一般为1000,超过这个距离将被裁剪看不到。
zNear和zFar说明了对于绘制的图形,它与视线原点的距离0.01到1000之间必须是在两者之间。
3、我们看向远方的视线(gluLookAt函数)
我们还用到了一个函数gluLookAt,这个函数形象的表述了我们在三维世界里头眼睛所在的位置和朝向的位置,还有一个视角的方向。
void gluLookAt(
GLdouble eyex,GLdouble eyey,GLdouble eyez,
GLdouble centerx,GLdouble centery,GLdouble centerz,
GLdouble upx,GLdouble upy,GLdouble upz);
前两个非常的好理解,第一个参数就表示的是你眼睛的位置,第二个参数就表示你要看物体的位置坐标,当然,我们第二个参数可以不确定到具体的物体位置坐标,只要你向前看,视线上的任何一点都可以。第三个参数,要是你视线的正上方向量,根据设定角度不同,可以实现侧着头观察世界的效果。
4、通过我们的眼睛和视线探索世界
通过我们前面讲到的我们的眼睛设定(gluPerspective函数)和我们看向远方的视线(gluLookAt函数),我们就可以描述出我们观察时间的方式。还拿上节课的例子,我们通过gluPerspective将眼睛以45度的角度睁开,我的眼睛位于一个(10.0f,10.0f,10.0f)点的位置,视线朝着原点(0.0f,0.0f,0.0f)点的位置望去,这就是我们以下代码大致实现的内容。
//获取窗口大小
RECT tempClientRect;
GetClientRect(hWnd,&tempClientRect);
//初始化3D视角
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45,(float)tempClientRect.right/(float)tempClientRect.bottom,0.01f,1000.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//设置用户眼睛视角,展示壮观的三维世界从这里开始
gluLookAt(10.0f,10.0f,10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f);
说到这里,后期当我们不停地改变眼睛的位置和视线的方向,就产生了视角移动的效果,可以产生人物移动观察世界的效果,类似第一人称射击游戏或RPG游戏人物视角的效果,是不是很有感觉,我们将在后期完善相关代码,随后再详细说。
5、窗口显示比例的锁定
当我们创建了视角以后,我们发现一个问题:当我们鼠标拖动窗口改变大小时,我们刚刚显示的物体竟然变形状了,而且立方体的位置不再显示在窗口的中心。试想以下,我们如果在玩游戏,出现人物如果会随着程序窗口大小的改变而不断地被拉长或压扁,那将是个多么糟糕的事情。因此我们需要另一个函数,来保证所有的物体保持横宽比例。
OpenGL中的glViewport 函数用于定义视口(Viewport),也就是确定窗口中显示的区域。它的定义如下:
void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
x:视口的左下角X坐标。
y:视口的左下角Y坐标。
width:视口的宽度。
height:视口的高度。
glViewport函数的作用是将正投影坐标(OpenGL的默认坐标系)映射到屏幕上实际显示的区域。它将正投影坐标系中的点映射到屏幕上指定大小的矩形区域内。这里我们将的glViewport(0,0,tempClientRect.right,tempClientRect.bottom)就时将整个程序窗口平面设定为显示区域,那么配合着gluPerspective中设定的平面横纵比为(float)tempClientRect.right/(float)tempClientRect.bottom,我们就可以保持整个世界的横纵比例保持一致,不至于出现拉长、压扁的情况了。
//获取窗口大小
RECT tempClientRect;
GetClientRect(hWnd,&tempClientRect);
//重置视窗设置
glViewport(0,0,tempClientRect.right,tempClientRect.bottom);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
在使用OpenGL进行绘图时,我们通常需要先通过glViewport来设置视口,将整个窗口或窗口的一部分作为渲染区域。这样,我们可以指定绘制的内容在窗口的哪个位置显示出来。通常情况下,我们将glViewport放置到窗口改变的消息处理函数中即可,但如果后期需要显示多个glViewport视口,就需要改变glViewport的设置位置。
大家还记不记得魔兽争霸3游戏界面里的人物选中头像界面,在二维平面相框中显示出来三维的头像,就可以个用glViewport实现出来,我们将在后面的程序中实现这一个功能,敬请期待。
6、我们需要一个三维坐标轴
接下来,我们看到的立方体是在一个黑色窗口里面,仿佛悬浮在空中一样,没有参照物,我们要创建一个可以看见的三维坐标轴方便观察。大家需要明确的一点是openGL中世界坐标系(World Coordinates)是右手坐标系,在二维屏幕上,屏幕水平方向是x 轴方向,向右为正,屏幕竖起方向是Y轴方向,向上为正,垂直于屏幕的方向是Z轴方向,从屏幕里往外为正。
我们首先去生成一个坐标轴,我们需要使用的画线操作。OpenGL的绘图的函数很多,OpenGL的绘图必须在在glBegin()和glEnd()函数之间完成,这里简单介绍基本几何绘图函数。
GL_POINTS:单个顶点集
GL_LINES :多组双顶点线段
GL_POLYGON: 单个简单填充凸多边形
GL_TRAINGLES:多组独立填充三角形
GL_QUADS:多组独立填充四边形
GL_LINE_STRIP: 不闭合折线
GL_LINE_LOOP: 闭合折线
GL_TRAINGLE_STRIP: 线型连续填充三角形串
GL_TRAINGLE_FAN: 扇形连续填充三角形串
GL_QUAD_STRIP: 连续填充四边形串
具体的使用方法上图一目了然。这个图我已经收藏了,用的时候超方便。
有了这些方法,我们就可以采用其中的GL_LINES (多组双顶点线段)来画我们的三维坐标轴了。这里我们用红色的线表示x轴,绿色的线表示y轴,蓝色线表示z轴。箭头方向的表示对应轴线的正值方向,另外一边就是负值方向。
//显示坐标轴
if(true)
{
//设置线的宽度
glLineWidth(3.0f);
//显示红色的X坐标轴
glColor3f(1.0f,0.0f,0.0f);
glBegin(GL_LINES);
glVertex3f(-10.0f,0.0f,0.0f);
glVertex3f(+10.0f,0.0f,0.0f);
glEnd();
//显示绿色的Y坐标轴
glColor3f(0.0f,1.0f,0.0f);
glBegin(GL_LINES);
glVertex3f(0.0f,-10.0f,0.0f);
glVertex3f(0.0f,+10.0f,0.0f);
glEnd();
//显示蓝色的Z坐标轴
glColor3f(0.0f,0.0f,1.0f);
glBegin(GL_LINES);
glVertex3f(0.0f,0.0f,-10.0f);
glVertex3f(0.0f,0.0f,+10.0f);
glEnd();
}
添加以上代码后,我们就可以看到一个简陋的坐标轴了。
7、添加坐标轴的方向箭头
我们画出了坐标轴,但是没有方向箭头,总感觉少点什么。我么还是把方向箭头显示出来,这样我们就可以明确的感知到各个方向的正方向和负方向的区别了。
这里我们箭头使用了前面正方体类似的系统函数,系统还提供了类似四面体、正八面体、正十二面体、正二十面体、球体、圆环体、茶壶等多个基础函数,方便用户使用。
//显示一个椎体
glutSolidCone(0.5,1.0,30,30);
当然,系统提供的这些函数画出的立体图形只有默认的方向,如果用户想调整立体图形的方向和位置,就要用到转换矩阵相关函数。我们这里简单介绍一下:
void glTranslatef(GLfloat x,GLfloat y,GLfloat z);
函数功能:简单的理解,就是物体的位置移动。参数x,y,z分别指定沿x,y,z轴方向的平移分量。其作用就是将你绘点坐标的原点在当前原点的基础上平移一个(x,y,z)向量。
void glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z);
函数功能:简单的理解,就物体的旋转。先解释一下旋转方向,做(0,0,0)到(x,y,z)的向量,方向满足右手定则,用右手握住这条向量,大拇指指向向量的正方向,四指环绕的方向就是旋转的方向。以点(0,0,0)到点(x,y,z)为轴,旋转angle角度。
接下来,我们就可以使用glTranslatef和glRotatef函数对圆锥箭头进行位置和角度的调整。在Opengl中存在大量的位置和角度的调整,后期还会详细了解以上操作函数。
//显示坐标轴箭头
glPushMatrix();
glColor3f(1,0,0);
glTranslatef(10,0,0);
glRotatef(90,0.0f,1.0f,0.0f);
glutSolidCone(0.5,1.0,30,30);
glPopMatrix();
//显示坐标轴箭头
glPushMatrix();
glColor3f(0,1,0);
glTranslatef(0,10,0);
glRotatef(-90,1.0f,0.0f,0.0f);
glutSolidCone(0.5,1.0,30,30);
glPopMatrix();
//显示坐标轴箭头
glPushMatrix();
glColor3f(0,0,1);
glTranslatef(0,0,10);
glRotatef(0,1.0f,0.0f,0.0f);
glutSolidCone(0.5,1.0,30,30);
glPopMatrix();