Jump to content
  • Advertisement
Sign in to follow this  
arc4nis

Need help with HLSL bump mapping

This topic is 3788 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have successfully implemented object space bump mapping in my game... however now I'm trying to implement tangent space bump mapping. The idea is to create a transformation matrix (TrasMatrix), that will trasform the normal from tangent space to object space. This matrix is created in the vertex shader and passed to the pixel shader. In the pixel shader the normal in tangent space is trasformed to object space multiplying it by the TrasMatrix. However the result is strange, everything looks fine, even when the object is rotating, however the light seems to be coming from the wrong way, when I try to change the direction of the light vector, the result is always a few degrees or more wrong. This is the snippet of the code that matters:
struct VS_INPUT1
{
    float3 position	: POSITION0;
    float2 texture0 : TEXCOORD0;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
    float3 binormal : BINORMAL;
};

struct VS_OUTPUT1
{
    float4 position  : POSITION0;
    float2 texture0  : TEXCOORD0;
    float3x3 TransMatrix  : TEXCOORD1;
};

//-----------------------------------------------------------------------------------------------
// Vertex Shader 1
//-----------------------------------------------------------------------------------------------

VS_OUTPUT1 vs1( VS_INPUT1 IN )
{
    VS_OUTPUT1 OUT;

    OUT.position = mul( worldViewProj, float4(IN.position, 1) );

    OUT.texture0 = IN.texture0;
	
    // Rotate the normal, binormal and tangent to match the object rotation
    IN.normal = normalize(mul(objectRotation, IN.normal));
    IN.binormal = normalize(mul(objectRotation, IN.binormal));
    IN.tangent = normalize(mul(objectRotation, IN.tangent));
	
    // Create the TransMatrix to trasform the normal from tangent space to object space
    OUT.TransMatrix = float3x3(IN.normal, IN.tangent, IN.binormal);
    
    return OUT;
}

//-----------------------------------------------------------------------------------------------
// Pixel Shader 1
//-----------------------------------------------------------------------------------------------

PS_OUTPUT1 ps1( VS_OUTPUT1 IN )
{
    PS_OUTPUT1 OUT;
    
    OUT.color = float4(0.0f, 0.0f, 0.0f, 1.0f); // Reset the Out.color
        
    // Get the pixel normal
    float3 normal = 2.0f * tex2D(Sampler1, IN.texture0).rgb - 1.0f;
    normal = normalize(normal);
    // For the test I have made all the normals point in the same direction
    normal.x = 0.0f; normal.y = 0.0f; normal.z = 1.0f;
	
    // Set the direction of the directional light	
    float3 light; light.x = lightDir1.x; light.y = lightDir1.y; light.z = lightDir1.z;
    // Trasform the normal in object space
    normal = mul(IN.TransMatrix, normal);
    // Set the diffuse color
    float diffuse = dot(normal, light);
    
    // For the test I am just outputting the diffuse color
    OUT.color.r = diffuse; OUT.color.g = diffuse; OUT.color.b = diffuse;

    return OUT;
}

What am I doing wrong, should I be trasforming the light vector to tangent space instead than the normal to object space. I prefer this way becouse it will be easier to implement. Any help is apprecciated...

Share this post


Link to post
Share on other sites
Advertisement
I'm in a hurry so I don't really have time to explain the code. But if you can pick this apart. It is tangent space, with parallax.


#include "c:\pixielib\default.shadeh"

float4x4 ViewProj;
float4x4 World;
float3 Eye;
float3 Point0;
float4 PointC0;
float3 Point1;
float4 PointC1;
float Range0 = 50;
float Range1 = 50;

texture Diffuse;
texture Normal;
texture Specular;
texture SubColour;

//-----------------------------------
// Samplers.
//-----------------

sampler2D DiffuseSampler = sampler_state
{
Texture = (Diffuse);
MIPFILTER = LINEAR;
MAGFILTER = LINEAR;
MINFILTER = LINEAR;
};

sampler2D NormSampler = sampler_state
{
Texture = (Normal);
MIPFILTER = LINEAR;
MAGFILTER = LINEAR;
MINFILTER = LINEAR;
};

sampler2D SpecSampler = sampler_state
{
Texture = (Specular);
MIPFILTER = LINEAR;
MAGFILTER = LINEAR;
MINFILTER = LINEAR;
};

sampler2D SubSampler = sampler_state
{
Texture = (SubColour);
MIPFILTER = LINEAR;
MAGFILTER = LINEAR;
MINFILTER = LINEAR;
};


//-----------------------------------
// Structures.
//-----------------

struct a2v //App->Vertex.
{
float4 Position : POSITION0;
float3 Normal : NORMAL;
float3 Tang : TANGENT;
float3 BiTang : BINORMAL;
float2 UV : TEXCOORD0;
};

