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);