Advertisement

Voxelization (VCT GI) - Handing out-of-bounds writes

Started by October 05, 2019 09:30 AM
1 comment, last by MJP 4 years, 11 months ago

I'm implementing single-pass surface voxelization (via Geometry Shader) for Voxel Cone Tracing (VCT),

and after some debugging I've discovered that I have to insert out-of-bounds checks into the pixel shader to avoid voxelizing geometry which is outside the voxel grid:

 


void main_PS_VoxelTerrain_DLoD( VSOutput pixelInput )
{
	const float3 posInVoxelGrid =
		(pixelInput.position_world - g_vxgi_voxel_radiance_grid_min_corner_world) * g_vxgi_inverse_voxel_size_world;

	const uint color_encoded = packR8G8B8A8( float4( posInVoxelGrid, 1 ) );

	const int3 writecoord = (int3) floor( posInVoxelGrid );

	const uint writeIndex1D = flattenIndex3D( (uint3)writecoord, (uint3)g_vxgi_voxel_radiance_grid_resolution_int );

	// HACK:
	bool inBounds =
		writecoord.x >= 0 && writecoord.x < g_vxgi_voxel_radiance_grid_resolution_int &&
		writecoord.y >= 0 && writecoord.y < g_vxgi_voxel_radiance_grid_resolution_int &&
		writecoord.z >= 0 && writecoord.z < g_vxgi_voxel_radiance_grid_resolution_int ;

	if( inBounds )
	{
		rwsb_voxelGrid[writeIndex1D] = color_encoded;
	}
	else
	{
		rwsb_voxelGrid[writeIndex1D] = 0xFF0000FF; //RED, ALPHA
	}
}

 

Why is this check needed, and how can I avoid it? Shouldn't Direct3D automatically clip the pixels falling outside the viewport? (I tried to ensure that out-of-bounds pixels are clipped in the geometry shader and I also enable depthClip in rasterizer, but it doesn't work.)

Here's a picture illustrating the problem (extraneous voxels are highlighted with red):

Spoiler

1749378791_voxelizationerrors.png.732787c796e05670b3b69a4b6243a4b7.png

And here the full HLSL code of the voxelization shader:

Spoiler

 



int maxIndex3( in float3 v )
{
	return ( v.x > v.y )
		? ( ( v.x > v.z ) ? 0 : 2 )
		: ( ( v.y > v.z ) ? 1 : 2 );
}

cbuffer Uniforms
{
	row_major float4x4	u_local_to_world_space;
};

RWStructuredBuffer< uint >	rwsb_voxelGrid;

//-----------------------------------------------------------------------------

struct VSOutput
{
	float3 position_world : Position;	// world-space position
	float3 normalWS : Normal0;	// world-space normal
};

VSOutput main_VS_VoxelTerrain_DLoD( in Vertex_DLOD vertexInput )
{
	VSOutput	vertexOutput;
	vertexOutput.position_world = mul( u_local_to_world_space, float4( vertexInput.localPosition, 1.0f ) ).xyz;
	vertexOutput.normalWS = Dir_Local_To_World( vertexInput.localNormal );
	return vertexOutput;
}

//-----------------------------------------------------------------------------

struct GSOutput
{
  	float4 positionNDC : SV_Position;	// clip-space position
	float3 position_world : Position;	// world-space position
	float3 normalWS : Normal0;	// world-space normal
};

[maxvertexcount(3)]
void main_GS(
	triangle VSOutput input[3],
	inout TriangleStream< GSOutput > outputStream
)
{
	// Calculate the dominant direction of the surface normal.
	const int axis = maxIndex3( abs( input[0].normalWS + input[1].normalWS + input[2].normalWS ) );
	
	// Project the triangle in the dominant direction for rasterization,
	// but not for lighting.
	[unroll]
	for( uint i = 0; i < 3; i++ )
	{
		GSOutput	output;

		output.position_world = input[i].position_world;
		
		const float3 position01_in_voxel_grid =
			((input[i].position_world - g_vxgi_voxel_radiance_grid_min_corner_world) * g_vxgi_inverse_voxel_size_world) * g_vxgi_voxel_radiance_grid_inverse_resolution;
	
		output.positionNDC.xyz = position01_in_voxel_grid * 2 - 1;

		[flatten]
		switch (axis) {
		case 0:
			output.positionNDC.xy = output.positionNDC.yz;
			break;
		case 1:
			output.positionNDC.xy = output.positionNDC.zx;
			break;
		default:
			break;
		}

		output.positionNDC.zw = 1;
		
		output.normalWS = input[i].normalWS;
		
		outputStream.Append(output);
	}

  	outputStream.RestartStrip();
}

//-----------------------------------------------------------------------------

void main_PS_VoxelTerrain_DLoD( VSOutput pixelInput )
{
	const float3 posInVoxelGrid =
		(pixelInput.position_world - g_vxgi_voxel_radiance_grid_min_corner_world) * g_vxgi_inverse_voxel_size_world;

	const uint color_encoded = packR8G8B8A8( float4( posInVoxelGrid, 1 ) );

	const int3 writecoord = (int3) floor( posInVoxelGrid );

	const uint writeIndex1D = flattenIndex3D( (uint3)writecoord, (uint3)g_vxgi_voxel_radiance_grid_resolution_int );

	// HACK:
	bool inBounds =
		writecoord.x >= 0 && writecoord.x < g_vxgi_voxel_radiance_grid_resolution_int &&
		writecoord.y >= 0 && writecoord.y < g_vxgi_voxel_radiance_grid_resolution_int &&
		writecoord.z >= 0 && writecoord.z < g_vxgi_voxel_radiance_grid_resolution_int ;

	if( inBounds )
	{
		rwsb_voxelGrid[writeIndex1D] = color_encoded;
	}
	else
	{
		rwsb_voxelGrid[writeIndex1D] = 0xFF0000FF; //RED, ALPHA
	}
}

 

You will only get pixel shader executions for pixels that lie within your specified viewport. With your current method you're not going to get any clipping along the Z axis in NDC space, since you're forcing z = w = 1. I would try setting w to 1.0, and outputting a proper value for Z instead of forcing it to 1 (keep in mind that Z is [0, 1] in NDC space, unlike X and Y which are [-1, 1]. So basically you'll want to generate Z by swizzling just like you're doing for X and Y, but then do Z = Z * 0.5 + 0.5 to get it to the [0, 1] range.

This topic is closed to new replies.

Advertisement