Skip to content

Commit 6f6aaae

Browse files
committed
Implement rasterization ray-query shadows and refine rendering pipelines:
- Add support for ray-query shadows in raster fragment shaders using TLAS. - Introduce frustum and LOD culling for planar reflections and TLAS. - Enhance reflection handling with adjustable intensity and refined tone mapping. - Improve material and instance masking for ray-query shadows. - Optimize descriptor binding updates, reducing redundancy. - Refactor draw queue logic to separate opaque and blended entities for efficiency. - Extend RayQuery uniforms to support soft shadows and reflection customization. - Introduce rendering shadows with ray query to all pipelines.
1 parent f6d85be commit 6f6aaae

13 files changed

+1081
-378
lines changed

attachments/simple_engine/mesh_component.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,90 @@
1717
#include "mesh_component.h"
1818
#include "model_loader.h"
1919
#include <cmath>
20+
#include <limits>
21+
22+
// Helper to transform an AABB by a matrix
23+
static void transformAABBLocal(const glm::mat4 &M,
24+
const glm::vec3 &localMin,
25+
const glm::vec3 &localMax,
26+
glm::vec3 &outMin,
27+
glm::vec3 &outMax)
28+
{
29+
const glm::vec3 c = 0.5f * (localMin + localMax);
30+
const glm::vec3 e = 0.5f * (localMax - localMin);
31+
32+
const glm::vec3 worldCenter = glm::vec3(M * glm::vec4(c, 1.0f));
33+
const glm::mat3 A = glm::mat3(M);
34+
const glm::mat3 AbsA = glm::mat3(glm::abs(A[0]), glm::abs(A[1]), glm::abs(A[2]));
35+
const glm::vec3 worldExtents = AbsA * e;
36+
37+
outMin = worldCenter - worldExtents;
38+
outMax = worldCenter + worldExtents;
39+
}
40+
41+
void MeshComponent::RecomputeMeshAABB()
42+
{
43+
if (meshAABBValid)
44+
return;
45+
46+
if (vertices.empty())
47+
{
48+
meshAABBMin = glm::vec3(0.0f);
49+
meshAABBMax = glm::vec3(0.0f);
50+
meshAABBValid = false;
51+
return;
52+
}
53+
glm::vec3 minB = vertices[0].position;
54+
glm::vec3 maxB = vertices[0].position;
55+
for (const auto &v : vertices)
56+
{
57+
minB = glm::min(minB, v.position);
58+
maxB = glm::max(maxB, v.position);
59+
}
60+
meshAABBMin = minB;
61+
meshAABBMax = maxB;
62+
meshAABBValid = true;
63+
}
64+
65+
void MeshComponent::RecomputeLocalAABB()
66+
{
67+
// First ensure base mesh AABB is up to date
68+
RecomputeMeshAABB();
69+
70+
if (!meshAABBValid)
71+
{
72+
localAABBMin = glm::vec3(0.0f);
73+
localAABBMax = glm::vec3(0.0f);
74+
localAABBValid = false;
75+
return;
76+
}
77+
78+
if (instances.empty())
79+
{
80+
// No instances: local AABB is just the mesh AABB
81+
localAABBMin = meshAABBMin;
82+
localAABBMax = meshAABBMax;
83+
localAABBValid = true;
84+
}
85+
else
86+
{
87+
// Union of all transformed instance AABBs
88+
glm::vec3 fullMin(std::numeric_limits<float>::max());
89+
glm::vec3 fullMax(-std::numeric_limits<float>::max());
90+
91+
for (const auto &inst : instances)
92+
{
93+
glm::vec3 instMin, instMax;
94+
transformAABBLocal(inst.modelMatrix, meshAABBMin, meshAABBMax, instMin, instMax);
95+
fullMin = glm::min(fullMin, instMin);
96+
fullMax = glm::max(fullMax, instMax);
97+
}
98+
99+
localAABBMin = fullMin;
100+
localAABBMax = fullMax;
101+
localAABBValid = true;
102+
}
103+
}
20104

