news 2026/4/15 17:47:28

透视投影矩阵:w分量的数学推导

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
透视投影矩阵:w分量的数学推导
  1. 先用直觉回顾一下“为什么要/w,w 大概干嘛的”,把脑子热起来;
  2. 然后从一个最简单的 2D→1D 透视投影例子开始推,先在一维上看 w 从哪来;
  3. 再上升到 3D 透视,从 Camera Space 到 NDC(忽略 near/far、只看 x/y 与 z 的关系)推一遍;
  4. 再引入完整的 near / far,推标准的 4×4 透视投影矩阵,分析它每个元素怎么来的;
  5. 最后总结:投影矩阵的第几行第几列如何决定 w,w 和 z 的关系到底是什么。

全程会尽量避免晦涩的线性代数术语,但公式会写完整,你可以对着纸推一遍。


一、热身:再用超简短的大白话回顾一下“透视除法在干嘛”

在摄像机空间(Camera Space)里,一个点是(x, y, z)

  • x、y:横向、纵向偏移;
  • z:离相机的深度。

透视的直觉需求只有一句话:

这个点投影到屏幕上之后,
它的“屏幕坐标(x_screen, y_screen)”应该跟x/z,y/z成正比。

也就是:

x_screen ∝ x / z y_screen ∝ y / z

这就是“近大远小”的本质 —— z 越大(越远),x/z 越小,投影越靠近中心,看起来越小。

问题来了:矩阵乘法是线性的,/z是非线性的,怎么统一?

答案就是:

把坐标升到四维(x, y, z, 1)
设计一个 4×4 投影矩阵 P,
让乘完以后得到(x', y', z', w')
然后再做一次:

x_ndc = x' / w' y_ndc = y' / w' z_ndc = z' / w'

通过“除以 w”去实现原来想要的 “除以 z”。

那这个 w’ 到底是怎么算出来的?
——就是投影矩阵 P 的最后一行和原始 (x,y,z,1) 点的点积

我们下面就从最简情况开始推:让你看见 w 的来源,而不是“记住有个 w”。


二、从最简单的 2D 透视推起:一维上的 w 从哪来?

先别上来就 3D,我们先搞一个简化版:

  • 只有一个二维空间:横轴 x,深度轴 z(可想成“平面世界”);
  • 我们要把 (x, z) 投影到一条线(屏幕)上,只关注 x_screen;
  • 透视要求:x_screen ∝ x / z

2.1 直接用齐次坐标来搞

我们把 2D 点 (x, z) 写成齐次坐标(x, z, 1)
设计一个 3×3 的投影矩阵 P,使得:

[x', w']^T = P * [x, z, 1]^T 的前两维 最后 x_screen = x' / w'

为简单起见,先让我们只看第一行和第三行(x’, w’),假定:

x' = a * x + b * z + c * 1 w' = d * x + e * z + f * 1

我们希望:

x' / w' = k * x / z (k 是比例常数,表示最终映射屏幕的缩放)

这要求对所有 x,z 都成立,推导很复杂。
但我们可以直接“聪明设计”一个简单的形式:

让 w’ 只和 z 有关,比如:

w' = z

再让 x’ 只和 x 有关:

x' = x

这样:

x_screen = x' / w' = x / z

是不是刚好满足我们的要求?是的。

那对应的矩阵是什么?

矩阵 P 作用在(x, z, 1)上:

x' = 1 * x + 0 * z + 0 * 1 z' = 0 * x + 1 * z + 0 * 1 (我们先不管) w' = 0 * x + 1 * z + 0 * 1

也就是说:

P = | 1 0 0 | | 0 1 0 | | 0 1 0 |

这是个非常粗糙的例子,但它告诉你两件事:

  1. w’ 完全可以被设计成“等于 z”,这样/w等价/z
  2. w’ 的值其实就是“投影矩阵最后一行·原始向量”的结果,是矩阵设计出来的,而不是凭空冒出来。

3D 透视投影也是同理,只是矩阵换成 4×4,事情复杂一点。


三、上到 3D 透视:先忽略 near/far,只关心横纵与 z 的关系

我们现在进入真实的摄像机空间:

  • 一个点在 Camera Space 中是(x, y, z)
  • 把它写成齐次坐标:(x, y, z, 1)
  • 我们要构造一个 4×4 的 Projection 矩阵,使得:
ndc.x ∝ x / z ndc.y ∝ y / z

NDC 是做完透视除法后的坐标:

