拍摄三步

渲染要把三维场景变成二维图像,可以对应现实中的拍照过程。

第一步是摆好场景,把每个物体从自己的局部坐标系放到统一的世界坐标系,这对应模型变换 Model Transformation。第二步是安排相机的位置、观察方向和画面上方的方向,这对应视图变换 View Transformation。第三步是把相机看到的三维空间投影到二维成像平面,这对应投影变换 Projection Transformation。

三者合称 MVP 变换。对局部坐标中的齐次点 plocal\mathbf p_{local},裁剪空间坐标为

pclip=MprojMviewMmodelplocal.\mathbf p_{clip} =M_{proj}M_{view}M_{model}\mathbf p_{local}.

列向量从右向左接受变换,所以模型矩阵先作用,视图矩阵随后作用,投影矩阵最后作用。投影矩阵输出的四维坐标仍然保留齐次分量,透视除法和视口变换在后面完成。

相机参数

相机需要三个要素:位置 e\mathbf e、观察方向 g\mathbf g 和向上参考方向 t\mathbf t。位置决定相机在哪里,观察方向决定镜头朝哪里,向上方向决定画面绕视线轴的旋转角度。

只给位置和观察方向还不够。相机可以绕视线自转,同一个物体会在画面中发生整体倾斜;向上参考向量锁定了这个自由度。当 t\mathbf t 与 g\mathbf g 平行时,叉积无法构造侧向,说明相机姿态退化,工程实现需要拒绝或修正这种输入。

本篇采用右手相机坐标系:相机位于原点,沿 −z-z 方向观察,+y+y 是画面上方,+x+x 指向画面右侧。令

w=−g∥g∥,u=t×w∥t×w∥,v=w×u.\mathbf w=-\frac{\mathbf g}{\lVert\mathbf g\rVert}, \qquad \mathbf u=\frac{\mathbf t\times\mathbf w} {\lVert\mathbf t\times\mathbf w\rVert}, \qquad \mathbf v=\mathbf w\times\mathbf u.

其中 w\mathbf w 指向相机后方,u\mathbf u 是相机右方,v\mathbf v 是经过正交化后的实际向上方向。这里的构造本质上是用叉积和归一化把用户提供的向上方向改造成标准正交基。

相机位置、观察方向与向上方向

空心圆标出相机位置,从这里指向物体的箭头确定观察方向;上方箭头确定画面朝上方向。保持前两者不变、改变向上方向,会让图像绕视线转动,这也是相机还需要第三个参数的原因。

固定相机

相机与整个场景一起平移或旋转时,相对位置和相对朝向没有变化,照片也不会变化。因此推导视图矩阵时,可以把相机固定在标准姿态,让场景施加相反的变换:先把相机位置移到原点,再把相机的三个轴对齐到 +x,+y,+z+x,+y,+z。

相机移动与世界施加逆变换在画面中产生等价的相对运动

左侧表示相机和物体共同移动,中间表示把两者移动后的相对关系保持不变,右侧表示把相机固定到原点的标准姿态。三个画面相同,说明“移动相机”和“对世界施加逆变换”是等价的观察操作。

视图矩阵

先求相机从标准姿态移动到世界中目标姿态的矩阵更容易。相机坐标到世界坐标的齐次矩阵把 u,v,w\mathbf u,\mathbf v,\mathbf w 作为列向量,把相机位置 e\mathbf e 作为平移列:

Mcam=(uxvxwxexuyvywyeyuzvzwzez0001).M_{cam}=\begin{pmatrix} u_x&v_x&w_x&e_x\\ u_y&v_y&w_y&e_y\\ u_z&v_z&w_z&e_z\\ 0&0&0&1 \end{pmatrix}.

它把相机空间的三个标准轴分别送到世界中的 u,v,w\mathbf u,\mathbf v,\mathbf w,把相机原点送到 e\mathbf e。真正需要的是相反方向的世界到相机变换:

Mview=Mcam−1.M_{view}=M_{cam}^{-1}.

