Skip to content

Initial clustered lights implementation (tiled clustering only) #16866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

matanui159
Copy link
Contributor

@matanui159 matanui159 commented Jul 9, 2025

Implementation Details

When this work is more complete ill move all this over to the official docs, but for now just using this PR as a rough set of notes/thoughts/todos

The implementation is mainly inspired by these CoD slides: https://advances.realtimerendering.com/s2017/2017_Sig_Improved_Culling_final.pdf

The main idea is to split the clustering into separate X/Y "tiled" clustering AND a Z "depth" clustering, and then checking if the lights is in both the tile and the depth slice.

Tiled Clustering

This uses the rendering pipeline to draw meshes for each light and writes bits out to a light mask texture if there are pixels infront of the depth buffer (effectively finding lights that intersect the depth buffer).

The render target with the light mask texture and depth buffer has a resolution equal to the amount of tiles.

Depth pre-pass

All the scene geometry is rendered to a low resolution render target. Currently this is repeated per ClusteredLight, in future these depth texture might be able to get shared

Stencil Pass (removed)

Draws all the light geometry with depthFunction=GREATER and sets stencil bits to 1.

This is combined with the next pass to only mark pixels that contain light geometry both behind AND infront of the depth buffer.

However this ended up causing issues due to the low-resolution aliased depth buffer where stencil bits weren't set for the edges of meshes.
image

In future we could maybe get around this by doing a post-process pass over the depth pre-pass to expand the max depth values out by one tile. Although for now this just adds extra complexity and we probably don't really need this pass.

Light Mask Pass

Each light mesh is drawn with a different index and any fragment pixels which pass the depth test (meaning the light is in front of the depth pre-pass) will set its bit in the light mask texture with an atomic OR.

Atleast thats the plan on WebGPU which isn't implemented yet. WebGL doesn't have support for storage textures or atomic operations, so we instead use a float buffer with additive blending.

To prevent accuracy issues with floating point values we ensure the mask doesn't go above the bits in the floating-point fraction, which limits us to 23 lights per clusteredlight on most systems.

Lighting pass

This light mask is then queried bit-by-bit in the lighting calculation to figure out which lights are in that "tile"

Light Meshes

All the light meshes are just spheres. To reduce the pixels set for spotlights they are modified in the vertex shader so positions on the sphere that are outside the spotlight angle are set to 0,0,0. This causes the sphere to end up having a more cone shape

Depth Clustering

TODO. not currently implemented

CoD seems to do this in CPU which makes sense since its only a min/max index per slice so can be cheaply calculated and uploaded.

Not Implemented / Broken

  • PBR materials
  • I have code for point lights but its untested. I feel like they likely won't work rn
  • moving the camera too close or too far from the lights make them disappear
    • this is an issue with the camera intersecting with the light geometry, on WebGPU this can be fixed by disabling back-face culling but on WebGL this will cause more issues due to its additive blending
    • on WebGL we could test if the light mesh intersects with the camera and draw back faces instead
  • spot lights that point in any other direction but down
    • to make the light mesh vertex shader simpler we assume a direction of down. this will have to get changed or (better) we just rotate the world matrix by the light direction
  • lights with a default range. trying to scale the light geometry by Number.MAX_VALUE seems to break it
    • maybe we should clamp it?
  • WebGPU support
  • Using instancing for the light meshes
  • make the amount of tiles configurable
    • I don't know if this should be an options to specify the number of tiles or to specify the size of each tile
  • figure out what to do for transferToNodeMaterialEffect

I don't plan to support these kinda lights:

  • anything that requires textures like IES textures or shadowed lights
  • anything other than point/spot lights
  • lights using anything other than the default falloff

@bjsplat
Copy link
Collaborator

bjsplat commented Jul 9, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Jul 10, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Jul 11, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Jul 14, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Collaborator

bjsplat commented Jul 16, 2025

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants