Skip to content

着色(光照与基本着色模型)

在上一章中,片元已经通过覆盖测试、深度测试与抗锯齿机制,成为真正有机会写入屏幕的候选颜色。但此时还有一个关键问题没有回答:这个片元到底应该是什么颜色?

如果一个三角形只携带固定颜色,那么整个物体会显得像一张没有体积的剪纸。真实世界中的明暗变化来自光线与表面之间的相互作用:同一块材质,在朝向光源时更亮,在背向光源时更暗;当表面较光滑时,还会出现集中的镜面高光。

着色 (Shading) 正是在片元或顶点层面,根据几何、光照、材质与观察方向计算最终颜色的过程。本章先讨论最基础、最常见的局部光照模型,它们虽然不是完整的物理真实模拟,却构成了理解后续 PBR、阴影、全局光照与路径追踪的入口。


光栅化器生成片元后,片元着色器 (Fragment Shader / Pixel Shader) 通常会接收到一组经过插值的数据:

  • 位置 (Position):片元在世界空间或观察空间中的位置。
  • 法线 (Normal):表面在该点的朝向。
  • 材质参数 (Material Parameters):基础颜色、粗糙度、镜面强度等。
  • 纹理坐标 (UV):用于从纹理图像中采样颜色或法线。
  • 光源信息 (Light Data):光源方向、颜色、强度、距离衰减等。

在最基本的局部光照模型中,着色计算只关心当前表面点、直接光源和观察者之间的关系,而暂时忽略其它物体反射来的间接光。这种模型称为局部光照模型 (Local Illumination Model)

一个经典的局部着色结果可拆分为三部分:

L=Lambient+Ldiffuse+LspecularL = L_{ambient} + L_{diffuse} + L_{specular}

其中,LambientL_{ambient} 表示环境光近似项,LdiffuseL_{diffuse} 表示漫反射项,LspecularL_{specular} 表示镜面反射高光项。接下来我们逐一解释它们的几何意义。


光照模型的核心变量是三个方向向量:

  • n^\hat{n}:单位表面法线,表示表面朝向。
  • l^\hat{l}:单位光照方向,通常表示从表面点指向光源的方向。
  • v^\hat{v}:单位观察方向,表示从表面点指向相机的方向。

所有方向向量都必须归一化,否则点乘结果无法稳定对应角度余弦值。回忆第一章中的点乘关系:

a^b^=cosθ\hat{a} \cdot \hat{b} = \cos\theta

因此,n^l^\hat{n} \cdot \hat{l} 可以直接衡量表面是否正对光源。当该值接近 11 时,光线几乎垂直照射表面,单位面积接收的光能最多;当该值接近 00 时,光线擦过表面,能量被摊薄;当该值小于 00 时,光源位于表面背面,物理上不应产生正面照明。

局部光照模型中的法线、光照方向、观察方向与高光方向

这张图把后续公式中最常出现的方向向量放在同一个表面点上:漫反射主要看 n^l^\hat{n} \cdot \hat{l},Blinn-Phong 高光主要看 n^h^\hat{n} \cdot \hat{h},而 Phong 高光会显式使用理想反射方向 r^\hat{r}


在真实世界中,即使某个表面没有被主光源直接照亮,它也未必完全黑暗。光线会在墙面、地面和其它物体之间多次反射,形成复杂的间接光照。

基础局部模型暂时不计算真实间接光,而是用一个常量项粗略补偿:

Lambient=kaIaL_{ambient} = k_a I_a

其中,IaI_a 表示环境光强度,kak_a 表示材质对环境光的响应系数。这个项没有方向性,不依赖法线,也不会产生真实的阴影层次。它的作用只是避免背光区域完全死黑。


漫反射 (Diffuse Reflection) 描述粗糙表面把入射光向各个方向均匀散射的现象。粉笔、粗糙墙面、未抛光木头等材质都可以用漫反射作为主要近似。

Lambert 漫反射模型基于一个简单但非常重要的几何事实:表面接收到的有效光照强度与入射光方向和表面法线夹角的余弦成正比。这就是Lambert 余弦定律 (Lambert’s Cosine Law)

Ldiffuse=kdIlmax(n^l^,0)L_{diffuse} = k_d I_l \max(\hat{n} \cdot \hat{l}, 0)

其中:

  • kdk_d 是材质的漫反射系数,通常与物体基础颜色相关。
  • IlI_l 是光源强度或颜色。
  • max(n^l^,0)\max(\hat{n} \cdot \hat{l}, 0) 用于截断背面光照。

