Add Hypershade-style Material Editor with node graph and shader-ball preview

Browser column lists all scene materials plus a searchable create-node
list; Show Graph (or double-click) loads a material into the node-graph
work area. The graph shows the entire network including MaterialX
(.mtlx) node graphs, resolving connections through NodeGraph boundaries
via UsdShadeUtils::GetValueProducingAttributes, with layered auto-layout
for nodes lacking authored uiPosition. Canvas navigates viewport-style
(Alt+MMB pan / Alt+RMB zoom) and TAB opens a Nuke-style search popup.
Selecting a node shows a typed property editor (live-apply, one undo
command per edit) and previews that node's output on the shader ball.

The preview renders through its own Hydra engine into a scratch stage
that composes the material via a reference to the source root layer (so
referenced .mtlx materials work), re-renders until progressive delegates
(Arnold/Cycles/Embree) converge, and lights with HDR dome presets
(External/Room/Interior/Sunset; CC0 Poly Haven EXRs fetched at CMake
configure). texture:format is authored latlong explicitly - left
automatic, hdArnold falls back to Arnold's angular fisheye default -
and hdCycles gets a -90 X pole rotation to match Storm's +Y-pole
sampling.

Mutations go through new ICommand subclasses (create shader node,
connect/disconnect attrs). imgui-node-editor is vendored (gitignored)
with a local one-line patch: c_ScrollButtonIndex 1->2 for MMB pan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 20:44:54 +08:00
parent 338970b243
commit 09819091c4
20 changed files with 2366 additions and 4 deletions
+43
View File
@@ -46,6 +46,7 @@ Application::Application()
, m_showPropertyPanel(true)
, m_showTimeline(true)
, m_showCurveEditor(false)
, m_showMaterialEditor(false)
, m_running(false) {
}
@@ -84,6 +85,12 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_curveEditorPanel = std::make_unique<CurveEditorPanel>();
m_curveEditorPanel->SetCommandHistory(&m_commandHistory);
m_materialManager = std::make_unique<MaterialManager>();
m_materialEditorPanel = std::make_unique<MaterialEditorPanel>();
m_materialEditorPanel->SetMaterialManager(m_materialManager.get());
m_materialEditorPanel->SetCommandHistory(&m_commandHistory);
m_timelinePanel = std::make_unique<TimelinePanel>();
m_timelinePanel->OnTimeChanged = [this](pxr::UsdTimeCode displayTime,
pxr::UsdTimeCode editTime) {
@@ -107,12 +114,14 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_viewportPanel->SetIconManager(m_iconManager.get());
m_timelinePanel->SetIconManager(m_iconManager.get());
m_stageEditorPanel->SetIconManager(m_iconManager.get());
m_materialEditorPanel->SetIconManager(m_iconManager.get());
m_sceneHierarchyPanel->SetOnPrimSelected(
[this](const std::string& path) {
m_viewportPanel->SetSelectedPrimPath(path);
m_propertyPanel->SetSelectedPrimPath(path);
m_curveEditorPanel->SetSelectedPrimPath(path);
m_materialEditorPanel->SetTargetPrimPath(path);
});
m_sceneHierarchyPanel->SetOnPrimsSelected(
@@ -130,6 +139,7 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_sceneHierarchyPanel->SetSelectedPath(path);
m_propertyPanel->SetSelectedPrimPath(path);
m_curveEditorPanel->SetSelectedPrimPath(path);
m_materialEditorPanel->SetTargetPrimPath(path);
};
// Rect drag in viewport → sync hierarchy + property panel + curve editor (primary)
@@ -137,6 +147,14 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_sceneHierarchyPanel->SetSelectedPaths(paths);
m_propertyPanel->SetSelectedPrimPath(paths.empty() ? "" : paths.front());
m_curveEditorPanel->SetSelectedPrimPath(paths.empty() ? "" : paths.front());
m_materialEditorPanel->SetTargetPrimPath(paths.empty() ? "" : paths.front());
};
// "Edit Material" in the Property Panel's Material Binding section →
// open the Material Editor on the resolved material.
m_propertyPanel->OnEditMaterialRequested = [this](const std::string& materialPath) {
m_showMaterialEditor = true;
m_materialEditorPanel->OpenOrCreateMaterial(materialPath);
};
@@ -173,6 +191,9 @@ bool Application::Initialize(const std::string& windowTitle, int width, int heig
m_viewportPanel->ApplyGlobalColorCorrection(
m_prefs.colorCorrectionMode, m_prefs.ocioDisplay,
m_prefs.ocioView, m_prefs.ocioColorSpace, m_prefs.ocioLook);
m_materialEditorPanel->SetColorCorrectionFromPrefs(
m_prefs.colorCorrectionMode, m_prefs.ocioDisplay,
m_prefs.ocioView, m_prefs.ocioColorSpace, m_prefs.ocioLook);
}
LOG_INFO("Application initialized successfully");
@@ -201,6 +222,9 @@ void Application::Shutdown() {
m_sceneHierarchyPanel.reset();
m_propertyPanel.reset();
m_stageEditorPanel.reset();
// Owns the shader-ball preview's Hydra engine + GL draw target — must be
// destroyed while the GL context still exists, like m_viewportPanel.
m_materialEditorPanel.reset();
m_propertyManager.reset();
m_layerManager.reset();
@@ -233,6 +257,8 @@ void Application::RefreshManagers() {
m_propertyPanel->SetStage(stage);
m_timelinePanel->SetStage(stage);
m_curveEditorPanel->SetStage(stage);
m_materialManager->SetStage(stage);
m_materialEditorPanel->SetStage(stage);
} else {
m_layerManager->SetStage(nullptr);
m_propertyManager->SetStage(nullptr);
@@ -241,6 +267,8 @@ void Application::RefreshManagers() {
m_propertyPanel->SetStage(nullptr);
m_timelinePanel->SetStage(nullptr);
m_curveEditorPanel->SetStage(nullptr);
m_materialManager->SetStage(nullptr);
m_materialEditorPanel->SetStage(nullptr);
}
}
@@ -318,6 +346,13 @@ void Application::RenderUI() {
ImGui::End();
}
if (m_showMaterialEditor) {
ImGui::SetNextWindowSize({960, 320}, ImGuiCond_FirstUseEver);
ImGui::Begin("Material Editor", &m_showMaterialEditor, ImGuiWindowFlags_NoCollapse);
m_materialEditorPanel->Render();
ImGui::End();
}
if (m_showPreferences)
RenderPreferencesDialog();
@@ -414,6 +449,13 @@ void Application::ApplyPrefsToAllViewports()
m_prefs.ocioView,
m_prefs.ocioColorSpace,
m_prefs.ocioLook);
if (m_materialEditorPanel)
m_materialEditorPanel->SetColorCorrectionFromPrefs(
m_prefs.colorCorrectionMode,
m_prefs.ocioDisplay,
m_prefs.ocioView,
m_prefs.ocioColorSpace,
m_prefs.ocioLook);
}
void Application::RenderPreferencesDialog()
@@ -689,6 +731,7 @@ void Application::RenderMenuBar() {
ImGui::MenuItem("Property Panel", nullptr, &m_showPropertyPanel);
ImGui::MenuItem("Timeline", nullptr, &m_showTimeline);
ImGui::MenuItem("Curve Editor", nullptr, &m_showCurveEditor);
ImGui::MenuItem("Material Editor", nullptr, &m_showMaterialEditor);
ImGui::Separator();
ImGui::MenuItem("Stage Info", nullptr, &m_showStageInfo);
ImGui::MenuItem("Demo Window", nullptr, &m_showDemoWindow);