{"id":19066089,"url":"https://github.com/wizgrav/cl2","last_synced_at":"2025-04-28T12:25:01.723Z","repository":{"id":232005983,"uuid":"760761137","full_name":"wizgrav/cl2","owner":"wizgrav","description":"Compute-Less Clustered Lighting","archived":false,"fork":false,"pushed_at":"2024-04-14T09:40:02.000Z","size":1686,"stargazers_count":56,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-18T16:16:24.674Z","etag":null,"topics":["clustered-shading","lighting","webgl"],"latest_commit_sha":null,"homepage":"https://wizgrav.github.io/cl2/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wizgrav.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-20T20:14:29.000Z","updated_at":"2025-04-17T07:23:57.000Z","dependencies_parsed_at":"2024-04-14T10:34:01.155Z","dependency_job_id":null,"html_url":"https://github.com/wizgrav/cl2","commit_stats":null,"previous_names":["wizgrav/cl2"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wizgrav%2Fcl2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wizgrav%2Fcl2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wizgrav%2Fcl2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wizgrav%2Fcl2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wizgrav","download_url":"https://codeload.github.com/wizgrav/cl2/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251312258,"owners_count":21569200,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clustered-shading","lighting","webgl"],"created_at":"2024-11-09T00:54:21.716Z","updated_at":"2025-04-28T12:25:01.707Z","avatar_url":"https://github.com/wizgrav.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Compute-Less Clustered Lighting\r\n\r\nA technique for handling thousands of lights in WebGL by slicing the camera frustum into clusters and assigning lights to them using the rasterization pipeline. \r\n\r\n![](promo.jpg)\r\n\r\n## Technique\r\n\r\nWe will use two textures to store the cluster lists.\r\n\r\n##### List texture \r\n\r\nThe list texture is of __RGBA unsigned byte__ type/format. We partition it spatially into XY tiles. Each 2D tile is then subdivided horizontally into Z depth slices / clusters. The above define a block of clusters where each texel contains a batch of 32 lights that belong in each cluster. \r\n\r\nWe replicate the above structure vertically to create a vertical list of __(lightCount / 32)__ blocks. The total texture dimensions will be __width = X tiles * Z slices__ and height = Y tiles * N batches/blocks.\r\n\r\nWe clear all 4 channels to zero and proceed to rasterize all our lights in the list texture with an instanced draw call. Each light is represented as an instance of a quad. \r\n\r\nIn the vertex shader we calculate the clip space bounds for each light sphere and apply it to the vertices of a quad. We snap the X clip coordinate to the Z cluster subdivisions to enclose whole tiles. We also scale and translate the Y coordinate to apply the quad on the appropriate batch/block with index __( lightInstanceID / 32 )__. Finally we calculate the min and max z depth slices that this light extends to and pass them as varyings to the fragment shader.\r\n\r\nIn the fragment shader, we find the depth slice of the current pixel as __( fragCoord.x \\% numberOfdepthSlices)__ and if it is outside the min and max range in the varyings we calculated before, we discard it. Otherwise we proceed to set the appropriate bit in the cluster bitmask.\r\n\r\nAs WebGL doesn't support logical operations to OR the bits directly, we emulate them on top of fixed point by setting the Blend to __( ONE, ONE )__. Each RGBA texel is treated as a sequence of 4 bitmasks of 8 elements each for a total of 32 elements per batch. \r\n\r\nBased on the position of the light in  the batch __( positionInBatch = instanceID \\% 32 )__ we raise two to the power 0 - 7 indicated by the position in the channel octet __( positionInBatch \\% 8 )__ and divide by 255.\r\n\r\nWe write the result to the rgba channel(0-3) at __( positionInBatch / 8 )__ with the rest of the channels set to zero. The additive blending will then set the bit corresponding to the position of the light in this batch.\r\n\r\n##### Master texture\r\n\r\nThe master texture is single channel __R32UI__. We use integers as 32 element bitmasks directly here since we don't need blending. The structure for this texture is similar to the list texture. \r\n\r\nWe divide spatially into XY tiles and subdivide them horizontally into Z clusters but here we also subdivide the XY tiles vertically as well to add multiple master texels per cluster. \r\n\r\nEvery master texel is a bitmask of 32 elements each of which indicates if a batch of 32 lights had at least one light active in this cluster. \r\n\r\nUnlike the list texture, we don't partition this texture into vertical blocks. The reason we arrange it like this, with the master masks contained in the XY tile, is to take advantage of caching later when we perform the lighting. \r\n\r\nWe draw a fullscreen triangle and for each texel of the master texture, we fetch 32 texels for the corresponding XYZ clusters from each of 32 vertical blocks of the list texture and check if any of the rgba channels of the list texels is non zero. If true, we set the bit for this batch in the master texel. Eventually we end up with an hierarchy of bitmasks contained in the two textures.\r\n\r\n##### Shading\r\n\r\nWhen we apply the lighting, we figure out the XYZ cluster the current fragment belongs to and fetch the corresponding master texel(s). We iterate their bits and for those which are set, we fetch the corresponding texels from the list texture. We multiply each rgba channel by 255 and convert back to uint to iterate their 32 bits(4 channels x 8 each). For the set bits, we calculate the light index __( 32 * batchIndex + positionInBatch )__ and fetch the light properties to shade it.\r\n\r\n\r\n## Performance\r\n\r\nThe most important optimization we can apply would be to presort the lights by their [morton order](https://en.wikipedia.org/wiki/Z-order_curve). This order can be calculated by multiplexing the bits of some of the, world or camera space, coordinates of the lights. \r\n\r\nThis operation will rearrange the representative bits of lights which are close in space, to come closer in the bitmasks as well leading to more empty batch texels overall with those remaining being fuller, resulting in significantly reduced texel fetching while shading.\r\n\r\nIn the demo where the lights are orbiting relatively close to an anchor, they are morton ordered just once on scene creation based on the world space XZ coordinates of their anchor.\r\n\r\nYou can use the url params [model=0](https://wizgrav.github.io/cl2/?model=0) and [wisp=0](https://wizgrav.github.io/cl2/?wisp=0) to hide various meshes in the demo. Lights are morton ordered by default, [shuffle=1](https://wizgrav.github.io/cl2/?shuffle=1) disables.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwizgrav%2Fcl2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwizgrav%2Fcl2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwizgrav%2Fcl2/lists"}