一、视图矩阵(将相机放置到原点)
在计算机图形学中,相机不仅有位置,还有自己的朝向。为了从相机的视角观察三维场景,我们需要将世界坐标系中的点转换到相机坐标系中。
这个过程叫作视图变换(View Transformation)。
视图变换可以分为两个部分:
- 旋转变换:将世界坐标系转换到相机坐标系的方向。
- 平移变换:将相机的位置移动到坐标原点。
在之前的文章里面,对于某一个点的旋转是不方便的,所以我们一般会将该点移动到原点,在进行旋转。
一、相机的四个基本符号
首先固定以下符号,避免混淆。
- e:相机在世界坐标系中的位置。
- g:相机的视线方向,从相机指向观察目标。
- t:相机的上方向参考向量。
- r:相机的右方向向量。
其中,e 决定相机的位置,而 g、t、r 用来确定相机的朝向。

假设相机位于:
e = (0, 0, 5)
相机观察世界原点:
target = (0, 0, 0)
那么,视线方向为:
g = target - e
= (0, 0, -5)
对向量进行归一化,只保留方向:
g = normalize(target - e)
= (0, 0, -1)
接下来,给出一个参考上方向:
t = (0, 1, 0)
这个向量表示相机的上方大致朝向世界坐标系的 Y 轴正方向,这个 t 也就相当于固定了照相机的角度。
注意:初始的 t 不一定与 g 垂直,因此后面还需要修正。
二、通过叉乘计算相机的右方向
已知视线方向 g 和上方向参考向量 t,可以通过叉乘计算相机的右方向:
r = g × t
叉乘顺序非常重要:
g × t = -(t × g)
如果将顺序颠倒,结果方向就会相反。
设:
g = (gx, gy, gz)
t = (tx, ty, tz)
那么叉乘公式为:
r = (
gy * tz - gz * ty,
gz * tx - gx * tz,
gx * ty - gy * tx
)
代入前面的例子:
g = (0, 0, -1)
t = (0, 1, 0)
计算得到:
r = g × t
= (1, 0, 0)
因此,相机的右方向就是世界坐标系的 X 轴正方向。
接下来,重新计算上方向:
t = r × g
这样可以让三个方向互相垂直。
实际计算时,应保证三个方向都是单位向量,并且两两正交。如果初始上方向与视线方向平行,叉乘会得到零向量,需要更换参考上方向。
最终得到三个相机方向:
r = 相机右方向
t = 相机上方向
-g = 相机后方向
三、为什么相机的 Z 轴是 -g?
使用的相机坐标系约定中:
X 轴正方向:相机右方
Y 轴正方向:相机上方
Z 轴正方向:相机后方
相机实际观察的方向是局部负 Z 轴方向。
但是,g 表示的是从相机指向目标的方向,也就是相机前方。
因此:
相机 Z 轴正方向 = -g
这里的负号来自相机坐标系的约定,而不是叉乘运算本身。
如果其他图形系统采用相机朝正 Z 轴观察的约定,相关矩阵的符号也可能不同。因此,推导矩阵之前必须先明确坐标系约定。
四、根据相机的三个方向向量构造旋转矩阵
前面已经求出了相机的三个方向向量:

r = 相机右方向
t = 相机上方向
g = 相机视线方向
由于使用相机朝局部负 Z 轴观察的约定,因此相机坐标系的三个正方向分别为:
X 轴: r
Y 轴: t
Z 轴:-g
接下来,我们要根据这三个方向向量构造旋转矩阵。
1. 先构造世界坐标到相机坐标的矩阵
我们的目标是将世界坐标中的向量转换为相机坐标中的向量。
在列向量左乘的约定下,世界坐标中的向量可以写成:
v_world = (x, y, z)^T
转换后的相机坐标为:
v_camera = R * v_world
矩阵的每一行,决定了输入向量在相应相机坐标轴上的投影分量。
例如,相机坐标的 X 分量,就是世界空间向量在相机右方向 r 上的投影:
x_camera = dot(r, v_world)
同理:
y_camera = dot(t, v_world)
z_camera = dot(-g, v_world)
因此,世界坐标到相机坐标的旋转矩阵为:
R_world_to_camera =
[ rx ry rz ]
[ tx ty tz ]
[-gx -gy -gz ]
这里,r、t 和 -g 分别构成矩阵的三行。
将矩阵乘法展开,就能得到:
x_camera = rx*x + ry*y + rz*z
y_camera = tx*x + ty*y + tz*z
z_camera = -gx*x - gy*y - gz*z
这三个公式的含义非常直观:分别计算世界空间中的点在相机右方向、上方向和后方向上的坐标分量。
2. 再通过求逆得到相机到世界坐标的矩阵

