# Kulla-Conty 多次散射补偿
# 单次反射的问题
经典的 BRDF 模型只描述单次表面反射。但真实微面之间会互相遮挡、反弹。
对于粗糙度较高的材质,由于模型忽略了光线在微表面间的多次弹射,所以会导致渲染结果比真实情况偏暗,且能量不守恒。
# 反射补偿
在游戏开发中,Kulla-Conty 近似方案是解决微表面模型能量流失的标准方法。Kulla-Conty 提供了一个简单且高效的经验公式来补偿这些丢失的多次散射能量。
在下文中,我们约定:
- n 为宏观表面法线
- v 为观察方向
- l 为光照方向
- μo=n⋅v 观察方向与法线的夹角余弦
- μi=n⋅l 光照方向与法线的夹角余弦
- fss 单次散射 BRDF 传统的微表面项
- 如果把法线 n 看作球体的北极
- θ 是极角,代表纬度,决定了向量离法线有多远
- ϕ 是方位角,代表经度,决定了向量绕着法线转到了哪个方向。
- ϕo 指的是出射方向(即视线 v)在表面切平面上的旋转角度
第一步:计算方向反照率 E(μ)
E(μ)=∫02π∫01fss(μ,μo,ϕ)μodμodϕo
对于一个入射方向 μi,单次散射模型反射出的总能量为 E(μi)。那么丢失的能量就是 1−E(μi)
第二步:计算平均反照率 Eavg 这是对所有入射角度的 E(μ) 进行二次加权平均
Eavg=2∫01E(μ)μdμ
第三步:构造多次散射补偿项 fms
为了补全能量,Kulla-Conty 提出了一个对称的补偿项。对于纯白物体,补偿项与单次反射项相加后,总的反照率接近 1
fms(μi,μo)=π(1−Eavg)(1−E(μi))(1−E(μo))
(1−E(μo))(1−E(μi)) 确保了根据观察角和入射角成比例地补充能量
π(1−Eavg) 是归一化因子,保证积分后的总能量恰好等于丢失的总量
第四步,考虑菲涅尔效应
上面的公式假设反射率是 100%。对于带颜色的金属或非金属,需要引入平均菲涅尔项 Favg 对补偿项进行修正。
为了得到 Favg,我们需要对这个函数在半球上进行加权积分:
Favg=2∫01(F0+(1−F0)(1−μ)5)μdμ
经过积分演算,可以得到一个非常简洁的线性代数公式:
Favg=F0+(1−F0)(1/21)
Favg=2120F0+211
最终结果
ffinal=fss+1−Favg(1−Eavg)fms⋅Favg
在游戏开发中的实现
在 Shader 中实时计算积分是很耗费性能的,所以通常的做法是将积分预计算并存储在一张 2D 贴图(LUT)中。在渲染管线中,直接读取这张图得到积分值,其他部分仍然按照上述公式计算即可。
# BRDF + 反射补偿 与 Albedo 的计算
计算方式如下:
specular=ffinal
kd=(1−F)(1−metallic)
diffuse=πkd⋅albedo
brdf=(diffuse+specular)(n⋅l)
在物理模型中,如果镜面反射因为多次散射而变强了,那么理论上留给漫反射的能量应该减少。
但是为了性能、计算复杂度、视觉效果之间的平衡,通常不会因为加入了 fms 而去扣除漫反射的能量,所以 kd 中仍然使用菲涅尔项 F,不做更改。