Does anyone have a Skin Data Set I can use?

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;
    }
}

And because this is Technical Discussion and I really like talking shop about shaders, here’s what’s going on in that shader!

There are two SSS-ish terms going on. There is NVidia’s Lambskin method as well as the version found in this thread. These are added to a standard Lambertian Diffuse to get a total diffuse lighting term. NdotL is saved off and used to knock out the ambient, so you can do heavily colored ambient lighting. I don’t know… it’s not physically correct, but really like that look. Rimlighting is masked the same way.

Specular is just a Cook-Torrance. It reads from a map where the more Red present, the smoother the surface is, and the more Blue present, the more upright the reflection angle is. These values act as a lerp between two artist-controllable clamps. The result is one of the more understandable cook-torrance setups I’ve yet been able to come up with.

Standard Diffuse / Specular Maps. It can take a Normal map, but I just used a flat one for these screenshots, because I didn’t really have anything.

I attached some screenshots of the intermediates, because pictures always help.