Sign in to follow this  

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

This topic is 1118 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

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 :
//----------------------------------------------
	// ...

	float3 baseColor = GBufferA.Load(sampleCoords).rgb;
	float4 N = float4(GBufferB.Load(sampleCoords).rgb, 0.0f);
	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 this post


Link to post
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 tongue.png other than that, this topic may be closed...

Edited by TiPiou

Share this post


Link to post
Share on other sites

This topic is 1118 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this