Simple Subsurface Scatterting for Mobile – (一)通透材质的次表面散射
基于平行光角度或者点光源位置的通透材质的次表面散射的做法。性能开销小,可支持移动端。
通透材质,例如玉器,玻璃,牛奶等,因为透光性比较强,所以次表面散射主要来自于背后透射光源的影响。
先说说主平行光源下的做法:
- 直接把主光源方向反转,让主光源从后方照亮物体。
- 物体的法线方向乘以一个背后透射光源的影响系数“_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
}
}
}