文章目录
观察视图
显示器本身是一个平面的、固定的,二维矩形区域,模型却是一个三维空间的几何体。将三维空间的模型投影到二维的关键方法,就是齐次坐标的应用、矩阵乘法的线性变换,以及视口映射。
相机模型
常见的视图变换操作可以类比为使用照相机拍摄照片的过程。
步骤如下:
- 将相机移动到准备拍摄的位置,将它对准某个方向(视图变换,view transform)。
- 将准备拍摄的对象移动到场景中必要的位置上(模型变换,model transform)。
- 设置相机的焦距,或者调整缩放比例(投影变换,projection transform)。
- 拍摄照片(应用变换结果)。
- 对结果图像进行拉伸或者挤压,将它变换到需要的图片大小(视口变换,viewport transform)。对于3D图形来说,这里同样需要对深度信息进行拉伸或者挤压(深度范围的缩放)。
第1步和第2步合并为一个模型-视图变换(model-view transform),这一合并过程的主要特性,就是构建一个独立的、统一的空间系统,将场景中所有的物体都变换到视图空间,或者人眼空间(eye space)当中。
下图为OpenGL中的坐标系统,坐标系统均使用左侧的信息来表达,中间的盒子表示从一个坐标变换到另一个的过程,右侧给出了不同过程中的数据单位。
从图中可以看到整个观察矩阵堆栈中包含的所有内容,最后一步就是设置OpenGL的视口和深度范围。OpenGL得到的最终坐标是归一化之后的齐次坐标,并且将进行裁减、剪切和光栅化的操作。也就是说,最后要绘制的坐标在[-1.0,1.0]的范围内,直到OpenGL对它们进行缩放以匹配视口大小。
视椎体
操作相机的第3步,设置焦距或者缩放的数值,这其实就是相机拍摄场景中,设置取景用的矩形椎体的宽度或者窄度。只有落于椎体内的几何体才会出现在最终图像上。同时,还计算用于透视投影的"近大远小"效果的相关参数(齐次坐标的第四个坐标值w)。
OpenGL可以去除过近或者是过远的几何体,因此,需要在增加两个平面,并且与已有的视景椎体的四个平面相交。这6个平面会定义出一个平截椎体(frustum)形状的观察范围。
视椎体的剪切
如果某个图元落在组成视椎体的四个平面之外,那么它将不会被绘制(它将被裁减,cull),因为此时这个图元已经落在矩形的显示区域之外。
如果一个图元正好穿过某个平面,OpenGL将会对此图元进行剪切(clip),会计算几何体与平面的交集,将落入视椎体范围内的形状进行计算后生成新的几何体。
正交视图模型
有时并不需要透视形式的窗口,而是采用正交(orthographic)投影的方式。这种投影方式常见于建筑设计图和计算机辅助设计的领域,主要作用是在投影之后依然保持物体的真实大小以及相互之间的角度。
可以简单地通过忽略x、y、z三个坐标轴中的一个来实现这一效果,也就是其余两个构成二维坐标。
用户变换
以上所述的视图模型的每个步骤都可以通过一次控件变换来表达,它们全部都是线性变换方式,可以通过齐次坐标的矩阵乘法来完成。
齐次坐标
进行变换的几何体本身就是三维形式,将三维的笛卡尔坐标转换为四维的齐次坐标,有两个优点:其一,可以进一步完成透视变换;其二,这样可以使用线性变换来实现模型的平移,使用了四维坐标系统,就可以通过矩阵乘法完成所有的旋转、平移、缩放和投影变换操作。
齐次坐标总是有一个额外的分量,并且如果所有的分量都除以一个相同的值,那么将不会改变它所表达的坐标位置。
举例来说,以下所有的坐标都表达了同一个点:
( 2.0 , 3.0 , 5.0 , 1.0 ) ( 4.0 , 6.0 , 10.0 , 2.0 ) ( 0.2 , 0.3 , 0.5 , 0.1 ) (2.0,3.0,5.0,1.0)\\ (4.0,6.0,10.0,2.0)\\ (0.2,0.3,0.5,0.1) (2.0,3.0,5.0,1.0)(4.0,6.0,10.0,2.0)(0.2,0.3,0.5,0.1)
一般直接添加第四个w分量,并设置值为1.0来实现齐次坐标的建立;使用第四个分量除以所有的分量,并且将其舍弃,以重新获得笛卡尔坐标。
当OpenGL准备显示几何体的时候,它会使用最后一个分量除以前三个分量,从而获得齐次坐标重新变换到三维的笛卡尔坐标。因此距离更远的物体(w值更大)的笛卡尔坐标也会更小,从而绘制的比例也会更小。
ω的零值和负值会带来较差的效果,因此要保证w值总是正数。
线性变换与矩阵
为了将数据映射到设备坐标系当中,首先对三维的笛卡尔坐标添加第四个分量,并且设置值为1.0,从而构建了齐次坐标。这些坐标通过与一个或多个4x4矩阵的乘法运算来表达旋转、缩放、平移以及透视投影的变换过程。
平移
例如,沿x轴正方向平移2.5
( x + 2.5 y z 1.0 ) = ( 1.0 0.0 0.0 2.5 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 ) ( x y z 1.0 ) \begin{pmatrix} x + 2.5 \\ y \\ z \\ 1.0 \end{pmatrix}= \begin{pmatrix} 1.0 & 0.0 & 0.0 & 2.5 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \end{pmatrix} \begin{pmatrix} x \\ y \\ z \\ 1.0 \end{pmatrix}
x+2.5yz1.0
=
1.00.00.00.00.01.00.00.00.00.01.00.02.50.00.01.0
xyz1.0
如果要使用工具库构建一个平移矩阵,可以调用:
vmath::mat4 vmath::translate(float x,float y,float z);
返回一个平移矩阵,平移距离为(x,y,z)。
示例:
#include "vmath.h"
//构建一个变换矩阵,将坐标平移(1,2,3)
vmath::mat4 translationMatrix = vmath::translate(1.0,2.0,3.0);
//将这个矩阵传递给当前的着色器程序
glUniformMatrix4fv(matrix_loc,1,GL_FALSE,translationMatrix);
缩放
例如,将几何体放大原来大小的3倍
( 3 x 3 y 3 z 1.0 ) = ( 3.0 0.0 0.0 0.0 0.0 3.0 0.0 0.0 0.0 0.0 3.0 0.0 0.0 0.0 0.0 1.0 ) ( x y z 1.0 ) \begin{pmatrix} 3x \\ 3y \\ 3z \\ 1.0 \end{pmatrix}= \begin{pmatrix} 3.0 & 0.0 & 0.0 & 0.0 \\ 0.0 & 3.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & 3.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \end{pmatrix} \begin{pmatrix} x \\ y \\ z \\ 1.0 \end{pmatrix}
3x3y3z1.0
=
3.00.00.00.00.03.00.00.00.00.03.00.00.00.00.01.0
xyz1.0
注意,因为缩放值是针对每个分量的,所以很容易做到非同型的缩放变换,但是在设置模型和视图变换的时候很少这么做。这是因为,如果需要将结果进行垂直或水平的拉伸,可以在视图变换完成之后进行。过早进行缩放会导致物体在旋转的时候变形。
注意,进行缩放时,并不会影响w值,否则会由于齐次坐标本身的特性(最后所有的分量都会除以w),结果不会发生改变。
如果物体缩放时,其中心没有处于(0,0,0)点,那么矩阵在缩放的同时也会将物体远离或者靠近(0,0,0)。如果需要改变一个偏移中心的物体大小,并且不希望它的位置同时发生改变,那么我们可以将物体中心移动到(0,0,0),然后再缩放大小,最后平移回到原来的位置。
M = T − 1 S T v ′ = M v M = T^{-1}ST \\ v' = Mv M=T−1STv′=Mv
使用工具库完成缩放,可以使用函数:
vmath::mat4 vmath::scale(float s);
返回一个缩放倍数为s的变换矩阵。
示例:
//C++ 程序代码
#include "vmath.h"
// 将平移和缩放变换进行合并
vmath::mat4 translateMatrix = vmath::translate(1.0,2.0,3.0);
vmath::mat4 scaleMatrix = vmath::scale(5.0);
vmath::mat4 scaleTranslateMatrix = scaleMatrix * translateMatrix;
旋转
-
绕Z轴旋转
R z = ( c o s θ − s i n θ 0.0 0.0 s i n θ c o s θ