TSE/Shaders/NewShaders
From TDN
|
Introduction
So you want to take your shader you developed in FX composer or RenderMonkey? Here's how to go about doing it. This tutorial is more centered on RenderMonkey, but since the HLSL files are the same, you don't need to worry about a thing.
Copy shader to shaderV.hlsl and shaderP.hlsl files
The first step is to copy the vertex and pixel shaders from RenderMonkey and put them in their own separate files. The TSE convention is to append a "V" after the name of a vertex shader, and a "P" after the name of a pixel shader. Make sure you copy both the top and bottom parts of the shader code displayed. In the case of the glitter shader, your files should look something like this:
glitterV.hlsl:
float4x4 view_proj_matrix: register(c0);
float4x4 view_matrix: register(c4);
struct VS_OUTPUT {
float4 Pos: POSITION;
float3 normal: TEXCOORD0;
float3 viewVec: TEXCOORD1;
float3 pos: TEXCOORD2;
};
VS_OUTPUT main(float4 Pos: POSITION, float3 normal: NORMAL){
VS_OUTPUT Out;
Out.Pos = mul(view_proj_matrix, Pos);
// Eye-space lighting
Out.normal = mul(view_matrix, normal);
Out.viewVec = mul(view_matrix, -Pos);
// Pass position to the fragment shader
Out.pos = Pos;
return Out;
}
glitterP.hlsl:
float4 color: register(c1);
float glitterStrength: register(c2);
float4 lightDir: register(c0);
sampler Noise: register(s0);
float4 main(float3 normal: TEXCOORD0, float3 viewVec: TEXCOORD1, float3 pos: TEXCOORD2) : COLOR {
float noisy = tex3D(Noise, pos * 0.04);
normal = normalize(normal);
// Basic lighting
float diffuse = dot(lightDir, normal);
float specBase = saturate(dot(reflect(-normalize(viewVec), normal), lightDir));
// Extract some random points to glitter.
// This is done by perturbing a grid pattern with some noise and
// with the view-vector to let the glittering change with view.
float3 fp = frac(0.7 * pos + 9 * noisy + 0.1 * viewVec);
fp *= (1 - fp);
float glitter = saturate(1 - 7 * (fp.x + fp.y + fp.z));
float specular = pow(specBase, 24);
// Only glitter around the specular highlight. We use a lower
// exponent for the glitter to let it spread more.
float glittering = glitter * pow(specBase, 1.5);
// Get a little more interesting base color
float4 base = (0.5 * noisy + 0.5) * color;
return base * diffuse + 0.5 * specular + glitterStrength * glittering;
}
Copy these files into the example/shaders directory.
Create ShaderData definition in shaders.cs
Open client/scripts/shaders.cs and add a new ShaderData definition:
new ShaderData( GlitterShader )
{
DXVertexShaderFile = "shaders/glitterV.hlsl";
DXPixelShaderFile = "shaders/glitterP.hlsl";
pixVersion = 2.0;
};
Create CustomMaterial definition in materials.cs
Open client/scripts/materials.cs and add a new CustomMaterial definition: ( or in data/shapes as the demo does this)
new CustomMaterial(Glitter)
{
texture[0] = "~/data/textures/noiseVolume.dds";
shader = GlitterShader;
version = 2.0;
};
Copy the textures that are used in the shader to whatever resource directory you want to use. In the CustomMaterial definition, make sure to match up the texture index up with whatever unit the shader is referencing. Ie. if the shader needs a noise texture in unit 0 and a gradiant texture in unit 1, make sure that texture[0] points to the noise texture file and texture[1] points to the gradiant texture.
Also make sure the "shader" parameter is pointing to the new ShaderData definition you have created.
Also note here that you need to create a "CustomMaterial" as opposed to a normal "Material" for TGEA to properly connect the new shader
Connect Torque output to shader inputs
At this point, the material/shader should be able to run in TSE without any errors.
The shader won't look right, but it should still run. Now comes the tricky part; matching up the constants that TSE outputs with whatever data the shader needs. There are a number of issues to be aware of here. Converting the glitter shader covers some of the issues, but there could be many more, and some shaders require significant code on the C++ end, making a simple port not possible. Most of the issues come from whatever transform space the shader is using vs. what TSE is using. Shaders use world space, object space, eye space, etc. for various pieces of data. Most of TSE's data is in object space in order to remain consistent.
TSE generates a standard set of data that is passed to all shaders through a set of
predefined constant slots. The slots are defined in the shaders\shdrConsts.h file.
There are a separate definitions for pixel and vertex shaders. The constants are set in the virtual function Material::setShaderConstants(). This function is overloaded in CustomMaterial, so more constants can be passed in from there.
In addition to constants, TSE has a number of special texture inputs that it will fill automatically rather than having the user specify a file on disk. A complete list is located in CustomMaterial::setStageData. Here's some of the more useful ones:
$lightmap - sets the current lightmap to the assigned texture stage - for interiors
$fog - the fog texture
$cubemap - specifies a cubemap - you can currently only set one cubemap per shader
$backBuff - copy of the backbuffer - useful for refraction
$reflectBuff - reflection buffer
$miscBuff - a general purpose variable for setting specific textures from code
Let's take a look at how the glitter shader needs to be changed to run in TSE. Here's the new vertex shader:
#define IN_HLSL
#include "shdrConsts.h"
float4x4 view_proj_matrix: register(VC_WORLD_PROJ);
float3 eye_pos: register(VC_EYE_POS);
float3 inLightDir : register(VC_LIGHT_DIR1);
struct VS_OUTPUT {
float4 Pos: POSITION;
float3 normal: TEXCOORD0;
float3 eyePos: TEXCOORD1;
float3 pos: TEXCOORD2;
float3 lightDir: TEXCOORD3;
};
VS_OUTPUT main(float4 Pos: POSITION, float3 normal: NORMAL){
VS_OUTPUT Out;
Out.Pos = mul(view_proj_matrix, Pos);
Out.normal = normal;
Out.eyePos = eye_pos;
Out.lightDir = inLightDir;
// Pass position to the fragment shader
Out.pos = Pos;
return Out;
}
Notice the #include "shdrConsts.h" at the top. That's there so we can map defined constants in TSE to variables in HLSL like we do on the next few lines down. Since TSE uses local object space, we don't need the view_matrix anymore, but we do need the eye position.
The light direction is now being passed into the vertex shader (and on to the pixel shader) rather than it being defined as a constant in the pixel shader. This allows TSE to pass in actual scene light information rather than having it be arbitrary like in RenderMonkey.
The eye position, normal, and light direction are all in object space, so we no longer need to multiply them by the view_matrix like the original shader, so we just pass them on to the pixel shader.
Here's the new pixel shader:
sampler Noise: register(s0);
float4 main(float3 normal: TEXCOORD0, float3 eyePos: TEXCOORD1, float3 pos: TEXCOORD2, float3 lightVec :TEXCOORD3) : COLOR {
// set constants
float4 color = {0.564, 0.284, 0.867, 1.0};
float glitterStrength = 0.96;
// convert vars
float3 lightDir = -lightVec;
float3 viewVec = normalize( eyePos - pos );
float noisy = tex3D(Noise, pos * 0.44);
normal = normalize(normal);
// Basic lighting
float diffuse = dot(lightDir, normal);
float specBase = saturate(dot(reflect(-normalize(viewVec), normal), lightDir));
// Extract some random points to glitter.
// This is done by perturbing a grid pattern with some noise and
// with the view-vector to let the glittering change with view.
float3 fp = frac(15.7 * pos + 9 * noisy + 0.1 * viewVec);
fp *= (1 - fp);
float glitter = saturate(1 - 7 * (fp.x + fp.y + fp.z));
float specular = pow(specBase, 24);
// Only glitter around the specular highlight. We use a lower
// exponent for the glitter to let it spread more.
float glittering = glitter * pow(specBase, 1.5);
// Get a little more interesting base color
float4 base = (0.5 * noisy + 0.5) * color;
return base * diffuse + 0.5 * specular + glitterStrength * glittering;
}
The largest changes are at the top of the main() function. The color and glitterStrength variables are now explictly set inside the shader rather than being passed in from the app. They can still be passed in from TSE, but it would require
some coding in CustomMaterial to get it running.
The lightDir now comes from the vertex shader rather than from the app. viewVec is now calculated in the pixel shader from the eyepos and pos variables rather than just passed in from the vertex shader. This is necessary since we aren't using view space transforms anymore.
Some of the other numbers of the shader have been changed like in the tex3D() call to assign a value to "noisy", and the "fp" calculation. This was done to better match the shader to the geometry I was using (the TSE demo blob). There are a lot of "magic numbers" like these in shaders, and you often have to play with them to get them to look right in your app.
That's about it. Keep in mind you probably don't want to use a noise volume texture just for a glitter shader as it takes up about 10 megs of video memory ;) Other RenderMonkey examples will take a little more tinkering to get working right, but hopefully this will be a good start towards figuring it out.