旋转部分是正交矩阵,因此逆可以用转置求出。视图矩阵可以写成先反向平移、再按相机基向量投影:

Mview=RviewT(−e),M_{view}=R_{view}T(-\mathbf e),

Rview=(uxuyuz0vxvyvz0wxwywz00001).R_{view}=\begin{pmatrix} u_x&u_y&u_z&0\\ v_x&v_y&v_z&0\\ w_x&w_y&w_z&0\\ 0&0&0&1 \end{pmatrix}.

对于世界坐标中的点 p\mathbf p,视图坐标前三个分量也可直接写成

pview=(u⋅(p−e)v⋅(p−e)w⋅(p−e)).\mathbf p_{view}=\begin{pmatrix} \mathbf u\cdot(\mathbf p-\mathbf e)\\ \mathbf v\cdot(\mathbf p-\mathbf e)\\ \mathbf w\cdot(\mathbf p-\mathbf e) \end{pmatrix}.

这说明视图变换的本质是:先以相机位置为新的测量起点,再把位移投影到相机的右、上、后三个轴上。模型矩阵和视图矩阵都作用在顶点上,常常预先合并为 MviewMmodelM_{view}M_{model},减少顶点处理时的重复计算。

把相机固定在标准姿态还有一个符号后果:可见物体通常位于相机空间的负 z 半轴,近平面深度虽然“更近”,数值却比远平面更大。后续所有近远边界、裁剪和深度映射的正负号都必须服从这个约定,不能一边使用负 z 相机,一边套用正 z 深度公式。

正交投影

正交投影 Orthographic Projection 的投影线互相平行,物体大小不随深度变化,适合工程制图和需要保持平行关系的场景。相机坐标下的可见区域用长方体表示:

[l,r]×[b,t]×[f,n].[l,r]\times[b,t]\times[f,n].

这里采用相机沿 −z-z 方向观察的约定,因此近裁剪面和远裁剪面都是负数,且

f<n<0.f<n<0.

这里 nn 是较近的深度,ff 是较远的深度。如果输入的是正的距离 dn,dfd_n,d_f,要转换为 n=−dn,f=−dfn=-d_n,f=-d_f。

一种直观理解是把所有点的 z 坐标丢掉,只留下 x、y;但这样会丢失遮挡所需的深度。正规管线保留三个坐标,把整个长方体映射到规范立方体 [−1,1]3[-1,1]^3,让深度可以交给后续的深度测试。

先将长方体中心

c=(l+r2,b+t2,n+f2)\mathbf c=\left(\frac{l+r}{2},\frac{b+t}{2},\frac{n+f}{2}\right)

平移到原点,再按三个方向的跨度缩放到长度 2:

Mortho=SorthoTorthoM_{ortho}=S_{ortho}T_{ortho}

其中

Sortho=(2r−l00002t−b00002n−f00001),S_{ortho}=\begin{pmatrix} \frac2{r-l}&0&0&0\\ 0&\frac2{t-b}&0&0\\ 0&0&\frac2{n-f}&0\\ 0&0&0&1 \end{pmatrix},

Tortho=(100−r+l2010−t+b2001−n+f20001).T_{ortho}=\begin{pmatrix} 1&0&0&-\frac{r+l}{2}\\ 0&1&0&-\frac{t+b}{2}\\ 0&0&1&-\frac{n+f}{2}\\ 0&0&0&1 \end{pmatrix}.

相乘后得到

Mortho=(2r−l00−r+lr−l02t−b0−t+bt−b002n−f−n+fn−f0001).M_{ortho}=\begin{pmatrix} \frac2{r-l}&0&0&-\frac{r+l}{r-l}\\ 0&\frac2{t-b}&0&-\frac{t+b}{t-b}\\ 0&0&\frac2{n-f}&-\frac{n+f}{n-f}\\ 0&0&0&1 \end{pmatrix}.

