From d5bf499375786c0f03b1f7d164952aa102fa31e2 Mon Sep 17 00:00:00 2001 From: Samuel Bouchet Date: Wed, 1 Apr 2026 18:12:58 +0200 Subject: [PATCH] Add debug tools --- shaders/voxelPS.hlsl | 54 +++++++++++++++++++++++++++++++------ shaders/voxelSmoothPS.hlsl | 48 ++++++++++++++++++++++++++++----- src/voxel/VoxelRenderer.cpp | 35 ++++++++++++++++++++---- src/voxel/VoxelRenderer.h | 5 ++++ 4 files changed, 123 insertions(+), 19 deletions(-) diff --git a/shaders/voxelPS.hlsl b/shaders/voxelPS.hlsl index ba70279..9be2527 100644 --- a/shaders/voxelPS.hlsl +++ b/shaders/voxelPS.hlsl @@ -106,7 +106,7 @@ float3 triplanarWeights(float3 normal, float sharpness) { // Triplanar sampling — RGB only (non-blended path) float3 sampleTriplanar(float3 worldPos, float3 normal, uint texIndex, float tiling) { float3 w = triplanarWeights(normal, 4.0); - float3 colX = materialTextures.Sample(materialSampler, float3(worldPos.yz * tiling, (float)texIndex)).rgb; + float3 colX = materialTextures.Sample(materialSampler, float3(worldPos.zy * tiling, (float)texIndex)).rgb; float3 colY = materialTextures.Sample(materialSampler, float3(worldPos.xz * tiling, (float)texIndex)).rgb; float3 colZ = materialTextures.Sample(materialSampler, float3(worldPos.xy * tiling, (float)texIndex)).rgb; return colX * w.x + colY * w.y + colZ * w.z; @@ -115,7 +115,7 @@ float3 sampleTriplanar(float3 worldPos, float3 normal, uint texIndex, float tili // Triplanar sampling — RGBA (includes heightmap in alpha) float4 sampleTriplanarRGBA(float3 worldPos, float3 normal, uint texIndex, float tiling) { float3 w = triplanarWeights(normal, 4.0); - float4 colX = materialTextures.Sample(materialSampler, float3(worldPos.yz * tiling, (float)texIndex)); + float4 colX = materialTextures.Sample(materialSampler, float3(worldPos.zy * tiling, (float)texIndex)); float4 colY = materialTextures.Sample(materialSampler, float3(worldPos.xz * tiling, (float)texIndex)); float4 colZ = materialTextures.Sample(materialSampler, float3(worldPos.xy * tiling, (float)texIndex)); return colX * w.x + colY * w.y + colZ * w.z; @@ -340,22 +340,27 @@ PSOutput main(PSInput input) } // ── Normal map perturbation ── - float3 perturbedN = sampleTriplanarNormal(input.worldPos, N, texIndex, tiling); - // Blend between flat and perturbed normal (strength control) - N = normalize(lerp(N, perturbedN, 0.7)); + float3 flatN = N; // preserve flat face normal for ambient + side-darkening + float nmStrength = toneMapParams.z; // 0 = off (F9 toggle) + if (nmStrength > 0.0) { + float3 perturbedN = sampleTriplanarNormal(input.worldPos, N, texIndex, tiling); + N = normalize(lerp(N, perturbedN, nmStrength)); + } // ── Lighting ── + // Use FLAT normal for hemisphere ambient + side-darkening (consistent per face) + // Use PERTURBED normal for NdotL only (organic detail variation) float3 L = normalize(-sunDirection.xyz); float NdotL = max(dot(N, L), 0.0); - float hemiLerp = N.y * 0.5 + 0.5; // 0=down, 1=up + float hemiLerp = flatN.y * 0.5 + 0.5; // flat: consistent per face orientation 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 + // Vertical face darkening: use FLAT normal for consistency + float verticalDarken = saturate(abs(flatN.y)); // 1=top, 0=side float sideFactor = lerp(0.60, 1.0, verticalDarken); // sides at 60% brightness albedo *= sideFactor; @@ -369,6 +374,39 @@ PSOutput main(PSInput input) ambient *= 1.15; } + // ── Debug lighting modes (F9 cycle) ── + uint dbgLight = (uint)toneMapParams.w; + if (dbgLight == 2) { + // FLAT: uniform color per face, no texture, no blend, no normal map + // Pure lighting with flat face normal. If two +X faces differ here, it's a VS/mesher bug. + float flatNdotL = max(dot(flatN, normalize(-sunDirection.xyz)), 0.0); + float flatHemi = flatN.y * 0.5 + 0.5; + float3 flatAmb = lerp(groundAmbient.rgb, skyAmbient.rgb, flatHemi); + float3 flatColor = float3(0.5, 0.5, 0.5) * (flatAmb + sunColor.rgb * flatNdotL); + output.color = float4(flatColor, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + if (dbgLight == 3) { + // ALBEDO only: texture + blend, no lighting + output.color = float4(albedo, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + if (dbgLight == 4) { + // NdotL only: grayscale NdotL with flat normal (no normal map) + float flatNdotL = max(dot(flatN, normalize(-sunDirection.xyz)), 0.0); + output.color = float4(flatNdotL, flatNdotL, flatNdotL, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + if (dbgLight == 5) { + // NORMAL viz: geometric normal mapped to RGB (XYZ → [0,1]) + output.color = float4(flatN * 0.5 + 0.5, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + float3 color = albedo * (ambient + diffuse); // ── Rim light ── diff --git a/shaders/voxelSmoothPS.hlsl b/shaders/voxelSmoothPS.hlsl index 2884e42..c411a0d 100644 --- a/shaders/voxelSmoothPS.hlsl +++ b/shaders/voxelSmoothPS.hlsl @@ -77,7 +77,7 @@ float3 triplanarWeights(float3 n, float sharpness) { float3 sampleTriplanar(float3 wp, float3 n, uint texIdx, float tiling) { float3 w = triplanarWeights(n, 4.0); - float3 cx = materialTextures.Sample(texSampler, float3(wp.yz * tiling, (float)texIdx)).rgb; + float3 cx = materialTextures.Sample(texSampler, float3(wp.zy * tiling, (float)texIdx)).rgb; float3 cy = materialTextures.Sample(texSampler, float3(wp.xz * tiling, (float)texIdx)).rgb; float3 cz = materialTextures.Sample(texSampler, float3(wp.xy * tiling, (float)texIdx)).rgb; return cx * w.x + cy * w.y + cz * w.z; @@ -85,7 +85,7 @@ float3 sampleTriplanar(float3 wp, float3 n, uint texIdx, float tiling) { float4 sampleTriplanarRGBA(float3 wp, float3 n, uint texIdx, float tiling) { float3 w = triplanarWeights(n, 4.0); - float4 cx = materialTextures.Sample(texSampler, float3(wp.yz * tiling, (float)texIdx)); + float4 cx = materialTextures.Sample(texSampler, float3(wp.zy * tiling, (float)texIdx)); float4 cy = materialTextures.Sample(texSampler, float3(wp.xz * tiling, (float)texIdx)); float4 cz = materialTextures.Sample(texSampler, float3(wp.xy * tiling, (float)texIdx)); return cx * w.x + cy * w.y + cz * w.z; @@ -249,13 +249,49 @@ PSOutput main(PSInput input) { } // ── Normal map perturbation ── - float3 perturbedN = sampleTriplanarNormal(input.worldPos, geoN, selfTexIdx, tiling); - N = normalize(lerp(N, perturbedN, 0.5)); // lighter strength on smooth surfaces + float3 flatN = N; // preserve for ambient + float nmStrength = toneMapParams.z; + if (nmStrength > 0.0) { + float3 perturbedN = sampleTriplanarNormal(input.worldPos, geoN, selfTexIdx, tiling); + N = normalize(lerp(N, perturbedN, nmStrength * 0.7)); // lighter on smooth + } - // Lighting + // ── Debug lighting modes (F9 cycle) ── + uint dbgLight = (uint)toneMapParams.w; + if (dbgLight == 2) { + // FLAT: uniform gray, no texture, no normal map — pure lighting with geometric normal + float flatNdotL = max(dot(flatN, normalize(-sunDirection.xyz)), 0.0); + float flatHemi = flatN.y * 0.5 + 0.5; + float3 flatAmb = lerp(groundAmbient.rgb, skyAmbient.rgb, flatHemi); + float3 flatColor = float3(0.5, 0.5, 0.5) * (flatAmb + sunColor.rgb * flatNdotL); + output.color = float4(flatColor, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + if (dbgLight == 3) { + // ALBEDO only: texture + blend, no lighting + output.color = float4(albedo, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + if (dbgLight == 4) { + // NdotL only: grayscale NdotL with geometric normal (no normal map) + float flatNdotL = max(dot(flatN, normalize(-sunDirection.xyz)), 0.0); + output.color = float4(flatNdotL, flatNdotL, flatNdotL, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + if (dbgLight == 5) { + // NORMAL viz: geometric normal mapped to RGB (XYZ → [0,1]) + output.color = float4(flatN * 0.5 + 0.5, 1.0); + output.normal = float4(flatN, 0.0); + return output; + } + + // Lighting: flat normal for ambient (consistent), perturbed for NdotL (detail) float3 L = normalize(-sunDirection.xyz); float NdotL = max(dot(N, L), 0.0); - float hemiLerp = N.y * 0.5 + 0.5; + float hemiLerp = flatN.y * 0.5 + 0.5; float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, hemiLerp); float3 color = albedo * (sunColor.rgb * NdotL + ambient); diff --git a/src/voxel/VoxelRenderer.cpp b/src/voxel/VoxelRenderer.cpp index a14972f..1ad7a69 100644 --- a/src/voxel/VoxelRenderer.cpp +++ b/src/voxel/VoxelRenderer.cpp @@ -785,7 +785,7 @@ void VoxelRenderer::render( 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.10f, 1.8f, 0.0f, 0.0f); // natural saturation, balanced exposure + cb.toneMapParams = XMFLOAT4(1.10f, 1.8f, normalStrength_, (float)debugLighting_); // saturation, exposure, normalStrength, debugLighting dev->UpdateBuffer(&constantBuffer_, &cb, cmd, sizeof(cb)); // Save current VP for next frame's temporal reprojection XMStoreFloat4x4(&rt_.prevViewProjection, vpMatrix); @@ -1519,6 +1519,18 @@ void VoxelRenderPath::Update(float dt) { anim_.showCrosshair = !anim_.showCrosshair; wi::backlog::post(anim_.showCrosshair ? "Crosshair + debug: ON" : "Crosshair + debug: OFF"); } + if (wi::input::Press(wi::input::KEYBOARD_BUTTON_F9)) { + anim_.debugLighting = (anim_.debugLighting + 1) % AnimationState::DEBUG_LIGHTING_MODES; + static const char* modeNames[] = { + "F9: ALL ON (normals+lighting+blend)", + "F9: Normal maps OFF", + "F9: FLAT lighting (no tex, no blend, no nmap)", + "F9: ALBEDO only (texture, no lighting)", + "F9: NdotL only (grayscale, flat normal)", + "F9: NORMAL viz (RGB = XYZ geometric normal)" + }; + wi::backlog::post(modeNames[anim_.debugLighting]); + } if (wi::input::Press(wi::input::KEYBOARD_BUTTON_F5)) { if (!renderer.rt_.isShadowsEnabled()) { renderer.rt_.setShadowsEnabled(true); renderer.rt_.setShadowDebug(0); @@ -1538,6 +1550,8 @@ void VoxelRenderPath::Update(float dt) { if (camera) camera_.handleInput(dt, camera); anim_.windTime += dt; renderer.windTime_ = anim_.windTime; + renderer.normalStrength_ = (anim_.debugLighting == 0) ? 0.7f : 0.0f; + renderer.debugLighting_ = anim_.debugLighting; // Sun direction: fixed or orbiting (F7) if (anim_.sunOrbit) { @@ -1757,8 +1771,8 @@ void VoxelRenderPath::Render() const { renderer.renderSmooth(cmd, voxelDepth_, voxelRT_, voxelNormalRT_); device->QueryEnd(&renderer.timestampHeap_, VoxelRenderer::TS_DRAW_END, cmd); - // Phase 6.2: RT Shadows + AO - if (renderer.isRTShadowsEnabled() && renderer.isRTReady()) { + // Phase 6.2: RT Shadows + AO (skip in F9 debug modes 2-4 for raw diagnostic output) + if (renderer.isRTShadowsEnabled() && renderer.isRTReady() && renderer.debugLighting_ < 2) { device->QueryEnd(&renderer.timestampHeap_, VoxelRenderer::TS_RT_SHADOWS_BEGIN, cmd); renderer.rt_.dispatchShadows(cmd, voxelDepth_, voxelRT_, voxelNormalRT_, renderer.constantBuffer_); @@ -1934,7 +1948,13 @@ void VoxelRenderPath::Compose(CommandList cmd) const { + "] | F4: dbg [" + std::string(renderer.debugBlend_ ? "ON" : "OFF") + "] | F5: shd+ao [" + std::string(renderer.rt_.getShadowDebug() == 1 ? "SHD" : (renderer.rt_.getShadowDebug() == 2 ? "AO" : (renderer.isRTShadowsEnabled() ? "ON" : "OFF"))) + "] | F7: sun [" + std::string(anim_.sunOrbit ? "ORBIT" : "FIXED") - + "] | F8: xhair [" + std::string(anim_.showCrosshair ? "ON" : "OFF") + "]"; + + "] | F8: xhair [" + std::string(anim_.showCrosshair ? "ON" : "OFF") + + "] | F9: " + std::string( + anim_.debugLighting == 0 ? "ALL" : + anim_.debugLighting == 1 ? "noNMAP" : + anim_.debugLighting == 2 ? "FLAT" : + anim_.debugLighting == 3 ? "ALBEDO" : + anim_.debugLighting == 4 ? "NdotL" : "NORMAL"); wi::font::Draw(stats, fp, cmd); @@ -2088,7 +2108,7 @@ std::string VoxelRenderPath::buildDebugLog() const { "FPS: %.1f (%.2f ms)\n" "Chunks: %u/%u Quads: %u GPU Mesh: %u\n" "Smooth verts: %u Toping instances: %zu\n" - "Animation: %s | Blend debug: %s | Sun orbit: %s | Crosshair: %s\n" + "Animation: %s | Blend debug: %s | Sun orbit: %s | Crosshair: %s | F9: %s\n" "RT available: %s | RT shadows+AO: %s | RT debug: %s\n" "Debug face mode: %s | Debug smooth: %s\n", smoothFps_, lastDt_ * 1000.0f, @@ -2099,6 +2119,11 @@ std::string VoxelRenderPath::buildDebugLog() const { renderer.debugBlend_ ? "ON" : "OFF", anim_.sunOrbit ? "ORBIT" : "FIXED", anim_.showCrosshair ? "ON" : "OFF", + anim_.debugLighting == 0 ? "ALL" : + anim_.debugLighting == 1 ? "noNMAP" : + anim_.debugLighting == 2 ? "FLAT" : + anim_.debugLighting == 3 ? "ALBEDO" : + anim_.debugLighting == 4 ? "NdotL" : "NORMAL", renderer.isRTAvailable() ? "yes" : "no", renderer.isRTShadowsEnabled() ? "ON" : "OFF", renderer.rt_.getShadowDebug() == 1 ? "SHADOWS" : diff --git a/src/voxel/VoxelRenderer.h b/src/voxel/VoxelRenderer.h index aa078a9..f965251 100644 --- a/src/voxel/VoxelRenderer.h +++ b/src/voxel/VoxelRenderer.h @@ -64,6 +64,8 @@ public: bool debugFaceColors_ = false; bool debugBlend_ = false; float windTime_ = 0.0f; // set by VoxelRenderPath::Update each frame + float normalStrength_ = 0.7f; // normal map strength (0=off) + int debugLighting_ = 0; // 0=all, 1=no nmap, 2=flat, 3=albedo, 4=NdotL XMFLOAT4 sunDirection_ = { -0.7f, -0.4f, -0.3f, 0.0f }; // set by VoxelRenderPath::Update private: @@ -303,6 +305,9 @@ struct AnimationState { bool terrainAnimated = false; // toggled with F3 bool sunOrbit = false; // toggled with F7: sun orbits in ~10s cycle bool showCrosshair = true; // toggled with F8: crosshair + face debug info + // F9 debug cycle: 0=all ON, 1=normals OFF, 2=flat lighting, 3=albedo only, 4=NdotL only, 5=normal viz + int debugLighting = 0; + static constexpr int DEBUG_LIGHTING_MODES = 6; float time = 0.0f; // current animation time offset float accum = 0.0f; // accumulator for 30 Hz timer static constexpr float INTERVAL = 1.0f / 30.0f; // ~33.3ms = 30 Hz