ui设计作品网站,优秀个人博客网站,优化seo搜索,北京网站设计公司新鸿儒先用直觉回顾一下“为什么要 /w#xff0c;w 大概干嘛的”#xff0c;把脑子热起来#xff1b;然后从一个最简单的 2D→1D 透视投影例子开始推#xff0c;先在一维上看 w 从哪来#xff1b;再上升到 3D 透视#xff0c;从 Camera Space 到 NDC#xff08;忽略 near/far、…先用直觉回顾一下“为什么要/ww 大概干嘛的”把脑子热起来然后从一个最简单的 2D→1D 透视投影例子开始推先在一维上看 w 从哪来再上升到 3D 透视从 Camera Space 到 NDC忽略 near/far、只看 x/y 与 z 的关系推一遍再引入完整的 near / far推标准的 4×4 透视投影矩阵分析它每个元素怎么来的最后总结投影矩阵的第几行第几列如何决定 ww 和 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 |这是个非常粗糙的例子但它告诉你两件事w’ 完全可以被设计成“等于 z”这样/w等价/zw’ 的值其实就是“投影矩阵最后一行·原始向量”的结果是矩阵设计出来的而不是凭空冒出来。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 / zNDC 是做完透视除法后的坐标(x_ndc, y_ndc, z_ndc) (x, y, z) / w3.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”的形式最简单的做法是令 β 0w’ 不依赖常数只依赖 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)决定了“在 z1 的那一截平面上能看到多高的 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 是 -1DirectX 是 0z f 的点映射到另一个值OpenGL 是 1DirectX 是 1中间是某种“非线性插值” —— 这就是深度缓冲为什么近处密、远处稀的原因。为了简单起见我先以OpenGL 标准右手系、z∈[-1,1]的透视矩阵为例推一遍大致结构最终目标形式OpenGL 常见P | A 0 0 0 | | 0 B 0 0 | | 0 0 (fn)/(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, (fn)/(n-f), 2fn/(n-f))z (fn)/(n-f) * z 2fn/(n-f) * 1透视除法后z_ndc z / w [ (fn)/(n-f) * z 2fn/(n-f) ] / (-z)整理一下z_ndc - (fn)/(n-f) - 2fn/[(n-f) * z]你会发现这是跟 1/z 有关的非线性函数这就是深度的非线性分布来源。现在我们重点看 w’ 的部分即可在这个 OpenGL 透视矩阵里w’ 始终等于 -zCamera Space 中的 z 深度的相反数。所以x_ndc x’ / w’ (A*x) / (-z)y_ndc y’ / w’ (B*y) / (-z)注意 OpenGL 的 Camera Space 是看向 -Zz 是负值用 -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]一个典型版本是看ZP | 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 标准右手系透视矩阵为例完整写一遍投影矩阵 PP | A 0 0 0 | | 0 B 0 0 | | 0 0 (fn)/(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 (fn)/(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 [ (fn)/(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 数学推导的主干逻辑是什么目标我们想要最终的 NDC 满足x_ndc ∝ x / z y_ndc ∝ y / z用齐次坐标把原始点写成(x, y, z, 1)用矩阵 P 变成(x, y, z, w)。NDC (x/w, y/w, z/w)。为了让x/w出现x/z的形式我们设计w’ 跟 z 成比例即w α * zx’ 跟 x 成比例即x A * x同理 y’ 跟 y 成比例。这样x_ndc x/w (A/α) * (x / z) y_ndc y/w (B/α) * (y / z)再根据 FOV 和 aspect 的几何关系确定 A、B 的具体值A 1/(tan(fovY/2) * aspect)B 1/tan(fovY/2)再根据希望 z_ndc 在 near, far 映射到 -1/1 或 0/1 的要求推出 P 的第三行z’ 的线性组合得到完整的四行四列。整个推导过程中的“关键一步”就是我们强行把 w’ 设计成 z 的线性函数最简单是 w’z 或 w’-z这样透视除法/w就自然而然变成了/z的透视缩放。八、最后用非常粗暴的几句话帮你把这套东西永久刻进脑子里w 是谁它不是凭空冒出来的它就是“Projection 矩阵最后一行 × (x, y, z, 1)” 的结果在常规透视矩阵里这一行是[0, 0, ±1, 0]所以 w’ ±z。为什么要/w因为我们想要/z但矩阵乘法不会自动带/z把“深度 z”灌进 w’之后统一/w就等价/z了。透视除法的公式就是v_clip P * v_camera // 得到 (x, y, z, w) v_ndc v_clip / w // (x/w, y/w, z/w)在标准透视矩阵中w z或w -z所以x_ndc const * x / z y_ndc const * y / z投影矩阵的推导思路先从平面几何出发确立FOV、aspect 决定 A、B再确定 near、far 映射到 z_ndc 的目标范围设计 z’ 的线性表达把 w’ 定成与 z 线性相关最后得到完整的 4×4 矩阵。所以w 其实一点也不神秘它就是“为了方便 GPU 用矩阵乘法做透视把深度 z 暂存到第四个分量里”透视除法/w就是“把 z 这层透视账结清”的那一下你看到的那一行ndc clipPos / clipPos.w就是全世界图形学工程师共同约定俗成的“好现在咱们把透视真的做出来吧”。如果你愿意你甚至可以在纸上写一个简单的 Projection 矩阵比如 OpenGL 那个取几组 (x,y,z)自己算一遍P * (x,y,z,1)再除以 w你会非常直观地看到点越远结果越靠近中心同时 w 每次真的就是 z 或 -z这样 w 的来源就不再是“玄学”而是能算的数。