什么是光栅化
简单说,光栅化(Rasterization)就是把3D的几何图形(比如三角形)转换成屏幕上的像素点。这是实时渲染的核心步骤,你玩的每一款3D游戏,背后都在疯狂地做光栅化。
为什么是三角形?因为三角形是最简单的多边形,三个点确定一个平面,不会出现凹多边形的问题。复杂的3D模型都是由成千上万个三角形组成的。
基本流程
光栅化的过程大致是这样的:
- 顶点处理:把3D坐标变换到屏幕坐标(MVP变换)
- 图元装配:把三个顶点组装成三角形
- 三角形遍历:找出哪些像素被这个三角形覆盖
- 片元处理:计算每个被覆盖像素的颜色
- 深度测试:决定哪个三角形在前面
坐标变换
3D物体从模型空间到屏幕需要经过几次变换:
模型空间 → 世界空间 → 观察空间 → 裁剪空间 → 屏幕空间
Model矩阵 View矩阵 Projection矩阵 视口变换
Model矩阵:把物体放到世界中正确的位置、旋转、缩放。
View矩阵:相当于相机,决定了从哪个角度看。
Projection矩阵:透视投影,让远处的东西看起来小。这就是为什么铁轨远处会交汇。
三角形遍历
这是光栅化最核心的步骤。给定屏幕上的三角形,怎么判断哪些像素在三角形内部?
最常用的方法是边函数(Edge Function)。三角形的三条边把平面分成两半,如果一个点在三条边的同一侧,那它就在三角形内部。
// 判断点P是否在边AB的左侧
float edgeFunction(vec2 A, vec2 B, vec2 P) {
return (P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x);
}
如果三个边函数的值都同号(都正或都负),点就在三角形内部。
深度测试
一个像素可能被多个三角形覆盖(比如前面的物体挡住了后面的)。深度测试通过比较Z值来决定哪个三角形"赢"。
通常使用Z-Buffer(深度缓冲):为每个像素存储一个深度值,新的片元只有Z值更小(更近)才能写入。
// 伪代码
if (fragment.depth < zbuffer[x][y]) {
zbuffer[x][y] = fragment.depth;
framebuffer[x][y] = fragment.color;
}
插值
三角形内部的像素需要计算颜色、纹理坐标等属性。这些属性在三个顶点上已知,内部的值通过重心坐标插值得到。
简单说就是:离哪个顶点近,就更偏向那个顶点的属性值。数学上用重心坐标(barycentric coordinates)来表示三角形内部任意点的位置。
和光线追踪的区别
光栅化是从物体出发,把物体投影到屏幕。光线追踪是从相机出发,发射光线看碰到什么。
- 光栅化:快,适合实时渲染(游戏)。但阴影、反射、折射需要额外处理
- 光线追踪:慢,但能自然地处理全局光照。电影CG用的就是光线追踪
现在GPU有了RT Core,实时光线追踪也开始普及了。但光栅化仍然是基础,大部分游戏还是以光栅化为主,光线追踪为辅。
学习资源推荐
图形渲染入门的资料推荐:
- LearnOpenGL(
learnopengl.com):最推荐!从光栅化基础到高级渲染技术,每一步都有代码示例。中文翻译版质量很高 - 赵新政的图形学教程:Telegram上有免费版本,用中文讲解OpenGL和图形学原理,适合不想啃英文的同学
- GAMES101:闫令琪老师的图形学课程,B站有免费视频,理论讲得非常好
LearnOpenGL是入门首选,赵新政的教程适合中文用户快速上手。GAMES101偏理论但讲得最深入。三个配合着看效果最好。