Phase 4.2+7: grass blade rework + soft RT shadows + toping BLAS optimization

Grass blades:
- Leaf-shaped profile (4 sections: base→belly→taper→tip) instead of spiky triangles
- Wider blades (base 0.055-0.095), more spacing between blades (±0.07 scatter)
- Natural green texture (50,140,35 → 80,180,55) instead of neon lime
- Reduced warm shift and removed artificial saturation boost
- Side faces at 60% brightness (dark green) instead of 38% (near-black)

Soft RT shadows:
- 2 jittered shadow rays per pixel with IGN+Cranley-Patterson temporal variation
- 2.3° cone around sun direction for soft penumbra
- Gradual shadow factor (0-100%) instead of binary on/off

Performance:
- Toping BLAS removed from TLAS (23M+ tris caused massive ray traversal slowdown)
- Toping BLAS position/index buffer construction skipped entirely
- Shadow rays reduced from 4 to 2 (temporal accumulation compensates)
This commit is contained in:
Samuel Bouchet 2026-03-29 19:46:25 +02:00
parent 82307269e8
commit 3d0c4f2f80
8 changed files with 165 additions and 77 deletions

View file

@ -296,6 +296,25 @@ PSOutput main(PSInput input)
float hemiLerp = N.y * 0.5 + 0.5; // 0=down, 1=up
float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, hemiLerp);
float3 diffuse = sunColor.rgb * NdotL;
// Grass-specific shading (Wonderbox style)
bool isGrass = (texIndex == 0); // material 1 = grass = texture layer 0
if (isGrass) {
// Vertical face darkening: grass sides are darker green (not black)
float verticalDarken = saturate(abs(N.y)); // 1=top, 0=side
float sideFactor = lerp(0.60, 1.0, verticalDarken); // sides at 60% brightness
albedo *= sideFactor;
// Subtle warm shift: sunlit grass slightly warmer
if (NdotL > 0.0) {
float3 warmShift = float3(0.08, 0.05, -0.03) * NdotL;
diffuse += warmShift;
}
// Boost ambient for grass: inter-reflection from dense foliage
ambient *= 1.15;
}
float3 color = albedo * (ambient + diffuse);
// ── Rim light ──

View file