代入边界可以验证:l↦−1,r↦1l\mapsto-1,r\mapsto1,b↦−1,t↦1b\mapsto-1,t\mapsto1,f↦−1,n↦1f\mapsto-1,n\mapsto1。

当可见区域不是正方形时,x、y 方向的归一化尺度可能不同。规范空间中的图像暂时出现拉伸并不表示物体真的变形;视口变换会把它映射到与屏幕宽高一致的区域。规范立方体的好处是裁剪规则与设备分辨率解耦,硬件可以先用统一边界处理所有投影结果。

正交观察体积经过平移和缩放进入规范空间

左侧长方体用六个边界标出可见范围,中间将中心移到原点,右侧将三个方向的跨度缩放为 2。平移负责消除中心偏移,缩放负责统一尺寸,两步分别对应矩阵的最后一列和对角项。

透视投影

透视投影 Perspective Projection 的投影线从相机一点发散,具有近大远小的特征。三维平行线投影到一般画面后可能汇聚到消失点;正交投影的平行投影线则保持平行。透视投影的可见区域是一个被近、远裁剪面截断的四棱锥,称为视锥体或平截头体。

三维到二维的管线中,投影矩阵先输出裁剪坐标 (xc,yc,zc,wc)T(x_c,y_c,z_c,w_c)^T。裁剪要在透视除法之前完成:完全在视锥外的图元可以丢弃,跨越裁剪面的图元需要在四维裁剪空间中生成新的顶点并插值属性。若先除以 wcw_c,穿过相机平面的边可能在除法后翻到画面的另一侧,造成错误的拓扑和属性插值。

对采用正 wcw_c 的裁剪约定,典型边界是 −wc≤xc,yc,zc≤wc-w_c\le x_c,y_c,z_c\le w_c;这里推导的负 wcw_c 版本需要相应翻转不等式。不同图形 API 还可能使用 z∈[0,1]z\in[0,1] 而不是 [−1,1][-1,1],应把投影矩阵、裁剪规则和深度映射作为一套约定一起使用。

在这里的负 wcw_c 约定下,wc<0w_c<0,同一组裁剪条件可写成

wc≤xc,yc,zc≤−wc.w_c\le x_c,y_c,z_c\le -w_c.

若把整个投影矩阵乘以 −1-1,可得到 wc>0w_c>0 的等价齐次代表元,再使用常见的 −wc≤xc,yc,zc≤wc-w_c\le x_c,y_c,z_c\le w_c 形式;这只是裁剪约定的改写,不能顺带改变坐标系手性。

透视矩阵可以拆成两步:先把平截头体压缩成一个长方体,再复用正交投影矩阵把长方体映射到规范立方体。

把透视视锥压缩为长方体

压缩需要满足三条几何条件:近平面上的点完全不动;远平面在横向收缩到与近平面相同的大小,但深度仍保持 ff;远平面中心仍位于中心轴上。设相机空间点为 P=(x,y,z)TP=(x,y,z)^T,其视线与近平面 z=nz=n 相交于 P′=(x′,y′,n)TP'=(x',y',n)^T。相似三角形给出

x′n=xz,y′n=yz,\frac{x'}n=\frac{x}z, \qquad \frac{y'}n=\frac{y}z,

因此

x′=nxz,y′=nyz.x'=\frac{nx}{z}, \qquad y'=\frac{ny}{z}.

由于 n,zn,z 都是负数,比例 n/zn/z 为正;∣z∣|z| 增大时,横向坐标的绝对值变小,这就是远小近大的来源。

齐次压缩

普通三维线性变换不能直接产生 x/zx/z、y/zy/z 这种除法。齐次坐标允许先把结果写成与原点成比例的四维代表元,最后统一做透视除法。输入 (x,y,z,1)T(x,y,z,1)^T 乘以压缩矩阵后,要求第一、第二、第四分量分别含有 nx,ny,znx,ny,z:

Mpersp→ortho=(n0000n0000AB0010).M_{persp\rightarrow ortho}= \begin{pmatrix} n&0&0&0\\ 0&n&0&0\\ 0&0&A&B\\ 0&0&1&0 \end{pmatrix}.

