diff --git a/App/main.cpp b/App/main.cpp index 70d0c12..4ccc2dc 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -28,7 +28,7 @@ class SandboxApp final : public Application { public: SandboxApp() - : Application("HelloVulkan", 1280, 720) + : Application("HelloVulkan", 800, 600) { } @@ -42,29 +42,39 @@ protected: // 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄) auto backpack = GetAssets().LoadModel("assets/models/backpack/backpack.obj"); + // 地面:程序化纯色平面(半边长 15,中心 y=-2) + auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); + + m_Ground = scene.CreateEntity("Ground"); + m_Ground.AddComponent() + .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)) + .Compose(); + m_Ground.AddComponent(MeshRendererComponent{ground}); + // 相机实体(primary = 当前渲染视角) m_Camera = scene.CreateEntity("MainCamera"); auto& camera = m_Camera.AddComponent(); camera.primary = true; camera.camera.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f)); - // 背包实体 × 3 + // 背包实体 × 3(悬浮在地面上方) for (int i = 0; i < 3; ++i) { auto e = scene.CreateEntity("Backpack" + std::to_string(i)); - e.AddComponent().world = glm::translate( - glm::mat4(1.0f), - glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f)); + e.AddComponent() + .SetPosition(glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f)) + .SetScale(glm::vec3(0.5f)) + .Compose(); e.AddComponent(MeshRendererComponent{backpack}); m_Spinners.push_back(e); } - // 灯光实体 + // 灯光实体(第一个灯 = 阴影光源) scene.CreateEntity("KeyLight") .AddComponent( - LightComponent{glm::vec3(-0.5f, -1.0f, -0.3f), + LightComponent{glm::vec3(-0.5f, -0.6f, -0.35f), glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)}); scene.CreateEntity("FillLight") @@ -81,18 +91,13 @@ protected: m_Camera.GetComponent() .camera.Update(GetWindow(), deltaTime); - // 旋转动画:直接写实体组件 + // 旋转动画:编辑 Transform 分量后 Compose 重算 world for (size_t i = 0; i < m_Spinners.size(); ++i) { - f32 angle = m_Time * glm::radians(60.0f) + static_cast(i) * 0.7f; - - auto& transform = m_Spinners[i].GetComponent(); - glm::mat4 t = glm::translate( - glm::mat4(1.0f), - glm::vec3((static_cast(i) - 1.0f) * 1.5f, 0.0f, 0.0f)); - glm::mat4 r = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 1.0f, 0.0f)); - glm::mat4 s = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f)); - - transform.world = t * r * s; + auto& t = m_Spinners[i].GetComponent(); + t.position = glm::vec3((static_cast(i) - 1.0f) * 1.5f, 0.0f, 0.0f); + t.rotation.y = m_Time * 60.0f + static_cast(i) * 40.0f; + t.scale = glm::vec3(0.5f); + t.Compose(); } } @@ -115,6 +120,7 @@ protected: private: EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空 + EntityHandle m_Ground{}; std::vector m_Spinners; f32 m_Time = 0.0f; }; diff --git a/Engine/include/Engine/Assets/AssetManager.h b/Engine/include/Engine/Assets/AssetManager.h index 9a5a579..2daf81c 100644 --- a/Engine/include/Engine/Assets/AssetManager.h +++ b/Engine/include/Engine/Assets/AssetManager.h @@ -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& Models() { return m_Models; } // 后续扩展点:TextureHandle / MaterialHandle / ShaderHandle diff --git a/Engine/include/Engine/ECS/Components.h b/Engine/include/Engine/ECS/Components.h index 5b30cce..5151ee6 100644 --- a/Engine/include/Engine/ECS/Components.h +++ b/Engine/include/Engine/ECS/Components.h @@ -14,6 +14,7 @@ #include #include +#include #include 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 资产。 diff --git a/Engine/include/Engine/RenderGraph/RenderGraph.h b/Engine/include/Engine/RenderGraph/RenderGraph.h index 51d9ae0..b8834ff 100644 --- a/Engine/include/Engine/RenderGraph/RenderGraph.h +++ b/Engine/include/Engine/RenderGraph/RenderGraph.h @@ -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 { diff --git a/Engine/include/Engine/Renderer/Model.h b/Engine/include/Engine/Renderer/Model.h index dc2aae0..730baba 100644 --- a/Engine/include/Engine/Renderer/Model.h +++ b/Engine/include/Engine/Renderer/Model.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,15 @@ namespace Engine DescriptorManager& descriptorManager, vk::DescriptorSetLayout textureLayout, const std::string& path); + + // 程序化模型:单位法线朝上的 XZ 平面(纯色贴图),用于地面等。 + // size 为半边长(平面总边长 = 2*size)。 + struct ProceduralTag {}; + static Scope 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>& GetMeshes() const { return m_Meshes; } private: + Model(Device& device, MemoryAllocator& allocator, + DescriptorManager& descriptorManager, + vk::DescriptorSetLayout textureLayout, + ProceduralTag, f32 halfSize, const std::array& color); + void LoadModel(const std::string& path); void ProcessNode(aiNode* node, const aiScene* scene); Scope ProcessMesh(aiMesh* mesh, const aiScene* scene); @@ -60,5 +75,4 @@ namespace Engine }; } // namespace Engine - #endif //HELLOVULKAN_MODEL_H diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index 305e29a..e4f99f5 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -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 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 m_UBODescriptorSets; // 每帧槽位一个 + vk::DescriptorSetLayout m_ShadowSetLayout; + std::vector m_UBODescriptorSets; // 每帧槽位一个 + std::vector 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; diff --git a/Engine/include/Engine/Vulkan/Texture.h b/Engine/include/Engine/Vulkan/Texture.h index 5247033..8d7bf52 100644 --- a/Engine/include/Engine/Vulkan/Texture.h +++ b/Engine/include/Engine/Vulkan/Texture.h @@ -9,6 +9,7 @@ #include #include +#include #include #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 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& 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; diff --git a/Engine/src/Assets/AssetManager.cpp b/Engine/src/Assets/AssetManager.cpp index d0de0d2..08c8ea2 100644 --- a/Engine/src/Assets/AssetManager.cpp +++ b/Engine/src/Assets/AssetManager.cpp @@ -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 { + return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors, + m_TextureLayout, halfSize, r, g, b); + }); + } + } // namespace Engine::Assets diff --git a/Engine/src/RenderGraph/RenderGraph.cpp b/Engine/src/RenderGraph/RenderGraph.cpp index e0dcb98..3c132c9 100644 --- a/Engine/src/RenderGraph/RenderGraph.cpp +++ b/Engine/src/RenderGraph/RenderGraph.cpp @@ -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( - static_cast(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(use.readStage)); + } + return mapped; + } case ResourceUse::TransferSrc: case ResourceUse::TransferDst: return vk::PipelineStageFlagBits2::eTransfer; diff --git a/Engine/src/Renderer/Camera.cpp b/Engine/src/Renderer/Camera.cpp index 12c0c20..0587262 100644 --- a/Engine/src/Renderer/Camera.cpp +++ b/Engine/src/Renderer/Camera.cpp @@ -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; } diff --git a/Engine/src/Renderer/Model.cpp b/Engine/src/Renderer/Model.cpp index 0ef4b9d..4585085 100644 --- a/Engine/src/Renderer/Model.cpp +++ b/Engine/src/Renderer/Model.cpp @@ -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& 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 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 indices = {0, 1, 2, 0, 2, 3}; + + auto mat = CreateScope( + m_Device, m_DescriptorManager, m_TextureLayout, tex.get()); + + m_Meshes.push_back(CreateScope( + m_Device, m_Allocator, vertices, indices, std::move(mat))); + + // 贴图生命周期随模型 + m_DefaultTexture = std::move(tex); +} + +Scope Model::CreatePlane(Device& device, MemoryAllocator& allocator, + DescriptorManager& descriptorManager, + vk::DescriptorSetLayout textureLayout, + f32 halfSize, u8 r, u8 g, u8 b) +{ + return Scope(new Model(device, allocator, descriptorManager, + textureLayout, ProceduralTag{}, + halfSize, std::array{r, g, b})); +} + void Model::LoadModel(const std::string& path) { m_ModelPath = path; diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 373079c..33c4600 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -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(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 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{}, kShadowPushSize); // 7. ShaderManager(VK_EXT_shader_object) m_ShaderManager = CreateScope(*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(*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 stages = { + vk::ShaderStageFlagBits::eVertex, + vk::ShaderStageFlagBits::eFragment + }; + std::array 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(kShadowMapSize), + static_cast(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( + [&](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 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([&](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); diff --git a/Engine/src/Vulkan/Texture.cpp b/Engine/src/Vulkan/Texture.cpp index cf3f8e4..308a27a 100644 --- a/Engine/src/Vulkan/Texture.cpp +++ b/Engine/src/Vulkan/Texture.cpp @@ -28,8 +28,31 @@ namespace Engine return; } - m_Width = static_cast(width); - m_Height = static_cast(height); + InitFromPixels(pixels, static_cast(width), static_cast(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& 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::CreateSolidColor(Device& device, MemoryAllocator& allocator, + u8 r, u8 g, u8 b) + { + return Scope(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(width) * static_cast(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(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(m_Width) * m_Height * 4; STATS.AddTexture(m_TextureBytes); diff --git a/shaders/shadow.frag b/shaders/shadow.frag new file mode 100644 index 0000000..e4d6053 --- /dev/null +++ b/shaders/shadow.frag @@ -0,0 +1,7 @@ +#version 450 + +// Shadow pass fragment:depth-only,无输出。 +// 深度写入由深度附件 + 光栅化完成(含管线深度 bias)。 + +void main() { +} diff --git a/shaders/shadow.vert b/shaders/shadow.vert new file mode 100644 index 0000000..6becc0e --- /dev/null +++ b/shaders/shadow.vert @@ -0,0 +1,17 @@ +#version 450 + +// Shadow pass vertex:光源视角写深度。 +// push constant = { lightVP, model },与 Forward pass 用同一份 +// lightVP(UBO.lightSpace)和同一份 model(TransformComponent.world), +// 保证阴影贴图与主视角几何严格一致。 + +layout(location = 0) in vec3 inPosition; + +layout(push_constant) uniform ShadowPush { + mat4 lightVP; + mat4 model; +} pc; + +void main() { + gl_Position = pc.lightVP * pc.model * vec4(inPosition, 1.0); +} diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 66132b0..375fe9c 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -10,17 +10,34 @@ struct Light { layout(location = 0) in vec2 fragUV; layout(location = 1) in vec3 fragNormal; layout(location = 2) in vec3 fragWorldPos; +layout(location = 3) in vec3 fragLightSpacePos; layout(location = 0) out vec4 outColor; layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 view; mat4 proj; + mat4 lightSpace; vec4 ambientColor; Light lights[MAX_LIGHTS]; } ubo; layout(set = 1, binding = 0) uniform sampler2D texSampler; +layout(set = 2, binding = 0) uniform sampler2D shadowMap; + +// 第 0 盏灯(KeyLight)的阴影因子:0.3 影中 / 1.0 受光。 +// 采样器为 clamp-to-border 白色:光视锥外自动 1.0(无阴影)。 +float ShadowFactor(vec3 N) { + // NDC [-1,1] → UV [0,1](ortho 的 w=1,无需透视除法) + vec2 uv = fragLightSpacePos.xy * 0.5 + 0.5; + float closest = texture(shadowMap, uv).r; + + // 斜率缩放 bias:掠射面用更大的 bias 压 acne + vec3 L = normalize(-ubo.lights[0].direction.xyz); + float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012); + + return (fragLightSpacePos.z - bias > closest) ? 0.3 : 1.0; +} void main() { vec3 albedo = texture(texSampler, fragUV).rgb; @@ -31,8 +48,9 @@ void main() { for (int i = 0; i < MAX_LIGHTS; ++i) { vec3 L = normalize(-ubo.lights[i].direction.xyz); float diff = max(dot(N, L), 0.0); - result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo; + float shade = (i == 0) ? ShadowFactor(N) : 1.0; + result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo * shade; } outColor = vec4(result, 1.0); -} \ No newline at end of file +} diff --git a/shaders/simple_shader.vert b/shaders/simple_shader.vert index 786be0f..ab0cdf3 100644 --- a/shaders/simple_shader.vert +++ b/shaders/simple_shader.vert @@ -14,6 +14,7 @@ layout(location = 2) in vec2 inUV; layout(location = 0) out vec2 fragUV; layout(location = 1) out vec3 fragNormal; layout(location = 2) out vec3 fragWorldPos; +layout(location = 3) out vec3 fragLightSpacePos; layout(push_constant) uniform PushConstants { mat4 model; @@ -22,6 +23,7 @@ layout(push_constant) uniform PushConstants { layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 view; mat4 proj; + mat4 lightSpace; vec4 ambientColor; Light lights[MAX_LIGHTS]; } ubo; @@ -31,6 +33,8 @@ void main() { gl_Position = ubo.proj * ubo.view * worldPos; fragWorldPos = worldPos.xyz; + // 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵) + fragLightSpacePos = (ubo.lightSpace * worldPos).xyz; mat3 normalMatrix = mat3(transpose(inverse(pc.model))); fragNormal = normalize(normalMatrix * inNormal);