Sign in to follow this  
nbertoa

DX12 [D3D12] SSAO Demo

Recommended Posts

The first video shows the ambient accessibility buffer.

The second video shows the scene where only ambient light is used (ambient factor * diffuse albedo * ambient accessibility)

The third video shows the scene with additional light from irradiance diffuse & specular environment cube maps.

There is no extra light in any scene (i.e. there is no directional light mounted to the camera)

Share this post


Link to post
Share on other sites

I agree with JoeJ, something seems a bit strange with your results. It seems to be view dependent, with the accessibility being higher in places where N dot V is higher. It particularly stands out on the floor, which is darkened despite having any obstructions.

 

Either way I'm not trying to pick on your work, just trying to help in case there's a bug. :)

Edited by MJP

Share this post


Link to post
Share on other sites
@MJP and @Joej, the intention of my posts is to receive feedback about my work (to learn and improve my knowledge). Do not worry! I receive all critics as constructive feedback.

I use normal vector buffer, where normals are stored in view space. Could that affect final result?

Share this post


Link to post
Share on other sites

I guess all AO samples return the result of being visible. In the video the camera rotates around the scene but there never shows up a typical artefact caused by missing SS information.

So it seems the sampling has no effect and the N dot V dependent look might become from elsewhere.

Or depth sampling does not work for some reason? Or all samples at zero distance? Not sure, but i wonder what causes banding if the bug would be something like that.

Share this post


Link to post
Share on other sites

@JoeJ

 

I modified occlusion radius and a lot of problems appeared. I recorded a video that shows them.

 

https://youtu.be/Dv09HuPF-Ps

 

Clearly, it shows that there is view dependent problem (when I move the camera, ambient accessibility changes a lot)

 

This the code that computes ambient occlusion:

float SSAOVersion1(
	const float3 sampleKernel,
	const float3x3 sampleKernelMatrix, 
	const float4x4 projMatrix,
	const float occlusionRadius,
	const float3 fragPosV,
	Texture2D<float> depthTex)
{
	// Get sample position
	float3 sampleV = mul(sampleKernel, sampleKernelMatrix);
	sampleV = sampleV * occlusionRadius + fragPosV;

	// Project sample position
	float4 sampleH = float4(sampleV, 1.0f);
	sampleH = mul(sampleH, projMatrix);
	sampleH.xy /= sampleH.w;
	sampleH.xy = sampleH.xy * 0.5 + 0.5;

	// Get sample depth
	float sampleDepthV = depthTex.Load(float3(sampleH.xy, 0));
	sampleDepthV = NdcDepthToViewDepth(sampleDepthV, projMatrix);

	// Range check and ambient occlusion factor
	const float rangeCheck = abs(fragPosV.z - sampleDepthV) < occlusionRadius ? 1.0 : 0.0;
	return (sampleDepthV <= sampleV.z ? 1.0 : 0.0) * rangeCheck;
}

where sampleKernelMatrix is:

	// Construct a change-of-basis matrix to reorient our sample kernel
	// along the origin's normal.
	const float3 noiseVec = NoiseTexture.Sample(TexSampler, NOISE_SCALE * input.mTexCoordO).xyz * 2.0f - 1.0f ;
	const float3 tangentV = normalize(noiseVec - normalV * dot(noiseVec, normalV));
	const float3 bitangentV = cross(normalV, tangentV);
	const float3x3 sampleKernelMatrix = float3x3(tangentV, bitangentV, normalV);

This is the method that generates sample kernel

	// Sample kernel for ambient occlusion. The requirements are that:
	// - Sample positions fall within the unit hemisphere
	// - Sample positions are more densely clustered towards the origin.
	//   This effectively attenuates the occlusion contribution
	//   according to distance from the kernel centre (samples closer
	//   to a point occlude it more than samples further away).
	void GenerateSampleKernel(const std::uint32_t numSamples, std::vector<XMFLOAT3>& kernels) {
		ASSERT(numSamples > 0U);

		kernels.resize(numSamples);
		XMFLOAT3* data(kernels.data());
		XMVECTOR vec;
		const float numSamplesF = static_cast<float>(numSamples);
		for (std::uint32_t i = 0U; i < numSamples; ++i) {
			XMFLOAT3& elem = data[i];

			// Create sample points on the surface of a hemisphere
			// oriented along the z axis
			const float x = MathUtils::RandF(-1.0f, 1.0f);
			const float y = MathUtils::RandF(-1.0f, 1.0f);
			const float z = MathUtils::RandF(-1.0f, 0.0f);
			elem = XMFLOAT3(x, y, z);
			vec = XMLoadFloat3(&elem);
			vec = XMVector3Normalize(vec);

			// Accelerating interpolation function to falloff 
			// from the distance from the origin.
			float scale = i / numSamplesF;
			scale = MathUtils::Lerp(0.1f, 1.0f, scale * scale);
			vec = XMVectorScale(vec, scale);
			XMStoreFloat3(&elem, vec);
		}
	}

