HLSL Shaders
- Repository Link - GitHub
Sub Surface Scattering (SS)
This is a real-time subsurface scattering approximation that uses two ingredients: wrap lighting (a soft diffuse falloff) and a light-space
depth map that estimates how much material light had to pass through, then attenuates it with Beer's law.
Jade material created using SS.
NdotL_wrap = (NdotL + WrapFactor) / (1.0 + WrapFactor) shifts the diffuse falloff so light "wraps" past the
terminator instead of hard-clipping at NdotL = 0. It's not physically SSS but classic Half-Lampert.
entryDepthcomes from the depth map: the distance from the light to the closest surface at that texel, where light first hits the object.fragDepthis this fragment's own distance from the light.totalDepthis the gap between them an estimate of how much material light would have to travel through to reach this point (useful for points on the far side of the mesh, like the inside of an ear or a thin fold).absorption = exp(-totalDepth * Absorption)Beer-Lambert law states the further light travels through the material, the more it's absorbed, so thin areas glow and thick areas go dark.
...
// Depth Map
texture DepthMap;
sampler2D DepthSampler = sampler_state
{
Texture = ;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = None;
AddressU = Clamp;
AddressV = Clamp;
};
struct VertexShaderInput
{
float4 Position : POSITION;
float3 Normal : NORMAL;
};
struct VertexShaderOutput
{
float4 Position : POSITION;
float3 WorldPosition : TEXCOORD0;
float3 Normal : TEXCOORD1;
float4 LightPosition : TEXCOORD3;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPos = mul(input.Position, World);
output.WorldPosition = worldPos.xyz;
output.Normal = normalize(mul(input.Normal, (float3x3) WorldInverseTranspose));
float4 viewPos = mul(worldPos, View);
output.Position = mul(viewPos, Projection);
// Transform to Light Space
float4 lightView = mul(worldPos, LightViewMatrix);
output.LightPosition = mul(lightView, LightProjectionMatrix);
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float2 uv = input.LightPosition; // Find the position of light on the UV
float4 sample = tex2D(DepthSampler, uv).a; // Get the depth from the depth map; 'a' component
float3 N = normalize(sample.rgb * 2.0f - 1.0f); // [0, 1] to [-1, 1]
float entryDepth = sample.a;
// Vectors
//float3 N = normalize(input.Normal);
float3 L = normalize(LightPosition - input.WorldPosition); // Light Direction Vector
float3 V = normalize(CameraPosition - input.WorldPosition); // View Direction Vector
float NdotL = dot(N, L); // Dot product between the normal and the light
// Light Wrapping y = (x + wrap) / (1 + wrap)
float NdotL_wrap = (NdotL + WrapFactor) / (1.0 + WrapFactor);
float wrap_diffuse = max(0, NdotL_wrap);
// Light Scatter
float4 scatterColor = float4(ScatterColor, 1.0);
float scatter = smoothstep(0.0, ScatterWidth, NdotL_wrap) * smoothstep(ScatterWidth * 2.0, ScatterWidth, NdotL_wrap);
// Light Absorption
float fragDepth = length(mul(float4(input.WorldPosition, 1), LightViewMatrix).xyz);
float totalDepth = max(0, fragDepth - entryDepth);
float absorption = exp(-totalDepth * Absorption); // Beer's Law = e^(-sigma * d)
// Light Components
float3 ambient = AmbientColor.rgb * AmbientIntensity;
float3 diffuse = DiffuseColor.rgb * DiffuseIntensity * wrap_diffuse;
// Get the final color
float3 color = (ambient + diffuse) + scatter * scatterColor.rgb;
color *= absorption;
return float4(color, 1.0);
}
...
Bump Map
Bump Map & Refraction Shader
bumpNormal = normalize(N + h_u·T + h_v·B)
where T/B/N are the interpolated tangent, binormal, and normal at that point on the surface, and h_u/h_v come straight from the normal map texel. Nudge the base normal toward the tangent and binormal directions, scaled by how much the surface detail leans in each direction."
...
// Normal Map Sampler
sampler NormalMapSamplerLinear = sampler_state // MipMap = on
{
texture = ;
magfilter = LINEAR; // None, POINT, LINEAR, Anisotropic
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = Wrap; // Clamp, Mirror, MirrorOnce, Wrap, Border
AddressV = Wrap;
};
sampler NormalMapSamplerNone = sampler_state // MipMap = off
{
texture = ;
magfilter = none; // None, POINT, LINEAR, Anisotropic
minfilter = none;
mipfilter = none;
AddressU = Wrap; // Clamp, Mirror, MirrorOnce, Wrap, Border
AddressV = Wrap;
};
// Skybox Sampler
samplerCUBE SkyBoxSampler = sampler_state
{
texture = ;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = Mirror;
AddressV = Mirror;
};
// Input for verticies
struct VertexShaderInput
{
float4 Position : POSITION0; // These are registers
float2 TexCoord : TEXCOORD0;
float4 Normal : NORMAL0;
float4 Binormal : BINORMAL0;
float4 Tangent : TANGENT0;
};
// Ouput for verticies
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float3 Normal : TEXCOORD1;
float3 Tangent : TEXCOORD2;
float3 Binormal : TEXCOORD3;
float3 WorldPos : TEXCOORD4;
};
// Bump Map Self Shadowing
float SselfStep2(float ndot1)
{
if (ndot1 > 0)
return 1;
else
return 0;
}
float SselfStep3(float ndot1)
{
float c = 0.125;
if (ndot1 > c)
return 1;
else if (ndot1 > 0)
return ndot1 / c;
else
return 0;
}
...
/// ******* Refractive Bump Mapping ********
VertexShaderOutput RefractiveVertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output; // output with diffuse applied to send to GPU
// Matrix multiplication sequence (Projection * (View * (Object * World)))
float4 worldPosition = mul(input.Position, World); // Point position x', y', x', w'
float4 viewPosition = mul(worldPosition, View); // View Point x", y", z", w"
output.Position = mul(viewPosition, Projection);
output.Normal = normalize(mul(input.Normal, World).xyz); // Normal Vector
output.Tangent = normalize(mul(input.Tangent, World).xyz); // Tangent Vector
output.Binormal = normalize(mul(input.Binormal, World).xyz);
output.WorldPos = worldPosition.xyz;
output.TexCoord = input.TexCoord * float2(NormalMapRepeatU, NormalMapRepeatV);
return output;
}
float4 RefractivePixelShaderFunction(VertexShaderOutput input) : SV_Target
{
float3 texColor;
if (MipMap == 0) // MipMap
texColor = tex2D(NormalMapSamplerNone, input.TexCoord).xyz;
else
texColor = tex2D(NormalMapSamplerLinear, input.TexCoord).xyz;
texColor -= 2.0 * float3(0.5, 0.5, 0.5);
// Bump Height; adjust x,y,z comp to change height of map
texColor.x *= (1 + 0.2 * (BumpHeight - 5));
texColor.y *= (1 + 0.2 * (BumpHeight - 5));
texColor.z *= (1 + 0.2 * (BumpHeight - 5));
// Vectors
float3 L = normalize(LightPosition - input.WorldPos.xyz); // Light vector
float3 N = normalize(input.Normal); // Normal
float3 T = normalize(input.Tangent); // Tangent
float3 B = normalize(input.Binormal); // Binormal
// N' = norm(N + h_u(N_n x P_v) + h_v(P_u x N_n))
float3 bumpNormal = normalize(N + texColor.x * T + texColor.y * B);
float Sself = 1; //default is 1
if (SelfShadow == 1)
Sself = SselfStep2(dot(N, L));
else if (SelfShadow == 2)
Sself = SselfStep3(dot(N, L));
float4 LightColor = 1;
// Calc ambient
float4 ambient = AmbientColor * AmbientIntensity;
// Calc diffuse component
float diffuse = Sself * saturate(dot(L, bumpNormal));
float4 decalColor = diffuse * DiffuseColor * DiffuseIntensity;
// Calc specular component
float3 V = normalize(CameraPosition - input.WorldPos);
// Calc view vector
float3 H = normalize(L + V); //halfway vector
float specular = saturate(dot(H, bumpNormal));
specular = Sself * pow(specular, Shininess * 3);
decalColor += specular * SpecularColor * SpecularIntensity;
decalColor.a = 1;
//float3 reflectNorm = normalize(mul(bumpNormal, WorldInverseTranspose).xyz); // Normal Vector
float3 I = normalize(input.WorldPos.xyz - CameraPosition); // Incident Vector
float3 R = refract(I, bumpNormal, reflectivity);
// texCUBE -> Calculates the mapping coordinates on the Skybox
float4 refractedColor = texCUBE(SkyBoxSampler, R);
// Linearly interpolate the model texture and the env map texture by a float value
return lerp(decalColor, refractedColor, 0.5);
}
Toon
This toon shader implements cel shading - collapsing a continuous lighting term into a small number of flat color bands instead of a smooth gradient. The vertex shader here does nothing but transform the vertex and pass world normal straight through, all the actual shading decision happens in the pixel shader.
Toon shader.
// Input for verticies
struct VertexShaderInput
{
float4 Position : POSITION;
float4 Normal : NORMAL;
};
// Ouput for verticies
struct VertexShaderOutput
{
float4 Position : POSITION;
float4 Color : COLOR;
float4 Normal : TEXCOORD0;
float4 WorldPosition : TEXCOORD1;
};
...
VertexShaderOutput ToonVertexShaderFunction(VertexShaderInput input) // Calculate only the data
{
VertexShaderOutput output; // output with diffuse applied to send to GPU
float4 worldPosition = mul(input.Position, World); // Point position x', y', x', w'
float4 viewPosition = mul(worldPosition, View); // View Point x", y", z", w"
output.Position = mul(viewPosition, Projection);
// Used as input for the pixel shader function
output.WorldPosition = worldPosition;
output.Normal = input.Normal;
output.Color = 0;
return output;
}
float4 ToonPixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float3 N = normalize(mul(input.Normal, WorldInverseTranspose).xyz); // Normal Vector
float3 V = normalize(CameraPosition - input.WorldPosition.xyz); // View Vector
float3 L = normalize(lightPosition) * lightIntensity; // Light Vector
float3 R = reflect(-L, N); // Reflection Vector
// Simple color palette that will change based on the angle between the view vector and reflection vector
float D = dot(V, R);
if (D < -0.7)
{
return float4(0, 0, 0, 1); // Color 1: Black
}
else if (D < 0.2)
{
return float4(0.25, 0.25, 0.25, 1); // Color 2: Dark Grey
}
else if (D < 0.6)
{
return float4(0.5, 0.5, 0.5, 1); // Color 3: Light Grey
}
else
{
return float4(1, 1, 1, 1); // Color 4: White
}
}
Reflection
Reflection shader.
Refraction
Refraction Shader
Phong Shading
Phong shading model.