// // Created by atdunbg on 2026/10/5. // #include #include #include #include #include #include #include namespace Engine { Scope CreateDefaultMaterialTexture( Device& device, MemoryAllocator& allocator, Material::Slot slot) { // 与 Model::InitDefaultTextures 同语义(单点实现,两处共用); // 颜色槽 sRGB / 数据槽线性 switch (slot) { case Material::Albedo: return Texture::CreateSolidColor(device, allocator, 255, 255, 255); case Material::Normal: return Texture::CreateSolidColor(device, allocator, 128, 128, 255, false); case Material::MetallicRoughness: return Texture::CreateSolidColor(device, allocator, 0, 128, 0, false); case Material::Emissive: // 兜底白(非黑):emissive = 贴图 × factor × strength——黑 // 兜底会把无贴图材质的 factor/strength 一起乘没(调了恒黑 // 的困惑源);白兜底 = 无贴图时 factor 即发光色,有贴图时 // glTF 语义不变(factor 黑的普通模型依旧不发光) return Texture::CreateSolidColor(device, allocator, 255, 255, 255); case Material::Ao: return Texture::CreateSolidColor(device, allocator, 255, 0, 0, false); case Material::Roughness: return Texture::CreateSolidColor(device, allocator, 128, 128, 128, false); } return nullptr; } namespace { // 槽位 ↔ YAML 键名(下标 = Material::Slot;改键名 = 破坏旧文件,勿动) constexpr const char* kSlotKeys[Material::kSlotCount] = { "Albedo", "Normal", "MetallicRoughness", "Emissive", "Ao", "Roughness" }; void EmitVec4(YAML::Emitter& e, const glm::vec4& v) { e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq; } glm::vec4 ReadVec4(const YAML::Node& n, const glm::vec4& def) { if (!n || !n.IsSequence() || n.size() < 4) return def; return {n[0].as(def.x), n[1].as(def.y), n[2].as(def.z), n[3].as(def.w)}; } } bool MaterialAsset::Save(const std::string& path, const MaterialAssetData& data) { YAML::Emitter out; out << YAML::BeginMap; out << YAML::Key << "Material" << YAML::Value << YAML::BeginMap; out << YAML::Key << "Version" << YAML::Value << kVersion; out << YAML::Key << "Textures" << YAML::Value << YAML::BeginMap; for (u32 slot = 0; slot < Material::kSlotCount; ++slot) { out << YAML::Key << kSlotKeys[slot] << YAML::Value << data.texPaths[slot]; } out << YAML::EndMap; // Textures // 因子块:flow 数组按 MaterialParams 分量序(shader UBO 同序) out << YAML::Key << "Factors" << YAML::Value; EmitVec4(out, data.params.factors); out << YAML::Key << "BaseColor" << YAML::Value; EmitVec4(out, data.params.baseColor); out << YAML::Key << "Surface" << YAML::Value; EmitVec4(out, data.params.surface); out << YAML::Key << "EmissiveFactor" << YAML::Value; EmitVec4(out, data.params.emissiveFactor); out << YAML::Key << "Misc" << YAML::Value; EmitVec4(out, data.params.misc); // per-slot UV 变换(KHR_texture_transform):6 槽各 A(4 分量) + T for (u32 slot = 0; slot < Material::kSlotCount; ++slot) { out << YAML::Key << ("UvA" + std::to_string(slot)) << YAML::Value; EmitVec4(out, data.params.uvA[slot]); out << YAML::Key << ("UvT" + std::to_string(slot)) << YAML::Value; EmitVec4(out, data.params.uvT[slot]); } out << YAML::Key << "Transparent" << YAML::Value << data.transparent; out << YAML::EndMap; // Material out << YAML::EndMap; std::ofstream file(path); if (!file.is_open()) { ENGINE_ERROR("MaterialAsset::Save: cannot open '{}'", path); return false; } file << out.c_str(); return true; } bool MaterialAsset::Load(const std::string& path, MaterialAssetData& out) { YAML::Node root; try { root = YAML::LoadFile(path); } catch (const YAML::Exception& e) { ENGINE_ERROR("MaterialAsset::Load: parse failed '{}': {}", path, e.what()); return false; } const YAML::Node m = root["Material"]; if (!m) { ENGINE_ERROR("MaterialAsset::Load: missing 'Material' root '{}'", path); return false; } // Version 向前兼容:当前只认 1;未来新字段全部走缺省键兼容 //(as(default)),旧文件永远可读 const u32 version = m["Version"].as(kVersion); if (version > kVersion) { ENGINE_WARN("MaterialAsset::Load: '{}' version {} > {} " "(newer file, missing fields fall back to defaults)", path, version, kVersion); } if (const YAML::Node t = m["Textures"]) { for (u32 slot = 0; slot < Material::kSlotCount; ++slot) { out.texPaths[slot] = t[kSlotKeys[slot]].as(""); } } // 分量序与 Save 一致;缺块 = MaterialParams 缺省值 out.params.factors = ReadVec4( m["Factors"], MaterialParams{}.factors); out.params.baseColor = ReadVec4( m["BaseColor"], MaterialParams{}.baseColor); out.params.surface = ReadVec4( m["Surface"], MaterialParams{}.surface); out.params.emissiveFactor = ReadVec4( m["EmissiveFactor"], MaterialParams{}.emissiveFactor); out.params.misc = ReadVec4( m["Misc"], MaterialParams{}.misc); for (u32 slot = 0; slot < Material::kSlotCount; ++slot) { out.params.uvA[slot] = ReadVec4( m["UvA" + std::to_string(slot)], MaterialParams{}.uvA[slot]); out.params.uvT[slot] = ReadVec4( m["UvT" + std::to_string(slot)], MaterialParams{}.uvT[slot]); } out.transparent = m["Transparent"].as(false); return true; } MaterialAsset::MaterialAsset( MaterialAssetData&& data, std::array, Material::kSlotCount>&& ownedTextures, Scope&& material) : m_Data(std::move(data)) , m_OwnedTextures(std::move(ownedTextures)) , m_Material(std::move(material)) { } MaterialAsset::~MaterialAsset() = default; void MaterialAsset::EditParams(const MaterialParams& params) { m_Data.params = params; m_Material->UpdateParams(params); } void MaterialAsset::SetTransparent(bool t) { m_Data.transparent = t; m_Material->SetTransparent(t); } } // namespace Engine