Game Development Community

Jill gets a makeover.... (big image warning)

by Pete Patterson · in Torque Game Engine Advanced · 04/29/2007 (7:32 am) · 13 replies

Looking like this is expensive

ideajuice.com/composite.png
Will post some shader code in a follow up post.

#1
04/29/2007 (7:38 am)
Looks interesting, like toon shader meets charcoal :-)
#2
04/29/2007 (7:46 am)
Here is a broad description of the technique...

The hair was stolen from the Rendermonkey hair example. Imported into the Jill model and the existing hair replaced with this geometry. The hair shader from Rendermonkey was massaged to work in Torque, and that's pretty much what you see.

The polygons for her boots were selected in Max and assigned their own material to keep them seperate from the rest of the body, which is mostly skin. The geometry is already designed so that it's easy to detach the boots, and although it's expensive to give them their own material, it got them out of the way. I may add them back in to one of the main materials at some point.

The skin shader was adapted from one written by Joel Styles (aka J.I. Styles) http://www.jistyles.com

For the main body, I got the skin example mostly working, and that's what you see doing the skin rendering. The coolest part of this is that the skin is rendered in one pass, and the clothes are rendered in another pass on top of it. This means I should be able to create the PNG files needed for the camoflauge clothing that came with the Jill model, and use most of the same shader code for that.

The skin shader used in the first pass works mostly the way it was written by Styles. The second pass uses the alpha channel of the bump map to pass a per pixel blending mask that allows the clothing to replace the underlying skin render selectively. At this point, the second pass is a basic diffuse plus ambient plus specular shader, but with a little more work it should be able to be improved.

I'd also like to add the ability for Jill to have metallic items on her clothing, and also possibly emmissive glowing things like a PDA display or such. I plan on doing that by using the per pixel blend info coming in on the bump map alpha channel to have the shader produce different results on different areas of the clothing.

NB - the photoshop files are still a little rough at the seams. This is mostly programmer art to learn how to do multi pass shaders, and prove the concept of having shaders that shade different areas of a model using different looks. It's also very likely that this shader would fail miserably on older hardware without me creating fallback versions that use even more passes.
#3
04/29/2007 (8:44 am)
The Skin Vertex Shader Skin1V.hlsl:


#define IN_HLSL
#include "shdrConsts.h"

struct VertData
{
   float4 position        : POSITION;
   float3 normal          : NORMAL;
   float4 texCoord        : TEXCOORD0;
   float3 T               : TEXCOORD2;
   float3 B               : TEXCOORD3;
   float3 N               : TEXCOORD4;
};

struct VertexOutput 
{
    float4 Position		:	POSITION;
    float2 TexCoord		:	TEXCOORD0;
    float3 WorldNormal	:	TEXCOORD1;
    float3 EyeVec     	:	TEXCOORD2;
    float3 LightVec		:	TEXCOORD3;
    float3 WorldTangent	: 	TEXCOORD4;
    float3 WorldBinormal: 	TEXCOORD5;
};

VertexOutput main( VertData IN,
                  uniform float4x4 modelview       : register(C0),
                  uniform float3   inLightVec      : register(C24),
                  uniform float3   eyePos          : register(C20),
                  uniform float4x4 objTrans        : register(C12),
                  uniform float4x4 matView          : register(C16)
)
{
    VertexOutput OUT;

    OUT.WorldNormal 	= IN.N;
    OUT.WorldTangent 	= IN.T;
    OUT.WorldBinormal 	= IN.B;

    float3 Pview = mul( matView, IN.position ); 

    OUT.LightVec = inLightVec;

    OUT.TexCoord.xy 	= IN.texCoord.xy;
    OUT.EyeVec 		= normalize(IN.position - eyePos);
    OUT.Position 	= mul(modelview, IN.position);
    return OUT;
}
#4
04/29/2007 (8:45 am)
The Skin Pixel Shader Skin1P.hlsl:

#define IN_HLSL
#include "shdrConsts.h"

/*
Originally written by Joel Styles (aka J.I. Styles)
Adapted to TGEA
http://www.jistyles.com
*/

const float4 LightPosition1 : POSITION
<
    string Object = "PointLight";
    string UIName =  "Light 1";
    string Space = "World";
    int refID = 0;
> 	= {1403.0f, 1441.0f, 1690.0f, 0.0f};


const float4 LightColour1 : LIGHTCOLOR
<
    int LightRef = 0;
> 	= { 1.0f, 1.0f, 1.0f, 0.0f };


