Files
HelloVulkan/Engine/src/Renderer/SceneRenderer.cpp
T
atdunbg 4b5990e6c8 feat: 环境背景管线(IBL 阶段①)——HDR 等距柱状转 cube + Skybox pass
- Cubemap:stbi_loadf 加载 .hdr,CPU 双线性重采样 6 面(GL 规范方向表),
  f32→fp16 转换后整块上传 eCubeCompatible image(R16G16B16A16Sfloat)
- SkyboxPass:HDRColor 的 first writer,承担清屏职责;无环境贴图自动
  退化为纯清屏(Forward 不受影响)
- frag 逐像素 NDC 反投影取世界方向(invVP push,投影变换不可线性插值)
- Forward color 附件 Clear→Load:几何合成在天空之上
- SceneRenderer 产出 skyInvVP = inverse(proj × mat4(mat3(view)))
2026-09-23 18:37:01 +08:00

333 lines
15 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/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 <tracy/Tracy.hpp>
#include <chrono>
#include <limits>
namespace Engine
{
using namespace ECS; // TransformComponent / Entity 等直接使用
namespace
{
// 相机裁剪范围(与 BuildFrame 中 GetProjection 的参数一致)
constexpr f32 kCameraNear = 0.1f;
constexpr f32 kCameraFar = 100.0f;
// 实用分割插值因子(0=均匀,1=对数;0.6 是业界常用值)
constexpr f32 kCascadeLambda = 0.6f;
// Halton 低差异序列(TAA 子像素抖动)
f32 Halton(u32 index, u32 base)
{
f32 f = 1.0f, r = 0.0f;
while (index > 0) {
f /= static_cast<f32>(base);
r += f * static_cast<f32>(index % base);
index /= base;
}
return r;
}
}
void SceneRenderer::ComputeCascades(const glm::vec3& shadowDir)
{
constexpr u32 N = kMaxCascades;
// ---- 1. 实用分割:均匀与对数分割按 λ 插值(视图深度,正值)----
f32 splits[N];
for (u32 i = 1; i <= N; ++i) {
const f32 t = static_cast<f32>(i) / N;
const f32 uniform = kCameraNear + (kCameraFar - kCameraNear) * t;
const f32 log = kCameraNear * glm::pow(kCameraFar / kCameraNear, t);
splits[i - 1] = glm::mix(uniform, log, kCascadeLambda);
}
for (u32 c = 0; c < N; ++c) {
m_Frame.ubo.cascadeSplits[c] = splits[c];
}
// ---- 2. 全视锥 8 角反投影到世界(NDC z: 0=近 1=远,Vulkan [0,1])----
const glm::mat4 invVP = glm::inverse(m_Frame.ubo.projNoJitter * m_Frame.ubo.view);
glm::vec3 nearC[4], farC[4];
const glm::vec2 ndcXY[4] = {{-1,-1},{1,-1},{-1,1},{1,1}};
for (u32 i = 0; i < 4; ++i) {
glm::vec4 p = invVP * glm::vec4(ndcXY[i], 0.0f, 1.0f);
nearC[i] = glm::vec3(p / p.w);
p = invVP * glm::vec4(ndcXY[i], 1.0f, 1.0f);
farC[i] = glm::vec3(p / p.w);
}
// ---- 3. 每级联:切片角点 → 光空间 AABB 拟合 → 纹素对齐 ----
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);
for (u32 c = 0; c < N; ++c) {
const f32 d0 = (c == 0) ? kCameraNear : splits[c - 1];
const f32 d1 = splits[c];
const f32 t0 = (d0 - kCameraNear) / (kCameraFar - kCameraNear);
const f32 t1 = (d1 - kCameraNear) / (kCameraFar - kCameraNear);
glm::vec3 slice[8];
for (u32 i = 0; i < 4; ++i) {
slice[i] = glm::mix(nearC[i], farC[i], t0);
slice[4 + i] = glm::mix(nearC[i], farC[i], t1);
}
// 光视图:位于切片中心,沿光方向看
glm::vec3 center(0.0f);
for (const auto& p : slice) center += p;
center *= (1.0f / 8.0f);
const glm::mat4 lightView = glm::lookAt(center, center + shadowDir, up);
glm::vec3 mn( std::numeric_limits<f32>::max());
glm::vec3 mx( std::numeric_limits<f32>::lowest());
for (const auto& p : slice) {
const glm::vec3 lv = glm::vec3(lightView * glm::vec4(p, 1.0f));
mn = glm::min(mn, lv);
mx = glm::max(mx, lv);
}
// 沿光方向扩展深度范围:切片盒外的 caster(如高树、城墙)
// 也能投影子进本级联——per-cascade 剔除的正确性前提。
// 只扩 z 不缩 XY,阴影分辨率不受影响
constexpr f32 kCasterPad = 30.0f;
mn.z -= kCasterPad;
mx.z += kCasterPad;
// 纹素对齐:X/Y 边界量化到纹素网格(消除相机移动时的阴影边缘闪烁)
const f32 texelX = (mx.x - mn.x) / static_cast<f32>(kCascadeRes);
const f32 texelY = (mx.y - mn.y) / static_cast<f32>(kCascadeRes);
mn.x = glm::floor(mn.x / texelX) * texelX;
mn.y = glm::floor(mn.y / texelY) * texelY;
mx.x = mn.x + glm::ceil((mx.x - mn.x) / texelX) * texelX;
mx.y = mn.y + glm::ceil((mx.y - mn.y) / texelY) * texelY;
// 光视图空间里 z 为负(看向 -Z);近远距离取正
glm::mat4 lightProj = glm::ortho(mn.x, mx.x, mn.y, mx.y,
-mx.z, -mn.z);
// Vulkan clip 深度 [0,1](ortho 的 w_clip=1,偏移折进 z 平移分量)
lightProj[2][2] *= 0.5f;
lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f;
m_Frame.ubo.lightSpace[c] = lightProj * lightView;
}
}
bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
vk::Extent2D targetExtent)
{
ZoneScopedN("BuildFrame");
const auto collectStart = std::chrono::steady_clock::now();
// ---- 0. TAA 时域状态先行(velocity = 上一帧姿态与相机)----
UniformBufferObject& ubo = m_Frame.ubo;
ubo.prevView = m_LastView;
ubo.prevProj = m_LastProjNoJitter;
// ---- 1. 相机(位姿单一事实源 = 相机实体的 Transform)----
TransformComponent* camTransform = nullptr;
auto* cameraComp = scene.GetPrimaryCamera(&camTransform);
if (!cameraComp || !camTransform) {
// 无相机:本帧跳过(正常场景必有相机,安全网)
return false;
}
const Camera& camera = cameraComp->camera;
const f32 aspect = static_cast<f32>(targetExtent.width) /
static_cast<f32>(targetExtent.height);
ubo.view = camera.GetView(*camTransform);
ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f);
// 未抖动投影:velocity 的当前帧 clip 位置用它(抖动不能进 velocity)
ubo.projNoJitter = ubo.proj;
// 子像素抖动(Halton×8):光栅化采样位置逐帧偏移,TAA 累积去锯齿。
// NDC 偏移 = 像素偏移 × 2 / 尺寸
{
const u32 idx = static_cast<u32>(m_FrameIndex % 8);
const f32 jx = Halton(idx + 1, 2) - 0.5f;
const f32 jy = Halton(idx + 1, 3) - 0.5f;
ubo.proj[2][0] += jx * 2.0f / static_cast<f32>(targetExtent.width);
ubo.proj[2][1] += jy * 2.0f / static_cast<f32>(targetExtent.height);
}
ubo.ambientColor = scene.GetSettings().ambientColor;
// 天空盒:视图去平移(只留旋转)× 投影 → 逆矩阵,push 给 Skybox pass;
// 反投影远平面点 = 相机相对世界方向,frag normalize 后采样 cube
m_Frame.skyInvVP =
glm::inverse(ubo.projNoJitter * glm::mat4(glm::mat3(ubo.view)));
// ---- 2. 灯光(第一个方向光作为 CSM 阴影光源;点光位置来自 Transform)----
u32 lightCount = 0;
glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射
scene.Each<TransformComponent, LightComponent>(
[&](Entity, TransformComponent& t, LightComponent& light) {
if (lightCount >= kMaxLights) return;
auto& dst = ubo.lights[lightCount];
dst.color = light.color;
dst.params = glm::vec4(
light.type == LightComponent::Type::Point ? 1.0f : 0.0f,
light.radius, 0.0f, 0.0f);
if (light.type == LightComponent::Type::Point) {
dst.position = glm::vec4(t.GetPosition(), 1.0f);
dst.direction = glm::vec4(0.0f);
} else {
glm::vec3 dir = glm::length(light.direction) > 0.0f
? glm::normalize(light.direction) : glm::vec3(0.0f);
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] = {};
}
ComputeCascades(shadowDir);
ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f);
// ---- 3. GPUDriven:单趟收集 + 多视图剔除 ----
// 同作用域第二个 zone:ZoneScopedN 变量名固定会重声明,用 ZoneNamedN
ZoneNamedN( ___tracy_collect, "GPUDriven Collect", true );
// 每个实例的包围球对 4 个视锥分别测试:
// 相机视锥 → 主视图(Forward 画)
// 各级联光视锥 → 各自的阴影 batch(Shadow 按 region 各画各的)
// 级联盒沿光方向加了 caster pad(见 ComputeCascades):
// 盒外 caster 投进级联的影子不会被剔除裁掉。
// 收集按视图分桶,最后拼接成 [主段|级联0|级联1|级联2],
// batch.first 拼接时加上段基址,保证段内区间连续。
m_Frame.instanceModels.clear();
m_Frame.prevInstanceModels.clear();
m_MainScratch.Clear();
for (auto& s : m_CascScratch) s.Clear();
u32 totalInstances = 0;
bool instanceTruncated = false;
// 剔除平面提取按 [0,1] 深度约定——相机 proj 与 lightSpace 都已
// 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用
const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view);
Frustum cascFrustum[kMaxCascades];
for (u32 c = 0; c < kMaxCascades; ++c) {
cascFrustum[c] = Frustum::FromMatrix(ubo.lightSpace[c]);
}
scene.Each<TransformComponent, MeshRendererComponent>(
[&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) {
Model* model = assets.GetModel(mr.model);
if (!model) return;
// TAA velocity:本实例的上一帧世界矩阵(首帧/新实体 = 当前
// 矩阵 → 速度为 0,不产生假运动)
const u32 entKey = static_cast<u32>(entt::to_integral(entity));
glm::mat4 prevWorld = t.World();
if (auto it = m_PrevWorlds.find(entKey); it != m_PrevWorlds.end()) {
prevWorld = it->second;
}
m_CurWorlds.emplace(entKey, t.World());
// 实例世界缩放(矩阵列向量最大长度,包围球半径随缩放)
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 = [&](ViewScratch& s, const Frustum& frustum) {
if (!frustum.IntersectsSphere(worldCenter, worldRadius)) {
return;
}
if (totalInstances >= kMaxInstances) {
instanceTruncated = true;
return;
}
const auto [it, inserted] = s.lookup.emplace(
mesh.get(), static_cast<u32>(s.worlds.size()));
if (inserted) {
s.result.batches.push_back({mesh.get(),
static_cast<u32>(s.worlds.size()), 0});
}
s.result.batches[it->second].count++;
s.result.instanceCount++;
s.worlds.push_back(t.World());
s.prevWorlds.push_back(prevWorld);
++totalInstances;
};
tryAppend(m_MainScratch, mainFrustum);
for (u32 c = 0; c < kMaxCascades; ++c) {
tryAppend(m_CascScratch[c], cascFrustum[c]);
}
}
});
// ---- 4. 拼接:[主段|级联0|级联1|级联2],batch.first 加段基址 ----
auto appendView = [&](CullResult& dst, ViewScratch& s) {
const u32 base = static_cast<u32>(m_Frame.instanceModels.size());
dst.batches.clear();
dst.batches.reserve(s.result.batches.size());
for (auto& b : s.result.batches) {
b.first += base;
dst.batches.push_back(b);
}
dst.instanceCount = s.result.instanceCount;
m_Frame.instanceModels.insert(m_Frame.instanceModels.end(),
s.worlds.begin(), s.worlds.end());
m_Frame.prevInstanceModels.insert(m_Frame.prevInstanceModels.end(),
s.prevWorlds.begin(),
s.prevWorlds.end());
};
appendView(m_Frame.mainView, m_MainScratch);
for (u32 c = 0; c < kMaxCascades; ++c) {
appendView(m_Frame.shadowCascades[c], m_CascScratch[c]);
}
// 帧末滚动时域状态:cur → prev(每帧重建,销毁的实体自动清出)
m_PrevWorlds.swap(m_CurWorlds);
m_CurWorlds.clear();
m_LastView = ubo.view;
m_LastProjNoJitter = ubo.projNoJitter;
++m_FrameIndex;
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