如果已经知道世界坐标到相机坐标的矩阵:
v_camera = R_world_to_camera * v_world
那么反过来,将相机坐标转换回世界坐标,就需要对矩阵求逆:
v_world = inverse(R_world_to_camera) * v_camera
因此:
R_camera_to_world = inverse(R_world_to_camera)
由于 r、t 和 -g 是互相正交的单位向量,旋转矩阵属于正交矩阵,所以满足:
inverse(R) = transpose(R)
于是:
R_camera_to_world = transpose(R_world_to_camera)
展开后得到:
R_camera_to_world =
[ rx tx -gx ]
[ ry ty -gy ]
[ rz tz -gz ]
这就是相机坐标到世界坐标的旋转矩阵。
需要注意:这里不是随意交换矩阵元素,而是利用正交矩阵的性质,将矩阵求逆转换成矩阵转置。
总结一下:
世界坐标 -> 相机坐标:
R_world_to_camera =
[ rx ry rz ]
[ tx ty tz ]
[-gx -gy -gz ]
相机坐标 -> 世界坐标:
R_camera_to_world =
[ rx tx -gx ]
[ ry ty -gy ]
[ rz tz -gz ]
两者互为逆矩阵。由于旋转矩阵是正交矩阵,因此它们也互为转置矩阵。
二、投影
在三维图形学中,我们通常在三维世界中定义物体,但最终需要将它们显示在二维屏幕上。因此,我们需要将三维空间中的点映射到一个用于显示的投影平面上,这个过程称为投影(Projection)。
常见的投影方式有两种:
- 正交投影(Orthographic Projection):投影线彼此平行。
- 透视投影(Perspective Projection):投影线汇聚于同一个投影中心,具有近大远小的效果。
一、正交投影
正交投影的核心特点是:物体的投影大小不会因为它距离相机的远近而发生变化。
例如,一个长度为 2 的立方体,无论放在相机前方较近的位置,还是较远的位置,只要没有进行其他缩放,它在投影平面上的大小就不会因为距离变化而改变。
正交投影可以理解为:从三维空间中沿着固定方向发出一组互相平行的投影线,将物体投射到投影平面上。
为了便于计算,我们通常先确定一个长方体作为观察范围。这个长方体由以下六个边界确定:
- 左边界:l
- 右边界:r
- 下边界:b
- 上边界:t
- 近裁剪面距离:n
- 远裁剪面距离:f
这里采用常见的相机约定:相机朝局部 -Z 方向观察,因此近裁剪面和远裁剪面的相机空间坐标分别为 z = -n 和 z = -f,其中 n、f 都是正数,且 f > n。
我们希望将这个观察长方体变换到一个标准立方体中,也就是规范设备坐标(NDC)所使用的范围:
- x 方向:-1, 1
- y 方向:-1, 1
- z 方向:-1, 1
这个过程就是正交投影矩阵要完成的工作。
一、正交投影的变换过程
将观察长方体映射到标准立方体,可以拆分为两个步骤:
- 平移:将观察长方体的中心移动到原点。
- 缩放:将平移后的长方体缩放到 -1, 1 范围内。
这样做的好处是,可以将一个复杂的空间映射问题拆解成我们熟悉的平移和缩放变换。
1. 平移变换
观察长方体在 x、y 方向的中心坐标分别为:
center_x = (l + r) / 2
center_y = (b + t) / 2
由于相机空间中的 z 范围是 -f, -n,因此长方体在 z 方向的中心坐标为:
center_z = (-f + (-n)) / 2
= -(f + n) / 2
为了将长方体中心移动到原点,需要沿三个方向分别平移:
x: -(l + r) / 2
y: -(b + t) / 2
z: (f + n) / 2
采用列向量左乘的约定,平移矩阵为:
T =
[ 1 0 0 -(l+r)/2 ]
[ 0 1 0 -(b+t)/2 ]
[ 0 0 1 (f+n)/2 ]
[ 0 0 0 1 ]
经过这一步,观察长方体的中心就移动到了原点。
此时,各轴方向的坐标范围变为:
x: [-(r-l)/2, (r-l)/2]
y: [-(t-b)/2, (t-b)/2]
z: [-(f-n)/2, (f-n)/2]
2. 缩放变换
平移完成后,我们还需要将长方体的三个方向分别缩放,使每个方向的范围都变成 -1, 1。
对于 x 方向,平移后的范围是:
[-(r-l)/2, (r-l)/2]
要将这个范围映射到 -1, 1,需要乘以:
scale_x = 2 / (r-l)
同理,y 方向的缩放系数为:
scale_y = 2 / (t-b)
z 方向稍有不同。由于我们采用相机朝 -Z 方向观察的约定,并希望近裁剪面 z = -n 映射到 -1、远裁剪面 z = -f 映射到 1,因此 z 方向需要进行反向缩放:
scale_z = -2 / (f-n)
于是,缩放矩阵为:
S =
[ 2/(r-l) 0 0 0 ]
[ 0 2/(t-b) 0 0 ]
[ 0 0 -2/(f-n) 0 ]
[ 0 0 0 1 ]
注意:z 方向出现负号,并不是所有正交投影都必然如此,而是由这里采用的相机坐标约定和深度映射方向决定的。
二、推导正交投影矩阵
现在我们已经得到了两个变换矩阵:
- T:将观察长方体的中心平移到原点。
- S:将平移后的长方体缩放到标准立方体。
由于使用列向量左乘,矩阵乘法的顺序与实际执行顺序相反:先执行平移,再执行缩放,因此总变换矩阵为:
M_ortho = S * T
将两个矩阵相乘,可以得到:
M_ortho =
[ 2/(r-l) 0 0 -(r+l)/(r-l) ]
[ 0 2/(t-b) 0 -(t+b)/(t-b) ]
[ 0 0 -2/(f-n) -(f+n)/(f-n) ]
[ 0 0 0 1 ]
这就是在上述坐标约定下的正交投影矩阵。
它将相机空间中指定的观察长方体映射到标准立方体,使三个方向的坐标都落入 -1, 1 范围。
需要注意,正交投影矩阵的作用是将观察范围映射到标准立方体,并不意味着所有位于长方体之外的物体都会自动被删除。实际渲染时,超出裁剪范围的部分还需要由图形管线进行裁剪。
二、透视投影
在三维图形学中,我们需要将三维世界中的物体映射到二维屏幕上。上一节介绍的正交投影,可以将观察范围映射到标准立方体,但它有一个明显的特点:物体不会因为距离相机更远而自然变小。
而在现实世界中,我们通常会观察到近大远小的现象。例如:
- 近处的汽车看起来比较大,远处的汽车看起来比较小。
- 铁轨两侧的边缘会随着距离增加逐渐靠近。
- 同样大小的物体,距离相机越远,在视野中通常占据越小的范围。
为了模拟这种视觉效果,我们需要使用透视投影(Perspective Projection)。
透视投影的核心思想是:将三维空间中的点沿着经过相机投影中心的射线,映射到投影平面上。
与正交投影不同,透视投影的投影线并不是互相平行的,而是汇聚于同一个投影中心,因此能够产生近大远小的效果。
一、透视投影的基本原理
其实,透视投影的原理并不复杂,可以想象一下:同样大小的东西,离我们越远,看起来就越小。
比如,站在铁轨上往远处看,两条铁轨好像慢慢靠到了一起。实际上,铁轨并没有变窄,只是因为距离越来越远,它们在我们眼中占据的范围越来越小。
在计算机图形学中,也是同样的道理。
我们先看相机坐标系中的一个点:
P = (x, y, z)
在 GAMES101 中,我们约定相机朝着 -Z 方向看,所以物体在相机前方时,z 通常是负数。
假设我们在相机前方放了一块投影平面,位置是:
z = -n
现在,想象从相机原点出发,穿过点 P,继续延伸这条线,直到它碰到投影平面。碰到的位置,就是点 P 的投影位置。
根据相似三角形,可以得到:
x_projected = n * x / (-z)
y_projected = n * y / (-z)
这两个公式看起来有点绕,其实只需要抓住一点:投影后的坐标,要除以点距离相机的深度。
举个例子,假设投影平面距离相机 1 个单位,两个点的 x 坐标都是 2,只是距离相机的远近不同。
近处的点:
x = 2
z = -2
x_projected = 1 * 2 / 2 = 1
远处的点:
x = 2
z = -4
x_projected = 1 * 2 / 4 = 0.5
可以看到,两个点原本的 x 坐标相同,但远处的点投影后,x 坐标只有近处点的一半。
这就是因为距离越远,除数越大,最终得到的投影坐标就越小。
对于物体来说,它的各个点都会受到这种深度缩放的影响,所以距离相机越远,投影到屏幕上的大小通常也就越小。
这就是透视投影产生近大远小效果的原因。
我们可以将透视投影理解为两个步骤:
- 将透视投影的视锥体变换为一个长方体。
- 再使用正交投影,将这个长方体映射到标准立方体。
一、构建矩阵,转化为长方形

