Skip to content

Instantly share code, notes, and snippets.

@Farfarer
Last active March 30, 2021 15:20
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Farfarer/4755146 to your computer and use it in GitHub Desktop.
Save Farfarer/4755146 to your computer and use it in GitHub Desktop.
Shader "Custom/Skin Shader" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Diffuse (RGB)", 2D) = "white" {}
_SpecularTex ("Specular (R) Gloss (G) SSS Mask (B)", 2D) = "yellow" {}
_BumpMap ("Normal (Normal)", 2D) = "bump" {}
// BRDF Lookup texture, light direction on x and curvature on y.
_BRDFTex ("BRDF Lookup (RGB)", 2D) = "gray" {}
// Curvature scale. Multiplier for the curvature - best to keep this very low - between 0.02 and 0.002.
_CurvatureScale ("Curvature Scale", Float) = 0.005
// Controller for fresnel specular mask. For skin, 0.028 if in linear mode, 0.2 for gamma mode.
_Fresnel ("Fresnel Value", Float) = 0.2
// Which mip-map to use when calculating curvature. Best to keep this between 1 and 2.
_BumpBias ("Normal Map Blur Bias", Float) = 1.5
}
SubShader{
Tags { "Queue" = "Geometry" "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf SkinShader fullforwardshadows
#pragma target 3.0
// Bit complex for non-desktop.
#pragma only_renderers d3d9 d3d11 opengl
// Required for tex2Dlod function.
#pragma glsl
struct SurfaceOutputSkinShader {
fixed3 Albedo;
fixed3 Normal;
fixed3 NormalBlur;
fixed3 Emission;
fixed3 Specular;
fixed Alpha;
float Curvature;
};
struct Input
{
float2 uv_MainTex;
float3 worldPos;
float3 worldNormal;
INTERNAL_DATA
};
sampler2D _MainTex, _SpecularTex, _BumpMap, _BRDFTex;
float _BumpBias, _CurvatureScale, _Fresnel;
void surf (Input IN, inout SurfaceOutputSkinShader o)
{
float4 albedo = tex2D ( _MainTex, IN.uv_MainTex );
o.Albedo = albedo.rgb;
o.Normal = UnpackNormal ( tex2D ( _BumpMap, IN.uv_MainTex ) );
o.Specular = tex2D ( _SpecularTex, IN.uv_MainTex ).rgb;
// Calculate the curvature of the model dynamically.
// Get a mip of the normal map to ignore any small details for regular shading.
o.NormalBlur = UnpackNormal( tex2Dlod ( _BumpMap, float4 ( IN.uv_MainTex, 0.0, _BumpBias ) ) );
// Transform it back into a world normal so we can get good derivatives from it.
float3 worldNormal = WorldNormalVector( IN, o.NormalBlur );
// Get the scale of the derivatives of the blurred world normal and the world position.
// From these it's possible to work out the rate of change of the surface normal; or it's curvature.
#if SHADER_API_D3D11
// In DX11, ddx_fine should give nicer results.
float deltaWorldNormal = length( abs(ddx_fine(worldNormal)) + abs(ddy_fine(worldNormal)) );
float deltaWorldPosition = length( abs(ddx_fine(IN.worldPos)) + abs(ddy_fine(IN.worldPos)) );
#else
// Otherwise stick with ddx or dFdx, which can be replaced with fwidth.
float deltaWorldNormal = length( fwidth( worldNormal ) );
float deltaWorldPosition = length( fwidth ( IN.worldPos ) );
#endif
o.Curvature = ( deltaWorldNormal / deltaWorldPosition ) * _CurvatureScale;
}
inline fixed4 LightingSkinShader( SurfaceOutputSkinShader s, fixed3 lightDir, fixed3 viewDir, fixed atten )
{
viewDir = normalize( viewDir );
lightDir = normalize( lightDir );
s.Normal = normalize( s.Normal );
s.NormalBlur = normalize( s.NormalBlur );
float NdotL = dot( s.Normal, lightDir );
float3 h = normalize( lightDir + viewDir );
float specBase = saturate( dot( s.Normal, h ) );
float fresnel = pow( 1.0 - dot( viewDir, h ), 5.0 );
fresnel += _Fresnel * ( 1.0 - fresnel );
float spec = pow( specBase, s.Specular.g * 128 ) * s.Specular.r * fresnel;
float2 brdfUV;
float NdotLBlur = dot( s.NormalBlur, lightDir );
// Half-lambert lighting value based on blurred normals.
brdfUV.x = NdotLBlur * 0.5 + 0.5;
//Curvature amount. Multiplied by light's luminosity so brighter light = more scattering.
brdfUV.y = s.Curvature * dot( _LightColor0.rgb, fixed3(0.22, 0.707, 0.071 ) );
float3 brdf = tex2D( _BRDFTex, brdfUV ).rgb;
float m = atten; // Multiplier for spec and brdf.
#if !defined (SHADOWS_SCREEN) && !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE)
// If shadows are off, we need to reduce the brightness
// of the scattering on polys facing away from the light
// as it won't get killed off by shadow value.
// Same for the specular highlights.
m *= saturate( ( (NdotLBlur * 0.5 + 0.5) * 2.0) * 2.0 - 1.0);
#endif
fixed4 c;
c.rgb = (lerp(s.Albedo * saturate(NdotL) * atten, s.Albedo * brdf * m, s.Specular.b ) * _LightColor0.rgb + (spec * m * _LightColor0.rgb) ) * 2;
c.a = s.Curvature; // Output the curvature to the frame alpha, just as a debug.
return c;
}
ENDCG
}
FallBack "VertexLit"
}
@Farfarer
Copy link
Author

Updated, Dan Treble noticed that my optimisation of moving s.Albedo out of the lerp caused it to break. So it's back in there.

@HongfeiXu
Copy link

HongfeiXu commented Jul 28, 2018

Nice blog and shader script! Learned a lot. But I got a question to ask, I don't have a image for _SpecularTex, so the "yellow" is applied instead. And I use the this shader in my project, the ear is translucent as your result for thumb, but not turn red in the edge area.

My images in Google Drive links below.

MyResult

MySettings

Compare to your results

I wanna know why the difference happens. Thanks a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment