#### Archived

This topic is now archived and is closed to further replies.

# Tangent space basis creation

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

## Recommended Posts

Hi all, I am doing my first steps in per pixel lighting at the moment. I am using DirectX 8 / VS / PS for that. I read an interesting paper at MSDN which pretty much explained everything I need to get started per pixel lighting techniques. For per pixel lighting it is required to have a tangent space basis for a triangle. As far as I understand it that is a vector space basis where the w component is the vertex normal. My problem is to get the other two vertices, namely the tangents. If I had just one triangle connected to a vertex that would be pretty obvious as I just take u = p1-p0 and v = u cross w. However there are usually multiple triangles connected to a vertex and that is my problem. How do I calculate the tangent space basis for that? Maybe anyone could help me with that? Thanks in advance, Alex

##### Share on other sites
A brute force approach is of course to calculate tangent vectors for EACH triangle that is attached to a certain vertex and then mediate them.

##### Share on other sites
Here''s a topic on the subject from opengl.org:
http://www.opengl.org/discussion_boards/cgi_directory/ultimatebb.cgi?ubb=get_topic;f=3;t=011349
It also has sample code.

##### Share on other sites
okay. i got the basic idea. however for the moment I am using that funny NVidia class MeshMender until I got that right. Simply eliminates one source of bugs until I got things working. Found it just an hour ago and it looks pretty useful. Does normal generation for you as well.
However I do now have strange results from my shaders. I am using Cg.

For keeping things short I only post the parts that are important to lighting...

