OpenGL 中场景进行变换,要经历一些过程:视图变换 à 模型变换 à 投影变换,然后到了窗口坐标。这几个变换开始的时候把我搞很混,这几天整理一下。
一般书上把这几个变换用照相机类比,其实每个变换都是产生着一个 4x4 矩阵,然后与当前矩阵 (Current Matrix) 相乘,得到一个坐标变换矩阵,最后把世界坐标系(欧式空间)中的物体变换到屏幕坐标系中。这里梳理一下概念:
1 、视图变换( VIEW Transformation ):它类似将照相机指向物体,即确定视点(观察点)的位置和观察方向。一般用的函数为 glu 封装的函数:
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, --------- 观察点
GLdouble centrex, GLdouble centrey, GLdouble centrez, -- 视线方向:从 eye 指向 centre
GLdouble upx, GLdouble upy, GLdouble upz ------------ 视图体自下而上的方向
)
这个函数会产生一个视图矩阵,并右乘到当前矩阵上。模型变换通常发生在模型变换之前。其实,视图变换也是通过平移和旋转得到的,观察位置与物体位置之间是个相对的状态,我们也把视图变换和模型变换统一成一个变换,产生一个矩阵:模型视图变换矩阵。
2 、模型变换 (MODEL Transformation) :它确定模型的位置和方向,对模型进行旋转、平移和缩放。用到三个子函数:glTranslate*(x, y, z) 、 glRotate*(x, y, z) 、 glScale*(x, y, z) 。每个函数都会产生一个矩阵,并右乘当前矩阵。
3 、投影变换( PROJECTION Transformation ):产生一个六面的视图体,把视图体以外的场景剪裁掉,把视图体内的物体、场景作为绘制对象,让“照相机拍摄”。两种投影方式,两个投影函数: glFrustum(left, right, bottom, top, near, far) ,
glOrtho(left, right, bottom, top, near, far )
这两个函数的参数非常对称,都是构筑了一个六面体,形成可视范围。它们都产生一个矩阵,并左乘当前矩阵。(当然,还有 glu的两个函数)。
要理解整个过程,关键在理解当前变换矩阵 CTM ,(简称为 C )。它是一个状态概念,应用到 OpenGL 流水线中的每一个定点: P = C*P’ 。这条等式是对同一个点在两个坐标系体统之间进行转换,从右边的坐标系下的坐标( P’ )转换到左边的坐标系下的坐标( P )。而矩阵 C 是 4x4 的齐次坐标矩阵,它都蕴含着一个局部坐标系信息:以右边坐标系为参考坐标系统,左边坐标系的位置和方向。
用手工定义一个矩阵,如下(按 OpenGL 矩阵方式定义。与数学定义矩阵的方式转置):
CTM[16] = { a0, a1, a2, a3, // x 轴的方向向量
a4, a5, a6, a7, // y 轴的方向向量
a8, a9, a10, a11, // z 轴的方向向量
a12, a13, a14, a15 // 原点的位置
}
再来考察当前变换矩阵 CTM ,它是在 OpenGL 流水线中一个模型视图矩阵和一个投影矩阵的复合。 CTM = P*C*M 。(注意到上面提到的左乘右乘了吗?)
我们来分析一个简单的例子:
1 #define NUM 0.70710678118654746 2 // 注意这个矩阵是正交的,没有正交就用,好像有放缩作用 GLfloat Tmat1[16] = { NUM, NUM, 0.0, 0.0, -NUM, NUM, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0};
GLfloat Tmat2[16] = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, }; 7 void setupRC( void ) 8 { 9 glClearColor( 0.0f , 0.0f , 0.0f , 1.0f );10 glShadeModel(GL_FLAT);11 } 12 13 void RenderScene( void )14 { 15 printf( " RenderScene\n " ); 16 glClear(GL_COLOR_BUFFER_BIT);17 glColor3f( 0.0f , 1.0f , 1.0f );18 glMatrixMode(GL_MODELVIEW);19 glLoadIdentity();20 gluLookAt( 0.0 , 0.0 , 5.0 , // view point 21 0.0 , 0.0 , 0.0 , // focus point 22 0.0 , 1.0 , 0.0 ); // up vector 23 24 glutSolidCube( 0.5 ); // 原点的参考位置 25 glMultMatrixf(Tmatr1); // 这个矩阵的动作和下面的两个变换是一样的。 glMultMatrixf(Tmatr2);26 // glRotatef(45.0, 0.0, 0.0, 1.0);27 // glTranslatef(3.0, 0.0, 0.0); 28 glutSolidCube( 1.0 );29 30 glutSwapBuffers();31 } 32 33 void ChangeSize( int w, int h)34 { 35 printf( " ChangeSize\n " ); // 从这里看出,是先调用ChangeSize()的36 GLfloat nRange = 10.0f ;37 38 if (h == 0 )39 h = 1 ;40 GLfloat fRatio = (GLfloat)w / (GLfloat)h;41 42 glMatrixMode(GL_PROJECTION);43 glLoadIdentity();44 45 if (w <= h)46 glOrtho( - nRange, nRange, - nRange / fRatio, nRange / fRatio, 1.0 , nRange);47 else 48 glOrtho( - nRange * fRatio, nRange * fRatio, - nRange, nRange, 1.0 , nRange);49 50 glViewport( 0 , 0 , w ,h);51 52 glMatrixMode(GL_MODELVIEW);53 glLoadIdentity();54 } 55 56 int main( int argc, char * argv[])57 { 58 glutInit( & argc, argv);59 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);60 glutInitWindowSize( 800 , 600 );61 glutCreateWindow( " example " );62 63 glutReshapeFunc(ChangeSize);64 glutDisplayFunc(RenderScene);65 66 setupRC();67 68 glutMainLoop();69 70 std::cout << " Hello world! " << std::endl;71 return 0 ;72 } 73
对 glRotatef(45.0, 0.0, 0.0, 1.0)
glTranslatef(3.0, 0.0, 0.0);
这两个变换,可以看成:
glMultMatrixf(R);
glMultMatrixf(T);
R,T 都是右乘到 CTM : CTM = CTM * R * T
对模型变换的理解有两种:
1、在全局固定坐标系下,对物体进行变换。这时候,我们要以相反的顺序来考虑代码中的变换函数了,它的实际过程是这样 P = CTM *( R*(T* p’)) 。
首先、对物体进行平移,平移到坐标( 3.0, 0.0, 0.0 )。 然后,把物体相对原点绕z轴旋转45度。
2、物体捆绑在局部坐标系下,所有的变换都是坐标系进行的。这时,我们用顺序来看这个变换。
glRotatef(45.0, 0.0, 0.0, 1.0) 产生一个齐次矩阵 R( 这可是代表一个局部坐标系哦 ) ,即局部坐标系 R 相对刚才开始的坐标系 I (单位矩阵)作了旋转变换,绕旋转了45度。
glTranslatef(3.0, 0.0, 0.0) 产生一个齐次矩阵 T (也是代表了一个局部坐标系),相对 R 坐标系沿x轴( R 系)平移了3个单位,得到了自己的局部坐标系 T 。
最后在这个局部坐标系 T 下画了 Cube 。
代码中的 Tmat = R * T ,它也是从 T 坐标系变换到 R 坐标系,再变换到最后的模型视图的世界坐标系。
后注:
对坐标系的几何变换是 “ 既采用基于齐次坐标的矩阵表达形式 ! 又在欧氏几何的 Cartesian 坐标系下以对其进行说明性的定义 . 由于齐次坐标是射影几何的语言工具 ! 前者表明几何变换的表达是基于射影几何的后者则带有欧氏几何色彩 ” ,所以对它的表述清晰统一的表述比较难。本文写的也比较零散,有语焉不详、理解错误指出,请多多指正!