feat: 解析式 PBR(Cook-Torrance GGX,metallic-roughness 工作流)

- 修 void* 指针算术警告(Texture::InitFromDds)
- Material:slot2 语义切为 metallicRoughness(glTF 约定 G=粗糙度
  B=金属度)+ 新增材质因子参数 UBO(binding 4,per-slot ring,
  创建时写死,metallic/roughness 因子乘 MR 贴图)
- simple_shader.frag:Blinn-Phong → Cook-Torrance(GGX 分布 +
  Smith 几何 + Schlick Fresnel,k=(r+1)²/8 解析光源版);金属不参与
  漫反射;环境光暂为常数(IBL 后续)
- Model:读 aiTextureType_UNKNOWN(glTF MR 落点)+ AI_MATKEY_
  METALLIC/ROUGHNESS_FACTOR(旧资产缺键退化为电介质 0/0.5);
  材质缓存键含因子
- main.cpp:演示场景换回 backpack(旧资产走因子路径验证 PBR 全链路)
- 旧 specular 贴图不再使用(语义被 MR 取代)
This commit is contained in:
2026-09-23 18:01:14 +08:00
parent 2c4efc5165
commit 89bf4256e8
23 changed files with 449 additions and 371 deletions
+2 -2
View File
@@ -44,7 +44,7 @@ namespace Engine {
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);
u32 GetWidth() const { return m_Width; }
@@ -63,7 +63,7 @@ namespace Engine {
// 输入事件系统
bool IsKeyPressed(int key) const;
void GetMouseDelta(float& dx, float& dy);
void GetMouseDelta(float& dx, float& dy) const;
+30 -16
View File
@@ -1,46 +1,58 @@
//
// Created by atdunbg on 2026/9/21.
//
// Material:着色材质 = 一组纹理槽 + 一个描述符集。
// Material:PBR 材质 = 纹理槽组 + 因子参数 UBO + 一个描述符集。
//
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致):
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
// 2 specular(高光强度/颜色,线性) 3 emissive(自发光,sRGB)
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致,glTF
// metallic-roughness 工作流):
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
// 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定)
// 3 emissive(自发光,sRGB) 4 材质参数 UBO(fragment)
//
// 纹理非拥有(Model 的纹理缓存持有);空槽由调用方传默认纹理
//(白/平法线/黑/黑,Model 持有),shader 永远可以安全采样。
// 纹理非拥有(Model 的纹理缓存持有);缺槽由调用方传默认纹理。
// 因子(metallicFactor/roughnessFactor)乘在 MR 贴图上——无 MR 图的
// 旧资产退化为纯因子(电介质 + 常数粗糙度)。
//
#ifndef HELLOVULKAN_MATERIAL_H
#define HELLOVULKAN_MATERIAL_H
#include <Engine/Core/Types.h>
#include <glm/glm.hpp>
#include <vulkan/vulkan.hpp>
#include <array>
#include <string>
namespace Engine
{
namespace Engine {
class Device;
class MemoryAllocator;
class DescriptorManager;
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:
// 槽位序号(与 binding 对齐)
enum Slot : u32 {
Albedo = 0,
Normal = 1,
Specular = 2,
MetallicRoughness = 2,
Emissive = 3,
kSlotCount = 4,
kSlotCount = 4, // 纹理槽数
kParamsBinding = 4, // 参数 UBO binding
};
Material(Device& device,
Material(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptorManager,
vk::DescriptorSetLayout layout,
const std::array<Texture*, kSlotCount>& textures);
const std::array<Texture*, kSlotCount>& textures,
const MaterialParams& params);
~Material();
Material(const Material&) = delete;
@@ -49,12 +61,14 @@ namespace Engine
vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; }
Texture* GetTexture(Slot slot) const { return m_Textures[slot]; }
// 材质缓存键:同贴图组合共享一份(Model 层使用)
static std::string MakeCacheKey(const std::string paths[kSlotCount]);
// 材质缓存键:同贴图组合+同因子共享一份(Model 层使用)
static std::string MakeCacheKey(const std::string paths[kSlotCount],
const MaterialParams& params);
private:
std::array<Texture*, kSlotCount> m_Textures{};
vk::DescriptorSet m_DescriptorSet;
Scope<UniformBuffer> m_ParamsBuffer; // per-slot 参数 ring(创建时写死)
};
} // namespace Engine
+4 -3
View File
@@ -64,10 +64,11 @@ namespace Engine
Texture* GetOrLoadTexture(const std::string& path, bool srgb);
// 材质缓存入口:同贴图组合共享同一 Material(一个描述符集),
// 材质缓存入口:同贴图组合+因子共享同一 Material(一个描述符集),
// 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。
// paths 下标 = Material::Slot(albedo/normal/specular/emissive)
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount]);
// paths 下标 = Material::Slot;params = metallic/roughness 因子
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount],
const MaterialParams& params);
Device& m_Device;
MemoryAllocator& m_Allocator;
+1 -1
View File
@@ -49,7 +49,7 @@ namespace Engine {
class Renderer {
public:
Renderer(Window& window);
explicit Renderer(Window& window);
~Renderer();
Renderer(const Renderer&) = delete;
@@ -28,10 +28,10 @@ namespace Engine {
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]; }
@@ -28,21 +28,21 @@ namespace Engine {
DescriptorManager(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);
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,
vk::Buffer buffer, u64 size);
vk::Buffer buffer, u64 size) const;
void UpdateTexture(vk::DescriptorSet set, u32 binding,
vk::Sampler sampler, vk::ImageView view);
vk::Sampler sampler, vk::ImageView view) const;
// 单个 set layout 的旧接口(保留)
vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout,
u32 pushConstantSize);
u32 pushConstantSize) const;
// 多 set layout 的新接口。
// push constant range 按程序级 vertex|fragment 声明(与
@@ -50,7 +50,7 @@ namespace Engine {
// push 调用点必须带同样的双 stage(VUID-01796)
vk::PipelineLayout CreatePipelineLayout(
const std::vector<vk::DescriptorSetLayout>& layouts,
u32 pushConstantSize);
u32 pushConstantSize) const;
private:
Device& m_Device;
@@ -64,7 +64,7 @@ namespace Engine {
// 每块的容量(set 数;各类型描述符数同值)
static constexpr u32 kChunkSets = 4096;
vk::DescriptorPool CreateChunk();
vk::DescriptorPool CreateChunk() const;
};
} // namespace Engine
+3 -3
View File
@@ -39,12 +39,12 @@ namespace Engine {
ShaderObject CreateFromSPIRV(const std::vector<u32>& spirv,
vk::ShaderStageFlagBits stage,
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:
std::vector<u32> ReadSPIRV(const std::string& path);
static std::vector<u32> ReadSPIRV(const std::string& path);
Device& m_Device;
};
+2 -2
View File
@@ -35,8 +35,8 @@ namespace Engine {
vk::Fence GetFrameFence(u32 frameIndex) const { return m_FrameFences[frameIndex]; }
// 等待并重置这一帧的 Fence
void WaitFence(u32 frameIndex); // 只等,不 reset
void ResetFence(u32 frameIndex); // 只 reset
void WaitFence(u32 frameIndex) const; // 只等,不 reset
void ResetFence(u32 frameIndex) const; // 只 reset
private:
Device& m_Device;
+1 -1
View File
@@ -39,7 +39,7 @@ namespace Engine {
u64 GetSize() const { return m_Size; }
// 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用)
void Update(u32 frameSlot, const void* data, u64 size);
void Update(u32 frameSlot, const void* data, u64 size) const;
private:
std::vector<Scope<Buffer>> m_Buffers;