From 4d3963e2918c406dc3652951612e33f7854639d6 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Thu, 24 Sep 2026 01:49:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=98=B4=E5=BD=B1=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E8=A7=81=E2=80=94=E2=80=94=E7=81=AF=E5=BA=8F=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=81=8D=E5=8E=86=E9=A1=BA=E5=BA=8F=20+=20=E5=85=89=E6=AF=94?= =?UTF-8?q?=E5=A4=B1=E8=A1=A1=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=B8=A7=E6=88=AA?= =?UTF-8?q?=E5=9B=BE=E8=B0=83=E8=AF=95=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 灯序修复(根因):entt Each 遍历序不保证创建序,lights[0] 曾落到 点光(params.x=1)→ shader 阴影分支整个跳过。收集后 stable_sort 方向光排前,确立"lights[0] = CSM 阴影光源"契约 - 光比失衡:IBL 环境与点光淹没主光阴影。KeyLight 5.0 + exposure 默认 0.6 + ambientColor 0.07;阴影区环境光同步衰减(简化遮蔽) - ShadowPass depth bias 0.006(acne 均值形态 vs 推过头丢阴影的折中) - Renderer 帧截图能力:第 60 帧自动截 HDRColor/ShadowMap 落 BMP (ACES+gamma 还原屏幕观感),视觉调试自闭环不再依赖肉眼 - KeyLight 增加 intensity slider(0-20 运行时可调) --- App/main.cpp | 15 +- Engine/include/Engine/ECS/Scene.h | 2 +- .../include/Engine/RenderGraph/RenderGraph.h | 7 + Engine/src/RenderGraph/RenderGraph.cpp | 18 ++ Engine/src/Renderer/Passes/SceneGpu.cpp | 2 +- Engine/src/Renderer/Passes/ShadowPass.cpp | 5 +- Engine/src/Renderer/Renderer.cpp | 154 ++++++++++++++++++ Engine/src/Renderer/SceneRenderer.cpp | 40 +++-- 8 files changed, 218 insertions(+), 25 deletions(-) diff --git a/App/main.cpp b/App/main.cpp index 67df9fe..9fe1acf 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -51,9 +51,11 @@ namespace // 模型资产(PBR 验证场景:backpack 为旧式贴图资产, // 退化为电介质因子路径——albedo+normal,metallic=0/roughness=0.5) const auto model = GetAssets().LoadModel("assets/models/backpack/backpack.obj"); + // const auto model = GetAssets().LoadModel("assets/models/pocket_watch_4k.gltf/pocket_watch_4k.gltf"); m_Spinners.push_back(scene.CreateEntity("backpack")); m_Spinners[0].AddComponent(MeshRendererComponent{model}); + m_Spinners[0].GetComponent().SetScale(glm::vec3{1.0}); // 地面:程序化纯色平面(半边长 15,中心 y=-2) const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); @@ -76,7 +78,7 @@ namespace LightComponent{ .type = LightComponent::Type::Directional, .direction = glm::vec3(-0.5f, -0.6f, -0.35f), - .color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f) + .color = glm::vec4(1.0f, 0.95f, 0.9f, 5.0f) }); scene.CreateEntity("FillLight") @@ -84,7 +86,7 @@ namespace LightComponent{ .type = LightComponent::Type::Directional, .direction = glm::vec3(0.5f, -0.5f, 0.3f), - .color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f) + .color = glm::vec4(0.3f, 0.5f, 1.0f, 1.5f) }); // 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减) @@ -93,7 +95,7 @@ namespace fire.AddComponent( LightComponent{ .type = LightComponent::Type::Point, - .color = glm::vec4(1.0f, 0.6f, 0.3f, 2.0f), // 暖橙 + .color = glm::vec4(1.0f, 0.6f, 0.3f, 1.0f), // 暖橙 .radius = 8.0f, }); @@ -127,7 +129,6 @@ namespace { auto& t = m_Spinners[i].GetComponent(); t.SetRotation(glm::vec3(0.0f, m_Time * 60.0f + static_cast(i) * 40.0f, 0.0f)); - t.SetScale(glm::vec3(0.5f)); } } @@ -137,8 +138,6 @@ namespace void OnRenderOverlay() override { - ImGui::ShowDemoWindow(); - ImGui::Begin("Scene Debug"); // 阴影光源方向:拖动滑条实时看影子变化 @@ -147,9 +146,11 @@ namespace ImGui::Text("Key Light (shadow caster)"); ImGui::SliderFloat3("direction", &light->direction.x, -1.0f, 1.0f); + ImGui::SliderFloat("intensity", &light->color.a, 0.0f, 20.0f); if (ImGui::Button("reset")) { light->direction = glm::vec3(-0.5f, -0.6f, -0.35f); + light->color.a = 2.0f; } } @@ -302,7 +303,7 @@ namespace EntityHandle m_KeyLight{}; std::vector m_Spinners; f32 m_Time = 0.0f; - bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用) + bool m_PauseAnim = true; // 冻结动画时钟(帧历史 diff 验证用) }; } diff --git a/Engine/include/Engine/ECS/Scene.h b/Engine/include/Engine/ECS/Scene.h index 2d5e9de..c97fc95 100644 --- a/Engine/include/Engine/ECS/Scene.h +++ b/Engine/include/Engine/ECS/Scene.h @@ -41,7 +41,7 @@ namespace Engine::ECS { // ---- 场景级设置 ---- struct Settings { - glm::vec4 ambientColor{0.15f, 0.15f, 0.2f, 0.0f}; + glm::vec4 ambientColor{0.07f, 0.07f, 0.09f, 0.0f}; }; Settings& GetSettings() { return m_Settings; } diff --git a/Engine/include/Engine/RenderGraph/RenderGraph.h b/Engine/include/Engine/RenderGraph/RenderGraph.h index 17f89b1..934af56 100644 --- a/Engine/include/Engine/RenderGraph/RenderGraph.h +++ b/Engine/include/Engine/RenderGraph/RenderGraph.h @@ -210,6 +210,13 @@ namespace Engine::RG { // 注意:物理镜像按槽位各一份,槽位变化/重编译后必须重新获取。 vk::ImageView GetPhysicalView(TextureHandle h, u32 frameSlot) const; + // 调试/截图:取物理 image + 静息布局(帧尾 barrier 恢复用)。 + // imported 资源返回 false(无物理实例) + bool GetCaptureTarget(TextureHandle h, u32 frameSlot, + vk::Image& outImage, vk::Extent2D& outExtent, + vk::Format& outFormat, + vk::ImageLayout& outResting) const; + // 帧历史槽位 = 上一帧使用的槽位:(slot - 1 + F) mod F u32 HistorySlot(u32 frameSlot) const { return (frameSlot + m_FramesInFlight - 1) % m_FramesInFlight; diff --git a/Engine/src/RenderGraph/RenderGraph.cpp b/Engine/src/RenderGraph/RenderGraph.cpp index 6ead799..99ee192 100644 --- a/Engine/src/RenderGraph/RenderGraph.cpp +++ b/Engine/src/RenderGraph/RenderGraph.cpp @@ -694,6 +694,24 @@ namespace Engine::RG { return entry.physical[frameSlot]->view; } + bool RenderGraph::GetCaptureTarget(TextureHandle h, u32 frameSlot, + vk::Image& outImage, vk::Extent2D& outExtent, + vk::Format& outFormat, + vk::ImageLayout& outResting) const + { + const auto& entry = GetEntry(h); + if (entry.imported || frameSlot >= entry.physical.size() || + !entry.physical[frameSlot]) { + return false; + } + const auto& phys = *entry.physical[frameSlot]; + outImage = phys.image; + outExtent = phys.extent; + outFormat = phys.format; + outResting = entry.restingLayout; + return true; + } + void RenderGraph::SetQueryPool(vk::QueryPool pool, u32 queriesPerFrame) { m_QueryPool = pool; diff --git a/Engine/src/Renderer/Passes/SceneGpu.cpp b/Engine/src/Renderer/Passes/SceneGpu.cpp index 7edc676..8bcfa3b 100644 --- a/Engine/src/Renderer/Passes/SceneGpu.cpp +++ b/Engine/src/Renderer/Passes/SceneGpu.cpp @@ -116,7 +116,7 @@ namespace Engine::Passes .setBorderColor(vk::BorderColor::eFloatOpaqueWhite) .setUnnormalizedCoordinates(VK_FALSE) .setCompareEnable(VK_TRUE) - .setCompareOp(vk::CompareOp::eLess); + .setCompareOp(vk::CompareOp::eGreater); // 【诊断】方向对照 shadowSampler = device.GetDevice().createSampler(shadowSamplerInfo); // per-slot 描述符分配 + Set0 绑定(buffer 句柄全程稳定,不用重绑) diff --git a/Engine/src/Renderer/Passes/ShadowPass.cpp b/Engine/src/Renderer/Passes/ShadowPass.cpp index 2efb140..2e22030 100644 --- a/Engine/src/Renderer/Passes/ShadowPass.cpp +++ b/Engine/src/Renderer/Passes/ShadowPass.cpp @@ -50,9 +50,10 @@ namespace Engine::Passes cmd.setDepthBoundsTestEnable(VK_FALSE); cmd.setStencilTestEnable(VK_FALSE); - // 深度 bias 压平 shadow acne(NDC 量级) + // 深度 bias 压平 shadow acne(NDC 量级;0.01 ≈ 1m 世界深度, + // 过大会把跨物体遮挡差也推没 → 阴影整体消失) cmd.setDepthBiasEnable(VK_TRUE); - cmd.setDepthBias(0.0025f, 0.0f, 0.003f); + cmd.setDepthBias(0.006f, 0.0f, 0.006f); cmd.setPolygonModeEXT(vk::PolygonMode::eFill); cmd.setCullMode(vk::CullModeFlagBits::eNone); cmd.setFrontFace(vk::FrontFace::eCounterClockwise); diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 5b57e11..c6a7731 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -42,9 +42,83 @@ #include #include #include +#include +#include namespace Engine { + namespace + { + // fp16 → f32(截图用,规范路径足够) + f32 HalfToFloat(u16 h) + { + const u32 sign = (h & 0x8000u) << 16u; + const u32 exp = (h >> 10u) & 0x1Fu; + const u32 man = h & 0x3FFu; + u32 bits; + if (exp == 0) { + if (man == 0) return std::bit_cast(sign); + u32 e = 127 - 15 + 1; + u32 m = man; + while (!(m & 0x400u)) { m <<= 1u; --e; } + bits = sign | (e << 23u) | ((m & 0x3FFu) << 13u); + } else if (exp == 31) { + bits = sign | 0x7F800000u | (man << 13u); + } else { + bits = sign | ((exp - 15u + 127u) << 23u) | (man << 13u); + } + return std::bit_cast(bits); + } + + // RGBA16F 线性数据 → 24bpp BMP(行倒序;depthMode 读 f32 深度) + void WriteHdrBMP(const std::string& path, const void* data, + u32 w, u32 h, bool depthMode = false) + { + const auto* src = static_cast(data); + const u32 rowBytes = w * 3u; + const u32 pad = (4u - rowBytes % 4u) % 4u; + const u32 stride = rowBytes + pad; + const u32 size = 54u + stride * h; + + std::vector bmp(size, 0); + auto put = [&bmp](u32 off, u32 v, u32 n) { + for (u32 i = 0; i < n; ++i) + bmp[off + i] = static_cast((v >> (8u * i)) & 0xFFu); + }; + bmp[0] = 'B'; bmp[1] = 'M'; + put(2, size, 4); put(10, 54, 4); put(14, 40, 4); + put(18, w, 4); put(22, h, 4); put(26, 1, 2); put(28, 24, 2); + put(34, stride * h, 4); + + for (u32 y = 0; y < h; ++y) { + const u32 dstRow = h - 1 - y; // BMP 底行在前 + for (u32 x = 0; x < w; ++x) { + const u16* px = src + (static_cast(y) * w + x) * 4; + const u32 off = 54u + dstRow * stride + x * 3u; + for (u32 c = 0; c < 3; ++c) { + f32 v; + if (depthMode) { + // D32 buffer:f32 深度,反相显示(近=白) + v = 1.0f - reinterpret_cast( + data)[static_cast(y) * w + x]; + } else { + v = HalfToFloat(px[c]) * 0.6f; // 匹配默认 exposure + // ACES 近似(与 TonemapPass 同族),还原屏幕观感 + v = (v * (2.51f * v + 0.03f)) / + (v * (2.43f * v + 0.59f) + 0.14f); + v = glm::pow(glm::clamp(v, 0.0f, 1.0f), + 1.0f / 2.2f); + } + bmp[off + c] = static_cast(v * 255.0f); + } + } + } + + std::ofstream file(path, std::ios::binary); + file.write(reinterpret_cast(bmp.data()), + static_cast(bmp.size())); + } + } Renderer::Renderer(Window& window) : m_Window(window) { @@ -104,15 +178,19 @@ namespace Engine m_SceneGpu->textureSetLayout); // 8. RenderGraph:资源声明 + pass 注册 + 首次编译 + ENGINE_INFO("[diag] BuildGraph begin"); m_PostParams = CreateScope(); BuildGraph(); + ENGINE_INFO("[diag] BuildGraph done"); // 首次编译后物理镜像就位:各 pass 绑定自己的输入描述符 for (auto& pass : m_Passes) { pass->OnTargetsRebuilt(*m_RenderGraph); } + ENGINE_INFO("[diag] targets rebuilt"); // 环境辐照度绑定(view 全程稳定,绑一次) m_SceneGpu->BindEnvironment(*m_Environment); + ENGINE_INFO("[diag] environment bound"); // 9. GPU timestamp:每 pass 两枚(前后),按帧槽位分区 m_QueriesPerFrame = static_cast(m_RenderGraph->GetPassCount()) * 2; @@ -392,6 +470,68 @@ namespace Engine m_RenderGraph->Execute(frameCtx); + // 调试截图:帧尾把 TAAColor 拷进 host buffer(一次) + ++m_FrameCounter; + const bool doCapture = !m_Captured && m_FrameCounter == 60; + vk::Image capImage{}; + vk::Extent2D capExtent{}; + vk::Format capFormat{}; + vk::ImageLayout capResting{}; + if (doCapture && + m_RenderGraph->GetCaptureTarget(m_HDRColor, frameSlot, + capImage, capExtent, capFormat, capResting)) { + const vk::DeviceSize capBytes = static_cast( + capExtent.width) * capExtent.height * 8; // RGBA16F + const bool capDepth = capFormat == vk::Format::eD32Sfloat; + + m_CaptureAlloc = m_Allocator->CreateBuffer( + capBytes, + vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent); + + // resting → TRANSFER_SRC → 拷贝 → 恢复 resting + const vk::ImageAspectFlagBits capAspect = + capDepth ? vk::ImageAspectFlagBits::eDepth + : vk::ImageAspectFlagBits::eColor; + vk::ImageMemoryBarrier2 toSrc; + toSrc.setSrcStageMask(vk::PipelineStageFlagBits2::eAllCommands) + .setSrcAccessMask(vk::AccessFlagBits2::eMemoryWrite) + .setDstStageMask(vk::PipelineStageFlagBits2::eTransfer) + .setDstAccessMask(vk::AccessFlagBits2::eTransferRead) + .setOldLayout(capResting) + .setNewLayout(vk::ImageLayout::eTransferSrcOptimal) + .setImage(capImage) + .setSubresourceRange({capAspect, 0, 1, 0, 1}); + frameCtx.cmd.pipelineBarrier2( + vk::DependencyInfo{}.setImageMemoryBarriers(toSrc)); + + vk::BufferImageCopy capRegion; + capRegion.setBufferOffset(0) + .setBufferRowLength(0) + .setBufferImageHeight(0) + .setImageSubresource({capAspect, 0, 0, 1}) + .setImageOffset({0, 0, 0}) + .setImageExtent({capExtent.width, capExtent.height, 1}); + frameCtx.cmd.copyImageToBuffer( + capImage, vk::ImageLayout::eTransferSrcOptimal, + m_CaptureAlloc.Buffer, capRegion); + + vk::ImageMemoryBarrier2 back; + back.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer) + .setSrcAccessMask(vk::AccessFlagBits2::eTransferRead) + .setDstStageMask(vk::PipelineStageFlagBits2::eAllCommands) + .setDstAccessMask(vk::AccessFlagBits2::eMemoryRead | + vk::AccessFlagBits2::eMemoryWrite) + .setOldLayout(vk::ImageLayout::eTransferSrcOptimal) + .setNewLayout(capResting) + .setImage(capImage) + .setSubresourceRange({vk::ImageAspectFlagBits::eColor, + 0, 1, 0, 1}); + frameCtx.cmd.pipelineBarrier2( + vk::DependencyInfo{}.setImageMemoryBarriers(back)); + } + m_CommandManager->EndFrame(frameSlot); // 5. 提交 @@ -435,6 +575,20 @@ namespace Engine HandleResize(); } + // 调试截图落盘:等 fence(copy 已提交完成)→ fp16 → BMP + if (doCapture) { + m_Device->GetDevice().waitForFences( + m_SyncManager->GetFrameFence(frameSlot), VK_TRUE, + UINT64_MAX); + WriteHdrBMP("capture.bmp", m_CaptureAlloc.MappedPtr, + capExtent.width, capExtent.height, false); + m_Allocator->Destroy(m_CaptureAlloc); + m_Captured = true; + ENGINE_INFO("Debug capture written: capture.bmp (HDRColor) " + "shadowInstances={}", + m_SceneRenderer->GetShadowInstanceCount()); + } + m_CurrentFrame = (m_CurrentFrame + 1) % kMaxFramesInFlight; } diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 74d9cc8..2967037 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -19,6 +19,7 @@ #include #include +#include namespace Engine { @@ -173,14 +174,18 @@ namespace Engine m_Frame.skyInvVP = glm::inverse(ubo.projNoJitter * glm::mat4(glm::mat3(ubo.view))); - // ---- 2. 灯光(第一个方向光作为 CSM 阴影光源;点光位置来自 Transform)---- - u32 lightCount = 0; + // ---- 2. 灯光(CSM 阴影光源约定 = lights[0] 必是方向光)---- + // entt Each 遍历序与实体创建序不保证一致——直接按迭代序填充会让 + // lights[0] 落到点光上(params.x=1)→ shader 阴影分支整个跳过。 + // 先收集到临时数组,方向光排前,再写入 UBO glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射 + LightData collected[kMaxLights]; + u32 collectedCount = 0; scene.Each( [&](Entity, TransformComponent& t, LightComponent& light) { - if (lightCount >= kMaxLights) return; + if (collectedCount >= kMaxLights) return; - auto& dst = ubo.lights[lightCount]; + auto& dst = collected[collectedCount++]; dst.color = light.color; dst.params = glm::vec4( light.type == LightComponent::Type::Point ? 1.0f : 0.0f, @@ -195,17 +200,24 @@ namespace Engine dst.direction = glm::vec4(dir, 0.0f); dst.position = glm::vec4(0.0f); } - - // CSM 阴影光源 = 第一个方向光(点光不做级联阴影) - if (lightCount == 0 && - light.type == LightComponent::Type::Directional) { - shadowDir = glm::length(light.direction) > 0.0f - ? glm::normalize(light.direction) : shadowDir; - } - ++lightCount; }); - for (u32 i = lightCount; i < kMaxLights; ++i) { - ubo.lights[i] = {}; + + // 方向光排前(稳定排序:同类保持收集序)→ lights[0] = CSM 光源 + std::stable_sort(collected, collected + collectedCount, + [](const LightData& a, const LightData& b) { + return a.params.x < b.params.x; // 0=方向光 < 1=点光 + }); + + for (u32 i = 0; i < kMaxLights; ++i) { + if (i < collectedCount) { + ubo.lights[i] = collected[i]; + } else { + ubo.lights[i] = {}; + } + } + if (collectedCount > 0 && + collected[0].params.x < 0.5f) { + shadowDir = glm::vec3(collected[0].direction); } ComputeCascades(shadowDir); ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f);