在游戏世界中,角色的位置可能是:
x = 10 y = 2 z = 30但屏幕只有两个方向:横向和纵向。
那么,三维角色应该显示在屏幕的什么位置?看起来多大?远处的敌人为什么更小?
这就需要投影矩阵。
投影矩阵负责把相机看到的三维空间,变换到适合裁剪和最终映射到屏幕的空间。
它决定了游戏画面的透视效果、可视范围以及深度映射方式。
一、先看整体:一个顶点怎样到达屏幕?
假设角色鼻尖在模型自身坐标中的位置是一个点,它要经历:
模型空间 ↓ 模型矩阵 M 世界空间 ↓ 观察矩阵 V 观察空间(相机空间) ↓ 投影矩阵 P 裁剪空间 ↓ 透视除法 标准化设备坐标 NDC ↓ 视口变换 屏幕坐标采用列向量约定时:
pclip=PVMplocalp_{clip}=PVMp_{local}pclip=PVMplocal
可以把三个矩阵的工作理解为:
| 矩阵 | 游戏中的含义 |
|---|---|
| 模型矩阵 M | 角色放在哪里,朝哪个方向,有多大 |
| 观察矩阵 V | 相机在哪里,从哪个方向观察 |
| 投影矩阵 P | 相机视野有多宽,远近如何影响大小 |
相机转头主要改变 V,调整 FOV 主要改变 P。
二、透视投影:为什么远处的敌人更小?
这是 3D 游戏最常见的投影方式。
想象两名身高相同的敌人:
敌人 A:距离相机 5 米 敌人 B:距离相机 20 米敌人 B 的投影高度大约是 A 的四分之一,前提是比较条件相同、都位于相近观察方向。
核心原理:相似三角形
为了便于理解,先约定相机朝 (+Z) 方向观察,投影平面距离相机为 (f)。
空间点为:
(x,y,z)
它在投影平面的位置满足:
x′=fxzx'=\frac{fx}{z}x′=zfx
y′=fyzy'=\frac{fy}{z}y′=zfy
最重要的是:
屏幕上的投影大小,与距离近似成反比。
例如一个物体的高度为 (h),投影高度就是:
h′=fhzh'=\frac{fh}{z}h′=zfh
这便是“近大远小”的来源。
不过,矩阵乘法本身不能直接完成“除以一个会变化的 (z)”。因此还需要一个关键机制:齐次坐标与透视除法。
三、投影矩阵的魔法:先构造 w,再做除法
三维点通常写成四维齐次形式:
p=[xyz1]p= \begin{bmatrix} x\\y\\z\\1 \end{bmatrix}p=xyz1
经过投影矩阵后,得到:
pclip=[xcyczcwc]p_{clip}= \begin{bmatrix} x_c\\y_c\\z_c\\w_c \end{bmatrix}pclip=xcyczcwc
随后执行透视除法:
xndc=xcwcx_{ndc}=\frac{x_c}{w_c}xndc=wcxc
yndc=ycwcy_{ndc}=\frac{y_c}{w_c}yndc=wcyc
zndc=zcwcz_{ndc}=\frac{z_c}{w_c}zndc=wczc
透视投影会让wcw_cwc与观察空间深度相关,从而产生“除以距离”的效果。
所以严格来说:
近大远小不是投影矩阵单独完成的,而是投影变换与透视除法共同产生的。
裁剪则依据齐次裁剪坐标进行,在透视除法之前处理视野边界等情况。
四、一个典型的透视投影矩阵
不同图形 API 和引擎的坐标约定不同,矩阵形式也会不同。
下面采用一组明确约定:
- 列向量;
- 右手观察空间;
- 相机朝 (-Z) 方向;
- NDC 深度范围为 ([-1,1]);
- 近、远裁剪距离分别为 (n)、(F),且 (0<n<F)。
透视投影矩阵可以写成:
P=[sa0000s0000−F+nF−n−2FnF−n00−10]P= \begin{bmatrix} \frac{s}{a}&0&0&0\\ 0&s&0&0\\ 0&0&-\frac{F+n}{F-n}&-\frac{2Fn}{F-n}\\ 0&0&-1&0 \end{bmatrix}P=as0000s0000−F−nF+n−100−F−n2Fn0
其中:
[
s=\frac{1}{\tan(\theta/2)}
]
- θ\thetaθ:垂直视场角,也就是垂直 FOV;
- (a):画面宽高比;
- (n):近裁剪距离;
- (F):远裁剪距离。
相乘后:
wc=−zw_c=-zwc=−z
于是:
xndc=sxa(−z)x_{ndc}=\frac{sx}{a(-z)}xndc=a(−z)sx
yndc=sy−zy_{ndc}=\frac{sy}{-z}yndc=−zsy
由于相机前方的点满足 (z<0),这里的 (-z) 就对应正的前向深度。
不必死记整张矩阵,重点理解:
前两行:控制视野宽度和高度 第三行:映射深度 第四行:为透视除法构造 w五、放进游戏里:四个参数影响什么?
1. FOV:看到更多,还是看得更大?
当 FOV 增大时:
可见范围更宽 同一物体在屏幕中更小 边缘的透视拉伸更明显当 FOV 减小时:
可见范围更窄 同一物体在屏幕中更大在 FPS 游戏中,开镜常通过减小 FOV 实现视觉放大;有些游戏则采用单独的瞄准镜相机或渲染方案。
需要区分:
- 缩小 FOV:相机位置不变,改变取景范围;
- 相机向前移动:观察位置改变,会产生视差和不同的遮挡关系。
两者不完全等价。
2. 宽高比:为什么宽屏能看到更多?
设垂直 FOV 固定:
a=widthheighta=\frac{width}{height}a=heightwidth
画面越宽,通常能够看到更多横向内容。
水平 FOV 与垂直 FOV 的关系为:
tan(θh2)\tan\left(\frac{\theta_h}{2}\right)tan(2θh)
atan(θv2)a\tan\left(\frac{\theta_v}{2}\right)atan(2θv)
所以,在竞技游戏中,屏幕适配不仅是 UI 问题,还涉及:
超宽屏是否应该获得更大的横向视野?
这取决于游戏采用固定垂直 FOV、固定水平 FOV,还是其他裁切策略。
3. Near:为什么靠近墙壁时会被切掉?
近裁剪面以前的几何部分不会正常进入可见范围。
例如:
Near = 0.3 米 武器部分顶点距离相机只有 0.1 米武器就可能被近裁剪面切掉。
FPS 游戏常使用独立武器相机或单独渲染策略处理这个问题。
但也不能无脑把 Near 调得极小,因为它还会影响深度精度。
4. Far:远处能画到哪里?
远裁剪面限制可见深度范围。
但需要注意:
增大 Far,不意味着 GPU 一定会绘制范围内的所有物体。
引擎还可能结合视锥剔除、遮挡剔除、LOD 和距离剔除等机制减少开销。
六、为什么远处的地面会闪烁?深度精度问题
透视投影后的深度,通常不是对距离做线性映射。
从矩阵可以推导出类似形式:
zndc=A+Bzz_{ndc}=A+\frac{B}{z}zndc=A+zB
这意味着深度值的分布与 (1/z) 有关。
在常规深度映射和有限精度深度缓冲下,距离很远的两个表面可能获得非常接近的深度值,难以稳定区分前后。
于是出现:
一帧显示表面 A 下一帧又显示表面 B这就是常说的Z-fighting。
常见处理方法
- 不把 Near 设置得过小;
- 合理设置 Far;
- 避免几乎重合的表面;
- 按场景需要使用深度偏移;
- 在支持的渲染方案中采用反向 Z。
**反向 Z 配合浮点深度缓冲,可以显著改善深度精度。**因此,分析深度问题时,要先确认项目实际使用的深度约定。
七、正交投影:为什么远处的角色不变小?
正交投影没有“除以观察深度”导致的缩放。
可以直观理解为:
x′=kxx+bxx'=k_xx+b_xx′=kxx+bx
y′=kyy+byy'=k_yy+b_yy′=kyy+by
同样大小的物体,无论前后距离如何变化,投影尺寸都保持一致。
| 透视投影 | 正交投影 |
|---|---|
| 近大远小 | 大小不随距离变化 |
| 适合 FPS、赛车、第三人称游戏 | 适合 2D、策略、建筑展示 |
| 空间纵深感明显 | 尺寸关系稳定、便于观察 |
正交相机仍然存在深度,也仍然需要处理遮挡和裁剪。
另外:
等距视角不只是使用正交投影,还需要配合特定的相机朝向。
八、Unity 中怎么对应?
常见设置:
Cameracam=GetComponent<Camera>();cam.orthographic=false;cam.fieldOfView=60f;cam.nearClipPlane=0.3f;cam.farClipPlane=1000f;正交模式:
cam.orthographic=true;cam.orthographicSize=5f;orthographicSize表示垂直可视范围的一半,因此这里的垂直范围约为 10 个世界单位。
如果需要检查矩阵:
Matrix4x4view=cam.worldToCameraMatrix;Matrix4x4projection=cam.projectionMatrix;但在直接向 GPU 或自定义渲染流程传递投影矩阵时,要注意平台差异:
Matrix4x4gpuProjection=GL.GetGPUProjectionMatrix(cam.projectionMatrix,renderIntoTexture);这里的renderIntoTexture应与实际渲染目标匹配。使用 SRP 时,还应优先遵循所在渲染管线的矩阵和 Shader 约定。
不要直接把某本教材中的矩阵复制进所有 Unity 平台,因为深度范围、反向 Z 和渲染目标方向等约定可能不同。
总结
从游戏角度理解投影矩阵,可以记住三句话:
- 观察矩阵决定“从哪里看”,投影矩阵决定“怎样看”。
- 透视投影通过构造 w,再进行透视除法,实现近大远小。
- FOV、宽高比和裁剪面,不仅影响画面,也影响玩法公平性、显示效果与深度精度。
它并不是简单地“丢掉 Z 轴”,而是:
把三维世界转换成可裁剪、可映射到屏幕,同时保留深度关系的表达。