n面为近裁剪面,z面为远裁剪面,n到z的对方也就是相机可以看到的范围,我们首先就要将这个棱台压缩到长方体,根据相似三角形性质,所以每一个点的x,y都可以使用x' ,y'来表示。
x' = (n / z) * x
y' = (n / z) * y
当投影平面距离 n 固定时,投影坐标与深度 z 成反比。
-
当 z 较小时,n / z 较大,投影后的坐标偏移更大。
-
当 z 较大时,n / z 较小,投影后的坐标偏移更小。
这里要注意,压缩后的x和y可以通过最近面的相似三角形性质来推导出来,除了近裁剪面和远裁剪面的z是不变的,中间的z坐标是会随着距离的增加而受到不同程度的压缩,是无法通过相似三角形表示出来的。
因此,同样大小的物体距离相机越远,投影到屏幕上的尺寸就越小,这就是透视投影中的近大远小现象。

也就是说某一个点(x,y,z,1)要应用某一个矩阵后要变成(nx,ny,unknown(不知道),z)的形式,所以我们就可以先推出来矩阵的大概为:

因为z坐标是不知道的,所以第三行的值暂时无法推导出来。
这个时候可以利用到近裁剪面和远裁剪面上面的点的z坐标是不会发生变化的。

也就是近裁剪面上点(x,y,n,1)的值经过经过矩阵变化后为(nx,ny,
n²,n)

