按深度除以实现即时 3D
Divide by Depth for Instant 3D

原始链接: https://gabrieloc.com/2026/09/15/perspective.html

虽然高级游戏引擎通常会将摄像机功能抽象化,但理解其底层的数学原理对于实现创意控制至关重要。其核心在于,3D 投影依赖于将坐标除以深度值($x/z, y/z$),这会自然地产生透视效果,即物体随距离增加而缩小。 然而,真实世界的图形渲染需要更复杂地处理视野(Field of View)、纵横比(Aspect Ratio)和裁剪平面(Clipping Planes)。这通过**透视投影矩阵**来实现,它将 3D 坐标变换为“裁剪空间”(Clip Space)。通过将点与该矩阵相乘并执行透视除法(除以 $w$ 分量),我们可以将 3D 点映射到**归一化设备坐标(NDC)**中。 简单的深度除法技巧本质上就是该矩阵的一个基础版本。标准的图形渲染管线遵循一条结构化的路径:**世界空间 → 观察空间 → 裁剪空间 → NDC → 屏幕空间**。通过掌握这些变换,开发者可以不再依赖引擎的“魔法”功能,而是根据特定需求实现自定义的渲染技术,无论是需要完整的渲染管线,还是仅需简单的基于深度的投影。

Gabriel O. (@gabrieloc) 发布了一款全新的交互式资源,旨在揭开 3D 摄像机机制的神秘面纱。该指南通过可视化滑块和清晰的讲解,力求让那些对传统图形学理论感到头疼的人,也能轻松理解如何将 3D 空间映射到 2D 屏幕等复杂概念。 在讨论中,评论者们称赞了这种交互式的方法。用户 `aappleby` 指出,从视锥体的几何结构入手,能让后续的投影数学运算比标准的“黑箱”矩阵公式直观得多。另一位用户 `dyarosla` 在赞赏其可视化效果的同时,也提出了改进建议,例如实现更准确的边缘及近/远剪裁平面,以更好地契合 3D 图形应用程序的预期。 总体而言,该项目作为学习计算机图形学基础知识的实用工具,受到了广泛好评。
相关文章

原文

When I first started working on games, I used high level frameworks that would give you a Camera that just kind of worked. But as I learned more and wanted to apply more creative techniques, I struggled to even have the vocabulary to search for what I wanted to do, and only after writing lower level graphics code did I understand cameras are based around kind of really simple math.

In the excellent One Formula That Demystifies 3D Graphics (via @tsoding), we’re presented with the following:

(x, y, z)
x' = x/z
y' = y/z

In essence, if we say \(y\) is up and \(z\) is forward, 3D coordinates \((x, y, z)\) can be projected into 2D coordinates \((x', y')\) by dividing \(x\) and \(y\) by \(z\). For example, if we have a series of 3D points that only vary in depth, as depth increases, their projected positions get closer to vanishing point \((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}\]

This can be demonstrated by a ball that moves and scales with depth as it orbits the camera’s up axis, offset along the \(z\) axis by forward:

Using the same principle, we can even draw more sophisticated “geometry” in the same way!

Obviously, these are very constrained and naive examples that are largely impractical in all but the simplest scenarios. When we do any kind of 3D work, we typically need to involve things like the direction the camera’s pointing in, it’s position, it’s field of view, etc. While we could in theory hack those shaders to support these features, there is a way that’s less effort and more practical. It’s called the perspective projection matrix, and it’s the magic behind the mighty Camera.

Perspective Projection

There are a few ways to construct a perspective projection matrix, depending on your use case. Computer vision and graphics for example, have slightly different conventions for layout, which is why it’s hard to point to a single Wikipedia article and expect a universal form. That being said, in triangle-based graphics, a common convention that’s often followed involves parameterization of field of view, aspect ratio, and near and far clipping planes. These values are all very important because they also double as a way to easily know what’s in frame and what isn’t, which lets us render our scenes in performant ways, such as through culling off-screen geometry and rendering only what’s visible.

While this varies across different coordinate conventions (little consensus around which axes correspond to up, right, and forward), the general structure is mostly consistent:

\[P = \begin{bmatrix} \frac{f}{a} & 0 & 0 & 0 \\ 0 & f & 0 & 0 \\ 0 & 0 & A & B \\ 0 & 0 & 1 & 0 \end{bmatrix}\]

This describes a camera with focal scale \(f\) (derived from vertical fov angle \(\theta\)) and aspect ratio \(a\):

\[f = \frac{1}{\tan(\theta/2)},\quad a = \frac{width}{height}\]

Where \(A\) and \(B\) represent depth mapping, derived from near and far clipping planes \(n\) and \(F\) respectively:

\[A = \frac{F+n}{F-n}, \quad B = -\frac{2Fn}{F-n}\]

So how does this connect to the depth division trick from earlier? Turns out that’s a special form of the perspective projection matrix.

Given a point somewhere in 3D space, we first describe its position relative to the camera. This takes us from world space, where coordinates are shared by the whole scene, into view space, where the camera sits at the origin. We then multiply by the perspective projection matrix, producing clip-space coordinates that are ready for the perspective division.

\[\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}\]

After dividing the first three clip-space components by \(w\), the coordinates no longer describe distances in the scene. Instead, they describe where the point falls inside the camera’s visible bounds. These are normalized device coordinates, usually shortened to NDC, which later allow points to finally be mapped to the screen’s output resolution.

\[x_{ndc} = \frac{\frac{f}{a}x}{z}, \qquad y_{ndc} = \frac{fy}{z}\]

In the earlier example, both focal scale \(f\) and aspect ratio \(a\) are omitted. Substituting them both with 1 gives us exactly the original depth division trick:

\[x_{ndc} = \frac{\frac{1}{1}x}{z} = \frac{x}{z}, \qquad y_{ndc} = \frac{1y}{z} = \frac{y}{z}\]

While the depth division trick works for camera-relative points in simple scenarios, it’s really just one step in the full graphics pipeline. Now that all of those terms have names, the whole journey looks like this:

\[\mathrm{world} \rightarrow \mathrm{view} \rightarrow \mathrm{clip} \xrightarrow{\,/w\,} \mathrm{NDC} \rightarrow \mathrm{pixels} \rightarrow \mathrm{rasterization}\]

Tying it all together

If there’s one thing to take away from this writeup, it’s that Cameras are just doing a few simple transformations, and if you understand what each of those transformations are for, you can pick and choose which parts to implement yourself based off your own needs and constraints. Sometimes you need the entire pipeline, and sometimes you need that one depth division.

联系我们 contact @ memedata.com