(x_ndc, y_ndc, z_ndc) = (x', y', z') / w'

3.1 先只看 x、y 与 w 的关系

我们先不管 z’,也忽略 near/far:

设 Projection 矩阵(简化版,只看和 x/w, y/w 有关的部分):

| A 0 0 0 | | 0 B 0 0 | | 0 0 ? ? | | 0 0 ? 0 |

作用在(x, y, z, 1)上:

x' = A * x y' = B * y z' = ... (先不管) w' = α * z + β * 1 (由第四行决定)

我们希望:

x_ndc = x' / w' = (A * x) / (α * z + β) y_ndc = y' / w' = (B * y) / (α * z + β)

如果我们想“本质上像 x/z”的形式,最简单的做法是:

  • 令 β = 0(w’ 不依赖常数,只依赖 z);
  • 令 α ≠ 0,使得w' = α * z

那么:

x_ndc = (A * x) / (α * z) = (A/α) * (x / z) y_ndc = (B * y) / (α * z) = (B/α) * (y / z)

这就是我们想要的形式 —— 比例 x/z、y/z,只是多了个缩放系数(A/α)(B/α)

这个缩放系数最终用于控制视野角(FOV)和画面宽高比(aspect),我们马上再讲。

关键点是:

w’ 可以简单设计成 “某个常数 × z”
于是/w'这一步就包含了/z的效果。

这就是那个神秘 w 的来源之一:
Projection 矩阵第四行,让 w’ 和原始 z 挂钩。

3.2 把 FOV 和宽高比塞进 A 和 B 里

透视相机有两个重要参数:

  • 垂直视野角fovY
  • 宽高比aspect = width / height

直观上:

  • y 方向 FOV 越大,同样 z 深度下,能看到的 y 范围越广;
  • x 方向和 y 方向的关系由aspect决定。

经过一番推导(这里不展开细节,只说结果和直觉),我们会得到:

A = 1 / (tan(fovY/2) * aspect) B = 1 / tan(fovY/2)

这两个值控制横、纵两个方向的“压缩程度”。

  • tan(fovY/2)决定了“在 z=1 的那一截平面上,能看到多高的 y”;
  • 取倒数就是“把这个高度映射回 [-1,1] 的范围”。

此时,我们已经有了:

x' = A * x y' = B * y w' = α * z (为了简单现在先让 α = 1,也就是 w' = z)

则:

x_ndc = x' / w' = A * x / z = x / (z * tan(fovY/2) * aspect) y_ndc = y' / w' = B * y / z = y / (z * tan(fovY/2))

这就是:视野角 + 透视 + 宽高比一起生效的结果。
你可以把它理解为:x/z 再除以个“窗口高度”,缩放到 [-1,1]。

此时 w’ 的来源已经非常清晰:

w’ 就是投影矩阵第四行和原始向量 (x, y, z, 1) 做点积得到的。
在这个简化里,它就是 z(或者某个常数乘以 z)。


四、加上 near / far:完整透视投影矩阵是怎么来的?

刚才我们只关心了 x/z、y/z,现在要把 z(深度)也搞清楚。
真实的透视投影还关心:

  • 近平面near = n
  • 远平面far = f

我们期望:

  • 在 Camera Space 中,z = n 的点映射到 NDC 的某个值(OpenGL 是 -1,DirectX 是 0);
  • z = f 的点映射到另一个值(OpenGL 是 1,DirectX 是 1);
  • 中间是某种“非线性插值” —— 这就是深度缓冲为什么近处密、远处稀的原因。

为了简单起见,我先以OpenGL 标准右手系、z∈[-1,1]的透视矩阵为例推一遍大致结构:

最终目标形式(OpenGL 常见):

P = | A 0 0 0 | | 0 B 0 0 | | 0 0 (f+n)/(n-f) 2fn/(n-f) | | 0 0 -1 0 |

这里:

  • A = 1 / (tan(fovY/2) * aspect)
  • B = 1 / tan(fovY/2)

看 w 行:(0, 0, -1, 0)
作用在(x, y, z, 1)上:

w' = 0*x + 0*y + (-1)*z + 0*1 = -z

这就是OpenGL 透视矩阵中的 w 来源,它就是-z

再看 z 行:(0, 0, (f+n)/(n-f), 2fn/(n-f))

z' = (f+n)/(n-f) * z + 2fn/(n-f) * 1

透视除法后:

z_ndc = z' / w' = [ (f+n)/(n-f) * z + 2fn/(n-f) ] / (-z)

整理一下:

z_ndc = - (f+n)/(n-f) - 2fn/[(n-f) * z]