const float4 LightPosition2 : POSITION
<
    string Object = "PointLight";
    string UIName =  "Light 2";
    string Space = "World";
    int refID = 1;
> 	= {1403.0f, 1441.0f, 1690.0f, 0.0f};


const float4 LightColour2 : LIGHTCOLOR
<
    int LightRef = 1;
> 	= { 1.0f, 1.0f, 1.0f, 0.0f };


const float4 LightPosition3 : POSITION
<
    string Object = "PointLight";
    string UIName =  "Light 3";
    string Space = "World";
    int refID = 2;
> 	= {1403.0f, 1441.0f, 1690.0f, 0.0f};


const float4 LightColour3 : LIGHTCOLOR
<
    int LightRef = 2;
> 	= { 1.0f, 1.0f, 1.0f, 0.0f };

const float4 SpecColour
<
    string UIWidget = "Color";
    string UIName =  "Specular Colour";
>  = {0.45f, 0.65f, 1.0f, 1.0f};


const float SpecPower
<
    string UIWidget = "Slider";
    float UIMin = 0.0;
    float UIMax = 100.0;
    float UIStep = 0.01;
    string UIName =  "Specular Power";
> = 0.2;


const float SpecGloss
<
    string UIWidget = "Slider";
    float UIMin = 0.0;
    float UIMax = 100.0;
    float UIStep = 0.01;
    string UIName =  "Specular Glossiness";
> = 1.0;

const float SpecFresnel
<
    string UIWidget = "Slider";
    float UIMin = 0.0;
    float UIMax = 100.0;
    float UIStep = 0.01;
    string UIName =  "Specular Fresnel";
> 	= 1.0;

const float FresnelPower
<
    string UIWidget = "Slider";
    float UIMin = 0.0;
    float UIMax = 100.0;
    float UIStep = 0.01;
    string UIName =  "Specular Fresnel Power";
> 	= 0.01;


const float FresnelGloss
<
    string UIWidget = "Slider";
    float UIMin = 0.0;
    float UIMax = 100.0;
    float UIStep = 0.01;
    string UIName =  "Specular Fresnel Gloss";
> 	= 0.0;

const float4 TransColIn
<
    string UIWidget = "Color";
    string UIName =  "Translucency - Unscattered";
>  = {0.87f, 0.91f, 0.96f, 1.0f};

const float4 TransColOut
<
    string UIWidget = "Color";
    string UIName =  "Translucency - Melanin";
>  = {1.0f, 0.71f, 0.32f, 1.0f};

const float4 TransColBack
<
    string UIWidget = "Color";
    string UIName =  "Translucency - Hemoglobin";
>  = {0.58f, 0.2f, 0.24f, 1.0f};

const float TransMultiplier
<
    string UIWidget = "Slider";
    float UIMin = 0.0;
    float UIMax = 100.0;
    float UIStep = 0.01;
    string UIName = "Translucent Power";
> 	= 0.002;

const float TransRampOff
<
    string UIWidget = "slider";
    float UIMin = -100.0;
    float UIMax = 100.0;
    float UIStep = 0.01;
    string UIName = "Translucent Ramp Off";
> 	= 1.5;

const float MicroScale
<
    string UIWidget = "slider";
    float UIMin = -1000;
    float UIMax = 1000;
    float UIStep = 1;
    string UIName = "Micro Structure Scale";
> 	= 50;


struct VertexOutput 
{
    float4 Position		:	POSITION;
    float2 TexCoord		:	TEXCOORD0;
    float3 WorldNormal	:	TEXCOORD1;
    float3 EyeVec     	:	TEXCOORD2;
    float3 LightVec		:	TEXCOORD3;
    float3 WorldTangent	: 	TEXCOORD4;
    float3 WorldBinormal: 	TEXCOORD5;
};

float4 TransPass(	float4 DotLN,
            float2 TexUV)
{
    float4 Translucence 	= smoothstep(-TransRampOff  * 0.6,1.0,DotLN) 
                    - smoothstep(1,1,DotLN);
    float4 Colourise		= lerp(TransColBack, lerp(TransColOut * TransMultiplier,TransColIn,DotLN),Translucence);
    
    return ( 0.1 * Colourise*Translucence);
}

