如果你刚接触计图(计算机图形学),大概率最先接触到的渲染方式就是前向渲染(Forward Rendering)。我刚开始学的时候,对“前向”这两个字完全没概念,以为就是“把物体画出来”这么简单。直到自己动手实现一个带多个点光源的场景,才发现前向渲染不仅在图形学里是基础,在实际工程项目里也是一个需要反复权衡的架构选择。这篇文章想把前向渲染这件事讲透:它到底渲染了什么、为什么简单场景下表现很好、光源一多又为什么会卡、以及真实项目里怎么避坑。无论你是刚入门图形学,还是在做引擎选型、想要理解渲染管线的底层逻辑,这篇内容应该都能帮到你。
1. 前向渲染到底渲染了什么:从顶点到像素的一条主链路
1.1 “前向”二字的含义
很多教程会直接说“前向渲染就是每个物体逐顶点、逐像素地走一遍渲染管线”,但“前向”到底对谁而言,往往不说清楚。我自己的理解是:它强调渲染过程是直接、即时、面向当前物体的。你提交一个球体,GPU就立刻处理这个球体,从顶点着色到光栅化再到片元光照,一条路走到底;处理完一个物体,再处理下一个。整个过程不会把场景信息先缓存到一个中间缓冲区里,也不会把光照计算推迟到后面某个阶段统一做。
这个“即时”特性,正是前向渲染和延迟渲染(Deferred Rendering)最大的分水岭。延迟渲染会先把场景的几何信息(位置、法线、颜色等)打包渲染到一张或多张中间纹理(G-Buffer),然后生成一个全屏四边形,在屏幕空间统一计算光照。前向渲染则没有这一步,把光照直接融进每个片元的计算里。
所以“前向”其实有两层含义:一是计算顺序靠前,你在片元阶段就把光源和表面属性做了乘法;二是流程直观,CPU提交什么,GPU就渲染什么,中间没有“绕一圈”的延迟阶段。
1.2 核心管线的五步:顶点、装配、光栅化、片元、输出
不管前向渲染的代码怎么写,底层都要经过下面这几步。我把每一步用工程视角拆开来看,而不是只背概念:
-
顶点处理:CPU把顶点数据(位置、法线、UV等)交给GPU,顶点着色器对每个顶点执行坐标变换。模型本地坐标经过模型矩阵、视图矩阵、投影矩阵,最终变成裁剪坐标,再映射到屏幕窗口坐标。这一步决定了物体的形状在屏幕上长什么样。
-
图元装配:GPU把顶点组装成三角形、线段或点。这里会做裁剪,把不在视锥体内的部分切掉。装配完成后,三角形就会被送去光栅化。
-
光栅化:把连续的三角形变成离散的像素片元。这一步会为每个像素生成一个“片元”,片元可以理解为“还没有最终确定颜色的候选像素”。片段内的颜色、法线、纹理坐标,都是通过对三个顶点做插值得到的。
-
片元处理:片元着色器拿到插值后的数据,执行光照计算。前向渲染的核心光照逻辑,几乎都集中在这一步。接下来要展示的Blinn-Phong光照模型,就是在片元阶段完成的。
-
测试与混合:每个片元还要经过深度测试、模板测试等,决定它是否真的能被写入帧缓冲。如果开了混合,比如做半透明物体,片元的颜色会和已有颜色按Alpha混合。
前向渲染的“一条路走到底”,指的就是从第1步到第5步,针对同一个物体连续完成。你不需要额外建立G-Buffer,也不需要把光照留到全屏pass再算。
1.3 光照计算的位置:片元着色器里发生了什么
既然光照主要发生在片元着色器里,我就以最经典的Blinn-Phong光照模型为例,把“一个片元被点亮”的过程拆开:
- 环境光(Ambient):模拟场景中散射到各处的间接光,通常用一个很小的常量乘上光源颜色,保证背光面不至于全黑。
- 漫反射(Diffuse):表面法线方向越对着光源,亮度越高。用
max(dot(N, L), 0)控制角度,保证背光面为零。 - 高光(Specular):Blinn-Phong用半程向量
H = normalize(L + V)代替反射向量,然后用pow(max(dot(N, H), 0), shininess)控制高光大小。这里V是视线方向。
片元着色器要做的事,就是把这些分量加起来,再乘上物体本身的颜色。听起来不复杂,但它的成本是按像素计算的。一个1920×1080的屏幕上,如果有500万个片元经过片元着色器,而场景里有10盏灯,那理想情况下每帧就要做5000万次光照计算。这还只是理想状态,实际还会更高。这就是为什么
