Files
HelloVulkan/Engine/src/Renderer/MaterialAsset.cpp
T

195 lines
7.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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