@ -101,7 +101,7 @@ void main(uint3 DTid : SV_DispatchThreadID) {
float3 N = normalTexture[DTid.xy].xyz;
float3 origin = worldPos + N * push.normalBias;
// ── Shadow ray toward sun ──────────────────────────────────
// ── Soft shadow: multiple jittered rays toward sun ─────────
float3 L = normalize(-sunDirection.xyz);
float NdotL = dot(N, L);
@ -109,9 +109,31 @@ void main(uint3 DTid : SV_DispatchThreadID) {
if (NdotL <= 0.0) {
shadowFactor = 0.45; // back-facing = fully in shadow
} else {
// Build basis around sun direction for jitter cone
float3 sunT, sunB;
buildBasis(L, sunT, sunB);
// 2 shadow rays with IGN-based jitter (soft penumbra, temporally accumulated)
const uint shadowRays = 2;
const float coneAngle = 0.04; // ~2.3° cone = soft sun
float shadowHits = 0;
float ignBase = interleavedGradientNoise(float2(DTid.xy));
float frameRot = float(push.frameIndex) * GOLDEN_RATIO;
[loop]
for (uint si = 0; si < shadowRays; si++) {
// Per-ray noise with temporal variation
float xi1 = frac(ignBase + frameRot + float(si) * GOLDEN_RATIO);
float xi2 = frac(ignBase * 1.7 + frameRot * 0.7 + float(si) * 0.3819);
// Uniform disk → cone direction
float r = sqrt(xi1) * coneAngle;
float phi = 6.28318530718 * xi2;
float3 jitteredL = normalize(L + r * cos(phi) * sunT + r * sin(phi) * sunB);
RayDesc ray;
ray.Origin = origin;
ray.Direction = L;
ray.Direction = jitteredL;
ray.TMin = 0.01;
ray.TMax = push.shadowMaxDist;
@ -120,10 +142,14 @@ void main(uint3 DTid : SV_DispatchThreadID) {
[loop] while (q.Proceed()) {}
if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) {
shadowFactor = 0.45;
shadowHits += 1.0;
}
}
float shadowAmount = shadowHits / float(shadowRays); // 0=fully lit, 1=fully shadowed
shadowFactor = lerp(1.0, 0.45, shadowAmount);
}
// ── AO: hemisphere rays with IGN + temporal rotation ──────
float aoFactor = 1.0;
uint rayCount = push.aoRayCount;

View file

@ -1,6 +1,6 @@
// BVLE Voxels - Toping Pixel Shader (Phase 4.2)
// Simplified version of voxelPS: triplanar texture sampling + basic lighting.
// No height-based blending (topings are small decorative meshes).
// Vegetation: vertical gradient + up-biased lighting for stable grass look.
// Stone: classic Lambert + hemisphere ambient.
#include "voxelCommon.hlsli"
@ -11,6 +11,7 @@ struct PSInput {
float4 position : SV_POSITION;
float3 worldPos : WORLDPOS;
float3 normal : NORMAL;
float localHeight : LOCALHEIGHT;
nointerpolation uint materialID : MATERIALID;
};
@ -39,48 +40,51 @@ PSOutput main(PSInput input) {
float3 texColor = (xSample.rgb * blend.x + ySample.rgb * blend.y + zSample.rgb * blend.z);
float3 L = normalize(-sunDirection.xyz);
float rawNdotL = dot(N, L);
// ── Material-dependent lighting ─────────────────────────────
// Stone (materialID=3): standard diffuse like voxel faces
// Vegetation (grass etc.): stylized wrap lighting + translucency
// inspired by Airborn Trees (simonschreibt.de/gat/airborn-trees/)
float3 lit;
float hemiLerp = N.y * 0.5 + 0.5;
float3 V = normalize(cameraPosition.xyz - input.worldPos);
if (input.materialID == 3u) {
// Stone: classic Lambert + hemisphere ambient
float NdotL = max(rawNdotL, 0.0);
float NdotL = max(dot(N, L), 0.0);
float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, hemiLerp);
lit = texColor * (sunColor.rgb * NdotL + ambient);
} else {
// ── Vegetation: soft wrap lighting ──────────────────────
// Half-Lambert: wraps light around the surface, no hard terminator
float halfLambert = rawNdotL * 0.5 + 0.5;
float wrap = halfLambert * halfLambert; // squared for falloff shape
// ── Vegetation shading ──────────────────────────────────
// Diffuse floor: even fully back-facing blades get some sun contribution
// Simulates light scattering through dense grass (cheap GI approximation)
wrap = max(wrap, 0.35);
// Vertical gradient: dark green at base → lighter at tip
float h = input.localHeight; // 0=base, ~1=tip
float3 baseColor = texColor * 0.55; // darker at roots
float3 tipColor = texColor * 1.15; // brighter at tips
float3 grassColor = lerp(baseColor, tipColor, h);
// Translucency: thin blades let light through from behind
// Stronger effect to reduce contrast when orbiting around grass
// Up-biased normal for stable lighting on thin geometry
// Blend between actual normal and UP vector based on how "grassy" (non-stone)
float3 lightN = normalize(lerp(N, float3(0, 1, 0), 0.65));
float NdotL = dot(lightN, L);
// Soft wrap lighting (half-Lambert)
float halfLambert = NdotL * 0.5 + 0.5;
float wrap = halfLambert * halfLambert;
// Translucency: light passing through thin blades from behind
float backLight = saturate(dot(V, L));
float transAmount = (1.0 - saturate(rawNdotL)) * 0.6;
float transAmount = (1.0 - saturate(NdotL)) * 0.5;
float translucency = backLight * transAmount;
// Hemisphere ambient for vegetation, scaled up 1.5x for inter-reflection
float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, hemiLerp) * 1.5;
// Hemisphere ambient, boosted for vegetation inter-reflection
float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, lightN.y * 0.5 + 0.5) * 1.3;
// Wrap + translucency for soft, low-contrast vegetation shading
float3 diffuse = sunColor.rgb * (wrap * 0.88 + translucency * 0.55);
lit = texColor * (diffuse + ambient);
// Combine
float3 diffuse = sunColor.rgb * (wrap * 0.85 + translucency * 0.35);
lit = grassColor * (diffuse + ambient);
}
// ── Rim light (reduced on vegetation — thin geometry causes halos) ──
// ── Rim light (reduced on vegetation) ──
float NdotV = saturate(dot(N, V));
float rimScale = (input.materialID == 3u) ? 1.0 : 0.3; // stone full, grass 30%
float rimScale = (input.materialID == 3u) ? 1.0 : 0.2;
float rim = pow(1.0 - NdotV, rimParams.x) * rimParams.y * rimScale;
lit += rimColor.rgb * rim;

