SSS = 背光 + 绕动 + 扩散
- 要实现背面光照,可以使用背离灯光的方向向量,也就是将灯光方向L加一个负号
- 因为只有物体表面的法线方向和灯光的夹角小于90度时才是受光面,灯光方向与法线方向的夹角超过90度就没有任何光照
- 注意点:
- 将灯光方向反过来再和物体表面法线方向做点乘得到的其实不是背光,只是让光照的效果相反而已
- 背光在计算点乘时,不是和向量N做点乘,而是和另外一个向量:与视线向量V做点乘
表面扰动的实现
上面是背光最基本的实现,但仅仅只是这样实现的背光还是有点假的,所以还有一个步骤,就是对背光进行表面扰动
- 当物体的表面不带有任何扰动时,它的背光会更亮,但这个背光并不能反映出物体表面方向不同,这是因为没有考虑到法线扰动,所以只能得到简单的背光
- 那怎样把法线朝向带来的变化考虑进去呢?方法是利用下面的公式
- 向量H是光照方向L与物体法线方向N的中间向量,它可以通过L + N得到,将它作为经过表面扰动后的向量来计算次表面散射可以使次表面散射效果更加真实
背光扩散的实现
- 扩散的实现并不难,就像在计算高光时调整高光的集中度或者扩散度一样,只需要对点乘的结果进行saturate,saturate就是把点乘的结果限制在0到1
- 然后将0到1之间的数只乘以P次方,P越大算出来的高光效果就越集中
- 整体背光强度由于P次方的存在会越乘越小,要将它的整体提亮就需要再乘以一个S,这个S是超过一的数值
Unity3D 代码
inline fixed4 LightingStandardTranslucent(SurtfaceOutputStandard s, fixed3 viewDir, UnityGI gi) // 参数:表面着色器的输出数据s,视方向ViewDir,全局光照相关信息gi
{
fixed4 pbr = LightingStandard(s, viewDir, gi); // 通过LightingStandard得到支持全局光照的着色器用来计算PBR着色结果
float3 L = gi.light.dir; // 灯光方向L
float3 V = viewDir; // 视方向V
float3 N = s.Normal; //物体表面法线N
float3 H = normalize(L + N * _Distortion); // _Distortion就是公式中的δ,将它乘以法线方向后再进行单位化,就得到了半角向量H
float I = pow(saturate(dot(V, -H)), _Power) * _Scale; // V与-H的点乘结果就是背光的强度,使用saturate调整它,通过POW函数求_Power次方的结果,再乘以控制整体的背光放大、缩小的_Scale
pbr.rgb = pbr.rbg + gi.light.color * I; // 用最终的背光强度与灯光的颜色做乘法,并且与PBR的颜色相加,得到最终PBR的颜色
return pbr;
}
效果增强:局部厚度
float thickness;
void surf(Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
thickness = tes2D(_LocalThickness, IN.uv_MainTex).r; // 通过纹理采样来采样这张局部厚度图
}
inline fixed4 LightingStandardTranslucent(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi)
{
fixed4 pbr = LightingStandard(s, viewDir, gi);
float3 L = gi.light.dir;
float3 V = viewDir;
float N = s.Normal;
float3 H = normalize(L + N * _Distortion); // 半角向量H
float VdotN = pow(saturtate(dot(v, -H)), _Power) * _Scale; // 背光强度VdotH
float3 I = _Attenuation * (VdotN + _Ambient) * thickness; // 将计算出的背光强度乘以厚度thickness就能得到带有厚度的光照
pbr.rgb = pbr.rgb +gi.light.color * I;
return pbr;
}