在顶点着色器和可编程图形管线几个阶段中最重要的一个工作是:原始顶点(比如三角形)从原始坐标系(也就是在3D建模工具中指定的)向屏幕坐标系的转换。然而可编程的顶点着色器允许多种方法实现顶点的变换,一些变换通常在顶点着色器之后的固定函数阶段执行。当开始编写顶点着色器程序,特别重要地是理解在顶点着色器中哪种变换会被执行。这些变换通常以统一的参数指定,并且通过矩阵向量乘法应用到输入顶点位置和法向量上。虽然对于点和方向来说这是直接的,但对于正常向量来说并不那么直接,如在“应用矩阵变换”一节中所讨论的那样。
这里,我们将会首先呈现坐标系统的全貌以及它们之间的转换,然后再讨论各自的转换。
概述:照相机类比
照相机类比: 1. 放置模型, 2. 放置照相机, 3. 调整变焦, 4. 裁剪图象
根据上图说明的照相机类比,考虑一下顶点转换的整个过程还是非常有用的。转换的步骤以及相应的顶点转换如下所示:
- 放置模型 – 模型变换
- 放置摄像机 – 观察变换
- 调整变焦 – 投影变换
- 裁剪图像 – 视口变换
前三个变换应用在顶点着色器中。透视除法(可以认为是投影变换的一部分)会在顶点着色之后被自动应用到固定函数阶段。视口变换同样会在固定函数阶段被自动应用。然而固定函数阶段的变换不能被改变,其它变换可以用其它这里描述的变换代替。但是,了解一下传统的变换还是很有用的,因为它们允许充分裁剪以及对不同变量进行透视校正。
以下的概览展示了在不同坐标系之间的顶点变换顺序以及涵盖了代表变换的矩阵:
物体/模型坐标系
↓模型变换:模型矩阵
世界坐标系
↓
观察/视坐标系
↓
裁剪坐标系
↓
归一化的设备坐标
↓
视口变换
↓
屏幕/窗口坐标系
注意模型、观察、投影变换是在顶点着色器中被应用的。透视变换和视口变换是在顶点着色之后的固定函数阶段被应用的。接下来的章节会详细地讨论这些变换。
模型变换
模型变换是指变换从物体坐标(也叫模型坐标或局部坐标)到世界坐标系。物体坐标通常特定于每个物体或模型,并且经常是由3D建模工具指定的。另一方面,对于场景中的物体来说世界坐标是一个通用的坐标系统,它包括灯光资源、3D音效资源等等其它资源。既然不同的物体有不同的坐标系统,那么模型变换也是不同的;也就是说,不同的模型变换会被应用到不同的物体上。
模型矩阵的结构
模型变换可以由一个4×4的矩阵来表示,我们将其表示为模型矩阵,它的结构如下:
A是一个3×3的矩阵,代表3D空间的线性变换。它包含了任何旋转、缩放以及其它比较少见的线性变换的组合。t是一个3D向量,代表3D空间的平移(即位移)。把A和t结合成一个4×4矩阵。从数学上讲,模型变换表示一个仿射变换:一个有平移的线性变换。为了使这个生效,所有的三维点由第四个坐标为1的四维向量来表示:
当我们把这个矩阵乘经点P,三维线性变换和平移的组合就出现在如下的结果中:
除了第四个坐标(对于一个点来说它的值是1),结果等价于:
在顶点着色器中访问模型矩阵
模型矩阵可以定义为一个统一的参数,使得它能够在顶点着色器中使用。但是,它通常会跟观察变换的矩阵组合起来形成模型观察矩阵,随后会被设置为一个统一的参数。在一些API中,这个矩阵可作为内置的统一参数。(可参考章节“应用矩阵变换”。)
计算模型变换
严格说来,因为模型变换是以统一参数的形式在顶点着色器中提供的,所以Cg程序员没有必要担心它的计算。实际上,渲染引擎、场景图和游戏引擎通常会提供模型矩阵;因此,编写顶点着色器的程序员没有必要担心如何计算模型矩阵。但是,在某种情况下,当开发图形应用时模型矩阵的计算还是有必要的。
The 4×4 matrix representing the translation by a vector t {\displaystyle =(t_{1},t_{2},t_{3})} {\displaystyle =(t_{1},t_{2},t_{3})} is:
模型矩阵通常是通过组合物体的初等变换的4×4矩阵来计算的,特别是平移、旋转和缩放。具体来说,在场景层次图的情况下,对象的所有父组(父、祖父等)的变换组合成模型矩阵。让我们来看一下最重要的初等变换以及它们的矩阵。
下面的4×4矩阵通过向量来表示平移:
下面的4×4矩阵通过延x轴的因子,延y轴的因子
,延z轴的因子
来表示缩放:
下面的4×4矩阵通过标准轴(x,y,z)的角度表示旋转:
关于特定轴旋转的特殊情况可以轻易地推导出。例如,实现欧拉角的旋转是很有必要的。但是关于欧拉角有多重约定,我们这里就不讨论了。
一个归一化的四元数对应于角度为
的旋转。旋转轴的方向通过归一化3D向量
来决定。
进一步的初等变换是存在的,但是跟模型矩阵的计算关系比较小。这些转换的4×4 矩阵是通过矩阵乘法组合起来的。假设矩阵,
和
通过特定的顺序指定到一个物体上。(
可能表示从物体坐标到父坐标系统的转换,
表示父集合向祖父集合的转换,
表示祖父集合向世界坐标的转换。)组合矩阵的乘法如下所示:
注意矩阵因子的顺序是很重要的,也要注意矩阵乘法应该从右(向量被乘的地方)向左阅读。比如首先被应用
在最后被应用。
观察变换
观察变换相当于放置和定向摄像机(或者观察者的眼睛)。但是,理解观察转换最好的方法就是它会把世界坐标转换到摄像机的观察坐标系(也就是视点坐标系)中,摄像机被放置在坐标系的原点,在OpenGL中点(按惯例)指向z轴负方向,而在Direct3D中是指向z轴正方向的,并且是被放置在xz平面上的,也就是说向上的方向是由y轴的正向来决定的。
在顶点着色器中访问观察矩阵
跟模型变换相似,观察变换是由一个4×4矩阵来表示的,这个矩阵被称作观察矩阵,在顶点着色器中它可以由一个统一的参数来定义;但是我们通常是用模型矩阵
来组合形成模型观察矩阵
。因为模型矩阵是第一个被应用的,正确的组合如下所示:
(可以参考章节“应用矩阵转换”)
计算观察矩阵
类似于模型矩阵,Cg程序员并不需要担心观察矩阵的计算,因为它会以一个统一参数的形式提供给顶点着色器。但是,当开发图形应用时,有时还是有必要来计算观察矩阵的。
这里我们简单概述一下观察矩阵是如何以摄像机的位置t,观察方向d,以及世界向上的向量k(所有的坐标都是在世界坐标系中)来计算的。此处我们以OpenGL的右手坐标系为准,摄像机是指向z轴负向的。(Direct3D中正好相反。)步骤很明确:
1.计算(在世界坐标系中)负的归一化的向量d作为观察坐标系z轴的方向z:
- 计算(同样是在世界坐标系中)观察坐标系x轴的方向x:
- 计算(还是在世界坐标系中)观察坐标系y轴的方向y:
使用向量x,y,z和t,逆观察矩阵很容易定下来,因为这个矩阵把原点(0,0,0)映射到t以及把单位向量(1,0,0)、(0,1,0)、(0,0,1)映射到x、y、z。因此后面的向量必须是矩阵
中的列:
但是我们需要矩阵,因此我们必须计算它的逆矩阵。注意矩阵
有如下的形式:
它有一个3×3的矩阵R和一个3D向量t,逆矩阵是:
因为在这种特殊情况下矩阵R是正交(因为它的列向量是归一化的并且互相正交)的,所以R的逆矩阵正好是它的转置矩阵,也就是第四步就是如下计算:
虽然以上结果的推导需要一些线性代数的知识,但是最终的计算只需要基础的向量和矩阵操作并且可以用任何常见的编程语言编写。
透视变换和透视除法
首先,投影变换决定投影的种类,比如透视或正交。透视投影对应于线性透视的缩短法(译者注:艺术家对物体近大远小的处理一般称之为缩短法),而正交投影就是没有缩短法的正交投影。缩短法实际上是通过透视除法来实现的;但是,所有控制透视投影的参数是在透视变换中设置的。
从技术上来说,透视变换就是把观察坐标转换到裁剪坐标。(在场景可视区域外面的所有基础图元部分会在裁剪坐标中被裁剪掉。)在顶点着色器中,在顶点以语义为SV_POSITION的输出参数返回之前,透视变换应该是应用到顶点上的最后一个变换了。这些裁剪坐标随后就通过透视除法转换成归一化的设备坐标,它是所有分量除以第四个坐标。(之所以叫归一化的设备坐标是因为场景中所有可见部分的点值在-1和+1之间。)
在顶点着色器中访问投影矩阵
跟模型变换和观察变换类似,投影变换也是由一个被称为投影矩阵的4×4矩阵来表示的。在顶点着色器中通常被定义为一个统一参数。
计算投影矩阵
类似于模型观察矩阵,Cg程序员并不需要担心投影矩阵的计算。但是,当开发应用时,有时还是有必要计算投影矩阵的。
这里,我们介绍一下投影矩阵的三种情况(遵循OpenGL的惯例:在观察坐标中摄像机指向z轴负向):
- 标准透视投影(对应于OpenGL 2.x中的函数gluPerspective)
- 斜透视投影(对应于OpenGL 2.x中的函数glFrustum)
- 正交投影(对应于OpenGL 2.x中的函数glOrtho)、
标准透视投影的特征如下:
- 如上图所示,角度指定了延y轴方向的观察范围,
- 如下图所示,n表示到近裁剪平面的距离,f表示到远裁剪平面的距离,
- 在近裁剪平面上中心矩形的宽度到高度的长宽比a。
视点和裁剪平面以及这个中心矩形一起定义了视锥体,也就是3D空间的可视区域是由投影变换指定的。所有在视锥体外的图元都会被裁剪掉。由于深度值以有限精度存储,所以近和远裁剪平面是必须的;因此不可能覆盖一个无限大的视锥体。
利用参数、a、n以及f,投影矩阵
可以表示为:
斜透视投影的特征如下:
- 跟标准透视投影一样,n和f表示到裁剪平面的距离,
- 如下图所示坐标r(右),l(左),t(上)和b(下),这些坐标定义了视锥体前面矩形的位置;因此,更多的视锥体(比如中心点外的)可以由横纵比α与视域角度来描述。
给定参数n、f、r、l、t和b,斜透视投影矩阵计算如下:
正交投影
没有缩短法的正交投影如下图所示。这里的参数跟在斜透视投影中用到的参数是一样的,但是视锥体(更精确地说是视景体)由简单的箱体替代了被截断的金字塔。
给定参数n、f、r、l、t和b,正交投影矩阵计算如下:
视口变换
投影变换是把观察坐标映射为裁剪坐标,随后会通过除以裁剪坐标的第四个分量的透视除法映射为归一化的设备坐标。在归一化的设备坐标(NDC)中,视景体通常是一个值在-1到1之间并且中心在坐标原点的箱体。如上图所示,这个箱体随后会通过视口(屏幕渲染的矩形)变换映射为屏幕坐标(也叫窗口坐标)。这个映射的参数就是视口左下方的和
以及宽度
和高度
,还有深度
和裁剪平面的距离
。(深度值的范围是-1到1之间)。
在 OpenGL 和 OpenGL ES中,这些参数在两个函数中设置:
视口变换矩阵不是非常重要,因为它在固定函数阶段会自动完成。但是完美一点的话还是写一下吧:
延伸阅读
传统的顶点变换同样在Nvida的Cg教程第4章中有少量描述。传统OpenGL变换的详细描述可以参考Khronos OpenGL 网站上提供的“OpenGL 4.1 兼容性简介详述”中的章节2.12。