# Problem with view-space transforms of Normals in HLSL [Fixed]

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

## Recommended Posts

Hi There,

I'm doing some deferred shading tests atm. I've started with normals expressed in world space when stored in the GBuffer. When I use those during lighting pass with a directional light whose direction is also expressed in world space, everything works fine.

But I think I'd rather have the normals stored in view space instead, so basically in VS of the geometry pass, I tried to mul() the normals with the view matrix from the camera. Having transformed the direction vector of the light with the same view matrix on the CPU, I expected the results to be the same. But they're not, and shading of a very static scene varies with camera orientation, which is bad.

To try and debug : with the world-space approach, I've displayed an output from the normals and they seem fine. With the view-space approach, i've output a mul() of the normal with the inverse of the view matrix, and results are not consistent with the first approach (and again, seem to vary with cam orientation)

What am I doing wrong ? I'm certain this is a really stupid mistake on my part, but I can't corner it atm.

// C++ caller :
//-------------
// Those will be given to UpdateSubResource later (they're all part of the same cbuffer)
// DirectX::XMMatrixTranspose() seem necessary here as DirectX::XMMATRIX seems stored as row-major, not default HLSL
mEyeView = DirectX::XMMatrixTranspose(mCamera->getView());
mEyeInvView = DirectX::XMMatrixTranspose(DirectX::XMMatrixInverse(0, mCamera->getView()));
mEyeViewProj = DirectX::XMMatrixTranspose(mCamera->getViewProj());

// ...

// This will be given to UpdateSubResource later (different cbuffer)
// View-space approach
DirectX::XMStoreFloat4(&vSunDir, DirectX::XMVector4Transform(worldSunDir, mCamera->getView()));
// World-space approach (commented out)
//vSunDir = worldSunDir;

// VS for Geometry pass :
//-----------------------
// ...

float4 transfPos = mul( float4(input.Pos, 1.0f), mObjInWorld );
output.Pos = mul( transfPos, mEyeViewProj );

float4 transfNorm = mul( float4(input.Norm, 0.0f), mObjInWorld );
// View-space approach
output.Norm = mul( transfNorm, mEyeView ).xyz;
// World-space approach (commented out)
//output.Norm = transfNorm.xyz;

// ...

// PS for Geometry pass :
//-----------------------
// ...

output.GBufferB = float4(normalize(input.Norm), 1.0f);

// ...

// PS for Lighting pass with directional light :
//----------------------------------------------
// ...

float4 L = -vSunDir;
float NdL = saturate(dot(N, L));

// Uncomment to output depth and red outlines
// output.Color.rgb = depthColor.rgb;

// Uncomment to output normal as stored
// output.Color.rgb = float3(0.5f, 0.5f, 0.5f) + N.xyz / 2.0f;

// Uncomment to output normal back in worldspace (when stored as viewspace)
// output.Color.rgb = float3(0.5f, 0.5f, 0.5f) + mul(N, mEyeInvView).xyz / 2.0f;

// Uncomment to output base color (diffuse albedo for dielectrics)
// output.Color.rgb = baseColor;

// Uncomment to output lambertian diffuse
output.Color.rgb = baseColor * NdL;

// Uncomment to output world to view to world conversion test
// Note to readers of this thread : This test showed consistently maxed R and B, while G may vary, as should be.
// float4 testN = float4(1.0f, 0.0f, 0.0f, 0.0f);
// float4 testNView = mul(testN, mEyeView);
// float4 testNBack = mul(testNView, mEyeInvView);
// output.Color.rgb = float3(testN.r, testNView.r, testNBack.r);

output.Color.a = 1.0f;

// ...


As a side note, all shaders use same constant buffers definitions (same #include'd file) and SetConstantBuffers on the C++ side is done for PS and VS with same input and only once, so this should rule out a whole range of possible explanations for this issue.

Edited by TiPiou

##### Share on other sites

Well this was stupid indeed, I've just realized (while trying to explain the matter to you, guys) that I forgot to bias and scale the normal components to [0.0f, 1.0f]... as my GBuffers format is UNORM, this was silly... so, my problem is fixed with this modification :

// PS for Geometry pass :
//-----------------------
output.GBufferB = float4(float3(0.5f, 0.5f, 0.5f) + normalize(input.Norm)/2.0f, 1.0f);


and the reciproqual :

// PS for Lighting pass with directional light :
//----------------------------------------------
float4 N = float4(GBufferB.Load(sampleCoords).rgb * 2.0f - float3(1.0f, 1.0f, 1.0f), 0.0f);


I'm still not sure why same mistake was almost unnoticeable in my test case for the world-space approach, but hey...

I hope at least somebody reading this will avoid this pitfall other than that, this topic may be closed...

Edited by TiPiou

1. 1
2. 2
3. 3
4. 4
Rutin
18
5. 5

• 13
• 14
• 9
• 9
• 9
• ### Forum Statistics

• Total Topics
632927
• Total Posts
3009249
• ### Who's Online (See full list)

There are no registered users currently online

×