67-GAMES101学习笔记(02)-Rasterization前置:从裁剪空间到屏幕坐标
前言
在上一篇笔记中,我们学习了三维模型如何经过 MVP 矩阵,从模型空间一路变换到裁剪空间。 但裁剪空间并不是最终显示在屏幕上的位置。 这一节,我们将继续探索 GPU 如何把裁剪空间中的顶点转换到屏幕坐标,并介绍进入 Rasterization(光栅化)之前必须理解的几个概念:
- NDC
- Viewport Transform
- Depth Buffer
NDC:标准化设备坐标
现在我们知道,裁剪坐标中的顶点坐标是四维齐次坐标,接下来怎么将其转换为屏幕上的二维坐标呢?
$$
(x_c,y_c,z_c,w_c)
$$
答案是透视除法(Perspective Divide),GPU会对每个顶点进行下面的透视除法计算:
$$
x_{ndc}=\frac{x_c}{w_c}
$$
$$
y_{ndc}=\frac{y_c}{w_c}
$$
$$
z_{ndc}=\frac{z_c}{w_c}
$$
经过透视除法之后,四维齐次坐标重新变回了三维坐标:
$$
(x_{ndc},y_{ndc},z_{ndc})
$$
这个坐标就是 NDC(Normalized Device Coordinates,标准化设备坐标)。
在 OpenGL / WebGL 中,可见范围会被统一到:
$$
[-1,1]\times[-1,1]\times[-1,1]
$$
也就是说,无论相机使用怎样的 FOV、Aspect、Near 和 Far,最终都会被转换到一个统一的标准坐标范围。
至此,从观察空间到 NDC 的过程可以总结为:
View Space → Projection Matrix → Clip Space → Clipping → Perspective Divide → NDC
接下来,只需要再把 NDC 映射到实际的视口,就能得到最终的屏幕坐标。
Viewport Transform:视口变换
有了 NDC 坐标后,我们终于来到了最后一步——Viewport Transform(视口变换)。
这一步相比之前就简单多了。
NDC 已经把不同相机、不同视锥体产生的坐标统一到了一个标准范围。视口变换要做的,就是把 NDC 中 X、Y 方向的 [-1,1] 映射到实际的视口范围。
假设视口的宽高分别为 $width$ 和 $height$,那么:
$$
x_{screen}=\frac{x_{ndc}+1}{2}\times width
$$
$$
y_{screen}=\frac{y_{ndc}+1}{2}\times height
$$
例如一个大小为 1920 × 1080 的视口,NDC 的中心:
$$
(0,0)
$$
最终会被映射到:
$$
(960,540)
$$
至此,一个最初定义在模型空间中的三维顶点,经过一系列坐标变换,终于找到了自己在二维屏幕上的位置。
整个过程可以总结为:
Model Space → World Space → View Space → Clip Space → NDC → Screen Space
NDC 坐标中的 Z 有什么用?
细心的朋友们可能已经发现了一个问题:在上面的视口变换中,我们只使用了 NDC 中的 X 和 Y 坐标,并没有用到 Z。
那么,NDC 中的 Z 坐标去哪了?
实际上,Z 坐标非常重要。
虽然屏幕最终是二维的,我们只需要 X、Y 来确定一个点在屏幕上的位置,但三维场景中可能存在多个物体投影到同一个屏幕位置。
例如在一张人像照片中,画面可能同时存在前景、人物和背景。它们之间具有明确的遮挡关系:前景会遮挡人物,人物又会遮挡身后的背景。
如果只保留 X、Y 坐标,这些物体投影到二维屏幕后,我们就无法知道谁在前、谁在后。
而 NDC 中的 Z 坐标保留了顶点经过投影后的深度信息。在后续渲染过程中,这些深度信息会被写入 Depth Buffer(深度缓冲区),并通过 Depth Test(深度测试) 判断物体之间的遮挡关系。
简单来说:
- X、Y 决定物体出现在屏幕的哪里;
- Z 决定同一个位置上,谁应该被看到。
所以,虽然 Z 没有直接参与二维屏幕位置的计算,但它仍然是三维场景能够正确呈现前后遮挡关系的关键。

