基本问题
试想我们的美术做了一个3D模型,然后渲染引擎把模型渲染到屏幕上。我们还可以选定不同的视角,模拟不同的光照条件去观察模型。现在来分析一下这个过程。如果说我们把这个过程看成一个函数,那么函数的输出就是屏幕上的图像。确切地说,是屏幕上的每个像素。这个函数的主要输入是这个3D模型,我们观察的视角,光照情况等等因素。渲染过程就是给出这些因素决定每个像素值的过程。
首先来看模型。模型通常是由可视化的建模软件制作,看上去是一个“实体”。然而从计算机的角度来说,模型文件只不过是包含渲染它所需要的数据的文件。真实世界中的物体细节是无法穷尽,极其复杂的。所以我们进行简化,把物体简化成多面体,进一步的,简化成每个面是三角形的多面体。(对于曲面和细节较多的地方,我们可以用更多更小的三角面来拟合)。很显然,每个顶点的相对位置是重要的。它决定了模型的形状。由于需要描述位置,自然就有了选择坐标空间的问题。模型描述自身顶点坐标的空间称为模型空间。于是我们可以得出模型文件里一定要包含模型空间下的顶点坐标。
通常,在一个渲染的场景中,包含了多个渲染的物体。它们根据不同的位置摆放,当然会构成不同的场景。那么为了描述场景里模型之间的相对位置,我们会选择一个独立于模型的坐标空间,称为世界空间。有了世界空间,我们也可以描述观察者的位置和观察的角度。这个观察者,通常我们可以叫它为camera。这样渲染出来的结果可以视作camera所拍得的图像。要决定观察的结果,显然要知道相机的位置和朝向。另外一点需要指出的是,根据成像投影方式的不同,有正交和透视两种相机。正交相机里无法判断看到的物体离相机的远近,不存在近大远小的关系。符合实际人眼观察结果的是透视相机。物体离相机越远,所成的像就越小。
现在的问题是,给定世界空间中一个点的位置,和相机的位置朝向。我们要确定这个点最后渲染到图像上哪个位置(它或许是不可见的,这时它也应有一个超出范围的坐标)由这个问题牵扯到了shader学习中常遇见的坐标变换。首先我们关心的是点相对于相机的位置。所以自然我们可以以相机为准建立一个坐标空间来描述被观察者的位置。这个空间被称为view space,观察空间。按照OPENGL的传统,我们以相机右方为+x轴,上方为+y轴,观察方向为-z轴。由此可见观察空间是一个右手系。在相机观察的物体中,远处的物体z值更小。现在问题在于,我们怎样把world space中的坐标变换到view space中。显然这是一个仿射变换,由一次线性变换和一次平移构成。3x3的矩阵只能描述线性变换,为统一起见,引入齐次坐标。将线性变换的矩阵扩充为4x4的矩阵,添加一列(或者一行,取决于使用行向量还是列向量)用于刻画平移。现在任意两个三维坐标空间的仿射变换都可以用一个4x4的矩阵来描述。
得到了顶点在相机空间的坐标,现在需要决定这个点投影在屏幕上的坐标。首先我们先将这个坐标归一化变换到一个左手系的剪裁空间中,使得点的坐标只有三维,并且任何一个分量超出-1到1范围的点都不在可视范围内。(openGL传统,directx则是0到1)
正交相机的可视范围是坐标满足约束
-Far <= z <= -Near,
-Size <= y <= Size,
-Size*Aspect <= x <= Size*Aspect
的点。这里Size和Aspect决定了相机所看到的矩形范围,Far和Near决定了深度范围。现在我们需要通过一个变换,把三个分量的边界映射到-1和1上。由于要改变手性,我们让-Far映射到1,-Near映射到-1.
解方程组
-k * Far + b = 1,
-k * Near + b = -1.
得出系数为 k= -2 / (Far - Near), b = (Near + Far) / (Near - Far)
于是得出变换矩阵
对于透视相机,它的可视范围是一个四棱台,或者说视锥体。定义相机竖直方向的张角为FOV,水平和竖直方向可视距离之比为Aspect。它的可视约束为(注意z为负数)
tan(FOV/2)*z*Aspect <= x <= -tan(FOV/2)*z*Aspect
tan(FOV/2)*z <= y <= -tan(FOV/2)*z
-Far<= z <= -Near
这里由于x,y同时还受到z的约束,因此我们需要利用w分量做齐次化,矩阵最后一行的第三个元素应为-1.使得最后的结果中w分量和变换前的z分量互为相反数。
和上面类似,-Far映射到Far,-Near映射到-Near.
解方程组
k*(-Far) + b = Far
k*(-Near) + b = -Near 得出 k = (Far + Near) / (Near - Far) b = 2Far*Near / (Near - Far)
从而得出透视相机的变换矩阵
利用变换矩阵左乘坐标的列向量,然后利用w分量做齐次除法即可得到归一化以后的坐标(x,y,z)对于可见的点,它每一个分量都在-1到1的范围内。对x,y将其加1除以2再分别乘以屏幕的像素宽高即可得到最终渲染的像素坐标。(opengl的屏幕坐标原点在左下角)z值则是这一点的深度值。
现在我们来总结一下完成这一系列变换所需要知道的量。
模型空间下的顶点坐标----->>观察空间下的顶点坐标 这一步需要知道观察者和被观察者的相对位置关系,才能给出模型空间到观察空间的变换矩阵。显然,模型和相机在场景中摆好以后,这个矩阵自然也就知道了。
观察空间的顶点坐标 ------->>剪裁空间 这一步只需要知道相机相关的参数。深度范围Far-Near,正交相机的size,透视相机的FOV,观察的宽高比Aspect。
剪裁空间----->> 屏幕像素坐标 只需要指定好像素宽高即可。