diff --git a/Engine/include/Engine/Renderer/Material.h b/Engine/include/Engine/Renderer/Material.h index d86e5d7..c394083 100644 --- a/Engine/include/Engine/Renderer/Material.h +++ b/Engine/include/Engine/Renderer/Material.h @@ -1,12 +1,22 @@ // // Created by atdunbg on 2026/9/21. // +// Material:着色材质 = 一组纹理槽 + 一个描述符集。 +// +// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致): +// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性) +// 2 specular(高光强度/颜色,线性) 3 emissive(自发光,sRGB) +// +// 纹理非拥有(Model 的纹理缓存持有);空槽由调用方传默认纹理 +//(白/平法线/黑/黑,Model 持有),shader 永远可以安全采样。 +// #ifndef HELLOVULKAN_MATERIAL_H #define HELLOVULKAN_MATERIAL_H #include #include +#include #include namespace Engine @@ -18,20 +28,32 @@ namespace Engine class Material { public: + // 槽位序号(与 binding 对齐) + enum Slot : u32 { + Albedo = 0, + Normal = 1, + Specular = 2, + Emissive = 3, + kSlotCount = 4, + }; + Material(Device& device, DescriptorManager& descriptorManager, vk::DescriptorSetLayout layout, - Texture* texture); + const std::array& textures); ~Material(); Material(const Material&) = delete; Material& operator=(const Material&) = delete; vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; } - Texture* GetTexture() const { return m_Texture; } + Texture* GetTexture(Slot slot) const { return m_Textures[slot]; } + + // 材质缓存键:同贴图组合共享一份(Model 层使用) + static std::string MakeCacheKey(const std::string paths[kSlotCount]); private: - Texture* m_Texture = nullptr; + std::array m_Textures{}; vk::DescriptorSet m_DescriptorSet; }; } // namespace Engine diff --git a/Engine/include/Engine/Renderer/Model.h b/Engine/include/Engine/Renderer/Model.h index da8717f..10c12b4 100644 --- a/Engine/include/Engine/Renderer/Model.h +++ b/Engine/include/Engine/Renderer/Model.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -59,12 +60,14 @@ namespace Engine void LoadModel(const std::string& path); void ProcessNode(aiNode* node, const aiScene* scene); Scope ProcessMesh(aiMesh* mesh, const aiScene* scene); + void InitDefaultTextures(); - Texture* GetOrLoadTexture(const std::string& path); + Texture* GetOrLoadTexture(const std::string& path, bool srgb); - // 材质缓存入口:同贴图路径共享同一 Material(一个描述符集), - // 空路径/缺文件折到兜底材质。路径已做反斜杠归一化。 - Material* GetOrCreateMaterial(const std::string& path); + // 材质缓存入口:同贴图组合共享同一 Material(一个描述符集), + // 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。 + // paths 下标 = Material::Slot(albedo/normal/specular/emissive) + Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount]); Device& m_Device; MemoryAllocator& m_Allocator; @@ -75,7 +78,8 @@ namespace Engine // 析构序 = meshes 先于材质/纹理缓存 std::unordered_map> m_MaterialCache; std::unordered_map> m_TextureCache; - Scope m_DefaultTexture; + // 各槽位默认纹理(材质缺图时兜底):白 / 平法线 / 黑高光 / 黑自发光 + std::array, Material::kSlotCount> m_DefaultTextures; std::vector> m_Meshes; std::string m_ModelPath; diff --git a/Engine/include/Engine/Renderer/SceneTypes.h b/Engine/include/Engine/Renderer/SceneTypes.h index 0dbee91..d218132 100644 --- a/Engine/include/Engine/Renderer/SceneTypes.h +++ b/Engine/include/Engine/Renderer/SceneTypes.h @@ -57,6 +57,7 @@ namespace Engine { glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用) glm::mat4 prevView; // 上一帧视图(velocity) glm::mat4 prevProj; // 上一帧未抖动投影(velocity) + glm::vec4 cameraPos; // 相机世界位置(高光 V 向量) }; // 每帧从场景收集的渲染数据(SceneRenderer 生产,pass 消费)。 diff --git a/Engine/include/Engine/Renderer/Vertex.h b/Engine/include/Engine/Renderer/Vertex.h index b7b1cc0..03ed1fd 100644 --- a/Engine/include/Engine/Renderer/Vertex.h +++ b/Engine/include/Engine/Renderer/Vertex.h @@ -14,6 +14,7 @@ namespace Engine { glm::vec3 position; glm::vec3 normal; glm::vec2 uv; + glm::vec3 tangent; // 切线(法线贴图 TBN 用;assimp CalcTangentSpace) static vk::VertexInputBindingDescription2EXT GetBindingDescription() { vk::VertexInputBindingDescription2EXT binding; @@ -24,8 +25,8 @@ namespace Engine { return binding; } - static std::array GetAttributeDescriptions() { - std::array attrs; + static std::array GetAttributeDescriptions() { + std::array attrs; attrs[0].setLocation(0) .setBinding(0) @@ -42,6 +43,11 @@ namespace Engine { .setFormat(vk::Format::eR32G32Sfloat) .setOffset(offsetof(Vertex, uv)); + attrs[3].setLocation(3) + .setBinding(0) + .setFormat(vk::Format::eR32G32B32Sfloat) + .setOffset(offsetof(Vertex, tangent)); + return attrs; } }; diff --git a/Engine/include/Engine/Vulkan/Texture.h b/Engine/include/Engine/Vulkan/Texture.h index db928cd..ca9326f 100644 --- a/Engine/include/Engine/Vulkan/Texture.h +++ b/Engine/include/Engine/Vulkan/Texture.h @@ -21,13 +21,16 @@ namespace Engine { class Texture { public: - // 从磁盘图片加载(按扩展名分发:.dds → DdsLoader,其它 → stbi) - Texture(Device& device, MemoryAllocator& allocator, const std::string& path); + // 从磁盘图片加载(按扩展名分发:.dds → DdsLoader,其它 → stbi)。 + // srgb:颜色贴图语义(BaseColor/Emissive)为 true;数据图 + //(Normal/Specular 等)为 false —— 决定 vk 格式的 _SRGB 变体 + Texture(Device& device, MemoryAllocator& allocator, const std::string& path, + bool srgb = true); // 程序化纹理:纯色(1x1),用于地面/调试等不依赖磁盘资产的场景 struct ProceduralTag {}; static Scope CreateSolidColor(Device& device, MemoryAllocator& allocator, - u8 r, u8 g, u8 b); + u8 r, u8 g, u8 b, bool srgb = true); ~Texture(); @@ -46,13 +49,13 @@ namespace Engine { private: // 两段式构造:先确定像素来源(磁盘 / 程序化),再走公共上传路径 Texture(Device& device, MemoryAllocator& allocator, ProceduralTag, - const std::array& rgba); + const std::array& rgba, bool srgb); // 公共路径:staging → device local image → view/sampler(RGBA8 单 mip) - void InitFromPixels(const void* pixels, u32 width, u32 height); + void InitFromPixels(const void* pixels, u32 width, u32 height, bool srgb); // DDS 路径:压缩数据直传(BC 硬件采样解码)+ 文件自带 mip 链 - void InitFromDds(const std::string& path); + void InitFromDds(const std::string& path, bool srgb); Device& m_Device; vk::Image m_Image; diff --git a/Engine/src/Renderer/Material.cpp b/Engine/src/Renderer/Material.cpp index a5dae6c..2646584 100644 --- a/Engine/src/Renderer/Material.cpp +++ b/Engine/src/Renderer/Material.cpp @@ -12,19 +12,31 @@ namespace Engine { Material::Material(Device& device, - DescriptorManager& descriptorManager, - vk::DescriptorSetLayout layout, - Texture* texture) - : m_Texture(texture) + DescriptorManager& descriptorManager, + vk::DescriptorSetLayout layout, + const std::array& textures) + : m_Textures(textures) { m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout); - descriptorManager.UpdateTexture( - m_DescriptorSet, 0, - m_Texture->GetSampler(), - m_Texture->GetView()); + for (u32 slot = 0; slot < kSlotCount; ++slot) { + descriptorManager.UpdateTexture( + m_DescriptorSet, slot, + m_Textures[slot]->GetSampler(), + m_Textures[slot]->GetView()); + } } Material::~Material() = default; + + std::string Material::MakeCacheKey(const std::string paths[kSlotCount]) + { + std::string key; + for (u32 slot = 0; slot < kSlotCount; ++slot) { + if (slot) key += '\n'; + key += paths[slot]; + } + return key; + } } // namespace Engine diff --git a/Engine/src/Renderer/Model.cpp b/Engine/src/Renderer/Model.cpp index b01625f..ff54636 100644 --- a/Engine/src/Renderer/Model.cpp +++ b/Engine/src/Renderer/Model.cpp @@ -28,15 +28,27 @@ Model::Model(Device& device, MemoryAllocator& allocator, , m_DescriptorManager(descriptorManager) , m_TextureLayout(textureLayout) { - // 兜底纹理 - m_DefaultTexture = CreateScope( - m_Device, m_Allocator, "assets/textures/default.tga"); + InitDefaultTextures(); LoadModel(path); } Model::~Model() = default; +void Model::InitDefaultTextures() +{ + // 各材质槽位的兜底纹理(1x1,开销可忽略): + // albedo 白(受光) / normal 平法线 (0.5,0.5,1) / specular 黑 / emissive 黑 + m_DefaultTextures[Material::Albedo] = + Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255); + m_DefaultTextures[Material::Normal] = + 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] = + Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0); +} + Model::Model(Device& device, MemoryAllocator& allocator, DescriptorManager& descriptorManager, vk::DescriptorSetLayout textureLayout, @@ -46,7 +58,8 @@ Model::Model(Device& device, MemoryAllocator& allocator, , m_DescriptorManager(descriptorManager) , m_TextureLayout(textureLayout) { - // 纯色 1x1 贴图 + // 纯色 1x1 贴图(albedo 兜底) + InitDefaultTextures(); auto tex = Texture::CreateSolidColor(device, allocator, color[0], color[1], color[2]); // XZ 平面,法线 +Y,从上往下看 CCW 绕序 @@ -59,9 +72,9 @@ Model::Model(Device& device, MemoryAllocator& allocator, }; const std::vector indices = {0, 1, 2, 0, 2, 3}; - // 贴图/材质生命周期随模型(缓存持有) - m_DefaultTexture = std::move(tex); - Material* mat = GetOrCreateMaterial(""); // 兜底材质 + // 材质/纹理生命周期随模型(缓存持有) + std::string paths[Material::kSlotCount] = {"", "", "", ""}; + Material* mat = GetOrCreateMaterial(paths); // 全默认材质 m_Meshes.push_back(CreateScope( m_Device, m_Allocator, vertices, indices, mat)); @@ -86,6 +99,7 @@ void Model::LoadModel(const std::string& path) aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenSmoothNormals | + aiProcess_CalcTangentSpace | // 法线贴图 TBN aiProcess_JoinIdenticalVertices); if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !scene->mRootNode) { @@ -109,13 +123,15 @@ void Model::ProcessNode(aiNode* node, const aiScene* scene) } } -Texture* Model::GetOrLoadTexture(const std::string& rawPath) +Texture* Model::GetOrLoadTexture(const std::string& rawPath, bool srgb) { // FBX/DCC 导出的路径常带反斜杠(Windows 语义),统一成正斜杠 std::string path = rawPath; std::replace(path.begin(), path.end(), '\\', '/'); - auto it = m_TextureCache.find(path); + // 缓存键带 sRGB 语义:同一路径可能同时被颜色槽与数据槽引用 + std::string key = (srgb ? "s:" : "l:") + path; + auto it = m_TextureCache.find(key); if (it != m_TextureCache.end()) { return it->second.get(); } @@ -123,40 +139,39 @@ Texture* Model::GetOrLoadTexture(const std::string& rawPath) // 文件不存在则用兜底纹理 if (!std::filesystem::exists(path)) { ENGINE_WARN("Texture not found, using default: {}", path); - return m_DefaultTexture.get(); + return nullptr; } - auto tex = CreateScope(m_Device, m_Allocator, path); + auto tex = CreateScope(m_Device, m_Allocator, path, srgb); Texture* ptr = tex.get(); - m_TextureCache.emplace(path, std::move(tex)); + m_TextureCache.emplace(key, std::move(tex)); return ptr; } -Material* Model::GetOrCreateMaterial(const std::string& rawPath) +Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount]) { - // 路径归一化 + 空路径/缺文件统一折到兜底材质(缓存键 "") - std::string path = rawPath; - std::replace(path.begin(), path.end(), '\\', '/'); - - if (path.empty() || !std::filesystem::exists(path)) { - if (!rawPath.empty()) { - ENGINE_WARN("Texture not found, using default: {}", path); - } - path.clear(); - } - - if (auto it = m_MaterialCache.find(path); it != m_MaterialCache.end()) { + const std::string key = Material::MakeCacheKey(paths); + if (auto it = m_MaterialCache.find(key); it != m_MaterialCache.end()) { return it->second.get(); } - Texture* tex = path.empty() - ? m_DefaultTexture.get() - : GetOrLoadTexture(path); + // 每槽位解析纹理:空/缺失折到该槽位默认纹理 + std::array textures{}; + // 槽位 sRGB 语义:颜色图 true,数据图 false + const bool slotSrgb[Material::kSlotCount] = {true, false, false, true}; + + for (u32 slot = 0; slot < Material::kSlotCount; ++slot) { + Texture* tex = nullptr; + if (!paths[slot].empty()) { + tex = GetOrLoadTexture(paths[slot], slotSrgb[slot]); + } + textures[slot] = tex ? tex : m_DefaultTextures[slot].get(); + } auto mat = CreateScope( - m_Device, m_DescriptorManager, m_TextureLayout, tex); + m_Device, m_DescriptorManager, m_TextureLayout, textures); Material* ptr = mat.get(); - m_MaterialCache.emplace(path, std::move(mat)); + m_MaterialCache.emplace(key, std::move(mat)); return ptr; } @@ -191,6 +206,15 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) }; } + // 切线(法线贴图 TBN;无切线数据置零,shader 侧退回几何法线) + if (mesh->mTangents) { + vertex.tangent = { + mesh->mTangents[i].x, + mesh->mTangents[i].y, + mesh->mTangents[i].z + }; + } + vertices.push_back(vertex); } @@ -201,22 +225,32 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) } } - // 读取材质纹理路径 - std::string texturePath; + // 读取材质纹理路径(4 槽:albedo/normal/specular/emissive) + std::string texPaths[Material::kSlotCount]; if (mesh->mMaterialIndex >= 0) { aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + const std::filesystem::path modelDir = + std::filesystem::path(m_ModelPath).parent_path(); - aiString path; - if (material->GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS) { - const std::filesystem::path modelDir = - std::filesystem::path(m_ModelPath).parent_path(); - texturePath = (modelDir / path.C_Str()).string(); + const std::pair slots[] = { + {aiTextureType_DIFFUSE, Material::Albedo}, + {aiTextureType_NORMALS, Material::Normal}, + {aiTextureType_SPECULAR, Material::Specular}, + {aiTextureType_EMISSIVE, Material::Emissive}, + }; + + for (const auto& [type, slot] : slots) { + aiString path; + if (material->GetTexture(type, 0, &path) == AI_SUCCESS && + path.length > 0) { + texPaths[slot] = (modelDir / path.C_Str()).string(); + } } } - // 材质缓存:同贴图 → 同一 Material(一个描述符集),mesh 数 ≠ 集数 - Material* mat = GetOrCreateMaterial(texturePath); + // 材质缓存:同贴图组合 → 同一 Material(一个描述符集),mesh 数 ≠ 集数 + Material* mat = GetOrCreateMaterial(texPaths); return CreateScope( m_Device, m_Allocator, vertices, indices, mat); diff --git a/Engine/src/Renderer/Passes/SceneGpu.cpp b/Engine/src/Renderer/Passes/SceneGpu.cpp index 9c8873e..9923efb 100644 --- a/Engine/src/Renderer/Passes/SceneGpu.cpp +++ b/Engine/src/Renderer/Passes/SceneGpu.cpp @@ -3,6 +3,7 @@ // #include +#include #include #include #include @@ -31,10 +32,14 @@ namespace Engine::Passes }; uboSetLayout = descriptors.CreateLayout(uboBindings); - // Set 1: texture - std::vector texBindings = { - {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, - }; + // Set 1: 材质纹理槽(binding = Material::Slot:0 albedo / 1 normal / + // 2 specular / 3 emissive) + std::vector texBindings; + for (u32 slot = 0; slot < Material::kSlotCount; ++slot) { + texBindings.push_back( + {slot, vk::DescriptorType::eCombinedImageSampler, + vk::ShaderStageFlagBits::eFragment}); + } textureSetLayout = descriptors.CreateLayout(texBindings); // Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号) diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 5c75b70..22b3106 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -177,6 +177,7 @@ namespace Engine ubo.lights[i].color = glm::vec4(0.0f); } ComputeCascades(shadowDir); + ubo.cameraPos = glm::vec4(camera.GetPosition(), 1.0f); // ---- 3. GPUDriven:单趟收集 + 双视图剔除 ---- // 每个实例对相机视锥、光源视锥分别做包围球测试: diff --git a/Engine/src/Vulkan/Texture.cpp b/Engine/src/Vulkan/Texture.cpp index b4f9791..f59a3cf 100644 --- a/Engine/src/Vulkan/Texture.cpp +++ b/Engine/src/Vulkan/Texture.cpp @@ -18,7 +18,8 @@ namespace Engine { - Texture::Texture(Device& device, MemoryAllocator& allocator, const std::string& path) + Texture::Texture(Device& device, MemoryAllocator& allocator, const std::string& path, + bool srgb) : m_Device(device) , m_Allocator(allocator) { // 按扩展名分发:DDS(BC 压缩直传 + mip 链)vs stbi(无压缩单 mip) @@ -28,7 +29,7 @@ namespace Engine for (char c : ext) lower += static_cast(std::tolower(c)); if (lower == ".dds") { - InitFromDds(path); + InitFromDds(path, srgb); } else { // 1. CPU 加载图片 int width = 0, height = 0, channels = 0; @@ -40,7 +41,7 @@ namespace Engine return; } - InitFromPixels(pixels, static_cast(width), static_cast(height)); + InitFromPixels(pixels, static_cast(width), static_cast(height), srgb); stbi_image_free(pixels); @@ -49,24 +50,25 @@ namespace Engine } Texture::Texture(Device& device, MemoryAllocator& allocator, ProceduralTag, - const std::array& rgba) + const std::array& rgba, bool srgb) : m_Device(device) , m_Allocator(allocator) { const u8 pixels[4] = { rgba[0], rgba[1], rgba[2], rgba[3] }; - InitFromPixels(pixels, 1, 1); + InitFromPixels(pixels, 1, 1, srgb); } Scope Texture::CreateSolidColor(Device& device, MemoryAllocator& allocator, - u8 r, u8 g, u8 b) + u8 r, u8 g, u8 b, bool srgb) { - return Scope(new Texture(device, allocator, ProceduralTag{}, {r, g, b, 255})); + return Scope(new Texture(device, allocator, ProceduralTag{}, + {r, g, b, 255}, srgb)); } - void Texture::InitFromPixels(const void* pixels, u32 width, u32 height) + void Texture::InitFromPixels(const void* pixels, u32 width, u32 height, bool srgb) { m_Width = width; m_Height = height; - m_Format = vk::Format::eR8G8B8A8Srgb; + m_Format = srgb ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm; m_MipLevels = 1; vk::DeviceSize imageSize = static_cast(width) * static_cast(height) * 4; // RGBA8 @@ -84,7 +86,7 @@ namespace Engine // 4. 创建 Device Local Image vk::ImageCreateInfo imageInfo; imageInfo.setImageType(vk::ImageType::e2D) - .setFormat(vk::Format::eR8G8B8A8Srgb) + .setFormat(m_Format) .setExtent({m_Width, m_Height, 1}) .setMipLevels(1) .setArrayLayers(1) @@ -214,21 +216,28 @@ namespace Engine STATS.AddTexture(m_TextureBytes); } - // DDS → Vulkan 格式映射(渲染侧职责:sRGB 语义按颜色贴图默认开启; - // BC4/BC5/BC6H 是数据图,保持线性) - vk::Format ToVkFormat(Assets::Dds::DdsFormat f) + // DDS → Vulkan 格式映射。sRGB 语义由贴图槽位决定(渲染侧职责): + // 颜色图(BaseColor/Emissive)→ SRGB 变体 + // 数据图(Normal/Specular 等线性槽位)→ UNORM + vk::Format ToVkFormat(Assets::Dds::DdsFormat f, bool srgb) { using DF = Assets::Dds::DdsFormat; switch (f) { - case DF::BC1: return vk::Format::eBc1RgbaSrgbBlock; - case DF::BC2: return vk::Format::eBc2SrgbBlock; - case DF::BC3: return vk::Format::eBc3SrgbBlock; + case DF::BC1: return srgb ? vk::Format::eBc1RgbaSrgbBlock + : vk::Format::eBc1RgbaUnormBlock; + case DF::BC2: return srgb ? vk::Format::eBc2SrgbBlock + : vk::Format::eBc2UnormBlock; + case DF::BC3: return srgb ? vk::Format::eBc3SrgbBlock + : vk::Format::eBc3UnormBlock; case DF::BC4: return vk::Format::eBc4UnormBlock; case DF::BC5: return vk::Format::eBc5UnormBlock; case DF::BC6H: return vk::Format::eBc6HUfloatBlock; - case DF::BC7: return vk::Format::eBc7SrgbBlock; - case DF::RGBA8: return vk::Format::eR8G8B8A8Srgb; - case DF::BGRA8: return vk::Format::eB8G8R8A8Srgb; + case DF::BC7: return srgb ? vk::Format::eBc7SrgbBlock + : vk::Format::eBc7UnormBlock; + case DF::RGBA8: return srgb ? vk::Format::eR8G8B8A8Srgb + : vk::Format::eR8G8B8A8Unorm; + case DF::BGRA8: return srgb ? vk::Format::eB8G8R8A8Srgb + : vk::Format::eB8G8R8A8Unorm; } return vk::Format::eUndefined; } @@ -238,7 +247,7 @@ namespace Engine return std::max(1u, v >> level); } - void Texture::InitFromDds(const std::string& path) + void Texture::InitFromDds(const std::string& path, bool srgb) { Assets::Dds::DdsData data; std::string err; @@ -250,7 +259,7 @@ namespace Engine m_Width = data.Width; m_Height = data.Height; m_MipLevels = data.MipLevels; - m_Format = ToVkFormat(data.Format); + m_Format = ToVkFormat(data.Format, srgb); ENGINE_INFO("DDS loaded: {} ({}x{}, {} mips, {})", path, m_Width, m_Height, m_MipLevels, vk::to_string(m_Format)); diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 9d4208c..6f58080 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -12,6 +12,7 @@ layout(location = 1) in vec3 fragNormal; layout(location = 2) in vec3 fragWorldPos; layout(location = 3) in float fragViewDepth; layout(location = 4) in vec2 fragVelocity; +layout(location = 5) in vec3 fragTangent; layout(location = 0) out vec4 outColor; layout(location = 1) out vec2 outVelocity; // TAA velocity 附件 @@ -26,9 +27,15 @@ layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 projNoJitter; mat4 prevView; mat4 prevProj; + vec4 cameraPos; } ubo; -layout(set = 1, binding = 0) uniform sampler2D texSampler; +// 材质纹理槽(binding = Material::Slot) +layout(set = 1, binding = 0) uniform sampler2D albedoMap; +layout(set = 1, binding = 1) uniform sampler2D normalMap; +layout(set = 1, binding = 2) uniform sampler2D specularMap; +layout(set = 1, binding = 3) uniform sampler2D emissiveMap; + layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近) layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1 layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远) @@ -60,19 +67,45 @@ float ShadowFactor(vec3 N, float viewDepth) { return (proj.z - bias > closest) ? 0.3 : 1.0; } +// 法线贴图 → 世界空间法线(TBN 构建)。 +// 无切线数据(零向量)或平法线贴图时退回几何法线。 +vec3 MappedNormal(vec3 geomN, vec3 T, vec2 uv) { + if (dot(T, T) < 1e-4) return geomN; + + vec3 N = geomN; + vec3 nT = normalize(T - N * dot(N, T)); // 正交化 + vec3 B = cross(N, nT); + vec3 sampled = texture(normalMap, uv).xyz * 2.0 - 1.0; + if (dot(sampled, sampled) < 1e-4) return geomN; + + return normalize(nT * sampled.x + B * sampled.y + N * sampled.z); +} + void main() { - vec3 albedo = texture(texSampler, fragUV).rgb; - vec3 N = normalize(fragNormal); + vec3 albedo = texture(albedoMap, fragUV).rgb; + float specIntensity = texture(specularMap, fragUV).r; + vec3 emissive = texture(emissiveMap, fragUV).rgb; + + vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV); + vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos); vec3 result = ubo.ambientColor.rgb * albedo; for (int i = 0; i < MAX_LIGHTS; ++i) { vec3 L = normalize(-ubo.lights[i].direction.xyz); float diff = max(dot(N, L), 0.0); + // Blinn-Phong 高光(半程向量;specular 图做强度系数) + vec3 H = normalize(L + V); + float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity; + float shade = (i == 0) ? ShadowFactor(N, fragViewDepth) : 1.0; - result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo * shade; + result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * + (albedo * diff + vec3(spec)) * shade; } + // 自发光不经光照/阴影直接叠加 + result += emissive; + outColor = vec4(result, 1.0); outVelocity = fragVelocity; } diff --git a/shaders/simple_shader.vert b/shaders/simple_shader.vert index ea1dea2..c12787a 100644 --- a/shaders/simple_shader.vert +++ b/shaders/simple_shader.vert @@ -10,12 +10,14 @@ struct Light { layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inNormal; layout(location = 2) in vec2 inUV; +layout(location = 3) in vec3 inTangent; layout(location = 0) out vec2 fragUV; layout(location = 1) out vec3 fragNormal; layout(location = 2) out vec3 fragWorldPos; layout(location = 3) out float fragViewDepth; // CSM:视图深度(级联选择) layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量 +layout(location = 5) out vec3 fragTangent; // 世界空间切线(法线贴图) // GPUDriven:模型矩阵从实例缓冲按实例索引取。 // 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N), @@ -39,6 +41,7 @@ layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 projNoJitter; mat4 prevView; mat4 prevProj; + vec4 cameraPos; } ubo; void main() { @@ -60,6 +63,10 @@ void main() { mat3 normalMatrix = mat3(transpose(inverse(model))); fragNormal = normalize(normalMatrix * inNormal); + // 切线随模型变换(法线贴图 TBN;无切线数据保持零,frag 侧退回几何法线) + fragTangent = normalize(mat3(model) * inTangent); + // 零向量 normalize 产生 NaN,这里直接透传零,frag 判长度 + fragTangent = dot(inTangent, inTangent) > 0.0 ? fragTangent : vec3(0.0); fragUV = inUV; }