Repository navigation
Conversation
CanReader
force-pushed
the
feat/cascaded-shadow-maps
branch
from
October 4, 2026 19:41
baf21c1 to
2c48a9d
Compare
CanReader
force-pushed
the
feat/clustered-lights
branch
from
October 4, 2026 19:46
cbbad2b to
a20950b
Compare
CanReader
force-pushed
the
feat/cascaded-shadow-maps
branch
from
October 4, 2026 19:53
2c48a9d to
afef417
Compare
LightManager keeps typed directional and local light lists, builds a 16x9x24 froxel grid on the CPU every frame and hands the light list, per-cluster ranges and index list to the renderer. Vulkan and GL read them from storage buffers in the lighting pass, so hundreds of point and spot lights work there. Forward shaders get the main light plus the nearest lights in the old 16-entry block. The engine now ships its own lighting_pass_gl.frag.
CanReader
force-pushed
the
feat/clustered-lights
branch
from
October 4, 2026 19:56
a20950b to
49213eb
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #213 (cascaded shadow maps), since both touch the light UBO,
LightManager::UpdateShadowDataand the lighting shaders. Review that one first; this diff is against its branch.What this changes
The deferred lighting pass on Vulkan and OpenGL now shades point and spot lights through a clustered light list, so a scene can have hundreds of them. The forward path keeps its 16-entry light block, now filled with the main directional light first and then the nearest lights.
LightManagerkeeps typed light lists instead of runningdynamic_castover every light two to three times a frame, and the 16-light registration cap and the 3-extra-directional-light limit are gone for the deferred path.Why
Before this,
RegisterLightrefused anything pastMAX_LIGHTS = 16, the lighting UBO packed the first 16 in registration order, andUpdateShadowDatadid threedynamic_castscans over all lights per frame (shadow light, fallback light, up to 3 extra directional lights). On Vulkan it was worse: the deferred lighting pass never read the light array at all, it only knew the main light and the 3 extras inShadowLightUBO, so point and spot lights did nothing. On OpenGL the engine did not even shiplighting_pass_gl.frag, so deferred only worked when the game brought its own.How it works:
RegisterLightsorts a light intom_directionalLightsorm_localLightsonce (the only cast left), andUnregisterLightremoves it from the matching list. The main directional light is "first enabled shadow caster, else first enabled", same rule as before, now a walk over the directional list.LightCBData.Lights[16]gets the main directional light, the other directional lights, then the nearest local lights. That ordering also means slot 0 is the shadowed light, which is what the forward shaders already assumed.MAX_CLUSTERED_LIGHTS = 1024, and the index list at 256K entries (warns once and drops the farthest lights' references past that).GlobalLightCount), then the local lights.AttenuateUE4), which reaches exactly zero atRange. That is what makes per-cluster culling exact instead of an approximation. Spots add the cone fromSpotInnerCos/SpotOuterCos; area lights are shaded as points for now.VulkanClusteredLights.cppwith a set-4 layout (params UBO plus three storage buffers) on the lighting pipeline and one set of host-visible VMA buffers per frame in flight.UpdateClusteredLightswrites straight into the current frame's buffers, same as the light UBO, which is safe becauseLightManager::UpdateAndBindruns afterBeginRenderhas waited on that frame's fence. Staging it to the nextBeginRenderinstead would have used last frame's camera for the clusters.lighting_pass_gl.frag. It follows the look of SleakCraft's version (wrap diffuse main light, hemisphere ambient, optional IBL, ACES and fog in the pass) plus the cascades from feat: cascaded shadow maps for the main directional light #213 and the clustered loop. Games that ship their ownlighting_pass_gl.fragstill override it, so SleakCraft keeps its shader until it opts in.RenderergetsSupportsClusteredLights()and a no-opUpdateClusteredLights, soLightManageronly builds clusters for backends that read them.DX11/DX12: no change. They have no deferred path and their forward shaders read the 16-entry block, which is now the nearest 16 instead of the first 16 registered, so they keep a correct fallback. Clustered shading there would need structured buffers in both backends with nothing to test it on here.
This overlaps #209, which ships the plain GL lighting shader for #144. Both add
assets/shaders/lighting_pass_gl.frag; both start from SleakCraft's copy, and this one adds the cascade and cluster loops (and drops the unused GGX helpers), so whichever lands second should keep this version.Not covered here: per-light shadows, tiled or clustered culling for the forward and transparent passes (water in SleakCraft still only sees the 16 nearest), real area light shading, and SleakCraft's own
lighting_pass_gl.frag, which still loops the 16-entry block until the game switches to the engine one or adds the cluster loop.How it was verified
Throwaway change in the verify tree (not committed): 300 lights scattered over the spawn area of
-world citest -seed 42, every 5th one a spot pointing down, ranges 6-14 m, sun dimmed so they show.--validateunder Xvfb: lights render, and the only validation messages are the twovkDestroyDeviceones from killing the process, same as on main. The vertex-attribute warnings at pipeline creation are also on main, same count.lighting_pass_gl.fragcopied over the game's inbin/: same result as Vulkan.-r opengl -w 640 -h 360 -world citest -seed 42 -rd 4run survives (exit 143) with no critical lines.--bench: 15.1 ms median with no extra lights and 12.7 ms with 300. That run-to-run noise is bigger than the clustering cost, so all I can say is it did not show up.Checklist
.spv(Vulkanlighting_pass.frag+.spv, new GLlighting_pass_gl.frag; DX has no deferred lighting shader)///commentssrc/orinclude/private/in public headers