Skip to content

Commit 86f4414

Browse files
committed
WIP: light culling, shadow
1 parent c4ab32e commit 86f4414

12 files changed

Lines changed: 528 additions & 164 deletions

File tree

Core/Source/Lux/Renderer/Renderer.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,13 @@ namespace Lux {
812812
});
813813
}
814814

815+
void Renderer::LightCulling(Ref<RenderCommandBuffer> renderCommandBuffer, Ref<ComputePass> computePass, Ref<Material> material, const glm::uvec3& workGroups)
816+
{
817+
Renderer::BeginComputePass(renderCommandBuffer, computePass);
818+
Renderer::DispatchCompute(renderCommandBuffer, computePass, material, workGroups);
819+
Renderer::EndComputePass(renderCommandBuffer, computePass);
820+
}
821+
815822
void Renderer::BeginGPUPerfMarker(Ref<RenderCommandBuffer> renderCommandBuffer, const std::string& label, const glm::vec4& markerColor)
816823
{
817824
Renderer::Submit([renderCommandBuffer, s = label]() mutable

Core/Source/Lux/Renderer/SceneRenderer.cpp

Lines changed: 246 additions & 8 deletions
Large diffs are not rendered by default.

Core/Source/Lux/Renderer/SceneRenderer.h

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#include "Lux/Renderer/Camera.h"
55
#include "Lux/Renderer/RenderCommandBuffer.h"
66
#include "Lux/Renderer/RenderPass.h"
7+
#include "Lux/Renderer/ComputePass.h"
78
#include "Lux/Renderer/Pipeline.h"
9+
#include "Lux/Renderer/PipelineCompute.h"
810
#include "Lux/Renderer/Framebuffer.h"
911
#include "Lux/Renderer/Material.h"
1012
#include "Lux/Renderer/Mesh.h"
@@ -138,6 +140,9 @@ namespace Lux {
138140
class SceneRenderer : public RefCounted
139141
{
140142
public:
143+
static constexpr uint32_t MaxSpotShadows = 16;
144+
static constexpr uint32_t LightCullingTileSize = 16;
145+
static constexpr uint32_t MaxVisibleLightsPerTile = 256;
141146
struct Statistics
142147
{
143148
uint32_t DrawCalls = 0;
@@ -279,19 +284,23 @@ namespace Lux {
279284
void FlushDrawList();
280285

281286
void ShadowMapPass();
287+
void SpotShadowMapPass();
282288
void PreDepthPass();
289+
void LightCullingPass();
283290
void SkyboxPass();
284291
void GeometryPass();
285292
void CompositePass();
286293
void GridPass();
287294

288295
void UpdateStatistics();
296+
void ResizeLightCullingResources();
289297

290298
// Render-thread draw helper (must be called inside Renderer::Submit).
291299
void RT_DrawStaticMesh(Ref<RenderCommandBuffer> cmd,
292300
const StaticDrawCommand& dc,
293301
const TransformMapData& tmd,
294-
bool bindMaterial);
302+
bool bindMaterial,
303+
uint32_t lightIndex = 0);
295304

296305
// ── Uniform buffer GPU structs ────────────────────────────────────────
297306

@@ -332,10 +341,10 @@ namespace Lux {
332341

333342
struct UBSpotShadow
334343
{
335-
glm::mat4 ViewProjection[16];
344+
glm::mat4 ViewProjection[MaxSpotShadows];
336345
uint32_t Count = 0;
337346
glm::vec3 Padding{};
338-
};
347+
} m_SpotShadowUB;
339348

340349
struct UBRendererData
341350
{
@@ -355,6 +364,14 @@ namespace Lux {
355364
char Pad3[3] = { 0, 0, 0 };
356365
} m_RendererDataUB;
357366

367+
struct UBScreenData
368+
{
369+
glm::vec2 InvFullResolution = { 1.0f, 1.0f };
370+
glm::vec2 FullResolution = { 1.0f, 1.0f };
371+
glm::vec2 InvHalfResolution = { 1.0f, 1.0f };
372+
glm::vec2 HalfResolution = { 1.0f, 1.0f };
373+
} m_ScreenDataUB;
374+
358375
struct UBPointLights
359376
{
360377
uint32_t Count = 0;
@@ -397,24 +414,40 @@ namespace Lux {
397414
Ref<UniformBufferSet> m_UBSShadow;
398415
Ref<UniformBufferSet> m_UBSSpotShadow;
399416
Ref<UniformBufferSet> m_UBSRendererData;
417+
Ref<UniformBufferSet> m_UBSScreenData;
400418
Ref<UniformBufferSet> m_UBSPointLights;
401419
Ref<UniformBufferSet> m_UBSSpotLights;
402420

403421
Ref<StorageBufferSet> m_SBSInstanceTransforms; // TransformVertexData[]
404422
Ref<StorageBufferSet> m_SBSObjectIndexes; // uint32_t[] – maps draw → transform
405423
Ref<StorageBufferSet> m_SBSVisiblePointLightIndices;
406424
Ref<StorageBufferSet> m_SBSVisibleSpotLightIndices;
425+
uint32_t m_LightTilesCountX = 1;
426+
uint32_t m_LightTilesCountY = 1;
427+
uint32_t m_VisibleLightIndexBufferSize = 0;
407428

408429
// ── Shadow map (single ortho cascade) ────────────────────────────────
409430
Ref<Image2D> m_ShadowMapImage;
410431
Ref<RenderPass> m_ShadowMapPass;
411432
Ref<Material> m_ShadowPassMaterial;
412433

434+
// ── Spot shadow atlas ───────────────────────────────────────────────
435+
Ref<Image2D> m_SpotShadowMapImage;
436+
Ref<RenderPass> m_SpotShadowMapPass;
437+
Ref<Material> m_SpotShadowPassMaterial;
438+
uint32_t m_SpotShadowMapSize = 2048;
439+
uint32_t m_SpotShadowAtlasGridSize = 1;
440+
uint32_t m_SpotShadowTileSize = 2048;
441+
uint32_t m_SpotShadowCount = 0;
442+
413443
// ── Pre-depth pass ────────────────────────────────────────────────────
414444
Ref<Pipeline> m_PreDepthPipeline;
415445
Ref<Material> m_PreDepthMaterial;
416446
Ref<RenderPass> m_PreDepthPass;
417447

448+
// ── Tiled light culling ──────────────────────────────────────────────
449+
Ref<ComputePass> m_LightCullingPass;
450+
418451
// ── Geometry pass ─────────────────────────────────────────────────────
419452
Ref<Framebuffer> m_GeometryPassFramebuffer; // owns the attachments
420453
Ref<Pipeline> m_GeometryPipeline; // opaque PBR

Editor/Resources/Shaders/Include/GLSL/Buffers.glslh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ layout(std140, set = 1, binding = 7) uniform SpotLightData
6969

7070
layout(std140, set = 1, binding = 8) uniform SpotShadowData
7171
{
72-
mat4 Mats[1024];
72+
mat4 Mats[16];
73+
uint Count;
74+
vec3 Padding;
7375
} u_SpotLightMatrices;
7476

7577
/*layout(std430, set = 1, binding = 9) readonly buffer VisiblePointLightIndicesBuffer

Editor/Resources/Shaders/Include/GLSL/Lighting.glslh

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ layout(std430, set = 1, binding = 10) readonly buffer VisibleSpotLightIndicesBuf
1414
int Indices[];
1515
} s_VisibleSpotLightIndicesBuffer;
1616

17+
const int LIGHT_CULLING_TILE_SIZE = 16;
18+
const int MAX_VISIBLE_LIGHTS_PER_TILE = 256;
19+
1720
// Used in PBR shader
1821
struct PBRParameters
1922
{
@@ -64,25 +67,43 @@ vec3 CalculateDirLights(vec3 F0)
6467

6568
int GetPointLightBufferIndex(int i)
6669
{
67-
// Lux does not currently dispatch the Hazel-style light-culling pass, so
68-
// fall back to iterating the uploaded point-light array directly.
69-
return i;
70+
if (u_RendererData.TilesCountX == 0)
71+
return i;
72+
73+
ivec2 tileID = ivec2(gl_FragCoord.xy) / LIGHT_CULLING_TILE_SIZE;
74+
tileID.x = clamp(tileID.x, 0, int(u_RendererData.TilesCountX) - 1);
75+
int tileIndex = tileID.y * int(u_RendererData.TilesCountX) + tileID.x;
76+
return s_VisiblePointLightIndicesBuffer.Indices[tileIndex * MAX_VISIBLE_LIGHTS_PER_TILE + i];
7077
}
7178

7279
int GetPointLightCount()
7380
{
74-
return int(u_PointLights.LightCount);
81+
if (u_RendererData.TilesCountX == 0)
82+
return int(u_PointLights.LightCount);
83+
84+
int result = 0;
85+
for (int i = 0; i < MAX_VISIBLE_LIGHTS_PER_TILE; i++)
86+
{
87+
if (GetPointLightBufferIndex(i) < 0)
88+
break;
89+
90+
result++;
91+
}
92+
93+
return result;
7594
}
7695

7796
vec3 CalculatePointLights(in vec3 F0, vec3 worldPos)
7897
{
7998
vec3 result = vec3(0.0);
80-
for (int i = 0; i < u_PointLights.LightCount; i++)
99+
for (int i = 0; i < GetPointLightCount(); i++)
81100
{
82-
uint lightIndex = GetPointLightBufferIndex(i);
83-
if (lightIndex == -1)
101+
int visibleIndex = GetPointLightBufferIndex(i);
102+
if (visibleIndex < 0)
84103
break;
85104

105+
uint lightIndex = uint(visibleIndex);
106+
86107
PointLight light = u_PointLights.Lights[lightIndex];
87108
vec3 Li = normalize(light.Position - worldPos);
88109
float lightDistance = length(light.Position - worldPos);
@@ -119,32 +140,50 @@ vec3 CalculatePointLights(in vec3 F0, vec3 worldPos)
119140

120141
int GetSpotLightBufferIndex(int i)
121142
{
122-
// Lux does not currently dispatch the Hazel-style light-culling pass, so
123-
// fall back to iterating the uploaded spot-light array directly.
124-
return i;
143+
if (u_RendererData.TilesCountX == 0)
144+
return i;
145+
146+
ivec2 tileID = ivec2(gl_FragCoord.xy) / LIGHT_CULLING_TILE_SIZE;
147+
tileID.x = clamp(tileID.x, 0, int(u_RendererData.TilesCountX) - 1);
148+
int tileIndex = tileID.y * int(u_RendererData.TilesCountX) + tileID.x;
149+
return s_VisibleSpotLightIndicesBuffer.Indices[tileIndex * MAX_VISIBLE_LIGHTS_PER_TILE + i];
125150
}
126151

127152

128153
int GetSpotLightCount()
129154
{
130-
return int(u_SpotLights.LightCount);
155+
if (u_RendererData.TilesCountX == 0)
156+
return int(u_SpotLights.LightCount);
157+
158+
int result = 0;
159+
for (int i = 0; i < MAX_VISIBLE_LIGHTS_PER_TILE; i++)
160+
{
161+
if (GetSpotLightBufferIndex(i) < 0)
162+
break;
163+
164+
result++;
165+
}
166+
167+
return result;
131168
}
132169

133170
vec3 CalculateSpotLights(in vec3 F0, vec3 worldPos)
134171
{
135172
vec3 result = vec3(0.0);
136-
for (int i = 0; i < u_SpotLights.LightCount; i++)
173+
for (int i = 0; i < GetSpotLightCount(); i++)
137174
{
138-
uint lightIndex = GetSpotLightBufferIndex(i);
139-
if (lightIndex == -1)
175+
int visibleIndex = GetSpotLightBufferIndex(i);
176+
if (visibleIndex < 0)
140177
break;
141178

179+
uint lightIndex = uint(visibleIndex);
180+
142181
SpotLight light = u_SpotLights.Lights[lightIndex];
143182
vec3 Li = normalize(light.Position - worldPos);
144183
float lightDistance = length(light.Position - worldPos);
145184

146185
float cutoff = cos(radians(light.Angle * 0.5f));
147-
float scos = max(dot(Li, light.Direction), cutoff);
186+
float scos = max(dot(-Li, normalize(light.Direction)), cutoff);
148187
float rim = (1.0 - scos) / (1.0 - cutoff);
149188

150189
float attenuation = clamp(1.0 - (lightDistance * lightDistance) / (light.Range * light.Range), 0.0, 1.0);

Editor/Resources/Shaders/Include/GLSL/ShadowMapping.glslh

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,28 +301,33 @@ float PCSS_SpotLight(texture2D shadowMap, vec2 atlasOffset, float atlasScale, ve
301301
vec3 CalculateSpotLightsShadowed(in vec3 F0, vec3 worldPos, texture2D SpotAtlas)
302302
{
303303
vec3 result = vec3(0.0);
304-
for (int i = 0; i < u_SpotLights.LightCount; i++)
304+
for (int i = 0; i < GetSpotLightCount(); i++)
305305
{
306-
uint lightIndex = GetSpotLightBufferIndex(i);
307-
if (lightIndex == -1)
306+
int visibleIndex = GetSpotLightBufferIndex(i);
307+
if (visibleIndex < 0)
308308
break;
309309

310+
uint lightIndex = uint(visibleIndex);
311+
310312
SpotLight light = u_SpotLights.Lights[lightIndex];
311313
vec3 Li = normalize(light.Position - worldPos);
312314
float lightDistance = length(light.Position - worldPos);
313315

314316
float cutoff = cos(radians(light.Angle * 0.5f));
315-
float scos = max(dot(Li, light.Direction), cutoff);
317+
float scos = max(dot(-Li, normalize(light.Direction)), cutoff);
316318
float rim = (1.0 - scos) / (1.0 - cutoff);
317319

318320
float attenuation = clamp(1.0 - (lightDistance * lightDistance) / (light.Range * light.Range), 0.0, 1.0);
319321
attenuation *= mix(attenuation, 1.0, light.Falloff);
320322
attenuation *= 1.0 - pow(max(rim, 0.001), light.AngleAttenuation);
321323

322324
// Shadow sampling - supports up to 16 spotlights with dynamic atlas
323-
if(light.CastsShadows && light.ShadowIndex < 16)
325+
if(light.CastsShadows && light.ShadowIndex < 16 && light.ShadowIndex < u_SpotLightMatrices.Count)
324326
{
325327
vec4 coords = u_SpotLightMatrices.Mats[light.ShadowIndex] * vec4(worldPos, 1.0f);
328+
if (coords.w <= 0.0f)
329+
continue;
330+
326331
vec3 shadowMapCoords = (coords.xyz / coords.w);
327332
// Flip Y to compensate for NVRHI viewport Y-axis inversion
328333
shadowMapCoords.xy = ShadowCoordsToUV(shadowMapCoords.xy);

0 commit comments

Comments
 (0)