Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
960bdf2662 | ||
|
|
2b875c9284 | ||
|
|
9cc36af3f0 | ||
|
|
d2b878c077 | ||
|
|
bd4ac6aa12 | ||
|
|
6e107ac2ba | ||
|
|
9269afaa18 | ||
|
|
fa25cb3348 | ||
|
|
4b5990e6c8 | ||
|
|
89bf4256e8 | ||
|
|
2c4efc5165 |
+38
-22
@@ -28,15 +28,17 @@
|
|||||||
using namespace Engine;
|
using namespace Engine;
|
||||||
using namespace Engine::ECS;
|
using namespace Engine::ECS;
|
||||||
|
|
||||||
class SandboxApp final : public Application
|
namespace
|
||||||
{
|
{
|
||||||
public:
|
class SandboxApp final : public Application
|
||||||
|
{
|
||||||
|
public:
|
||||||
SandboxApp()
|
SandboxApp()
|
||||||
: Application("HelloVulkan", 800, 600)
|
: Application("HelloVulkan", 800, 600)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// ---- 生命周期 ----
|
// ---- 生命周期 ----
|
||||||
|
|
||||||
void OnInit() override
|
void OnInit() override
|
||||||
@@ -46,10 +48,9 @@ protected:
|
|||||||
// ImGui 叠加层(框架只做管线桥接,UI 内容全部在这里定义)
|
// ImGui 叠加层(框架只做管线桥接,UI 内容全部在这里定义)
|
||||||
EnableImGui();
|
EnableImGui();
|
||||||
|
|
||||||
// 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄)
|
// 模型资产(PBR 验证场景:backpack 为旧式贴图资产,
|
||||||
// SunTemple:FBX + DDS 压缩贴图(DdsLoader 验证场景)
|
// 退化为电介质因子路径——albedo+normal,metallic=0/roughness=0.5)
|
||||||
const auto model = GetAssets().LoadModel("assets/models/viking_room/viking_room.obj");
|
const auto model = GetAssets().LoadModel("assets/models/backpack/backpack.obj");
|
||||||
const auto temple = GetAssets().LoadModel("assets/models/SunTemple/SunTemple.fbx");
|
|
||||||
|
|
||||||
m_Spinners.push_back(scene.CreateEntity("backpack"));
|
m_Spinners.push_back(scene.CreateEntity("backpack"));
|
||||||
m_Spinners[0].AddComponent<MeshRendererComponent>(MeshRendererComponent{model});
|
m_Spinners[0].AddComponent<MeshRendererComponent>(MeshRendererComponent{model});
|
||||||
@@ -69,27 +70,40 @@ protected:
|
|||||||
m_Camera.GetComponent<TransformComponent>()
|
m_Camera.GetComponent<TransformComponent>()
|
||||||
.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
||||||
|
|
||||||
// 神庙实体 × 1( FBX 单位较大,缩放到场景尺度)
|
// 灯光实体(第一个方向光 = 阴影光源)
|
||||||
{
|
|
||||||
auto e = scene.CreateEntity("SunTemple");
|
|
||||||
e.GetComponent<TransformComponent>()
|
|
||||||
.SetScale(glm::vec3(0.05f));
|
|
||||||
e.AddComponent<MeshRendererComponent>(MeshRendererComponent{temple});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 灯光实体(第一个灯 = 阴影光源)
|
|
||||||
m_KeyLight = scene.CreateEntity("KeyLight");
|
m_KeyLight = scene.CreateEntity("KeyLight");
|
||||||
m_KeyLight.AddComponent<LightComponent>(
|
m_KeyLight.AddComponent<LightComponent>(
|
||||||
LightComponent{
|
LightComponent{
|
||||||
glm::vec3(-0.5f, -0.6f, -0.35f),
|
.type = LightComponent::Type::Directional,
|
||||||
glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)
|
.direction = glm::vec3(-0.5f, -0.6f, -0.35f),
|
||||||
|
.color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)
|
||||||
});
|
});
|
||||||
|
|
||||||
scene.CreateEntity("FillLight")
|
scene.CreateEntity("FillLight")
|
||||||
.AddComponent<LightComponent>(
|
.AddComponent<LightComponent>(
|
||||||
LightComponent{
|
LightComponent{
|
||||||
glm::vec3(0.5f, -0.5f, 0.3f),
|
.type = LightComponent::Type::Directional,
|
||||||
glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)
|
.direction = glm::vec3(0.5f, -0.5f, 0.3f),
|
||||||
|
.color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减)
|
||||||
|
auto fire = scene.CreateEntity("FirePitLight");
|
||||||
|
fire.GetComponent<TransformComponent>().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f));
|
||||||
|
fire.AddComponent<LightComponent>(
|
||||||
|
LightComponent{
|
||||||
|
.type = LightComponent::Type::Point,
|
||||||
|
.color = glm::vec4(1.0f, 0.6f, 0.3f, 2.0f), // 暖橙
|
||||||
|
.radius = 8.0f,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto cool = scene.CreateEntity("CoolFillLight");
|
||||||
|
cool.GetComponent<TransformComponent>().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f));
|
||||||
|
cool.AddComponent<LightComponent>(
|
||||||
|
LightComponent{
|
||||||
|
.type = LightComponent::Type::Point,
|
||||||
|
.color = glm::vec4(0.3f, 0.5f, 1.0f, 1.2f), // 冷蓝
|
||||||
|
.radius = 10.0f,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +272,7 @@ protected:
|
|||||||
|
|
||||||
void OnWindowClose(const Event& event) override
|
void OnWindowClose(const Event& event) override
|
||||||
{
|
{
|
||||||
|
SetImGuiEnabled(true);
|
||||||
m_ShowQuitConfirm = true;
|
m_ShowQuitConfirm = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +294,7 @@ protected:
|
|||||||
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
|
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_ShowQuitConfirm = false;
|
bool m_ShowQuitConfirm = false;
|
||||||
bool m_QuitConfirmed = false;
|
bool m_QuitConfirmed = false;
|
||||||
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
|
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
|
||||||
@@ -288,7 +303,8 @@ private:
|
|||||||
std::vector<EntityHandle> m_Spinners;
|
std::vector<EntityHandle> m_Spinners;
|
||||||
f32 m_Time = 0.0f;
|
f32 m_Time = 0.0f;
|
||||||
bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用)
|
bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用)
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Engine {
|
|||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
Application(std::string title, u32 width, u32 height);
|
Application(const std::string& title, u32 width, u32 height);
|
||||||
virtual ~Application();
|
virtual ~Application();
|
||||||
|
|
||||||
Application(const Application&) = delete;
|
Application(const Application&) = delete;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Engine {
|
|||||||
m_MouseCapture = std::move(mouse);
|
m_MouseCapture = std::move(mouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::SurfaceKHR CreateSurface(vk::Instance instance);
|
vk::SurfaceKHR CreateSurface(vk::Instance instance) const;
|
||||||
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
||||||
|
|
||||||
u32 GetWidth() const { return m_Width; }
|
u32 GetWidth() const { return m_Width; }
|
||||||
@@ -63,7 +63,7 @@ namespace Engine {
|
|||||||
// 输入事件系统
|
// 输入事件系统
|
||||||
|
|
||||||
bool IsKeyPressed(int key) const;
|
bool IsKeyPressed(int key) const;
|
||||||
void GetMouseDelta(float& dx, float& dy);
|
void GetMouseDelta(float& dx, float& dy) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -89,10 +89,16 @@ namespace Engine::ECS {
|
|||||||
bool primary = false;
|
bool primary = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 方向光
|
// 灯光。类型:
|
||||||
|
// Directional——方向即照射方向(无位置,阴影光源用第 0 盏方向光)
|
||||||
|
// Point——位置取实体 Transform(固有的),向四周发光 + 距离衰减
|
||||||
struct LightComponent {
|
struct LightComponent {
|
||||||
glm::vec3 direction{0.0f, -1.0f, 0.0f};
|
enum class Type : u8 { Directional = 0, Point = 1 };
|
||||||
glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f};
|
|
||||||
|
Type type = Type::Directional;
|
||||||
|
glm::vec3 direction{0.0f, -1.0f, 0.0f}; // 仅方向光
|
||||||
|
glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f}; // rgb = 颜色,a = 强度
|
||||||
|
f32 radius = 10.0f; // 仅点光:衰减半径
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine::ECS
|
} // namespace Engine::ECS
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// Cubemap:环境立方体贴图 + IBL 预积分资源(阶段②③,split-sum)。
|
||||||
|
//
|
||||||
|
// 从等距柱状 HDR(.hdr,polyhaven 等)生成,CPU 一次性预积分:
|
||||||
|
// env cube —— 重采样 6 面(Skybox 背景)
|
||||||
|
// irradiance cube —— 半球 Lambert 积分(PBR 漫反射 ambient)
|
||||||
|
// prefilter cube —— GGX 重要性采样 × mip 分级 roughness(镜面 IBL)
|
||||||
|
// BRDF LUT —— 2D 积分表(纯数学,与环境内容无关)
|
||||||
|
// 全部 fp16 上传,运行时采样零积分开销。
|
||||||
|
//
|
||||||
|
// 后续演进:CPU 预积分 → compute pass(动态环境零卡顿);本类的
|
||||||
|
// 方向表/上传路径届时整体迁移,接口不变。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_CUBEMAP_H
|
||||||
|
#define HELLOVULKAN_CUBEMAP_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
|
||||||
|
// GPU 纹理资源三元组(RHI 惯例):image/view/显存分配同生共死,
|
||||||
|
// 析构统一释放——杜绝"建了 image 丢了 view"类半套泄漏。
|
||||||
|
// 上传 helper 直接以此为单位产出,消费端只拿引用计数语义的整单元
|
||||||
|
struct EnvTexture {
|
||||||
|
vk::Image image{};
|
||||||
|
vk::ImageView view{};
|
||||||
|
MemoryAllocator::Allocation allocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Cubemap {
|
||||||
|
public:
|
||||||
|
// 从等距柱状 HDR 生成(CPU 重采样 + irradiance/prefilter/BRDF 预积分)
|
||||||
|
static Scope<Cubemap> CreateFromHdrEquirect(Device& device,
|
||||||
|
MemoryAllocator& allocator,
|
||||||
|
const std::string& path,
|
||||||
|
u32 faceSize);
|
||||||
|
|
||||||
|
// 中性兜底(无 HDR 资产时):cube 均为常数色;BRDF LUT 仍为
|
||||||
|
// 真实积分(纯数学)。保证引用侧描述符恒有效(validation)
|
||||||
|
static Scope<Cubemap> CreateNeutral(Device& device,
|
||||||
|
MemoryAllocator& allocator,
|
||||||
|
const glm::vec3& rgb);
|
||||||
|
|
||||||
|
~Cubemap();
|
||||||
|
|
||||||
|
Cubemap(const Cubemap&) = delete;
|
||||||
|
Cubemap& operator=(const Cubemap&) = delete;
|
||||||
|
|
||||||
|
// ---- 消费端契约(SkyboxPass:背景;SceneGpu:PBR ambient)----
|
||||||
|
vk::ImageView GetView() const { return m_Env.view; } // 环境 cube
|
||||||
|
vk::ImageView GetIrradianceView() const { return m_Irr.view; } // 漫反射
|
||||||
|
vk::ImageView GetPrefilteredView() const { return m_Pref.view; } // 镜面
|
||||||
|
vk::ImageView GetBrdfLutView() const { return m_Lut.view; } // LUT
|
||||||
|
|
||||||
|
// cube 资源共享采样器(maxLod 覆盖 pre-filter mip 链)
|
||||||
|
vk::Sampler GetSampler() const { return m_EnvSampler; }
|
||||||
|
// LUT 专用采样器(clamp-to-edge):共享的 border 白采样器会在
|
||||||
|
// uv=1.0 处混入 50% 白色 border,导致高 roughness 镜面偏大
|
||||||
|
vk::Sampler GetBrdfLutSampler() const { return m_LutSampler; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Cubemap(Device& device, MemoryAllocator& allocator,
|
||||||
|
EnvTexture env, EnvTexture irr, EnvTexture pref,
|
||||||
|
EnvTexture lut, vk::Sampler envSampler, vk::Sampler lutSampler)
|
||||||
|
: m_Device(device), m_Allocator(allocator),
|
||||||
|
m_Env(env), m_Irr(irr), m_Pref(pref), m_Lut(lut),
|
||||||
|
m_EnvSampler(envSampler), m_LutSampler(lutSampler) {}
|
||||||
|
|
||||||
|
Device& m_Device;
|
||||||
|
MemoryAllocator& m_Allocator;
|
||||||
|
|
||||||
|
EnvTexture m_Env; // 环境 cube(Skybox 背景)
|
||||||
|
EnvTexture m_Irr; // 漫反射辐照度 cube
|
||||||
|
EnvTexture m_Pref; // pre-filtered cube(mip 分 roughness)
|
||||||
|
EnvTexture m_Lut; // BRDF 积分 LUT(2D)
|
||||||
|
vk::Sampler m_EnvSampler;
|
||||||
|
vk::Sampler m_LutSampler;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_CUBEMAP_H
|
||||||
@@ -1,46 +1,61 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/21.
|
// Created by atdunbg on 2026/9/21.
|
||||||
//
|
//
|
||||||
// Material:着色材质 = 一组纹理槽 + 一个描述符集。
|
// Material:PBR 材质 = 纹理槽组 + 因子参数 UBO + 一个描述符集。
|
||||||
//
|
//
|
||||||
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致):
|
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致):
|
||||||
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
||||||
// 2 specular(高光强度/颜色,线性) 3 emissive(自发光,sRGB)
|
// 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定)
|
||||||
|
// 3 emissive(自发光,sRGB) 4 ao(环境光遮蔽,线性)
|
||||||
|
// 5 材质参数 UBO(fragment,kParamsBinding)
|
||||||
//
|
//
|
||||||
// 纹理非拥有(Model 的纹理缓存持有);空槽由调用方传默认纹理
|
// 纹理非拥有(Model 的纹理缓存持有);缺槽由调用方传默认纹理。
|
||||||
//(白/平法线/黑/黑,Model 持有),shader 永远可以安全采样。
|
// 因子(metallicFactor/roughnessFactor)乘在 MR 贴图上:
|
||||||
|
// glTF(MR 合图 B=金属度语义)→ 因子取 glTF 声明(默认 1/1)
|
||||||
|
// OBJ(specular 工作流,独立 roughness 图或无图)→ metallic=0,
|
||||||
|
// roughness 因子按贴图有无/Ns 换算——金属度强制 0
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef HELLOVULKAN_MATERIAL_H
|
#ifndef HELLOVULKAN_MATERIAL_H
|
||||||
#define HELLOVULKAN_MATERIAL_H
|
#define HELLOVULKAN_MATERIAL_H
|
||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine {
|
||||||
{
|
|
||||||
class Device;
|
class Device;
|
||||||
|
class MemoryAllocator;
|
||||||
class DescriptorManager;
|
class DescriptorManager;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
class UniformBuffer;
|
||||||
|
|
||||||
class Material
|
// 材质因子(乘 MR 贴图;无贴图时即最终值)
|
||||||
{
|
struct MaterialParams {
|
||||||
|
glm::vec4 factors{0.0f, 0.5f, 0.0f, 0.0f}; // x=metallic y=roughness
|
||||||
|
};
|
||||||
|
|
||||||
|
class Material {
|
||||||
public:
|
public:
|
||||||
// 槽位序号(与 binding 对齐)
|
// 槽位序号(与 binding 对齐)
|
||||||
enum Slot : u32 {
|
enum Slot : u32 {
|
||||||
Albedo = 0,
|
Albedo = 0,
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
Specular = 2,
|
MetallicRoughness = 2,
|
||||||
Emissive = 3,
|
Emissive = 3,
|
||||||
kSlotCount = 4,
|
Ao = 4,
|
||||||
|
kSlotCount = 5, // 纹理槽数
|
||||||
|
kParamsBinding = 5, // 参数 UBO binding
|
||||||
};
|
};
|
||||||
|
|
||||||
Material(Device& device,
|
Material(Device& device, MemoryAllocator& allocator,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
vk::DescriptorSetLayout layout,
|
vk::DescriptorSetLayout layout,
|
||||||
const std::array<Texture*, kSlotCount>& textures);
|
const std::array<Texture*, kSlotCount>& textures,
|
||||||
|
const MaterialParams& params);
|
||||||
~Material();
|
~Material();
|
||||||
|
|
||||||
Material(const Material&) = delete;
|
Material(const Material&) = delete;
|
||||||
@@ -49,12 +64,14 @@ namespace Engine
|
|||||||
vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; }
|
vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; }
|
||||||
Texture* GetTexture(Slot slot) const { return m_Textures[slot]; }
|
Texture* GetTexture(Slot slot) const { return m_Textures[slot]; }
|
||||||
|
|
||||||
// 材质缓存键:同贴图组合共享一份(Model 层使用)
|
// 材质缓存键:同贴图组合+同因子共享一份(Model 层使用)
|
||||||
static std::string MakeCacheKey(const std::string paths[kSlotCount]);
|
static std::string MakeCacheKey(const std::string paths[kSlotCount],
|
||||||
|
const MaterialParams& params);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<Texture*, kSlotCount> m_Textures{};
|
std::array<Texture*, kSlotCount> m_Textures{};
|
||||||
vk::DescriptorSet m_DescriptorSet;
|
vk::DescriptorSet m_DescriptorSet;
|
||||||
|
Scope<UniformBuffer> m_ParamsBuffer; // per-slot 参数 ring(创建时写死)
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ namespace Engine
|
|||||||
|
|
||||||
Texture* GetOrLoadTexture(const std::string& path, bool srgb);
|
Texture* GetOrLoadTexture(const std::string& path, bool srgb);
|
||||||
|
|
||||||
// 材质缓存入口:同贴图组合共享同一 Material(一个描述符集),
|
// 材质缓存入口:同贴图组合+因子共享同一 Material(一个描述符集),
|
||||||
// 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。
|
// 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。
|
||||||
// paths 下标 = Material::Slot(albedo/normal/specular/emissive)
|
// paths 下标 = Material::Slot;params = metallic/roughness 因子
|
||||||
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount]);
|
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount],
|
||||||
|
const MaterialParams& params);
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
MemoryAllocator& m_Allocator;
|
MemoryAllocator& m_Allocator;
|
||||||
@@ -83,6 +84,7 @@ namespace Engine
|
|||||||
|
|
||||||
std::vector<Scope<Mesh>> m_Meshes;
|
std::vector<Scope<Mesh>> m_Meshes;
|
||||||
std::string m_ModelPath;
|
std::string m_ModelPath;
|
||||||
|
bool m_IsGltf = false; // glTF 家族:MR 合图语义 + 不翻转 UV
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace Engine {
|
|||||||
class DescriptorManager;
|
class DescriptorManager;
|
||||||
class ShaderManager;
|
class ShaderManager;
|
||||||
class UniformBuffer;
|
class UniformBuffer;
|
||||||
|
class Cubemap;
|
||||||
|
|
||||||
namespace Passes {
|
namespace Passes {
|
||||||
|
|
||||||
@@ -40,8 +41,8 @@ namespace Engine {
|
|||||||
|
|
||||||
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
|
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
|
||||||
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
|
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
|
||||||
vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo}
|
vk::DescriptorSetLayout textureSetLayout; // Set1 = {材质 4 槽 + 参数 UBO}
|
||||||
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图}
|
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图, irradiance cube}
|
||||||
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
|
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
|
||||||
vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B
|
vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B
|
||||||
|
|
||||||
@@ -60,6 +61,9 @@ namespace Engine {
|
|||||||
void BindShadowMaps(RG::RenderGraph& rg,
|
void BindShadowMaps(RG::RenderGraph& rg,
|
||||||
const std::array<RG::TextureHandle, kMaxCascades>& maps);
|
const std::array<RG::TextureHandle, kMaxCascades>& maps);
|
||||||
|
|
||||||
|
// 环境辐照度(Set2 binding 3):view 全程稳定,绑一次即可
|
||||||
|
void BindEnvironment(const Cubemap& env);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
DescriptorManager& m_Descriptors;
|
DescriptorManager& m_Descriptors;
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// SkyboxPass:环境背景(IBL 阶段①)。
|
||||||
|
//
|
||||||
|
// HDRColor 的 first writer:承担清屏职责(loadOp Clear),有环境贴图时
|
||||||
|
// 再画天空——Forward 随之改为 loadOp Load,几何在天空之上合成。
|
||||||
|
// 职责收口对齐 UE:sky pass 是场景渲染的第一步,无深度附件,
|
||||||
|
// 深度测试关(几何完全覆盖由 Forward 的深度决定)。
|
||||||
|
//
|
||||||
|
// 环境贴图生命周期:Renderer 持有 Cubemap(声明序先于 pass),
|
||||||
|
// 本 pass 只引用。描述符绑定一次(cube view 全程稳定,不随 RG 重编译)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SKYBOXPASS_H
|
||||||
|
#define HELLOVULKAN_SKYBOXPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class DescriptorManager;
|
||||||
|
class ShaderManager;
|
||||||
|
class Cubemap;
|
||||||
|
|
||||||
|
namespace Passes {
|
||||||
|
|
||||||
|
class SkyboxPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
// env 可为空(HDR 资产缺失):仅承担清屏,天空保持清屏色
|
||||||
|
SkyboxPass(Device& device, ShaderManager& shaders,
|
||||||
|
DescriptorManager& descriptors, FrameSceneData& frame,
|
||||||
|
RG::TextureHandle hdrColor, const Cubemap* env);
|
||||||
|
~SkyboxPass();
|
||||||
|
|
||||||
|
SkyboxPass(const SkyboxPass&) = delete;
|
||||||
|
SkyboxPass& operator=(const SkyboxPass&) = delete;
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& m_Device;
|
||||||
|
DescriptorManager& m_Descriptors;
|
||||||
|
FrameSceneData& m_Frame;
|
||||||
|
RG::TextureHandle m_HDRColor;
|
||||||
|
const Cubemap* m_Env; // 可空
|
||||||
|
|
||||||
|
vk::DescriptorSetLayout m_SetLayout; // Set0 = {samplerCube}
|
||||||
|
vk::PipelineLayout m_Layout; // push = invVP 64B
|
||||||
|
vk::ShaderEXT m_Vert{}, m_Frag{};
|
||||||
|
std::array<vk::DescriptorSet, kMaxFramesInFlight> m_Sets{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Passes
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SKYBOXPASS_H
|
||||||
@@ -41,6 +41,7 @@ namespace Engine {
|
|||||||
class UniformBuffer;
|
class UniformBuffer;
|
||||||
class SceneRenderer;
|
class SceneRenderer;
|
||||||
class VulkanAssetUploader;
|
class VulkanAssetUploader;
|
||||||
|
class Cubemap;
|
||||||
class ImGuiLayer;
|
class ImGuiLayer;
|
||||||
|
|
||||||
namespace ECS { class Scene; }
|
namespace ECS { class Scene; }
|
||||||
@@ -49,7 +50,7 @@ namespace Engine {
|
|||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
Renderer(Window& window);
|
explicit Renderer(Window& window);
|
||||||
~Renderer();
|
~Renderer();
|
||||||
|
|
||||||
Renderer(const Renderer&) = delete;
|
Renderer(const Renderer&) = delete;
|
||||||
@@ -113,6 +114,9 @@ namespace Engine {
|
|||||||
Scope<UniformBuffer> m_PrevObjectBuffer; // 上一帧实例矩阵 ring(TAA velocity)
|
Scope<UniformBuffer> m_PrevObjectBuffer; // 上一帧实例矩阵 ring(TAA velocity)
|
||||||
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
|
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
|
||||||
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
|
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
|
||||||
|
// 环境贴图(IBL 阶段①):HDR → cube,被 SkyboxPass 引用;
|
||||||
|
// 声明在 m_Passes 之前 → 析构晚于 pass(引用安全)
|
||||||
|
Scope<Cubemap> m_Environment;
|
||||||
Scope<Swapchain> m_Swapchain;
|
Scope<Swapchain> m_Swapchain;
|
||||||
Scope<SyncManager> m_SyncManager;
|
Scope<SyncManager> m_SyncManager;
|
||||||
Scope<CommandManager> m_CommandManager;
|
Scope<CommandManager> m_CommandManager;
|
||||||
|
|||||||
@@ -37,10 +37,13 @@ namespace Engine {
|
|||||||
constexpr u32 kMaxCascades = 3; // CSM 级联数
|
constexpr u32 kMaxCascades = 3; // CSM 级联数
|
||||||
constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长
|
constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长
|
||||||
|
|
||||||
// 镜像 shader 侧 UBO 布局的灯光项
|
// 镜像 shader 侧 UBO 布局的灯光项(std140,每灯 64B)。
|
||||||
|
// 字段未用部分置零——shader 按类型分支读取
|
||||||
struct LightData {
|
struct LightData {
|
||||||
glm::vec4 direction; // xyz = 方向(已归一),w 未用
|
|
||||||
glm::vec4 color; // rgb = 颜色,a = 强度
|
glm::vec4 color; // rgb = 颜色,a = 强度
|
||||||
|
glm::vec4 direction; // 方向光:xyz = 方向(已归一);点光未用
|
||||||
|
glm::vec4 position; // 点光:xyz = 世界位置(来自实体 Transform)
|
||||||
|
glm::vec4 params; // x = 类型(0=方向,1=点),y = 衰减半径,z/w 未用
|
||||||
};
|
};
|
||||||
|
|
||||||
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。
|
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。
|
||||||
@@ -65,6 +68,10 @@ namespace Engine {
|
|||||||
struct FrameSceneData {
|
struct FrameSceneData {
|
||||||
UniformBufferObject ubo{};
|
UniformBufferObject ubo{};
|
||||||
|
|
||||||
|
// 天空盒反投影矩阵:inverse(projNoJitter × mat4(mat3(view)))
|
||||||
|
//(视图去平移只留旋转 → 反投影结果即世界方向;Skybox pass push)
|
||||||
|
glm::mat4 skyInvVP{1.0f};
|
||||||
|
|
||||||
// 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO)
|
// 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO)
|
||||||
std::vector<glm::mat4> instanceModels;
|
std::vector<glm::mat4> instanceModels;
|
||||||
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
|
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Engine {
|
|||||||
vk::DeviceSize GetSize() const { return m_Size; }
|
vk::DeviceSize GetSize() const { return m_Size; }
|
||||||
|
|
||||||
// 只有 HOST_VISIBLE 的 Buffer 能调
|
// 只有 HOST_VISIBLE 的 Buffer 能调
|
||||||
void Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset = 0);
|
void Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset = 0) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ namespace Engine {
|
|||||||
CommandManager& operator=(const CommandManager&) = delete;
|
CommandManager& operator=(const CommandManager&) = delete;
|
||||||
|
|
||||||
// 开始录制指定帧槽位的命令缓冲
|
// 开始录制指定帧槽位的命令缓冲
|
||||||
vk::CommandBuffer BeginFrame(u32 frameSlot);
|
vk::CommandBuffer BeginFrame(u32 frameSlot) const;
|
||||||
|
|
||||||
// 结束录制
|
// 结束录制
|
||||||
void EndFrame(u32 frameSlot);
|
void EndFrame(u32 frameSlot) const;
|
||||||
|
|
||||||
vk::CommandBuffer GetBuffer(u32 frameSlot) const { return m_Buffers[frameSlot]; }
|
vk::CommandBuffer GetBuffer(u32 frameSlot) const { return m_Buffers[frameSlot]; }
|
||||||
|
|
||||||
|
|||||||
@@ -28,21 +28,21 @@ namespace Engine {
|
|||||||
DescriptorManager(const DescriptorManager&) = delete;
|
DescriptorManager(const DescriptorManager&) = delete;
|
||||||
DescriptorManager& operator=(const DescriptorManager&) = delete;
|
DescriptorManager& operator=(const DescriptorManager&) = delete;
|
||||||
|
|
||||||
vk::DescriptorSetLayout CreateLayout(const std::vector<DescriptorBinding>& bindings);
|
vk::DescriptorSetLayout CreateLayout(const std::vector<DescriptorBinding>& bindings) const;
|
||||||
|
|
||||||
vk::DescriptorSet AllocateDescriptorSet(vk::DescriptorSetLayout layout);
|
vk::DescriptorSet AllocateDescriptorSet(vk::DescriptorSetLayout layout);
|
||||||
|
|
||||||
void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Buffer buffer, u64 size);
|
vk::Buffer buffer, u64 size) const;
|
||||||
void UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
void UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Buffer buffer, u64 size);
|
vk::Buffer buffer, u64 size) const;
|
||||||
|
|
||||||
void UpdateTexture(vk::DescriptorSet set, u32 binding,
|
void UpdateTexture(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Sampler sampler, vk::ImageView view);
|
vk::Sampler sampler, vk::ImageView view) const;
|
||||||
|
|
||||||
// 单个 set layout 的旧接口(保留)
|
// 单个 set layout 的旧接口(保留)
|
||||||
vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
||||||
u32 pushConstantSize);
|
u32 pushConstantSize) const;
|
||||||
|
|
||||||
// 多 set layout 的新接口。
|
// 多 set layout 的新接口。
|
||||||
// push constant range 按程序级 vertex|fragment 声明(与
|
// push constant range 按程序级 vertex|fragment 声明(与
|
||||||
@@ -50,7 +50,7 @@ namespace Engine {
|
|||||||
// push 调用点必须带同样的双 stage(VUID-01796)
|
// push 调用点必须带同样的双 stage(VUID-01796)
|
||||||
vk::PipelineLayout CreatePipelineLayout(
|
vk::PipelineLayout CreatePipelineLayout(
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts,
|
const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize);
|
u32 pushConstantSize) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
@@ -64,7 +64,7 @@ namespace Engine {
|
|||||||
// 每块的容量(set 数;各类型描述符数同值)
|
// 每块的容量(set 数;各类型描述符数同值)
|
||||||
static constexpr u32 kChunkSets = 4096;
|
static constexpr u32 kChunkSets = 4096;
|
||||||
|
|
||||||
vk::DescriptorPool CreateChunk();
|
vk::DescriptorPool CreateChunk() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -54,15 +54,15 @@ namespace Engine {
|
|||||||
|
|
||||||
// 创建 + 分配 + 绑定一步完成(VMA 惯用法)
|
// 创建 + 分配 + 绑定一步完成(VMA 惯用法)
|
||||||
Allocation CreateBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
Allocation CreateBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
vk::MemoryPropertyFlags props);
|
vk::MemoryPropertyFlags props) const;
|
||||||
Allocation CreateImage(const vk::ImageCreateInfo& imageInfo,
|
Allocation CreateImage(const vk::ImageCreateInfo& imageInfo,
|
||||||
vk::MemoryPropertyFlags props);
|
vk::MemoryPropertyFlags props) const;
|
||||||
|
|
||||||
// 销毁资源并释放分配(按 alloc.Buffer/Image 判断类型),随后 alloc 归零
|
// 销毁资源并释放分配(按 alloc.Buffer/Image 判断类型),随后 alloc 归零
|
||||||
void Destroy(Allocation& alloc);
|
void Destroy(Allocation& alloc) const;
|
||||||
|
|
||||||
// HOST_VISIBLE 非 coherent 写入后冲刷(VMA 内部对 coherent 自动跳过)
|
// HOST_VISIBLE 非 coherent 写入后冲刷(VMA 内部对 coherent 自动跳过)
|
||||||
void Flush(const Allocation& alloc);
|
void Flush(const Allocation& alloc) const;
|
||||||
|
|
||||||
void PrintStats() const;
|
void PrintStats() const;
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ namespace Engine {
|
|||||||
ShaderObject CreateFromSPIRV(const std::vector<u32>& spirv,
|
ShaderObject CreateFromSPIRV(const std::vector<u32>& spirv,
|
||||||
vk::ShaderStageFlagBits stage,
|
vk::ShaderStageFlagBits stage,
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts = {},
|
const std::vector<vk::DescriptorSetLayout>& layouts = {},
|
||||||
u32 pushConstantSize = 0);
|
u32 pushConstantSize = 0) const;
|
||||||
|
|
||||||
void Destroy(const ShaderObject& shader);
|
void Destroy(const ShaderObject& shader) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<u32> ReadSPIRV(const std::string& path);
|
static std::vector<u32> ReadSPIRV(const std::string& path);
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ namespace Engine {
|
|||||||
vk::Fence GetFrameFence(u32 frameIndex) const { return m_FrameFences[frameIndex]; }
|
vk::Fence GetFrameFence(u32 frameIndex) const { return m_FrameFences[frameIndex]; }
|
||||||
|
|
||||||
// 等待并重置这一帧的 Fence
|
// 等待并重置这一帧的 Fence
|
||||||
void WaitFence(u32 frameIndex); // 只等,不 reset
|
void WaitFence(u32 frameIndex) const; // 只等,不 reset
|
||||||
void ResetFence(u32 frameIndex); // 只 reset
|
void ResetFence(u32 frameIndex) const; // 只 reset
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Engine {
|
|||||||
u64 GetSize() const { return m_Size; }
|
u64 GetSize() const { return m_Size; }
|
||||||
|
|
||||||
// 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用)
|
// 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用)
|
||||||
void Update(u32 frameSlot, const void* data, u64 size);
|
void Update(u32 frameSlot, const void* data, u64 size) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Scope<Buffer>> m_Buffers;
|
std::vector<Scope<Buffer>> m_Buffers;
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
Application::Application(std::string title, u32 width, u32 height)
|
Application::Application(const std::string& title, u32 width, u32 height)
|
||||||
{
|
{
|
||||||
m_Window = CreateScope<Window>(std::move(title), width, height);
|
m_Window = CreateScope<Window>(title, width, height);
|
||||||
|
|
||||||
// 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子
|
// 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子
|
||||||
m_Window->SetEventCallback(
|
m_Window->SetEventCallback(
|
||||||
|
|||||||
+16
-16
@@ -18,7 +18,7 @@ namespace Engine
|
|||||||
|
|
||||||
// ============ 资源计数 ============
|
// ============ 资源计数 ============
|
||||||
|
|
||||||
void Stats::AddTexture(u64 bytes)
|
void Stats::AddTexture(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_TextureCount++;
|
m_TextureCount++;
|
||||||
@@ -27,7 +27,7 @@ namespace Engine
|
|||||||
m_TotalAllocation += bytes;
|
m_TotalAllocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveTexture(u64 bytes)
|
void Stats::RemoveTexture(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_TextureCount > 0) m_TextureCount--;
|
if (m_TextureCount > 0) m_TextureCount--;
|
||||||
@@ -36,27 +36,27 @@ namespace Engine
|
|||||||
m_TotalDeallocation += bytes;
|
m_TotalDeallocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddMesh(u64 vertexBytes, u64 indexBytes)
|
void Stats::AddMesh(const u64 vertexBytes, const u64 indexBytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_MeshCount++;
|
m_MeshCount++;
|
||||||
u64 total = vertexBytes + indexBytes;
|
const u64 total = vertexBytes + indexBytes;
|
||||||
m_MeshBytes += total;
|
m_MeshBytes += total;
|
||||||
m_FrameAllocation += total;
|
m_FrameAllocation += total;
|
||||||
m_TotalAllocation += total;
|
m_TotalAllocation += total;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveMesh(u64 vertexBytes, u64 indexBytes)
|
void Stats::RemoveMesh(const u64 vertexBytes, const u64 indexBytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_MeshCount > 0) m_MeshCount--;
|
if (m_MeshCount > 0) m_MeshCount--;
|
||||||
u64 total = vertexBytes + indexBytes;
|
const u64 total = vertexBytes + indexBytes;
|
||||||
if (m_MeshBytes >= total) m_MeshBytes -= total;
|
if (m_MeshBytes >= total) m_MeshBytes -= total;
|
||||||
m_FrameDeallocation += total;
|
m_FrameDeallocation += total;
|
||||||
m_TotalDeallocation += total;
|
m_TotalDeallocation += total;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddBuffer(u64 bytes)
|
void Stats::AddBuffer(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_BufferCount++;
|
m_BufferCount++;
|
||||||
@@ -65,7 +65,7 @@ namespace Engine
|
|||||||
m_TotalAllocation += bytes;
|
m_TotalAllocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveBuffer(u64 bytes)
|
void Stats::RemoveBuffer(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_BufferCount > 0) m_BufferCount--;
|
if (m_BufferCount > 0) m_BufferCount--;
|
||||||
@@ -74,25 +74,25 @@ namespace Engine
|
|||||||
m_TotalDeallocation += bytes;
|
m_TotalDeallocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddMemoryBlock(u64 bytes)
|
void Stats::AddMemoryBlock(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_MemoryBlockBytes += bytes;
|
m_MemoryBlockBytes += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveMemoryBlock(u64 bytes)
|
void Stats::RemoveMemoryBlock(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_MemoryBlockBytes >= bytes) m_MemoryBlockBytes -= bytes;
|
if (m_MemoryBlockBytes >= bytes) m_MemoryBlockBytes -= bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddMemoryUsed(u64 bytes)
|
void Stats::AddMemoryUsed(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_MemoryUsedBytes += bytes;
|
m_MemoryUsedBytes += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::RemoveMemoryUsed(u64 bytes)
|
void Stats::RemoveMemoryUsed(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
if (m_MemoryUsedBytes >= bytes) m_MemoryUsedBytes -= bytes;
|
if (m_MemoryUsedBytes >= bytes) m_MemoryUsedBytes -= bytes;
|
||||||
@@ -111,14 +111,14 @@ namespace Engine
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddAllocation(u64 bytes)
|
void Stats::AddAllocation(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_FrameAllocation += bytes;
|
m_FrameAllocation += bytes;
|
||||||
m_TotalAllocation += bytes;
|
m_TotalAllocation += bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::AddDeallocation(u64 bytes)
|
void Stats::AddDeallocation(const u64 bytes)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
m_FrameDeallocation += bytes;
|
m_FrameDeallocation += bytes;
|
||||||
@@ -206,13 +206,13 @@ namespace Engine
|
|||||||
|
|
||||||
// ============ 打印 ============
|
// ============ 打印 ============
|
||||||
|
|
||||||
static double MB(u64 bytes) { return bytes / (1024.0 * 1024.0); }
|
static double MB(const u64 bytes) { return bytes / (1024.0 * 1024.0); }
|
||||||
|
|
||||||
void Stats::Print() const
|
void Stats::Print() const
|
||||||
{
|
{
|
||||||
std::lock_guard lock(m_Mutex);
|
std::lock_guard lock(m_Mutex);
|
||||||
|
|
||||||
auto proc = ReadProcessMemory();
|
const auto proc = ReadProcessMemory();
|
||||||
|
|
||||||
ENGINE_INFO("========== Resource Stats ==========");
|
ENGINE_INFO("========== Resource Stats ==========");
|
||||||
ENGINE_INFO("Textures: {:>4} ({:.2f} MB)", m_TextureCount, MB(m_TextureBytes));
|
ENGINE_INFO("Textures: {:>4} ({:.2f} MB)", m_TextureCount, MB(m_TextureBytes));
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ namespace Engine
|
|||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::SurfaceKHR Window::CreateSurface(vk::Instance instance)
|
vk::SurfaceKHR Window::CreateSurface(const vk::Instance instance) const
|
||||||
{
|
{
|
||||||
VkSurfaceKHR surface;
|
VkSurfaceKHR surface;
|
||||||
if (glfwCreateWindowSurface(instance, m_Window, nullptr, &surface) != VK_SUCCESS)
|
if (glfwCreateWindowSurface(instance, m_Window, nullptr, &surface) != VK_SUCCESS)
|
||||||
@@ -167,7 +167,7 @@ namespace Engine
|
|||||||
return surface;
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::DestroySurface(vk::Instance instance, vk::SurfaceKHR surface)
|
void Window::DestroySurface(const vk::Instance instance, const vk::SurfaceKHR surface)
|
||||||
{
|
{
|
||||||
instance.destroySurfaceKHR(surface);
|
instance.destroySurfaceKHR(surface);
|
||||||
}
|
}
|
||||||
@@ -184,14 +184,14 @@ namespace Engine
|
|||||||
glfwGetFramebufferSize(m_Window, &width, &height);
|
glfwGetFramebufferSize(m_Window, &width, &height);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Window::IsKeyPressed(int key) const
|
bool Window::IsKeyPressed(const int key) const
|
||||||
{
|
{
|
||||||
// UI 捕获键盘时,轮询接口同样静默(相机等轮询型消费方)
|
// UI 捕获键盘时,轮询接口同样静默(相机等轮询型消费方)
|
||||||
if (m_KeyboardCapture && m_KeyboardCapture()) return false;
|
if (m_KeyboardCapture && m_KeyboardCapture()) return false;
|
||||||
return glfwGetKey(m_Window, key) == GLFW_PRESS;
|
return glfwGetKey(m_Window, key) == GLFW_PRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::GetMouseDelta(float& dx, float& dy)
|
void Window::GetMouseDelta(float& dx, float& dy) const
|
||||||
{
|
{
|
||||||
double mx, my;
|
double mx, my;
|
||||||
glfwGetCursorPos(m_Window, &mx, &my);
|
glfwGetCursorPos(m_Window, &mx, &my);
|
||||||
|
|||||||
@@ -0,0 +1,765 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// Cubemap 实现:HDR 等距柱状 → CPU 预积分四件套 → fp16 上传。
|
||||||
|
//
|
||||||
|
// 一次性加载成本(秒级 CPU),换取运行时零积分开销;硬件 Cube 采样
|
||||||
|
//(filtering/边缘接缝由 driver 处理)。split-sum 预积分(LearnOpenGL/
|
||||||
|
// UE 拆分方案的离线端):
|
||||||
|
// irradiance —— 半球 Lambert 积分,漫反射 ambient
|
||||||
|
// prefilter —— GGX 重要性采样(Hammersley 序列),mip 分级 roughness
|
||||||
|
// BRDF LUT —— (NdotV, roughness) → (F 缩放, F 偏置),纯数学表
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Cubemap.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <stb/stb_image.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <bit>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr u32 kFaceCount = 6;
|
||||||
|
constexpr u32 kIrradianceFaceSize = 32; // 漫反射辐照度面边长(低频)
|
||||||
|
constexpr u32 kPhiSteps = 32; // 方位角采样数(512 采样/像素)
|
||||||
|
constexpr u32 kThetaSteps = 16; // 极角采样数
|
||||||
|
constexpr u32 kPrefilterFaceSize = 128; // pre-filter 面边长(mip0)
|
||||||
|
constexpr u32 kPrefilterMips = 5; // mip 分级 roughness 数
|
||||||
|
constexpr u32 kBrdfLutSize = 128; // BRDF LUT 边长
|
||||||
|
constexpr u32 kBrdfLutSamples = 128; // LUT 每像素采样数
|
||||||
|
|
||||||
|
// ---- GL/Vulkan cube 面方向表(与 GL spec 立方体选择表互逆)----
|
||||||
|
// (u,v)∈[0,1](u 向右 v 向下)→ s,t∈[-1,1] → 单位方向
|
||||||
|
glm::vec3 FaceDir(u32 face, f32 u, f32 v)
|
||||||
|
{
|
||||||
|
const f32 s = u * 2.0f - 1.0f;
|
||||||
|
const f32 t = v * 2.0f - 1.0f;
|
||||||
|
glm::vec3 dir;
|
||||||
|
switch (face) {
|
||||||
|
case 0: dir = { 1.0f, -t, -s }; break; // +X(面片顶行 = +Y)
|
||||||
|
case 1: dir = {-1.0f, -t, s }; break; // -X
|
||||||
|
case 2: dir = { s, 1.0f, t }; break; // +Y
|
||||||
|
case 3: dir = { s,-1.0f, -t }; break; // -Y
|
||||||
|
case 4: dir = { s, -t, 1.0f }; break; // +Z
|
||||||
|
default: dir = {-s, -t,-1.0f }; break; // -Z
|
||||||
|
}
|
||||||
|
return glm::normalize(dir); // equirect 查表要求单位方向
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等距柱状双线性采样(x 环绕跨经度接缝,y 钳制到极点)。
|
||||||
|
// 图像行 0 = 天顶(+Y):v = acos(y)/π,与 stbi 行序一致,无需翻转
|
||||||
|
glm::vec3 SampleEquirect(const f32* img, u32 w, u32 h, const glm::vec3& dir)
|
||||||
|
{
|
||||||
|
const f32 u = std::atan2(dir.z, dir.x) * (0.5f / glm::pi<f32>()) + 0.5f;
|
||||||
|
const f32 v = std::acos(glm::clamp(dir.y, -1.0f, 1.0f)) / glm::pi<f32>();
|
||||||
|
|
||||||
|
const f32 fx = u * static_cast<f32>(w) - 0.5f;
|
||||||
|
const f32 fy = v * static_cast<f32>(h) - 0.5f;
|
||||||
|
const u32 x0 = static_cast<u32>(std::floor(fx));
|
||||||
|
const u32 y0 = static_cast<u32>(std::floor(fy));
|
||||||
|
const f32 tx = fx - std::floor(fx);
|
||||||
|
const f32 ty = fy - std::floor(fy);
|
||||||
|
|
||||||
|
auto fetch = [&](u32 x, u32 y) {
|
||||||
|
const u32 xc = ((x % w) + w) % w; // 经度环绕
|
||||||
|
const u32 yc = glm::clamp(y, 0u, h - 1u); // 纬度钳制
|
||||||
|
const size_t off = (static_cast<size_t>(yc) * w + xc) * 4;
|
||||||
|
return glm::vec3(img[off + 0], img[off + 1], img[off + 2]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const glm::vec3 c00 = fetch(x0, y0);
|
||||||
|
const glm::vec3 c10 = fetch(x0 + 1, y0);
|
||||||
|
const glm::vec3 c01 = fetch(x0, y0 + 1);
|
||||||
|
const glm::vec3 c11 = fetch(x0 + 1, y0 + 1);
|
||||||
|
|
||||||
|
return glm::mix(glm::mix(c00, c10, tx), glm::mix(c01, c11, tx), ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 漫反射辐照度半球积分(Lambert):E = ∫ L·cosθ·sinθ dθ dφ。
|
||||||
|
// 切线基 up×N 构造;均匀网格 ≈2048 采样/像素,对 32² 低频图足够
|
||||||
|
glm::vec3 ConvolveIrradiance(const f32* img, u32 w, u32 h,
|
||||||
|
const glm::vec3& N)
|
||||||
|
{
|
||||||
|
const glm::vec3 up = glm::abs(N.y) > 0.999f
|
||||||
|
? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
const glm::vec3 T = glm::normalize(glm::cross(up, N));
|
||||||
|
const glm::vec3 B = glm::cross(N, T);
|
||||||
|
|
||||||
|
const f32 dPhi = 2.0f * glm::pi<f32>() / static_cast<f32>(kPhiSteps);
|
||||||
|
const f32 dTheta = 0.5f * glm::pi<f32>() / static_cast<f32>(kThetaSteps);
|
||||||
|
|
||||||
|
glm::vec3 irradiance(0.0f);
|
||||||
|
for (u32 p = 0; p < kPhiSteps; ++p) {
|
||||||
|
const f32 phi = (static_cast<f32>(p) + 0.5f) * dPhi;
|
||||||
|
for (u32 t = 0; t < kThetaSteps; ++t) {
|
||||||
|
const f32 theta = (static_cast<f32>(t) + 0.5f) * dTheta;
|
||||||
|
const f32 sinT = std::sin(theta);
|
||||||
|
const f32 cosT = std::cos(theta);
|
||||||
|
const glm::vec3 sampleDir =
|
||||||
|
T * (sinT * std::cos(phi)) +
|
||||||
|
B * (sinT * std::sin(phi)) +
|
||||||
|
N * cosT;
|
||||||
|
irradiance += SampleEquirect(img, w, h, sampleDir) *
|
||||||
|
(cosT * sinT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return irradiance * (dPhi * dTheta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- GGX 重要性采样(split-sum 镜面端)----
|
||||||
|
|
||||||
|
// van der Corput 基底反转(位反转技巧)。
|
||||||
|
// 注意是整数值转浮点(static_cast),不是位型重解读——
|
||||||
|
// bit_cast 会把反转后的整数当 IEEE754 解读,序列完全错乱
|
||||||
|
f32 RadicalInverseVdC(u32 bits)
|
||||||
|
{
|
||||||
|
bits = (bits << 16u) | (bits >> 16u);
|
||||||
|
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
|
||||||
|
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
||||||
|
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
||||||
|
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
||||||
|
return static_cast<f32>(bits) * 2.3283064365386963e-10f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// n = 当次积分总采样数(xi.x = i/n 必须覆盖 [0,1] 全程)
|
||||||
|
glm::vec2 Hammersley(u32 i, u32 n)
|
||||||
|
{
|
||||||
|
return { static_cast<f32>(i) / static_cast<f32>(n),
|
||||||
|
RadicalInverseVdC(i) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// GGX NDF 按概率密度取半程向量 H(切线空间 → 世界)
|
||||||
|
glm::vec3 ImportanceSampleGGX(const glm::vec2& xi, const glm::vec3& N,
|
||||||
|
f32 roughness)
|
||||||
|
{
|
||||||
|
const f32 a = roughness * roughness;
|
||||||
|
const f32 phi = 2.0f * glm::pi<f32>() * xi.x;
|
||||||
|
const f32 cosTheta =
|
||||||
|
std::sqrt((1.0f - xi.y) / (1.0f + (a * a - 1.0f) * xi.y));
|
||||||
|
const f32 sinTheta = std::sqrt(1.0f - cosTheta * cosTheta);
|
||||||
|
|
||||||
|
const glm::vec3 H(sinTheta * std::cos(phi),
|
||||||
|
sinTheta * std::sin(phi), cosTheta);
|
||||||
|
|
||||||
|
const glm::vec3 up = glm::abs(N.z) > 0.999f
|
||||||
|
? glm::vec3(1.0f, 0.0f, 0.0f) : glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
const glm::vec3 T = glm::normalize(glm::cross(up, N));
|
||||||
|
const glm::vec3 B = glm::cross(N, T);
|
||||||
|
return glm::normalize(T * H.x + B * H.y + N * H.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre-filter 卷积:R(N) ≈ Σ L·NdotL / Σ NdotL(GGX 加权镜面均值)
|
||||||
|
glm::vec3 Prefilter(const f32* img, u32 w, u32 h,
|
||||||
|
const glm::vec3& N, f32 roughness, u32 samples)
|
||||||
|
{
|
||||||
|
const glm::vec3 V = N; // split-sum 约定:V 沿 N
|
||||||
|
glm::vec3 acc(0.0f);
|
||||||
|
f32 weight = 0.0f;
|
||||||
|
for (u32 i = 0; i < samples; ++i) {
|
||||||
|
const glm::vec3 H = ImportanceSampleGGX(
|
||||||
|
Hammersley(i, samples), N, roughness);
|
||||||
|
const glm::vec3 L = glm::normalize(2.0f * glm::dot(V, H) * H - V);
|
||||||
|
const f32 nDotL = glm::max(glm::dot(N, L), 0.0f);
|
||||||
|
if (nDotL > 0.0f) {
|
||||||
|
acc += SampleEquirect(img, w, h, L) * nDotL;
|
||||||
|
weight += nDotL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return weight > 0.0f ? acc / weight : glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BRDF LUT 积分:(NdotV, roughness) → (A, B)。
|
||||||
|
// A/B 是 Fresnel 的缩放/偏置:specular = prefiltered·(F·A + B)
|
||||||
|
glm::vec2 IntegrateBrdf(f32 nDotV, f32 roughness)
|
||||||
|
{
|
||||||
|
// V 在以 N 为 +Z 的切线空间里(积分只依赖这两角度)
|
||||||
|
const glm::vec3 V(std::sqrt(1.0f - nDotV * nDotV), 0.0f, nDotV);
|
||||||
|
|
||||||
|
f32 a = 0.0f, b = 0.0f;
|
||||||
|
const f32 k = roughness * roughness * 0.5f; // IBL 版 Smith k
|
||||||
|
|
||||||
|
for (u32 i = 0; i < kBrdfLutSamples; ++i) {
|
||||||
|
const glm::vec2 xi = Hammersley(i, kBrdfLutSamples);
|
||||||
|
const glm::vec3 H = ImportanceSampleGGX(xi, glm::vec3(0, 0, 1),
|
||||||
|
roughness);
|
||||||
|
const glm::vec3 L = glm::normalize(
|
||||||
|
2.0f * glm::dot(V, H) * H - V);
|
||||||
|
|
||||||
|
const f32 nDotL = glm::max(L.z, 0.0f);
|
||||||
|
const f32 nDotH = glm::max(H.z, 0.0f);
|
||||||
|
const f32 vDotH = glm::max(glm::dot(V, H), 0.0f);
|
||||||
|
if (nDotL <= 0.0f) continue;
|
||||||
|
|
||||||
|
const f32 g = (nDotV / (nDotV * (1.0f - k) + k)) *
|
||||||
|
(nDotL / (nDotL * (1.0f - k) + k));
|
||||||
|
// 高 roughness 时部分样本 H.z 恰为 0(g 也为 0),
|
||||||
|
// 0/0 = NaN 会累进 LUT——分母钳 epsilon
|
||||||
|
const f32 gVis = g * vDotH /
|
||||||
|
glm::max(nDotH * nDotV, 1e-6f);
|
||||||
|
const f32 fc = 1.0f - vDotH;
|
||||||
|
const f32 f = fc * fc * fc * fc * fc; // (1-vDotH)⁵,乘法链
|
||||||
|
|
||||||
|
a += (1.0f - f) * gVis;
|
||||||
|
b += f * gVis;
|
||||||
|
}
|
||||||
|
return { a / static_cast<f32>(kBrdfLutSamples),
|
||||||
|
b / static_cast<f32>(kBrdfLutSamples) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// IEEE754 f32 → fp16(round-to-nearest-even;溢出饱和为 Inf)
|
||||||
|
u16 FloatToHalf(f32 value)
|
||||||
|
{
|
||||||
|
const u32 x = std::bit_cast<u32>(value);
|
||||||
|
const u16 sign = static_cast<u16>((x >> 16) & 0x8000u);
|
||||||
|
const u32 absVal = x & 0x7fffffffu;
|
||||||
|
|
||||||
|
if (absVal > 0x7f800000u) {
|
||||||
|
return static_cast<u16>(sign | 0x7e00u); // NaN
|
||||||
|
}
|
||||||
|
if (absVal >= 0x47800000u) {
|
||||||
|
return static_cast<u16>(sign | 0x7c00u); // Inf / 溢出饱和
|
||||||
|
}
|
||||||
|
if (absVal >= 0x38800000u) {
|
||||||
|
const u32 bits = absVal - 0x38000000u;
|
||||||
|
const u32 round = 0x0fffu + ((bits >> 13) & 1u);
|
||||||
|
return static_cast<u16>(sign | ((bits + round) >> 13));
|
||||||
|
}
|
||||||
|
if (absVal < 0x33000000u) {
|
||||||
|
return sign; // < 2^-25:舍入为 0
|
||||||
|
}
|
||||||
|
const u32 mantissa = (absVal & 0x007fffffu) | 0x00800000u;
|
||||||
|
const u32 shift = 126u - (absVal >> 23u);
|
||||||
|
const u32 m = (mantissa + (1u << (shift - 1))) >> shift;
|
||||||
|
return static_cast<u16>(sign | std::min(m, 0x400u));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 上传 ----
|
||||||
|
|
||||||
|
vk::ImageView CreateCubeView(Device& device, vk::Image image, u32 mips)
|
||||||
|
{
|
||||||
|
return device.GetDevice().createImageView(
|
||||||
|
vk::ImageViewCreateInfo{}
|
||||||
|
.setImage(image)
|
||||||
|
.setViewType(vk::ImageViewType::eCube)
|
||||||
|
.setFormat(vk::Format::eR16G16B16A16Sfloat)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, mips, 0, kFaceCount}));
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageView CreateLutView(Device& device, vk::Image image)
|
||||||
|
{
|
||||||
|
return device.GetDevice().createImageView(
|
||||||
|
vk::ImageViewCreateInfo{}
|
||||||
|
.setImage(image)
|
||||||
|
.setViewType(vk::ImageViewType::e2D)
|
||||||
|
.setFormat(vk::Format::eR16G16Sfloat)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, 1, 0, 1}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// cube mip 链数据:[mip0: 6 面][mip1: 6 面]...,每面 faceSize²×8B
|
||||||
|
struct CubeMipData {
|
||||||
|
const u16* data; // 6 面连续
|
||||||
|
u32 faceSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传 cube(含 mip 链)为 eCubeCompatible image(fp16),
|
||||||
|
// 返回完整资源单元(image + view + 显存分配)
|
||||||
|
EnvTexture UploadCube(Device& device, MemoryAllocator& allocator,
|
||||||
|
const std::vector<CubeMipData>& mips,
|
||||||
|
u32 viewMipCount)
|
||||||
|
{
|
||||||
|
const u32 mipCount = static_cast<u32>(mips.size());
|
||||||
|
|
||||||
|
// staging:各 mip 紧凑排布,记偏移
|
||||||
|
vk::DeviceSize total = 0;
|
||||||
|
for (const auto& mip : mips) {
|
||||||
|
total += static_cast<vk::DeviceSize>(mip.faceSize) *
|
||||||
|
mip.faceSize * 8 * kFaceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stagingAlloc = allocator.CreateBuffer(
|
||||||
|
total,
|
||||||
|
vk::BufferUsageFlagBits::eTransferSrc,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||||
|
{
|
||||||
|
auto* dst = static_cast<u8*>(stagingAlloc.MappedPtr);
|
||||||
|
vk::DeviceSize off = 0;
|
||||||
|
for (const auto& mip : mips) {
|
||||||
|
const vk::DeviceSize bytes =
|
||||||
|
static_cast<vk::DeviceSize>(mip.faceSize) *
|
||||||
|
mip.faceSize * 8 * kFaceCount;
|
||||||
|
std::memcpy(dst + off, mip.data,
|
||||||
|
static_cast<size_t>(bytes));
|
||||||
|
off += bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 baseSize = mips[0].faceSize;
|
||||||
|
vk::ImageCreateInfo imageInfo;
|
||||||
|
imageInfo.setImageType(vk::ImageType::e2D)
|
||||||
|
.setFormat(vk::Format::eR16G16B16A16Sfloat)
|
||||||
|
.setExtent({baseSize, baseSize, 1})
|
||||||
|
.setMipLevels(mipCount)
|
||||||
|
.setArrayLayers(kFaceCount)
|
||||||
|
.setSamples(vk::SampleCountFlagBits::e1)
|
||||||
|
.setTiling(vk::ImageTiling::eOptimal)
|
||||||
|
.setFlags(vk::ImageCreateFlagBits::eCubeCompatible)
|
||||||
|
.setUsage(vk::ImageUsageFlagBits::eTransferDst |
|
||||||
|
vk::ImageUsageFlagBits::eSampled)
|
||||||
|
.setSharingMode(vk::SharingMode::eExclusive)
|
||||||
|
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||||
|
|
||||||
|
auto imageAlloc = allocator.CreateImage(imageInfo,
|
||||||
|
vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||||
|
const vk::Image image = imageAlloc.Image;
|
||||||
|
|
||||||
|
vk::CommandPoolCreateInfo poolInfo;
|
||||||
|
poolInfo.setQueueFamilyIndex(device.GetGraphicsFamily())
|
||||||
|
.setFlags(vk::CommandPoolCreateFlagBits::eTransient);
|
||||||
|
vk::CommandPool pool = device.GetDevice().createCommandPool(poolInfo);
|
||||||
|
|
||||||
|
vk::CommandBuffer cmd = device.GetDevice().allocateCommandBuffers(
|
||||||
|
vk::CommandBufferAllocateInfo{}
|
||||||
|
.setCommandPool(pool)
|
||||||
|
.setLevel(vk::CommandBufferLevel::ePrimary)
|
||||||
|
.setCommandBufferCount(1))[0];
|
||||||
|
|
||||||
|
cmd.begin(vk::CommandBufferBeginInfo{
|
||||||
|
vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
|
||||||
|
|
||||||
|
// 全 mip × 全层一起转 layout
|
||||||
|
vk::ImageMemoryBarrier2 toTransfer;
|
||||||
|
toTransfer.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
|
||||||
|
.setDstStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||||
|
.setDstAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||||
|
.setOldLayout(vk::ImageLayout::eUndefined)
|
||||||
|
.setNewLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||||
|
.setImage(image)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, mipCount, 0, kFaceCount});
|
||||||
|
cmd.pipelineBarrier2(
|
||||||
|
vk::DependencyInfo{}.setImageMemoryBarriers(toTransfer));
|
||||||
|
|
||||||
|
// 逐 mip 逐面拷贝;buffer 偏移必须按 mips[0..mip) 字节数累积
|
||||||
|
//(mip 面尺寸递减,用当前 dim 乘 mip 序号会错位 → 读到
|
||||||
|
// staging 未初始化数据 → fp16 NaN 污染整屏)
|
||||||
|
vk::DeviceSize offset = 0;
|
||||||
|
for (u32 mip = 0; mip < mipCount; ++mip) {
|
||||||
|
const u32 dim = mips[mip].faceSize;
|
||||||
|
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||||
|
vk::BufferImageCopy region;
|
||||||
|
region.setBufferOffset(
|
||||||
|
offset + static_cast<vk::DeviceSize>(dim) *
|
||||||
|
dim * 8 * f)
|
||||||
|
.setBufferRowLength(0)
|
||||||
|
.setBufferImageHeight(0)
|
||||||
|
.setImageSubresource({vk::ImageAspectFlagBits::eColor,
|
||||||
|
mip, f, 1})
|
||||||
|
.setImageOffset({0, 0, 0})
|
||||||
|
.setImageExtent({dim, dim, 1});
|
||||||
|
cmd.copyBufferToImage(stagingAlloc.Buffer, image,
|
||||||
|
vk::ImageLayout::eTransferDstOptimal,
|
||||||
|
region);
|
||||||
|
}
|
||||||
|
offset += static_cast<vk::DeviceSize>(dim) * dim * 8 *
|
||||||
|
kFaceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 toShader;
|
||||||
|
toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||||
|
.setDstStageMask(vk::PipelineStageFlagBits2::eFragmentShader)
|
||||||
|
.setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
|
||||||
|
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||||
|
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
|
||||||
|
.setImage(image)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, mipCount, 0, kFaceCount});
|
||||||
|
cmd.pipelineBarrier2(
|
||||||
|
vk::DependencyInfo{}.setImageMemoryBarriers(toShader));
|
||||||
|
|
||||||
|
cmd.end();
|
||||||
|
|
||||||
|
vk::Fence fence = device.GetDevice().createFence({});
|
||||||
|
device.GetGraphicsQueue().submit(
|
||||||
|
vk::SubmitInfo{}.setCommandBuffers(cmd), fence);
|
||||||
|
if (device.GetDevice().waitForFences(fence, VK_TRUE, UINT64_MAX) !=
|
||||||
|
vk::Result::eSuccess) {
|
||||||
|
ENGINE_ERROR("Cubemap upload: waitForFences failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
device.GetDevice().destroyFence(fence);
|
||||||
|
device.GetDevice().destroyCommandPool(pool);
|
||||||
|
allocator.Destroy(stagingAlloc);
|
||||||
|
|
||||||
|
return {imageAlloc.Image,
|
||||||
|
CreateCubeView(device, imageAlloc.Image, viewMipCount),
|
||||||
|
imageAlloc};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传 BRDF LUT(2D,R16G16Sfloat,8B/px),返回完整资源单元
|
||||||
|
EnvTexture UploadTexture2D(Device& device,
|
||||||
|
MemoryAllocator& allocator,
|
||||||
|
const std::vector<u16>& texels,
|
||||||
|
u32 size, vk::Format format)
|
||||||
|
{
|
||||||
|
const vk::DeviceSize total =
|
||||||
|
static_cast<vk::DeviceSize>(size) * size * 8;
|
||||||
|
|
||||||
|
auto stagingAlloc = allocator.CreateBuffer(
|
||||||
|
total,
|
||||||
|
vk::BufferUsageFlagBits::eTransferSrc,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||||
|
std::memcpy(stagingAlloc.MappedPtr, texels.data(),
|
||||||
|
static_cast<size_t>(total));
|
||||||
|
|
||||||
|
vk::ImageCreateInfo imageInfo;
|
||||||
|
imageInfo.setImageType(vk::ImageType::e2D)
|
||||||
|
.setFormat(format)
|
||||||
|
.setExtent({size, size, 1})
|
||||||
|
.setMipLevels(1)
|
||||||
|
.setArrayLayers(1)
|
||||||
|
.setSamples(vk::SampleCountFlagBits::e1)
|
||||||
|
.setTiling(vk::ImageTiling::eOptimal)
|
||||||
|
.setUsage(vk::ImageUsageFlagBits::eTransferDst |
|
||||||
|
vk::ImageUsageFlagBits::eSampled)
|
||||||
|
.setSharingMode(vk::SharingMode::eExclusive)
|
||||||
|
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||||
|
|
||||||
|
auto imageAlloc = allocator.CreateImage(imageInfo,
|
||||||
|
vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||||
|
const vk::Image image = imageAlloc.Image;
|
||||||
|
|
||||||
|
vk::CommandPoolCreateInfo poolInfo;
|
||||||
|
poolInfo.setQueueFamilyIndex(device.GetGraphicsFamily())
|
||||||
|
.setFlags(vk::CommandPoolCreateFlagBits::eTransient);
|
||||||
|
vk::CommandPool pool = device.GetDevice().createCommandPool(poolInfo);
|
||||||
|
|
||||||
|
vk::CommandBuffer cmd = device.GetDevice().allocateCommandBuffers(
|
||||||
|
vk::CommandBufferAllocateInfo{}
|
||||||
|
.setCommandPool(pool)
|
||||||
|
.setLevel(vk::CommandBufferLevel::ePrimary)
|
||||||
|
.setCommandBufferCount(1))[0];
|
||||||
|
|
||||||
|
cmd.begin(vk::CommandBufferBeginInfo{
|
||||||
|
vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 toTransfer;
|
||||||
|
toTransfer.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
|
||||||
|
.setDstStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||||
|
.setDstAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||||
|
.setOldLayout(vk::ImageLayout::eUndefined)
|
||||||
|
.setNewLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||||
|
.setImage(image)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, 1, 0, 1});
|
||||||
|
cmd.pipelineBarrier2(
|
||||||
|
vk::DependencyInfo{}.setImageMemoryBarriers(toTransfer));
|
||||||
|
|
||||||
|
vk::BufferImageCopy region;
|
||||||
|
region.setBufferOffset(0)
|
||||||
|
.setBufferRowLength(0)
|
||||||
|
.setBufferImageHeight(0)
|
||||||
|
.setImageSubresource({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, 0, 1})
|
||||||
|
.setImageOffset({0, 0, 0})
|
||||||
|
.setImageExtent({size, size, 1});
|
||||||
|
cmd.copyBufferToImage(stagingAlloc.Buffer, image,
|
||||||
|
vk::ImageLayout::eTransferDstOptimal, region);
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 toShader;
|
||||||
|
toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||||
|
.setDstStageMask(vk::PipelineStageFlagBits2::eFragmentShader)
|
||||||
|
.setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
|
||||||
|
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||||
|
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
|
||||||
|
.setImage(image)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, 1, 0, 1});
|
||||||
|
cmd.pipelineBarrier2(
|
||||||
|
vk::DependencyInfo{}.setImageMemoryBarriers(toShader));
|
||||||
|
|
||||||
|
cmd.end();
|
||||||
|
|
||||||
|
vk::Fence fence = device.GetDevice().createFence({});
|
||||||
|
device.GetGraphicsQueue().submit(
|
||||||
|
vk::SubmitInfo{}.setCommandBuffers(cmd), fence);
|
||||||
|
if (device.GetDevice().waitForFences(fence, VK_TRUE, UINT64_MAX) !=
|
||||||
|
vk::Result::eSuccess) {
|
||||||
|
ENGINE_ERROR("BRDF LUT upload: waitForFences failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
device.GetDevice().destroyFence(fence);
|
||||||
|
device.GetDevice().destroyCommandPool(pool);
|
||||||
|
allocator.Destroy(stagingAlloc);
|
||||||
|
|
||||||
|
return {imageAlloc.Image,
|
||||||
|
CreateLutView(device, imageAlloc.Image), imageAlloc};
|
||||||
|
}
|
||||||
|
|
||||||
|
// fp16 NaN 自检(指数全 1 且尾数非 0):预积分数据上传前的
|
||||||
|
// 最后一道防线——NaN 进 GPU 会污染整屏输出
|
||||||
|
bool ContainsNaNHalf(const u16* data, size_t count)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
const u32 abs = data[i] & 0x7FFFu;
|
||||||
|
if (abs > 0x7C00u) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Sampler CreateEnvSampler(Device& device, u32 maxLod)
|
||||||
|
{
|
||||||
|
vk::SamplerCreateInfo samplerInfo;
|
||||||
|
samplerInfo.setMagFilter(vk::Filter::eLinear)
|
||||||
|
.setMinFilter(vk::Filter::eLinear)
|
||||||
|
.setMipmapMode(vk::SamplerMipmapMode::eNearest)
|
||||||
|
.setAddressModeU(vk::SamplerAddressMode::eClampToEdge)
|
||||||
|
.setAddressModeV(vk::SamplerAddressMode::eClampToEdge)
|
||||||
|
.setAddressModeW(vk::SamplerAddressMode::eClampToEdge)
|
||||||
|
.setAnisotropyEnable(VK_FALSE)
|
||||||
|
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
|
||||||
|
.setUnnormalizedCoordinates(VK_FALSE)
|
||||||
|
.setCompareEnable(VK_FALSE)
|
||||||
|
.setMinLod(0.0f)
|
||||||
|
.setMaxLod(static_cast<f32>(maxLod));
|
||||||
|
return device.GetDevice().createSampler(samplerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BRDF LUT 专用:clamp-to-edge(共享 border 白采样器会在 uv=1.0
|
||||||
|
// 处混入白色 border,高 roughness 镜面偏大)
|
||||||
|
vk::Sampler CreateLutSampler(Device& device)
|
||||||
|
{
|
||||||
|
return CreateEnvSampler(device, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope<Cubemap> Cubemap::CreateFromHdrEquirect(Device& device,
|
||||||
|
MemoryAllocator& allocator,
|
||||||
|
const std::string& path,
|
||||||
|
u32 faceSize)
|
||||||
|
{
|
||||||
|
// ---- 1. CPU 加载 HDR(float RGBA)----
|
||||||
|
int w = 0, h = 0, channels = 0;
|
||||||
|
f32* pixels = stbi_loadf(path.c_str(), &w, &h, &channels, STBI_rgb_alpha);
|
||||||
|
if (!pixels) {
|
||||||
|
ENGINE_ERROR("Cubemap: failed to load HDR: {}", path);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const u32 srcW = static_cast<u32>(w);
|
||||||
|
const u32 srcH = static_cast<u32>(h);
|
||||||
|
|
||||||
|
// ---- 2a. 环境 cube:重采样 6 面 ----
|
||||||
|
const u32 facePixels = faceSize * faceSize;
|
||||||
|
std::vector<u16> envFaces(kFaceCount * facePixels * 4);
|
||||||
|
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||||
|
u16* dst = envFaces.data() + f * facePixels * 4;
|
||||||
|
for (u32 y = 0; y < faceSize; ++y) {
|
||||||
|
for (u32 x = 0; x < faceSize; ++x) {
|
||||||
|
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(faceSize);
|
||||||
|
const f32 v = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(faceSize);
|
||||||
|
const glm::vec3 rgb =
|
||||||
|
SampleEquirect(pixels, srcW, srcH, FaceDir(f, u, v));
|
||||||
|
u16* px = dst + (static_cast<size_t>(y) * faceSize + x) * 4;
|
||||||
|
px[0] = FloatToHalf(rgb.r);
|
||||||
|
px[1] = FloatToHalf(rgb.g);
|
||||||
|
px[2] = FloatToHalf(rgb.b);
|
||||||
|
px[3] = 0x3C00; // 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 2b. irradiance cube:半球 Lambert 积分 ----
|
||||||
|
const u32 irrSize = kIrradianceFaceSize;
|
||||||
|
const u32 irrPixels = irrSize * irrSize;
|
||||||
|
std::vector<u16> irrFaces(kFaceCount * irrPixels * 4);
|
||||||
|
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||||
|
u16* dst = irrFaces.data() + f * irrPixels * 4;
|
||||||
|
for (u32 y = 0; y < irrSize; ++y) {
|
||||||
|
for (u32 x = 0; x < irrSize; ++x) {
|
||||||
|
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(irrSize);
|
||||||
|
const f32 v = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(irrSize);
|
||||||
|
const glm::vec3 rgb = ConvolveIrradiance(
|
||||||
|
pixels, srcW, srcH, FaceDir(f, u, v));
|
||||||
|
u16* px = dst + (static_cast<size_t>(y) * irrSize + x) * 4;
|
||||||
|
px[0] = FloatToHalf(rgb.r);
|
||||||
|
px[1] = FloatToHalf(rgb.g);
|
||||||
|
px[2] = FloatToHalf(rgb.b);
|
||||||
|
px[3] = 0x3C00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 2c. pre-filter cube:GGX 重要性采样 × mip 分级 roughness ----
|
||||||
|
// 采样数随 mip 递增(高 roughness 分布更宽需要更多样本)。
|
||||||
|
// 注意:必须一次性分配总容量——逐 mip resize 会 realloc,使
|
||||||
|
// prefMips 里已记录的 data 指针悬空(上传读到垃圾 = NaN 全黑)
|
||||||
|
u32 prefTotal = 0;
|
||||||
|
for (u32 mip = 0; mip < kPrefilterMips; ++mip) {
|
||||||
|
const u32 dim = kPrefilterFaceSize >> mip;
|
||||||
|
prefTotal += kFaceCount * dim * dim * 4;
|
||||||
|
}
|
||||||
|
std::vector<u16> prefFaces(prefTotal);
|
||||||
|
std::vector<CubeMipData> prefMips;
|
||||||
|
prefMips.reserve(kPrefilterMips);
|
||||||
|
size_t prefOffset = 0;
|
||||||
|
for (u32 mip = 0; mip < kPrefilterMips; ++mip) {
|
||||||
|
const u32 dim = kPrefilterFaceSize >> mip;
|
||||||
|
const f32 roughness =
|
||||||
|
static_cast<f32>(mip) / static_cast<f32>(kPrefilterMips - 1);
|
||||||
|
const u32 samples = std::min(32u << mip, 256u);
|
||||||
|
|
||||||
|
u16* base = prefFaces.data() + prefOffset;
|
||||||
|
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||||
|
u16* dst = base + f * dim * dim * 4;
|
||||||
|
for (u32 y = 0; y < dim; ++y) {
|
||||||
|
for (u32 x = 0; x < dim; ++x) {
|
||||||
|
const f32 u = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(dim);
|
||||||
|
const f32 v = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(dim);
|
||||||
|
const glm::vec3 rgb = Prefilter(
|
||||||
|
pixels, srcW, srcH, FaceDir(f, u, v),
|
||||||
|
roughness, samples);
|
||||||
|
u16* px = dst + (static_cast<size_t>(y) * dim + x) * 4;
|
||||||
|
px[0] = FloatToHalf(rgb.r);
|
||||||
|
px[1] = FloatToHalf(rgb.g);
|
||||||
|
px[2] = FloatToHalf(rgb.b);
|
||||||
|
px[3] = 0x3C00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefMips.push_back({base, dim});
|
||||||
|
prefOffset += kFaceCount * dim * dim * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_image_free(pixels); // 源数据消费完毕
|
||||||
|
|
||||||
|
// ---- 2d. BRDF LUT:纯数学积分表(与环境内容无关)----
|
||||||
|
std::vector<u16> lutTexels(kBrdfLutSize * kBrdfLutSize * 2);
|
||||||
|
for (u32 y = 0; y < kBrdfLutSize; ++y) {
|
||||||
|
const f32 roughness = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||||
|
for (u32 x = 0; x < kBrdfLutSize; ++x) {
|
||||||
|
const f32 nDotV = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||||
|
const glm::vec2 ab = IntegrateBrdf(nDotV, roughness);
|
||||||
|
u16* px = lutTexels.data() + (static_cast<size_t>(y) * kBrdfLutSize + x) * 2;
|
||||||
|
px[0] = FloatToHalf(ab.x);
|
||||||
|
px[1] = FloatToHalf(ab.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 3. 上传:env / irradiance / prefilter / LUT(NaN 自检)----
|
||||||
|
if (ContainsNaNHalf(envFaces.data(), envFaces.size()) ||
|
||||||
|
ContainsNaNHalf(irrFaces.data(), irrFaces.size()) ||
|
||||||
|
ContainsNaNHalf(prefFaces.data(), prefFaces.size())) {
|
||||||
|
ENGINE_ERROR("Cubemap: NaN in pre-integrated data, "
|
||||||
|
"skipping IBL upload");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (ContainsNaNHalf(lutTexels.data(), lutTexels.size())) {
|
||||||
|
ENGINE_ERROR("Cubemap: NaN in BRDF LUT");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvTexture env = UploadCube(device, allocator,
|
||||||
|
{{envFaces.data(), faceSize}}, 1);
|
||||||
|
EnvTexture irr = UploadCube(device, allocator,
|
||||||
|
{{irrFaces.data(), irrSize}}, 1);
|
||||||
|
EnvTexture pref = UploadCube(device, allocator, prefMips,
|
||||||
|
kPrefilterMips);
|
||||||
|
EnvTexture lut = UploadTexture2D(device, allocator, lutTexels,
|
||||||
|
kBrdfLutSize,
|
||||||
|
vk::Format::eR16G16Sfloat);
|
||||||
|
|
||||||
|
envFaces.clear(); envFaces.shrink_to_fit();
|
||||||
|
irrFaces.clear(); irrFaces.shrink_to_fit();
|
||||||
|
prefFaces.clear(); prefFaces.shrink_to_fit();
|
||||||
|
|
||||||
|
// cube sampler 共享:maxLod 覆盖 pre-filter mip 链(textureLod 显式
|
||||||
|
// 采样会被 clamp 到 maxLod,必须 ≥ mipCount-1)
|
||||||
|
vk::Sampler sampler = CreateEnvSampler(device, kPrefilterMips - 1);
|
||||||
|
vk::Sampler lutSampler = CreateLutSampler(device);
|
||||||
|
|
||||||
|
ENGINE_INFO("Environment IBL: {} ({}x6 env, {}x6 irradiance, "
|
||||||
|
"{}x6 prefilter {} mips, {}² BRDF LUT)",
|
||||||
|
path, faceSize, irrSize, kPrefilterFaceSize,
|
||||||
|
kPrefilterMips, kBrdfLutSize);
|
||||||
|
|
||||||
|
return Scope<Cubemap>(new Cubemap(device, allocator,
|
||||||
|
env, irr, pref, lut, sampler, lutSampler));
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope<Cubemap> Cubemap::CreateNeutral(Device& device,
|
||||||
|
MemoryAllocator& allocator,
|
||||||
|
const glm::vec3& rgb)
|
||||||
|
{
|
||||||
|
// 1×1×6 常数 cube(prefilter 仅 1 mip——1px 图不能建 mip 链,
|
||||||
|
// 采样越界 lod 会被 view range clamp 到 mip0,效果等同);
|
||||||
|
// BRDF LUT 仍为真实积分(纯数学,无环境依赖)——描述符恒有效
|
||||||
|
const u16 px[4] = { FloatToHalf(rgb.r), FloatToHalf(rgb.g),
|
||||||
|
FloatToHalf(rgb.b), 0x3C00 };
|
||||||
|
std::vector<u16> faces(kFaceCount * 4, 0);
|
||||||
|
for (u32 f = 0; f < kFaceCount; ++f) {
|
||||||
|
std::memcpy(faces.data() + f * 4, px, sizeof(px));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u16> prefFaces(kFaceCount * 4, 0);
|
||||||
|
std::vector<CubeMipData> prefMips = {{prefFaces.data(), 1}};
|
||||||
|
|
||||||
|
std::vector<u16> lutTexels(kBrdfLutSize * kBrdfLutSize * 2);
|
||||||
|
for (u32 y = 0; y < kBrdfLutSize; ++y) {
|
||||||
|
const f32 roughness = (static_cast<f32>(y) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||||
|
for (u32 x = 0; x < kBrdfLutSize; ++x) {
|
||||||
|
const f32 nDotV = (static_cast<f32>(x) + 0.5f) / static_cast<f32>(kBrdfLutSize);
|
||||||
|
const glm::vec2 ab = IntegrateBrdf(nDotV, roughness);
|
||||||
|
u16* t = lutTexels.data() + (static_cast<size_t>(y) * kBrdfLutSize + x) * 2;
|
||||||
|
t[0] = FloatToHalf(ab.x);
|
||||||
|
t[1] = FloatToHalf(ab.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvTexture env = UploadCube(device, allocator, {{faces.data(), 1}}, 1);
|
||||||
|
EnvTexture irr = UploadCube(device, allocator, {{faces.data(), 1}}, 1);
|
||||||
|
EnvTexture pref = UploadCube(device, allocator, prefMips, 1);
|
||||||
|
EnvTexture lut = UploadTexture2D(device, allocator, lutTexels,
|
||||||
|
kBrdfLutSize, vk::Format::eR16G16Sfloat);
|
||||||
|
|
||||||
|
vk::Sampler sampler = CreateEnvSampler(device, kPrefilterMips - 1);
|
||||||
|
vk::Sampler lutSampler = CreateLutSampler(device);
|
||||||
|
|
||||||
|
ENGINE_INFO("Neutral IBL environment (constant fallback, real BRDF LUT)");
|
||||||
|
return Scope<Cubemap>(new Cubemap(device, allocator,
|
||||||
|
env, irr, pref, lut, sampler, lutSampler));
|
||||||
|
}
|
||||||
|
|
||||||
|
Cubemap::~Cubemap()
|
||||||
|
{
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
|
vkDevice.destroySampler(m_EnvSampler);
|
||||||
|
vkDevice.destroySampler(m_LutSampler);
|
||||||
|
|
||||||
|
// 三元组成对释放:view → (image + 显存) 经 VMA 成对销毁
|
||||||
|
for (EnvTexture* tex : {&m_Env, &m_Irr, &m_Pref, &m_Lut}) {
|
||||||
|
vkDevice.destroyImageView(tex->view);
|
||||||
|
m_Allocator.Destroy(tex->allocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
@@ -7,14 +7,17 @@
|
|||||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
#include <Engine/Vulkan/DescriptorManager.h>
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
#include <Engine/Vulkan/Texture.h>
|
#include <Engine/Vulkan/Texture.h>
|
||||||
|
#include <Engine/Vulkan/UniformBuffer.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine
|
||||||
{
|
{
|
||||||
Material::Material(Device& device,
|
Material::Material(Device& device, MemoryAllocator& allocator,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
vk::DescriptorSetLayout layout,
|
vk::DescriptorSetLayout layout,
|
||||||
const std::array<Texture*, kSlotCount>& textures)
|
const std::array<Texture*, kSlotCount>& textures,
|
||||||
|
const MaterialParams& params)
|
||||||
: m_Textures(textures)
|
: m_Textures(textures)
|
||||||
{
|
{
|
||||||
m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout);
|
m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout);
|
||||||
@@ -25,18 +28,31 @@ namespace Engine
|
|||||||
m_Textures[slot]->GetSampler(),
|
m_Textures[slot]->GetSampler(),
|
||||||
m_Textures[slot]->GetView());
|
m_Textures[slot]->GetView());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 材质参数:per-slot ring,创建时写死(参数静态,无每帧更新)
|
||||||
|
m_ParamsBuffer = CreateScope<UniformBuffer>(
|
||||||
|
device, allocator, sizeof(MaterialParams), kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_ParamsBuffer->Update(slot, ¶ms, sizeof(params));
|
||||||
|
descriptorManager.UpdateUniformBuffer(
|
||||||
|
m_DescriptorSet, kParamsBinding,
|
||||||
|
m_ParamsBuffer->Get(slot), m_ParamsBuffer->GetSize());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Material::~Material() = default;
|
Material::~Material() = default;
|
||||||
|
|
||||||
std::string Material::MakeCacheKey(const std::string paths[kSlotCount])
|
std::string Material::MakeCacheKey(const std::string paths[kSlotCount],
|
||||||
|
const MaterialParams& params)
|
||||||
{
|
{
|
||||||
std::string key;
|
std::string key;
|
||||||
for (u32 slot = 0; slot < kSlotCount; ++slot) {
|
for (u32 slot = 0; slot < kSlotCount; ++slot) {
|
||||||
if (slot) key += '\n';
|
|
||||||
key += paths[slot];
|
key += paths[slot];
|
||||||
|
key += '\n';
|
||||||
}
|
}
|
||||||
|
key += "f:" + std::to_string(params.factors.x) +
|
||||||
|
"," + std::to_string(params.factors.y);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -38,15 +38,17 @@ Model::~Model() = default;
|
|||||||
void Model::InitDefaultTextures()
|
void Model::InitDefaultTextures()
|
||||||
{
|
{
|
||||||
// 各材质槽位的兜底纹理(1x1,开销可忽略):
|
// 各材质槽位的兜底纹理(1x1,开销可忽略):
|
||||||
// albedo 白(受光) / normal 平法线 (0.5,0.5,1) / specular 黑 / emissive 黑
|
// albedo 白 / MR 平参数(roughness 0.5, metallic 0)/ emissive 黑
|
||||||
m_DefaultTextures[Material::Albedo] =
|
m_DefaultTextures[Material::Albedo] =
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255);
|
Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255);
|
||||||
|
m_DefaultTextures[Material::MetallicRoughness] =
|
||||||
|
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 128, 0, false);
|
||||||
m_DefaultTextures[Material::Normal] =
|
m_DefaultTextures[Material::Normal] =
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false);
|
Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false);
|
||||||
m_DefaultTextures[Material::Specular] =
|
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0, false);
|
|
||||||
m_DefaultTextures[Material::Emissive] =
|
m_DefaultTextures[Material::Emissive] =
|
||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0);
|
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0);
|
||||||
|
m_DefaultTextures[Material::Ao] =
|
||||||
|
Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Model::Model(Device& device, MemoryAllocator& allocator,
|
Model::Model(Device& device, MemoryAllocator& allocator,
|
||||||
@@ -74,7 +76,7 @@ Model::Model(Device& device, MemoryAllocator& allocator,
|
|||||||
|
|
||||||
// 材质/纹理生命周期随模型(缓存持有)
|
// 材质/纹理生命周期随模型(缓存持有)
|
||||||
std::string paths[Material::kSlotCount] = {"", "", "", ""};
|
std::string paths[Material::kSlotCount] = {"", "", "", ""};
|
||||||
Material* mat = GetOrCreateMaterial(paths); // 全默认材质
|
Material* mat = GetOrCreateMaterial(paths, MaterialParams{}); // 全默认材质
|
||||||
|
|
||||||
m_Meshes.push_back(CreateScope<Mesh>(
|
m_Meshes.push_back(CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, mat));
|
m_Device, m_Allocator, vertices, indices, mat));
|
||||||
@@ -94,10 +96,24 @@ void Model::LoadModel(const std::string& path)
|
|||||||
{
|
{
|
||||||
m_ModelPath = path;
|
m_ModelPath = path;
|
||||||
|
|
||||||
|
// UV 翻转按格式区分:
|
||||||
|
// OBJ/FBX —— v 原点在底部,需翻到左上惯例(配合"v=0=图像顶"采样)
|
||||||
|
// glTF/GLB —— UV 本就是左上原点(v=0=图像顶),翻转会把所有
|
||||||
|
// 贴图上下颠倒(法线/MR 全反 = 塑料感元凶)
|
||||||
|
const std::string lower = [&] {
|
||||||
|
std::string s = path;
|
||||||
|
std::transform(s.begin(), s.end(), s.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
return s;
|
||||||
|
}();
|
||||||
|
m_IsGltf = lower.size() >= 5 &&
|
||||||
|
(lower.rfind(".gltf") == lower.size() - 5 ||
|
||||||
|
lower.rfind(".glb") == lower.size() - 4);
|
||||||
|
|
||||||
Assimp::Importer importer;
|
Assimp::Importer importer;
|
||||||
const aiScene* scene = importer.ReadFile(path,
|
const aiScene* scene = importer.ReadFile(path,
|
||||||
aiProcess_Triangulate |
|
aiProcess_Triangulate |
|
||||||
aiProcess_FlipUVs |
|
(m_IsGltf ? 0 : aiProcess_FlipUVs) |
|
||||||
aiProcess_GenSmoothNormals |
|
aiProcess_GenSmoothNormals |
|
||||||
aiProcess_CalcTangentSpace | // 法线贴图 TBN
|
aiProcess_CalcTangentSpace | // 法线贴图 TBN
|
||||||
aiProcess_JoinIdenticalVertices);
|
aiProcess_JoinIdenticalVertices);
|
||||||
@@ -148,9 +164,10 @@ Texture* Model::GetOrLoadTexture(const std::string& rawPath, bool srgb)
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount])
|
Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount],
|
||||||
|
const MaterialParams& params)
|
||||||
{
|
{
|
||||||
const std::string key = Material::MakeCacheKey(paths);
|
const std::string key = Material::MakeCacheKey(paths, params);
|
||||||
if (auto it = m_MaterialCache.find(key); it != m_MaterialCache.end()) {
|
if (auto it = m_MaterialCache.find(key); it != m_MaterialCache.end()) {
|
||||||
return it->second.get();
|
return it->second.get();
|
||||||
}
|
}
|
||||||
@@ -169,7 +186,8 @@ Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto mat = CreateScope<Material>(
|
auto mat = CreateScope<Material>(
|
||||||
m_Device, m_DescriptorManager, m_TextureLayout, textures);
|
m_Device, m_Allocator, m_DescriptorManager, m_TextureLayout,
|
||||||
|
textures, params);
|
||||||
Material* ptr = mat.get();
|
Material* ptr = mat.get();
|
||||||
m_MaterialCache.emplace(key, std::move(mat));
|
m_MaterialCache.emplace(key, std::move(mat));
|
||||||
return ptr;
|
return ptr;
|
||||||
@@ -225,32 +243,88 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取材质纹理路径(4 槽:albedo/normal/specular/emissive)
|
// 读取材质纹理路径(PBR metallic-roughness 槽位)+ 金属度/粗糙度因子
|
||||||
std::string texPaths[Material::kSlotCount];
|
std::string texPaths[Material::kSlotCount];
|
||||||
|
MaterialParams params;
|
||||||
|
|
||||||
if (mesh->mMaterialIndex >= 0) {
|
if (mesh->mMaterialIndex >= 0) {
|
||||||
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
||||||
const std::filesystem::path modelDir =
|
const std::filesystem::path modelDir =
|
||||||
std::filesystem::path(m_ModelPath).parent_path();
|
std::filesystem::path(m_ModelPath).parent_path();
|
||||||
|
|
||||||
|
// glTF2 贴图在 assimp 5.x 的落点:MR 合图 → METALNESS(15)/
|
||||||
|
// DIFFUSE_ROUGHNESS(16)(同图双槽);baseColor → BASE_COLOR(12)。
|
||||||
|
// OBJ:map_Ns → SHININESS(独立 roughness 图)、map_Ka 常绑 AO。
|
||||||
|
// 先到先得,数组顺序即优先级
|
||||||
const std::pair<aiTextureType, u32> slots[] = {
|
const std::pair<aiTextureType, u32> slots[] = {
|
||||||
{aiTextureType_DIFFUSE, Material::Albedo},
|
{aiTextureType_DIFFUSE, Material::Albedo},
|
||||||
|
{aiTextureType_BASE_COLOR, Material::Albedo},
|
||||||
{aiTextureType_NORMALS, Material::Normal},
|
{aiTextureType_NORMALS, Material::Normal},
|
||||||
{aiTextureType_SPECULAR, Material::Specular},
|
{aiTextureType_HEIGHT, Material::Normal}, // OBJ map_Bump 落点
|
||||||
|
{aiTextureType_METALNESS, Material::MetallicRoughness},
|
||||||
|
{aiTextureType_DIFFUSE_ROUGHNESS, Material::MetallicRoughness},
|
||||||
|
{aiTextureType_UNKNOWN, Material::MetallicRoughness},
|
||||||
|
{aiTextureType_SHININESS, Material::MetallicRoughness},
|
||||||
|
{aiTextureType_AMBIENT_OCCLUSION, Material::Ao},
|
||||||
|
{aiTextureType_LIGHTMAP, Material::Ao},
|
||||||
|
{aiTextureType_AMBIENT, Material::Ao},
|
||||||
{aiTextureType_EMISSIVE, Material::Emissive},
|
{aiTextureType_EMISSIVE, Material::Emissive},
|
||||||
|
{aiTextureType_EMISSION_COLOR, Material::Emissive},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& [type, slot] : slots) {
|
for (const auto& [type, slot] : slots) {
|
||||||
|
if (!texPaths[slot].empty()) continue; // 高优先级已命中
|
||||||
aiString path;
|
aiString path;
|
||||||
if (material->GetTexture(type, 0, &path) == AI_SUCCESS &&
|
if (material->GetTexture(type, 0, &path) == AI_SUCCESS &&
|
||||||
path.length > 0) {
|
path.length > 0) {
|
||||||
texPaths[slot] = (modelDir / path.C_Str()).string();
|
texPaths[slot] = (modelDir / path.C_Str()).string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 注:不做文件名探测/自动补槽——资产缺什么就是什么;
|
||||||
|
// 补槽是将来手动材质编辑的职责,加载期保持确定性
|
||||||
|
|
||||||
|
// 材质加载可见性:贴图槽缺失/路径解析错误会静默退化成默认
|
||||||
|
// 纹理(金属全没 → 塑料感),这里留下诊断锚点
|
||||||
|
ENGINE_INFO("Material '{}': albedo={} normal={} MR={} emissive={} ao={}",
|
||||||
|
material->GetName().C_Str(),
|
||||||
|
texPaths[Material::Albedo].empty() ? "<none>"
|
||||||
|
: texPaths[Material::Albedo],
|
||||||
|
texPaths[Material::Normal].empty() ? "<none>"
|
||||||
|
: texPaths[Material::Normal],
|
||||||
|
texPaths[Material::MetallicRoughness].empty() ? "<none>"
|
||||||
|
: texPaths[Material::MetallicRoughness],
|
||||||
|
texPaths[Material::Emissive].empty() ? "<none>"
|
||||||
|
: texPaths[Material::Emissive],
|
||||||
|
texPaths[Material::Ao].empty() ? "<none>"
|
||||||
|
: texPaths[Material::Ao]);
|
||||||
|
|
||||||
|
// 因子按工作流分支:
|
||||||
|
// glTF2:MR 合图(B=金属度语义),因子按声明(缺省 1/1)
|
||||||
|
// OBJ:specular 工作流无金属概念 → metallic=0;
|
||||||
|
// roughness 有独立贴图 → 因子 1(G 通道直达);
|
||||||
|
// 无贴图 → Blinn-Phong Ns 换算 R=sqrt(2/(Ns+2)),
|
||||||
|
// 补偿默认 MR 贴图 G=0.5 → 因子 = 2R
|
||||||
|
if (m_IsGltf) {
|
||||||
|
ai_real metallic = 1.0f, roughness = 1.0f;
|
||||||
|
material->Get(AI_MATKEY_METALLIC_FACTOR, metallic);
|
||||||
|
material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness);
|
||||||
|
params.factors = glm::vec4(static_cast<f32>(metallic),
|
||||||
|
static_cast<f32>(roughness), 0.0f, 0.0f);
|
||||||
|
} else {
|
||||||
|
f32 roughnessFactor = 1.0f;
|
||||||
|
if (texPaths[Material::MetallicRoughness].empty()) {
|
||||||
|
ai_real shininess = 10.0f;
|
||||||
|
material->Get(AI_MATKEY_SHININESS, shininess);
|
||||||
|
const f32 rough =
|
||||||
|
std::sqrt(2.0f / (static_cast<f32>(shininess) + 2.0f));
|
||||||
|
roughnessFactor = 2.0f * glm::clamp(rough, 0.05f, 1.0f);
|
||||||
|
}
|
||||||
|
params.factors = glm::vec4(0.0f, roughnessFactor, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 材质缓存:同贴图组合 → 同一 Material(一个描述符集),mesh 数 ≠ 集数
|
// 材质缓存:同贴图组合+因子 → 同一 Material(一个描述符集)
|
||||||
Material* mat = GetOrCreateMaterial(texPaths);
|
Material* mat = GetOrCreateMaterial(texPaths, params);
|
||||||
|
|
||||||
return CreateScope<Mesh>(
|
return CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, mat);
|
m_Device, m_Allocator, vertices, indices, mat);
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ namespace Engine::Passes
|
|||||||
std::array<float, 4>{0.1f, 0.1f, 0.1f, 0.0f});
|
std::array<float, 4>{0.1f, 0.1f, 0.1f, 0.0f});
|
||||||
|
|
||||||
vk::RenderingAttachmentInfo colorAttachments[2];
|
vk::RenderingAttachmentInfo colorAttachments[2];
|
||||||
|
// HDR 附件由 Skybox pass 清屏并画背景,这里 Load 在其上合成几何
|
||||||
colorAttachments[0].setImageView(ctx.View(m_HDRColor))
|
colorAttachments[0].setImageView(ctx.View(m_HDRColor))
|
||||||
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
.setLoadOp(vk::AttachmentLoadOp::eLoad)
|
||||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
.setClearValue(clearValue);
|
.setClearValue(clearValue);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <Engine/Renderer/Passes/SceneGpu.h>
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
#include <Engine/Renderer/Material.h>
|
#include <Engine/Renderer/Material.h>
|
||||||
|
#include <Engine/Renderer/Cubemap.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
#include <Engine/Vulkan/DescriptorManager.h>
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
#include <Engine/Vulkan/ShaderManager.h>
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
@@ -32,23 +33,32 @@ namespace Engine::Passes
|
|||||||
};
|
};
|
||||||
uboSetLayout = descriptors.CreateLayout(uboBindings);
|
uboSetLayout = descriptors.CreateLayout(uboBindings);
|
||||||
|
|
||||||
// Set 1: 材质纹理槽(binding = Material::Slot:0 albedo / 1 normal /
|
// Set 1: PBR 材质纹理槽(binding = Material::Slot:0 albedo /
|
||||||
// 2 specular / 3 emissive)
|
// 1 normal / 2 metallicRoughness / 3 emissive)+ 参数 UBO(binding 4)
|
||||||
std::vector<DescriptorBinding> texBindings;
|
std::vector<DescriptorBinding> texBindings;
|
||||||
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
|
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
|
||||||
texBindings.push_back(
|
texBindings.push_back(
|
||||||
{slot, vk::DescriptorType::eCombinedImageSampler,
|
{slot, vk::DescriptorType::eCombinedImageSampler,
|
||||||
vk::ShaderStageFlagBits::eFragment});
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
}
|
}
|
||||||
|
texBindings.push_back(
|
||||||
|
{Material::kParamsBinding, vk::DescriptorType::eUniformBuffer,
|
||||||
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
textureSetLayout = descriptors.CreateLayout(texBindings);
|
textureSetLayout = descriptors.CreateLayout(texBindings);
|
||||||
|
|
||||||
// Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
|
// Set 2: CSM 级联阴影图(0/1/2)+ IBL 环境三件套(3 irradiance
|
||||||
|
// / 4 pre-filtered / 5 BRDF LUT),均 fragment 采样
|
||||||
std::vector<DescriptorBinding> shadowBindings;
|
std::vector<DescriptorBinding> shadowBindings;
|
||||||
for (u32 c = 0; c < kMaxCascades; ++c) {
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
shadowBindings.push_back(
|
shadowBindings.push_back(
|
||||||
{c, vk::DescriptorType::eCombinedImageSampler,
|
{c, vk::DescriptorType::eCombinedImageSampler,
|
||||||
vk::ShaderStageFlagBits::eFragment});
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
}
|
}
|
||||||
|
for (const u32 b : {3u, 4u, 5u}) {
|
||||||
|
shadowBindings.push_back(
|
||||||
|
{b, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
|
}
|
||||||
shadowSetLayout = descriptors.CreateLayout(shadowBindings);
|
shadowSetLayout = descriptors.CreateLayout(shadowBindings);
|
||||||
|
|
||||||
// Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO)
|
// Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO)
|
||||||
@@ -154,4 +164,21 @@ namespace Engine::Passes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SceneGpu::BindEnvironment(const Cubemap& env)
|
||||||
|
{
|
||||||
|
// IBL 资源 view 全程稳定(不随 RG 重编译变化),绑一次即可。
|
||||||
|
// cube 资源必须用带 mip 的环境采样器(textureLod 显式 lod 会被
|
||||||
|
// clamp 到 maxLod;shadowSampler maxLod=0 会让 pre-filter 永远
|
||||||
|
// 停在最锐的 mip)
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Descriptors.UpdateTexture(shadowSets[slot], 3, env.GetSampler(),
|
||||||
|
env.GetIrradianceView());
|
||||||
|
m_Descriptors.UpdateTexture(shadowSets[slot], 4, env.GetSampler(),
|
||||||
|
env.GetPrefilteredView());
|
||||||
|
m_Descriptors.UpdateTexture(shadowSets[slot], 5,
|
||||||
|
env.GetBrdfLutSampler(),
|
||||||
|
env.GetBrdfLutView());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Engine::Passes
|
} // namespace Engine::Passes
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/SkyboxPass.h>
|
||||||
|
#include <Engine/Renderer/Cubemap.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
SkyboxPass::SkyboxPass(Device& device, ShaderManager& shaders,
|
||||||
|
DescriptorManager& descriptors, FrameSceneData& frame,
|
||||||
|
RG::TextureHandle hdrColor, const Cubemap* env)
|
||||||
|
: m_Device(device), m_Descriptors(descriptors), m_Frame(frame),
|
||||||
|
m_HDRColor(hdrColor), m_Env(env)
|
||||||
|
{
|
||||||
|
// Set0 = {samplerCube}(fragment 采样)
|
||||||
|
std::vector<DescriptorBinding> bindings = {
|
||||||
|
{0, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
|
};
|
||||||
|
m_SetLayout = descriptors.CreateLayout(bindings);
|
||||||
|
|
||||||
|
// push = invVP 64B(skybox.vert/.frag 声明一致,VUID-08878)
|
||||||
|
m_Layout = descriptors.CreatePipelineLayout(
|
||||||
|
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
|
||||||
|
const std::string shaderDir = "shaders/";
|
||||||
|
auto vert = shaders.CreateFromFile(
|
||||||
|
shaderDir + "skybox.vert.spv",
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
auto frag = shaders.CreateFromFile(
|
||||||
|
shaderDir + "skybox.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{m_SetLayout},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
|
||||||
|
m_Vert = vert.Handle;
|
||||||
|
m_Frag = frag.Handle;
|
||||||
|
if (!m_Vert || !m_Frag) {
|
||||||
|
ENGINE_FATAL("SkyboxPass: failed to create shader objects");
|
||||||
|
}
|
||||||
|
|
||||||
|
// per-slot set:cube view 全程稳定,构造期绑定一次即可
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Sets[slot] = descriptors.AllocateDescriptorSet(m_SetLayout);
|
||||||
|
if (m_Env) {
|
||||||
|
descriptors.UpdateTexture(m_Sets[slot], 0,
|
||||||
|
m_Env->GetSampler(),
|
||||||
|
m_Env->GetView());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SkyboxPass::~SkyboxPass()
|
||||||
|
{
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
|
if (m_Vert) vkDevice.destroyShaderEXT(m_Vert);
|
||||||
|
if (m_Frag) vkDevice.destroyShaderEXT(m_Frag);
|
||||||
|
vkDevice.destroyPipelineLayout(m_Layout);
|
||||||
|
vkDevice.destroyDescriptorSetLayout(m_SetLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkyboxPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
rg.AddPass("Skybox", [this](RG::RenderGraphContext& ctx) {
|
||||||
|
ZoneScopedN("Skybox");
|
||||||
|
const vk::CommandBuffer cmd = ctx.cmd;
|
||||||
|
|
||||||
|
vk::ClearValue clear;
|
||||||
|
clear.color = vk::ClearColorValue(
|
||||||
|
std::array<float, 4>{0.1f, 0.1f, 0.1f, 1.0f});
|
||||||
|
|
||||||
|
vk::RenderingAttachmentInfo color;
|
||||||
|
color.setImageView(ctx.View(m_HDRColor))
|
||||||
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eClear) // 清屏职责在本 pass
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
|
.setClearValue(clear);
|
||||||
|
|
||||||
|
vk::RenderingInfo renderingInfo;
|
||||||
|
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
|
||||||
|
.setLayerCount(1)
|
||||||
|
.setColorAttachments(color); // 无深度附件
|
||||||
|
|
||||||
|
cmd.beginRendering(renderingInfo);
|
||||||
|
|
||||||
|
if (m_Env) {
|
||||||
|
// 无环境贴图:只清屏(Forward loadOp=Load 依赖这里先写)
|
||||||
|
std::array<vk::ShaderStageFlagBits, 2> stages = {
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
vk::ShaderStageFlagBits::eFragment
|
||||||
|
};
|
||||||
|
std::array<vk::ShaderEXT, 2> shaders = {m_Vert, m_Frag};
|
||||||
|
cmd.bindShadersEXT(stages, shaders);
|
||||||
|
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
m_Layout, 0,
|
||||||
|
m_Sets[ctx.frame.frameSlot], {});
|
||||||
|
cmd.pushConstants<glm::mat4>(
|
||||||
|
m_Layout,
|
||||||
|
vk::ShaderStageFlagBits::eVertex |
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
0, m_Frame.skyInvVP);
|
||||||
|
|
||||||
|
// ---- 动态状态 ----
|
||||||
|
cmd.setRasterizerDiscardEnable(VK_FALSE);
|
||||||
|
|
||||||
|
cmd.setDepthTestEnable(VK_FALSE);
|
||||||
|
cmd.setDepthWriteEnable(VK_FALSE);
|
||||||
|
cmd.setDepthBoundsTestEnable(VK_FALSE);
|
||||||
|
cmd.setStencilTestEnable(VK_FALSE);
|
||||||
|
cmd.setDepthBiasEnable(VK_FALSE);
|
||||||
|
|
||||||
|
cmd.setPolygonModeEXT(vk::PolygonMode::eFill);
|
||||||
|
cmd.setCullMode(vk::CullModeFlagBits::eNone);
|
||||||
|
cmd.setFrontFace(vk::FrontFace::eCounterClockwise);
|
||||||
|
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
|
||||||
|
cmd.setPrimitiveRestartEnable(VK_FALSE);
|
||||||
|
|
||||||
|
// 全屏三角:无顶点输入
|
||||||
|
cmd.setVertexInputEXT({}, {});
|
||||||
|
|
||||||
|
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>(ctx.extent.width),
|
||||||
|
static_cast<float>(ctx.extent.height),
|
||||||
|
0.0f, 1.0f);
|
||||||
|
cmd.setViewportWithCount(viewport);
|
||||||
|
cmd.setScissorWithCount(vk::Rect2D({0, 0}, ctx.extent));
|
||||||
|
|
||||||
|
// 无混合直写(天空不透明)
|
||||||
|
vk::ColorBlendEquationEXT blendEquation;
|
||||||
|
blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne)
|
||||||
|
.setDstColorBlendFactor(vk::BlendFactor::eZero)
|
||||||
|
.setColorBlendOp(vk::BlendOp::eAdd)
|
||||||
|
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
|
||||||
|
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
|
||||||
|
.setAlphaBlendOp(vk::BlendOp::eAdd);
|
||||||
|
// ArrayProxyNoTemporaries 拒绝临时量:具名局部变量传入
|
||||||
|
vk::Bool32 blendEnable = VK_FALSE;
|
||||||
|
vk::ColorComponentFlags colorWriteMask =
|
||||||
|
vk::ColorComponentFlagBits::eR |
|
||||||
|
vk::ColorComponentFlagBits::eG |
|
||||||
|
vk::ColorComponentFlagBits::eB |
|
||||||
|
vk::ColorComponentFlagBits::eA;
|
||||||
|
cmd.setColorBlendEnableEXT(0, blendEnable);
|
||||||
|
cmd.setColorBlendEquationEXT(0, blendEquation);
|
||||||
|
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
||||||
|
|
||||||
|
cmd.draw(3, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.endRendering();
|
||||||
|
})
|
||||||
|
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -12,8 +12,10 @@
|
|||||||
#include <Engine/Renderer/Passes/SceneGpu.h>
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
#include <Engine/Renderer/Passes/RenderPass.h>
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/SkyboxPass.h>
|
||||||
#include <Engine/Renderer/Passes/ShadowPass.h>
|
#include <Engine/Renderer/Passes/ShadowPass.h>
|
||||||
#include <Engine/Renderer/Passes/ForwardPass.h>
|
#include <Engine/Renderer/Passes/ForwardPass.h>
|
||||||
|
#include <Engine/Renderer/Cubemap.h>
|
||||||
#include <Engine/Renderer/Passes/TAAPass.h>
|
#include <Engine/Renderer/Passes/TAAPass.h>
|
||||||
#include <Engine/Renderer/Passes/BloomPass.h>
|
#include <Engine/Renderer/Passes/BloomPass.h>
|
||||||
#include <Engine/Renderer/Passes/TonemapPass.h>
|
#include <Engine/Renderer/Passes/TonemapPass.h>
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine
|
||||||
@@ -71,6 +74,18 @@ namespace Engine
|
|||||||
m_FullscreenGpu = CreateScope<Passes::FullscreenGpu>(
|
m_FullscreenGpu = CreateScope<Passes::FullscreenGpu>(
|
||||||
*m_Device, *m_DescriptorManager, *m_ShaderManager);
|
*m_Device, *m_DescriptorManager, *m_ShaderManager);
|
||||||
|
|
||||||
|
// 3.5 环境贴图(IBL):HDR 等距柱状 → cube + 预积分。
|
||||||
|
// 加载失败/文件缺失一律中性常数兜底(描述符恒有效)
|
||||||
|
const std::string envPath = "assets/textures/venice_dawn_1_4k.hdr";
|
||||||
|
if (std::filesystem::exists(envPath)) {
|
||||||
|
m_Environment = Cubemap::CreateFromHdrEquirect(
|
||||||
|
*m_Device, *m_Allocator, envPath, 1024);
|
||||||
|
}
|
||||||
|
if (!m_Environment) {
|
||||||
|
m_Environment = Cubemap::CreateNeutral(
|
||||||
|
*m_Device, *m_Allocator, glm::vec3(0.1f));
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Swapchain(只管呈现;渲染目标统一归 RenderGraph)
|
// 4. Swapchain(只管呈现;渲染目标统一归 RenderGraph)
|
||||||
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, m_Window);
|
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, m_Window);
|
||||||
m_Extent = m_Swapchain->GetExtent();
|
m_Extent = m_Swapchain->GetExtent();
|
||||||
@@ -96,6 +111,8 @@ namespace Engine
|
|||||||
for (auto& pass : m_Passes) {
|
for (auto& pass : m_Passes) {
|
||||||
pass->OnTargetsRebuilt(*m_RenderGraph);
|
pass->OnTargetsRebuilt(*m_RenderGraph);
|
||||||
}
|
}
|
||||||
|
// 环境辐照度绑定(view 全程稳定,绑一次)
|
||||||
|
m_SceneGpu->BindEnvironment(*m_Environment);
|
||||||
|
|
||||||
// 9. GPU timestamp:每 pass 两枚(前后),按帧槽位分区
|
// 9. GPU timestamp:每 pass 两枚(前后),按帧槽位分区
|
||||||
m_QueriesPerFrame = static_cast<u32>(m_RenderGraph->GetPassCount()) * 2;
|
m_QueriesPerFrame = static_cast<u32>(m_RenderGraph->GetPassCount()) * 2;
|
||||||
@@ -228,6 +245,10 @@ namespace Engine
|
|||||||
FrameSceneData& frame = m_SceneRenderer->Frame();
|
FrameSceneData& frame = m_SceneRenderer->Frame();
|
||||||
m_Passes.push_back(CreateScope<Passes::ShadowPass>(
|
m_Passes.push_back(CreateScope<Passes::ShadowPass>(
|
||||||
*m_SceneGpu, frame, m_ShadowMaps));
|
*m_SceneGpu, frame, m_ShadowMaps));
|
||||||
|
// Skybox = HDRColor 的 first writer(清屏 + 画背景),先于 Forward 注册
|
||||||
|
m_Passes.push_back(CreateScope<Passes::SkyboxPass>(
|
||||||
|
*m_Device, *m_ShaderManager, *m_DescriptorManager, frame,
|
||||||
|
m_HDRColor, m_Environment.get()));
|
||||||
m_Passes.push_back(CreateScope<Passes::ForwardPass>(
|
m_Passes.push_back(CreateScope<Passes::ForwardPass>(
|
||||||
*m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_ShadowMaps));
|
*m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_ShadowMaps));
|
||||||
m_Passes.push_back(CreateScope<Passes::TAAPass>(
|
m_Passes.push_back(CreateScope<Passes::TAAPass>(
|
||||||
|
|||||||
@@ -168,24 +168,44 @@ namespace Engine
|
|||||||
|
|
||||||
ubo.ambientColor = scene.GetSettings().ambientColor;
|
ubo.ambientColor = scene.GetSettings().ambientColor;
|
||||||
|
|
||||||
// ---- 2. 灯光(第一个灯作为阴影光源)----
|
// 天空盒:视图去平移(只留旋转)× 投影 → 逆矩阵,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;
|
u32 lightCount = 0;
|
||||||
glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射
|
glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射
|
||||||
scene.Each<LightComponent>([&](Entity, LightComponent& light) {
|
scene.Each<TransformComponent, LightComponent>(
|
||||||
|
[&](Entity, TransformComponent& t, LightComponent& light) {
|
||||||
if (lightCount >= kMaxLights) return;
|
if (lightCount >= kMaxLights) return;
|
||||||
|
|
||||||
glm::vec3 dir = glm::length(light.direction) > 0.0f
|
auto& dst = ubo.lights[lightCount];
|
||||||
? glm::normalize(light.direction)
|
dst.color = light.color;
|
||||||
: glm::vec3(0.0f);
|
dst.params = glm::vec4(
|
||||||
ubo.lights[lightCount].direction = glm::vec4(dir, 0.0f);
|
light.type == LightComponent::Type::Point ? 1.0f : 0.0f,
|
||||||
ubo.lights[lightCount].color = light.color;
|
light.radius, 0.0f, 0.0f);
|
||||||
|
|
||||||
if (lightCount == 0) shadowDir = dir;
|
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;
|
++lightCount;
|
||||||
});
|
});
|
||||||
for (u32 i = lightCount; i < kMaxLights; ++i) {
|
for (u32 i = lightCount; i < kMaxLights; ++i) {
|
||||||
ubo.lights[i].direction = glm::vec4(0.0f);
|
ubo.lights[i] = {};
|
||||||
ubo.lights[i].color = glm::vec4(0.0f);
|
|
||||||
}
|
}
|
||||||
ComputeCascades(shadowDir);
|
ComputeCascades(shadowDir);
|
||||||
ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f);
|
ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f);
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
Buffer::Buffer(Device& device, MemoryAllocator& allocator,
|
Buffer::Buffer(Device& device, MemoryAllocator& allocator,
|
||||||
vk::DeviceSize size, vk::BufferUsageFlags usage,
|
const vk::DeviceSize size, const vk::BufferUsageFlags usage,
|
||||||
vk::MemoryPropertyFlags memProps)
|
const vk::MemoryPropertyFlags memProps)
|
||||||
: m_Device(device), m_Allocator(allocator) , m_Size(size)
|
: m_Device(device), m_Size(size) , m_Allocator(allocator)
|
||||||
{
|
{
|
||||||
// VMA 惯用法:创建 + 分配 + 绑定一步完成;
|
// VMA 惯用法:创建 + 分配 + 绑定一步完成;
|
||||||
// HOST_VISIBLE 时持久映射,MappedPtr 直接可用
|
// HOST_VISIBLE 时持久映射,MappedPtr 直接可用
|
||||||
@@ -33,7 +33,7 @@ namespace Engine {
|
|||||||
STATS.RemoveBuffer(m_Size);
|
STATS.RemoveBuffer(m_Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Buffer::Upload(const void* data, vk::DeviceSize size, vk::DeviceSize offset)
|
void Buffer::Upload(const void* data, const vk::DeviceSize size, const vk::DeviceSize offset) const
|
||||||
{
|
{
|
||||||
if (!m_MappedPtr) {
|
if (!m_MappedPtr) {
|
||||||
ENGINE_ERROR("Buffer::Upload: buffer is not host visible");
|
ENGINE_ERROR("Buffer::Upload: buffer is not host visible");
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ namespace Engine {
|
|||||||
m_Device.GetDevice().destroyCommandPool(m_Pool);
|
m_Device.GetDevice().destroyCommandPool(m_Pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::CommandBuffer CommandManager::BeginFrame(u32 frameSlot)
|
vk::CommandBuffer CommandManager::BeginFrame(const u32 frameSlot) const
|
||||||
{
|
{
|
||||||
auto cmd = m_Buffers[frameSlot];
|
const auto cmd = m_Buffers[frameSlot];
|
||||||
cmd.reset();
|
cmd.reset();
|
||||||
|
|
||||||
vk::CommandBufferBeginInfo beginInfo;
|
vk::CommandBufferBeginInfo beginInfo;
|
||||||
@@ -47,7 +47,7 @@ namespace Engine {
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandManager::EndFrame(u32 frameSlot)
|
void CommandManager::EndFrame(const u32 frameSlot) const
|
||||||
{
|
{
|
||||||
m_Buffers[frameSlot].end();
|
m_Buffers[frameSlot].end();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ namespace Engine
|
|||||||
|
|
||||||
DescriptorManager::~DescriptorManager()
|
DescriptorManager::~DescriptorManager()
|
||||||
{
|
{
|
||||||
for (auto pool : m_Pools) {
|
for (const auto pool : m_Pools) {
|
||||||
m_Device.GetDevice().destroyDescriptorPool(pool);
|
m_Device.GetDevice().destroyDescriptorPool(pool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::DescriptorPool DescriptorManager::CreateChunk()
|
vk::DescriptorPool DescriptorManager::CreateChunk() const
|
||||||
{
|
{
|
||||||
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
||||||
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets),
|
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets),
|
||||||
@@ -39,7 +39,7 @@ namespace Engine
|
|||||||
return m_Device.GetDevice().createDescriptorPool(poolInfo);
|
return m_Device.GetDevice().createDescriptorPool(poolInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings)
|
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings) const
|
||||||
{
|
{
|
||||||
std::vector<vk::DescriptorSetLayoutBinding> vkBindings;
|
std::vector<vk::DescriptorSetLayoutBinding> vkBindings;
|
||||||
for (auto& b : bindings)
|
for (auto& b : bindings)
|
||||||
@@ -80,8 +80,8 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateUniformBuffer(const vk::DescriptorSet set, const u32 binding,
|
||||||
vk::Buffer buffer, u64 size)
|
const vk::Buffer buffer, const u64 size) const
|
||||||
{
|
{
|
||||||
vk::DescriptorBufferInfo bufferInfo;
|
vk::DescriptorBufferInfo bufferInfo;
|
||||||
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
||||||
@@ -97,8 +97,8 @@ namespace Engine
|
|||||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateStorageBuffer(const vk::DescriptorSet set, const u32 binding,
|
||||||
vk::Buffer buffer, u64 size)
|
const vk::Buffer buffer, const u64 size) const
|
||||||
{
|
{
|
||||||
vk::DescriptorBufferInfo bufferInfo;
|
vk::DescriptorBufferInfo bufferInfo;
|
||||||
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
||||||
@@ -114,8 +114,8 @@ namespace Engine
|
|||||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateTexture(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateTexture(const vk::DescriptorSet set, const u32 binding,
|
||||||
vk::Sampler sampler, vk::ImageView view)
|
const vk::Sampler sampler, const vk::ImageView view) const
|
||||||
{
|
{
|
||||||
vk::DescriptorImageInfo imageInfo;
|
vk::DescriptorImageInfo imageInfo;
|
||||||
imageInfo.setSampler(sampler)
|
imageInfo.setSampler(sampler)
|
||||||
@@ -134,7 +134,7 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
||||||
const u32 pushConstantSize)
|
const u32 pushConstantSize) const
|
||||||
{
|
{
|
||||||
vk::PipelineLayoutCreateInfo info;
|
vk::PipelineLayoutCreateInfo info;
|
||||||
info.setSetLayouts(layout);
|
info.setSetLayouts(layout);
|
||||||
@@ -154,7 +154,7 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(const std::vector<vk::DescriptorSetLayout>& layouts,
|
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize)
|
const u32 pushConstantSize) const
|
||||||
{
|
{
|
||||||
vk::PipelineLayoutCreateInfo info;
|
vk::PipelineLayoutCreateInfo info;
|
||||||
info.setSetLayouts(layouts);
|
info.setSetLayouts(layouts);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MemoryAllocator::Allocation MemoryAllocator::CreateBuffer(
|
MemoryAllocator::Allocation MemoryAllocator::CreateBuffer(
|
||||||
vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags props)
|
vk::DeviceSize size, const vk::BufferUsageFlags usage, const vk::MemoryPropertyFlags props) const
|
||||||
{
|
{
|
||||||
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||||
bi.size = size;
|
bi.size = size;
|
||||||
@@ -92,7 +92,7 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MemoryAllocator::Allocation MemoryAllocator::CreateImage(
|
MemoryAllocator::Allocation MemoryAllocator::CreateImage(
|
||||||
const vk::ImageCreateInfo& imageInfo, vk::MemoryPropertyFlags props)
|
const vk::ImageCreateInfo& imageInfo, const vk::MemoryPropertyFlags props) const
|
||||||
{
|
{
|
||||||
VkImageCreateInfo ii = imageInfo; // 同布局,隐式转换
|
VkImageCreateInfo ii = imageInfo; // 同布局,隐式转换
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ namespace Engine {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryAllocator::Destroy(Allocation& alloc)
|
void MemoryAllocator::Destroy(Allocation& alloc) const
|
||||||
{
|
{
|
||||||
if (!alloc.Valid) return;
|
if (!alloc.Valid) return;
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ namespace Engine {
|
|||||||
alloc = {};
|
alloc = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryAllocator::Flush(const Allocation& alloc)
|
void MemoryAllocator::Flush(const Allocation& alloc) const
|
||||||
{
|
{
|
||||||
if (!alloc.Valid || !alloc.Handle) return;
|
if (!alloc.Valid || !alloc.Handle) return;
|
||||||
// HOST_COHERENT 时 VMA 内部自动跳过
|
// HOST_COHERENT 时 VMA 内部自动跳过
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ ShaderManager::~ShaderManager()
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath,
|
ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath,
|
||||||
vk::ShaderStageFlagBits stage,
|
const vk::ShaderStageFlagBits stage,
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts,
|
const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize)
|
const u32 pushConstantSize)
|
||||||
{
|
{
|
||||||
auto spirv = ReadSPIRV(spvPath);
|
const auto spirv = ReadSPIRV(spvPath);
|
||||||
if (spirv.empty()) {
|
if (spirv.empty()) {
|
||||||
ENGINE_ERROR("Failed to read SPIR-V: {}", spvPath);
|
ENGINE_ERROR("Failed to read SPIR-V: {}", spvPath);
|
||||||
return { nullptr, stage };
|
return { nullptr, stage };
|
||||||
@@ -34,9 +34,9 @@ ShaderObject ShaderManager::CreateFromFile(const std::string& spvPath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
||||||
vk::ShaderStageFlagBits stage,
|
const vk::ShaderStageFlagBits stage,
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts,
|
const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize)
|
const u32 pushConstantSize) const
|
||||||
{
|
{
|
||||||
vk::ShaderStageFlags nextStage = {};
|
vk::ShaderStageFlags nextStage = {};
|
||||||
if (stage == vk::ShaderStageFlagBits::eVertex) {
|
if (stage == vk::ShaderStageFlagBits::eVertex) {
|
||||||
@@ -70,7 +70,7 @@ ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
|||||||
|
|
||||||
vk::ShaderEXT shader;
|
vk::ShaderEXT shader;
|
||||||
try {
|
try {
|
||||||
auto result = m_Device.GetDevice().createShadersEXT(createInfo);
|
const auto result = m_Device.GetDevice().createShadersEXT(createInfo);
|
||||||
shader = result.value[0];
|
shader = result.value[0];
|
||||||
}
|
}
|
||||||
catch (const vk::SystemError& e) {
|
catch (const vk::SystemError& e) {
|
||||||
@@ -81,7 +81,7 @@ ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
|||||||
return { shader, stage };
|
return { shader, stage };
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderManager::Destroy(const ShaderObject& shader)
|
void ShaderManager::Destroy(const ShaderObject& shader) const
|
||||||
{
|
{
|
||||||
if (shader.Handle) {
|
if (shader.Handle) {
|
||||||
m_Device.GetDevice().destroyShaderEXT(shader.Handle);
|
m_Device.GetDevice().destroyShaderEXT(shader.Handle);
|
||||||
@@ -96,7 +96,7 @@ std::vector<u32> ShaderManager::ReadSPIRV(const std::string& path)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size = file.tellg();
|
const size_t size = file.tellg();
|
||||||
std::vector<u32> buffer(size / sizeof(u32));
|
std::vector<u32> buffer(size / sizeof(u32));
|
||||||
file.seekg(0);
|
file.seekg(0);
|
||||||
file.read(reinterpret_cast<char*>(buffer.data()), size);
|
file.read(reinterpret_cast<char*>(buffer.data()), size);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Engine {
|
|||||||
SyncManager::SyncManager(Device& device, u32 maxFramesInFlight, u32 imageCount)
|
SyncManager::SyncManager(Device& device, u32 maxFramesInFlight, u32 imageCount)
|
||||||
: m_Device(device)
|
: m_Device(device)
|
||||||
{
|
{
|
||||||
vk::SemaphoreCreateInfo semInfo;
|
const vk::SemaphoreCreateInfo semInfo;
|
||||||
|
|
||||||
// 每帧一个 ImageAvailable
|
// 每帧一个 ImageAvailable
|
||||||
m_ImageAvailable.resize(maxFramesInFlight);
|
m_ImageAvailable.resize(maxFramesInFlight);
|
||||||
@@ -48,7 +48,7 @@ namespace Engine {
|
|||||||
for (auto s : m_RenderFinished) vkDevice.destroySemaphore(s);
|
for (auto s : m_RenderFinished) vkDevice.destroySemaphore(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncManager::WaitFence(u32 frameIndex)
|
void SyncManager::WaitFence(const u32 frameIndex) const
|
||||||
{
|
{
|
||||||
auto result = m_Device.GetDevice().waitForFences(
|
auto result = m_Device.GetDevice().waitForFences(
|
||||||
m_FrameFences[frameIndex], VK_TRUE, std::numeric_limits<u64>::max());
|
m_FrameFences[frameIndex], VK_TRUE, std::numeric_limits<u64>::max());
|
||||||
@@ -57,7 +57,7 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncManager::ResetFence(u32 frameIndex)
|
void SyncManager::ResetFence(const u32 frameIndex) const
|
||||||
{
|
{
|
||||||
m_Device.GetDevice().resetFences(m_FrameFences[frameIndex]);
|
m_Device.GetDevice().resetFences(m_FrameFences[frameIndex]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ namespace Engine
|
|||||||
// DDS → Vulkan 格式映射。sRGB 语义由贴图槽位决定(渲染侧职责):
|
// DDS → Vulkan 格式映射。sRGB 语义由贴图槽位决定(渲染侧职责):
|
||||||
// 颜色图(BaseColor/Emissive)→ SRGB 变体
|
// 颜色图(BaseColor/Emissive)→ SRGB 变体
|
||||||
// 数据图(Normal/Specular 等线性槽位)→ UNORM
|
// 数据图(Normal/Specular 等线性槽位)→ UNORM
|
||||||
vk::Format ToVkFormat(Assets::Dds::DdsFormat f, bool srgb)
|
vk::Format ToVkFormat(const Assets::Dds::DdsFormat f, const bool srgb)
|
||||||
{
|
{
|
||||||
using DF = Assets::Dds::DdsFormat;
|
using DF = Assets::Dds::DdsFormat;
|
||||||
switch (f) {
|
switch (f) {
|
||||||
@@ -279,9 +279,10 @@ namespace Engine
|
|||||||
std::vector<vk::DeviceSize> offsets(m_MipLevels);
|
std::vector<vk::DeviceSize> offsets(m_MipLevels);
|
||||||
{
|
{
|
||||||
vk::DeviceSize off = 0;
|
vk::DeviceSize off = 0;
|
||||||
|
auto* dst = static_cast<u8*>(stagingAlloc.MappedPtr);
|
||||||
for (u32 mip = 0; mip < m_MipLevels; ++mip) {
|
for (u32 mip = 0; mip < m_MipLevels; ++mip) {
|
||||||
offsets[mip] = off;
|
offsets[mip] = off;
|
||||||
std::memcpy(stagingAlloc.MappedPtr + off,
|
std::memcpy(dst + off,
|
||||||
data.Mips[mip].data(), data.Mips[mip].size());
|
data.Mips[mip].data(), data.Mips[mip].size());
|
||||||
off += data.Mips[mip].size();
|
off += data.Mips[mip].size();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Engine {
|
|||||||
ENGINE_INFO("UniformBuffer created: {} bytes x {} frames", size, frameCount);
|
ENGINE_INFO("UniformBuffer created: {} bytes x {} frames", size, frameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBuffer::Update(u32 frameSlot, const void* data, u64 size)
|
void UniformBuffer::Update(const u32 frameSlot, const void* data, const u64 size) const
|
||||||
{
|
{
|
||||||
m_Buffers[frameSlot]->Upload(data, size);
|
m_Buffers[frameSlot]->Upload(data, size);
|
||||||
}
|
}
|
||||||
|
|||||||
+107
-21
@@ -1,10 +1,13 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
const int MAX_LIGHTS = 4;
|
const int MAX_LIGHTS = 4;
|
||||||
|
const float PI = 3.14159265359;
|
||||||
|
|
||||||
struct Light {
|
struct Light {
|
||||||
vec4 direction;
|
vec4 color; // rgb = 颜色,a = 强度
|
||||||
vec4 color;
|
vec4 direction; // 方向光:xyz = 方向(已归一)
|
||||||
|
vec4 position; // 点光:xyz = 世界位置
|
||||||
|
vec4 params; // x = 类型(0=方向,1=点),y = 衰减半径
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location = 0) in vec2 fragUV;
|
layout(location = 0) in vec2 fragUV;
|
||||||
@@ -33,18 +36,24 @@ layout(set = 0, binding = 0) uniform UniformBufferObject {
|
|||||||
// 材质纹理槽(binding = Material::Slot)
|
// 材质纹理槽(binding = Material::Slot)
|
||||||
layout(set = 1, binding = 0) uniform sampler2D albedoMap;
|
layout(set = 1, binding = 0) uniform sampler2D albedoMap;
|
||||||
layout(set = 1, binding = 1) uniform sampler2D normalMap;
|
layout(set = 1, binding = 1) uniform sampler2D normalMap;
|
||||||
layout(set = 1, binding = 2) uniform sampler2D specularMap;
|
layout(set = 1, binding = 2) uniform sampler2D metallicRoughnessMap; // G=粗糙度 B=金属度
|
||||||
layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
|
layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
|
||||||
|
layout(set = 1, binding = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道)
|
||||||
|
layout(set = 1, binding = 5) uniform MaterialParams {
|
||||||
|
vec4 factors; // x = metallic 因子,y = roughness 因子
|
||||||
|
} matParams;
|
||||||
|
|
||||||
layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近)
|
layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近)
|
||||||
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
|
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
|
||||||
layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远)
|
layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远)
|
||||||
|
layout(set = 2, binding = 3) uniform samplerCube irradianceMap; // 漫反射环境光(IBL 阶段②)
|
||||||
|
layout(set = 2, binding = 4) uniform samplerCube prefilteredMap; // 镜面环境(mip 分 roughness,阶段③)
|
||||||
|
layout(set = 2, binding = 5) uniform sampler2D brdfLut; // BRDF 积分表 (NdotV, roughness) → (scale, bias)
|
||||||
|
|
||||||
// 第 0 盏灯(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
|
// 第 0 盏方向光(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
|
||||||
// 级联按视图深度选择;采样器均 clamp-to-border 白色:
|
// 级联按视图深度选择;采样器均 clamp-to-border 白色:
|
||||||
// 光视锥外自动 1.0(无阴影),省出界分支。
|
// 光视锥外自动 1.0(无阴影),省出界分支。
|
||||||
float ShadowFactor(vec3 N, float viewDepth) {
|
float ShadowFactor(vec3 N, float viewDepth) {
|
||||||
// 级联选择:第一个 viewDepth < split 的级联
|
|
||||||
int cascade = 2;
|
int cascade = 2;
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < 3; ++i) {
|
||||||
if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; }
|
if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; }
|
||||||
@@ -52,7 +61,6 @@ float ShadowFactor(vec3 N, float viewDepth) {
|
|||||||
|
|
||||||
vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0);
|
vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0);
|
||||||
vec3 proj = ls.xyz / ls.w;
|
vec3 proj = ls.xyz / ls.w;
|
||||||
// NDC [-1,1] → UV [0,1](ortho 的 w=1)
|
|
||||||
vec2 uv = proj.xy * 0.5 + 0.5;
|
vec2 uv = proj.xy * 0.5 + 0.5;
|
||||||
|
|
||||||
float closest;
|
float closest;
|
||||||
@@ -60,7 +68,6 @@ float ShadowFactor(vec3 N, float viewDepth) {
|
|||||||
else if (cascade == 1) closest = texture(shadowMap1, uv).r;
|
else if (cascade == 1) closest = texture(shadowMap1, uv).r;
|
||||||
else closest = texture(shadowMap2, uv).r;
|
else closest = texture(shadowMap2, uv).r;
|
||||||
|
|
||||||
// 斜率缩放 bias:掠射面用更大的 bias 压 acne
|
|
||||||
vec3 L = normalize(-ubo.lights[0].direction.xyz);
|
vec3 L = normalize(-ubo.lights[0].direction.xyz);
|
||||||
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012);
|
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012);
|
||||||
|
|
||||||
@@ -81,31 +88,110 @@ vec3 MappedNormal(vec3 geomN, vec3 T, vec2 uv) {
|
|||||||
return normalize(nT * sampled.x + B * sampled.y + N * sampled.z);
|
return normalize(nT * sampled.x + B * sampled.y + N * sampled.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Cook-Torrance BRDF 各项 ----
|
||||||
|
float DistributionGGX(float NdotH, float roughness) {
|
||||||
|
float a = roughness * roughness;
|
||||||
|
float a2 = a * a;
|
||||||
|
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
||||||
|
return a2 / (PI * d * d);
|
||||||
|
}
|
||||||
|
|
||||||
|
float GeometrySmith(float NdotV, float NdotL, float roughness) {
|
||||||
|
float r = roughness + 1.0;
|
||||||
|
float k = (r * r) / 8.0; // 解析光源用此 k(IBL 才用 IBL 版本)
|
||||||
|
float gv = NdotV / (NdotV * (1.0 - k) + k);
|
||||||
|
float gl = NdotL / (NdotL * (1.0 - k) + k);
|
||||||
|
return gv * gl;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 FresnelSchlick(float cosTheta, vec3 F0) {
|
||||||
|
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 粗糙度感知的 Fresnel(IBL 用):粗糙表面掠射边缘的 F 上升更平缓
|
||||||
|
vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
|
||||||
|
vec3 Fmax = max(vec3(1.0 - roughness), F0);
|
||||||
|
return F0 + (Fmax - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 albedo = texture(albedoMap, fragUV).rgb;
|
vec3 albedo = texture(albedoMap, fragUV).rgb;
|
||||||
float specIntensity = texture(specularMap, fragUV).r;
|
vec4 mr = texture(metallicRoughnessMap, fragUV);
|
||||||
|
float roughness = clamp(mr.g * matParams.factors.y, 0.045, 1.0);
|
||||||
|
float metallic = clamp(mr.b * matParams.factors.x, 0.0, 1.0);
|
||||||
vec3 emissive = texture(emissiveMap, fragUV).rgb;
|
vec3 emissive = texture(emissiveMap, fragUV).rgb;
|
||||||
|
|
||||||
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV);
|
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV);
|
||||||
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
|
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
|
||||||
|
float NdotV = max(dot(N, V), 1e-4);
|
||||||
|
vec3 F0 = mix(vec3(0.04), albedo, metallic); // 电介质基准反射率 4%
|
||||||
|
|
||||||
vec3 result = ubo.ambientColor.rgb * albedo;
|
vec3 Lo = vec3(0.0);
|
||||||
|
|
||||||
for (int i = 0; i < MAX_LIGHTS; ++i) {
|
for (int i = 0; i < MAX_LIGHTS; ++i) {
|
||||||
vec3 L = normalize(-ubo.lights[i].direction.xyz);
|
// 按类型分支:方向光无衰减;点光方向指向光源 + 平方距离衰减
|
||||||
float diff = max(dot(N, L), 0.0);
|
vec3 L;
|
||||||
// Blinn-Phong 高光(半程向量;specular 图做强度系数)
|
float atten = 1.0;
|
||||||
vec3 H = normalize(L + V);
|
if (ubo.lights[i].params.x > 0.5) {
|
||||||
float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity;
|
vec3 toLight = ubo.lights[i].position.xyz - fragWorldPos;
|
||||||
|
float d = length(toLight);
|
||||||
float shade = (i == 0) ? ShadowFactor(N, fragViewDepth) : 1.0;
|
if (d >= ubo.lights[i].params.y) continue;
|
||||||
result += ubo.lights[i].color.rgb * ubo.lights[i].color.a *
|
L = toLight / d;
|
||||||
(albedo * diff + vec3(spec)) * shade;
|
float a = clamp(1.0 - (d * d) /
|
||||||
|
(ubo.lights[i].params.y * ubo.lights[i].params.y),
|
||||||
|
0.0, 1.0);
|
||||||
|
atten = a * a;
|
||||||
|
} else {
|
||||||
|
L = normalize(-ubo.lights[i].direction.xyz);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自发光不经光照/阴影直接叠加
|
vec3 radiance = ubo.lights[i].color.rgb * ubo.lights[i].color.a * atten;
|
||||||
result += emissive;
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
|
if (NdotL <= 0.0) continue;
|
||||||
|
|
||||||
outColor = vec4(result, 1.0);
|
// Cook-Torrance 镜面项
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
float NdotH = max(dot(N, H), 0.0);
|
||||||
|
float NdotH2 = max(dot(H, V), 0.0);
|
||||||
|
float D = DistributionGGX(NdotH, roughness);
|
||||||
|
float G = GeometrySmith(NdotV, NdotL, roughness);
|
||||||
|
vec3 F = FresnelSchlick(NdotH2, F0);
|
||||||
|
|
||||||
|
vec3 kS = F;
|
||||||
|
vec3 kD = (1.0 - kS) * (1.0 - metallic); // 金属不参与漫反射
|
||||||
|
vec3 specular = (D * G * F) /
|
||||||
|
max(4.0 * NdotV * NdotL, 1e-4);
|
||||||
|
|
||||||
|
// 阴影只来自第 0 盏方向光(CSM)
|
||||||
|
float shade = (i == 0 && ubo.lights[0].params.x < 0.5)
|
||||||
|
? ShadowFactor(N, fragViewDepth) : 1.0;
|
||||||
|
|
||||||
|
Lo += (kD * albedo / PI + specular) * radiance * NdotL * shade;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 环境光:IBL split-sum(阶段②③)。
|
||||||
|
// 漫反射:N 采样 irradiance cube(半球 Lambert 预卷积)
|
||||||
|
// 镜面:R 采样 pre-filtered cube(mip = roughness 分级)
|
||||||
|
// × BRDF LUT 查表得 F 缩放/偏置
|
||||||
|
// ambientColor = 用户可调强度/色调乘子
|
||||||
|
vec3 F_amb = FresnelSchlickRoughness(NdotV, F0, roughness);
|
||||||
|
vec3 kD_amb = (1.0 - F_amb) * (1.0 - metallic);
|
||||||
|
|
||||||
|
vec3 irradiance = texture(irradianceMap, N).rgb;
|
||||||
|
float ao = texture(aoMap, fragUV).r;
|
||||||
|
vec3 diffuse = kD_amb * irradiance * albedo * ao;
|
||||||
|
|
||||||
|
vec3 R = reflect(-V, N);
|
||||||
|
const float kMaxPrefilterMip = 4.0; // 与 Cubemap::kPrefilterMips - 1 同步
|
||||||
|
vec3 prefilteredColor = textureLod(prefilteredMap, R,
|
||||||
|
roughness * kMaxPrefilterMip).rgb;
|
||||||
|
vec2 envBRDF = texture(brdfLut, vec2(NdotV, roughness)).rg;
|
||||||
|
vec3 specular = prefilteredColor * (F_amb * envBRDF.x + envBRDF.y);
|
||||||
|
|
||||||
|
vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb;
|
||||||
|
|
||||||
|
vec3 color = ambient + Lo + emissive;
|
||||||
|
|
||||||
|
outColor = vec4(color, 1.0);
|
||||||
outVelocity = fragVelocity;
|
outVelocity = fragVelocity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
const int MAX_LIGHTS = 4;
|
const int MAX_LIGHTS = 4;
|
||||||
|
|
||||||
struct Light {
|
struct Light {
|
||||||
vec4 direction;
|
vec4 color; // rgb = 颜色,a = 强度
|
||||||
vec4 color;
|
vec4 direction; // 方向光:xyz = 方向(已归一)
|
||||||
|
vec4 position; // 点光:xyz = 世界位置
|
||||||
|
vec4 params; // x = 类型(0=方向,1=点),y = 衰减半径
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location = 0) in vec3 inPosition;
|
layout(location = 0) in vec3 inPosition;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// 天空盒像素:NDC → 世界方向 → cube 采样。
|
||||||
|
// invVP = inverse(projNoJitter × mat4(mat3(view))):去掉平移的视图,
|
||||||
|
// 反投影结果即"相机相对"的世界方向,normalize 后直接采样 cube。
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 fragUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform samplerCube envMap;
|
||||||
|
|
||||||
|
// push 与 skybox.vert 一致声明(VUID-08878)
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
mat4 invVP;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 target = pc.invVP * vec4(fragUV * 2.0 - 1.0, 1.0, 1.0);
|
||||||
|
vec3 dir = normalize(target.xyz / target.w);
|
||||||
|
outColor = vec4(texture(envMap, dir).rgb, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// 天空盒顶点:全屏三角形(无顶点缓冲),UV 交给 fragment 反投影。
|
||||||
|
// 方向在 fragment 逐像素计算(invVP 是投影变换,对 NDC 线性插值方向
|
||||||
|
// 不精确;UV 插值是仿射的,逐像素重建方向零误差)。
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 fragUV;
|
||||||
|
|
||||||
|
// push 与 skybox.frag 一致声明(VUID-08878:shader object 接口一致)
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
mat4 invVP;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 p = vec2(
|
||||||
|
float((gl_VertexIndex << 1) & 2),
|
||||||
|
float(gl_VertexIndex & 2));
|
||||||
|
fragUV = p;
|
||||||
|
// 远平面:z = 1(Vulkan [0,1] 深度),无深度附件,只关心覆盖屏幕
|
||||||
|
gl_Position = vec4(p * 2.0 - 1.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user