OpenGL坐标转换过程
之前我们已经提到在OpenGL中,所有物体都是在一个3D空间里的,但是屏幕都是2D像素数组,所以OpenGL会把3D坐标转变为适应屏幕的2D像素,最终投射到2D的屏幕上去。所以对于每一个顶点坐标都会依次进行model、view、projection三种变换。这三种变换实现代码如下:
代码语言:txt复制gl_Position = projection * view * model * vec4(position.xyz, 1);
简单来说就是gl_Position就是我们的每一个3维坐标经过model、view、projection三种变换的得出在标准化设备坐标系(Normalized Device Coordinates, NDC)中的坐标,可以回顾一下前文提到的每个坐标系之间关系如下图,
实际上model、view、projection这三种变换,分别是通过左乘一个矩阵来完成的。总的来说在OpenGL体现中,如果要实现3D物体的运动实际上是每个顶点的位置改变,而顶点的位置改变则是通过矩阵乘法来实现的。
代码中的vec4(position.xyz, 1)表示顶点在本地坐标系中的坐标(是一个四维的齐次坐标)。它左边乘上model矩阵,就得到了该顶点在世界坐标系中的坐标。这个model变换可能包含了缩放、旋转、平移(这三种变换。然后,世界坐标系中的坐标再左乘一个view矩阵,就变换到了相机坐标系。最后,再左乘projection矩阵。也就是说上面代码projection, view ,model分别对应着一种矩阵。
平移矩阵的推导过程
我们前文一直在说顶点位置的变换,3D对象的本地坐标经过一个model变换,就变换到成了世界坐标。不同的对象经过各自的model变换之后,就都位于同一个世界坐标系中了,它们的世界坐标就能表达各自的相对位置。一般来说,model变换又包含三种可能的变换:缩放、旋转、平移。而顶点的缩放、旋转、平移是通过顶点坐标和矩阵乘法来实现的,那么这个矩阵是怎么确定的呢,我们可以从线性代数的基础理论上进行一下了解。
如果我们要对顶点坐一个平移,我们最先可能会想到通过向量的平移来实现,先从二维开始分析,如下图
我们建立了一个直角坐标系,添加向量overrightarrow{OP} ,它是一个有大小和方向的量。把它表示在坐标系里的时候,起点在原点O,终点指向点P。这个向量的坐标和点P的坐标一样,都可以记为(1,2)。因为的向量有一个性质,是向量与起点位置无关,也就是说,一个向量向任意方向平移之后不变。比如,在上图中,向量 overrightarrow{OP} 平移之后得到向量overrightarrow{AQ},平移前后它们的大小和方向都一样,所以它们表示相同的向量。所以,平移后的向量overrightarrow{AQ} 也是用以坐标(1,2)来表示。也就是说向量平移后坐标不变,那么我们要对顶点进行平移变换,就不能直接通过对一个向量的平移来得到。
那么我们必须换一种方法来实现顶点的平移,比如通过向量加法来完成。见下图。
图中A点平移到B点,相当于做一个向量加法:overrightarrow{OA} overrightarrow{AB} =overrightarrow{OB} 我们看到,在直角坐标系中,向量加法满足三角形法则。其中向量 overrightarrow{AB} 代表了平移的大小和方向,称为平移向量。因为向量平移大小和方向不变,overrightarrow{AB}和向量 overrightarrow{OA'} 相等,能看出它的坐标是(0.5,1)。向量 overrightarrow{OA} 加上这样的一个平移向量,相当于把点A沿x轴平移0.5个单位,并沿y轴平移1个单位,这样就平移到了点B的位置。
前面图中是2维向量的例子,现在我们扩展到3维,假设顶点坐标是(x,y,z)将平移变换用向量坐标来表示,如下:
left[ begin{matrix} x \ y \ z \ end{matrix} right] left[ begin{matrix} P_x \ P_y \ P_z \ end{matrix} right]=left[ begin{matrix} x P_x \ y P_x \ z P_x \ end{matrix} right] ,其中的left[ begin{matrix} P_x \ P_y \ P_z \ end{matrix} right] 就代表前面提到的平移向量的值。
在线性代数中,一个变换通常使用矩阵的乘法来表达。而且OpenGL 使用GPU来进行运算,GPU对于矩阵乘法有着非常高效的算法。我们也希望这里的平移变换能用矩阵乘法(具体说是左乘)来表达。我们设想一个3x3的矩阵A,让它乘上顶点的3维坐标:
A left[ begin{matrix} x \ y \ z \ end{matrix} right]=left[ begin{matrix} a_{11} & a_{12} & a_{13} \ a_{21} & a_{22} & a_{23} \ a_{31} & a_{32} & a_{33} \ end{matrix} right]left[ begin{matrix} x \ y \ z \ end{matrix} right]=left[ begin{matrix} a_{11}x & a_{12}y & a_{13}z \ a_{21}x & a_{22}y & a_{23}z \ a_{31}x & a_{32}y & a_{33}z \ end{matrix} right]
我们发现,无论矩阵A的各个元素取什么样的值,我们只能得到x,y,z的线性组合,而怎么样也得不到类似前面向量加法的结果形式(x,y,z分别加上一个常数)。为了解决这个问题,我们将3维的顶点坐标换成4维的齐次坐标。所谓齐次坐标,就是在3维坐标的基础上,加上第4个维度,并把它的值设成1。也就是说,3维坐标 left[ begin{matrix} x \ y \ z \ end{matrix} right]变成齐次坐标就是:left[ begin{matrix} x \ y \ z \ 1 \ end{matrix} right]
当然,齐次坐标的第4个元素,也可以不是1,不过这种情况我们暂时用不到,现在我们可以简单的认为,齐次坐标就是多了第4个维度,并且它是一个固定的1。多出来的这个1只要在需要的时候把它去掉,我们就能得到原来的3维坐标。实际上,在OpenGL ES中,我们总是以4维的齐次坐标来表示顶点坐标。所以上文代码中vertex shader程序中的 vec4(position.xyz, 1)
代码语言:txt复制gl_Position = projection * view * model * vec4(position.xyz, 1);
vec4(position.xyz,1)就是一个齐次坐标。
这样一个4维的顶点坐标经过左乘一个矩阵,得到的结果也是一个4维的顶点坐标(仍然是个齐次坐标)。这个矩阵需要是4X4的。根据矩阵乘法的定义,现在我们很容易拼出一个能表示平移的矩阵来:left[ begin{matrix} 1 & 0 & 0 & P_{x} \ 1 & 0 & 0 & P_{y} \ 1 & 0 & 0 & P_{z} \ 1 & 0 & 0 & 1 \ end{matrix} right]left[ begin{matrix} x \ y \ z \ 1 \ end{matrix} right]=left[ begin{matrix} P_{x} x \ P_{y} y \ P_{z} z \ 1 \ end{matrix} right]
其中矩阵:left[ begin{matrix} 1 & 0 & 0 & P_{x} \ 1 & 0 & 0 & P_{y} \ 1 & 0 & 0 & P_{z} \ 1 & 0 & 0 & 1 \ end{matrix} right]就是我们要推导的,它是4x4平移矩阵。其中Px,Py,Pz元素恰好是平移向量,使得顶点坐标
经过矩阵乘法之后得到了向量加法的结果形式left[ begin{matrix} P_{x} x \ P_{y} y \ P_{z} z \ 1 \ end{matrix} right]。
缩放矩阵的推导过程
上图表示的是一个2维向量的缩放过程。向量 overrightarrow{OP} 在x和y方向上都放大了1.5倍就得到了向量overrightarrow{OP_{1}},记作(3,1.5)。也就是一个2维向量的放大和缩小就是自身对应的坐标在x轴和y轴大小的放大和缩小。同理一个顶点在3维空间的放大和缩小则是在3维空间顶点自身坐标(x,y,z)也放大和缩小相同的倍数。还是以上图2维向量为例,向量 overrightarrow{OP} 在x方向上缩小为原来的0.5倍,在y方向上放大为原来的2倍,就得到了向量 overrightarrow{OP_{2}} ,坐标从(2,1)变换成了(1,2),这也是一种缩放变换。
在3维空间,假设我们把顶点坐标(x,y,z)用4维的齐次坐标表示(x,y,z,1)各个维度的坐标分别放大或缩小一个倍数对应指为
S_x,S_y,S_z,1则可以用left[ begin{matrix} S_{x}x \ S_{y}y \ S_{z}z \ 1 \ end{matrix} right]表示。缩放操作用矩阵乘法可以写成: left[ begin{matrix} S_{x} & 0 & 0 & 0 \ 0 & S_{y} & 0 & 0 \ 0 & 0 & S_{z} & 0 \ 0 & 0 & 0 & 1 \ end{matrix} right]left[ begin{matrix} x \ y \ z \ 1 \ end{matrix} right]=left[ begin{matrix} S_{x}x \ S_{y}y \ S_{z}z \ 1 \ end{matrix} right]上面这个式子意思就是,一个向量的x,y,z坐标经过缩放变换之后,分别变成了原来Sx,Sy,Sz倍。而式子中左乘的这个4x4的矩阵,就是我们要推导的缩放矩阵:left[ begin{matrix} S_{x} & 0 & 0 & 0 \ 0 & S_{y} & 0 & 0 \ 0 & 0 & S_{z} & 0 \ 0 & 0 & 0 & 1 \ end{matrix} right] 。
小结
以上两种矩阵推算过程只是OpenGL 众多矩阵变换中的两种,是为了举例说明顶点坐标变换的思维过程,让初学者容易触摸到入门的门槛。有兴趣的同学,可以再在这基础上作更深入详细的研究。