Tutorial 21 HLSL Per pixel object

September 15th, 2010

No Comments

Per pixel object shader

We’ve seen an object shader already, but now we’re going to do something more interesting. In tutorial 4 (per pixel lighting), we’ve seen some per pixel lighting already. It would be good to try and replicate or even improve this effect. We are going to do this in this tutorial. Before you continue, make sure you at least have an idea of what tangent space and parallax mapping might be :) .


What do we need?

- Since we’re going to do parallax mapping as well as normal mapping, we’re going to have a special texture that will contain the tangentspace normal map in the rgb channels and the parallax map in the alpha channel. This way we can save some texture memory as well as bandwidth.

- We’ll need a fairly advanced shader. It will be explained a bit more in the render section of this tutorial.

- We’re going to have the depth of the bumping variable, so we need the input engine to allow input to happen.

How do we set things up?

- Let’s initialize the variables of the shader first, so it knows where the light is and how much it should bump.

Shader.SetEffectParamVector3("LPos", new TV_3DVECTOR(0, 0, -10));
Shader.SetEffectParamFloat("BumpAmount", fBumpAmount);

The first line sends a Vector3 variable to the shader. It sets the values of the vector3 (which is a float array with a size of 3) to the “LPos” variable in the shader.
The second line sets the “BumpAmount” variable of the shader to fBumpAmount. fBumpAmount will be adjustable by pressing the ’1′ and ’2′ keys.

- We also set the lighting mode to tangent space to be able to use the tangent space coordinates in the shader:

Cube.SetLightingMode(CONST_TV_LIGHTINGMODE.TV_LIGHTING_BUMPMAPPING_TANGENTSPACE, 0, 0);

We set the lightsamount to 0, so the engine won’t worry about lighting the cube.

How do we render this?

- Now for the shader. Let’s see what’s in the vertex shader:

void VertexProgram(in TV3DtoVERTEX IN, out VERTEXtoFRAGMENT OUT)
{       
    OUT.PosOfVertex = mul(IN.PosOfVertex, wViewProj);
    OUT.UV          = IN.UV;

    float3x3 TBN             = float3x3(IN.Tangent, IN.BiNormal, IN.Normal);
    float3x3 WTTS            = mul(TBN, World);

    float3   PosWorld        = mul(IN.PosOfVertex, World);

    OUT.LightVec    = mul(WTTS, (LPos – PosWorld));
    OUT.ViewVec     = mul(WTTS, (ViewPos – PosWorld));
}

First of all, the vertices are transformed to the right place. Then the UV coordinates are passed through.
The next 2 lines will transform the tangent, binormal (which is the perpendicular vector to the tangent and normal vector) and normal vectors to world space.
Then the position of the vertex is multiplied with the world matrix, to know where the vertex is in world space.
Then the light vector is calculated from its position minus the vertex position, multiplied by the (tangent, binormal, normal) matrix. This is to get the light direction in the right space.
Last of all the view vector is calculated, in the same way as the light vector.

- Now the pixel shader:

void FragmentProgram(in VERTEXtoFRAGMENT IN, out FRAGMENTtoSCREEN OUT)
{
    float3 V          = normalize(IN.ViewVec);
    float2 pUV        = (tex2D(NormalSample, IN.UV).a * BumpAmount – (BumpAmount * 0.5)) * V.xy + IN.UV;
    float4 Col        = tex2D(DiffuseSample, pUV);
    float3 N          = 2 * tex2D(NormalSample, pUV).rgb – 1;
    float3 L          = normalize(IN.LightVec);
    float  D          = saturate(dot(L, N));

    OUT.Colour = D * Col;
}

The first line normalizes the view vector to make sure it has a length of 1.
Then the UV offset is calculated from the parallax map (normal map alpha channel), of course multiplied with the bump amount. We will add the UV coordinates as well, so we have the right UV coordinates to sample from now.
Then the color is fetched from the texture.
Then the normal is fetched from its textures’ rgb channel. It also needs to be multiplied by 2 and decreased by 1 to allow negative normals as well.
Now we normalize the light vector to make sure it has a length of 1.
We now calculate the diffuse term, by calculating the dot product of the light and the normal vector. If you don’t know what this is about, have a look at this.
The last line multiplies the light intensity with the color, to make sure the model is correctly lit.

Things you can add/change yourself

- Try writing a phong shader, parallax shader, or whatever you want to try. Try to get things moving, you should follow some tutorials on HLSL or try to replicate some samples from developer.nvidia.com’s shader library.

Reply