struct v2f //Vertex->Frag.
{
float4 Position : POSITION0;
float3 LV0 : TEXCOORD0;
float3 LV1 : TEXCOORD1;
float2 UV : TEXCOORD2;
float3 VV : TEXCOORD3;
float Att0 : TEXCOORD4;
float Att1 : TEXCOORD5;
};

struct f2s //Frag->Screen.
{
float4 Colour : COLOR0;
};

//-----------------------------------
// Shaders.
//-----------------

void vp(in a2v IN, out v2f OUT) //Vertex program.
{
float4 PosWorld = mul(IN.Position, World);

float3x3 TBNMatrix = float3x3(IN.Tang, IN.BiTang, IN.Normal);
float3x3 WTTS = mul(TBNMatrix, (float3x3)World);

OUT.Att0 = (Point0.xyz - PosWorld);
OUT.Att1 = (Point1.xyz - PosWorld);

OUT.Position = mul(IN.Position, ViewProj);
OUT.UV = IN.UV;

OUT.LV0 = normalize(mul(WTTS, normalize(Point0 - PosWorld)));
OUT.LV1 = normalize(mul(WTTS, normalize(Point1 - PosWorld)));

OUT.VV = normalize(mul(WTTS, Eye - PosWorld));
}

void fp(in v2f IN, out f2s OUT) //Fragment program.
{
float4 Col = tex2D(DiffuseSampler, IN.UV);
float2 Para = (tex2D(NormSampler, IN.UV).a * 0.069 - 0.069 * 0.8) * IN.VV + IN.UV;
float3 Norm = 2 * tex2D(NormSampler, Para) - 1;
float3 SubC = tex2D(SubSampler, Para);
float Spec = tex2D(SpecSampler, Para).r;
float A0 = tex2D(SpecSampler, Para).a;
float A = (1 - A0);

float D0 = (saturate(dot(Norm, IN.LV0)) * clamp(1 - length(IN.Att0) / Range0, 0, 1)) - A;
float S0 = LyonSpec(IN.VV, IN.LV0, Norm);// * (Spec / 2);

float4 DS0;
float4 SS0;
SSS(Norm, IN.LV0, SubC, DS0, SS0);
float4 SF0 = (DS0 + SS0);

OUT.Colour = (Col + SF0 + S0) * (D0 * PointC0);
}

technique render
{
pass pass0
{
vertexshader = compile vs_1_1 vp();
pixelshader = compile ps_2_0 fp();
}
}




Ignore the specular and sss function calls. They're not relevant to what you want.

Share this post


Link to post
Share on other sites
Quote:
Original post by arc4nis

What am I doing wrong, should I be trasforming the light vector to tangent space instead than the normal to object space.



That is the "traditional" way to perform normal-mapping, as it keeps the matrix multiplication in the vertex shader. Of course there can be advantages to the way you're doing it, as it can allow you to keep all of your calculations in world space (when you say "object space" you're talking about "world space", object space is the coordinate space your vertices are in before you transform them in any way).

Anyway I think your problem may be that you're not normalizing the vector components of your TBN matrix in the pixel shader. Linerally interpolating between two normalized vectors doesn't result in a normalized vector. Here's rough sketch in MS Paint that should show you why:


Share this post


Link to post
Share on other sites
MJP >> I have tryed to normalize the vectors, that was one of the problems, however the problem is still somewhere else...

AriusMyst >> Thanks for the code, but it is too complex without explanation, of what are you passing from the application to the global variables... so it is hard to modify it for bump mapping without parallx.

Share this post


Link to post
Share on other sites
Not shameless. I completely forgot about this thread, sorry for that. If you give me 30 mins or so I'll write out a simple normal mapper with commenting and post it for you :). Sorry again, I didn't mean to forget, just with Easter etc...

Share this post


Link to post
Share on other sites
Ok, here we go. I should say firstly. I'm not 100% sure that this is the best way to do things. I'm entirely self-taught in HLSL, in a kind of "trial and error" way. So I may very well be wrong in places. But in any case, I hope this helps some :).


//------------------------------------------------------------------------------
// Normal Mapping Frag. ( t-space )
// Supports one point light.
//-----------------------------------

float4x4 ViewProj;
float4x4 World;
float3 Eye; //Camera position.
float3 Point; //Point light position.
float4 PointC; //Point light colour.
float Range = 50; //Range of point light.

texture Diffuse;
texture Normal;

//-----------------------------------
// Samplers.
//-----------------

sampler2D DiffuseSampler = sampler_state
{
Texture = (Diffuse);
MIPFILTER = LINEAR;
MAGFILTER = LINEAR;
MINFILTER = LINEAR;
};

