NekoLab

CG练习(4) 次表面散射(SSS)近似方法

次表面散射(SubSurface Scattering)常用来表现蜡质、玉和皮肤这种质感,与通常计算着色只考虑表面效应不同,还要考虑光透过物体表面发生散射,之后再反射或透射出来,增加了许多计算的复杂性。

这里实现了GDC2011年Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look展示的一种产生近似SSS效果的方法。在这个算法里面,根本就没有考虑物体的厚度和表面散射情况,只用了几次很巧妙的向量计算,模拟出以假乱真的效果。

完整项目见https://github.com/wjw12/Fast-Approximate-SSS,这个掷铁饼的模型是从Asset Store免费下载的。

在表面光照模型中,模型的背光面也就是法线N与光源方向L相背的情形是没有考虑受光的,而要模拟蜡质这种半透明效果,当光源从背后照射时,背光面一样会被照亮,只是更昏暗模糊一点。这就好像在原来光源L的相反方向-L位置出现了一个新的光源:

光透过材质经过散射,出射光的方向一般会因为散射和折射发生改变,改变了多少呢?由于光路可逆原理,这也就好像在新的位置出现了一个虚拟光源一样(想想高中光学:水中的筷子经折射变断了)在极端情况下,虚拟光源的光线是掠过物体表面的,另一个极端就是光路一点都不改变,也就还是-L位置。

用一个参数$\delta$来表示光源位置的改变量。新的光源位置可以设为$-(L+N\delta)$,当$\delta=1$时就是$-(L+N)$,即L与N的平分线位置。

再联想起Phong模型,要让这个虚拟光源的光照结果与视线方向有关,只需要作点乘就可以了,再加上一个幂指数power和乘在整个式子上的强度系数scale,我们背面的光照模型就完成了:

$$I_{back}=saturate(V\cdot -(L+N\delta))^{p}\cdot s$$

是不是非常简单?

Shader的核心代码只有这么几行:

inline fixed4 LightingSSSTranslucent(SurfaceOutputStandard s, float3 viewDir, UnityGI gi)
{
    // original color 
    fixed4 c = LightingStandard(s, viewDir, gi);
    float3 lightDir = gi.light.dir;
    float3 normal = s.Normal;
    float3 H = lightDir + _Distortion * normal;
    float VdotH = pow(saturate(dot(viewDir, -H)), _Power) * _Scale;
    c.rgb += gi.light.color * VdotH;
    return c;
}

这里使用了Unity GI的光照信息而不是当个光源,使用时要加上#include “UnityPBSLighting.cginc”。先获取原来GI计算的颜色,叠加上我们这里计算的背面SSS亮度,就是最终颜色了。

要让效果做得更逼真,原来的方法中还增加了一个模拟物体厚度的贴图,控制各个部位不同的散射强度。总之计算量很小,对于背面受光的物体效果不错,但是正面受光的情况下是失效的,因为计算过程中saturate函数排除了对正面的影响。