//================Specular component 1 (front on)=======================
float4 SpecularFrontOn(	float3 Normals,
                    float3 EyeVec,
                    float3 LightVec,
                    float4 Power,
                    float4 Gloss)
{
    float3 SpecReflect = (2 * dot(Normals,LightVec) * Normals - LightVec);
    float4 Specular = pow(saturate(dot(SpecReflect, EyeVec)), Gloss) * Power;
    Specular.rgb = SpecReflect;
    Specular.rgb = saturate(dot(SpecReflect, EyeVec));
    return Specular * 0.05;
}

float4 FresnelMask(	float3 Normals,
                float3 EyeVec)
{
    float4 Fresnel = dot(EyeVec,Normals) * SpecFresnel;
    return saturate(Fresnel);
}

float4 main(VertexOutput IN,
              uniform sampler2D DiffSampler      : register(S0),
              uniform sampler2D NormalSampler         : register(S1),
              uniform sampler2D D2Sampler         : register(S2),
              uniform sampler2D N2Sampler         : register(S3)
) : COLOR 
{

    float2 TexUV		= IN.TexCoord.xy;
    float3 normal 		= (tex2D(NormalSampler,TexUV).xyz * 2.0 - 1.0);
    float blend = tex2D(NormalSampler,TexUV).w;

   IN.LightVec = IN.LightVec * 2.0 - 1.0;

    float3 WN = IN.WorldNormal;
    float3 TN = IN.WorldTangent;
    float3 BN = IN.WorldBinormal;

    float3 N = (WN * normal.z) + (normal.x * BN + normal.y * -TN);
    N = normalize(N);

    float3 EV 			= normalize(IN.EyeVec);
    float3 LV 			= normalize(IN.LightVec.xyz);

    float4 DotLN		= dot(LV,N);

    float4 Translucency	= TransPass(DotLN,TexUV);
    float4 a 			= tex2D(DiffSampler,TexUV);
    float4 b 			= (Translucency);
    float4 S	= ((1 - a) * (a*b) + a * (1 - (1 - a) * (1 - b))) * b;

    float4 FresnelStrength	= saturate(1 - FresnelMask(WN,EV)) * FresnelPower;// + 1;
    float4 FresnelGlossiness	= FresnelMask(WN,EV) * FresnelGloss;// + 1;
    float4 Spec			= SpecularFrontOn(N,EV,LV,SpecPower * FresnelStrength ,SpecGloss * FresnelGlossiness * a.a);

    S.rgb += Spec.rgb;
    float4 BaseLighting	= lerp(a, S, blend);
    return (BaseLighting);
}
#5
04/29/2007 (8:48 am)
The second pass clothing vertex shader Skin2V.hlsl:

#define IN_HLSL
#include "shdrConsts.h"

//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct VertData
{
   float4 texCoord        : TEXCOORD0;
   float2 lmCoord         : TEXCOORD1;
   float3 T               : TEXCOORD2;
   float3 B               : TEXCOORD3;
   float3 N               : TEXCOORD4;
   float3 normal          : NORMAL;
   float4 position        : POSITION;
};

struct VertexOutput 
{
    float4 Position		:	POSITION;
    float2 TexCoord		:	TEXCOORD0;
    float3 WorldNormal	:	TEXCOORD1;
    float3 EyeVec     	:	TEXCOORD2;
    float3 LightVec		:	TEXCOORD3;
    float3 WorldTangent	: 	TEXCOORD4;
    float3 WorldBinormal	: 	TEXCOORD5;
    float3 Normal	:	TEXCOORD6;
};


//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
VertexOutput main( VertData IN,
                  uniform float4x4 modelview       : register(C0),
                  uniform float4x4 texMat          : register(C4),
                  uniform float3   inLightVec      : register(C24),
                  uniform float3   eyePos          : register(C20),
                  uniform float4x4 objTrans        : register(C12)
)
{
    VertexOutput OUT;

    OUT.WorldNormal 	= IN.N;
    OUT.WorldTangent 	= IN.T;
    OUT.WorldBinormal 	= IN.B;

    OUT.LightVec = -inLightVec;

    OUT.TexCoord.xy 	= IN.texCoord.xy;
    OUT.EyeVec 		= normalize(IN.position - eyePos);
    OUT.Position 	= mul(modelview, IN.position);
    OUT.Normal = IN.normal;
    return OUT;
}
#6
04/29/2007 (8:49 am)
The second pass clothing pixel shader Skin2P.hlsl:

#define IN_HLSL
#include "shdrConsts.h"

