深度除法实现即时3D
摘要
本文解释了图形学中3D到2D投影的基础数学,重点关注深度除法和透视投影矩阵,并包含实际示例。
暂无内容
查看缓存全文
缓存时间: 2026/09/21 21:45
# 通过深度除法即时实现3D效果
来源:https://gabrieloc.com/2026/09/15/perspective.html
最初接触游戏开发时,我使用的是高级框架,它们提供的`Camera`组件开箱即用。但随着学习的深入,我想要尝试更多创意技术时,却连搜索所需功能的术语都不清楚。直到编写底层图形代码后,我才明白摄像头原理基于相当简单的数学。
在精彩的《解密3D图形的唯一公式》(https://www.youtube.com/watch?v=qjWkNZ0SXfo)(via@tsoding (https://twitter.com/tsoding))中,我们看到以下内容:
```
(x, y, z)
x' = x/z
y' = y/z
```
本质上,若我们定义`y`为垂直轴、`z`为深度轴,则三维坐标`(x, y, z)`可通过将`x`和`y`除以`z`投影到二维坐标`(x', y')`。例如,当一系列三维点仅在深度方向变化时,随着深度增加,它们的投影位置会向消失点`(0,0)`靠拢:
\[
\begin{array}{c|c}
(x,y,z) & (x',y') \\
\hline
(2,1,2) & (1,0.5) \\
(2,1,4) & (0.5,0.25) \\
(2,1,8) & (0.25,0.125)
\end{array}
\]
这可以通过一个球体来演示:它在绕摄像头垂直轴运动时,会根据深度变化移动和缩放,沿`z`轴偏移`forward`值:
运用相同原理,我们甚至能以同样方式绘制更复杂的“几何图形”!
显然,这些是非常受限且朴素的示例,仅在最简单的场景中实用。当我们进行任何3D工作时,通常需要考虑摄像头指向方向、位置、视场角等因素。虽然理论上可以强行修改着色器来支持这些特性,但存在一种更省力且实用的方法——称为**透视投影矩阵**,这正是强大`Camera`组件背后的核心魔法。
## 透视投影
构建透视投影矩阵有多种方式,取决于具体应用场景。例如计算机视觉和图形学在矩阵布局上略有不同,这也解释了为何很难通过单篇维基百科文章(https://en.wikipedia.org/wiki/3D_projection)获得通用形式。话虽如此,在基于三角形的图形系统中,常见的惯例通常包含*视场角*、*宽高比*以及*近远裁剪平面*的参数化。这些值至关重要,因为它们同时用于判断场景对象是否在视锥体内,从而支持高效渲染(例如剔除屏幕外几何体,仅渲染可见部分)。
尽管不同坐标系存在差异(关于哪个轴对应上、右、前方向存在不同惯例(https://x.com/FreyaHolmer/status/1325556229410861056)),但整体结构基本一致:
\[
P = \begin{bmatrix}
\frac{f}{a} & 0 & 0 & 0 \\
0 & f & 0 & 0 \\
0 & 0 & A & B \\
0 & 0 & 1 & 0
\end{bmatrix}
\]
这描述了一个具有焦距缩放`f`(由垂直视场角`θ`推导)和宽高比`a`的摄像头:
\[
f = \frac{1}{\tan(\theta/2)},\quad a = \frac{\text{width}}{\text{height}}
\]
其中`A`和`B`代表深度映射,分别由近裁剪平面`n`和远裁剪平面`F`推导:
\[
A = \frac{F+n}{F-n}, \quad B = -\frac{2Fn}{F-n}
\]
那么这与之前的深度除法技巧有何关联?实际上,后者是透视投影矩阵的特殊形式。
给定三维空间中的某点,我们首先计算其相对摄像头的位置。这将坐标从**世界空间**(整个场景共享的坐标系)转换到**视图空间**(摄像头位于原点的坐标系)。然后乘以透视投影矩阵,生成**裁剪空间**坐标,为透视除法做好准备:
\[
\begin{bmatrix}
\frac{f}{a} & 0 & 0 & 0 \\
0 & f & 0 & 0 \\
0 & 0 & A & B \\
0 & 0 & 1 & 0
\end{bmatrix}
\begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} =
\begin{bmatrix}
\frac{f}{a}x + 0y + 0z + 0(1) \\
0x + fy + 0z + 0(1) \\
0x + 0y + Az + B(1) \\
0x + 0y + z + 0(1)
\end{bmatrix} =
\begin{bmatrix}
\frac{f}{a}x \\
fy \\
Az + B \\
z
\end{bmatrix}
\]
将裁剪空间坐标的前三个分量除以`w`后,坐标不再描述场景中的距离,而是表示该点在摄像头可视范围内的位置。这就是**标准化设备坐标**(通常缩写为NDC),最终可将点映射到屏幕输出分辨率:
\[
x_{ndc} = \frac{\frac{f}{a}x}{z}, \qquad y_{ndc} = \frac{fy}{z}
\]
在之前的示例中,焦距缩放`f`和宽高比`a`均被省略。将两者都代入1,就得到了原始的深度除法技巧:
\[
x_{ndc} = \frac{\frac{1}{1}x}{z} = \frac{x}{z}, \qquad y_{ndc} = \frac{1y}{z} = \frac{y}{z}
\]
虽然深度除法技巧适用于简单场景中的摄像头相对坐标,但它实际上只是完整图形管线中的一步。现在所有术语都有了明确定义,整个流程如下所示:
\[
\text{世界空间} \rightarrow \text{视图空间} \rightarrow \text{裁剪空间} \xrightarrow{\,/w\,} \text{NDC} \rightarrow \text{像素} \rightarrow \text{光栅化}
\]
## 整合所有环节
如果这篇技术总结有一个核心要点,那就是:`Camera`组件只是执行几个简单的变换,如果你理解每个变换的作用,就可以根据自身需求和约束选择性地实现特定部分。有时需要完整管线,有时仅需深度除法这一步。
相似文章
快得惊人的散度定理体积计算
本文介绍了一种使用散度定理计算简单、闭合、三角化三维网格体积的快速算法。
同一场景,两种深度:探索单目基础模型中的几何模糊性
引入MultiDepth-3k基准,用于评估单目深度基础模型中的深度层偏好,并展示拉普拉斯视觉提示(Laplacian Visual Prompting)可以改变报告的深度层,表明不同模型之间存在互补的几何假设。
αDepth:学习单次软边界分解用于立体转换
αDepth 引入了一种带有圆形Alpha表示(CAR)的分层表示,以解决立体转换中的软边界挑战,无需手动指导即可实现最先进的性能。
一个无需光栅化即可投影纹理的3D SVG渲染器
一位开发者描述了一个用原生TypeScript编写的3D转SVG渲染器,该渲染器通过将图像细分为带裁剪路径的仿射变换多边形来近似透视纹理效果,而无需光栅化,旨在用于在React中渲染电路板。
计算相机射线
一篇技术博客文章,推导如何从视图投影矩阵计算用于光线追踪的相机射线,包含着色器代码并处理数值稳定性问题。