From 3d0c4f2f804773af3ec289b1e5a8609be527a327 Mon Sep 17 00:00:00 2001 From: Samuel Bouchet Date: Sun, 29 Mar 2026 19:46:25 +0200 Subject: [PATCH] Phase 4.2+7: grass blade rework + soft RT shadows + toping BLAS optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- shaders/voxelPS.hlsl | 19 ++++++++++++ shaders/voxelShadowCS.hlsl | 48 ++++++++++++++++++++++------- shaders/voxelTopingPS.hlsl | 54 +++++++++++++++++---------------- shaders/voxelTopingVS.hlsl | 10 ++++--- src/app/main.cpp | 7 ++--- src/voxel/TopingSystem.cpp | 60 ++++++++++++++++++++----------------- src/voxel/VoxelRenderer.cpp | 38 +++++++++++++++++++---- src/voxel/VoxelRenderer.h | 6 ++++ 8 files changed, 165 insertions(+), 77 deletions(-) diff --git a/shaders/voxelPS.hlsl b/shaders/voxelPS.hlsl index f5a014a..7654e58 100644 --- a/shaders/voxelPS.hlsl +++ b/shaders/voxelPS.hlsl @@ -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 ── diff --git a/shaders/voxelShadowCS.hlsl b/shaders/voxelShadowCS.hlsl index bb1f269..8ec3bde 100644 --- a/shaders/voxelShadowCS.hlsl +++ b/shaders/voxelShadowCS.hlsl @@ -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,19 +109,45 @@ void main(uint3 DTid : SV_DispatchThreadID) { if (NdotL <= 0.0) { shadowFactor = 0.45; // back-facing = fully in shadow } else { - RayDesc ray; - ray.Origin = origin; - ray.Direction = L; - ray.TMin = 0.01; - ray.TMax = push.shadowMaxDist; + // Build basis around sun direction for jitter cone + float3 sunT, sunB; + buildBasis(L, sunT, sunB); - RayQuery q; - q.TraceRayInline(tlas, 0, 0xFF, ray); - [loop] while (q.Proceed()) {} + // 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; - if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { - shadowFactor = 0.45; + [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 = jitteredL; + ray.TMin = 0.01; + ray.TMax = push.shadowMaxDist; + + RayQuery q; + q.TraceRayInline(tlas, 0, 0xFF, ray); + [loop] while (q.Proceed()) {} + + if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { + 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 ────── diff --git a/shaders/voxelTopingPS.hlsl b/shaders/voxelTopingPS.hlsl index e3733a7..50a1700 100644 --- a/shaders/voxelTopingPS.hlsl +++ b/shaders/voxelTopingPS.hlsl @@ -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; diff --git a/shaders/voxelTopingVS.hlsl b/shaders/voxelTopingVS.hlsl index b824bfb..338b8d6 100644 --- a/shaders/voxelTopingVS.hlsl +++ b/shaders/voxelTopingVS.hlsl @@ -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; }; @@ -63,9 +64,10 @@ VSOutput main(uint vertexID : SV_VertexID, uint instanceID : SV_InstanceID) { } VSOutput output; - output.position = mul(viewProjection, float4(worldPos, 1.0)); - output.worldPos = worldPos; - output.normal = vtx.normal; - output.materialID = push.materialID; + 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; } diff --git a/src/app/main.cpp b/src/app/main.cpp index 53ae25b..a11a9fd 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -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; diff --git a/src/voxel/TopingSystem.cpp b/src/voxel/TopingSystem.cpp index c907dc1..58cf8eb 100644 --- a/src/voxel/TopingSystem.cpp +++ b/src/voxel/TopingSystem.cpp @@ -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& 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) diff --git a/src/voxel/VoxelRenderer.cpp b/src/voxel/VoxelRenderer.cpp index a017b8e..6b004c7 100644 --- a/src/voxel/VoxelRenderer.cpp +++ b/src/voxel/VoxelRenderer.cpp @@ -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"; diff --git a/src/voxel/VoxelRenderer.h b/src/voxel/VoxelRenderer.h index 4fbe57b..a54c5da 100644 --- a/src/voxel/VoxelRenderer.h +++ b/src/voxel/VoxelRenderer.h @@ -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_; } };