struct VertexOutput 
{
    float4 Position		:	POSITION;
    float2 TexCoord		:	TEXCOORD0;
    float3 WorldNormal	:	TEXCOORD1;
    float3 EyeVec     		:	TEXCOORD2;
    float3 LightVec		:	TEXCOORD3;
    float3 WorldTangent	: 	TEXCOORD4;
    float3 WorldBinormal	: 	TEXCOORD5;
    float3 normal	: 	TEXCOORD6;
};


float4 main(VertexOutput IN,
              uniform sampler2D DASampler      : register(S0),
              uniform sampler2D NBSampler         : register(S1),
              uniform sampler2D DiffSampler         : register(S2),
              uniform sampler2D NormSampler         : register(S3),
              uniform float4    ambient         : register(C2)
) : COLOR 
{

    float2 TexUV		= IN.TexCoord.xy;
    float3 normal 		= (tex2D(NormSampler,TexUV).xyz * 2.0 - 1.0);
    normal += IN.normal.xyz - 0.5;
    float3 halfAng = normalize(IN.LightVec - IN.EyeVec);
    float4 bumpDot = saturate( dot(normal, halfAng) );

    float blend = tex2D(NormSampler,TexUV).w;
    float4 a 			= tex2D(DiffSampler,TexUV);

    float specular = saturate(pow(bumpDot, 16.0) ) * a.a;

    a += specular;
    a *= ambient;
    a.a = blend;
    return (a);
}
#7
04/29/2007 (8:51 am)
Script bits:

In client/shaders.cs :

new ShaderData( skinShader1 )
{
   DXVertexShaderFile     = "shaders/skin1V.hlsl";
   DXPixelShaderFile      = "shaders/skin1P.hlsl";
   pixVersion = 2.0;
};

new ShaderData( skinShader2 )
{
   DXVertexShaderFile     = "shaders/skin2V.hlsl";
   DXPixelShaderFile      = "shaders/skin2P.hlsl";
   pixVersion = 2.0;
};

In some materials.cs file:

new CustomMaterial(mat_lp29b)
{
   mapTo = "mat_lp29b";
   texture[0] = "jillnude";
   texture[1] = "jillnudeb";
   texture[2] = "jillleather";
   texture[3] = "jillleatherb";
   blendOp = LerpAlpha;
   shader = skinShader2;
   version = 2.0;
   specular[0] = "0.6 0.6 0.6 0.6";
   specularPower[0] = 16.0;
};

new CustomMaterial(mat_lp29)
{
   mapTo = "base.jilll";
   texture[0] = "jillnude";
   texture[1] = "jillnudeb";
   texture[2] = "jillleather";
   texture[3] = "jillleatherb";
   shader = skinShader1;
   version = 2.0;
   specular[0] = "0.6 0.6 0.6 0.6";
   specularPower[0] = 16.0;
   pass[0] = mat_lp29b;
};
#8
04/29/2007 (9:06 am)
Notes:

Even though the material uses two passes, I still seem to have to set all four textures for both shaders. In other words, I ought to be able to do the first pass with just the nude texture, and the second pass with just the clothing textures. I think that what is happening is that it's potentially possible for hardware to render two passes simultaneously by configuring it's pipelines to feed the output of one pass directly into the input of another pass, so you need to assign all the textures for all the passes before you even begin doing the first pass.

There may be unused variables and other artifiacts around. This stuff is still in the playing around stages, but it's a useful example.

These shaders don't neccessarily use available TGEA variables that the engine is going to the trouble of providing the way they should. Most of them are still hooked up to the hardcoded variables that came with the original shader. Things like the specular power and color are being provided by the engine and these should be modified to use those whenever possible so they can be passed in from script definitions.

A lot of shaders seem to want a View matrix, and after much navel gazing it seems that they want one in order to calculate an eye vector. Torque provides an eye position, but no info on what direction the camera is actually looking.. just where the camera is relative to the object in question. I figured that I could just provide an eye vector by subtracting the eye position from the object position and seeing how that worked.

It seems to work fairly well, but I'm not 100% comfortable that it's doing the same thing the original did. If anyone out there has a good solid explanation of how to derive the camera matrix, including the direction the camera is facing, from a shader in Torque, I'd love to hear it.

If anyone is really gung ho, in materials->Material.cpp->Material::setShaderConstants():

