feat: OBJ 全量贴图接入——Material 加 AO 槽 + OBJ 工作流适配
- Material 槽位 4→5(+AO),参数 UBO binding 4→5;shader ambient diffuse 项乘 ao(IBL 遮蔽),AO 默认纹理白色 - OBJ 槽落点补全:map_Bump→HEIGHT(5)、map_Ns→SHININESS(7)、 map_Ka→AMBIENT(3)(assimp OBJ importer 约定)——backpack 的 法线贴图此前也从未加载 - MTL 导出遗漏(roughness.jpg/ao.jpg 在目录未被引用):约定文件名 探测补槽(roughness/ao/occlusion × jpg/png),命中留 INFO 锚点 - 因子按工作流分支:glTF 用声明因子(MR 合图 B=金属度); OBJ metallic=0(specular 工作流无金属概念),roughness 有图→1, 无图→Blinn-Phong Ns 换算 R=sqrt(2/(Ns+2))(补偿默认贴图 G=0.5)
This commit is contained in:
@@ -3,15 +3,17 @@
|
|||||||
//
|
//
|
||||||
// Material:PBR 材质 = 纹理槽组 + 因子参数 UBO + 一个描述符集。
|
// Material:PBR 材质 = 纹理槽组 + 因子参数 UBO + 一个描述符集。
|
||||||
//
|
//
|
||||||
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致,glTF
|
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致):
|
||||||
// metallic-roughness 工作流):
|
|
||||||
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
||||||
// 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定)
|
// 2 metallicRoughness(线性:G=粗糙度,B=金属度,glTF 约定)
|
||||||
// 3 emissive(自发光,sRGB) 4 材质参数 UBO(fragment)
|
// 3 emissive(自发光,sRGB) 4 ao(环境光遮蔽,线性)
|
||||||
|
// 5 材质参数 UBO(fragment,kParamsBinding)
|
||||||
//
|
//
|
||||||
// 纹理非拥有(Model 的纹理缓存持有);缺槽由调用方传默认纹理。
|
// 纹理非拥有(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
|
#ifndef HELLOVULKAN_MATERIAL_H
|
||||||
@@ -44,8 +46,9 @@ namespace Engine {
|
|||||||
Normal = 1,
|
Normal = 1,
|
||||||
MetallicRoughness = 2,
|
MetallicRoughness = 2,
|
||||||
Emissive = 3,
|
Emissive = 3,
|
||||||
kSlotCount = 4, // 纹理槽数
|
Ao = 4,
|
||||||
kParamsBinding = 4, // 参数 UBO binding
|
kSlotCount = 5, // 纹理槽数
|
||||||
|
kParamsBinding = 5, // 参数 UBO binding
|
||||||
};
|
};
|
||||||
|
|
||||||
Material(Device& device, MemoryAllocator& allocator,
|
Material(Device& device, MemoryAllocator& allocator,
|
||||||
|
|||||||
@@ -84,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
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ void Model::InitDefaultTextures()
|
|||||||
Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false);
|
Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, 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,
|
||||||
@@ -104,14 +106,14 @@ void Model::LoadModel(const std::string& path)
|
|||||||
[](unsigned char c) { return std::tolower(c); });
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
return s;
|
return s;
|
||||||
}();
|
}();
|
||||||
const bool isGltf = lower.size() >= 5 &&
|
m_IsGltf = lower.size() >= 5 &&
|
||||||
(lower.rfind(".gltf") == lower.size() - 5 ||
|
(lower.rfind(".gltf") == lower.size() - 5 ||
|
||||||
lower.rfind(".glb") == lower.size() - 4);
|
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 |
|
||||||
(isGltf ? 0 : aiProcess_FlipUVs) |
|
(m_IsGltf ? 0 : aiProcess_FlipUVs) |
|
||||||
aiProcess_GenSmoothNormals |
|
aiProcess_GenSmoothNormals |
|
||||||
aiProcess_CalcTangentSpace | // 法线贴图 TBN
|
aiProcess_CalcTangentSpace | // 法线贴图 TBN
|
||||||
aiProcess_JoinIdenticalVertices);
|
aiProcess_JoinIdenticalVertices);
|
||||||
@@ -252,16 +254,22 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
|
|
||||||
// glTF2 贴图在 assimp 5.x 的落点:MR 合图 → METALNESS(15)/
|
// glTF2 贴图在 assimp 5.x 的落点:MR 合图 → METALNESS(15)/
|
||||||
// DIFFUSE_ROUGHNESS(16)(同图双槽);baseColor → BASE_COLOR(12)。
|
// 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_BASE_COLOR, Material::Albedo},
|
||||||
{aiTextureType_NORMALS, Material::Normal},
|
{aiTextureType_NORMALS, Material::Normal},
|
||||||
{aiTextureType_METALNESS, Material::MetallicRoughness},
|
{aiTextureType_HEIGHT, Material::Normal}, // OBJ map_Bump 落点
|
||||||
|
{aiTextureType_METALNESS, Material::MetallicRoughness},
|
||||||
{aiTextureType_DIFFUSE_ROUGHNESS, Material::MetallicRoughness},
|
{aiTextureType_DIFFUSE_ROUGHNESS, Material::MetallicRoughness},
|
||||||
{aiTextureType_UNKNOWN, Material::MetallicRoughness},
|
{aiTextureType_UNKNOWN, Material::MetallicRoughness},
|
||||||
{aiTextureType_EMISSIVE, Material::Emissive},
|
{aiTextureType_SHININESS, Material::MetallicRoughness},
|
||||||
{aiTextureType_EMISSION_COLOR, Material::Emissive},
|
{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) {
|
for (const auto& [type, slot] : slots) {
|
||||||
@@ -273,9 +281,31 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OBJ 常见导出遗漏:PBR 贴图在目录里但 MTL 未引用
|
||||||
|
//(backpack 的 roughness.jpg/ao.jpg 正是如此)。
|
||||||
|
// 约定文件名探测补槽,命中留 INFO 锚点
|
||||||
|
if (!m_IsGltf) {
|
||||||
|
const std::pair<const char*, Material::Slot> 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(),
|
material->GetName().C_Str(),
|
||||||
texPaths[Material::Albedo].empty() ? "<none>"
|
texPaths[Material::Albedo].empty() ? "<none>"
|
||||||
: texPaths[Material::Albedo],
|
: texPaths[Material::Albedo],
|
||||||
@@ -284,14 +314,33 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
texPaths[Material::MetallicRoughness].empty() ? "<none>"
|
texPaths[Material::MetallicRoughness].empty() ? "<none>"
|
||||||
: texPaths[Material::MetallicRoughness],
|
: texPaths[Material::MetallicRoughness],
|
||||||
texPaths[Material::Emissive].empty() ? "<none>"
|
texPaths[Material::Emissive].empty() ? "<none>"
|
||||||
: texPaths[Material::Emissive]);
|
: texPaths[Material::Emissive],
|
||||||
|
texPaths[Material::Ao].empty() ? "<none>"
|
||||||
|
: texPaths[Material::Ao]);
|
||||||
|
|
||||||
// PBR 因子(glTF2 材质属性;旧资产无此键 → 默认电介质 0/0.5)
|
// 因子按工作流分支:
|
||||||
ai_real metallic = 0.0f, roughness = 0.5f;
|
// glTF2:MR 合图(B=金属度语义),因子按声明(缺省 1/1)
|
||||||
material->Get(AI_MATKEY_METALLIC_FACTOR, metallic);
|
// OBJ:specular 工作流无金属概念 → metallic=0;
|
||||||
material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness);
|
// roughness 有独立贴图 → 因子 1(G 通道直达);
|
||||||
params.factors = glm::vec4(static_cast<f32>(metallic),
|
// 无贴图 → Blinn-Phong Ns 换算 R=sqrt(2/(Ns+2)),
|
||||||
static_cast<f32>(roughness), 0.0f, 0.0f);
|
// 补偿默认 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(一个描述符集)
|
// 材质缓存:同贴图组合+因子 → 同一 Material(一个描述符集)
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ 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 metallicRoughnessMap; // G=粗糙度 B=金属度
|
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 MaterialParams {
|
layout(set = 1, binding = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道)
|
||||||
|
layout(set = 1, binding = 5) uniform MaterialParams {
|
||||||
vec4 factors; // x = metallic 因子,y = roughness 因子
|
vec4 factors; // x = metallic 因子,y = roughness 因子
|
||||||
} matParams;
|
} matParams;
|
||||||
|
|
||||||
@@ -177,7 +178,8 @@ void main() {
|
|||||||
vec3 kD_amb = (1.0 - F_amb) * (1.0 - metallic);
|
vec3 kD_amb = (1.0 - F_amb) * (1.0 - metallic);
|
||||||
|
|
||||||
vec3 irradiance = texture(irradianceMap, N).rgb;
|
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);
|
vec3 R = reflect(-V, N);
|
||||||
const float kMaxPrefilterMip = 4.0; // 与 Cubemap::kPrefilterMips - 1 同步
|
const float kMaxPrefilterMip = 4.0; // 与 Cubemap::kPrefilterMips - 1 同步
|
||||||
|
|||||||
Reference in New Issue
Block a user