基于 OpenGL 进行 3D 图形开发
原文地址:http://blog.chinaunix.net/space.php?uid=8210028&do=blog&id=338230分类: openGL3D 开发的一般思路转自:http://www.linuxgraphics.cn/graphics/opengl_dev_summary.html3D 开发主要分为三个步骤:建模、渲染、逻辑控制。
3D 开发的一般思路
转自:http://www.linuxgraphics.cn/graphics/opengl_dev_summary.html3D 开发主要分为三个步骤:建模、渲染、逻辑控制。
建模,指通过一些基本图元如点、线、三角形、多边形将物体画出来,一般使用 3DMax、MilkShape 等建模工具来完成。具体来说,建模涉及模型的构建、贴纹 理、制作动画等。
渲染,即使用 OpenGL 图形接口将模型在计算机上画出来。
逻辑控制,若要模型动起来,需要根据时间计算模型各个顶点的坐标,这个通过 程序逻辑来控制。
在 3D 开发中有一个常用的速度指标,即帧每秒(f/s),其计算方法如下:
fps = numFrame / Interval
- numFrame 是当前的 frame 总和,从程序运行起,每运行一个 frame 这个就加1;
- Interval 指从程序运行起的时间总和。
建模
用 3dMax 等工具建模的一般思路是:
- 用基本形状,如三角形、多边形、曲面、球体、立方体等,把物体轮廓构件出来
- 通过拉伸、调整顶点等方法对细节进行处理
建模工具有很多,如 3DMax、Maya、Blender、MilkShape、AC3D 等,其中 MilkShape 短小精悍,容易上手比较推荐新手使用。
Milkshape :建模、贴纹理、加骨骼、做动画
- mode: 做模型
- group: 构成模型的各个子部分
- material:往各个子部分上贴材质
- joint:骨骼结点,现在还没想清楚这些 joint 的作用是什么
- animation:设置关键祯,生成动画
- 生成的模型格式是 .ms3d
Milkshape 的教程可在这里下载。
保存模型有很多种格式,如 md2、ms3d、3ds、obj等。
- md2 速度快,但数据量大,典型的以空间换时间; ms3d 保存的数据量小,但需要的计算比较多
- md2 的动画可能不如 ms3d 细腻
OpenGL based Graphics
It's a state machine - Setup the stage, lighting, actors... Then draw it!
使用 OpenGL 进行渲染的总体思路
- clear the screen:glClear (XXX | XXX)
- Reset the view:glLoadIdentity
- move / rotate axis:glTranslatef (x, y, z), glRotatef ()
- draw scene: glBegin, ..., glEnd
- glDrawArray/glDrawElements 完成点、线、多边形等基本图元的绘制
贴纹理
生成纹理
OpenGL 中的纹理通过一个唯一号引用,通过函数 glBindTexture() 实现。你 可以自己指定这个唯一号,或者通过调用 glGenTextures () 函数生成一个唯一 号。
GLuint texture[1]; glGenTextures(1, &texture[0]); glBindTexture(GL_TEXTURE_2D, texture[0]); glTexImage2D(GL_TEXTURE_2D, 0, 3, sizeX, sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, pData); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); 此时 pdata 里的数据就可以释放了
纹理管理器
在实际开发时,往往需要一个纹理管理器提供纹理的装载、释放、获取等功能, 下面的文章介绍了一个纹理管理器的设计和实现。
A Singleton Texture Manager for OpenGL
如何贴纹理
以上图片来自:http://blogs.msdn.com/danlehen/
核心绘制代码如下所示:
glVertexPointer(3, GL_FLOAT, 0, m_vertices); glNormalPointer(GL_FLOAT, 0, m_normals); glBindTexture(GL_TEXTURE_2D, m_textureId); glTexCoordPointer(2, GL_FLOAT, 0, m_uvs); glDrawElements(GL_TRIANGLES, m_triangleNums * 3, GL_UNSIGNED_SHORT,m_indices); 或者 glDrawArrays(GL_TRIANGLES, 0, m_triangleNums * 3);
在嵌入式设备上的用 OpenGL ES 贴纹理,以 Androiod 为例
在 android 平台上,纹理图的长宽必须是 2 的幂,纹理图尺寸超过 512x512 时 fps 会显著降低。一般的做法是将所有纹理组合到成一张 256x256 或者 512x512 的大图。
使用纹理有两种方法,一是直接用建模工具映射好,二是通过动态调整纹理坐标 来获取需要的纹理。前者不做介绍,重点介绍下后者。
调整纹理坐标的方法有两种:一是手工修改现有纹理代码,二是通过 glMatrixMode (glTexture) 对纹理坐标进行操作。
glMatrixMode (glTexture) 调整纹理坐标的代码如下:
// move texture glMatrixMode(GL_TEXTURE); glPushMatrix(); glLoadIdentity(); // move the texture - this does not work glTranslatef(offset, 0.0f, 0.0f); glTranslatef(0.0f, offset, 0.0f); glTranslatef(0.0f, 0.0f, offset); glPopMatrix();
注意:
- 纹理坐标的偏移值范围为:-1~1
- 纹理坐标的原点在纹理图的左下角,若沿 Y 轴向下偏移,偏移量为负
- glMatrixMode (GL_TEXTURE)往往与 glMatrixMode (GL_MODELVIEW)混用,注意保存好现场。
gluPerspective 和 gluLookAt 1
函数原型如下所示:
gluLookAt(GLdoble eyex,GLdouble eyey,GLdouble eyez, GLdouble centerx,GLdouble centery,GLdouble centerz, GLdouble upx,GLdouble upy,GLdouble upz); gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar);
gluPerspective
首先得设置 gluPerspective,来看看它的参数都表示什么意思:
- fovy,可以理解成眼睛睁开的角度,即,视角的大小,如果设置为0,相当你闭上眼睛了,所以什么也看不到,如果为180,那么可以认为你的视界很广阔;
- aspect,实际窗口的纵横比,即x/y;
- zNear,表示你近处的裁面;
- zFar,表示远处的裁面。
我们知道,远处的东西看起来要小一些,近处的东西看起来会大一些,这就是透视 (perspective)原理,如下图所示:
假设那两条线表示公路,理论上讲,它们的两条边是平行的,但现实情况中,它 们在远方(可以无限远)总要相交于一点,实际线段 AB 的长度等于 CD 的长度, 只是在此例中使用了透视角,故会有如上的效果,是不是很接近现实的情况?
结合我们刚才这两个函数:
- zNear,眼睛距离近处的距离,假设为10米远,请不要设置为负值,OpenGl 就傻了,不知道怎么算了;
- zFar,表示远处的裁面,假设为1000米远;
就是这两个参数的意义了。
再解释下那个"眼睛睁开的角度"是什么意思,首先假设我们现在距离物体有50个 单位距离远的位置,在眼睛睁开角度设置为 45 时,请看大屏幕:
我们可以看到,在远处一个球;现在我们将眼睛再张开点看,将“眼睛睁开的角 度”设置为 178 (180度表示平角,那时候我们将什么也看不到,眼睛睁太大了,眼 大无神)。
我们只看到一个点,因为我们看的范围太大了,这个球本身大小没有改变,但是 它在我们的“视界”内太小了。
反之,我们将眼睛闭小些,改为 1 度看看会出现什么情况呢?
在我们距离该物体 3000 距离远,“眼睛睁开的角度”为 1 时,我们似乎走进了 这个球内,这个是不是类似于相机的焦距?
当我们将“透视角”设置为 0 时,我们相当于闭上双眼,这个世界清静了,我们 什么也看不到了。
gluLookAt
现在来看 gluLookAt 函数。
它共接受三对坐标,分别为 eye,center,up。
- eye, 表示我们眼睛在“世界坐标系”中的位置;
- center, 表示眼睛“看”的那个点的坐标;
- up,表示观察者本身的方向。
如果将观察点比喻成我们的眼睛,那么这个 up 则表示我们是正立还是倒立。从 不同角度看,所看的影像大不相同。若需要指明我们现在正立,那么 X,Z 轴为 0,Y 轴为正即可,通常将其设置为 1,只要表示一个向上的向量(方向)即可。 我们指定 0.1f 或 0.00001f 或 1000.0f,效果是一样的,只要能表示方向即可。
球是画在世界坐标系的原点上的,即 O(0,0,0) 坐标上,我们的眼睛位于观察点 A(0,0,100),Z 轴向屏幕里看去的方向为负,屏幕外我们的位置,Z 轴为正值, 其实很好理解,即我们距离原点的距离,设置 100,将观察到如下图所示的影像。
如果我们向前或向后移动,则相应的图像会变大或变小,这里其实就是运用了透 视原理,近处的物体大,远处的物体小,实际物体的大小是不变的。
同理改变 center 坐标(眼睛看去的那个点,可简单理解为视线的终点)也会影响 球的大小,同样可以认为是改变了物体与观察点的距离所致。
测试
以上理解了之后,来做一个测试。
透视图不变,最远处仍为 3000,近处为 0.1。
gluPerspective // 设置透视图 (45, // 透视角设置为 45 度,在Y方向上以角度为单位的视野 (GLfloat)x/(GLfloat)y, // 窗口的宽与高比 0.1f, // 视野透视深度:近点1.0f 3000.0f // 视野透视深度:始点0.1f远点1000.0f );
将我们的观察点置于 A(0,10,0),将观察位置(视线终点)坐标置于(0,0,0),然 后在原点开始绘图,画一个 V 字形,并将 Z 轴的值从 -1000 递增加到 +1000, 增量为10,代码如下:
glColor3f(0.5f, 0.7f, 1.0f); glBegin(GL_LINES); for(int i=-1000;i<=1000;i+=10) { glVertex3f(0,0,i); glVertex3f(10,10,i); glVertex3f(0,0,i); glVertex3f(-10,10,i); } glEnd();
效果图如下所示:
OpenGL 的坐标系统变换
OpenGL 的重要功能之一是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备上对应的位置。
每次计算屏幕输出时,OpenGL 采取这样的方式进行:
屏幕坐标点 = 3D 模型点 * 几何变换栈矩阵(n...1) * 投影变换栈矩阵 (n...1)。其中 n...1 表示从栈顶到栈底。
更复杂的流程是:
屏幕坐标点 = 3D 模型坐标 -〉 (几何变换矩阵) -〉 人眼坐标 -〉(投影变换矩阵) -〉 正则设备坐标(相对于 OpenGL 屏幕坐标原点) -〉 校正成窗口坐标(相对于窗口坐标)
简单的讲,OpenGL 中从三维场景到屏幕图形要经历如下所示的变换过程:
模型坐标-〉世界坐标-〉观察坐标-〉投影坐标-〉设备坐标
其中四种坐标经常要在程序中用到:物体坐标(也叫模型坐标、局部坐标),世界坐标,眼坐标(也叫观察坐标)和设备坐标.
物体坐标:
以物体某一点为原点而建立的“世界坐标”,该坐标系仅对该物体适用,用来简化对物体各部分坐标的描述。物体放到场景中时,各部分经历的坐标变换相同,相对位置不变,所以可视为一个整体,与人类的思维习惯一致;
世界坐标:
是OpenGL中用来描述场景的坐标,Z+轴垂直屏幕向外,X+从左到右,Y+轴从下到上,是右手笛卡尔坐标系统。我们用这个坐标系来描述物体及光 源的位置。 OpenGL 中有一个坐标转换矩阵栈 (Modeview),栈顶就是当前坐标变换矩阵,进入 OpenGL 管道的每个坐标 (齐次坐标)都会乘上这个矩阵,结果才是对应点在场景中的世界坐标。将物体放到场景中也就是将物体平移到特定位置、旋转一定角度,这些操作都是坐标变换。
眼坐标:
是以视点为原点,以视线的方向为Z+轴正方向的坐标系中的方向。OpenGL管道会将世界坐标先变换到眼坐标,然后进行裁剪,只有在视线范围(视见 体)之内的场景才会进入下一阶段的计算。 OpenGL 有个投影变换矩阵栈 (Projection),栈顶矩阵就是当前投影变换矩阵,负责将场景各坐标变换到眼坐标。由于所得到的结果是裁剪后的场景部分,称为裁剪坐标。
设备坐标:
OpenGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备上对应的位置,这个位置就称为设备坐标。在屏幕、打印机等设备上的坐标是二维坐标。
几何变换:平移、旋转、缩放
- glTranslatef (x, y, z)
- glRotatef (alpha, x, y, z);
- glScalef (x, y, z);
几何变换栈:为了维护几何变换, OpenGL 维护一个几何变换栈,每次几何变换, 都用相应的矩阵乘对应的栈顶元素,并替换掉上次的栈顶。
对于几何变换栈,有两个操作可以使用:
- glPushMatrix(): 保存当前坐标系 ,复制当前栈顶,并把复制的内容再次放到栈顶,能够保护栈上以前的内容;
- glPopMatrix(): 恢复当前坐标系。
投影变换 : 正交投影 (正射投影、平行投影),透视投影
- 正交投影:将 3D 模型平行的映射到平面上,glOrtho(xleft, xright, ybottom, ytop, znear, zfar);
- 透视投影:将 3D 模型映射到相对某个观察点的平面上, gluPerspective(fovy, aspect, znear, zfar);
深入探讨透视投影变换:http://www.cppblog.com/zmj/archive/2008/08/28/58936.html
glOrtho 函数的意义: http://flyingliang.spaces.live.com/blog/cns!9112491EC93C817B!1228.entry
正交投影的特点:
无论物体距离相机多远,投影后的物体大小尺寸不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。
透视投影的特点:离观察者越远的对象越小。
切换当前栈
- 切换当前操作的栈为投影变换栈:glMatrixMode(GL_PROJECTION);
- 切换当前操作的栈为几何变换栈:glMatrixMode(GL_MODELVIEW);
- 清除当前操作栈的内容:glLoadIdentity();
变化观察模型的角度,可以在切换到几何变换栈的时候进行:
- glMatrixMode(GL_MODELVIEW); // 模型视图矩阵
- glLoadIdentity();
- gluLookAt(6.0, 8.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // 旋转视点
一段典型的 OpenGL 代码
// Init * glClearColor (0.0f, 0.0f, 0.0f, 0.0f); /* Set background color*/ glClearDepth (1.0); glShadeModel (GL_SMOOTH); glMatrixMode (GL_PROJECTION); /* 设置投影变换 */ glLoadIdentity (); gluPerspective (......); glMatrixMode (GL_MODEVIEW); //* 清屏 * glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity (); glTranslatef (x, y, z); /* 平移 */ glRotatef (angle, x, y, z); /* 旋转 */ glBegin (GL_POLYGON); glColor3f (...); glVertex3f (...); ... ... glEnd (); /* ReSizeScene */ glViewport (0, 0, width, height); glMatrixMode (GL_PROJECTION); glLoadIdentity(); gluPerspective(... ...); glMatrixMode(GL_MODELVIEW);
How to walk around and explore the world
- move camera around and draw the 3D environment relative to the camera positon. But it is slow and hard to code.
- Rotate / translate the world in the opposite manner.
- Rotate and translate the camera position according to user commands
- Rotate the world around the origin in the opposite direction of the camera rotation (giving the illusion that the camera has been rotated)
- Translate the world in the opposite manner that the camera has been translated (again, giving the illusion that the camera has moved)
Reference
更多推荐
所有评论(0)