feat: Shadow pass 接入 RenderGraph,支持 SRT 变换与程序化平面

阴影(本修正的核心根因):
- Shadow pass 光源正交写深度(2048 D32),Forward 采样比较;
  写入与采样共用同一 lightSpace 矩阵,杜绝姿态/镜像不一致
- 修复:push constant range 64B→128B(原 Shadow pass 推 {lightVP,model}
  超出 layout 声明范围 = UB,阴影中模型矩阵读到垃圾导致旋转错乱)
- 修复:ShadowFactor 缺少 NDC→UV 映射(*0.5+0.5),采样区域整体错位
- 修复:RG ShaderRead stage 位值直转(eFragment=0x10 在 PipelineStage2
  是 TESSELLATION_CONTROL),改为逐位显式映射
- Camera perspective 深度重映射到 Vulkan [0,1](公式与 ortho 不同:
  M22=M22*0.5-0.5, M32*=0.5);光源 ortho remap 用 M32=M32*0.5+0.5

引擎配套:
- TransformComponent 重构为 position/rotation/scale + Compose()
- Texture::CreateSolidColor / Model::CreatePlane 程序化资产
  (两段式上传公共路径),AssetManager.CreatePlane 带缓存入口
- RG 新增 GetPhysicalView(h, slot):按帧槽位把 RG 资源绑进外部描述符
  (阴影图 per-slot 物理镜像各自绑定)