and this is the method that generates noise vectors

	// Generate a set of random values used to rotate the sample kernel,
	// which will effectively increase the sample count and minimize 
	// the 'banding' artifacts.
	void GenerateNoise(const std::uint32_t numSamples, std::vector<XMFLOAT4>& noises) {
		ASSERT(numSamples > 0U);

		noises.resize(numSamples);
		XMFLOAT4* data(noises.data());
		XMVECTOR vec;
		for (std::uint32_t i = 0U; i < numSamples; ++i) {
			XMFLOAT4& elem = data[i];

			// Create sample points on the surface of a hemisphere
			// oriented along the z axis
			const float x = MathUtils::RandF(-1.0f, 1.0f);
			const float y = MathUtils::RandF(-1.0f, 1.0f);
			const float z = 0.0f;			
			elem = XMFLOAT4(x, y, z, 0.0f);
			vec = XMLoadFloat4(&elem);
			vec = XMVector4Normalize(vec);
			XMStoreFloat4(&elem, vec);
			XMFLOAT3 mappedVec = MathUtils::MapF1(XMFLOAT3(elem.x, elem.y, elem.z));
			elem.x = mappedVec.x;
			elem.y = mappedVec.y;
			elem.z = mappedVec.z;
		}
	}
Edited by nicolas.bertoa

Share this post


Link to post
Share on other sites

Finally, I fixed the ambient occlusion algorithm. I attached some screenshots with the results.

 

The problem was here

// Project sample position
float4 sampleH = float4(sampleV, 1.0f);
sampleH = mul(sampleH, projMatrix);
sampleH.xy /= sampleH.w;

// Get sample depth
float sampleDepthV = depthTex.Load(float3(sampleH.xy, 0));
sampleDepthV = NdcDepthToViewDepth(sampleDepthV, projMatrix);

because I was using Load() with sampleH that is in NDC space, not in viewport space.

So I did the following

// Convert sample position to NDC and sample depth at that position in depth buffer.
float4 samplePosH = mul(samplePosV, gFrameCBuffer.mP);
samplePosH.xy /= samplePosH.w;
	
const int2 sampleViewportSpace = NdcToViewportCoordinates(samplePosH.xy, 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT);
const float sampleDepthNDC = Depth.Load(int3(sampleViewportSpace, 0));

Share this post


