fix: 阴影不可见——灯序依赖遍历顺序 + 光比失衡,新增帧截图调试能力

- 灯序修复(根因):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 运行时可调)
This commit is contained in:
2026-09-24 01:49:11 +08:00
parent 8c0a11d2e8
commit 4d3963e291
8 changed files with 218 additions and 25 deletions
+154
View File
@@ -42,9 +42,83 @@
#include <array>
#include <filesystem>
#include <limits>
#include <bit>
#include <fstream>
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<f32>(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<f32>(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<const u16*>(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<u8> bmp(size, 0);
auto put = [&bmp](u32 off, u32 v, u32 n) {
for (u32 i = 0; i < n; ++i)
bmp[off + i] = static_cast<u8>((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<size_t>(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<const f32*>(
data)[static_cast<size_t>(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<u8>(v * 255.0f);
}
}
}
std::ofstream file(path, std::ios::binary);
file.write(reinterpret_cast<const char*>(bmp.data()),
static_cast<std::streamsize>(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<Passes::PostProcessParams>();
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<u32>(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<vk::DeviceSize>(
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;
}