-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
base: master
Are you sure you want to change the base?
Conversation
Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s). |
Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s). |
Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s). |
Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s). |
Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s). |
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 sharedStencil 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.

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
Number.MAX_VALUE
seems to break ittransferToNodeMaterialEffect
I don't plan to support these kinda lights: