diff --git a/Engine/include/Engine/Renderer/Material.h b/Engine/include/Engine/Renderer/Material.h index 69f7c91..0933aa5 100644 --- a/Engine/include/Engine/Renderer/Material.h +++ b/Engine/include/Engine/Renderer/Material.h @@ -3,15 +3,17 @@ // // Material:PBR 材质 = 纹理槽组 + 因子参数 UBO + 一个描述符集。 // -// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致,glTF -// metallic-roughness 工作流): +// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致): // 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性) // 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定) -// 3 emissive(自发光,sRGB) 4 材质参数 UBO(fragment) +// 3 emissive(自发光,sRGB) 4 ao(环境光遮蔽,线性) +// 5 材质参数 UBO(fragment,kParamsBinding) // // 纹理非拥有(Model 的纹理缓存持有);缺槽由调用方传默认纹理。 -// 因子(metallicFactor/roughnessFactor)乘在 MR 贴图上——无 MR 图的 -// 旧资产退化为纯因子(电介质 + 常数粗糙度)。 +// 因子(metallicFactor/roughnessFactor)乘在 MR 贴图上: +// glTF(MR 合图 B=金属度语义)→ 因子取 glTF 声明(默认 1/1) +// OBJ(specular 工作流,独立 roughness 图或无图)→ metallic=0, +// roughness 因子按贴图有无/Ns 换算——金属度强制 0 // #ifndef HELLOVULKAN_MATERIAL_H @@ -44,8 +46,9 @@ namespace Engine { Normal = 1, MetallicRoughness = 2, Emissive = 3, - kSlotCount = 4, // 纹理槽数 - kParamsBinding = 4, // 参数 UBO binding + Ao = 4, + kSlotCount = 5, // 纹理槽数 + kParamsBinding = 5, // 参数 UBO binding }; Material(Device& device, MemoryAllocator& allocator, diff --git a/Engine/include/Engine/Renderer/Model.h b/Engine/include/Engine/Renderer/Model.h index 8ec168b..4f61e22 100644 --- a/Engine/include/Engine/Renderer/Model.h +++ b/Engine/include/Engine/Renderer/Model.h @@ -84,6 +84,7 @@ namespace Engine std::vector> m_Meshes; std::string m_ModelPath; + bool m_IsGltf = false; // glTF 家族:MR 合图语义 + 不翻转 UV }; } // namespace Engine diff --git a/Engine/src/Renderer/Model.cpp b/Engine/src/Renderer/Model.cpp index 1715da1..3c19415 100644 --- a/Engine/src/Renderer/Model.cpp +++ b/Engine/src/Renderer/Model.cpp @@ -47,6 +47,8 @@ void Model::InitDefaultTextures() Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false); m_DefaultTextures[Material::Emissive] = 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, @@ -104,14 +106,14 @@ void Model::LoadModel(const std::string& path) [](unsigned char c) { return std::tolower(c); }); return s; }(); - const bool isGltf = lower.size() >= 5 && + m_IsGltf = lower.size() >= 5 && (lower.rfind(".gltf") == lower.size() - 5 || lower.rfind(".glb") == lower.size() - 4); Assimp::Importer importer; const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | - (isGltf ? 0 : aiProcess_FlipUVs) | + (m_IsGltf ? 0 : aiProcess_FlipUVs) | aiProcess_GenSmoothNormals | aiProcess_CalcTangentSpace | // 法线贴图 TBN aiProcess_JoinIdenticalVertices); @@ -252,16 +254,22 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) // 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 slots[] = { - {aiTextureType_DIFFUSE, Material::Albedo}, - {aiTextureType_BASE_COLOR, Material::Albedo}, - {aiTextureType_NORMALS, Material::Normal}, - {aiTextureType_METALNESS, Material::MetallicRoughness}, + {aiTextureType_DIFFUSE, Material::Albedo}, + {aiTextureType_BASE_COLOR, Material::Albedo}, + {aiTextureType_NORMALS, Material::Normal}, + {aiTextureType_HEIGHT, Material::Normal}, // OBJ map_Bump 落点 + {aiTextureType_METALNESS, Material::MetallicRoughness}, {aiTextureType_DIFFUSE_ROUGHNESS, Material::MetallicRoughness}, - {aiTextureType_UNKNOWN, Material::MetallicRoughness}, - {aiTextureType_EMISSIVE, Material::Emissive}, - {aiTextureType_EMISSION_COLOR, Material::Emissive}, + {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_EMISSION_COLOR, Material::Emissive}, }; for (const auto& [type, slot] : slots) { @@ -273,9 +281,31 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) } } + // OBJ 常见导出遗漏:PBR 贴图在目录里但 MTL 未引用 + //(backpack 的 roughness.jpg/ao.jpg 正是如此)。 + // 约定文件名探测补槽,命中留 INFO 锚点 + if (!m_IsGltf) { + const std::pair probes[] = { + {"roughness", Material::MetallicRoughness}, + {"ao", Material::Ao}, + {"occlusion", Material::Ao}, + }; + for (const auto& [stem, slot] : probes) { + if (!texPaths[slot].empty()) continue; + for (const char* ext : {".jpg", ".png"}) { + std::filesystem::path p = modelDir / (std::string(stem) + ext); + if (std::filesystem::exists(p)) { + texPaths[slot] = p.string(); + ENGINE_INFO("Probed {} texture: {}", stem, p.string()); + break; + } + } + } + } + // 材质加载可见性:贴图槽缺失/路径解析错误会静默退化成默认 // 纹理(金属全没 → 塑料感),这里留下诊断锚点 - ENGINE_INFO("Material '{}': albedo={} normal={} MR={} emissive={}", + ENGINE_INFO("Material '{}': albedo={} normal={} MR={} emissive={} ao={}", material->GetName().C_Str(), texPaths[Material::Albedo].empty() ? "" : texPaths[Material::Albedo], @@ -284,14 +314,33 @@ Scope Model::ProcessMesh(aiMesh* mesh, const aiScene* scene) texPaths[Material::MetallicRoughness].empty() ? "" : texPaths[Material::MetallicRoughness], texPaths[Material::Emissive].empty() ? "" - : texPaths[Material::Emissive]); + : texPaths[Material::Emissive], + texPaths[Material::Ao].empty() ? "" + : texPaths[Material::Ao]); - // PBR 因子(glTF2 材质属性;旧资产无此键 → 默认电介质 0/0.5) - ai_real metallic = 0.0f, roughness = 0.5f; - material->Get(AI_MATKEY_METALLIC_FACTOR, metallic); - material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness); - params.factors = glm::vec4(static_cast(metallic), - static_cast(roughness), 0.0f, 0.0f); + // 因子按工作流分支: + // 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(metallic), + static_cast(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(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(一个描述符集) diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 2c242e4..043c1ea 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -38,7 +38,8 @@ layout(set = 1, binding = 0) uniform sampler2D albedoMap; layout(set = 1, binding = 1) uniform sampler2D normalMap; layout(set = 1, binding = 2) uniform sampler2D metallicRoughnessMap; // G=粗糙度 B=金属度 layout(set = 1, binding = 3) uniform sampler2D emissiveMap; -layout(set = 1, binding = 4) uniform MaterialParams { +layout(set = 1, binding = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道) +layout(set = 1, binding = 5) uniform MaterialParams { vec4 factors; // x = metallic 因子,y = roughness 因子 } matParams; @@ -177,7 +178,8 @@ void main() { vec3 kD_amb = (1.0 - F_amb) * (1.0 - metallic); vec3 irradiance = texture(irradianceMap, N).rgb; - vec3 diffuse = kD_amb * irradiance * albedo; + 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 同步