Simple Subsurface Scatterting for Mobile – (一)通透材质的次表面散射

Last modified date

基于平行光角度或者点光源位置的通透材质的次表面散射的做法。性能开销小,可支持移动端。

通透材质,例如玉器,玻璃,牛奶等,因为透光性比较强,所以次表面散射主要来自于背后透射光源的影响。

先说说主平行光源下的做法:
  • 直接把主光源方向反转,让主光源从后方照亮物体。
  • 物体的法线方向乘以一个背后透射光源的影响系数“_SubsurfaceDistortion”。
核心代码:
inline float SubsurfaceScattering (float3 viewDir, float3 lightDir, float3 normalDir, float subsurfaceDistortion)
{
    float3 backLitDir = normalDir * subsurfaceDistortion + lightDir;
    float result = saturate(dot(viewDir, -backLitDir));
    return result;
}

效果如下,主平行光源的方向控制散射效果。

通过“_SubsurfaceDistortion”系数控制散射强度。

现在有了基础的SSS值。把SSS的值与内在颜色“_InteriorColor”混合,并加上一个基础的Diffuse。用lerp的方式混合“_InteriorColor”和灯光颜色,并且用Pow幂运算来提升SSS值的强度,公式如下:

fixed3 sssCol = lerp(_InteriorColor, _LightColor0, saturate(pow(sssValue, _InteriorColorPower))).rgb * sssValue;

效果如下,“InteriorColor”的颜色设成了红色。灯光在前面是时候是Diffuse效果,灯光从后面照过来就显示通透的红色散射光。

通过“_InteriorColorPower”调整内在颜色的影响强度。

现在缺点是正面光照的时候Diffuse效果完全没有通透的感觉。所以要在正面光照时也加入一点点背光。做法是再写一个正面光的SSS值,混合到正面的Diffuse光照里。

Shader代码参考:
inline float SubsurfaceScattering (float3 viewDir, float3 lightDir, float3 normalDir, 
                float frontSubsurfaceDistortion, float backSubsurfaceDistortion, float frontSssIntensity)
{
    // 分别计算正面和反面的次表面散射
    float3 frontLitDir = normalDir * frontSubsurfaceDistortion - lightDir;
    float3 backLitDir = normalDir * backSubsurfaceDistortion + lightDir;
                
    float frontSSS = saturate(dot(viewDir, -frontLitDir));
    float backSSS = saturate(dot(viewDir, -backLitDir));
    // 最后叠加到一起
    float result = saturate(frontSSS * frontSssIntensity + backSSS);

    return result;
}
……

fixed4 frag (v2f i) : SV_Target
{
    // sample the texture
    fixed4 col = tex2D(_MainTex, i.uv) * _Tint;
                
    // 获得次表面散射的颜色
    float sssValue = SubsurfaceScattering (i.viewDir, i.lightDir, i.normalDir, 
        _FrontSubsurfaceDistortion, _BackSubsurfaceDistortion, _FrontSssIntensity);
    fixed3 sssCol = lerp(_InteriorColor, _LightColor0, saturate(pow(sssValue, _InteriorColorPower))).rgb * sssValue;
                
    // 计算Diffuse的颜色
    fixed4 unlitCol = col * _InteriorColor * 0.5; // 计算暗部颜色
    fixed4 diffCol = lerp(unlitCol, col, i.diff);
                
    // 最后把SSS和Diffuse颜色加到一起
    fixed3 final = sssCol + diffCol.rgb;
                
    // apply fog    
    UNITY_APPLY_FOG(i.fogCoord, final);
    return fixed4(final, 1);
}
ENDCG
……

效果如下图,加上了Bloom效果。

再加上高光和菲涅尔看看。

正面光:

背面光:

以上就是主平行光源下的简易Subsurface Scattering做法。下面再继续实现点光源的SSS效果。

我是通过点光源与渲染对象的posWorld的距离,计算出点光源的影响范围。因为我并没有实际用到unity的Light,只需要用到Transform的坐标作为点光源的世界坐标,所以我自己写了一个CustomPointLight的类来储存点光源的坐标,颜色,范围和强度,然后通过Shader.SetGlobalVectorArray的方式传递给Shader。这么做虽然光照效果会有一点假,但是优势是可以支持无数盏点光源。

最后把点光源与之前到平行光源效果混合。

