Tile frustum calculation for the point light culling

Tile-based deferred shading throught the DirectX Compute Shader for achieving the high performance with many lights by amortizing the light culling overhead over screen tiles as well as grouping lights to avoid wasting memory bandwidth.

As you know, we need to calculate the frustum of each tile for our tile-based rendering – (0) Intel of sample “Deferred Rendering for Current and Future Rendering Pipelines”

#define COMPUTE_SHADER_TILE_GROUP_DIM 10
// Work out scale/bias from [0, 1]
float2 tileScale = float2(mFramebufferDimensions.xy) * rcp(float(2 * COMPUTE_SHADER_TILE_GROUP_DIM));
float2 tileBias = tileScale - float2(groupId.xy);

// Now work out composite projection matrix
// Relevant matrix columns for this tile frusta
float4 c1 = float4(mCameraProj._11 * tileScale.x, 0.0f, tileBias.x, 0.0f);
float4 c2 = float4(0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);
float4 c4 = float4(0.0f, 0.0f, 1.0f, 0.0f);

// Derive frustum planes
float4 frustumPlanes[6];
// Sides
frustumPlanes[0] = c4 - c1; // (-X direction)
frustumPlanes[1] = c1;      // (+ X direction).
frustumPlanes[2] = c4 - c2; // (-Y direction).
frustumPlanes[3] = c2;      // (+ Y direction)
// Near/far
frustumPlanes[4] = float4(0.0f, 0.0f, 1.0f, -minTileZ);
frustumPlanes[5] = float4(0.0f, 0.0f, -1.0f, maxTileZ);

// Normalize frustum planes (near/far already normalized)
[unroll] for (uint i = 0; i < 4; ++i) {
frustumPlanes[i] *= rcp(length(frustumPlanes[i].xyz));
}

In the above code, we have to calculate the normal of 6 planes that constitute the frustum.
The calculation is performed with a view space, but the normal line of the calculation is considered in the tile, not the distance unit.

Meaning of the variables:

mFramebufferDimensions = length and width of the frame buffer size (pixels)
tileScale = the center as (0, 0), one tile = 1.0 to become such a maximum value of the tile coordinate system
tileBias = coordinates of the tile you are dealing now (beginning from +tileScale, ending in -tileScale)

In the following description mFramebufferDimensions = (600,600), and the COMPUTE_SHADER_TILE_GROUP_DIM = 10.
Since the tileScale = 600 / (2 * 10) = 30, the minimum -30.0 and range to a maximum of 30.0, will coordinate system:

time

Now lets try to calculate the normal line (+ Y direction) of the frustum.
float4 c2 = float4 to (0.0f, tileBias.y, mCameraProj._22 * tileScale.y, 0.0f)  (C2 tile coordinate system in the view space).

The value of mCameraProj._22 is, row 2, column 2 of the projection matrix = (1.0f / tan (fovY / 2))

First, consider the case of groupID = 0.

group-id-0

Rotating c2 90 degrees, since the normal direction of the plane of the + Y side of the frustum is understood to be a c2. We have the expression of the following Intel’s sample.
float4 c2 ‘= float4 (0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);

group-id-0-90

Similarly, consider the case of groupID = 1 is next to the tile. (In this case, tileBias = tileScale – 20)

group-id-1

Rotating c2 90 degrees, since the normal direction of the plane of the + Y side of the frustum is understood to be a c2. We have the expression of the following Intel’s sample.
float4 c2 ‘= float4 (0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);

group-id-1-90

Try to calculate the normal line of the lower side of the frustum (-Y direction).
float4 c2′ = float4 (0.0f, -mCameraProj._22 * tileScale.y, tileBias.y, 0.0f);
float4 c4 = float4 (0.0f, 0.0f, 1.0f, 0.0f);
(C2′ and C4 tile coordinate system in the view space)

Then, the normal direction of the lower side of the frustum (-Y direction), c4 – can be represented by c2′.

Consider the case of groupID = 0.

group-id-0-c2

Repeat this for each coordinate of the point light with collision detection of the frustum.

// Cull lights for this tile
for (uint lightIndex = groupIndex; lightIndex < numPointLights; lightIndex += COMPUTE_SHADER_TILE_GROUP_SIZE) {

float3 lightPosition = mul(float4(PointLightDataArray[lightIndex].pos.xyz, 1.0f), matView).xyz;
float cutoffRadius = PointLightDataArray[lightIndex].col_radius.w;
// Cull: point light sphere vs tile frustum
bool inFrustum = true;
[unroll] for (uint i = 0; i < 6; ++i) {
float d = dot(frustumPlanes[i], float4(lightPosition, 1.0f));
inFrustum = inFrustum && (d >= -cutoffRadius);
}

[branch] if (inFrustum) {
// Append light to list
// Compaction might be better if we expect a lot of lights
uint listIndex;
InterlockedAdd(sTileNumLights, 1, listIndex);
sTileLightIndices[listIndex] = lightIndex;
}
}