Hello again!
I’ve been pounding away today at a skin shader, and I’m fairly sure I have a shader that doesn’t suck. I am, however, using a really old model and texture set I made in college that isn’t all too hot. I never was a very good artist.:tear:
I was wondering if anyone had just a generic skin diffuse / specular / normal texture set that I could use. A model that it could go on would be gravy, but I’ll be happy if I get a sphere looking right. Sphere, forearm, foot, character, it doesn’t matter. I’ll take anything.
I can’t very well ask for something and not give in return, so here’s the current revision of the shader, and some WIP screenshots.
// +--------------------------------------------+
// Skin: by Lithium
// +--------------------------------------------+
// +--------------------------------------------+
// Semantics
// +--------------------------------------------+
float4x4 WorldInverseTranspose : WorldInverseTranspose < string UIWidget="None"; >;
float4x4 WorldViewProject : WorldViewProjection < string UIWidget="None"; >;
float4x4 World : World < string UIWidget="None"; >;
float4x4 ViewInverse : ViewInverse < string UIWidget="None"; >;
// +--------------------------------------------+
// Parameters
// +--------------------------------------------+
// +--------------------------------------------+
// Frame
float3 AmbientLight : Ambient <
string UIName = "Ambient Lighting";
string UIWidget = "Color";
> = {0.1f, 0.1f, 0.1f};
// +--------------------------------------------+
// Bound
bool UseDirect0 <
string UIName = "Use Direct Light 0";
> = true;
float3 Direct0Dir : DIRECTION <
string Object = "Direct Light 0";
string UIName = "Direct 0 Direction";
string Space = "World";
> = { -.239f, -0.65f, 0.722f };
float3 Direct0Col : COLOR <
string Object = "Direct Light 0";
string UIName = "Direct 0 Color";
string UIWidget = "Color";
> = {1.0f,1.0f,1.0f};
bool UsePoint0 <
string UIName = "Use Point Light 0";
> = true;
float3 Point0Pos : POSITION <
string Object = "Point Light 0";
string UIName = "Lamp 0 Position";
string Space = "World";
> = { -2.45f, 0.765f, -1.14f };
float3 Point0Col : COLOR <
string Object = "Point Light 0";
string UIName = "Lamp 0 Color";
string UIWidget = "Color";
> = {1.0f,1.0f,1.0f};
float Point0Constant : CONSTANTATTENUATION <
string Object = "Point Light 0";
string UIName = "Lamp 0 Constant Attenuation";
string UIWidget ="slider";
> = 1;
float Point0Linear : LINEARATTENUATION <
string Object = "Point Light 0";
string UIName = "Lamp 0 Linear Attenuation";
string UIWidget ="slider";
> = 0.05;
float Point0Quadratic : QUADRATICATTENUATION <
string Object = "Point Light 0";
string UIName = "Lamp 0 Linear Attenuation";
string UIWidget ="slider";
> = 0;
bool UsePoint1
<
string UIName = "Use Point Light 1";
> = true;
float3 Point1Pos : POSITION
<
string Object = "Point Light 1";
string UIName = "Lamp 1 Position";
string Space = "World";
> = {0.5f,2.0f,1.25f};
float3 Point1Col : COLOR <
string Object = "Point Light 1";
string UIName = "Lamp 1 Color";
string UIWidget = "Color";
> = {1.0f,1.0f,1.0f};
float Point1Constant : CONSTANTATTENUATION <
string Object = "Point Light 1";
string UIName = "Lamp 1 Constant Attenuation";
string UIWidget ="slider";
> = 1;
float Point1Linear : LINEARATTENUATION <
string Object = "Point Light 1";
string UIName = "Lamp 1 Linear Attenuation";
string UIWidget ="slider";
> = 0.05;
float Point1Quadratic : QUADRATICATTENUATION <
string Object = "Point Light 1";
string UIName = "Lamp 1 Linear Attenuation";
string UIWidget ="slider";
> = 0;
// +--------------------------------------------+
// Surface Properties
texture NormalMap <
string ResourceName = "";
string UIName = "Normal Texture";
string ResourceType = "2D";
>;
sampler2D NormalMapSampler = sampler_state {
Texture = <NormalMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Wrap;
AddressV = Wrap;
};
float4 DiffuseColor : DIFFUSE <
string UIName = "Diffuse Color";
string UIWidget = "Color";
> = {0.93f, 0.87f, 0.75f, 1.0f};
texture DiffuseMap <
string ResourceName = "";
string UIName = "Diffuse Texture";
string ResourceType = "2D";
>;
sampler2D DiffuseMapSampler = sampler_state {
Texture = <DiffuseMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Wrap;
AddressV = Wrap;
};
// +--------------------------------------------+
// Specular
float3 SpecularColor : SPECULAR <
string UIName = "Specular Color";
string UIWidget = "Color";
> = {0.72, 0.70f, .5f};
texture SpecularMap <
string ResourceName = "";
string UIName = "Specular Texture";
string ResourceType = "2D";
>;
sampler2D SpecularMapSampler = sampler_state {
Texture = <SpecularMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Wrap;
AddressV = Wrap;
};
float SpecularIntensity <
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.01;
string UIName = "Specular Intensity";
> = 0.5;
texture TorranceMap <
string ResourceName = "";
string UIName = "Specular Roughness Texture";
string ResourceType = "2D";
>;
sampler2D TorranceMapSampler = sampler_state {
Texture = <TorranceMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Wrap;
AddressV = Wrap;
};
float SpecularRoughnessMin <
string UIName = "Specular Roughness Min";
string UIWidget = "slider";
float UIMin = 0.0f;
float UIMax = 1.0f;
float UIStep = .01;
> = 0.2f;
float SpecularRoughnessMax <
string UIName = "Specular Roughness Max";
string UIWidget = "slider";
float UIMin = 0.0f;
float UIMax = 1.0f;
float UIStep = .01;
> = 0.5f;
float SpecularFresnelMin <
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.01;
string UIName = "Specular Fresnel Min";
> = .15f;
float SpecularFresnelMax <
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.01;
string UIName = "Specular Fresnel Max";
> = .35f;
// +--------------------------------------------+
// Fake-Light
float3 RimColor <
string UIName = "Rimlight Color";
string UIWidget = "color";
> = { .5, .5, .59 };
float RimPower <
string UIWidget = "slider";
float UIMin = 0.1;
float UIMax = 30.0;
float UIStep = 0.1;
string UIName = "Rimlight Power";
> = 2.0;
float3 SubsurfaceColor <
string UIName = "Subsurface Color";
string UIWidget = "color";
> = { .65, .5, .5 };
float SubsurfaceRolloff <
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.01;
string UIName = "Subsurface Rolloff";
> = .2;
float3 BleedColor <
string UIName = "Bleed Color";
string UIWidget = "color";
> = { .3f, .1f, .1f };
float BleedFactor <
string UIName = "Bleed Factor";
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.01;
> = .3f;
// +--------------------------------------------+
// Interop
// +--------------------------------------------+
struct appdata
{
float3 Position : POSITION;
float4 UV : TEXCOORD0;
float4 Normal : NORMAL0;
float4 Tangent : TANGENT0;
float4 Binormal : BINORMAL0;
};
struct vertexOutput
{
float4 HPos : POSITION;
float2 UV : TEXCOORD0;
float3 Normal : TEXCOORD1;
float3 Tangent : TEXCOORD2;
float3 Binormal : TEXCOORD3;
float3 Eye : TEXCOORD4;
float3 Light0 : TEXCOORD5;
float3 Light1 : TEXCOORD6;
};
// +--------------------------------------------+
// VERTEX
// +--------------------------------------------+
vertexOutput SkinVS( appdata IN )
{
vertexOutput OUT;
// Pass Through
OUT.UV = float2( IN.UV.x, 1 - IN.UV.y );
// World Space
OUT.Normal = normalize( mul( float4(IN.Normal.xyz,0), WorldInverseTranspose ).xyz );
OUT.Tangent = normalize( mul( float4(IN.Tangent.xyz, 0), WorldInverseTranspose ).xyz );
OUT.Binormal = normalize( mul( float4(IN.Binormal.xyz, 0), WorldInverseTranspose ).xyz );
float4 MPos = float4( IN.Position.xyz, 1 );
float4 WPos = mul( MPos, World );
OUT.Light0 = Point0Pos - WPos.xyz;
OUT.Light1 = Point1Pos - WPos.xyz;
OUT.Eye = ViewInverse[3].xyz - WPos.xyz;
// Screen Space
OUT.HPos = mul( MPos, WorldViewProject );
return OUT;
}
// +--------------------------------------------+
// PIXEL UTILITY
// +--------------------------------------------+
float3 NormalFromTex2D( sampler2D normalSampler, float2 uv )
{
float3 vec = tex2D( normalSampler, uv ) * 2 - 1;
return ( vec );
}
struct FragData
{
float3 Normal;
float3 Eye;
float Roughness;
float Fresnel;
};
FragData Frag( float3 Normal, float3 Eye, float Roughness, float Fresnel )
{
FragData OUT;
OUT.Normal = Normal;
OUT.Eye = normalize( Eye );
OUT.Roughness = Roughness;
OUT.Fresnel = Fresnel;
return OUT;
}
struct Light
{
float3 Position;
float3 Direction;
float3 Color;
float Attenuation;
};
Light PointLight( float3 pos, float3 vec, float3 col, float constFall, bool linFall, float quadFall )
{
Light OUT;
OUT.Position = pos;
OUT.Direction = normalize( vec );
OUT.Color = col;
float mag = length( vec );
float atten = constFall + linFall * mag + quadFall * mag * mag;
OUT.Attenuation = saturate( 1 / atten );
return OUT;
}
Light DirectLight( float3 vec, float3 col )
{
Light OUT;
OUT.Position = 0;
OUT.Direction = normalize( -vec );
OUT.Color = col;
OUT.Attenuation = 1;
return OUT;
}
// +--------------------------------------------+
// PIXEL Light Equations
// NB: These assume the compiler will be
// smart enough to crunch out repeated
// calculations such as dot products
// If the compiler isn't aggressive
// enough, pre-calc the common ones.
// +--------------------------------------------+
float TorranceSpecular( float3 N, float3 L, float3 V, float roughness, float fresnel )
{ // Cook-Torrance Specular: DFG / E.N
// http://en.wikipedia.org/wiki/Cook-Torrance
// Input vectors must be normalized or bad things happen.
// Half Vector
float3 H = normalize( L + V );
// Common products
float VdotH = dot( V, H );
float NdotH = dot( N, H );
float NdotV = dot( N, V );
float NdotL = dot( N, L );
float NdotH_sq = NdotH * NdotH;
float R_sq = roughness * roughness;
float D = exp( -( 1.0f - NdotH_sq ) / ( R_sq * NdotH_sq ) );
D /= 4.0f * R_sq * NdotH_sq * NdotH_sq;
// Fresnel Eq #10 from http://developer.nvidia.com/object/fresnel_wp.html
float F = fresnel + ( 1 - fresnel ) * pow( 1 - NdotV, 5 );
float G1 = ( 2.0f * NdotH * NdotV ) / VdotH;
float G2 = ( 2.0f * NdotH * NdotV ) / VdotH;
float G = saturate( min( G1, G2 ) );
return ( D * F * G ) / max( NdotV, 1e-7 ); // Need to catch the potential INF.
}
float HalfLambert( float3 N, float3 L )
{
return ( dot( N, L) * .5f + .5f );
}
float LambSkin( float3 N, float3 L, float rolloff )
{ // NVidia's Lambskin SSS Method
float NdotL = dot( N, L );
// Lamb term is an expanded diffuse term
float subsurface = smoothstep( -rolloff, 1.0, NdotL ); // Expanded Diffuse Term
float mask = smoothstep( 0.0, 1.0, NdotL ); // Mask using a skewed Diffuse Term
return max( 0, subsurface - mask );
}
float Bleed( float3 N, float3 L, float3 V )
{ // InvalidPointer's "Hack" SSS Method.
// http://www.gamedev.net/community/forums/topic.asp?topic_id=481494
// Negative terms because we want to be on the "wrong" side of the surface
float bleedTerm = max( 0, -dot( N, L ) ); // Light that goes straight through.
float fresnel = 1 - HalfLambert( L, V ); // The Light gets brighter as you look down it
return bleedTerm + fresnel;
}
float Accumulate( FragData frag, Light light, inout float3 diffuse, inout float3 specular )
{
// Lambertian Diffuse
float NdotL = dot( frag.Normal, light.Direction );
// Torrance Specular
float torrance = TorranceSpecular( frag.Normal, light.Direction, frag.Eye, frag.Roughness, frag.Fresnel );
// Apply Light Factors
float diffTerm = saturate( NdotL );
float specTerm = saturate( NdotL * torrance );
float lambTerm = LambSkin( frag.Normal, light.Direction, SubsurfaceRolloff );
float bleedTerm = Bleed( frag.Normal, light.Direction, frag.Eye );
float3 sssTerm = SubsurfaceColor * lambTerm + BleedColor * BleedFactor * bleedTerm;
diffuse += ( sssTerm + diffTerm ) * light.Color * light.Attenuation; // Lambert
specular += specTerm * light.Color * light.Attenuation; // Phong
// Return the lighting amount. Useful for masking.
return diffTerm;
}
float Rim( FragData frag, float power )
{
float NdotV = dot( frag.Normal, frag.Eye );
float fresnel = saturate( pow( 1 - NdotV, power ) );
return fresnel;
}
// +--------------------------------------------+
// PIXEL
// +--------------------------------------------+
float4 SkinPS(vertexOutput IN) : COLOR
{
float3x3 TBN = float3x3( IN.Tangent, IN.Binormal, IN.Normal );
float3 Normal = NormalFromTex2D( NormalMapSampler, IN.UV );
float2 torranceMap = 1 - tex2D( TorranceMapSampler, IN.UV ).rb;
float Roughness = lerp( SpecularRoughnessMin, SpecularRoughnessMax, torranceMap.r );
float Fresnel = lerp( SpecularFresnelMin, SpecularFresnelMax, torranceMap.g );
FragData frag = Frag( mul( Normal, TBN ), IN.Eye, Roughness, Fresnel );
float3 diffTerm = 0;
float3 specTerm = 0;
float lit = 0;
if( UsePoint0 )
{
Light point0 = PointLight( Point0Pos, IN.Light0, Point0Col, Point0Constant, Point0Linear, Point0Quadratic );
lit += Accumulate( frag, point0, diffTerm, specTerm );
}
if( UsePoint1 )
{
Light point1 = PointLight( Point1Pos, IN.Light1, Point1Col, Point1Constant, Point1Linear, Point1Quadratic );
lit += Accumulate( frag, point1, diffTerm, specTerm );
}
if( UseDirect0 )
{
Light direct0 = DirectLight( Direct0Dir, Direct0Col );
lit += Accumulate( frag, direct0, diffTerm, specTerm );
}
// Clamp lighting Values.
diffTerm = saturate( diffTerm );
specTerm = saturate( specTerm * SpecularIntensity );
lit = saturate( lit );
float unlit = ( 1 - lit );
float3 ambientTerms = AmbientLight + RimColor * Rim( frag, RimPower );
// Mask Ambients to only places where there is no light.
diffTerm += unlit * ambientTerms;
float4 diffTex = tex2D( DiffuseMapSampler, IN.UV );
float4 specTex = tex2D( SpecularMapSampler, IN.UV );
diffTerm *= DiffuseColor.rgb * diffTex.rgb;
specTerm *= SpecularColor.rgb * specTex.rgb;
return float4( diffTerm + specTerm, diffTex.a);
}
// +--------------------------------------------+
// Technique
// +--------------------------------------------+
technique main
{
pass p0
{
VertexShader = compile vs_2_a SkinVS();
PixelShader = compile ps_2_a SkinPS();
ZEnable = true;
ZWriteEnable = true;
//CullMode= cw;
}
}