Files
HelloVulkan/Engine/src/Renderer/Passes/SceneGpu.cpp
T
atdunbg 20f0035baa feat: ImGui docking Editor + 显示链/帧同步/输入语义重构
- App/editor: Hierarchy/Inspector/Viewport/Stats 四面板 + DockBuilder 默认布局,main.cpp 瘦身为场景搭建与生命周期
- 显示链: Tonemap→PresentTarget 经 ImGui::Image 呈现(零色偏),backbuffer 仅承载 ImGui;Direct/Viewport 双显示模式 + 面板分辨率上报
- 帧首同步 BeginFrameSync: resize/vsync/尺寸/模式意图统一帧首落定(修描述符悬垂);修复 vsync 意图未落 swapchain 导致的每帧重建交换链
- 输入: Editor 每帧判定键盘/鼠标归属(打字让位、Viewport hover/focus 放行、Fullscreen 全放行)
- 材质: roughness 独立槽位 + 非 glTF 金属/粗糙贴图映射;透明队列 depthWrite=FALSE + translucent 独立 shader + scratch 清帧
- FBX: 非蒙皮网格烘焙节点链全局变换(Z-up 旋转/单位换算落顶点)
- Hierarchy 行 ImGui ID 以 UUID 作用域(重名不冲突、改名不丢选中)
2026-09-30 00:24:53 +08:00