经过矩阵变换,齐次向量为

(nxnyAz+Bz).\begin{pmatrix}nx\\ny\\Az+B\\z\end{pmatrix}.

透视除法后,横向坐标已经是 nx/z,ny/znx/z,ny/z,深度为

z′=Az+Bz.z'=\frac{Az+B}{z}.

从四维裁剪坐标到 NDC 的步骤是

(xndc,yndc,zndc)=(xcwc,ycwc,zcwc).(x_{ndc},y_{ndc},z_{ndc}) =\left(\frac{x_c}{w_c},\frac{y_c}{w_c},\frac{z_c}{w_c}\right).

这一步才真正产生近大远小。齐次向量整体乘一个非零常数不会改变 NDC,因为分子和分母同时缩放;但 wcw_c 的符号仍会影响裁剪判定,因此矩阵整体乘 −1 虽不改变几何 NDC,却会改变适用的裁剪不等式。

要求近平面和远平面的深度在除法后保持不变:

An+Bn=n,Af+Bf=f.\frac{An+B}{n}=n, \qquad \frac{Af+B}{f}=f.

等价于

An+B=n2,Af+B=f2.An+B=n^2, \qquad Af+B=f^2.

解得

A=n+f,B=−nf.A=n+f, \qquad B=-nf.

所以

Mpersp→ortho=(n0000n0000n+f−nf0010).M_{persp\rightarrow ortho}= \begin{pmatrix} n&0&0&0\\ 0&n&0&0\\ 0&0&n+f&-nf\\ 0&0&1&0 \end{pmatrix}.

把它和正交投影相乘,得到一般平截头体的透视矩阵:

Mpersp=MorthoMpersp→ortho=(2nr−l0−r+lr−l002nt−b−t+bt−b000n+fn−f−2nfn−f0010).M_{persp}=M_{ortho}M_{persp\rightarrow ortho} = \begin{pmatrix} \frac{2n}{r-l}&0&-\frac{r+l}{r-l}&0\\ 0&\frac{2n}{t-b}&-\frac{t+b}{t-b}&0\\ 0&0&\frac{n+f}{n-f}&-\frac{2nf}{n-f}\\ 0&0&1&0 \end{pmatrix}.

矩阵右侧的压缩先作用,左侧的正交归一化后作用。对称视锥满足 l=−r,b=−tl=-r,b=-t,矩阵中的横向偏移项消失:

Mpersp=(nr0000nt0000n+fn−f−2nfn−f0010).M_{persp}=\begin{pmatrix} \frac nr&0&0&0\\ 0&\frac nt&0&0\\ 0&0&\frac{n+f}{n-f}&-\frac{2nf}{n-f}\\ 0&0&1&0 \end{pmatrix}.

这里的推导产生 wc=z<0w_c=z<0。有些 API 采用 wc>0w_c>0 或不同的 NDC 深度范围,可以通过整体符号和深度行调整适配,但这属于接口约定,不应把某个 API 的手性简单归结为左手或右手。

深度映射

压缩后的相机空间深度为

z′=(n+f)−nfz.z'=(n+f)-\frac{nf}{z}.

正交归一化后,NDC 深度为

zndc=n+f−2nfzn−f.z_{ndc}=\frac{n+f-\frac{2nf}{z}}{n-f}.

代入边界可得 z=n↦1z=n\mapsto1、z=f↦−1z=f\mapsto-1。若窗口深度约定是“越小越近”,可以使用

d=1−zndc2,d=\frac{1-z_{ndc}}2,

于是近平面对应 0,远平面对应 1。

透视深度不是相机空间深度的线性映射:

dzndcdz=2nf(n−f)z2.\frac{\mathrm d z_{ndc}}{\mathrm dz} =\frac{2nf}{(n-f)z^2}.