VS_OUT main(VS_IN IN){	VS_OUT OUT;	// Transform normal & tangent from object to world space	float3 N = mul(TRANSFORM_WORLD_3x3, IN.normal);	float3 T = mul(TRANSFORM_WORLD_3x3, IN.tangent);		// Generate the binormal	float3 B = cross(IN.normal, IN.tangent);		// Store the normal, tangent and binormal	OUT.normal = N;	OUT.tangent = T;	OUT.binormal = B;		// Generate a 3x3 matrix transformation matrix for tangent space	float3x3 TRANSFORM_TANGENT_3x3;	TRANSFORM_TANGENT_3x3[0] = N;	TRANSFORM_TANGENT_3x3[1] = T;	TRANSFORM_TANGENT_3x3[2] = B;		float3 L = normalize(LIGHT0.pos.xyz - world_pos.xyz);		// Transform the light vector to tangent space	L = mul(TRANSFORM_TANGENT_3x3, L);		// Store half angle light vector (this just looks wrong?)	OUT.light0.xyz = L * 0.5 + 0.5;		// Done	return OUT;}

And the pixel shader:
PS_OUT main(PS_IN IN){	PS_OUT OUT;	// For simplicity, no normal map at the moment	float3 normal;	normal.x = 0;	normal.y = 1;	normal.z = 0;		float light0 = saturate(dot(normal, IN.light0));	OUT.color.rgb = DIFFUSE_COLOR * light0;			return OUT;}

And it looks plain wrong. Any ideas?

Thanks,
Alex

##### Share on other sites
I think the problem is "OUT.light0.xyz" & "IN.light0.xyz"

Is this a texture coordinate or a diffuse/specular color iterator?

If it''s a color, you need the *0.5f + 0.5 to compress the -1,1 range into 0,1 b/c colors can''t be negative.

If you do this, then you need to expand it again with * 2.0f - 1.0f in the pixel shader from 0,1 to -1,1.

If the light vector is a texture coordinate, those can be negative without a problem, so just take out the *0.5 + 0.5 from the vertex shader, and don''t add the *2 -1 to the pixel shader and it should work.

Glad you are getting use out of the MeshMender...

##### Share on other sites
Thanks for your reply. MeshMender is just like a tool has to be: small, non-intrusive and easy to use. Great thing really.

Yeah, from what I understood that whole /2+0.5 story looked somewhat wrong.
For the moment I'll be perfectly happy with per pixel diffuse lighting as that's a good basis to start from.

So what I want to do is simply pass the tangent space normal along with the light-pos vector to the pixel shader and do my dot3 there. Sounds simple enough but I am having heavy difficulties getting that to work.

I made a screentshot which is to be found at:
http://www.dajudge.com/shot1.jpg

It's a sphere with normals which is lit by a point light source to it's left. So what you can see is that the very left part of the sphere is already lit correctly but that's only the case for that specific rotation...

My current shader code looks like this:

/////////////////////////////////////////////////////// >> OUTPUT SEMANTICS <</////////////////////////////////////////////////////struct VS_OUT{	float4 pos	: POSITION;	float3 tex0	: COLOR0;	float3 normal	: TEXCOORD1;	float3 tangent	: TEXCOORD2;	float3 binormal	: TEXCOORD3;	float3 light0	: TEXCOORD0;};/////////////////////////////////////////////////////// >> CODE SNIPPET <</////////////////////////////////////////////////////	///////////////////////////////////////////////	// LIGHTING PREPARATION	///////////////////////////////////////////////	// Transform normal & tangent from object to world space	float3 N = mul(TRANSFORM_WORLD_3x3, IN.normal);	float3 T = mul(TRANSFORM_WORLD_3x3, IN.tangent);		// Generate the binormal	float3 B = cross(IN.normal, IN.tangent);		// Generate a 3x3 matrix transformation matrix for tangent space	float3x3 TRANSFORM_TANGENT_3x3;	TRANSFORM_TANGENT_3x3[0] = T;	TRANSFORM_TANGENT_3x3[1] = B;	TRANSFORM_TANGENT_3x3[2] = N;		///////////////////////////////////////////////	// PER LIGHT CALCULATIONS	///////////////////////////////////////////////	// Light 0	float3 L = normalize(LIGHT0.pos.xyz - world_pos.xyz);		// Transform the light vector to tangent space	L = mul(TRANSFORM_TANGENT_3x3, L);		// Store half angle light vector	OUT.light0.xyz = L;	OUT.normal = mul(TRANSFORM_TANGENT_3x3,N);

and the pixel shader:

PS_OUT main(PS_IN IN){	PS_OUT OUT;		float light0 = dot(IN.normal.xyz, IN.light0);	OUT.color.rgb = CAR_COLOR.rgb * light0;	OUT.color.a = 1;			return OUT;}

A yeah, does anyone know how to get the Cg compiler to produce DX8 conform pixel shader asm? Most of times it messes up some swizzling (say: it's not wrong, DX just doesn't assemble it saying "invalid swizzle") and I have to modify the ASM code by hand (which kind of messes up the whole concept of using Cg in the first place).

[edited by - dajudge on April 16, 2004 1:49:14 PM]

##### Share on other sites
The tangent vector is designed to move from object->tangent space. You are using it to move from object->world space.

Instead, take your light vector, calculated in world space, and then move through the tangent space matrix, and pass to the pixel shader.

##### Share on other sites
Okay, let's get this somewhat simpler. For the beginning I'd be happy if I can move the final dot product for light calcs from the vertex shader to the pixel shader (I know it doesn't make much sense, but let's just do small steps for starters...).

So for testing I make a vertex shader that outputs the per-vertex calculated dot product of the light vector and the normal, plus (for doing the same thing in the pixel shader) the normal and the light vector.
All lighting calculations are done in world space (which should be no problem at all since we're doing only diffuse shading without texture lookups here).

///////////////////////////////////////////////// Output semantics///////////////////////////////////////////////struct VS_OUT{	float4 pos	: POSITION;	float3 color	: COLOR0;	float3 normal	: TEXCOORD1;	float3 light0	: TEXCOORD0;};///////////////////////////////////////////////// Code snippet:///////////////////////////////////////////////	///////////////////////////////////////////////	// PER LIGHT CALCULATIONS	///////////////////////////////////////////////	// Light 0 (LIGHT0.pos.xyz is already world space)	float3 L = normalize(LIGHT0.pos.xyz - world_pos.xyz);		// Store L,N (both world space) and sat(L dot N)	OUT.light0 = L;	OUT.normal = N;	OUT.color = max(0, dot(L,N));

and now the pixel shader (this time asm) in both versions, one of them simple sending the vertex-shader calculated color to the screen, the other one doing the dot-product on it's own:

//////// VERSION ONE: ///////////////// Calc sat(N dot L) and output thatps.1.1def c4, 0.000000, 0.000000, 0.000000, 0.000000texcoord t0		// light0texcoord t1		// normalmov r1, c4		// set r1 to 0dp3_sat r1.xyz, t0, t1	// r1.xyz = max(0,dot(n,l))mov r0, r1		// output

//////// VERSION TWO: ///////////////// Simply output color from VSps.1.1mov r0, v0		// output

However, switching the two pixel shader gives different results: the vertex shader dot-product produces the correct result while the one calculated in the pixel shader does not. Can someone please explain that to me?

To me it looks like the texture coordinates are clamped after being pushed out the vertex shader. But why would that happen? Can't texture coordinates be negative?

Alex

[edited by - dajudge on April 17, 2004 1:28:28 AM]

##### Share on other sites
When using texcoord, the value is clamped between 0 and 1.

In your vertex shader, mad with 0.5, 0.5 to convert to 0 to 1 range.
In your pixel shader use _bx2 to convert to -1 to 1 range.
If it makes you feel better, bitterly swear under your breath at this design decision.
Again, if it helps, keep complaining when you realize ps.1.1 constant registers have the same boneheaded limitation while it makes even less sense in the constant regs.

It will be a glorious day when everyone has ps.2.0 capable hardware and we can forget ps.1.1 exists.

##### Share on other sites
Yeah, ps1.x is not exactly what one would really call "programmable". Too many limitations.

Your tip using the [0;1] range worked perfectly! I''m pretty close to per-pixel diffuse+specular lighting... Thanks!

Now, the only question I have left is: How to get Cg to compile working dx8ps shaders? I have the latest version which was shortly released on the NVidia site. But most of time it uses some swizzling the DirectX runtime doesn''t like. Here''s an example:

...mul r0.rgb, c3, t0+ mov r0.a, c4.b

D3DXAssembleShader() fails with the error message "Invalid swizzle..." at this line. What concerns me is that the Cg compiler is actually meant to be used for that purpose. And now it generates code that is not supported by the DirectX runtime? Strange thing...

Any ideas? Thanks in advance,

Alex

1. 1
Rutin
42
2. 2
3. 3
4. 4
5. 5

• 18
• 20
• 14
• 14
• 9
• ### Forum Statistics

• Total Topics
633370
• Total Posts
3011542
• ### Who's Online (See full list)

There are no registered users currently online

×