Link to post
Share on other sites

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  

  • Forum Statistics

    • Total Topics
      628290
    • Total Posts
      2981858
  • Similar Content

    • By lubbe75
      I am looking for some example projects and tutorials using sharpDX, in particular DX12 examples using sharpDX. I have only found a few. Among them the porting of Microsoft's D3D12 Hello World examples (https://github.com/RobyDX/SharpDX_D3D12HelloWorld), and Johan Falk's tutorials (http://www.johanfalk.eu/).
      For instance, I would like to see an example how to use multisampling, and debugging using sharpDX DX12.
      Let me know if you have any useful examples.
      Thanks!
    • By lubbe75
      I'm writing a 3D engine using SharpDX and DX12. It takes a handle to a System.Windows.Forms.Control for drawing onto. This handle is used when creating the swapchain (it's set as the OutputHandle in the SwapChainDescription). 
      After rendering I want to give up this control to another renderer (for instance a GDI renderer), so I dispose various objects, among them the swapchain. However, no other renderer seem to be able to draw on this control after my DX12 renderer has used it. I see no exceptions or strange behaviour when debugging the other renderers trying to draw, except that nothing gets drawn to the area. If I then switch back to my DX12 renderer it can still draw to the control, but no other renderers seem to be able to. If I don't use my DX12 renderer, then I am able to switch between other renderers with no problem. My DX12 renderer is clearly messing up something in the control somehow, but what could I be doing wrong with just SharpDX calls? I read a tip about not disposing when in fullscreen mode, but I don't use fullscreen so it can't be that.
      Anyway, my question is, how do I properly release this handle to my control so that others can draw to it later? Disposing things doesn't seem to be enough.
    • By Tubby94
      I'm currently learning how to store multiple objects in a single vertex buffer for efficiency reasons. So far I have a cube and pyramid rendered using ID3D12GraphicsCommandList::DrawIndexedInstanced; but when the screen is drawn, I can't see the pyramid because it is drawn inside the cube. I'm told to "Use the world transformation matrix so that the box and pyramid are disjoint in world space".
       
      Can anyone give insight on how this is accomplished? 
       
           First I init the verts in Local Space
      std::array<VPosData, 13> vertices =     {         //Cube         VPosData({ XMFLOAT3(-1.0f, -1.0f, -1.0f) }),         VPosData({ XMFLOAT3(-1.0f, +1.0f, -1.0f) }),         VPosData({ XMFLOAT3(+1.0f, +1.0f, -1.0f) }),         VPosData({ XMFLOAT3(+1.0f, -1.0f, -1.0f) }),         VPosData({ XMFLOAT3(-1.0f, -1.0f, +1.0f) }),         VPosData({ XMFLOAT3(-1.0f, +1.0f, +1.0f) }),         VPosData({ XMFLOAT3(+1.0f, +1.0f, +1.0f) }),         VPosData({ XMFLOAT3(+1.0f, -1.0f, +1.0f) }),         //Pyramid         VPosData({ XMFLOAT3(-1.0f, -1.0f, -1.0f) }),         VPosData({ XMFLOAT3(-1.0f, -1.0f, +1.0f) }),         VPosData({ XMFLOAT3(+1.0f, -1.0f, -1.0f) }),         VPosData({ XMFLOAT3(+1.0f, -1.0f, +1.0f) }),         VPosData({ XMFLOAT3(0.0f,  +1.0f, 0.0f) }) } Then  data is stored into a container so sub meshes can be drawn individually
      SubmeshGeometry submesh; submesh.IndexCount = (UINT)indices.size(); submesh.StartIndexLocation = 0; submesh.BaseVertexLocation = 0; SubmeshGeometry pyramid; pyramid.IndexCount = (UINT)indices.size(); pyramid.StartIndexLocation = 36; pyramid.BaseVertexLocation = 8; mBoxGeo->DrawArgs["box"] = submesh; mBoxGeo->DrawArgs["pyramid"] = pyramid;  
      Objects are drawn
      mCommandList->DrawIndexedInstanced( mBoxGeo->DrawArgs["box"].IndexCount, 1, 0, 0, 0); mCommandList->DrawIndexedInstanced( mBoxGeo->DrawArgs["pyramid"].IndexCount, 1, 36, 8, 0);  
      Vertex Shader
       
      cbuffer cbPerObject : register(b0) { float4x4 gWorldViewProj; }; struct VertexIn { float3 PosL : POSITION; float4 Color : COLOR; }; struct VertexOut { float4 PosH : SV_POSITION; float4 Color : COLOR; }; VertexOut VS(VertexIn vin) { VertexOut vout; // Transform to homogeneous clip space. vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj); // Just pass vertex color into the pixel shader. vout.Color = vin.Color; return vout; } float4 PS(VertexOut pin) : SV_Target { return pin.Color; }  

    • By mark_braga
      I am confused why this code works because the lights array is not 16 bytes aligned.
      struct Light {     float4 position;     float radius;     float intensity; // How does this work without adding // uint _pad0, _pad1; }; cbuffer lightData : register(b0) {     uint lightCount;     uint _pad0;     uint _pad1;     uint _pad2; // Shouldn't the shader be not able to read the second element in the light struct // Because after float intensity, we need 8 more bytes to make it 16 byte aligned?     Light lights[NUM_LIGHTS]; } This has erased everything I thought I knew about constant buffer alignment. Any explanation will help clear my head.
      Thank you
    • By HD86
      I don't know in advance the total number of textures my app will be using. I wanted to use this approach but it turned out to be impractical because D3D11 hardware may not allow binding more than 128 SRVs to the shaders. Next I decided to keep all the texture SRV's in a default heap that is invisible to the shaders, and when I need to render a texture I would copy its SRV from the invisible heap to another heap that is bound to the pixel shader, but this also seems impractical because ID3D12Device::CopyDescriptorsSimple cannot be used in a command list. It executes immediately when it is called. I would need to close, execute and reset the command list every time I need to switch the texture.
      What is the correct way to do this?
  • Popular Now