一、变换的概念
1.1 图形流水线
1.2 变换(Transformation)
变换主要分为四个步骤,主要就是在Vertex operations阶段操作顶点信息,会在流水线中依次进行。
- 几何变换
- 投影变换
- 裁剪
- 视口变换
三维模型到二维图形的主要变换过程,注意各种窗口的区别。
- 投影窗口:是一个虚拟的窗口,与具体的硬件设备无关。
- 视口变换:视口与相关的硬件设备有关。
1.3 OpenGL中变换相关的函数
- 几何变换:glTranslate、glRotate、glScale,这些函数只是计算一个变换矩阵,然后作为参数设置到图形流水线中。
- 投影:glFrustum()、gluPerspective()、glOrtho();
- 视口变换:glFrustum()、gluPerspective()、glOrtho(),glViewport();
1.4 几何变换
1.4.1 平移
点P(x,y,z)平移到 P’(x’,y’,z’),平移向量为T(∆x,∆y,∆z)
x’ = x + ∆x;
y’ = y + ∆y;
z’ = z + ∆z;
表达为向量形式为:P’ = P + T;
表达为矩阵形式:
但是上述矩阵表达并不完美,因此可以将其进行齐次化,使得矩阵的表达结构都变得统一,OpenGL中采用4 x 4 的矩阵来表示。
OpenGL中,在调用平移函数glTranslatef(∆x,∆y,∆z)时,就构造了一个平移矩阵。
1.4.2 缩放
点P(x,y,z)经过缩放得到点P’( x’,y’,z’) ,即乘以一个缩放因子:
x’ = x * SxS_xSx;
y’ = y * SyS_ySy;
z’ = z * SzS_zSz;
表达为向量形式:P’ = P * S,表达为矩阵形式,并齐次化。
OpenGL中,在调用平移函数glScalef(SxS_xSx,SyS_ySy,SzS_zSz)时,就构造了一个缩放矩阵。
缩放中心在原点(0,0,0),若仅缩放图形的大小,不改变图形的位置,即:
可以采用组合的方式来实现,即先平移到缩放中心,在缩放,再平移到原来的位置。
glTranslatef(- xpx_pxp,- ypy_pyp,- zpz_pzp)
glScalef(SxS_xSx,SyS_ySy,SzS_zSz)
glTranslatef(xpx_pxp,ypy_pyp,zpz_pzp)
1.4.3 旋转
点P绕z轴逆时针选旋转 α 角:
P(x,y)、P(x’,y’)点的极坐标形式为:
则可以把x’,y’,z’表示出来:
将其表达为矩阵形式:
同理,可以推导出沿y轴和沿x轴的旋转:
实现沿任意向量(AxA_xAx、AyA_yAy、AzA_zAz)的旋转:
在OpenGL中,构建了一个新的坐标系统Oxyz‾\overline{xyz}xyz,该坐标系以向量(AxA_xAx、AyA_yAy、AzA_zAz)为z轴,让后将顶点从Oxyz坐标中变换到Oxyz‾\overline{xyz}xyz中,直观理解为点的位置不变,但在不同坐标系中的坐标不一样,再绕z轴旋转,最后将旋转后的顶点从Oxyz‾\overline{xyz}xyz坐标系变换到Oxyz坐标系中。
- 坐标系Oxyz →\rightarrow→ Oxyz‾\overline{xyz}xyz :矩阵A;
- 旋转;
- 坐标系Oxyz‾\overline{xyz}xyz →\rightarrow→ Oxyz :矩阵ATA^TAT(转置矩阵),实际上应该为A−1A^{-1}A−1(逆矩阵),因为A是一个正交矩阵,因此A−1A^{-1}A−1 == ATA^TAT;
glRotate(angle,x,y,z);
1.5 采用矩阵形式来表达几何变换,可以方便把各种变换组合起来,也可以减少计算量,最终只需要一个矩阵作用与顶点数据。
在OpenGL中,当前模型的变换矩阵为:M,调用glTranslatef()函数时,会生成一个矩阵T,让后将矩阵T右乘于矩阵M,即:
M’ = M * T;
当在调用glRotatef()函数时,会生成一个R矩阵,右乘于矩阵 M’:
M’’ = M’ * R;
当最终将矩阵作用于顶点P(x,y,z)时,为:
M’’ * P = (M * T * R ) * P;
1.6 在1.5中,我们注意到,顶点P位于矩阵 M’'的右边,可以看出,P点是先旋转,再平移,再进行M变换,为什么矩阵是写在原矩阵的右边,而最终的矩阵要写在顶点P的左边呢?后面我们将进行解答。
1.7 矩阵的应用
图元P1、P2经过了变换T1、T2、… 、Tn,OpenGL中是先进行一系列的变换矩阵的处理,最后再作用于图元(点,三角形,三角形带,四边形等都称为Primitive)。
glLoadIdentity(); //初始化矩阵M为单位矩阵,M = I;Tranformation T1;...Tranformation Tn; //变换,M = I * T1 * ... * Tn;Primitive P1;Primitive P2;
P1经过变换T1、T2;P2经过变换T3、T4;OpenGL中基本的逻辑顺序为:
glLoadIdentity(); //初始化矩阵M为单位矩阵,M = I;Tranformation T1;Tranformation T2; //变换,M = I * T1 *T2;Primitive P1;
glLoadIdentity(); //初始化矩阵M为单位矩阵,M = ITranformation T3;Tranformation T4; //变换,M = I * T3 *T4;Primitive P2;
二、矩阵的管理
2.1 OpenGL中采用堆栈来管理矩阵,主要采用** glPushMatrix() 和 glPopMatrix() **函数。
- 假设P1、P2有相同的变换TcT_cTc;P1有经过了变换T1、T2;P2经过了变换T3、T4,则伪码为:
glLoadIdentity();Transformation Tc;
glPushMatrix();Transformation T1;Transformation T2;Primitive P1;
glPopMatrix();
glPushMatrix();Transformation T3;Transformation T4;Primitive P2;
glPopMatrix();
glPushMatrix()起到了保护环境的作用;glPopMatrix()起到了恢复环境的作用,两者一般配合起来使用,起到了隔离的作用,中间的部分不会对 glPushMatrix()和glPopMatrix()之外的操作产生影响。
glPushMatrix();glTranslatef(-1.0, 0.0, 0.0);glRotatef((GLfloat)shoulder,0.0,0.0,1.0);glTranslatef(1.0, 0.0, 0.0);glPushMatrix();glScalef(2.0,0.4,1.0);glutWireCube(1.0);glPopMatrix();glPushMatrix();glScalef(1.0,0.4,1.0);glRotatef((GLfloat)elbow,0.0,0.0,1.0);glTranslatef(1.0, 0.0, 0.0);glScalef(2.0,0.4,1.0);glutWireCube(1.0);glPopMatrix();
glPopMatrix();
2.2 相关操作对栈中矩阵的影响
-
glLoadIdentity(): 使栈顶矩阵为单位矩阵;
-
glTranslate*(),glRotate*(),glScale*():栈顶矩阵左乘于变换矩阵;
-
glPushMatrix():将栈顶矩阵复制一份,入栈。
-
glPopMatrix() : 退栈,恢复到Push之前的状态;
三、模型变换与视点变换
3.1 Model-View Transformation
视点不变。变换模型位置;OpenGL默认的视点位置在原点,视线方向为z轴负方向。
3.2 View transformation
物体不变,变换视点位置,如漫游功能。
两种变换是可以统一的,也可以相互转化。在OpenGL中有各种堆栈,有的用来管理模型变换与视点变换,有的用来管理投影变换,有的用来管理纹理变换。 glMatrixMode(GL_MODELVIEW)用来设置操作的堆栈,即管理模型与视点变换的堆栈。因为模型变换与视点变换是相对的,在OpenGL中,我们可以认为视点总是不变的,所有的变换都是模型变换。
3.3 gluLookAt()函数
void glutLookAt(eyex,eyey,eyez,centerx,centery,centerz,upx,upy,upz),可以用来实现漫游功能,即视点变换,通过指定摄像机的视线方向,中心点和上方向,就可以实现漫游功能。
glTranslate()、glRotate()、glScale()都可以理解为视点不动,变换模型,而gluLookAt()是模拟物体不动,变换视点的控制效果。本质上两者都一样,gluLookAt()函数也是计算了一个变换矩阵。
四、全局变换与局部变换
4.1 Global transformation:全局坐标系变换模式,固定坐标系模型,图形模式;如下变换,物体的局部坐标也随着物体改变;
4.2 Local transformation : 局部坐标系变换模式,活动坐标系模式,空间模式。如下变换模式,物体在局部坐标系中变换。第一种采用与全局变换顺序一样的矩阵进行局部变换;第二种与全局坐标相反的矩阵进行局部变换。
4.3 两种变换方式产生的效果完全不一样,但是两种方法之间又具有联系,将局部变换的顺序调整一下,即可产生与全局变换完全一样的效果。
4.4 局部坐标系的变换顺序的逆变换即为全局坐标系的变换,全局坐标系的变换顺序的逆变换即为局部坐标系变换。
- 如果只有依次变换(旋转、平移、缩放),那么全局变换和局部变换效果一样;
- 多种变换的组合时,全局变换与局部变换效果一般不同;
- 局部变换正好相当于组合顺序相反的全局变换;
4.5 局部变换
局部变换在子模型相对于父模型的变换中很有用,可以很方便的进行变换,而全局变换则比较复杂;在OpenGL中,单次的变换是全局变换,而组合的多次变换则是局部变换。
4.6 例子
glLoadIdentity();
glTranslatef(10.0f,0.0f,0.0f);
glRotatef(45.0f,0.0f,0.0f,1.0f);glBegin(GL_QUADS);
...
glEnd();
生成的矩阵:
最终作用于顶点:
通过变换的过程可以理解:代码里面是先平移、再旋转、而实际作用于顶点时,将矩阵放在了点的左边,是先旋转,再平移,这就是局部变换的逆变换就是全局变换的含义。
注意:上述矩阵采用的都是行优先,矩阵都是乘再原矩阵的右边,而再OpenGL中采用的是列矩阵,因此新产生的矩阵总是乘再原矩阵的左边。
附录:中国大学Mooc,图形编程技术,北京林业大学,杨刚