你会发现这是跟 1/z 有关的非线性函数,这就是深度的非线性分布来源。

现在我们重点看 w’ 的部分即可:

在这个 OpenGL 透视矩阵里,w’ 始终等于 -z(Camera Space 中的 z 深度的相反数)。

所以:

  • x_ndc = x’ / w’ = (A*x) / (-z)
  • y_ndc = y’ / w’ = (B*y) / (-z)

注意 OpenGL 的 Camera Space 是看向 -Z,z 是负值,用 -z 相当于用深度的绝对值。
换个坐标系(比如 DirectX 左手系看 +Z),符号会变,但“w 和 z 强相关”这件事不会变。

你可以记住一句特别关键的话:

对于标准的透视投影矩阵,w’ 要么等于 z,要么等于 -z,要么等于乘了个常数的 z
所以透视除法本质上就是:

x_ndc ≈ (某个常数) * x / z y_ndc ≈ (某个常数) * y / z

那个“某个常数”里藏着 FOV 和 aspect 的信息。


五、再用 DirectX 风格(0~1 深度)的矩阵看一眼 w 的来源

为了让你对照两个风格,再看一个常见的 DirectX 左手系透视矩阵(z∈[0,1]):

一个典型版本是(看+Z):

P = | A 0 0 0 | | 0 B 0 0 | | 0 0 f/(f-n) 1 | | 0 0 -n*f/(f-n) 0 | ← 注意这里很多教材写法不同,我这里给的是一种常见变种

更常见的 Unity / D3D 风格(简化一下写):

Unity 官方文档里(左手坐标)Projection 矩阵的第三、四行经常是:

[ 0 0 1 0 ] // 这里行列顺序要注意具体实现,我用的是“列主序” vs “行主序”略有差别 [ 0 0 ? 0 ]

因为不同引擎内存布局和约定有所不同,具体符号你不用死记,只抓大框架:

  • 最后一行(决定 w)的那行,一定是:

    “某个常数 × z + 某个常数 × 1”,
    但在大多数标准透视矩阵里,都是简单到:
    “w’ = z” 或 “w’ = -z”。

这一点是核心:w 直接取自摄像机空间 z。


六、把全过程拉直:从 (x, y, z, 1) 到/w的显式公式

我们以 OpenGL 标准右手系透视矩阵为例,完整写一遍:

投影矩阵 P:

P = | A 0 0 0 | | 0 B 0 0 | | 0 0 (f+n)/(n-f) 2fn/(n-f) | | 0 0 -1 0 |

顶点在摄像机空间:v = (x, y, z, 1)

6.1 乘投影矩阵:v’ = P * v

得到 Clip Space 坐标(x', y', z', w')

x' = A * x y' = B * y z' = (f+n)/(n-f) * z + 2fn/(n-f) * 1 w' = -1 * z

注意:就是这么直接,w’ 就是 -z。

6.2 透视除法:NDC = v’ / w’

x_ndc = x' / w' = (A * x) / (-z) y_ndc = y' / w' = (B * y) / (-z) z_ndc = z' / w' = [ (f+n)/(n-f) * z + 2fn/(n-f) ] / (-z)

你可以看到:

  • x_ndc 与 x/z 成正比;
  • y_ndc 与 y/z 成正比;
  • z_ndc 是一个关于 1/z 的函数(非线性深度)。

所以:

那个神秘的 w 分量,在 OpenGL 标准透视矩阵中,就是 Camera Space z 的相反数:w’ = -z。
它一半参加裁剪(Clip),一半参加透视除法,
/w'这一步真正完成了“近大远小”。

在 DirectX / Unity 风格下,你会看到有的实现是w' = z而不是-z
只是坐标系和矩阵的符号习惯不同,但核心关系不变:w ~ z


七、再把问题原话完全对上:

“那个神秘的 w 分量具体数值来自哪里?具体数学推导是什么?”

现在可以给你一个高度精炼、但对应你问题的答案:

7.1 w 的数值来自哪里?

  • 从数学上说:

    w’ = (投影矩阵的第 4 行) · (原始齐次坐标向量)

    对点(x, y, z, 1)来说:

    w' = p40 * x + p41 * y + p42 * z + p43 * 1

    在标准透视投影矩阵的设计中,这一行通常是:

    [0, 0, ±1, 0]

    于是:

    w' = ± z
  • 从直觉上说:

    w 就是 Camera Space 中 z 深度的一个线性函数(常常就是 z 或 -z),
    这样/w就变成/z,实现透视缩放。

