From 0a33e3081c68ad84d082fe0a6ee33aca592a57c0 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 6 Apr 2026 19:36:30 -0700 Subject: [PATCH] fix(rendering): disable HiZ pyramid, fix WMO interior shadow clamping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HiZ occlusion culling built ~11 mip levels with per-level barriers behind a blocking vkWaitForFences every frame — the main frame-rate bottleneck. Disable the pyramid build and fall back to GPU frustum- only culling which is nearly free behind the fence. WMO interiors now receive full-strength directional shadows but clamp minimum brightness at 0.45 with a 0.35 ambient floor, so interiors get real shadow contrast without going too dark. --- assets/shaders/wmo.frag.glsl | 21 +++++++++---------- src/rendering/renderer.cpp | 40 +++++++++--------------------------- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/assets/shaders/wmo.frag.glsl b/assets/shaders/wmo.frag.glsl index e99d535d..ee9ac769 100644 --- a/assets/shaders/wmo.frag.glsl +++ b/assets/shaders/wmo.frag.glsl @@ -163,11 +163,11 @@ void main() { vec3 result; - // Sample shadow map — skip entirely for interior groups (flag 0x2000). - // Interior surfaces rely on pre-baked MOCV vertex-color lighting and the - // directional shadow map only makes them darker without any benefit. + // Sample shadow map for all groups. Interior groups receive attenuated + // shadow (30%) so they get subtle light/shadow variation without the full + // outdoor darkening that makes them look wrong. float shadow = 1.0; - if (isInterior == 0 && shadowParams.x > 0.5) { + if (shadowParams.x > 0.5) { vec3 ldir = normalize(-lightDir.xyz); float normalOffset = SHADOW_TEXEL * 2.0 * (1.0 - abs(dot(norm, ldir))); vec3 biasedPos = FragPos + norm * normalOffset; @@ -188,15 +188,14 @@ void main() { result = texColor.rgb * 1.5; } else if (isInterior != 0) { // WMO interior: vertex colors (MOCV) are pre-baked lighting from the artist. - // The MOHD ambient color tints/floors the vertex colors so dark spots don't - // go completely black, matching the WoW client's interior shading. - // We handle BOTH lit and unlit interior materials — directional - // sun shadows and lighting are skipped for all interior groups. + // The MOHD ambient color floors the vertex colors so dark spots don't go + // completely black. Full shadow strength is applied but clamped so + // interiors never go darker than a minimum brightness. vec3 wmoAmbient = vec3(wmoAmbientR, wmoAmbientG, wmoAmbientB); - // Clamp ambient to at least 0.3 to avoid total darkness when MOHD color is zero - wmoAmbient = max(wmoAmbient, vec3(0.3)); + wmoAmbient = max(wmoAmbient, vec3(0.35)); vec3 mocv = max(VertColor.rgb, wmoAmbient); - result = texColor.rgb * mocv; + float clampedShadow = max(shadow, 0.45); + result = texColor.rgb * mocv * clampedShadow; } else if (unlit != 0) { // Outdoor unlit surface — still receives directional shadows result = texColor.rgb * shadow; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index c76a86be..472e5cf9 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -894,29 +894,21 @@ void Renderer::beginFrame() { // Update per-frame UBO with current camera/lighting state updatePerFrameUBO(); - // ── Early compute: HiZ pyramid build + M2 frustum/occlusion cull ── - // These run in a SEPARATE command buffer submission so the GPU executes - // them immediately. The CPU then reads the fresh visibility results - // before recording the main render pass — eliminating the 2-frame - // staleness that occurs when compute + render share one submission. + // ── Early compute: M2 frustum culling ── + // GPU frustum cull keeps draw call counts low. The HiZ occlusion pyramid + // is skipped for now — building ~11 mip levels with per-level barriers + // behind a blocking fence was the main frame-rate bottleneck. Frustum- + // only culling is fast enough that the fence wait is negligible. if (m2Renderer && camera && vkCtx) { VkCommandBuffer computeCmd = vkCtx->beginSingleTimeCommands(); uint32_t frame = vkCtx->getCurrentFrame(); - // Build HiZ depth pyramid from previous frame's depth buffer - if (hizSystem_ && hizSystem_->isReady()) { - VkImage depthSrc = vkCtx->getDepthCopySourceImage(); - hizSystem_->buildPyramid(computeCmd, frame, depthSrc); - } - - // Dispatch GPU frustum + HiZ occlusion culling + // Dispatch GPU frustum culling (HiZ disabled → frustum-only pipeline) m2Renderer->dispatchCullCompute(computeCmd, frame, *camera); vkCtx->endSingleTimeCommands(computeCmd); - // Ensure GPU→CPU buffer writes are visible to host (non-coherent memory). m2Renderer->invalidateCullOutput(frame); - // Visibility results are now in cullOutputMapped_[frame], readable by CPU. } // --- Off-screen pre-passes --- @@ -1948,22 +1940,10 @@ bool Renderer::initializeRenderers(pipeline::AssetManager* assetManager, const s } } - // HiZ occlusion culling — temporal reprojection. - // The HiZ pyramid is built from the previous frame's depth buffer. The cull - // compute shader uses prevViewProj to project objects into the previous frame's - // screen space so that depth samples match the pyramid, eliminating flicker - // caused by camera movement between frames. - if (!hizSystem_ && m2Renderer && vkCtx) { - hizSystem_ = std::make_unique(); - auto extent = vkCtx->getSwapchainExtent(); - if (hizSystem_->initialize(vkCtx, extent.width, extent.height)) { - m2Renderer->setHiZSystem(hizSystem_.get()); - LOG_INFO("HiZ occlusion culling initialized (", extent.width, "x", extent.height, ")"); - } else { - LOG_WARNING("HiZ occlusion culling unavailable — falling back to frustum-only culling"); - hizSystem_.reset(); - } - } + // HiZ occlusion culling disabled — the pyramid build + blocking fence was + // the main frame-rate bottleneck. GPU frustum culling alone provides good + // draw-call reduction without the per-frame GPU stall. HiZ can be re- + // enabled once the pyramid build is moved to an async compute queue. if (!wmoRenderer) { wmoRenderer = std::make_unique(); if (!wmoRenderer->initialize(vkCtx, perFrameSetLayout, assetManager))