195 lines
7.4 KiB
C++
195 lines
7.4 KiB
C++
//
|
||
// Created by atdunbg on 2026/10/5.
|
||
//
|
||
|
||
#include <Engine/Renderer/MaterialAsset.h>
|
||
#include <Engine/Vulkan/Texture.h>
|
||
#include <Engine/Vulkan/Device.h>
|
||
#include <Engine/Vulkan/MemoryAllocator.h>
|
||
|
||
#include <yaml-cpp/yaml.h>
|
||
#include <Engine/Core/Log.h>
|
||
|
||
#include <fstream>
|
||
|
||
namespace Engine
|
||
{
|
||
Scope<Texture> 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<f32>(def.x), n[1].as<f32>(def.y),
|
||
n[2].as<f32>(def.z), n[3].as<f32>(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<T>(default)),旧文件永远可读
|
||
const u32 version = m["Version"].as<u32>(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<std::string>("");
|
||
}
|
||
}
|
||
// 分量序与 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<bool>(false);
|
||
return true;
|
||
}
|
||
|
||
MaterialAsset::MaterialAsset(
|
||
MaterialAssetData&& data,
|
||
std::array<Scope<Texture>, Material::kSlotCount>&& ownedTextures,
|
||
Scope<Material>&& 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
|