验证:validation 0 error 0 warning,阴影随实体旋转正确
This commit is contained in:
2026-09-22 16:40:54 +08:00
parent 394bfd3d0c
commit 07a3b87001
17 changed files with 494 additions and 55 deletions
@@ -153,6 +153,9 @@ namespace Engine {
Model* GetModel(ModelHandle h) const { return m_Models.Get(h); }
void UnloadModel(ModelHandle h) { m_Models.Unload(h); }
// 程序化平面(纯色贴图):size 为半边长。带缓存,同参数共享一份。
ModelHandle CreatePlane(f32 halfSize, u8 r, u8 g, u8 b);
AssetCache<Model>& Models() { return m_Models; }
// 后续扩展点:TextureHandle / MaterialHandle / ShaderHandle
+24 -2
View File
@@ -14,6 +14,7 @@
#include <Engine/Assets/AssetHandle.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <string>
namespace Engine {
@@ -33,10 +34,31 @@ namespace Engine::ECS {
std::string name;
};
// 世界变换。当前直接存世界矩阵(渲染热路径友好);
// 需要 SRT 编辑时再加 LocalTransform 组件 + 层级同步系统。
// 世界变换:可编辑的 SRT 分量 + 派生的世界矩阵。
// 编辑分量后调用 Compose() 重算 world;渲染热路径只读 world。
// 层级(父子变换)留给后续的层级同步系统。
struct TransformComponent {
glm::vec3 position{0.0f};
glm::vec3 rotation{0.0f}; // 欧拉角(度),应用顺序 Y→X→Z
glm::vec3 scale{1.0f};
glm::mat4 world{1.0f};
TransformComponent& SetPosition(const glm::vec3& p) { position = p; return *this; }
TransformComponent& SetRotation(const glm::vec3& r) { rotation = r; return *this; }
TransformComponent& SetScale(const glm::vec3& s) { scale = s; return *this; }
// 分量 → world 矩阵(T * R * S)
TransformComponent& Compose()
{
glm::mat4 t = glm::translate(glm::mat4(1.0f), position);
glm::mat4 r =
glm::rotate(glm::mat4(1.0f), glm::radians(rotation.y), glm::vec3(0, 1, 0)) *
glm::rotate(glm::mat4(1.0f), glm::radians(rotation.x), glm::vec3(1, 0, 0)) *
glm::rotate(glm::mat4(1.0f), glm::radians(rotation.z), glm::vec3(0, 0, 1));
glm::mat4 s = glm::scale(glm::mat4(1.0f), scale);
world = t * r * s;
return *this;
}
};
// 网格渲染体:引用共享的 Model 资产。
@@ -188,6 +188,11 @@ namespace Engine::RG {
// 每帧 acquire 后调用,把当帧 swapchain image 绑到导入资源
void SetImportedImage(TextureHandle h, vk::Image image, vk::ImageView view);
// 指定帧槽位的物理镜像 view(Compile 后有效)。
// 用途:把 RG 管理的资源绑定到外部描述符(如阴影图采样)。
// 注意:物理镜像按槽位各一份,槽位变化/重编译后必须重新获取。
vk::ImageView GetPhysicalView(TextureHandle h, u32 frameSlot) const;
// ---- Pass 声明 ----
class PassBuilder {
+15 -1
View File
@@ -10,6 +10,7 @@
#include <Engine/Core/Types.h>
#include <Engine/Renderer/Mesh.h>
#include <vulkan/vulkan.hpp>
#include <array>
#include <string>
#include <unordered_map>
#include <vector>
@@ -32,6 +33,15 @@ namespace Engine
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
const std::string& path);
// 程序化模型:单位法线朝上的 XZ 平面(纯色贴图),用于地面等。
// size 为半边长(平面总边长 = 2*size)。
struct ProceduralTag {};
static Scope<Model> CreatePlane(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
f32 halfSize, u8 r, u8 g, u8 b);
~Model();
Model(const Model&) = delete;
@@ -40,6 +50,11 @@ namespace Engine
const std::vector<Scope<Mesh>>& GetMeshes() const { return m_Meshes; }
private:
Model(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
ProceduralTag, f32 halfSize, const std::array<u8, 3>& color);
void LoadModel(const std::string& path);
void ProcessNode(aiNode* node, const aiScene* scene);
Scope<Mesh> ProcessMesh(aiMesh* mesh, const aiScene* scene);
@@ -60,5 +75,4 @@ namespace Engine
};
} // namespace Engine
#endif //HELLOVULKAN_MODEL_H
+14 -1
View File
@@ -56,6 +56,7 @@ namespace Engine {
private:
void BuildGraph(); // 声明 pass 结构 + 首次 Compile
void HandleResize();
void UpdateShadowDescriptors();
Window& m_Window;
@@ -71,20 +72,32 @@ namespace Engine {
Scope<RG::RenderGraph> m_RenderGraph;
// ---- 管线装配(shader object 方案,无 vk::Pipeline)----
// Forward layout:3 个 set(UBO/贴图/阴影图)+ 128B push(与 Shadow pass
// 共用上限;forward 的 shader block 只有 64B,block ⊂ range 合法)
vk::PipelineLayout m_PipelineLayout;
vk::DescriptorSetLayout m_UBOSetLayout;
vk::DescriptorSetLayout m_TextureSetLayout;
std::vector<vk::DescriptorSet> m_UBODescriptorSets; // 每帧槽位一个
vk::DescriptorSetLayout m_ShadowSetLayout;
std::vector<vk::DescriptorSet> m_UBODescriptorSets; // 每帧槽位一个
std::vector<vk::DescriptorSet> m_ShadowDescriptorSets; // 每帧槽位一个
vk::Sampler m_ShadowSampler;
vk::ShaderEXT m_VertShader{};
vk::ShaderEXT m_FragShader{};
// Shadow pass 独立装配:无 set,push constant = {lightVP, model} 128B
vk::PipelineLayout m_ShadowPipelineLayout;
vk::ShaderEXT m_ShadowVertShader{};
vk::ShaderEXT m_ShadowFragShader{};
// ---- RenderGraph 资源句柄 ----
RG::TextureHandle m_Backbuffer{};
RG::TextureHandle m_Depth{};
RG::TextureHandle m_ShadowMap{};
// ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)----
ECS::Scene* m_BoundScene = nullptr;
Assets::AssetManager* m_BoundAssets = nullptr;
glm::mat4 m_LightSpace{1.0f}; // 阴影光源 VP(每帧由 Render 算好)
vk::Extent2D m_Extent{};
u32 m_CurrentFrame = 0;
+15
View File
@@ -9,6 +9,7 @@
#include <Engine/Core/Types.h>
#include <vulkan/vulkan.hpp>
#include <array>
#include <string>
#include "MemoryAllocator.h"
@@ -20,7 +21,14 @@ namespace Engine {
class Texture {
public:
// 从磁盘图片加载
Texture(Device& device, MemoryAllocator& allocator, const std::string& path);
// 程序化纹理:纯色(1x1),用于地面/调试等不依赖磁盘资产的场景
struct ProceduralTag {};
static Scope<Texture> CreateSolidColor(Device& device, MemoryAllocator& allocator,
u8 r, u8 g, u8 b);
~Texture();
Texture(const Texture&) = delete;
@@ -34,6 +42,13 @@ namespace Engine {
vk::Sampler GetSampler() const { return m_Sampler; }
private:
// 两段式构造:先确定像素来源(磁盘 / 程序化),再走公共上传路径
Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
const std::array<u8, 4>& rgba);
// 公共路径:staging → device local image → view/sampler
void InitFromPixels(const void* pixels, u32 width, u32 height);
Device& m_Device;
vk::Image m_Image;
vk::ImageView m_View;
+12
View File
@@ -28,4 +28,16 @@ namespace Engine::Assets {
});
}
ModelHandle AssetManager::CreatePlane(f32 halfSize, u8 r, u8 g, u8 b)
{
// 程序化资产复用同一缓存:键 = 伪路径 + 参数
std::string key = "procedural://plane/" + std::to_string(halfSize) +
"/" + std::to_string(r) + "," + std::to_string(g) +
"," + std::to_string(b);
return m_Models.Load(key, [&](const std::string&) -> Scope<Model> {
return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors,
m_TextureLayout, halfSize, r, g, b);
});
}
} // namespace Engine::Assets
+33 -5
View File
@@ -562,6 +562,17 @@ namespace Engine::RG {
return img.release();
}
vk::ImageView RenderGraph::GetPhysicalView(TextureHandle h, u32 frameSlot) const
{
const auto& entry = GetEntry(h);
if (entry.imported) return entry.importedView;
if (frameSlot >= entry.physical.size() || !entry.physical[frameSlot]) {
ENGINE_FATAL("RenderGraph: no physical image for '{}' slot {} "
"(compile first?)", entry.name, frameSlot);
}
return entry.physical[frameSlot]->view;
}
// ---------- 执行 ----------
void RenderGraph::Execute(const FrameContext& frame)
@@ -679,11 +690,28 @@ namespace Engine::RG {
case ResourceUse::DepthAttachment:
return vk::PipelineStageFlagBits2::eEarlyFragmentTests |
vk::PipelineStageFlagBits2::eLateFragmentTests;
case ResourceUse::ShaderRead:
// ShaderStageFlags 与 PipelineStageFlags2 位值兼容,需显式转换
return vk::PipelineStageFlags2(
static_cast<VkPipelineStageFlags2>(
static_cast<VkShaderStageFlags>(use.readStage)));
case ResourceUse::ShaderRead: {
// ShaderStageFlags 与 PipelineStageFlags2 位值不同
//(eFragment=0x10 vs eFragmentShader=0x80),禁止位值直转,逐位映射
vk::PipelineStageFlags2 mapped{};
if (use.readStage & vk::ShaderStageFlagBits::eVertex)
mapped |= vk::PipelineStageFlagBits2::eVertexShader;
if (use.readStage & vk::ShaderStageFlagBits::eFragment)
mapped |= vk::PipelineStageFlagBits2::eFragmentShader;
if (use.readStage & vk::ShaderStageFlagBits::eCompute)
mapped |= vk::PipelineStageFlagBits2::eComputeShader;
if (use.readStage & vk::ShaderStageFlagBits::eGeometry)
mapped |= vk::PipelineStageFlagBits2::eGeometryShader;
if (use.readStage & vk::ShaderStageFlagBits::eTessellationControl)
mapped |= vk::PipelineStageFlagBits2::eTessellationControlShader;
if (use.readStage & vk::ShaderStageFlagBits::eTessellationEvaluation)
mapped |= vk::PipelineStageFlagBits2::eTessellationEvaluationShader;
if (!mapped) {
ENGINE_FATAL("RenderGraph: ShaderRead stage not mappable: {}",
static_cast<u32>(use.readStage));
}
return mapped;
}
case ResourceUse::TransferSrc:
case ResourceUse::TransferDst:
return vk::PipelineStageFlagBits2::eTransfer;
+6
View File
@@ -58,6 +58,12 @@ namespace Engine {
{
auto proj = glm::perspective(glm::radians(45.0f), aspect, nearZ, farZ);
proj[1][1] *= -1.0f; // Vulkan Y 翻转
// Vulkan clip 深度 [0,1]:z' = 0.5*z_clip + 0.5*w_clip。
// perspective 的 w_clip = -z_eye,偏移折算进 z 系数
//(注意:与 ortho 的公式不同,ortho 的 w=1 才能直接加到 [3][2])
proj[2][2] = proj[2][2] * 0.5f - 0.5f;
proj[3][2] *= 0.5f;
return proj;
}
+42
View File
@@ -36,6 +36,48 @@ Model::Model(Device& device, MemoryAllocator& allocator,
Model::~Model() = default;
Model::Model(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
ProceduralTag, f32 halfSize, const std::array<u8, 3>& color)
: m_Device(device)
, m_Allocator(allocator)
, m_DescriptorManager(descriptorManager)
, m_TextureLayout(textureLayout)
{
// 纯色 1x1 贴图
auto tex = Texture::CreateSolidColor(device, allocator, color[0], color[1], color[2]);
// XZ 平面,法线 +Y,从上往下看 CCW 绕序
const f32 h = halfSize;
std::vector<Vertex> vertices = {
{{-h, 0.0f, -h}, {0, 1, 0}, {0.0f, 0.0f}},
{{-h, 0.0f, h}, {0, 1, 0}, {0.0f, 0.0f}},
{{ h, 0.0f, h}, {0, 1, 0}, {0.0f, 0.0f}},
{{ h, 0.0f, -h}, {0, 1, 0}, {0.0f, 0.0f}},
};
const std::vector<u32> indices = {0, 1, 2, 0, 2, 3};
auto mat = CreateScope<Material>(
m_Device, m_DescriptorManager, m_TextureLayout, tex.get());
m_Meshes.push_back(CreateScope<Mesh>(
m_Device, m_Allocator, vertices, indices, std::move(mat)));
// 贴图生命周期随模型
m_DefaultTexture = std::move(tex);
}
Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout textureLayout,
f32 halfSize, u8 r, u8 g, u8 b)
{
return Scope<Model>(new Model(device, allocator, descriptorManager,
textureLayout, ProceduralTag{},
halfSize, std::array<u8, 3>{r, g, b}));
}
void Model::LoadModel(const std::string& path)
{
m_ModelPath = path;
+225 -16
View File
@@ -40,6 +40,7 @@ namespace Engine
struct UniformBufferObject {
glm::mat4 view;
glm::mat4 proj;
glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵)
glm::vec4 ambientColor;
Light lights[4];
};
@@ -47,7 +48,18 @@ namespace Engine
constexpr u32 kUboBinding = 0;
constexpr u32 kUBOSetIndex = 0;
constexpr u32 kTextureSetIndex = 1;
constexpr u32 kShadowSetIndex = 2;
constexpr u32 kMaxLights = 4;
// Shadow pass:push constant = { lightVP, model }
constexpr u32 kShadowMapSize = 2048;
constexpr u32 kShadowPushSize = static_cast<u32>(2 * sizeof(glm::mat4));
// 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应)
constexpr f32 kLightOrthoExtent = 15.0f;
constexpr f32 kLightDistance = 30.0f;
constexpr f32 kLightNear = 1.0f;
constexpr f32 kLightFar = 80.0f;
}
Renderer::Renderer(Window& window)
@@ -75,9 +87,20 @@ namespace Engine
};
m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings);
// 6. PipelineLayout:两个 set
// 5.5 Set 2: shadow map(fragment 采样)
std::vector<DescriptorBinding> shadowBindings = {
{0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment},
};
m_ShadowSetLayout = m_DescriptorManager->CreateLayout(shadowBindings);
// 6. PipelineLayout:三个 set;push constant 上限 128B
//(Shadow pass 的 {lightVP, model} 用满;forward shader block 只用前 64B)
m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout(
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
// Shadow pass 独立 layout:无 set,128B push constant
m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout(
std::vector<vk::DescriptorSetLayout>{}, kShadowPushSize);
// 7. ShaderManager(VK_EXT_shader_object)
m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
@@ -86,19 +109,43 @@ namespace Engine
auto vert = m_ShaderManager->CreateFromFile(
shaderDir + "simple_shader.vert.spv",
vk::ShaderStageFlagBits::eVertex,
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
auto frag = m_ShaderManager->CreateFromFile(
shaderDir + "simple_shader.frag.spv",
vk::ShaderStageFlagBits::eFragment,
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
m_VertShader = vert.Handle;
m_FragShader = frag.Handle;
if (!m_VertShader || !m_FragShader) {
// shadow shader:depth-only,无 set
auto shadowVert = m_ShaderManager->CreateFromFile(
shaderDir + "shadow.vert.spv",
vk::ShaderStageFlagBits::eVertex, {}, kShadowPushSize);
auto shadowFrag = m_ShaderManager->CreateFromFile(
shaderDir + "shadow.frag.spv",
vk::ShaderStageFlagBits::eFragment, {}, kShadowPushSize);
m_ShadowVertShader = shadowVert.Handle;
m_ShadowFragShader = shadowFrag.Handle;
if (!m_VertShader || !m_FragShader || !m_ShadowVertShader || !m_ShadowFragShader) {
ENGINE_FATAL("Failed to create shader objects");
}
// 阴影采样器:clamp-to-border 白色 = 光视锥之外无阴影(省 shader 出界分支)
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_FALSE);
m_ShadowSampler = m_Device->GetDevice().createSampler(shadowSamplerInfo);
// 8. Swapchain(只管呈现;深度图由 RenderGraph 创建)
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, m_Window);
m_Extent = m_Swapchain->GetExtent();
@@ -113,17 +160,24 @@ namespace Engine
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
m_UBODescriptorSets.resize(kMaxFramesInFlight);
m_ShadowDescriptorSets.resize(kMaxFramesInFlight);
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
m_UBODescriptorSets[slot] =
m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout);
m_DescriptorManager->UpdateUniformBuffer(
m_UBODescriptorSets[slot], kUboBinding,
m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize());
m_ShadowDescriptorSets[slot] =
m_DescriptorManager->AllocateDescriptorSet(m_ShadowSetLayout);
}
// 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定)
BuildGraph();
// Compile 已分配物理镜像:把各槽位的阴影图 view 绑进描述符
UpdateShadowDescriptors();
ENGINE_INFO("Renderer initialized");
}
@@ -131,16 +185,27 @@ namespace Engine
{
m_Device->GetDevice().waitIdle();
const auto& vkDevice = m_Device->GetDevice();
if (m_VertShader) {
m_Device->GetDevice().destroyShaderEXT(m_VertShader);
vkDevice.destroyShaderEXT(m_VertShader);
}
if (m_FragShader) {
m_Device->GetDevice().destroyShaderEXT(m_FragShader);
vkDevice.destroyShaderEXT(m_FragShader);
}
if (m_ShadowVertShader) {
vkDevice.destroyShaderEXT(m_ShadowVertShader);
}
if (m_ShadowFragShader) {
vkDevice.destroyShaderEXT(m_ShadowFragShader);
}
m_Device->GetDevice().destroyPipelineLayout(m_PipelineLayout);
m_Device->GetDevice().destroyDescriptorSetLayout(m_UBOSetLayout);
m_Device->GetDevice().destroyDescriptorSetLayout(m_TextureSetLayout);
vkDevice.destroySampler(m_ShadowSampler);
vkDevice.destroyPipelineLayout(m_PipelineLayout);
vkDevice.destroyPipelineLayout(m_ShadowPipelineLayout);
vkDevice.destroyDescriptorSetLayout(m_UBOSetLayout);
vkDevice.destroyDescriptorSetLayout(m_TextureSetLayout);
vkDevice.destroyDescriptorSetLayout(m_ShadowSetLayout);
}
void Renderer::BuildGraph()
@@ -162,6 +227,102 @@ namespace Engine
m_Depth = m_RenderGraph->CreateTexture(
"MainDepth", RG::TextureDesc{depthFormat});
// 阴影图:固定尺寸正交深度图(D32 采样)
m_ShadowMap = m_RenderGraph->CreateTexture(
"ShadowMap",
RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize});
// ---- Shadow pass:光源视角写深度(所有渲染体都写入,不做实体级开关——
// castShadow 应由材质/实体数据驱动剔除,而非 pass 逻辑特判)----
m_RenderGraph->AddPass("Shadow",
[this](RG::RenderGraphContext& ctx) {
const vk::CommandBuffer cmd = ctx.cmd;
vk::RenderingAttachmentInfo depthAttachment;
depthAttachment.setImageView(ctx.View(m_ShadowMap))
.setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eStore)
.setClearValue(vk::ClearValue(
vk::ClearDepthStencilValue(1.0f, 0)));
vk::RenderingInfo renderingInfo;
renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}})
.setLayerCount(1)
.setPDepthAttachment(&depthAttachment);
cmd.beginRendering(renderingInfo);
std::array<vk::ShaderStageFlagBits, 2> stages = {
vk::ShaderStageFlagBits::eVertex,
vk::ShaderStageFlagBits::eFragment
};
std::array<vk::ShaderEXT, 2> shaders = {m_ShadowVertShader, m_ShadowFragShader};
cmd.bindShadersEXT(stages, shaders);
// ---- 动态状态(depth-only)----
cmd.setRasterizerDiscardEnable(VK_FALSE);
cmd.setDepthTestEnable(VK_TRUE);
cmd.setDepthWriteEnable(VK_TRUE);
cmd.setDepthCompareOp(vk::CompareOp::eLess);
cmd.setDepthBoundsTestEnable(VK_FALSE);
cmd.setStencilTestEnable(VK_FALSE);
// 深度 bias 压平 shadow acne(NDC 量级)
cmd.setDepthBiasEnable(VK_TRUE);
cmd.setDepthBias(0.0025f, 0.0f, 0.003f);
cmd.setPolygonModeEXT(vk::PolygonMode::eFill);
cmd.setCullMode(vk::CullModeFlagBits::eNone);
cmd.setFrontFace(vk::FrontFace::eCounterClockwise);
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
cmd.setPrimitiveRestartEnable(VK_FALSE);
auto binding = Vertex::GetBindingDescription();
auto attributes = Vertex::GetAttributeDescriptions();
cmd.setVertexInputEXT(binding, attributes);
cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1);
vk::SampleMask sampleMask = 0xFFFFFFFF;
cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask);
cmd.setAlphaToCoverageEnableEXT(VK_FALSE);
cmd.setAlphaToOneEnableEXT(VK_FALSE);
vk::Viewport viewport(0.0f, 0.0f,
static_cast<float>(kShadowMapSize),
static_cast<float>(kShadowMapSize),
0.0f, 1.0f);
cmd.setViewportWithCount(viewport);
vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize});
cmd.setScissorWithCount(scissor);
// ---- 绘制:push {lightVP, model},与 Forward 用同一份 world 矩阵 ----
struct ShadowPush {
glm::mat4 lightVP;
glm::mat4 model;
};
m_BoundScene->Each<TransformComponent, MeshRendererComponent>(
[&](Entity, TransformComponent& transform,
MeshRendererComponent& meshRenderer) {
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
if (!model) return;
const ShadowPush push{m_LightSpace, transform.world};
cmd.pushConstants(m_ShadowPipelineLayout,
vk::ShaderStageFlagBits::eVertex,
0, kShadowPushSize, &push);
for (auto& mesh : model->GetMeshes()) {
mesh->Draw(cmd);
}
});
cmd.endRendering();
})
.Write(m_ShadowMap, RG::ResourceUse::DepthAttachment);
// ---- Forward pass:直接渲染到 swapchain ----
// 场景数据每帧经 m_BoundScene 从 ECS 读取
m_RenderGraph->AddPass("Forward",
@@ -203,10 +364,13 @@ namespace Engine
std::array<vk::ShaderEXT, 2> shaders = {m_VertShader, m_FragShader};
cmd.bindShadersEXT(stages, shaders);
// Set 0:本帧槽位的 UBO
// Set 0:本帧槽位的 UBO;Set 2:本帧槽位的阴影图
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
m_PipelineLayout, kUBOSetIndex,
m_UBODescriptorSets[ctx.frame.frameSlot], {});
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
m_PipelineLayout, kShadowSetIndex,
m_ShadowDescriptorSets[ctx.frame.frameSlot], {});
// ---- 动态状态 ----
cmd.setRasterizerDiscardEnable(VK_FALSE);
@@ -287,7 +451,9 @@ namespace Engine
cmd.endRendering();
})
.Write(m_Backbuffer, RG::ResourceUse::ColorAttachment)
.Write(m_Depth, RG::ResourceUse::DepthAttachment);
.Write(m_Depth, RG::ResourceUse::DepthAttachment)
.Read(m_ShadowMap, RG::ResourceUse::ShaderRead,
vk::ShaderStageFlagBits::eFragment);
// ---- Present pass:transition 到 ePresentSrcKHR ----
m_RenderGraph->AddPass("Present",
@@ -313,6 +479,20 @@ namespace Engine
// RG 重编译:物理镜像按新尺寸轮换
m_RenderGraph->SetTargetExtent(m_Extent);
// 重编译后物理镜像可能变化,阴影描述符重新绑定
UpdateShadowDescriptors();
}
void Renderer::UpdateShadowDescriptors()
{
// 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
m_DescriptorManager->UpdateTexture(
m_ShadowDescriptorSets[slot], 0,
m_ShadowSampler,
m_RenderGraph->GetPhysicalView(m_ShadowMap, slot));
}
}
void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets)
@@ -345,13 +525,18 @@ namespace Engine
ubo.ambientColor = scene.GetSettings().ambientColor;
u32 lightCount = 0;
glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射
scene.Each<LightComponent>([&](Entity, LightComponent& light) {
if (lightCount >= kMaxLights) return;
ubo.lights[lightCount].direction =
glm::length(light.direction) > 0.0f
? glm::vec4(glm::normalize(light.direction), 0.0f)
: glm::vec4(0.0f);
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) {
@@ -359,6 +544,30 @@ namespace Engine
ubo.lights[i].color = glm::vec4(0.0f);
}
// ---- 阴影光空间矩阵:写入(Shadow pass)与采样(Forward UBO)共用,
// 从根上排除任何"旋转/镜像不一致"的可能 ----
// 光源位置在光方向的反方向远处,看向场景中心。
{
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_LightSpace = lightProj * lightView;
ubo.lightSpace = m_LightSpace;
}
// 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮),
// 再写 UBO,写-读竞争关闭。
m_SyncManager->WaitFence(frameSlot);
+28 -10
View File
@@ -28,8 +28,31 @@ namespace Engine
return;
}
m_Width = static_cast<u32>(width);
m_Height = static_cast<u32>(height);
InitFromPixels(pixels, static_cast<u32>(width), static_cast<u32>(height));
stbi_image_free(pixels);
ENGINE_INFO("Texture loaded: {} ({}x{})", path, m_Width, m_Height);
}
Texture::Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
const std::array<u8, 4>& rgba)
: m_Device(device) , m_Allocator(allocator)
{
const u8 pixels[4] = { rgba[0], rgba[1], rgba[2], rgba[3] };
InitFromPixels(pixels, 1, 1);
}
Scope<Texture> Texture::CreateSolidColor(Device& device, MemoryAllocator& allocator,
u8 r, u8 g, u8 b)
{
return Scope<Texture>(new Texture(device, allocator, ProceduralTag{}, {r, g, b, 255}));
}
void Texture::InitFromPixels(const void* pixels, u32 width, u32 height)
{
m_Width = width;
m_Height = height;
vk::DeviceSize imageSize = static_cast<vk::DeviceSize>(width) *
static_cast<vk::DeviceSize>(height) * 4; // RGBA8
@@ -42,7 +65,7 @@ namespace Engine
vk::Buffer stagingBuffer = m_Device.GetDevice().createBuffer(stagingInfo);
auto stagingReqs = m_Device.GetDevice().getBufferMemoryRequirements(stagingBuffer);
auto stagingAlloc = allocator.Allocate(
auto stagingAlloc = m_Allocator.Allocate(
stagingReqs,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent);
@@ -52,8 +75,6 @@ namespace Engine
// 3. 拷贝像素到 Staging Buffer
std::memcpy(stagingAlloc.MappedPtr, pixels, static_cast<size_t>(imageSize));
stbi_image_free(pixels);
// 4. 创建 Device Local Image
vk::ImageCreateInfo imageInfo;
imageInfo.setImageType(vk::ImageType::e2D)
@@ -70,7 +91,7 @@ namespace Engine
m_Image = m_Device.GetDevice().createImage(imageInfo);
auto imageReqs = m_Device.GetDevice().getImageMemoryRequirements(m_Image);
auto imageAlloc = allocator.Allocate(imageReqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
auto imageAlloc = m_Allocator.Allocate(imageReqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
m_ImageAllocation = imageAlloc;
@@ -185,12 +206,9 @@ namespace Engine
m_Device.GetDevice().destroyFence(fence);
m_Device.GetDevice().destroyCommandPool(pool);
m_Device.GetDevice().destroyBuffer(stagingBuffer);
allocator.Free(stagingAlloc);
m_Allocator.Free(stagingAlloc);
// staging 内存由 MemoryAllocator 统一释放
ENGINE_INFO("Texture loaded: {} ({}x{})", path, m_Width, m_Height);
// 显存占用 = 宽 * 高 * 4 字节(RGBA8)
m_TextureBytes = static_cast<u64>(m_Width) * m_Height * 4;
STATS.AddTexture(m_TextureBytes);