Hi,

when you are doing per-pixel shading and you use precomputed tangents, you are bending a vertex normal probably like this:

Code:
float3x3 matTangent;

struct VS_IN
{
	//...
	float3 Normal : NORMAL;
	float4 Tangent : TEXCOORD2;
	//...
};

struct VS_OUT
{
	//...
	float3 Normal : TEXCOORD2;
	float3 Tangent : TEXCOORD3;
	float3 Binormal : TEXCOORD4;
	//...
};

VS_OUT VS (VS_IN In)
{
	//...
	Out.Normal = mul(In.Normal.xyz, (float3x3)matWorld);
	Out.Tangent = mul(In.Tangent.xyz, (float3x3)matWorld);
	Out.Binormal = mul(cross(In.Tangent.xyz, In.Normal.xyz) * In.Tangent.w, (float3x3)matWorld);
	//...
}

float4 PS (VS_OUT In): COLOR
{
	//...
	float3 tsBump = (tex2D(smpNormal, In.Texcoord.xy).rgb * 2 - 1);	
	float3 wsBumpNormal = normalize(In.Tangent * tsBump.r + In.Binormal * tsBump.g + In.Normal * tsBump.b);
	//...
}



This is fast, easy and the classic approach, but requires tangent precomputation. If this is not feasible (too many texcoords used or for whatever reason it is not desired), you can compute the tangent frame on the fly in the pixel shader just with the vertex normal, the view direction to the pixel's surface position and the texcoord, like this:

Code:
struct VS_IN
{
	//...
	float3 Normal : NORMAL;
	//...
};

struct VS_OUT
{
	//...
	float3 Normal : TEXCOORD2;
	float3 ViewDir : TEXCOORD3;
	//...
};

VS_OUT VS (VS_IN In)
{
	//...
	Out.Normal = mul(In.Normal.xyz, (float3x3)matWorld);
	Out.ViewDir = vecViewPos - mul(In.Pos.xyz, (float3x3)matWorld);
	//...
}

// Calculates a cotangent frame without precomputed tangents by Christian Schüler
// ported from GLSL to HLSL; see: http://www.thetenthplanet.de/archives/1180
float3x3 calcWsCotangentFrame (float3 wsNormal, float3 wsInvViewDir, float2 tsCoord)
{
    // get edge vectors of the pixel triangle
    float3 dp1 = ddx(wsInvViewDir);
    float3 dp2 = ddy(wsInvViewDir);
    float2 duv1 = ddx(tsCoord);
    float2 duv2 = ddy(tsCoord);
 
    // solve the linear system
    float3 dp2perp = cross(dp2, wsNormal);
    float3 dp1perp = cross(wsNormal, dp1);
    float3 T = dp2perp * duv1.x + dp1perp * duv2.x;
    float3 B = dp2perp * duv1.y + dp1perp * duv2.y;
 
    // construct and return a scale-invariant cotangent frame
    float invmax = rsqrt(max(dot(T,T), dot(B,B)));
    return float3x3(T * invmax, B * invmax, wsNormal);
}

float4 PS (VS_OUT In): COLOR
{
	//...
	float3 tsBump = (tex2D(smpNormal, In.Texcoord.xy).rgb * 2 - 1);	
	float3 wsBumpNormal = normalize(mul(tsBump, calcWsCotangentFrame(In.Normal, -In.ViewDir, In.Texcoord)));
	//...
}


Of course this is slower than with precomputed tangents, but only about ~14 instructions and requires shader model 3.0. It is in particular useful for procedural geometry. The code was ported from GLSL and is from Christian Schüler (see his blog).

I hope this is useful for some people out there.

Last edited by HeelX; 04/05/13 13:48.