277 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/23.
//
#include <Engine/Renderer/Passes/SceneGpu.h>
#include <Engine/Renderer/Material.h>
#include <Engine/Renderer/Mesh.h> // IndirectGroup 取数(inline 经 Mesh)
#include <Engine/Renderer/Cubemap.h>
#include <Engine/Vulkan/Device.h>
#include <Engine/Vulkan/Buffer.h>
#include <Engine/Vulkan/DescriptorManager.h>
#include <Engine/Vulkan/ShaderManager.h>
#include <Engine/Vulkan/UniformBuffer.h>
#include <Engine/Core/Log.h>
#include <glm/glm.hpp>
namespace Engine::Passes
{
SceneGpu::SceneGpu(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptors,
ShaderManager& shaders,
UniformBuffer& ubo, UniformBuffer& objectBuffer,
UniformBuffer& prevObjectBuffer,
UniformBuffer& skinOffsetBuffer,
UniformBuffer& skinPaletteBuffer)
: m_Device(device), m_Allocator(allocator), m_Descriptors(descriptors)
{
// Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取)
// + 上一帧实例矩阵(TAA velocity,vertex 读取)
// + 骨骼蒙皮:SkinOffsets(实例→palette 基址)+ SkinPalettes(骨骼矩阵)
std::vector<DescriptorBinding> uboBindings = {
{0, vk::DescriptorType::eUniformBuffer,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
{1, vk::DescriptorType::eStorageBuffer,
vk::ShaderStageFlagBits::eVertex},
{2, vk::DescriptorType::eStorageBuffer,
vk::ShaderStageFlagBits::eVertex},
{3, vk::DescriptorType::eStorageBuffer,
vk::ShaderStageFlagBits::eVertex},
{4, vk::DescriptorType::eStorageBuffer,
vk::ShaderStageFlagBits::eVertex},
};
uboSetLayout = descriptors.CreateLayout(uboBindings);
// Set 1: PBR 材质纹理槽(binding = Material::Slot:0 albedo /
// 1 normal / 2 metallicRoughness / 3 emissive)+ 参数 UBO(binding 4)
std::vector<DescriptorBinding> texBindings;
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
texBindings.push_back(
{slot, vk::DescriptorType::eCombinedImageSampler,
vk::ShaderStageFlagBits::eFragment});
}
texBindings.push_back(
{Material::kParamsBinding, vk::DescriptorType::eUniformBuffer,
vk::ShaderStageFlagBits::eFragment});
textureSetLayout = descriptors.CreateLayout(texBindings);
// Set 2: CSM 级联阴影图(0/1/2)+ IBL 环境三件套(3 irradiance
// / 4 pre-filtered / 5 BRDF LUT),均 fragment 采样
std::vector<DescriptorBinding> shadowBindings;
for (u32 c = 0; c < kMaxCascades; ++c) {
shadowBindings.push_back(
{c, vk::DescriptorType::eCombinedImageSampler,
vk::ShaderStageFlagBits::eFragment});
}
for (const u32 b : {3u, 4u, 5u}) {
shadowBindings.push_back(
{b, vk::DescriptorType::eCombinedImageSampler,
vk::ShaderStageFlagBits::eFragment});
}
shadowSetLayout = descriptors.CreateLayout(shadowBindings);
// Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO)
forwardLayout = descriptors.CreatePipelineLayout(
{uboSetLayout, textureSetLayout, shadowSetLayout}, 0);
// Shadow pipeline layout:Set0 = UBO/ObjectBuffer(实例矩阵),
// push = lightVP 64B
shadowLayout = descriptors.CreatePipelineLayout(
std::vector<vk::DescriptorSetLayout>{uboSetLayout},
sizeof(glm::mat4));
const std::string shaderDir = "shaders/";
// forward vert/frag 均无 push(range 定义一致:都没有,VUID-08878)
auto vert = shaders.CreateFromFile(
shaderDir + "simple_shader.vert.spv",
vk::ShaderStageFlagBits::eVertex,
{uboSetLayout, textureSetLayout, shadowSetLayout}, 0);
auto frag = shaders.CreateFromFile(
shaderDir + "simple_shader.frag.spv",
vk::ShaderStageFlagBits::eFragment,
{uboSetLayout, textureSetLayout, shadowSetLayout}, 0);
// 透明变体:同一套布局,frag 末尾写 gl_FragDepth=1.0
auto transFrag = shaders.CreateFromFile(
shaderDir + "simple_shader_translucent.frag.spv",
vk::ShaderStageFlagBits::eFragment,
{uboSetLayout, textureSetLayout, shadowSetLayout}, 0);
// shadow shader:depth-only,push = lightVP
auto shadowVertShader = shaders.CreateFromFile(
shaderDir + "shadow.vert.spv",
vk::ShaderStageFlagBits::eVertex,
std::vector<vk::DescriptorSetLayout>{uboSetLayout},
sizeof(glm::mat4));
auto shadowFragShader = shaders.CreateFromFile(
shaderDir + "shadow.frag.spv",
vk::ShaderStageFlagBits::eFragment,
std::vector<vk::DescriptorSetLayout>{uboSetLayout},
sizeof(glm::mat4));
forwardVert = vert.Handle;
forwardFrag = frag.Handle;
translucentFrag = transFrag.Handle;
shadowVert = shadowVertShader.Handle;
shadowFrag = shadowFragShader.Handle;
if (!forwardVert || !forwardFrag || !translucentFrag ||
!shadowVert || !shadowFrag) {
ENGINE_FATAL("SceneGpu: failed to create shader objects");
}
// 阴影采样器:clamp-to-border 白色 = 光视锥外自动无阴影
//(省 shader 出界分支);开启硬件深度比较(sampler2DShadow)
// ——单次采样即 2×2 PCF,配合 shader 3×3 tap 得软阴影
vk::SamplerCreateInfo shadowSamplerInfo;
shadowSamplerInfo.setMagFilter(vk::Filter::eLinear)
.setMinFilter(vk::Filter::eLinear)
.setMipmapMode(vk::SamplerMipmapMode::eNearest)
.setAddressModeU(vk::SamplerAddressMode::eClampToBorder)
.setAddressModeV(vk::SamplerAddressMode::eClampToBorder)
.setAddressModeW(vk::SamplerAddressMode::eClampToBorder)
.setBorderColor(vk::BorderColor::eFloatOpaqueWhite)
.setUnnormalizedCoordinates(VK_FALSE)
.setCompareEnable(VK_TRUE)
// ref < stored → lit=1:片元比遮挡物近即被照亮
.setCompareOp(vk::CompareOp::eLess);
shadowSampler = device.GetDevice().createSampler(shadowSamplerInfo);
// per-slot 描述符分配 + Set0 绑定(buffer 句柄全程稳定,不用重绑)
uboSets.resize(kMaxFramesInFlight);
shadowSets.resize(kMaxFramesInFlight);
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
uboSets[slot] = descriptors.AllocateDescriptorSet(uboSetLayout);
descriptors.UpdateUniformBuffer(
uboSets[slot], 0, ubo.Get(slot), ubo.GetSize());
descriptors.UpdateStorageBuffer(
uboSets[slot], 1, objectBuffer.Get(slot), objectBuffer.GetSize());
descriptors.UpdateStorageBuffer(
uboSets[slot], 2, prevObjectBuffer.Get(slot),
prevObjectBuffer.GetSize());
descriptors.UpdateStorageBuffer(
uboSets[slot], 3, skinOffsetBuffer.Get(slot),
skinOffsetBuffer.GetSize());
descriptors.UpdateStorageBuffer(
uboSets[slot], 4, skinPaletteBuffer.Get(slot),
skinPaletteBuffer.GetSize());
shadowSets[slot] = descriptors.AllocateDescriptorSet(shadowSetLayout);
}
// shadowSets 的图片绑定在 Compile 后由 BindShadowMap 完成
// ---- MDI per-slot ring:命令 + count(CPU 直写,host visible)----
// 布局 [main | casc0 | casc1 | casc2] × kMaxInstances 命令预算;
// 两者 usage 都要 eIndirectBuffer(count buffer 的 spec 要求)
const vk::DeviceSize cmdBytes = vk::DeviceSize(kIndirectViewMain + 1 + kMaxCascades) *
kMaxInstances * sizeof(IndirectDrawCmd);
const vk::DeviceSize cntBytes = vk::DeviceSize(kIndirectViewMain + 1 + kMaxCascades) *
kMaxInstances * sizeof(u32);
constexpr auto memProps = vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent;
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
indirectCmdBuffers[slot] = CreateScope<Buffer>(
device, allocator, cmdBytes,
vk::BufferUsageFlagBits::eIndirectBuffer, memProps);
indirectCountBuffers[slot] = CreateScope<Buffer>(
device, allocator, cntBytes,
vk::BufferUsageFlagBits::eIndirectBuffer, memProps);
}
}
SceneGpu::~SceneGpu()
{
const auto& vkDevice = m_Device.GetDevice();
if (forwardVert) vkDevice.destroyShaderEXT(forwardVert);
if (forwardFrag) vkDevice.destroyShaderEXT(forwardFrag);
if (translucentFrag) vkDevice.destroyShaderEXT(translucentFrag);
if (shadowVert) vkDevice.destroyShaderEXT(shadowVert);
if (shadowFrag) vkDevice.destroyShaderEXT(shadowFrag);
vkDevice.destroySampler(shadowSampler);
vkDevice.destroyPipelineLayout(forwardLayout);
vkDevice.destroyPipelineLayout(shadowLayout);
vkDevice.destroyDescriptorSetLayout(uboSetLayout);
vkDevice.destroyDescriptorSetLayout(textureSetLayout);
vkDevice.destroyDescriptorSetLayout(shadowSetLayout);
}
void SceneGpu::BindShadowMaps(RG::RenderGraph& rg,
const std::array<RG::TextureHandle, kMaxCascades>& maps)
{
// 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
for (u32 c = 0; c < kMaxCascades; ++c) {
m_Descriptors.UpdateTexture(
shadowSets[slot], c, shadowSampler,
rg.GetPhysicalView(maps[c], slot));
}
}
}
// ---- MDI:per-slot 上传 + 组绘制 ----
void SceneGpu::UploadIndirect(u32 frameSlot, const FrameSceneData& frame)
{
auto uploadView = [&](u32 viewIdx, const CullResult& v) {
if (v.indirectCommands.empty()) return;
const vk::DeviceSize base =
vk::DeviceSize(viewIdx) * kMaxInstances;
indirectCmdBuffers[frameSlot]->Upload(
v.indirectCommands.data(),
v.indirectCommands.size() * sizeof(IndirectDrawCmd),
base * sizeof(IndirectDrawCmd));
// 每组一个 count,下标 = 组首命令下标(drawIndexedIndirectCount
// 从此读取实际执行条数——GPU 剔除切片接管时只换写入者)
for (const auto& g : v.indirectGroups) {
indirectCountBuffers[frameSlot]->Upload(
&g.commandCount, sizeof(u32),
(base + g.firstCommand) * sizeof(u32));
}
};
uploadView(kIndirectViewMain, frame.mainView);
for (u32 c = 0; c < kMaxCascades; ++c) {
uploadView(kIndirectViewMain + 1 + c, frame.shadowCascades[c]);
}
}
void SceneGpu::DrawIndirectGroup(vk::CommandBuffer cmd, u32 frameSlot,
u32 viewIndex, const IndirectGroup& g)
{
// 组内全部命令共享同一 megabuffer:bind 一次,逐 mesh 定位由
// 命令里的 firstIndex/vertexOffset 承担
cmd.bindVertexBuffers(0, g.mesh->GetVertexBuffer(), {vk::DeviceSize(0)});
cmd.bindIndexBuffer(g.mesh->GetIndexBuffer(), vk::DeviceSize(0),
vk::IndexType::eUint32);
const vk::DeviceSize base =
vk::DeviceSize(viewIndex) * kMaxInstances;
cmd.drawIndexedIndirectCount(
indirectCmdBuffers[frameSlot]->Get(),
base * sizeof(IndirectDrawCmd),
indirectCountBuffers[frameSlot]->Get(),
(base + g.firstCommand) * sizeof(u32),
g.commandCount, // maxDrawCount = CPU 打包的组大小
sizeof(IndirectDrawCmd));
}
void SceneGpu::BindEnvironment(const Cubemap& env)
{
// IBL 资源 view 全程稳定(不随 RG 重编译变化),绑一次即可。
// cube 资源必须用带 mip 的环境采样器(textureLod 显式 lod 会被
// clamp 到 maxLod;shadowSampler maxLod=0 会让 pre-filter 永远
// 停在最锐的 mip)
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
m_Descriptors.UpdateTexture(shadowSets[slot], 3, env.GetSampler(),
env.GetIrradianceView());
m_Descriptors.UpdateTexture(shadowSets[slot], 4, env.GetSampler(),
env.GetPrefilteredView());
m_Descriptors.UpdateTexture(shadowSets[slot], 5,
env.GetBrdfLutSampler(),
env.GetBrdfLutView());
}
}
} // namespace Engine::Passes