Files
HelloVulkan/App/editor/panels/MaterialEditorPanel.cpp
T

340 lines
11 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.
//
// MaterialEditorPanel.cpp:独立材质编辑器实现。
//
// 编辑语义原样承接自原 Inspector "Edit Material" 内嵌块(行为不变的
// 搬家):参数 EditParams 直写 UBO + IsItemDeactivatedAfterEdit 落盘;
// 纹理槽改动落盘 + ReloadMaterialAsset 重建(旧指针即刻悬空,本帧
// 不再触碰 asset)。
//
#include "MaterialEditorPanel.h"
#include "MaterialAssetUtil.h"
#include <Engine/ECS/Components.h>
#include <Engine/Renderer/Model.h>
#include <imgui.h>
#include <cstdio>
#include <filesystem>
namespace {
// InputText 需要 char 缓冲;提交时写回 std::string(与 Inspector 同款)
bool InputTextString(const char* label, std::string& str)
{
char buf[256];
std::snprintf(buf, sizeof(buf), "%s", str.c_str());
if (ImGui::InputText(label, buf, sizeof(buf)))
{
str = buf;
return true;
}
return false;
}
constexpr float kPreviewSize = 128.0f; // 编辑器顶部大球(Inspector 槽位为 48²)
} // namespace
void MaterialEditorPanel::OpenAsset(const std::string& path)
{
m_AssetMode = true;
m_Path = path;
m_LoadTried = false;
m_Owner = entt::null;
m_Open = true;
}
void MaterialEditorPanel::OpenBuiltin(entt::entity owner, Engine::u32 meshIdx,
const std::string& displayName)
{
m_AssetMode = false;
m_Owner = owner;
m_MeshIdx = meshIdx;
m_DisplayName = displayName;
m_Path.clear();
m_Open = true;
}
void MaterialEditorPanel::Draw(const EditorContext& ctx)
{
if (!m_Open)
{
return;
}
if (!ImGui::Begin("Material Editor", &m_Open))
{
ImGui::End();
return;
}
if (m_AssetMode)
{
DrawAssetMode(ctx);
}
else
{
DrawBuiltinMode(ctx);
}
ImGui::End();
}
// 顶部预览球(tile 池请求幂等——窗口开着才请求,池按可见项换页)
void MaterialEditorPanel::DrawPreview(const EditorContext& ctx,
Engine::Material* mat)
{
Engine::i32 tile = -1;
void* atlasTex = nullptr;
if (ctx.renderer && mat)
{
tile = ctx.renderer->RequestMaterialPreview(mat);
atlasTex = ctx.renderer->GetMaterialPreviewTexture();
}
if (atlasTex && tile >= 0)
{
const float uvStep = 1.0f / float(Engine::Passes::kPreviewTilesPerSide);
const float u0 =
float(tile % Engine::Passes::kPreviewTilesPerSide) * uvStep;
const float v0 =
float(tile / Engine::Passes::kPreviewTilesPerSide) * uvStep;
ImGui::Image(atlasTex, ImVec2(kPreviewSize, kPreviewSize),
ImVec2(u0, v0), ImVec2(u0 + uvStep, v0 + uvStep));
}
else
{
ImGui::Dummy(ImVec2(kPreviewSize, kPreviewSize));
}
}
void MaterialEditorPanel::DrawAssetMode(const EditorContext& ctx)
{
if (!ctx.assets)
{
return;
}
// 每帧重解析:ReloadMaterialAsset 换实例 / 未加载自动 Load 一次
Engine::MaterialAsset* asset = ctx.assets->FindMaterialAsset(m_Path);
if (!asset && !m_LoadTried)
{
m_LoadTried = true;
ctx.assets->LoadMaterialAsset(m_Path);
asset = ctx.assets->FindMaterialAsset(m_Path);
}
if (!asset)
{
ImGui::TextDisabled("material not loaded: %s", m_Path.c_str());
if (ImGui::Button("Retry Load"))
{
m_LoadTried = false;
}
return;
}
const std::string& path = m_Path;
const std::string name =
std::filesystem::path(path).filename().generic_string();
// ---- 头部:预览球 + 资产名/路径 ----
DrawPreview(ctx, asset->GetMaterial());
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::TextUnformatted(name.c_str());
ImGui::TextDisabled("%s", path.c_str());
ImGui::TextDisabled("shared asset: edits apply to all entities "
"referencing it");
ImGui::EndGroup();
ImGui::Separator();
// ---- 透明队列归属(收集侧 CPU 标志)----
bool trans = asset->GetMaterial()->IsTransparent();
if (ImGui::Checkbox("Transparent", &trans))
{
asset->SetTransparent(trans);
Engine::MaterialAsset::Save(path, asset->GetData());
}
// ---- 参数(变化直写 UBO 实时预览;松手落盘,拖拽中不产生每帧 IO)----
Engine::MaterialParams p = asset->GetData().params;
auto EditParam = [&](bool changed)
{
if (changed)
{
asset->EditParams(p);
}
if (ImGui::IsItemDeactivatedAfterEdit())
{
Engine::MaterialAsset::Save(path, asset->GetData());
}
};
EditParam(
ImGui::DragFloat("Metallic", &p.factors.x, 0.01f, 0.0f, 1.0f));
EditParam(
ImGui::DragFloat("Roughness", &p.factors.y, 0.01f, 0.0f, 1.0f));
EditParam(ImGui::ColorEdit4("Base Color", &p.baseColor.x));
EditParam(ImGui::DragFloat("IOR", &p.surface.x, 0.01f, 1.0f, 3.0f));
// 发光 = emissiveFactor × emissive 贴图 × Strength(Emissive 槽
// 兜底贴图恒黑,无贴图时颜色因子是唯一入口)
EditParam(ImGui::ColorEdit3("Emissive Color", &p.emissiveFactor.x));
EditParam(ImGui::DragFloat("Emissive Strength", &p.surface.z, 0.05f,
0.0f, 100.0f));
// transmission/thickness 只有不透明段不读——透明 shader 专属
//(未勾 Transparent 时禁用编辑,防"拖了没反应"的困惑)
ImGui::BeginDisabled(!trans);
EditParam(ImGui::DragFloat("Transmission", &p.surface.y, 0.01f, 0.0f,
1.0f));
EditParam(ImGui::DragFloat("Thickness", &p.surface.w, 0.01f, 0.0f,
10.0f));
ImGui::EndDisabled();
if (!trans)
{
ImGui::TextDisabled("Transmission/Thickness need Transparent");
}
// ---- 纹理槽(变化 → 落盘 + waitIdle 重建)----
ImGui::SeparatorText("Textures");
static constexpr const char* kSlotNames[Engine::Material::kSlotCount] = {
"Albedo", "Normal", "MetallicRoughness",
"Emissive", "Ao", "Roughness"};
int pendingSlot = -1;
std::string pendingPath;
for (Engine::u32 s = 0; s < Engine::Material::kSlotCount; ++s)
{
std::string tp = asset->GetData().texPaths[s];
ImGui::PushID(static_cast<int>(s));
InputTextString(kSlotNames[s], tp);
if (ImGui::IsItemDeactivatedAfterEdit())
{
pendingSlot = static_cast<int>(s);
pendingPath = tp;
}
ImGui::SameLine();
if (ImGui::SmallButton("..."))
{
ImGui::OpenPopup("tex_pick");
}
if (ImGui::BeginPopup("tex_pick"))
{
for (const auto& f : EditorMaterialUtil::AssetTextureFiles())
{
if (ImGui::Selectable(f.c_str()))
{
pendingSlot = static_cast<int>(s);
pendingPath = f;
}
}
ImGui::EndPopup();
}
ImGui::PopID();
}
if (pendingSlot >= 0)
{
// 拷出数据段 → 落盘 → 重建资产(旧指针即刻悬空:本帧到此不再
// 触碰 asset,下帧 Find 取新实例)。旧材质已在本帧 Request 进
// 预览池脏表(新 tile 未画满三槽位时每帧重入队)——先 drop 再
// 销毁,否则 Render 段解引用悬空指针
Engine::MaterialAssetData d = asset->GetData();
d.texPaths[static_cast<Engine::u32>(pendingSlot)] = pendingPath;
Engine::MaterialAsset::Save(path, d);
if (ctx.renderer)
{
ctx.renderer->DropMaterialPreview(asset->GetMaterial());
}
ctx.assets->ReloadMaterialAsset(path);
}
}
void MaterialEditorPanel::DrawBuiltinMode(const EditorContext& ctx)
{
// 每帧重解析:实体可销毁(PIE Stop 换血)/ 模型可卸载
if (!ctx.scene || !ctx.assets)
{
ImGui::End();
return;
}
auto& reg = ctx.scene->GetRegistry();
auto* mr = reg.valid(m_Owner)
? reg.try_get<Engine::ECS::MeshRendererComponent>(m_Owner)
: nullptr;
const Engine::Model* model =
(mr && mr->model.Valid()) ? ctx.assets->GetModel(mr->model) : nullptr;
const Engine::MeshMaterialInfo* info =
(model && m_MeshIdx < model->GetMeshMaterialInfos().size())
? &model->GetMeshMaterialInfos()[m_MeshIdx]
: nullptr;
if (!mr || !model || !info)
{
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"built-in material no longer available");
ImGui::TextDisabled("(entity destroyed or model unloaded)");
return;
}
const std::string meshName =
(!info->name.empty())
? info->name
: "Slot " + std::to_string(m_MeshIdx);
// ---- 头部:预览球 + 名称/来源 ----
DrawPreview(ctx, model->GetMeshes()[m_MeshIdx]->GetMaterial());
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::TextUnformatted(meshName.c_str());
ImGui::TextDisabled("built-in material of %s",
std::filesystem::path(
ctx.assets->Models().Path(mr->model))
.filename()
.generic_string()
.c_str());
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f),
"read-only: built-in materials belong to the model");
ImGui::EndGroup();
ImGui::Separator();
// ---- 一键转可编辑:Create Override → 自动切到资产模式 ----
if (ImGui::Button("Create Override"))
{
const std::string rel = EditorMaterialUtil::CreateMaterialOverride(
ctx.assets->Models().Path(mr->model), *info, m_MeshIdx, *mr,
*ctx.assets);
if (!rel.empty())
{
OpenAsset(rel); // 数据已快照进资产,无缝进入编辑
}
}
ImGui::Separator();
// ---- 只读展示(与资产模式同款控件,锁编辑看值)----
ImGui::BeginDisabled(true);
bool trans = info->transparent;
ImGui::Checkbox("Transparent", &trans);
Engine::MaterialParams p = info->params;
ImGui::DragFloat("Metallic", &p.factors.x, 0.01f, 0.0f, 1.0f);
ImGui::DragFloat("Roughness", &p.factors.y, 0.01f, 0.0f, 1.0f);
ImGui::ColorEdit4("Base Color", &p.baseColor.x);
ImGui::DragFloat("IOR", &p.surface.x, 0.01f, 1.0f, 3.0f);
ImGui::ColorEdit3("Emissive Color", &p.emissiveFactor.x);
ImGui::DragFloat("Emissive Strength", &p.surface.z, 0.05f, 0.0f, 100.0f);
ImGui::BeginDisabled(!trans);
ImGui::DragFloat("Transmission", &p.surface.y, 0.01f, 0.0f, 1.0f);
ImGui::DragFloat("Thickness", &p.surface.w, 0.01f, 0.0f, 10.0f);
ImGui::EndDisabled();
if (!trans)
{
ImGui::TextDisabled("Transmission/Thickness need Transparent");
}
ImGui::SeparatorText("Textures");
static constexpr const char* kSlotNames[Engine::Material::kSlotCount] = {
"Albedo", "Normal", "MetallicRoughness",
"Emissive", "Ao", "Roughness"};
for (Engine::u32 s = 0; s < Engine::Material::kSlotCount; ++s)
{
std::string tp = info->texPaths[s];
ImGui::PushID(static_cast<int>(s));
InputTextString(kSlotNames[s], tp);
ImGui::PopID();
}
ImGui::EndDisabled();
}