WebGPU RT: port Sponza to wavefront (shadow ray in SHADE)

Restructure Sponza for the wavefront model: raygen emits the primary ray;
closesthit (in SHADE) gathers albedo/normal, accumulates ambient, and
emits a shadow ray carrying the pending direct term; miss adds the sky
(primary) or the direct term (shadow miss). resolve.wgsl applies the same
Reinhard+gamma the megakernel raygen did inline. User bindings moved to
group 3 (groups 0..2 reserved). RTPass maxDepth=2.

Renders the atrium correctly through the wavefront pipeline (textures,
two-sided shading, sun+ambient, shadows, tonemap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-05-31 20:16:04 +00:00
commit 376e66aeed
6 changed files with 70 additions and 134 deletions

View file

@ -253,10 +253,11 @@ int main() {
DescriptorHeapWebGPU heap;
heap.Initialize(/*images*/ 2, /*buffers*/ 2, /*samplers*/ 2);
std::array<WebGPUShader, 3> shaders {{
std::array<WebGPUShader, 4> shaders {{
WebGPUShader(fs::path("raygen.wgsl"), "raygen_main", WebGPURTStage::Raygen),
WebGPUShader(fs::path("miss.wgsl"), "miss_main", WebGPURTStage::Miss),
WebGPUShader(fs::path("closesthit.wgsl"), "closesthit_main", WebGPURTStage::ClosestHit),
WebGPUShader(fs::path("resolve.wgsl"), "resolve_main", WebGPURTStage::Resolve),
}};
ShaderBindingTableWebGPU sbt;
sbt.Init(shaders);
@ -271,14 +272,15 @@ int main() {
{ .type = RTShaderGroupType::TrianglesHitGroup, .closestHitShader = 2 },
}};
// Three user bindings at @group(2):
// Three user bindings at @group(3) (the wavefront pipeline reserves
// groups 0..2 for WfParams / data heaps / indirect args):
// binding 0 — albedo texture_2d_array (one layer per material)
// binding 1 — sampler (linear clamp)
// binding 2 — Camera storage buffer (host-driven, updated per frame)
std::array<UICustomBinding, 3> bindings {{
{ .group = 2, .binding = 0, .kind = UICustomBindingKind::SampledTextureArray, ._pad = 0, .pushOffset = 0 },
{ .group = 2, .binding = 1, .kind = UICustomBindingKind::Sampler, ._pad = 0, .pushOffset = 0 },
{ .group = 2, .binding = 2, .kind = UICustomBindingKind::Buffer, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::SampledTextureArray, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 1, .kind = UICustomBindingKind::Sampler, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 2, .kind = UICustomBindingKind::Buffer, ._pad = 0, .pushOffset = 0 },
}};
PipelineRTWebGPU pipeline;
@ -367,6 +369,7 @@ int main() {
RTPass rtPass(&pipeline);
rtPass.handlesPtr = userHandles.data();
rtPass.handlesCount = static_cast<std::uint32_t>(userHandles.size());
rtPass.maxDepth = 2; // primary + shadow
window.passes.push_back(&rtPass);
// ── Free camera: WASD + mouse-delta look ───────────────────────────
@ -375,9 +378,10 @@ int main() {
// height, looking +X down the long axis (bbox: X[-1921..1800],
// Y[-126..1429], Z[-1183..1105]). The user can fine-tune from there.
struct CamState {
Vector<float, 3, 4> position{ -1500.0f, 200.0f, 0.0f };
float yaw = 0.0f; // radians, around world +Y
float pitch = 0.0f; // radians, +pitch looks up
// 3/4 view from a corner aimed at the atrium centre.
Vector<float, 3, 4> position{ -1400.0f, 700.0f, -600.0f };
float yaw = 0.405f; // radians, around world +Y
float pitch = -0.317f; // radians, +pitch looks up
} cam;
Input::Map inputMap;