21105
// Most of the MeshComponent class implementation is in the header file
22106
// This file is mainly for any methods that might need additional implementation

attachments/simple_engine/mesh_component.h

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <array>
2020
#include <glm/glm.hpp>
21+
#include <limits>
2122
#include <string>
2223
#include <vector>
2324

@@ -259,11 +260,16 @@ class MeshComponent final : public Component
259260
std::vector<Vertex> vertices;
260261
std::vector<uint32_t> indices;
261262

262-
// Cached local-space AABB
263+
// Cached local-space AABB (encompassing all instances)
263264
glm::vec3 localAABBMin{0.0f};
264265
glm::vec3 localAABBMax{0.0f};
265266
bool localAABBValid = false;
266267

268+
// Cached base mesh AABB (vertices only)
269+
glm::vec3 meshAABBMin{0.0f};
270+
glm::vec3 meshAABBMax{0.0f};
271+
bool meshAABBValid = false;
272+
267273
// All PBR texture paths for this mesh
268274
std::string texturePath; // Primary texture path (baseColor) - kept for backward compatibility
269275
std::string baseColorTexturePath; // Base color (albedo) texture
@@ -289,26 +295,8 @@ class MeshComponent final : public Component
289295
{}
290296

291297
// Local AABB utilities
292-
void RecomputeLocalAABB()
293-
{
294-
if (vertices.empty())
295-
{
296-
localAABBMin = glm::vec3(0.0f);
297-
localAABBMax = glm::vec3(0.0f);
298-
localAABBValid = false;
299-
return;
300-
}
301-
glm::vec3 minB = vertices[0].position;
302-
glm::vec3 maxB = vertices[0].position;
303-
for (const auto &v : vertices)
304-
{
305-
minB = glm::min(minB, v.position);
306-
maxB = glm::max(maxB, v.position);
307-
}
308-
localAABBMin = minB;
309-
localAABBMax = maxB;
310-
localAABBValid = true;
311-
}
298+
void RecomputeLocalAABB();
299+
void RecomputeMeshAABB();
312300
[[nodiscard]] bool HasLocalAABB() const
313301
{
314302
return localAABBValid;
@@ -321,14 +309,24 @@ class MeshComponent final : public Component
321309
{
322310
return localAABBMax;
323311
}
312+
[[nodiscard]] glm::vec3 GetBaseMeshAABBMin() const
313+
{
314+
return meshAABBMin;
315+
}
316+
[[nodiscard]] glm::vec3 GetBaseMeshAABBMax() const
317+
{
318+
return meshAABBMax;
319+
}
324320

325321
/**
326322
* @brief Set the vertices of the mesh.
327323
* @param newVertices The new vertices.
328324
*/
329325
void SetVertices(const std::vector<Vertex> &newVertices)
330326
{
331-
vertices = newVertices;
327+
vertices = newVertices;
328+
meshAABBValid = false;
329+
localAABBValid = false;
332330
RecomputeLocalAABB();
333331
}
334332

@@ -447,6 +445,7 @@ class MeshComponent final : public Component
447445
{
448446
instances.emplace_back(transform, materialIndex);
449447
isInstanced = instances.size() > 1;
448+
RecomputeLocalAABB();
450449
}
451450

452451
/**
@@ -457,6 +456,7 @@ class MeshComponent final : public Component
457456
{
458457
instances = newInstances;
459458
isInstanced = instances.size() > 1;
459+
RecomputeLocalAABB();
460460
}
461461

462462
/**
@@ -493,6 +493,7 @@ class MeshComponent final : public Component
493493
{
494494
instances.clear();
495495
isInstanced = false;
496+
RecomputeLocalAABB();
496497
}
497498

498499
/**
@@ -506,6 +507,7 @@ class MeshComponent final : public Component
506507
if (index < instances.size())
507508
{
508509
instances[index] = InstanceData(transform, materialIndex);
510+
RecomputeLocalAABB();
509511
}
510512
}
511513

attachments/simple_engine/renderer.h

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ struct LightData
9393
alignas(16) glm::vec4 position; // Light position (w component used for direction vs position)
9494
alignas(16) glm::vec4 color; // Light color and intensity
9595
alignas(16) glm::mat4 lightSpaceMatrix; // Light space matrix for shadow mapping
96+
alignas(16) glm::vec4 direction; // Light direction (for directional/spotlights)
9697
alignas(4) int lightType; // 0=Point, 1=Directional, 2=Spot, 3=Emissive
9798
alignas(4) float range; // Light range
9899
alignas(4) float innerConeAngle; // For spotlights
@@ -169,18 +170,23 @@ struct RayQueryUniformBufferObject
169170
alignas(4) int geometryInfoCount;
170171
alignas(4) int materialCount;
171172
alignas(4) int _pad0; // used for rayQueryMaxBounces
172-
// Thick-glass controls (RQ-only)
173-
alignas(4) int enableThickGlass; // 0/1 toggle
174-
alignas(4) float thicknessClamp; // max thickness in meters
175-
alignas(4) float absorptionScale; // scales sigma_a
176-
alignas(4) int _pad1; // reserved
177-
};
178-
179-
static_assert(sizeof(RayQueryUniformBufferObject) == 272, "RayQueryUniformBufferObject size must match shader layout");
180-
static_assert(offsetof(RayQueryUniformBufferObject, model) == 0);
181-
static_assert(offsetof(RayQueryUniformBufferObject, view) == 64);
182-
static_assert(offsetof(RayQueryUniformBufferObject, proj) == 128);
183-
static_assert(offsetof(RayQueryUniformBufferObject, camPos) == 192);
173+
// Thick-glass controls (RQ-only)
174+
alignas(4) int enableThickGlass; // 0/1 toggle
175+
alignas(4) float thicknessClamp; // max thickness in meters
176+
alignas(4) float absorptionScale; // scales sigma_a
177+
alignas(4) int _pad1; // Ray Query: enable hard shadows for direct lighting (0/1)
178+
// Ray Query soft shadows (area-light approximation)
179+
alignas(4) int shadowSampleCount; // 1 = hard shadows; >1 = multi-sample
180+
alignas(4) float shadowSoftness; // 0 = hard; otherwise scales effective light radius (fraction of range)
181+
alignas(4) float reflectionIntensity; // User control for glass reflection strength
182+
alignas(4) float _padShadow[2]{};
183+
};
184+
185+
static_assert(sizeof(RayQueryUniformBufferObject) == 288, "RayQueryUniformBufferObject size must match shader layout");
186+
static_assert(offsetof(RayQueryUniformBufferObject, model) == 0);
187+
static_assert(offsetof(RayQueryUniformBufferObject, view) == 64);
188+
static_assert(offsetof(RayQueryUniformBufferObject, proj) == 128);
189+
static_assert(offsetof(RayQueryUniformBufferObject, camPos) == 192);
184190
static_assert(offsetof(RayQueryUniformBufferObject, exposure) == 208);
185191
static_assert(offsetof(RayQueryUniformBufferObject, gamma) == 212);
186192
static_assert(offsetof(RayQueryUniformBufferObject, scaleIBLAmbient) == 216);
@@ -195,6 +201,8 @@ static_assert(offsetof(RayQueryUniformBufferObject, enableThickGlass) == 252);
195201
static_assert(offsetof(RayQueryUniformBufferObject, thicknessClamp) == 256);
196202
static_assert(offsetof(RayQueryUniformBufferObject, absorptionScale) == 260);
197203
static_assert(offsetof(RayQueryUniformBufferObject, _pad1) == 264);
204+
static_assert(offsetof(RayQueryUniformBufferObject, shadowSampleCount) == 268);
205+
static_assert(offsetof(RayQueryUniformBufferObject, shadowSoftness) == 272);
198206

199207
/**
200208
* @brief Structure for PBR material properties.
@@ -891,7 +899,7 @@ class Renderer
891899
bool buildAccelerationStructures(const std::vector<Entity *> &entities);
892900

893901
// Refit/UPDATE the TLAS with latest entity transforms (no rebuild)
894-
bool refitTopLevelAS(const std::vector<Entity *> &entities);
902+
bool refitTopLevelAS(const std::vector<Entity *> &entities, CameraComponent *camera);
895903

896904
/**
897905
* @brief Update ray query descriptor sets with current resources.
@@ -1003,9 +1011,15 @@ class Renderer
10031011
float gamma = 2.2f; // Gamma correction value
10041012
float exposure = 1.2f; // HDR exposure value (default tuned to avoid washout)
10051013
float reflectionIntensity = 1.0f; // User control for glass reflection strength
1014+
// Raster shadows (experimental): use ray queries in the raster PBR fragment shader.
1015+
// Wired through `UniformBufferObject.padding2` to avoid UBO layout churn.
1016+
bool enableRasterRayQueryShadows = false;
10061017

10071018
// Ray Query tuning
1008-
int rayQueryMaxBounces = 1; // 0 = no secondary rays, 1 = one-bounce reflection/refraction
1019+
int rayQueryMaxBounces = 1; // 0 = no secondary rays, 1 = one-bounce reflection/refraction
1020+
bool enableRayQueryShadows = true; // Hard shadows for Ray Query direct lighting (shadow rays)
1021+
int rayQueryShadowSampleCount = 1; // 1 = hard; >1 enables soft-shadow sampling in the shader
1022+
float rayQueryShadowSoftness = 0.0f; // 0 = hard; otherwise scales effective light radius (fraction of range)
10091023
// Thick-glass controls (RQ-only)
10101024
bool enableThickGlass = true;
10111025
float thickGlassAbsorptionScale = 1.0f;
@@ -1563,6 +1577,13 @@ class Renderer
15631577
std::vector<bool> pbrImagesWritten; // size = MAX_FRAMES_IN_FLIGHT
15641578
std::vector<bool> basicImagesWritten; // size = MAX_FRAMES_IN_FLIGHT
15651579

1580+
// Tracks whether the remaining required bindings in the PBR set 0 layout have
1581+
// been written at least once for each frame.
1582+
// This includes bindings like Forward+ tile buffers (7/8), reflection sampler (10),
1583+
// and TLAS (11). These bindings are required by the pipeline layout and must be
1584+
// valid before any draw that uses the PBR/glass pipelines.
1585+
std::vector<bool> pbrFixedBindingsWritten; // size = MAX_FRAMES_IN_FLIGHT
1586+
15661587
// Cached material lookup/classification for raster rendering.
15671588
// Avoids per-frame string parsing of entity names ("_Material_") and repeated
15681589
// ModelLoader material lookups across culling, sorting, and draw loops.

attachments/simple_engine/renderer_pipelines.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,19 @@ bool Renderer::createPBRDescriptorSetLayout()
159159
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
160160
.descriptorCount = 1,
161161
.stageFlags = vk::ShaderStageFlagBits::eFragment,
162+
.pImmutableSamplers = nullptr},
163+
// Binding 11: TLAS (ray-query shadows in raster fragment shader)
164+
vk::DescriptorSetLayoutBinding{
165+
.binding = 11,
166+
.descriptorType = vk::DescriptorType::eAccelerationStructureKHR,
167+
.descriptorCount = 1,
168+
.stageFlags = vk::ShaderStageFlagBits::eFragment,
162169
.pImmutableSamplers = nullptr}};
163170

164171
// Create a descriptor set layout
165172
// Descriptor indexing: set per-binding flags for UPDATE_AFTER_BIND on UBO (0) and sampled images (1..5)
166173
vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsInfo{};
167-
std::array<vk::DescriptorBindingFlags, 11> bindingFlags{};
174+
std::array<vk::DescriptorBindingFlags, 12> bindingFlags{};
168175
if (descriptorIndexingEnabled)
169176
{
170177
bindingFlags[0] = vk::DescriptorBindingFlagBits::eUpdateAfterBind | vk::DescriptorBindingFlagBits::eUpdateUnusedWhilePending;

0 commit comments

Comments
 (0)