Shader代码:
Shader "WalkingFat/SSS/SimpleDirLitSSS"
{
    Properties
    {
        //Basic
        _MainTex ("Texture", 2D) = "white" {}
        _Tint ("Tint", Color) = (1,1,1,1)
        
        // Directional Subsurface Scattering
        _InteriorColor ("Interior Color", Color) = (1,1,1,1)
        _FrontSubsurfaceDistortion ("Front Subsurface Distortion", Range(0,1)) = 0.5
        _BackSubsurfaceDistortion ("Back Subsurface Distortion", Range(0,1)) = 0.5
        _FrontSssIntensity ("Front SSS Intensity", Range(0,1)) = 0.2
        _InteriorColorPower ("Interior Color Power", Range(0,5)) = 2
        
        // Specular
        _Specular ("Specular", Range(0,1)) = 0.5
        _Gloss ("Gloss", Range(0,1) ) = 0.5
        
        // fresnel
        _RimPower("Rim Power", Range(0.0, 36)) = 0.1
        _RimIntensity("Rim Intensity", Range(0, 1)) = 0.2
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Tags {"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityLightingCommon.cginc" // for _LightColor0
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                fixed4 diff : COLOR0; // diffuse lighting color
                float2 uv : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float3 lightDir : TEXCOORD3;
                float3 viewDir : TEXCOORD4;
                UNITY_FOG_COORDS(5)
            };
            
            inline float SubsurfaceScattering (float3 viewDir, float3 lightDir, float3 normalDir, 
                float frontSubsurfaceDistortion, float backSubsurfaceDistortion, float frontSssIntensity)
            {
                float3 frontLitDir = normalDir * frontSubsurfaceDistortion - lightDir;
                float3 backLitDir = normalDir * backSubsurfaceDistortion + lightDir;
                
                float frontSSS = saturate(dot(viewDir, -frontLitDir));
                float backSSS = saturate(dot(viewDir, -backLitDir));
                
                float result = saturate(frontSSS * frontSssIntensity + backSSS);
                
                return result;
            }

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _Tint, _InteriorColor;
            float _FrontSubsurfaceDistortion, _BackSubsurfaceDistortion, _FrontSssIntensity, _InteriorColorPower;
            float _PointLitRadius;
            float _Specular, _Gloss, _RimPower, _RimIntensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (v.vertex);
                o.normalDir = UnityObjectToWorldNormal (v.normal);
                o.posWorld = mul (unity_ObjectToWorld, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                
                o.viewDir = normalize(_WorldSpaceCameraPos.xyz - o.posWorld.xyz);
                o.lightDir = normalize(_WorldSpaceLightPos0.xyz);
                
                // Diffuse
                half nl = max(0, dot(o.normalDir, _WorldSpaceLightPos0.xyz));
                o.diff = nl * _LightColor0;
                o.diff.rgb += ShadeSH9(half4(o.normalDir,1));
                
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }
            
            // Custom point light
            float _CustomPointLitArray;
            float4 _CustomPointLitPosList[20];
            float4 _CustomPointLitColorList[20];
            float _CustomPointLitRangeList[20];
            
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv) * _Tint;
                
                // Point Light SSS
                float pointLitSssValue;
                fixed3 pointLitSssCol;
                for (int n = 0; n < _CustomPointLitArray; n++)
                {
                    float plsValue = saturate(_CustomPointLitRangeList[n] - distance(i.posWorld, _CustomPointLitPosList[n])) / _CustomPointLitRangeList[n];
                    pointLitSssValue += plsValue;
                    pointLitSssCol += plsValue * _CustomPointLitColorList[n];
                }
                
                // Directional light SSS
                float sssValue = SubsurfaceScattering (i.viewDir, i.lightDir, i.normalDir, 
                    _FrontSubsurfaceDistortion, _BackSubsurfaceDistortion, _FrontSssIntensity);
                fixed3 sssCol = lerp(_InteriorColor, _LightColor0, saturate(pow(sssValue, _InteriorColorPower))).rgb * sssValue;
                
                
                // Diffuse
                fixed4 unlitCol = col * _InteriorColor * 0.5;
                fixed4 diffCol = lerp(unlitCol, col, i.diff); 
                
                // Specular
                float specularPow = exp2 ((1 - _Gloss) * 10.0 + 1.0);
                float3 specularColor = float4 (_Specular,_Specular,_Specular,1);
                float3 halfVector = normalize (i.lightDir + i.viewDir);
                float3 directSpecular = pow (max (0,dot (halfVector, normalize(i.normalDir))), specularPow) * specularColor;
                float3 specular = directSpecular * _LightColor0.rgb;
                
                // Rim
                float rim = 1.0 - max(0, dot(i.normalDir, i.viewDir));
                float rimValue = lerp(rim, 0, sssValue);
                float3 rimCol = lerp(_InteriorColor, _LightColor0.rgb, rimValue) * pow(rimValue, _RimPower) * _RimIntensity;  
                
                // final color
                fixed3 final = sssCol + diffCol.rgb + specular + rimCol;
                final += pointLitSssCol * final;
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, final);
                return fixed4(final, 1);
            }
            ENDCG
        }
    }
}

Share

This site is protected by wp-copyrightpro.com