这个公式把“表面朝向光源的程度”直接映射为亮度。它不关心观察者位置,因此一个完全漫反射表面从不同视角看去,局部亮度不会因为相机移动而发生镜面高光式变化。

假设某片元的单位法线为 n^=(0,0,1)\hat{n} = (0, 0, 1),光照方向为 l^=(0.6,0,0.8)\hat{l} = (0.6, 0, 0.8)。则:

n^l^=0×0.6+0×0+1×0.8=0.8\hat{n} \cdot \hat{l} = 0 \times 0.6 + 0 \times 0 + 1 \times 0.8 = 0.8

若材质漫反射系数 kd=0.7k_d = 0.7,光源强度 Il=1.0I_l = 1.0,则漫反射贡献为:

Ldiffuse=0.7×1.0×0.8=0.56L_{diffuse} = 0.7 \times 1.0 \times 0.8 = 0.56

这说明该表面正以较高角度面向光源,因此会呈现明显亮部。


仅有 Lambert 漫反射时,物体可以呈现基本体积感,但无法表现光滑表面的高光。塑料、陶瓷、金属和湿润表面都会在某些观察角度形成集中的亮斑,这就是镜面反射 (Specular Reflection)

Phong 模型的思想是:如果观察方向 v^\hat{v} 接近理想反射方向 r^\hat{r},则高光强;如果偏离较远,则高光迅速衰减。

理想反射方向可由入射方向围绕法线反射得到:

r^=2(n^l^)n^l^\hat{r} = 2(\hat{n} \cdot \hat{l})\hat{n} - \hat{l}

Phong 镜面项写作:

Lspecular=ksIlmax(r^v^,0)pL_{specular} = k_s I_l \max(\hat{r} \cdot \hat{v}, 0)^p

其中,ksk_s 是镜面反射强度,pp高光指数 (Shininess / Specular Power)pp 越大,高光越集中、越尖锐;pp 越小,高光越宽、越柔和。


Phong 模型需要计算反射向量 r^\hat{r},在早期硬件或大量光源场景中成本较高。Blinn-Phong 模型引入了一个更方便的向量:半程向量 (Half Vector)

半程向量定义为光照方向与观察方向的角平分方向:

h^=l^+v^l^+v^\hat{h} = \frac{\hat{l} + \hat{v}}{||\hat{l} + \hat{v}||}

如果表面法线 n^\hat{n} 接近半程向量 h^\hat{h},意味着法线正好位于光源与观察者之间的反射关系附近,此时会产生镜面高光。Blinn-Phong 的镜面项为:

Lspecular=ksIlmax(n^h^,0)pL_{specular} = k_s I_l \max(\hat{n} \cdot \hat{h}, 0)^p

它的几何意义比公式看起来更直观:当表面朝向刚好把光线“弹”向观察者时,高光出现;偏离越大,高光越快衰减。

前向采样点估算
N · L0.000
N · H0.000
漫反射项0.000
镜面项0.000
L = ambient + diffuse · max(N · L, 0) + specular · max(N · H, 0)s
拖动光源方向与材质参数,观察法线、光照方向、视线方向如何共同决定漫反射亮度与高光形状。黄色箭头表示光照方向,青色箭头表示观察方向,白色箭头表示当前前向采样点的表面法线。

将环境光、漫反射与镜面项组合后,经典 Blinn-Phong 模型可以写作:

L=kaIa+kdIlmax(n^l^,0)+ksIlmax(n^h^,0)pL = k_a I_a + k_d I_l \max(\hat{n} \cdot \hat{l}, 0) + k_s I_l \max(\hat{n} \cdot \hat{h}, 0)^p

若把颜色也纳入考虑,kdk_d 往往是一个 RGB 向量,例如物体的基础颜色;IlI_l 也可以是 RGB 光源颜色。最终片元颜色就是这些分量逐通道相乘与相加的结果。

  1. 准备输入数据:从插值得到片元位置、法线、基础颜色,并从场景中读取光源位置与相机位置。
  2. 统一坐标空间:确保 n^\hat{n}l^\hat{l}v^\hat{v} 都位于同一空间,例如世界空间或观察空间。
  3. 归一化方向向量:对法线、光照方向、观察方向执行 normalize(),保证点乘结果可以解释为角度余弦。
  4. 计算漫反射:使用 max(n^l^,0)\max(\hat{n} \cdot \hat{l}, 0) 得到表面正对光源的程度。
  5. 计算镜面高光:使用半程向量 h^\hat{h} 与高光指数 pp 估算光滑表面的高光贡献。
  6. 累加颜色项:将环境光、漫反射和镜面项相加,并在必要时执行色调映射或伽马校正。