// fill in cubemap data
   //-------------------------
   if( mCubemapData || sgData.cubemap )
   {
      Point3F cubeEyePos = sgData.camPos - sgData.objTrans.getPosition();
      GFX->setVertexShaderConstF( VC_CUBE_EYE_POS, (float*)&cubeEyePos, 1 );

      MatrixF cubeTrans = sgData.objTrans;
      cubeTrans.setPosition( Point3F( 0.0, 0.0, 0.0 ) );
      cubeTrans.transpose();
      GFX->setVertexShaderConstF( VC_CUBE_TRANS, (float*)&cubeTrans, 3 );
   }
   else
   {
      MatrixF viewTrans = GFX->getViewMatrix();
      GFX->setVertexShaderConstF( VC_CUBE_TRANS, (float*)&viewTrans, 4 );
   }

This small addition will get the engine to provide a View matrix to the vertex shader whenever there is not a cube map defined for the material. This is a big ugly hack, and it may break things like atlas or water textures, but it can be a big help when porting a shader that expects to use a view matrix to get it up and running while you figure out how to recode it so it doesn't need one.

To create the four textures needed:

Combine the RGB of the jill photoshop nude layer with a layer mask that contains more white where you want more specular effect.

Save that out as a PNG with alpha.

Now create a normal map using the nVidia normal map filter as you prefer. Since this is just the nude map, don't put any bump mapping in to simulate the clothing parts. Add a layer mask to this normal map layer, and into the layer mask, put a blend greyscale with dark where you don't want the skin effect to happen. Basically, the eyes, teeth, lips and eyebrows should probably not have the skin applied so they get dark, and the rest of the skin gets a fairly bright white with a little bit of variance to simulate thinner and thicker skin areas.

Given these two textures, the first pass shader will apply the skin effect more where the alpha channel of the bump map has a brighter color.

The main texture for the second pass is made by creating a layer with just the clothing on it, and adding the regular specular channel to that as a layer mask. I put a bit of time into reworking the stock jill leather outfit so that there wasn't so much ligthing and depth baked into the clothing. I wanted to see the shader supplying that.

The bump texture for the second pass was also generated using the nVidia normal filter plugin, and then a blending mask was added to it as a layer mask so that it's alpha channel controls where the clothes go over top of the first pass. Where the alpha is zero, no clothes exist, and where the alpha is white, the clothes completely overwrite the skin.
#9
04/29/2007 (11:57 am)
Thanks for sharing.
Can you please also post the shader for hair?

Just a quick note on the color of the skin: it's too red, as if she got a sun burn.

Thanx.
#10
04/29/2007 (1:38 pm)
Thank you for sharing.
#11
04/30/2007 (6:10 am)
@Sorin i believe he mention it was taken from rendermonkey, which can be downloaded at www.Ati.com in the developers section, good luck with the shaders:)
#12
04/30/2007 (7:09 am)
Turns out that there was a giant bug in the shader source I posted. Basically, none of the const shader variables used in the pixel shader are being picked up by the functions that use them. I have no idea how I managed to get it to even look like it was working.

On the up side, now that I've discovered the problem, I'll be able to post fixed code at some point soon.

Sorin's comment about the red tint sent me down the path that revealed the problem. I realized that I was still using the original pink colored diffuse texture even though the shader adds the skin tints dynamically, so I assumed that she was getting two doses of pink - one from the texture itself, and one from the shader. I desaturated the main diffuse skin texture to a greyscale, and then tried to make an Oriental jill by changing the skin tint colors in the shader, and that's when I discovered that the shader variables had no effect.

Moving the shader variable declarations inside the functions they are used seems to fix the problem. Will post the fixed source and a new screenshot with some tinted Jills when I'm happy that it's working as it should.
#13
04/30/2007 (7:14 am)
This code works more as it should:

www.ideajuice.com/jillblue.png
That image is using a completly uncolored diffuse map for Jill's skin. All of the color is coming from the shader and the three shader variables

const float4 TransColIn = {0.93f, 0.81f, 0.86f, 1.0f};
const float4 TransColOut = {1.0f, 0.61f, 0.22f, 1.0f};
const float4 TransColBack = {0.68f, 0.15f, 0.2f, 1.0f};

I tinted her blue in this shot just to verify that they are working. Obviously the shader has a lot of tuning that could be done.

#define IN_HLSL
#include "shdrConsts.h"

const float4 SpecColour
<
    string UIWidget = "Color";
    string UIName =  "Specular Colour";
>  = {0.45f, 0.65f, 1.0f, 1.0f};

const float MicroScale
<
    string UIWidget = "slider";
    float UIMin = -1000;
    float UIMax = 1000;
    float UIStep = 1;
    string UIName = "Micro Structure Scale";
