顶点着色器输出后,三角形为什么是四个分量 (x, y, z, w)?
一、先给结论(最重要)
顶点着色器输出的不是“屏幕坐标”,
而是裁剪空间齐次坐标(Clip Space, Homogeneous Coordinates):
(x, y, z, w)其中:
分量 | 含义 |
|---|---|
x | 裁剪空间 X |
y | 裁剪空间 Y |
z | 裁剪空间 Z(深度) |
w | 齐次坐标的“除数” |
👉真正的屏幕坐标 = (x/w, y/w, z/w)
二、为什么不是 (x, y, z) 就够了?
1️⃣ 透视投影无法用 3D 坐标正确表达
在透视投影下:
- 远处的物体更小
- 平行线会交汇(透视收敛)
如果用普通 3D 坐标:
(x, y, z)你无法在后续步骤中做正确的透视除法
2️⃣ 齐次坐标的本质作用
w的作用是:
把“透视除法”延迟到最后一步
这样:
- 裁剪
- 插值
- 深度计算
都可以在线性空间 中正确进行
三、这四个值是怎么来的?
1️⃣ 顶点着色器中你通常写的代码
gl_Position = projection * view * model * vec4(aPos, 1.0);最终得到的:
gl_Position = (x, y, z, w)2️⃣ 投影矩阵做了什么?
以透视投影为例:
x_clip = x_eye * fov_scale / aspect y_clip = y_eye * fov_scale z_clip = 混合(near, far) w_clip = -z_eye✅注意:w 通常等于 -z_eye
四、为什么 z 也要保留?
1️⃣ z 不是“深度值本身”
在裁剪空间中:
z ∈ [-w, w]它用于:
- 深度缓冲(Z-Buffer)
- 裁剪判断
- 透视正确插值
2️⃣ 屏幕深度是“非线性”的
经过透视除法后:
depth = z/w结果:
- 近处精度高
- 远处精度低
✅ 这是故意设计的(符合人眼)
五、w 到底为什么这么重要?
1️⃣ 透视除法(Perspective Division)
GPU 在光栅化前自动做:
x_ndc = x / w y_ndc = y / w z_ndc = z / w得到NDC(标准化设备坐标):
x ∈ [-1, 1] y ∈ [-1, 1] z ∈ [-1, 1]2️⃣ 再映射到屏幕
screen_x = (x_ndc + 1) * width / 2 screen_y = (1 - y_ndc) * height / 2👉 这才是你最终看到的像素位置
六、为什么插值也必须用 (x, y, z, w)?
❌ 错误做法
直接在屏幕空间插值 (x, y)
→ 近大远小会变形
✅ 正确做法(GPU 实际行为)
- 在裁剪空间 插值 (x, y, z, w)
- 对每个像素做:
x/w, y/w, z/w✅ 保证:
- 纹理不变形
- 深度正确
- 透视正确
七、w 还能干什么?(高级但重要)
1️⃣ 投影纹理(Projective Texture)
uv = (x/w, y/w)2️⃣ 阴影贴图
light_space = x/w3️⃣ 顶点属性插值
颜色、法线、UV 都“除以 w”
八、一句话总结
顶点着色器输出的 (x, y, z, w)
不是屏幕坐标,而是“透视正确的中间表达”
w是透视投影的灵魂- 没有
w,光栅化会全错 - 屏幕坐标 =
(x/w, y/w)