URP 渲染实战 / 第 5 章 经典光照模型 / 案例 5.2
案例 5.2Half-Lambert:让暗部不再死黑入门
目标:理解 Valve 在《半条命 2》中提出的 Half-Lambert 光照,用它让角色背光面保留形体。
原理
Lambert 的问题在于:从交界线开始,整个背光的半球都是同一个值 0。没有环境光时背光面一片死黑;即使有环境光,背光面也只剩下环境光那一点点起伏,形体信息丢失严重。Valve 的做法非常直接:不截断 N·L,而是把它的取值范围 [−1, 1] 线性地映射到 [0, 1],再取平方:
映射以后,正对光源仍是 1,交界线处变成 0.5(平方后 0.25),只有正背面才是 0。背光面从“一片 0”变成了一个平滑的渐变,形体就回来了。平方的作用是把整体拉暗一点、让亮暗过渡更接近 Lambert 的观感,否则映射后的结果会显得发灰、发平。下图把几种漫反射曲线画在一起,横轴是法线与光线的夹角 θ。
代码
Half-Lambert 的 Shader 只有片元函数和 Lambert 不同,这里只列出这一段:
Assets/Cookbook/Ch05/HalfLambert.shader(节选:frag)
half4 frag(Varyings input) : SV_Target { half3 N = normalize(input.normalWS); Light mainLight = GetMainLight(); half3 radiance = mainLight.color * mainLight.distanceAttenuation; // 注意这里没有 saturate:背光面的负值也要参与映射 half NdotL = dot(N, mainLight.direction); half halfLambert = NdotL * 0.5 + 0.5; // [-1,1] -> [0,1] halfLambert = pow(halfLambert, _Power); // Valve 原版取平方,让暗部重新“沉下去” half3 diffuse = _BaseColor.rgb * radiance * halfLambert; half3 ambient = _BaseColor.rgb * SampleSH(N) * _AmbientStrength; return half4(diffuse + ambient, 1); }
void main() { vec3 N = normalize(normalWS); Light mainLight = GetMainLight(); vec3 radiance = mainLight.color * mainLight.distanceAttenuation; // 注意这里没有 saturate:背光面的负值也要参与映射 float NdotL = dot(N, mainLight.direction); float halfLambert = NdotL * 0.5 + 0.5; // [-1,1] -> [0,1] halfLambert = pow(halfLambert, _Power); // Valve 原版取平方,让暗部重新“沉下去” vec3 diffuse = _BaseColor.rgb * radiance * halfLambert; vec3 ambient = _BaseColor.rgb * SampleSH(N) * _AmbientStrength; fragColor = vec4(diffuse + ambient, 1.0); }
和 Lambert 的唯一区别是:先映射再截断,所以 dot 的结果不能 saturate。_Power 设为 1 是不平方的版本,设为 2 是 Valve 原版。

Half-Lambert 在物理上没有依据:它让背光面也“收到”了光,能量凭空多出来一截。它真正模拟的,是“环境里还有很多来自背后的反弹光”这件事——在没有像样的全局光照的年代,这是一个便宜又好看的折中。今天它主要用于:
- 卡通渲染:用 Half-Lambert 的结果去查一张色阶 Ramp 图,交界线的位置和宽度更好控制(第 14 章)。
- 没有烘焙光照的移动端场景:环境光太平、背光面太暗时,用它“提亮”角色。
- 毛发、布料等需要柔和明暗过渡的材质。
坑Half-Lambert 和实时阴影放在一起时容易“穿帮”:阴影图判断的是几何遮挡,它的边界在 N·L = 0 附近;而 Half-Lambert 在 N·L = 0 处仍有 0.25 的亮度。于是物体自身投影的边缘会出现一条突兀的硬线。常见的处理是让阴影只作用于 Lambert 部分,或者把阴影衰减也做同样的“柔化”。第 14 章的卡通 Shader 会具体处理这个问题。