> 	= 50;


struct VertexOutput 
{
    float4 Position		:	POSITION;
    float2 TexCoord		:	TEXCOORD0;
    float3 WorldNormal	:	TEXCOORD1;
    float3 EyeVec     	:	TEXCOORD2;
    float3 LightVec		:	TEXCOORD3;
    float3 WorldTangent	: 	TEXCOORD4;
    float3 WorldBinormal: 	TEXCOORD5;
};

float4 TransPass(	float4 DotLN,
            float2 TexUV)
{

const float4 TransColIn  = {0.93f, 0.81f, 0.86f, 1.0f};
const float4 TransColOut  = {1.0f, 0.61f, 0.22f, 1.0f};
const float4 TransColBack  = {0.68f, 0.15f, 0.2f, 1.0f};
/*
const float4 TransColIn  = {0.87f, 0.91f, 0.96f, 1.0f};
const float4 TransColOut  = {1.0f, 0.71f, 0.32f, 1.0f};
const float4 TransColBack  = {0.58f, 0.2f, 0.24f, 1.0f};
*/
const float TransMultiplier 	= 1.0;
const float TransRampOff 	= 1.5;

    float4 Translucence 	= smoothstep(-TransRampOff  * 0.6,1.0,DotLN) 
                    - smoothstep(1,1,DotLN);
    float4 col1 = lerp(TransColOut * TransMultiplier,TransColIn,DotLN);
    float4 Colourise		= lerp(TransColBack, col1, Translucence);
    
    return (Colourise*Translucence);
}

//================Specular component 1 (front on)=======================
float4 SpecularFrontOn(	float3 Normals,
                    float3 EyeVec,
                    float3 LightVec,
                    float4 Power,
                    float4 Gloss)
{
    float3 SpecReflect = (2 * dot(Normals,LightVec) * Normals - LightVec);
    float4 Specular = pow(saturate(dot(SpecReflect, EyeVec)), Gloss) * Power;

    return Specular;
}

float4 FresnelMask(	float3 Normals,
                float3 EyeVec)
{
	const float SpecFresnel 	= 0.50;

    float4 Fresnel = dot(EyeVec,Normals) * SpecFresnel;
    return saturate(Fresnel);
}

float4 main(VertexOutput IN,
              uniform sampler2D DiffSampler      : register(S0),
              uniform sampler2D NormalSampler     : register(S1),
              uniform sampler2D D2Sampler         : register(S2),
              uniform sampler2D N2Sampler         : register(S3)
) : COLOR 
{

    float2 TexUV		= IN.TexCoord.xy;
    float3 normal 		= (tex2D(NormalSampler,TexUV).xyz * 2.0 - 1.0);
    float blend = tex2D(NormalSampler,TexUV).w;

   IN.LightVec = IN.LightVec * 2.0 - 1.0;

    float3 WN = IN.WorldNormal;
    float3 TN = IN.WorldTangent;
    float3 BN = IN.WorldBinormal;

    float3 N = (WN * normal.z) + (normal.x * BN + normal.y * -TN);
    N = normalize(N);

    float3 EV 			= normalize(IN.EyeVec);
    float3 LV 			= normalize(IN.LightVec.xyz);

    float4 DotLN		= dot(LV,N);

    float4 Translucency	= TransPass(DotLN,TexUV);
    float4 a 			= tex2D(DiffSampler,TexUV);
    float4 b 			= (Translucency);
    float4 S	= ((1 - a) * (a*b) + a * (1 - (1 - a) * (1 - b))) * b;

	const float FresnelPower 	= 1.0;
	const float FresnelGloss 	= 0.0;
	const float SpecGloss = 3.0;
	const float SpecPower = 0.2;

    float4 FresnelStrength	= saturate(1 - FresnelMask(WN,EV)) * FresnelPower;// + 1;
    float4 FresnelGlossiness	= FresnelMask(WN,EV) * FresnelGloss;// + 1;
    float4 Spec			= SpecularFrontOn(N,EV,LV,SpecPower * FresnelStrength ,SpecGloss * FresnelGlossiness * a.a);

    S.rgb += Spec.rgb;
    float4 BaseLighting	= lerp(a, S, blend);
    return (BaseLighting);
}

The vertex shader for skin1V.hlsl also needs to do:

OUT.LightVec = -inLightVec;

basically, the lightVec needs to be inverted from my original post. I'm leaving the incorrect code above so that other people can see what I did wrong originally. Hopefully it saves someone else some pain.