【技术美术】双向反射分布函数
在光的物理中,除了如辐射率、菲涅尔、几何遮蔽等现象,双向反射也是其中一种,因此接下来要学习反射现象对光照强度的影响。反射对光的衰减是一种线性变化,所以直接考虑计算最终的系数即可。
漫反射系数
漫反射因为多次反弹的缘故,其入射信息颜色等都大幅丢失,所以其表现效果均匀柔和,而且计算方法也变的非常简单。
不过根据能量守恒,除非是发生了遮蔽,不然入射光和出射光强度应该是相同的,尤其是漫射这种又不考虑观察角度,反射方向的情况(方向信息在多次反弹中失去了意义),所以漫反射应该不会削减光照强度,故始终 \(y=x\)。
但除了PBR外,大部分经验模型依旧对漫射光进行了衰减计算。当然,这部分衰减计算在PBR中也是有的,但其本质不是因为漫射而产生,而是因为入射光的截面积和接收光的表面积是不一定对等的,根据能量守恒,光的能量就会被表面积均分(上文辐射率和辐照率的关系)。故PBR中,漫射系数始终为1,只有经验光照模型中可以考虑下方的计算方法。
Lambert
经典漫射光照,哪怕如今的PBR也仍在使用。
float diffuseTerm = saturate(dot(n,l));
Half-Lambert
能额外模拟环境光和此表面散射,因此得到的光效柔和阴影过渡自然,在一些特殊光效场景中经常被使用
float b:亮度(brightness)。影响背光面亮度,越低越暗,越高越亮。也可以看成是lambert到half-lambert的比例,为0时为lambert,为0.5时是标准的half-lambert。
float diffuseTerm = saturate(dot(n,l) * (1-b) + b);
镜射系数
Phong
最早期的镜射光实现,使用最直观的方式计算。
float s:光泽度(shininess)。越大光斑越小。float b:亮度(brightness)。越大光斑越亮。
float specularTerm = pow(saturate(dot(reflect(-v,n),l)), s) * b;
Blinn-Phong
改进版的Phong,采用一种称为半角向量(h)的参数进行计算(这也是半角向量的龙兴之地),开销更小,掠角观察光斑依旧圆润。
float3 h = normalize(v + l);
float specularTerm = pow(saturate(dot(h,n)), s) * b;
GGX
同样采用半角向量进行计算,并省去“光泽度”和“亮度”,改用一种更加先进基于01范围的光滑度或粗糙度来控制(两者可以相互转化),其光斑效果更加真实可控。
float roughness2 = roughness * roughness;
float specularTerm = roughness2 / pow(1.0001f + (roughness2 - 1) * pow(saturate(dot(h, n)), 2), 2)
该公式有如下特点:
- 粗糙度越大时,视角权重越小,光斑越分散;
- 分子分母都受粗糙度权重影响,但分母的变化速度约等于分子的平方,故粗糙度较小时,其强度成指数变大。
结果就是该高光是可以满足能量守恒的,其光斑越大亮度越低,光斑越小时亮度越高。
PBR Blinn-Phong
由于 PBR 太出名了,导致很多地方的材质属性都改成了基于粗糙度(或光滑度),相比之下,光泽度和亮度就显得很落后了。于是一种基于0-1粗糙度的 Blinn-Phong 出现了,而且其效果特意实现的与 GGX 等 PBR 镜射光接近,因此可以自由替换,同时不影响 PBR 效果。
float specularTerm = (1 / roughness2) * pow(saturate(dot(h, n)), max(2 / roughness2 - 2, 0.0001));
观察其构成可以发现,其依然是由一个系数项和一个次数项组成,实际上他们就是亮度和光泽度的升级改装,现在一个粗糙度就可以通吃了各种光照了。
有了 GGX 为什么还要 Blinn-Phong?虽然 Blinn-Phng 可能没有 GGX 那样物理正确,但其优点在于易于改造,也因此兼容性强。其中的基数部分可以任意替换成其他内容还依旧可用。例如著名的头发高光,kk模型,就是把 Blinn-Phong 中的法线计算项换成了切线计算,结果依旧可用。这种兼容性,对 PBR Blinn-Phong 同样有效,所以 Blinn 模型并没有过时,其依然是各种特殊光照场景下的全能选手。