View file

@ -34,6 +34,7 @@ struct VSOutput {
float4 position : SV_POSITION;
float3 worldPos : WORLDPOS;
float3 normal : NORMAL;
float localHeight : LOCALHEIGHT; // height above voxel face (0=base, 1=tip)
nointerpolation uint materialID : MATERIALID;
};
@ -66,6 +67,7 @@ VSOutput main(uint vertexID : SV_VertexID, uint instanceID : SV_InstanceID) {
output.position = mul(viewProjection, float4(worldPos, 1.0));
output.worldPos = worldPos;
output.normal = vtx.normal;
output.localHeight = saturate((vtx.position.y - 1.0) * 5.0); // normalized: 0=base, 1=tip (blades ~0.06-0.24 tall)
output.materialID = push.materialID;
return output;
}

View file

@ -199,10 +199,9 @@ int APIENTRY wWinMain(
if (renderPath.screenshotMode) {
struct CamView { float x, y, z, pitch, yaw; const char* name; };
static const CamView views[] = {
{ 270.f, 48.f, 240.f, -0.30f, 0.6f, "landscape" }, // wide terrain, slight down
{ 272.f, 44.f, 248.f, -0.20f, 1.0f, "sideview" }, // side angle, ground level
{ 268.f, 55.f, 242.f, -0.70f, 0.5f, "topdown" }, // steep top-down
{ 275.f, 46.f, 235.f, -0.15f, 2.8f, "backlit" }, // looking toward sun
{ 223.f, 36.5f, 261.f, -0.20f, 0.7f, "closeup" }, // close-up: slightly above grass, looking across
{ 222.5f, 36.2f, 261.f, -0.10f, 0.5f,"blade" }, // eye-level with grass blades
{ 220.f, 39.f, 258.f, -0.35f, 0.7f, "medium" }, // medium shot of grass patch
};
static const int numViews = sizeof(views) / sizeof(views[0]);
static int currentView = 0;

View file

@ -98,9 +98,10 @@ static float hashF(int a, int b, int c) {
return x - floorf(x);
}
// ── Emit a single grass blade (2 segments, double-sided) ────────
// The blade is a tapered ribbon curving outward from the voxel face.
// 2 segments give curvature; double-sided for backface-culled PSO.
// ── Emit a single grass blade (3 segments, double-sided) ────────
// The blade is a wide, leaf-shaped ribbon with rounded tip.
// 4 cross-sections: wide base → widest at 1/3 height → taper → rounded tip.
// Double-sided for backface-culled PSO.
//
// midLeanRatio controls the curvature profile:
// low (0.05-0.15): mostly straight bottom, sharp curve at top
@ -122,15 +123,18 @@ static void emitBlade(std::vector<TopingVertex>& verts,
// Width direction (perpendicular to lean in XZ plane)
float wx = -lz, wz = lx;
// 3 cross-sections with variable curvature
// 4 cross-sections: leaf shape (wide base, widest at 1/3, taper, round tip)
float leanMid1 = lean * midLeanRatio * 0.5f;
float leanMid2 = lean * (midLeanRatio + (1.0f - midLeanRatio) * 0.5f);
struct Section { float x, y, z, hw; };
Section secs[3] = {
{ px, 1.0f, pz, baseW * 0.50f },
{ px + lean * midLeanRatio * lx, 1.0f + height * 0.5f, pz + lean * midLeanRatio * lz, baseW * 0.32f },
{ px + lean * lx, 1.0f + height, pz + lean * lz, baseW * 0.08f },
Section secs[4] = {
{ px, 1.0f, pz, baseW * 0.50f }, // base
{ px + leanMid1 * lx, 1.0f + height * 0.30f, pz + leanMid1 * lz, baseW * 0.55f }, // widest (leaf belly)
{ px + leanMid2 * lx, 1.0f + height * 0.70f, pz + leanMid2 * lz, baseW * 0.30f }, // taper
{ px + lean * lx, 1.0f + height, pz + lean * lz, baseW * 0.10f }, // rounded tip
};
for (int s = 0; s < 2; s++) {
for (int s = 0; s < 3; s++) {
const auto& s0 = secs[s];
const auto& s1 = secs[s + 1];
@ -350,10 +354,10 @@ void TopingSystem::generateGrassVariant(TopingDef& def, uint8_t bitmask) {
// Tuft count per edge (more open edges → fewer tufts each)
int tuftsPerEdge;
switch (openEdges) {
case 1: tuftsPerEdge = 7; break;
case 2: tuftsPerEdge = 5; break;
case 3: tuftsPerEdge = 4; break;
default: tuftsPerEdge = 4; break;
case 1: tuftsPerEdge = 9; break;
case 2: tuftsPerEdge = 7; break;
case 3: tuftsPerEdge = 5; break;
default: tuftsPerEdge = 5; break;
}
// ── 1. Tufts along open edges ───────────────────────────────
@ -379,9 +383,9 @@ void TopingSystem::generateGrassVariant(TopingDef& def, uint8_t bitmask) {
float tuftCenterZ = e.sz + t * dz + tuftInset * e.iz;
// ── Per-tuft personality ─────────────────────────────
float tuftHeightScale = 0.20f + hashF(edge, ti, 77) * 0.80f;
float tuftLeanScale = 0.3f + hashF(edge, ti, 55) * 1.5f;
int bladesInTuft = 3 + (int)(hashF(edge, ti, 33) * 6.99f);
float tuftHeightScale = 0.40f + hashF(edge, ti, 77) * 0.60f;
float tuftLeanScale = 0.2f + hashF(edge, ti, 55) * 0.8f;
int bladesInTuft = 5 + (int)(hashF(edge, ti, 33) * 6.99f);
// Emit blades tightly clustered around tuft center
for (int bi = 0; bi < bladesInTuft; bi++) {
@ -399,17 +403,17 @@ void TopingSystem::generateGrassVariant(TopingDef& def, uint8_t bitmask) {
float angle = (fanT - 0.5f) * 2.0f * spreadAngle;
angle += (h1 - 0.5f) * 20.0f;
// Height: per-blade × per-tuft
float bladeHeight = (0.09f + h2 * 0.42f) * tuftHeightScale; // +50% height
// Height: short leaf-like blades
float bladeHeight = (0.08f + h2 * 0.20f) * tuftHeightScale;
float baseWidth = 0.030f + h3 * 0.030f;
float lean = (0.03f + h4 * 0.12f) * tuftLeanScale;
float baseWidth = 0.055f + h3 * 0.040f; // wider blades
float lean = (0.02f + h4 * 0.08f) * tuftLeanScale;
float midLean = 0.08f + h5 * 0.35f;
// Tight scatter around tuft center (±0.03 — clustered)
// Spread blades within tuft (±0.07 — clearly separated)
float h6 = hashF(edge + 8, ti, bi * 3 + 2);
float offX = (h1 - 0.5f) * 0.06f;
float offZ = (h6 - 0.5f) * 0.06f;
float offX = (h1 - 0.5f) * 0.14f;
float offZ = (h6 - 0.5f) * 0.14f;
float bx = tuftCenterX + offX;
float bz = tuftCenterZ + offZ;
@ -436,14 +440,14 @@ void TopingSystem::generateGrassVariant(TopingDef& def, uint8_t bitmask) {
if (diagLen > 1e-6f) { diagX /= diagLen; diagZ /= diagLen; }
// Corner tuft: cluster filling the diagonal gap
int cornerBlades = 4 + (int)(hashF(c + 10, 0, 33) * 5.99f);
int cornerBlades = 5 + (int)(hashF(c + 10, 0, 33) * 6.99f);
// Corner tuft inset: 0.05 to 0.35 diagonally inward
float cDistHash = hashF(c + 10, 0, 44);
float cornerInset = 0.05f + cDistHash * 0.30f;
float cbx = corner.cx + cornerInset * (eA.ix + eB.ix);
float cbz = corner.cz + cornerInset * (eA.iz + eB.iz);
float cornerHeightScale = 0.25f + hashF(c + 10, 0, 77) * 0.75f;
float cornerHeightScale = 0.40f + hashF(c + 10, 0, 77) * 0.60f;
float cornerLeanScale = 0.3f + hashF(c + 10, 0, 55) * 1.5f;
for (int bi = 0; bi < cornerBlades; bi++) {
@ -458,9 +462,9 @@ void TopingSystem::generateGrassVariant(TopingDef& def, uint8_t bitmask) {
float angle = (fanT - 0.5f) * 2.0f * 70.0f;
angle += (h1 - 0.5f) * 15.0f;
float height = (0.15f + h2 * 0.33f) * cornerHeightScale; // +50% height
float baseWidth = 0.030f + h3 * 0.025f;
float lean = (0.04f + h4 * 0.10f) * cornerLeanScale;
float height = (0.08f + h2 * 0.20f) * cornerHeightScale;
float baseWidth = 0.055f + h3 * 0.040f;
float lean = (0.02f + h4 * 0.06f) * cornerLeanScale;
float midLean = 0.10f + h5 * 0.30f;
// Tight scatter around corner tuft center (clustered)

View file

@ -366,7 +366,7 @@ void VoxelRenderer::generateTextures() {
float heightContrast; // heightmap contrast (higher = more defined peaks)
};
MatColor colors[NUM_MATERIALS] = {
{ 60, 140, 40, 80, 180, 60, 101, 1.5f, 0.8f }, // 1: Grass: medium bumps
{ 50, 140, 35, 80, 180, 55, 101, 1.5f, 0.8f }, // 1: Grass: natural rich green
{ 100, 70, 40, 140, 100, 60, 202, 0.8f, 0.6f }, // 2: Dirt: smooth mounds
{ 80, 80, 90, 120, 120, 130, 303, 2.5f, 0.5f }, // 3: Stone (blocky): darker blue-gray
{ 220, 200, 130, 245, 230, 160, 404, 3.0f, 0.4f }, // 4: Sand: warmer yellow, fine
@ -1103,6 +1103,11 @@ void VoxelRenderer::buildAccelerationStructures(CommandList cmd) const {
rtSmoothVertexCount_ = smoothVertCount;
}
// ── Toping BLAS: SKIPPED ─────────────────────────────────────
// Topings generate 23M+ tris which massively slows ray traversal
// for negligible shadow contribution (blades too thin).
// Toping BLAS build and TLAS inclusion both disabled for performance.
// ── Memory barrier: sync BLAS builds before TLAS ──────────────
// Without this, TLAS build can execute before BLASes are complete.
// (Same pattern as wiRenderer.cpp line 5788)
@ -1111,7 +1116,9 @@ void VoxelRenderer::buildAccelerationStructures(CommandList cmd) const {
dev->Barrier(barriers, 1, cmd);
}
// ── TLAS (2 instances: blocky + smooth) ──────────────────────
// ── TLAS (2 instances: blocky + smooth) ─────────────────────
// Topings excluded from TLAS: 23M+ tris slows all ray traversal
// for negligible shadow contribution (blades too thin).
// Always recreate TLAS with pre-filled instance data via CreateBuffer2.
// RAY_TRACING instance buffers have special resource state requirements,
// so UpdateBuffer (CopyBufferRegion) would crash on state mismatch.
@ -1172,6 +1179,8 @@ void VoxelRenderer::buildAccelerationStructures(CommandList cmd) const {
dev->WriteTopLevelAccelerationStructureInstance(&inst, (uint8_t*)dest + idx * instSize);
idx++;
}
// Topings excluded from TLAS for performance (23M+ tris, negligible shadows)
};
bool ok = dev->CreateBuffer2(&bufdesc, initInstances, &desc.top_level.instance_buffer);
@ -1452,13 +1461,13 @@ void VoxelRenderer::render(
cb.windTime = windTime_;
// Stylized lighting (Phase 7) — Wonderbox-inspired
cb.skyAmbient = XMFLOAT4(0.50f, 0.55f, 0.65f, 0.0f); // cool sky fill
cb.groundAmbient = XMFLOAT4(0.28f, 0.22f, 0.15f, 0.0f); // warm brown ground
cb.groundAmbient = XMFLOAT4(0.25f, 0.24f, 0.14f, 0.0f); // warm green-brown ground bounce
cb.shadowTint = XMFLOAT4(0.50f, 0.42f, 0.70f, 0.0f); // purple-blue shadows
cb.fogColor = XMFLOAT4(0.78f, 0.73f, 0.60f, 1.0f); // warm sandy fog
cb.fogParams = XMFLOAT4(0.004f, 0.0f, 0.0f, 0.0f); // fog density
cb.rimColor = XMFLOAT4(0.90f, 0.80f, 0.55f, 0.0f); // warm golden rim
cb.rimParams = XMFLOAT4(2.5f, 0.45f, 0.0f, 0.0f); // exponent, intensity
cb.toneMapParams = XMFLOAT4(1.15f, 1.8f, 0.0f, 0.0f); // moderate saturation, good exposure
cb.toneMapParams = XMFLOAT4(1.10f, 1.8f, 0.0f, 0.0f); // natural saturation, balanced exposure
dev->UpdateBuffer(&constantBuffer_, &cb, cmd, sizeof(cb));
// Save current VP for next frame's temporal reprojection
XMStoreFloat4x4(&prevViewProjection_, vpMatrix);
@ -1948,6 +1957,22 @@ void VoxelRenderer::uploadTopingData(const TopingSystem& topingSystem) {
const auto& instances = topingSystem.getInstances();
if (instances.empty()) return;
// Log a few grass toping positions (one-time, for camera placement debug)
static bool loggedPositions = false;
if (!loggedPositions) {
loggedPositions = true;
int count = 0;
for (const auto& inst : instances) {
if (inst.topingType == 1 && count < 5) { // type 1 = grass
char msg[128];
snprintf(msg, sizeof(msg), "GRASS toping: pos=(%.0f, %.0f, %.0f) variant=%d",
inst.wx, inst.wy, inst.wz, inst.variant);
wi::backlog::post(msg);
count++;
}
}
}
// GPU instances are just float3 (12 bytes), sorted by (type, variant) for batched draws.
// We sort a copy and build a draw group table.
// Reuse persistent vectors to avoid per-frame allocations.
@ -1977,6 +2002,8 @@ void VoxelRenderer::uploadTopingData(const TopingSystem& topingSystem) {
ibDesc.stride = sizeof(TopingGPUInst);
ibDesc.usage = Usage::DEFAULT;
device_->CreateBuffer(&ibDesc, topingGpuInsts_.data(), &topingInstanceBuffer_);
// Toping BLAS: SKIPPED (23M+ tris slows RT for negligible shadow contribution)
}
void VoxelRenderer::renderTopings(
@ -2764,7 +2791,8 @@ void VoxelRenderPath::Compose(CommandList cmd) const {
if (renderer.isRTReady()) {
stats += "RT: TLAS ready | Blocky "
+ std::to_string(renderer.getRTBlockyTriCount()) + " tris | Smooth "
+ std::to_string(renderer.getRTSmoothTriCount()) + " tris"
+ std::to_string(renderer.getRTSmoothTriCount()) + " tris | Topings "
+ std::to_string(renderer.getRTTopingTriCount()) + " tris"
+ " | Shadows+AO " + std::string(renderer.rtShadowDebug_ == 1 ? "DBG_SHD" : (renderer.rtShadowDebug_ == 2 ? "DBG_AO" : (renderer.isRTShadowsEnabled() ? "ON" : "OFF"))) + "\n";
} else {
stats += "RT: building...\n";

View file

@ -206,12 +206,17 @@ private:
wi::graphics::GPUBuffer blasIndexBuffer_; // sequential uint32 indices [0,1,2,...] for BLAS
mutable wi::graphics::RaytracingAccelerationStructure blockyBLAS_;
mutable wi::graphics::RaytracingAccelerationStructure smoothBLAS_;
mutable wi::graphics::RaytracingAccelerationStructure topingBLAS_;
mutable wi::graphics::RaytracingAccelerationStructure tlas_;
mutable wi::graphics::GPUBuffer topingBLASPositionBuffer_; // float3[] world-space toping positions
mutable wi::graphics::GPUBuffer topingBLASIndexBuffer_; // sequential indices for toping BLAS
mutable uint32_t topingBLASIndexCount_ = 0; // size of toping index buffer
static constexpr uint32_t MAX_BLAS_VERTICES = MEGA_BUFFER_CAPACITY * 6; // 6 verts per quad
mutable bool rtAvailable_ = false; // GPU supports RT
mutable bool rtDirty_ = true; // BLAS/TLAS need rebuild
mutable uint32_t rtBlockyVertexCount_ = 0; // current blocky BLAS vertex count
mutable uint32_t rtSmoothVertexCount_ = 0; // current smooth BLAS vertex count
mutable uint32_t rtTopingVertexCount_ = 0; // current toping BLAS vertex count
void dispatchBLASExtract(wi::graphics::CommandList cmd) const;
void buildAccelerationStructures(wi::graphics::CommandList cmd) const;
@ -304,6 +309,7 @@ public:
bool isRTShadowsEnabled() const { return rtShadowsEnabled_; }
uint32_t getRTBlockyTriCount() const { return rtBlockyVertexCount_ / 3; }
uint32_t getRTSmoothTriCount() const { return rtSmoothVertexCount_ / 3; }
uint32_t getRTTopingTriCount() const { return rtTopingVertexCount_ / 3; }
const wi::graphics::RaytracingAccelerationStructure& getTLAS() const { return tlas_; }
};