久违了,我的blog。这几天被透视投影卡住了,虽然到现在也没有完全搞明白,但也还是应该阶段性总结一下。
3D图像在2D平面显示要经过以下几个步骤,
1. 数据配置环节,配置顶点、颜色等数据,确定了物体的大小和外观。
2. 指定模型视点变换参数,将物体到3D场景中,并确定我们的观察点,也就是人眼的位置。
3. 指定投影变换参数,确定投影方式和方向。
4. 指定视口变换参数,将投影结果映射至视口(2D显示平面)。
以上4步都属于配置动作,分别用于修改OpenGL状态机中的不同配置参数(顶点数组,变换矩阵等),真正触发OpenGL开始工作的是类似glDrawElements这样的API。记住,OpenGL就是一堆数据配置和动作。
2,3,4中涉及的各种变换都是通过矩阵来表示的(这里有太多数学知识,再单独总结吧),和各种数据缓存不同的是,整个OpenGL似乎只有一个矩阵堆栈,各种变换是共享的,所以还需要一个状态量来表示当前矩阵是用于做什么变换的。这个状态量通过glMatrixMode来指定,参数可以是GL_PROJECTION, GL_MODELVIEW等等。
1. 模型视点变换
模型视点变换,亦可分别称为视点变换、模型变换,但由于两者关系比较密切,所以一般会连在一起说。为什么两者关系密切呢?因为很多情况下,“人的观察点不变,改变物体的位置”(模型变换)与“物体不变,改变人的位置”(视点变换)可以达到同样的效果。
模型变换相关的函数有,glRotate*(旋转),glTranslate*(平移),glScale*(缩放)等等。。
对于视点变换OpenGL并没有提供专门的函数(这更体现了模型视点变换的互换性),但在glu里提供了gluLookAt函数。它有三组参数,分别表示人眼所处的位置(eyex, eyey, eyez),人眼所观察的位置(或者说所聚焦的位置,两点一线,(centerx, centery, centerz)),以及眼睛与颈部所形成的向量(upx, upy, upz)。
拿拍摄来比喻,仅改变第一组参数表示相机绕物体移动,仅改变第二组参数表示相机原地转动(在xoz平面),仅改变第三组参数表示相机旋转(在xoy平面)。如果仍然不能理解,建议去看Nate Robin OpenGL教程中的projection演示。
2. 投影变换
投影(projection)的目的是将3D图形转换至2D平面显示。OpenGL提供了正交投影(orthogonal projection)和透视投影(perspective projection)两种投影方式。正交的另一种说法是垂直(数学没学好...),其投影结果不会改变物体的大小。透视投影和人眼的视觉模型,或者说小孔成像比较类似,投影之后远处的东西看上去会小一些。因此,绝大多数情况下使用的是透视投影。
透视投影的模型定义了一个视景体,这是一个平截头体(倒放的棱台),所有在这个视景体内的物体才会被显示出来。由于平截头体的底面比顶面要大,所以可以理解为,底面经过投影之后缩小成顶面般的大小,也就是人眼模型所要的效果。至于位于视景体内的物体是经过怎样的运算而最终变成2D平面图像的,这是一个内部算法的问题,如开篇所说我还没有完全掌握......
OpenGL提供了glFrustum接口来实现透视投影,这个API完全是以视景体模型来定义的,如果觉得用起来不太直接,还有一个glu的接口 gluPerspective。
3. 视口变换
视口可以理解为最终在显示器上显示的大小,虽然一般来说就是OpenGL窗口的大小,但理论上两者并不一定要相同。视口大小通过glViewport接口来指定。注意每当窗口大小发生改变时都需要重新指定。同时也要注意透视投影的输出与视口大小之间的关系,gluPerspective有个参数是宽高比,通常该值应该等于视口的宽高比,否则会造成图像被缩放。
想要真正掌握这些概念,还需要亲自实践,写段代码来体会一下。
static void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(3, GL_FLOAT, 0, colors);
glVertexPointer(3, GL_INT, 0, vetices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, front);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, end);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, left);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, right);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, top);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, bot);
glFlush();
}
static void reshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, (GLfloat)w/(GLfloat)h, 0.1f, 100);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//眼睛在(-5, 0, 5),盯着(0, 0, 0)点,身体朝向正上方与y轴平行
gluLookAt(-5, 0, 5, 0, 0, 0, 0, 1, 0);
}
一些心得:
1. 试着调整一下gluLookAt的参数,看看结果是否与你想像中的一致。
2. 可以在display中把x,y,z轴画出来,以辅助理解
3. 看不到图像,有可能是不在视线范围内,也有可能是离得太近,甚至还有可能是颜色填错了,这些心得只有靠自己在实践中去总结,才能变成自己的财富。
4. 别人的教程写得再好,只能缩短你学习的过程,但永远也不能替代自己动手这一环节。