// // MaterialEditorPanel.cpp:独立材质编辑器实现。 // // 编辑语义原样承接自原 Inspector "Edit Material" 内嵌块(行为不变的 // 搬家):参数 EditParams 直写 UBO + IsItemDeactivatedAfterEdit 落盘; // 纹理槽改动落盘 + ReloadMaterialAsset 重建(旧指针即刻悬空,本帧 // 不再触碰 asset)。 // #include "MaterialEditorPanel.h" #include "MaterialAssetUtil.h" #include #include #include #include #include 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(s)); InputTextString(kSlotNames[s], tp); if (ImGui::IsItemDeactivatedAfterEdit()) { pendingSlot = static_cast(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(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(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(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(s)); InputTextString(kSlotNames[s], tp); ImGui::PopID(); } ImGui::EndDisabled(); }