sampler2D NormSampler = sampler_state
{
Texture = (Normal);
MIPFILTER = LINEAR;
MAGFILTER = LINEAR;
MINFILTER = LINEAR;
};


//-----------------------------------
// Structures.
//-----------------

struct a2v //App->Vertex.
{
float4 Position : POSITION0;
float3 Normal : NORMAL;
float3 Tang : TANGENT;
float3 BiTang : BINORMAL;
float2 UV : TEXCOORD0;
};

struct v2f //Vertex->Frag.
{
float4 Position : POSITION0;
float3 LV : TEXCOORD0;
float2 UV : TEXCOORD2;
float Att : TEXCOORD4;
};

struct f2s //Frag->Screen.
{
float4 Colour : COLOR0;
};

//-----------------------------------
// Shaders.
//-----------------

void vp(in a2v IN, out v2f OUT) //Vertex program.
{
float4 PosWorld = mul(IN.Position, World);

//Calc TBN( Tang, BiTang, Norm ).
float3x3 TBNMatrix = float3x3(IN.Tang, IN.BiTang, IN.Normal);
float3x3 WTTS = mul(TBNMatrix, (float3x3)World);

//Start Attenuation.
OUT.Att = (Point.xyz - PosWorld);

//Standard junk.
OUT.Position = mul(IN.Position, ViewProj);
OUT.UV = IN.UV;

//Move Light-Vector in to tangent space.
OUT.LV = normalize(mul(WTTS, normalize(Point - PosWorld)));
}

void fp(in v2f IN, out f2s OUT) //Fragment program.
{
//Sample colour/diffuse.
float4 Col = tex2D(DiffuseSampler, IN.UV);

//Sample tangent-spaced normal map.
float3 Norm = 2 * tex2D(NormSampler, IN.UV) - 1;

//Calculate light scaled by attenuation - this takes the point lights range in to consideration.
//Is better to do atten per-pixel than per-vert.
float D0 = (saturate(dot(Norm, IN.LV)) * clamp(1 - length(IN.Att) / Range, 0, 1));

//Dump results.
OUT.Colour = D0 * Col * PointC;
}

technique render
{
pass pass0
{
vertexshader = compile vs_1_1 vp();
pixelshader = compile ps_2_0 fp();
}
}


//-------------------- EOF -----------------------------------------




Anyways, I hope this helps some. Good luck :).

Edit: I wasn't thinking before. You need to calculate the tangents before hand, for the mesh I mean. If you're not doing this already( I assume you are? ) just drop a post in here and I'll show you how I'm doing it. Ok.

[Edited by - AriusMyst on March 21, 2008 7:10:15 PM]

Share this post


Link to post
Share on other sites
That is great Arius... seems simple and clear... too bad I'm on vacation, and cant test it right now... in a few days I'm back and I'll post the progress...

As for the normal maps, I'm creating them with the nvidia photoshop plugin.

Again thanks!

Share this post


Link to post
Share on other sites
So... I adapted the code for my engine (I'm using just one directional light), now the position of the light is correctly calculated when I'm rotating the object, however there is something strange going on, as I rotate the object, at times the light seems to go brighter to the point that the object is half black/white, and other times it goes back to normal...

this is what I'm doing:

Vertex Shader:
-the objectRotation matrix is given by D3DXMatrixRotationYawPitchRoll
-the lightDirection is a float3 vector normalized that is the direction of the light


//-----------------------------------------------------------------------------------------------
// Vertex Shader 1
//-----------------------------------------------------------------------------------------------

VS_OUTPUT1 vs1( VS_INPUT1 IN )
{
VS_OUTPUT1 OUT;

float3x3 TBNMatrix = float3x3(IN.tangent, IN.binormal, IN.normal);
float3x3 WTTS = mul(TBNMatrix, (float3x3)objectRotation);

OUT.position = mul( worldViewProj, float4(IN.position, 1) );
OUT.texture0 = IN.texture0;

OUT.light = normalize(mul(WTTS, lightDirection));

return OUT;
}



Pixel Shader:


//-----------------------------------------------------------------------------------------------
// Pixel Shader 1
//-----------------------------------------------------------------------------------------------

PS_OUTPUT1 ps1( VS_OUTPUT1 IN )
{
PS_OUTPUT1 OUT;

// This is just a temporary replacement of the normal map
float3 normal = float3( 0.0f, 0.0f, 1.0f );

// Just output the light for test
OUT.color = saturate(dot(normal, IN.light));

return OUT;
}



I'm really lost, I dont know what I'm doing wrong, the code seems simple and clear. Any ideas ?

Share this post


Link to post
Share on other sites
I'm not 100% sure that is a viable replacement for the normal map. In any case, the colour is supposed to be in a -1, 1 range.

Try maybe:


float3 normal = 2 * float3( 0.0f, 0.0f, 1.0f ) - 1;

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!