7.2 数学推导的主干逻辑是什么?

  1. 目标:我们想要最终的 NDC 满足

    x_ndc ∝ x / z y_ndc ∝ y / z
  2. 用齐次坐标:
    把原始点写成(x, y, z, 1),用矩阵 P 变成(x', y', z', w')
    NDC =(x'/w', y'/w', z'/w')

  3. 为了让x'/w'出现x/z的形式,我们设计:

    w’ 跟 z 成比例,即w' = α * z
    x’ 跟 x 成比例,即x' = A * x
    同理 y’ 跟 y 成比例。

    这样:

    x_ndc = x'/w' = (A/α) * (x / z) y_ndc = y'/w' = (B/α) * (y / z)
  4. 再根据 FOV 和 aspect 的几何关系,确定 A、B 的具体值:

    A = 1/(tan(fovY/2) * aspect)
    B = 1/tan(fovY/2)

  5. 再根据希望 z_ndc 在 near, far 映射到 -1/1 或 0/1 的要求,
    推出 P 的第三行(z’ 的线性组合),得到完整的四行四列。

整个推导过程中的“关键一步”就是:

我们强行把 w’ 设计成 z 的线性函数(最简单是 w’=z 或 w’=-z),
这样透视除法/w就自然而然变成了/z的透视缩放。


八、最后用非常粗暴的几句话,帮你把这套东西永久刻进脑子里

  1. w 是谁?

    • 它不是凭空冒出来的,它就是:

      “Projection 矩阵最后一行 × (x, y, z, 1)” 的结果;

    • 在常规透视矩阵里,这一行是[0, 0, ±1, 0]
      所以 w’ = ±z。
  2. 为什么要/w

    • 因为我们想要/z,但矩阵乘法不会自动带/z
    • 把“深度 z”灌进 w’,之后统一/w',就等价/z了。
  3. 透视除法的公式就是:

    v_clip = P * v_camera // 得到 (x', y', z', w') v_ndc = v_clip / w' // (x'/w', y'/w', z'/w')

    在标准透视矩阵中,
    w' = zw' = -z
    所以:

    x_ndc = const * x / z y_ndc = const * y / z
  4. 投影矩阵的推导思路:

    • 先从平面几何出发,确立:FOV、aspect 决定 A、B;
    • 再确定 near、far 映射到 z_ndc 的目标范围;
    • 设计 z’ 的线性表达;
    • 把 w’ 定成与 z 线性相关;
    • 最后得到完整的 4×4 矩阵。
  5. 所以,w 其实一点也不神秘:

    • 它就是“为了方便 GPU 用矩阵乘法做透视,把深度 z 暂存到第四个分量里”;
    • 透视除法/w就是“把 z 这层透视账结清”的那一下;
    • 你看到的那一行ndc = clipPos / clipPos.w
      就是全世界图形学工程师共同约定俗成的:

      “好,现在咱们把透视真的做出来吧”。

如果你愿意,你甚至可以在纸上写一个简单的 Projection 矩阵(比如 OpenGL 那个),
取几组 (x,y,z),自己算一遍P * (x,y,z,1),再除以 w,
你会非常直观地看到:点越远,结果越靠近中心,
同时 w 每次真的就是 z 或 -z,这样 w 的来源就不再是“玄学”,而是能算的数。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 18:10:36

Yuzu模拟器终极配置手册:从零开始打造完美游戏体验

还在为Nintendo Switch模拟器的性能表现而困扰?画面卡顿、频繁闪退、配置复杂,这些问题是否让你望而却步?作为你的专属技术向导,我将带你层层深入,从基础安装到高级调优,彻底解决这些技术痛点。 【免费下载…

作者头像 李华
网站建设 2026/4/11 3:41:27

如何快速配置DTCoreText:HTML富文本渲染终极指南

如何快速配置DTCoreText:HTML富文本渲染终极指南 【免费下载链接】DTCoreText Methods to allow using HTML code with CoreText 项目地址: https://gitcode.com/gh_mirrors/dt/DTCoreText 在iOS开发中,处理HTML富文本显示一直是个痛点。DTCoreTe…

作者头像 李华
网站建设 2026/4/16 1:28:13

AltStore实战指南:突破iOS限制的自由应用安装方案

AltStore实战指南:突破iOS限制的自由应用安装方案 【免费下载链接】AltStore AltStore is an alternative app store for non-jailbroken iOS devices. 项目地址: https://gitcode.com/gh_mirrors/al/AltStore 在iOS生态系统中,用户常常面临应用选…

作者头像 李华