Files
HelloVulkan/Engine/include/Engine/Renderer/Renderer.h
T
atdunbg 1449bea86d feat: HitProxy GPU 拾取 + UE 选中描边 + 编辑器拾取交互
- HitProxy 拾取:RG 管线渲染实体 id → host readback,点击帧
  PickEntity 精确命中;拾取按视觉几何(Model 联合包围球射线求交),
  与 Collider 解耦(UE/Unity 编辑器语义),命中打印实体名+碰撞体参数
- 选中描边(UE 语义):SelectionMask pass(R8Unorm 完整投影轮廓,
  无深度测试透墙可见)→ Outline pass(边缘检测 + alpha 叠加,
  必须注册在 Tonemap 后);edgeCut 贴视口边缘;widthPx=6 可调
- shader object 实证修复:VUID-08879 同程序 set layouts 超集一致、
  VUID-10065 push block 4×float 平铺、新 pass 析构销毁 VkShaderEXT
- RG:SideEffect() 保活"产出无 RG 内消费者"的 pass 链
- Editor:层次面板点击选中、Inspector 姿态编辑、Stats 面板拾取信息
- 相机 Update 改 const(姿态写入 Transform 引用,Camera 本身无状态变更)
2026-10-02 20:09:05 +08:00

267 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/20.
//
// Renderer:渲染门面 + 渲染侧组合根。
//
// 分工(对照 UE/Unity 的渲染架构):
// Renderer —— 装配模块、声明 RG 资源、注册 pass 对象、
// 每帧编排(翻译场景 → 写 buffer → 驱动 RG → 提交呈现)。
// 不理解场景细节,不含 pass 实现。
// SceneRenderer —— 场景 → FrameSceneData 的翻译层(相机/灯光/剔除,
// 对应 UE FSceneRenderer / Unity CullingResults)。
// Passes::* —— 各 pass 的声明与录制(对应 Unity ScriptableRenderPass)。
// 新增效果 = 新增 pass 类 + BuildGraph 注册一行。
//
// 对外只暴露两个入口:Render(Scene&, Assets&) 和给 App 层调参的访问器。
//
#ifndef HELLOVULKAN_RENDERER_H
#define HELLOVULKAN_RENDERER_H
#pragma once
#include <Engine/Core/Types.h>
#include <Engine/Core/FrameContext.h>
#include <Engine/Renderer/SceneTypes.h>
#include <Engine/RenderGraph/RenderGraph.h>
#include <vulkan/vulkan.hpp>
#include <array>
#include <optional>
#include <vector>
namespace Engine {
class Window;
class Device;
class MemoryAllocator;
class DescriptorManager;
class ShaderManager;
class Swapchain;
class SyncManager;
class CommandManager;
class UniformBuffer;
class SceneRenderer;
class VulkanAssetUploader;
class Cubemap;
class IblGenerator;
class ImGuiLayer;
class TracyGpuProfiler;
struct PhysicsDebugFrame; // PhysicsDebugTypes.h(物理调试图元契约)
namespace ECS { class Scene; }
namespace Assets { class AssetManager; class IAssetUploader; }
namespace Passes {
class RenderPass; class SceneGpu; class FullscreenGpu;
class PostProcessParams; class PhysicsDebugPass; class HitProxyPass;
}
class Renderer {
public:
explicit Renderer(Window& window);
~Renderer();
Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete;
void Render(ECS::Scene& scene, Assets::AssetManager& assets, f32 dt);
// ---- 组合根(Application)装配其他模块时需要的访问器 ----
Device& GetDevice() const { return *m_Device; }
MemoryAllocator& GetAllocator() const { return *m_Allocator; }
DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; }
vk::DescriptorSetLayout GetTextureSetLayout() const; // 实现在 cpp(SceneGpu 成员)
vk::Format GetSwapchainFormat() const;
u32 GetSwapchainImageCount() const;
// 资产上传服务(渲染层对资产层提供的 IAssetUploader 实现)
Assets::IAssetUploader& GetAssetUploader() const;
// 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。
// 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。
void SetImGuiLayer(ImGuiLayer* layer)
{
m_ImGuiLayer = layer;
if (layer) RefreshSceneTextures(); // 后端就绪:注册场景纹理
}
// 物理调试数据源(PhysicsWorld 每帧收集的图元缓冲;不拥有)。
// Application 装配时注入;nullptr = 无物理调试
void SetPhysicsDebugSource(const PhysicsDebugFrame* src)
{
m_PhysicsDebug = src;
}
// ---- 场景显示纹理(tonemap 输出,ImGui::Image 可采样显示)----
// 按 m_CurrentFrame 取当帧槽位的描述符(UI 构建段与紧随的
// Render 同槽位)。无 ImGui 层时返回 nullptr
void* GetSceneTexture() const;
// 渲染分辨率(PresentTarget 尺寸,等比适配用)
vk::Extent2D GetRenderExtent() const { return m_Extent; }
// ---- 后处理参数(ImGui 可调,转发到 pass 共享的 PostProcessParams)----
f32 GetExposure() const;
void SetExposure(f32 e);
bool GetUseACES() const;
void SetUseACES(bool b);
f32 GetBloomThreshold() const;
void SetBloomThreshold(f32 v);
f32 GetBloomStrength() const;
void SetBloomStrength(f32 v);
// ---- 特性开关 / 调试视图(转发到 SceneRenderer,每帧落
// FrameSceneData 契约,pass 只读;ImGui 直接对接)----
bool GetShadowsEnabled() const;
void SetShadowsEnabled(bool b);
bool GetTAAEnabled() const;
void SetTAAEnabled(bool b);
bool GetBloomEnabled() const;
void SetBloomEnabled(bool b);
bool GetLodEnabled() const;
void SetLodEnabled(bool b); // 关 = 全部强制 LOD0
int GetDebugView() const;
void SetDebugView(int v); // Passes::DebugView 枚举值
// 调试视图名表(下标 = DebugView 枚举值,ImGui combo 直接枚举)
const std::vector<const char*>& GetDebugViewNames() const;
// ---- 动态环境切换(资产归 Assets 层,Renderer 只消费)----
// env 传 AssetManager::GetEnvironment(ref) 的指针;nullptr 回中性兜底。
// 内部 waitIdle 后统一换绑(引用环境描述符的 pass 全部同步);
// compute 预积分已把生成成本压到毫秒级,waitIdle 尾巴 ≈ 单帧
void SetEnvironment(const Cubemap* env);
// pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应)
const std::vector<std::string>& GetPassNames() const { return m_PassNames; }
const std::vector<f32>& GetPassGpuMs() const { return m_PassGpuMs; }
// 双视图剔除后的实例数(遥测)
u32 GetMainInstanceCount() const;
u32 GetShadowInstanceCount() const;
bool GetInstancesTruncated() const; // 实例预算溢出(遥测红字)
f32 GetCollectMs() const; // CPU 收集+剔除段耗时
// ---- 显示模式(Editor 视口 ↔ 全屏直出;帧首同步生效)----
int GetDisplayMode() const { return m_DesiredDisplayMode; }
void SetDisplayMode(int mode); // cpp 内定义(SceneRenderer 前置声明)
// 当帧场景数据(只读直通)。CPU 消费如视口拾取:反投影矩阵必须
// 与渲染同一台相机同一投影(含 [0,1] 深度语义),禁止自行重算
const FrameSceneData& GetFrameSceneData() const; // cpp 内定义
// Editor 视口的期望渲染尺寸(像素;UI 面板每帧上报,帧首统一生效)
void SetViewportSize(u32 w, u32 h)
{
m_DesiredViewport = vk::Extent2D{w, h};
}
// ---- Editor 视口拾取(UE 式 GPU HitProxy:id pass + 点击 readback)----
// UI 段点击时调用:下一帧 HitProxy 渲染 + 请求像素拷贝(Vulkan
// NDC,y 向下,与渲染同构)
void RequestPick(const glm::vec2& ndc);
// UI 段轮询:拷贝结果就绪则取走(一次性;nullopt = 暂无结果)。
// 返回 kPickMiss = 未命中(天空/空隙),有效值 = 实体位模式。
// 内部 waitIdle 保证提交完成(点击低频,单帧 hitch 不可感知)
std::optional<u32> TakePickResult();
// ---- Editor 选中描边(UE 编辑器选中轮廓)----
// UI 段选中变化时调用(bits = 实体位模式,0 = 清选中);
// 下一帧 SelectionMask 只画该实体,Outline 在其轮廓叠加描边色
void SetSelectedEntity(u32 entityBits);
// 垂直同步开关(present mode 切换会重建 swapchain),
// 只应在帧 UI 构建段调用(Render 之前)
void SetVSync(bool vsync) { m_DesiredVSync = vsync; } // 帧首同步生效
// 帧首同步:窗口 resize / 视口尺寸 / 显示模式 / vsync 意图统一在
// UI 构建之前落定(waitIdle → 重编译 → 重绑描述符)。必须先于
// UI 构建段调用:本帧 draw data 引用的描述符与物理资源由此保证
// 全程新鲜(resize 中途换描述符会绑出已销毁句柄)
void BeginFrameSync();
private:
void BuildGraph(); // 声明 RG 资源 + 注册 pass 对象
void HandleResize(bool recreateSwapchain);
void ReadGpuTimes(u32 frameSlot);
void RefreshSceneTextures(); // 物理 view → ImGui 描述符(重建后刷新)
// 期望渲染分辨率:Editor 视口 = 面板尺寸(未上报时回退 swapchain);
// Direct = swapchain 尺寸
vk::Extent2D DesiredRenderExtent() const;
Window& m_Window;
// ---- 底层模块(析构序:后声明者先析构;引用他人者先析构)----
Scope<Device> m_Device;
Scope<MemoryAllocator> m_Allocator;
Scope<DescriptorManager> m_DescriptorManager;
Scope<ShaderManager> m_ShaderManager;
Scope<UniformBuffer> m_UniformBuffer; // per-slot UBO ring
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
Scope<UniformBuffer> m_PrevObjectBuffer; // 上一帧实例矩阵 ring(TAA velocity)
Scope<UniformBuffer> m_SkinOffsetBuffer; // 实例→palette 基址 ring(SSBO,与实例序平行)
Scope<UniformBuffer> m_SkinPaletteBuffer; // 骨骼蒙皮矩阵 palette ring(SSBO,帧首 bump)
Scope<UniformBuffer> m_EntityIdBuffer; // 实例→实体位模式 ring(SSBO,HitProxy 拾取)
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
// 物理调试图元 ring VB(eVertexBuffer;per-slot fence 语义复用
// UniformBuffer——PhysicsDebugPass::PrepareFrame 上传,pass 内绑定)
Scope<UniformBuffer> m_PhysicsLineVB;
Scope<UniformBuffer> m_PhysicsTriVB;
// 环境预积分生成端(compute 链),先于 m_Environment/m_Passes 构造
Scope<IblGenerator> m_Ibl;
// 中性兜底环境(IBL 四件套):Application 未加载/加载失败时保底;
// 声明在 m_Passes 之前 → 析构晚于 pass(引用安全)
Scope<Cubemap> m_Environment;
Scope<Swapchain> m_Swapchain;
Scope<SyncManager> m_SyncManager;
Scope<CommandManager> m_CommandManager;
Scope<SceneRenderer> m_SceneRenderer; // 场景翻译层(先于 pass 构造)
Scope<RG::RenderGraph> m_RenderGraph;
Scope<VulkanAssetUploader> m_AssetUploader;
// pass 对象(注册序;执行序由 RG 拓扑排序决定)
std::vector<Scope<Passes::RenderPass>> m_Passes;
// ---- RenderGraph 资源句柄 ----
RG::TextureHandle m_Backbuffer{};
RG::TextureHandle m_PresentTarget{}; // tonemap 输出(offscreen,ImGui 采样)
RG::TextureHandle m_Depth{};
std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps{}; // CSM 级联
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一
RG::TextureHandle m_Velocity{}; // Forward 输出(clip 空间运动向量)
RG::TextureHandle m_HitProxyId{}; // 拾取 id 目标(R32Uint,片元写实体位模式)
RG::TextureHandle m_SelectionMask{}; // 选中描边 mask(R8Unorm,选中像素=1)
RG::TextureHandle m_TAAColor{}; // TAA 累积输出(自引用历史),后处理源
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
RG::TextureHandle m_TemporalDiff{}; // 半分辨率:帧历史差异(调试)
// pass 共享的可调参数(Renderer 持有,App 层访问器转发)
Scope<Passes::PostProcessParams> m_PostParams;
// 可选叠加层(不拥有;Application 持有)
ImGuiLayer* m_ImGuiLayer = nullptr;
// 物理调试(非拥有指针:pass 在 m_Passes 内,数据源由 Application 注入)
Passes::PhysicsDebugPass* m_PhysicsDebugPass = nullptr;
const PhysicsDebugFrame* m_PhysicsDebug = nullptr;
// HitProxy 拾取(非拥有指针:pass 在 m_Passes 内)
Passes::HitProxyPass* m_HitProxyPass = nullptr;
// GPU timestamp(pass 级耗时,ImGui 可读)
vk::QueryPool m_QueryPool;
u32 m_QueriesPerFrame = 0; // 每帧槽位的查询数 = pass 数 × 2
f32 m_TimestampPeriod = 1.0f; // ns/tick(设备属性缓存)
std::vector<std::string> m_PassNames; // 执行序 pass 名
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取
// Tracy GPU 时间线(pass zone;TRACY_ENABLE 关闭时空实现)
Scope<TracyGpuProfiler> m_TracyProfiler;
vk::Extent2D m_Extent{};
u32 m_CurrentFrame = 0;
// ---- 帧首同步的意图缓冲(UI 段写入,下一帧帧首落定)----
int m_DesiredDisplayMode = kDisplayDirect; // 默认直接渲染, 视口渲染需要手动指定一下
vk::Extent2D m_DesiredViewport{}; // Editor 视口渲染分辨率(面板上报)
bool m_DesiredVSync = true;
};
} // namespace Engine
#endif //HELLOVULKAN_RENDERER_H