推导出A*n+B=n²。
在利用远裁剪面的z坐标也不会发送改变。
这里我们设远裁剪面的距离为f。、
也就可以推导出另一个公式。
A*n+B=f²。
得出A值为n+f
得出B值为-nf
所以压缩为长方体的矩阵就为
M_persp =
| n 0 0 0 |
| 0 n 0 0 |
| 0 0 f+n -f*n |
| 0 0 1 0 |
二、使用正交投影
之后将这个矩阵再进行一次正交投影,t,b,l,r分别为上边界,下边界,左边界,右边界。
① 挤压矩阵(把棱台压成长方体)
M_persp_to_ortho =
| n 0 0 0 |
| 0 n 0 0 |
| 0 0 n+f -nf |
| 0 0 1 0 |
② 正交投影矩阵(代入对称条件 l = -r,b = -t)
一般正交投影矩阵:
M_ortho =
| 2/(r-l) 0 0 0 |
| 0 2/(t-b) 0 0 |
| 0 0 2/(n-f) 0 |
| 0 0 0 1 |
代入对称条件 l = -r,b = -t,得到:
M_ortho =
| 1/r 0 0 0 |
| 0 1/t 0 0 |
| 0 0 2/(n-f) 0 |
| 0 0 0 1 |
如果需要将深度从 -n, -f 映射到 -1, 1,还需要包含 Z 方向的平移项。对应的正交投影矩阵应为:
M_ortho =
| 1/r 0 0 0 |
| 0 1/t 0 0 |
| 0 0 2/(n-f) -(n+f)/(n-f) |
| 0 0 0 1 |
③ 两者相乘,得到最终透视投影矩阵
M_persp = M_ortho * M_persp_to_ortho
最终结果:
M_persp =
| n/r 0 0 0 |
| 0 n/t 0 0 |
| 0 0 (n+f)/(n-f) -2*n*f/(n-f)|
| 0 0 1 0 |
最后我们还可以使用含有fovY(视场角),aspect(宽高比)的版本

最后的公式可以表示为:
代入得到用视场角(FOV)表示的透视投影矩阵:
M_persp =
| 1 / (aspect * tan(fovY / 2)) 0 0 0 |
| 0 1 / tan(fovY / 2) 0 0 |
| 0 0 (n+f) / (n-f) -2*n*f/(n-f) |
| 0 0 1 0 |