DaveH355 / clustered-shading

An OpenGL tutorial on clustered shading. A technique for efficiently rendering thousands of dyanmic lights in games.
MIT License
42 stars 2 forks source link

Light radius not in view space #3

Closed Gpinchon closed 3 months ago

Gpinchon commented 3 months ago

Except if I'm missing something, the light radius is not in view space when the light sphere is tested against the AABB in this function

bool testSphereAABB(uint i, Cluster cluster)
{
    vec3 center = vec3(viewMatrix * pointLight[i].position);
    >>> float radius = pointLight[i].radius;

    vec3 aabbMin = cluster.minPoint.xyz;
    vec3 aabbMax = cluster.maxPoint.xyz;

    return sphereAABBIntersection(center, radius, aabbMin, aabbMax);
}
DaveH355 commented 3 months ago

The light radius is the same in view space as in world space.

Gpinchon commented 3 months ago

Shouldn't the light radius get "smaller" in view space as the light moves away from the camera ? Here is how I would do it :

After checking the result of this calculation, this results in changing sphere radius in NDC depending on the sphere's position in world space relative to the camera (but maybe my calculations are wrong)

DaveH355 commented 3 months ago

The intersection test happens entirely in view space. The cluster AABB is defined in view space, so we multiply the sphere position by the view matrix to "bring it" to view space.

The sphere radius is the same because view space is world space, just rotated and translated to be relative to the camera. It wouldn't make sense for the radius to get smaller somehow because a sphere is still the same size after rotation and translation.

Your approach: multiplying sphere position by the MVP matrix would actually produce clip space coordinate. You would need to divide clip.xyz by clip.w to get to NDC. Unless the projection matrix is orthographic, then clip space and NDC are the same.

Gpinchon commented 3 months ago

Ah my bad, I mixed up NDC and view space, thank you very much for the explanation ! :+1:

I went for a culling against NDC space as the calculations seem more simple because you end up checking intersection against the NDC bounding box spanning from [-1, 1] (generating the cluster's bounding boxes is also simpler and never requires update). I'll implement both View and NDC space culling to make sure the necessary projections don't impact performance and if it works as expected.

For those who wanna do the same here is how I went (hoping I made no mistake here), this is the CPP version, but it's easy to port it to GLSL, sorry for the auto everywhere, I'm lazzy :

//GLSL::LightBase is just a position (vec3) and a range (float)
//a_MVP is cameraProjection * cameraView;
GLSL::LightBase CreateNDCLight(const GLSL::LightBase& a_WorldLight, const glm::mat4x4& a_MVP)
{
    auto worldLightLimit = glm::vec3(
        a_WorldLight.position.x + a_WorldLight.range,
        a_WorldLight.position.y,
        a_WorldLight.position.z);
    auto viewLightPos   = a_MVP * glm::vec4(a_WorldLight.position, 1);
    auto viewLightLimit = a_MVP * glm::vec4(worldLightLimit, 1);
    auto NDCLightPos    = glm::vec3(viewLightPos) / viewLightPos.w;
    auto NDCLightLimit  = glm::vec3(viewLightLimit) / viewLightLimit.w;
    auto NDCLightRange  = glm::distance(NDCLightPos, NDCLightLimit);
    return { NDCLightPos, NDCLightRange };
}