refactor: 解耦重构——还清四处架构债(对齐 UE/Unity 分层)

- SceneRenderer:场景→渲染数据翻译层(UE FSceneRenderer / Unity CullingResults
  模式),收口相机/灯光/光空间矩阵/双视图剔除,Renderer 只编排
- pass 对象化(Unity ScriptableRenderPass 模式):8 个 pass lambda 拆为
  Passes::{Shadow,Forward,Bloom,Tonemap,ImGui,Present},BuildGraph 超级函数消失;
  SceneGpu/FullscreenGpu 承接共享装配
- 中性 AssetRef(Core 层):ECS 组件不再依赖 Assets 模块(Unity InstanceID 思路)
- IAssetUploader 接口:AssetManager 不感知 Vulkan 具体类,上传服务由渲染层
  VulkanAssetUploader 提供
- Renderer 公共 API 不变(App 层零改动),编译零错误 validation 零警告
This commit is contained in:
2026-09-23 12:02:49 +08:00
parent 91880d88fe
commit 718a658f5d
30 changed files with 1777 additions and 1054 deletions
+168
View File
@@ -0,0 +1,168 @@
//
// Created by atdunbg on 2026/9/23.
//
#include <Engine/Renderer/SceneRenderer.h>
#include <Engine/ECS/Scene.h>
#include <Engine/ECS/Components.h>
#include <Engine/Renderer/Camera.h>
#include <Engine/Renderer/Frustum.h>
#include <Engine/Renderer/Model.h>
#include <Engine/Renderer/Mesh.h>
#include <Engine/Assets/AssetManager.h>
#include <Engine/Core/Log.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <chrono>
namespace Engine
{
using namespace ECS; // TransformComponent / Entity 等直接使用
namespace
{
// 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应)
constexpr f32 kLightOrthoExtent = 15.0f;
constexpr f32 kLightDistance = 30.0f;
constexpr f32 kLightNear = 1.0f;
constexpr f32 kLightFar = 80.0f;
}
void SceneRenderer::ComputeLightSpace(const glm::vec3& shadowDir)
{
// 光源位置在光方向的反方向远处,看向场景中心
const glm::vec3 lightPos = -shadowDir * kLightDistance;
// up 与视线平行时叉积退化(NaN),切换 up
const glm::vec3 up = glm::abs(shadowDir.y) > 0.999f
? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f);
const glm::mat4 lightView = glm::lookAt(
lightPos, glm::vec3(0.0f), up);
glm::mat4 lightProj = glm::ortho(
-kLightOrthoExtent, kLightOrthoExtent,
-kLightOrthoExtent, kLightOrthoExtent,
kLightNear, kLightFar);
// Vulkan clip 深度 [0,1]:z' = 0.5*z + 0.5*w。
// ortho 的 w_clip = 1,偏移折算进 z 的平移分量(与 perspective 公式不同!)
lightProj[2][2] *= 0.5f;
lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f;
m_Frame.ubo.lightSpace = lightProj * lightView;
}
bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
f32 aspect)
{
const auto collectStart = std::chrono::steady_clock::now();
// ---- 1. 相机 ----
auto* cameraComp = scene.GetPrimaryCamera();
if (!cameraComp) {
// 无相机:本帧跳过(正常场景必有相机,安全网)
return false;
}
const Camera& camera = cameraComp->camera;
UniformBufferObject& ubo = m_Frame.ubo;
ubo.view = camera.GetView();
ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f);
ubo.ambientColor = scene.GetSettings().ambientColor;
// ---- 2. 灯光(第一个灯作为阴影光源)----
u32 lightCount = 0;
glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射
scene.Each<LightComponent>([&](Entity, LightComponent& light) {
if (lightCount >= kMaxLights) return;
glm::vec3 dir = glm::length(light.direction) > 0.0f
? glm::normalize(light.direction)
: glm::vec3(0.0f);
ubo.lights[lightCount].direction = glm::vec4(dir, 0.0f);
ubo.lights[lightCount].color = light.color;
if (lightCount == 0) shadowDir = dir;
++lightCount;
});
for (u32 i = lightCount; i < kMaxLights; ++i) {
ubo.lights[i].direction = glm::vec4(0.0f);
ubo.lights[i].color = glm::vec4(0.0f);
}
ComputeLightSpace(shadowDir);
// ---- 3. GPUDriven:单趟收集 + 双视图剔除 ----
// 每个实例对相机视锥、光源视锥分别做包围球测试:
// 进相机视锥 → 主视图 batch(Forward 画)
// 进光源视锥 → 阴影 batch(Shadow 画)——相机外实体仍可投影子
// 实例矩阵数组 = [主视图段 | 阴影视图段],batch.first 为全局下标。
// 哈希聚合保证各视图内 batch 区间连续。
m_Frame.instanceModels.clear();
m_Frame.mainView.batches.clear();
m_Frame.shadowView.batches.clear();
m_Frame.mainView.instanceCount = 0;
m_Frame.shadowView.instanceCount = 0;
m_MainLookup.clear();
m_ShadowLookup.clear();
bool instanceTruncated = false;
// 剔除平面提取按 [0,1] 深度约定——相机 proj 与 lightSpace 都已
// 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用
const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view);
const Frustum shadowFrustum = Frustum::FromMatrix(ubo.lightSpace);
scene.Each<TransformComponent, MeshRendererComponent>(
[&](Entity, TransformComponent& t, MeshRendererComponent& mr) {
Model* model = assets.GetModel(mr.model);
if (!model) return;
// 实例世界缩放(矩阵列向量最大长度,包围球半径随缩放)
const f32 worldScale = glm::max(
glm::length(glm::vec3(t.world[0])),
glm::max(glm::length(glm::vec3(t.world[1])),
glm::length(glm::vec3(t.world[2]))));
for (auto& mesh : model->GetMeshes()) {
const auto& bounds = mesh->GetBounds();
const glm::vec3 worldCenter =
glm::vec3(t.world * glm::vec4(bounds.center, 1.0f));
const f32 worldRadius = bounds.radius * worldScale;
auto tryAppend = [&](CullResult& view, const Frustum& frustum,
std::unordered_map<Mesh*, u32>& lookup) {
if (!frustum.IntersectsSphere(worldCenter, worldRadius)) {
return;
}
if (m_Frame.instanceModels.size() >= kMaxInstances) {
instanceTruncated = true;
return;
}
const auto [it, inserted] = lookup.emplace(
mesh.get(), static_cast<u32>(view.batches.size()));
if (inserted) {
view.batches.push_back({mesh.get(),
static_cast<u32>(m_Frame.instanceModels.size()), 0});
}
view.batches[it->second].count++;
view.instanceCount++;
m_Frame.instanceModels.push_back(t.world);
};
tryAppend(m_Frame.mainView, mainFrustum, m_MainLookup);
tryAppend(m_Frame.shadowView, shadowFrustum, m_ShadowLookup);
}
});
if (instanceTruncated && !m_TruncationWarned) {
// 只告警一次(每帧告警会刷屏)
m_TruncationWarned = true;
ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated",
kMaxInstances);
}
m_Frame.collectMs = std::chrono::duration<f32, std::milli>(
std::chrono::steady_clock::now() - collectStart).count();
return true;
}
} // namespace Engine