下面的伪代码只展示局部光照计算的核心关系,省略了具体 API 的输入输出绑定:

vec3 N = normalize(normal);
vec3 L = normalize(lightPosition - worldPosition);
vec3 V = normalize(cameraPosition - worldPosition);
vec3 H = normalize(L + V);
float ndotl = max(dot(N, L), 0.0);
float ndoth = max(dot(N, H), 0.0);
vec3 ambient = ambientColor * baseColor;
vec3 diffuse = lightColor * baseColor * ndotl;
vec3 specular = lightColor * specularColor * pow(ndoth, shininess);
vec3 color = ambient + diffuse + specular;

这段代码正是本章公式在 GPU 上的直接映射。它把表面几何关系转化为颜色贡献,并在每个片元上并行执行。


到目前为止,我们默认光照方向对整个物体近似一致,这对应方向光 (Directional Light),例如远处太阳光。若光源位于场景中的某个具体位置,例如灯泡、火把或发光球体,则它更接近点光源 (Point Light)

点光源的方向需要针对每个片元重新计算:

l^=PlightPsurfacePlightPsurface\hat{l} = \frac{P_{light} - P_{surface}}{||P_{light} - P_{surface}||}

光照强度通常还会随距离衰减。理想物理模型中,光能分布在越来越大的球面上,因此强度与距离平方成反比:

attenuation=1d2attenuation = \frac{1}{d^2}

其中 dd 是表面点到光源的距离。实际引擎中常会对这个公式做工程化调整,避免近距离过曝或远距离过早归零。


在模型从局部空间变换到世界空间时,顶点位置通常乘以模型矩阵 M\mathbf{M}。但法线不能总是简单乘以同一个矩阵,尤其当模型包含非均匀缩放时,直接变换法线会破坏其与表面的垂直关系。

正确做法通常是使用模型矩阵线性部分的逆转置矩阵 (Inverse Transpose Matrix)

Nworld=(M1)TNlocal\mathbf{N}_{world} = (\mathbf{M}^{-1})^T \mathbf{N}_{local}

直觉上,法线不是普通的位置向量,而是“保持与表面切线垂直”的约束方向。非均匀缩放会改变切线空间的形状,因此需要用逆转置来维持正交关系。


Lambert、Phong 与 Blinn-Phong 模型非常适合理解基础着色,但它们并不能完整描述真实光照。它们主要存在以下限制:

  • 不计算真实阴影,需要额外的阴影映射或光线可见性测试。
  • 不描述间接光照,无法自然产生颜色反弹与环境遮蔽。
  • 镜面项是经验模型,不能严格保证能量守恒。
  • 材质参数与真实物理材质之间缺少稳定对应关系。

这些限制并不削弱它们的教学价值。相反,理解这些基础模型能帮助我们看清更高级渲染模型到底在改进什么:PBR 会重新组织材质参数,阴影映射会补充光源可见性,全局光照会追踪多次反射,路径追踪则会从积分角度统一描述光传输。


本章从片元颜色计算出发,建立了基础局部光照模型的核心框架。请务必记住以下结论:

  1. 着色的目标是根据表面法线、光照方向、观察方向和材质参数,为通过可见性测试的片元计算最终颜色。
  2. Lambert 漫反射使用 max(n^l^,0)\max(\hat{n} \cdot \hat{l}, 0) 表示表面正对光源的程度,是最基础的粗糙表面照明模型。
  3. Phong 与 Blinn-Phong 镜面项通过观察方向、反射方向或半程向量估算高光,高光指数控制亮斑集中程度。
  4. 所有参与点乘的方向必须位于同一坐标空间并保持归一化;法线在非均匀缩放下需要使用逆转置矩阵处理。
  5. 基础局部模型不包含真实阴影和间接光照,但它为理解后续着色频率、纹理映射、材质模型和全局光照打下了必要基础。

下一章将继续讨论这些着色计算应该在顶点、片元还是更复杂的管线阶段执行,并进一步引入纹理映射如何为材质提供空间变化的细节。