Game Development Community

Specular map diffuse shader

by Jameson Bennett · in Torque Game Engine Advanced · 05/25/2005 (10:12 am) · 9 replies

There's been some demand for a diffuse color bump mapped shader that uses a bitmap to define the specular color, attenuation and power rather than individual values. This allows artists freedom to play around and create interesting materials. With differing specular values a wide range of visual 'texture' can be defined. I put together a quick example to post on the forums based on the procedural textures generated by TSE. Hopefully it will help others mess around a bit with this technique. Feel free to use as needed, I've added comments in the pixel shader file to illustrate how to change the alpha channels used.

NOTE: this shader has not been profiled nor optimized. I expect it may not be too bad, but does involve another texture lookup vs. the standard shader and a few more calculations (muls and adds). I also have been lazy and only compiled under 2.0 to test that it works as expected: you may be able to compile and run it under an earlier version.

in materials.cs: (server/scripts)
datablock CustomMaterial(MySpecMapCustomMat)
{
	version = 2.0;
	texture[0] = "~/data/test/myskin";
	texture[1] = "~/data/test/myskinNorm";
	texture[2] = "~/data/test/myskinSpec";
	
	shader = JSkinSpecMap;

};
Of course make sure the above myskin matches your myskin.png files.

in shaders.cs: (server/scripts)
datablock ShaderData( JSkinSpecMap )
{
   DXVertexShaderFile     = "shaders/JSkinSpecMapV.hlsl";
   DXPixelShaderFile      = "shaders/JSkinSpecMapP.hlsl";
   pixVersion = 2.0;
};

in materialMap.cs: (data)
addMaterialMapping("mydtsmodeltexture", "material: MySpecMapCustomMat");

#1
05/25/2005 (10:13 am)
And now for the vertex shader (modified with workaround to address issue with ts normals). Copy into new text doc, paste and save as JSkinSpecMapV.hlsl in the shaders directory. You can change the name to whatever (or create new modified files), but make sure you update the ShaderData datablock to reflect the new file name/location.
//*****************************************************************************
// OSJ basic skin vertex shader
// Based on TSE procedural shader
// modified to correct for normal map issue   
//*****************************************************************************
//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct VertData
{
   float2 texCoord        : TEXCOORD0;
   float3 T               : TEXCOORD2;
   float3 B               : TEXCOORD3;
   float3 N               : TEXCOORD4;
   float3 normal          : NORMAL;
   float4 position        : POSITION;
};


struct ConnectData
{
   float4 hpos            : POSITION;
   float2 outTexCoord     : TEXCOORD0;
   float4 outLightVec     : TEXCOORD2;
   float3 pos             : TEXCOORD3;
   float3 outEyePos       : TEXCOORD4;
};


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

   OUT.hpos = mul(modelview, IN.position);
   OUT.outTexCoord = IN.texCoord;

   float3x3 objToTangentSpace;
   objToTangentSpace[0] = IN.T;
   objToTangentSpace[1] = IN.B;
	//WORKAROUND  
	//this:
   objToTangentSpace[2] = IN.normal;
	//was originally (uncomment below and comment out above
	//to use this shader for interiors):
	//objToTangentSpace[2] = IN.N;
	//should be changed back after ms3 when IN.N is fixed for ts

   OUT.outLightVec.xyz = -inLightVec;
   OUT.outLightVec.xyz = mul(objToTangentSpace, OUT.outLightVec);
   OUT.pos = mul(objToTangentSpace, IN.position.xyz / 100.0);
   OUT.outEyePos.xyz = mul(objToTangentSpace, eyePos.xyz / 100.0);
   OUT.outLightVec.w = step( 0.0, dot( -inLightVec, IN.normal ) );

   return OUT;
}
#2
05/25/2005 (10:14 am)
Finally the Pixel shader, save as JSkinSpecMapP.hlsl in the shaders directory:
//*****************************************************************************
// OSJ basic skin pixel shader
// Based on TSE procedural shader
// modifications:
//  specular map rgb used for specular color, 
//  spec alpha used for specular power (range 1-128)
//    black will not bring the specular to a power (ie spec^1),
//    white will bring spec^128
//  specular attenuation is still controlled by the diffuse color map alpha channel.
//    black will show no specular (fully attenuated), white will show full specular
//*****************************************************************************

//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct ConnectData
{
   float2 texCoord        : TEXCOORD0;
   float4 lightVec        : TEXCOORD2;
   float3 pixPos          : TEXCOORD3;
   float3 eyePos          : TEXCOORD4;
};


struct Fragout
{
   float4 col : COLOR0;
};


//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
Fragout main( ConnectData IN,
              uniform sampler2D diffuseMap      : register(S0),
              uniform sampler2D bumpMap         : register(S1),
				  uniform sampler2D specMap			: register(S2),
              uniform float4    ambient         : register(C2)
)
{
   Fragout OUT;

   //Diffuse
   float4 diffuseColor = tex2D(diffuseMap, IN.texCoord);
   OUT.col = diffuseColor;
   
   //Bump
   float4 bumpNormal = tex2D(bumpMap, IN.texCoord);
   float4 bumpDot = saturate( dot(bumpNormal.xyz * 2.0 - 1.0, IN.lightVec.xyz) );
   OUT.col *= bumpDot + ambient;

   //Specular
   float3 eyeVec = normalize(IN.eyePos - IN.pixPos);
   float3 halfAng = normalize(eyeVec + IN.lightVec.xyz);
   float specular = 
	saturate( dot(bumpNormal.xyz * 2.0 - 1.0, halfAng) ) * IN.lightVec.w;
	
	//SpecMap-->
	//get data from the specular map
	float4 specColor = tex2D(specMap, IN.texCoord);
	//tighten edge of specular based on power defined by the specMap alpha channel
	//scale and offset for 1-128 scale
	specular = pow(specular, (specColor.a * 127) +1);
	//change '127' to change the overall range
	//blend in and attenuate according to the diffuse color alpha
	//exhange the above specColor.a with the below diffuseColor.a
	//if you want to use the channels differently
	//or possibly use bumpNormal.a for either if you want to 
	//use the normal map alpha channel
   OUT.col += specColor * specular * diffuseColor.a;
	//SpecMap<--
	
   return OUT;
}


Let me know how it works out!
#3
05/25/2005 (1:15 pm)
Nice..
#4
02/25/2008 (2:17 am)
The hlsl code does not seem to be compatible with the current TGEA 1.0.3.
#5
02/25/2008 (5:53 am)
Replace "datablock" with "new" and it should work.
#6
02/25/2008 (12:45 pm)
Jameson, sometime ago I merged 2 shaders submitted as resources in order to have a Parallax shader capable of specular and dynamic lights... What i think it miss is specular based on a texture instead of having the material completely specular... Dou you think you can take a look into it?
That done I'd like to submit it to the community as a resource...

JoZ
#7
03/23/2008 (10:52 pm)
Thanks - just what I was looking for :)
#8
06/20/2008 (12:20 pm)
Any idea why no matter what color I put for the sun's ambient color in the mission editor, the ambient value in this shader is always gray?
#9
06/20/2008 (5:20 pm)
Could be related to this bug if you are using the shader on an Interior (since it was never setting the light's ambient value to match the Sun's ambient value).