近处 ∣z∣|z| 小,单位世界距离会占据更大的深度刻度;远处深度刻度更密,容易出现深度冲突。举例取 n=−0.1,f=−100n=-0.1,f=-100:

相机空间深度 zz zndcz_{ndc}
−0.1-0.1 1.0001.000
−1-1 −0.802-0.802
−2-2 −0.902-0.902
−10-10 −0.982-0.982
−100-100 −1.000-1.000

大部分深度范围被分配给近平面附近。把近裁剪面设得过近,会让有限位宽的深度缓冲把大量精度浪费在相机附近,远处表面更容易出现 Z-fighting。工程上可以适当推远近裁剪面,也可以使用反向深度和浮点深度缓冲改善分布。

压缩过程中,内部点的 z 也会变化。由

z′=(n+f)−nfzz'=(n+f)-\frac{nf}{z}

可得

z′−z=−(z−n)(z−f)z.z'-z=-\frac{(z-n)(z-f)}{z}.

内部区间是 f<z<n<0f<z<n<0。在这个区间内,z−n<0z-n<0、z−f>0z-f>0,两者乘积为负;前面的负号使分子为正,再除以负的 zz,所以 z′−z<0z'-z<0,内部点整体被推向远平面一侧。取 n=−1,f=−5,z=−3n=-1,f=-5,z=-3,有

z′=−6−5−3≈−4.333,z'=-6-\frac5{-3}\approx-4.333,

确实从中间深度 −3 移向远处。近平面和远平面本身保持不动,内部点的非线性位移正是深度分布不均匀的来源。

视锥参数

对称视锥常用垂直视场角 fovY⁡\operatorname{fovY}、视口宽高比 a=W/Ha=W/H、近远裁剪面距离来定义。近裁剪面距离取正值 ∣n∣|n|,相似三角形给出

tan⁡fovY⁡2=t∣n∣.\tan\frac{\operatorname{fovY}}2 =\frac{t}{|n|}.

因此

t=∣n∣tan⁡fovY⁡2,b=−t,t=|n|\tan\frac{\operatorname{fovY}}2, \qquad b=-t,

r=at,l=−r.r=a t, \qquad l=-r.

水平视场角满足

tan⁡fovX⁡2=atan⁡fovY⁡2.\tan\frac{\operatorname{fovX}}2 =a\tan\frac{\operatorname{fovY}}2.

视场角越大,视野越宽,主体在画面中的占比和边缘透视观感会改变;视场角越小,视野变窄,画面更接近正交投影的取景效果。透视关系本身来自投影中心与成像平面的几何位置,不能只用“视场角越大,畸变越强”概括。相机位置不变时改变焦距,主要表现为取景范围和主体尺度的变化。接口若使用角度制,应先转换成弧度再代入三角函数。

视口变换

投影后的 NDC 是与设备分辨率无关的连续坐标。视口变换把 [−1,1]2[-1,1]^2 映射到屏幕连续区域 [0,W]×[0,H][0,W]\times[0,H]:

xs=W2(xndc+1),ys=H2(yndc+1).x_s=\frac W2(x_{ndc}+1), \qquad y_s=\frac H2(y_{ndc}+1).

对应的齐次矩阵为

Mviewport=(W200W20H20H200100001).M_{viewport}=\begin{pmatrix} \frac W2&0&0&\frac W2\\ 0&\frac H2&0&\frac H2\\ 0&0&1&0\\ 0&0&0&1 \end{pmatrix}.

这里约定屏幕左下角为原点、+y+y 向上,视口变换只改变 x、y,z 留给深度测试。像素索引 (x,y)(x,y) 对应的中心位于 (x+0.5,y+0.5)(x+0.5,y+0.5),索引范围是 0≤x<W,0≤y<H0\le x<W,0\le y<H。

如果图像文件或窗口系统使用左上角原点、y 轴向下的约定,就需要在接口边界处翻转 y;这属于坐标约定转换,不应混入投影矩阵的几何推导。视口变换完成后,连续三维图元才进入下一篇的离散覆盖测试和属性插值。