QmlDesigner: Add shader overrides for materials and models in 3D view

It is now possible to override all materials and models used in the
scene with options available in QtQuick3D.DebugSettings. The overrides
are assigned per-split.

Fixes: QDS-11068
Change-Id: I3a3bc372e860d7f61942eb40166464c9c86efd8e
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Mats Honkamaa <mats.honkamaa@qt.io>
This commit is contained in:
Miikka Heikkinen
2023-10-31 15:15:11 +02:00
parent 6a4850b1d8
commit 23f12f7b42
10 changed files with 291 additions and 5 deletions

View File

@@ -16,6 +16,7 @@ public:
Edit3DToolState,
Render3DView,
ActiveSceneChanged,
ActiveSplitChanged,
RenderModelNodePreviewImage,
Import3DSupport,
NodeAtPos,

View File

@@ -47,7 +47,9 @@ enum class View3DActionType {
SyncEnvBackground,
GetNodeAtPos,
SetBakeLightsView3D,
SplitViewToggle
SplitViewToggle,
MaterialOverride,
ShowWireframe
};
constexpr bool isNanotraceEnabled()

View File

@@ -58,6 +58,9 @@ Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies)
m_compressionTimer.setInterval(1000);
m_compressionTimer.setSingleShot(true);
connect(&m_compressionTimer, &QTimer::timeout, this, &Edit3DView::handleEntriesChanged);
for (int i = 0; i < 4; ++i)
m_splitToolStates.append({0, false});
}
void Edit3DView::createEdit3DWidget()
@@ -108,6 +111,18 @@ void Edit3DView::renderImage3DChanged(const QImage &img)
void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
{
const QString activeSplitKey = QStringLiteral("activeSplit");
if (sceneState.contains(activeSplitKey)) {
m_activeSplit = sceneState[activeSplitKey].toInt();
// If the sceneState contained just activeSplit key, then this is simply an active split
// change rather than entire active scene change, and we don't need to process further.
if (sceneState.size() == 1)
return;
} else {
m_activeSplit = 0;
}
const QString sceneKey = QStringLiteral("sceneInstanceId");
const QString selectKey = QStringLiteral("selectionMode");
const QString transformKey = QStringLiteral("transformMode");
@@ -122,6 +137,8 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
const QString particlesPlayKey = QStringLiteral("particlePlay");
const QString syncEnvBgKey = QStringLiteral("syncEnvBackground");
const QString splitViewKey = QStringLiteral("splitView");
const QString matOverrideKey = QStringLiteral("matOverride");
const QString showWireframeKey = QStringLiteral("showWireframe");
if (sceneState.contains(sceneKey)) {
qint32 newActiveScene = sceneState[sceneKey].value<qint32>();
@@ -197,6 +214,24 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
else
m_splitViewAction->action()->setChecked(false);
if (sceneState.contains(matOverrideKey)) {
const QVariantList overrides = sceneState[matOverrideKey].toList();
for (int i = 0; i < 4; ++i)
m_splitToolStates[i].matOverride = i < overrides.size() ? overrides[i].toInt() : 0;
} else {
for (SplitToolState &state : m_splitToolStates)
state.matOverride = 0;
}
if (sceneState.contains(showWireframeKey)) {
const QVariantList showList = sceneState[showWireframeKey].toList();
for (int i = 0; i < 4; ++i)
m_splitToolStates[i].showWireframe = i < showList.size() ? showList[i].toBool() : false;
} else {
for (SplitToolState &state : m_splitToolStates)
state.showWireframe = false;
}
// Syncing background color only makes sense for children of View3D instances
bool syncValue = false;
bool syncEnabled = false;
@@ -663,6 +698,24 @@ void Edit3DView::syncSnapAuxPropsToSettings()
Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL));
}
const QList<Edit3DView::SplitToolState> &Edit3DView::splitToolStates() const
{
return m_splitToolStates;
}
void Edit3DView::setSplitToolState(int splitIndex, const SplitToolState &state)
{
if (splitIndex >= m_splitToolStates.size())
return;
m_splitToolStates[splitIndex] = state;
}
int Edit3DView::activeSplit() const
{
return m_activeSplit;
}
void Edit3DView::createEdit3DActions()
{
m_selectionModeAction = std::make_unique<Edit3DAction>(

View File

@@ -32,6 +32,12 @@ class QMLDESIGNERCOMPONENTS_EXPORT Edit3DView : public AbstractView
Q_OBJECT
public:
struct SplitToolState
{
int matOverride = 0;
bool showWireframe = false;
};
Edit3DView(ExternalDependenciesInterface &externalDependencies);
WidgetInfo widgetInfo() override;
@@ -78,6 +84,11 @@ public:
void syncSnapAuxPropsToSettings();
const QList<SplitToolState> &splitToolStates() const;
void setSplitToolState(int splitIndex, const SplitToolState &state);
int activeSplit() const;
private slots:
void onEntriesChanged();
@@ -160,6 +171,9 @@ private:
QPointer<BakeLights> m_bakeLights;
bool m_isBakingLightsSupported = false;
QPointer<SnapConfiguration> m_snapConfiguration;
int m_activeSplit = 0;
QList<SplitToolState> m_splitToolStates;
friend class Edit3DAction;
};

View File

@@ -281,6 +281,95 @@ void Edit3DWidget::createContextMenu()
m_toggleGroupAction->setChecked(defaultToggleGroupAction->isChecked());
m_contextMenu->addSeparator();
auto overridesSubMenu = new QmlEditorMenu(tr("Shader Overrides"), m_contextMenu);
overridesSubMenu->setToolTipsVisible(true);
m_contextMenu->addMenu(overridesSubMenu);
m_wireFrameAction = overridesSubMenu->addAction(
tr("Wireframe"), this, &Edit3DWidget::onWireframeAction);
m_wireFrameAction->setCheckable(true);
m_wireFrameAction->setToolTip(tr("Show models as wireframe."));
overridesSubMenu->addSeparator();
// The type enum order must match QQuick3DDebugSettings::QQuick3DMaterialOverrides enums
enum class MaterialOverrideType {
None,
BaseColor,
Roughness,
Metalness,
Diffuse,
Specular,
ShadowOcclusion,
Emission,
AmbientOcclusion,
Normals,
Tangents,
Binormals,
F0
};
auto addOverrideMenuAction = [&](const QString &label, const QString &toolTip,
MaterialOverrideType type) {
QAction *action = overridesSubMenu->addAction(
label, this, &Edit3DWidget::onMatOverrideAction);
action->setData(int(type));
action->setCheckable(true);
action->setToolTip(toolTip);
m_matOverrideActions.insert(int(type), action);
};
addOverrideMenuAction(tr("No Material Override"),
tr("Rendering occurs as normal."),
MaterialOverrideType::None);
addOverrideMenuAction(tr("Base Color"),
tr("The BaseColor or Diffuse color of a material is passed through without any lighting."),
MaterialOverrideType::BaseColor);
addOverrideMenuAction(tr("Roughness"),
tr("The Roughness of a material is passed through as an unlit greyscale value."),
MaterialOverrideType::Roughness);
addOverrideMenuAction(tr("Metalness"),
tr("The Metalness of a material is passed through as an unlit greyscale value."),
MaterialOverrideType::Metalness);
addOverrideMenuAction(tr("Diffuse"),
tr("Only the diffuse contribution of the material after all lighting."),
MaterialOverrideType::Diffuse);
addOverrideMenuAction(tr("Specular"),
tr("Only the specular contribution of the material after all lighting."),
MaterialOverrideType::Specular);
addOverrideMenuAction(tr("Shadow Occlusion"),
tr("The Occlusion caused by shadows as a greyscale value."),
MaterialOverrideType::ShadowOcclusion);
addOverrideMenuAction(tr("Emission"),
tr("Only the emissive contribution of the material."),
MaterialOverrideType::Emission);
addOverrideMenuAction(tr("Ambient Occlusion"),
tr("Only the Ambient Occlusion of the material."),
MaterialOverrideType::AmbientOcclusion);
addOverrideMenuAction(tr("Normals"),
tr("The interpolated world space Normal value of the material mapped to an RGB color."),
MaterialOverrideType::Normals);
addOverrideMenuAction(tr("Tangents"),
tr("The interpolated world space Tangent value of the material mapped to an RGB color.\n"
"This will only be visible if the Tangent value is used."),
MaterialOverrideType::Tangents);
addOverrideMenuAction(tr("Binormals"),
tr("The interpolated world space Binormal value of the material mapped to an RGB color.\n"
"This will only be visible if the Binormal value is used."),
MaterialOverrideType::Binormals);
addOverrideMenuAction(tr("Fresnel 0"),
tr("This represents the Fresnel Reflectance at 0 Degrees.\n"
"This will only be visible for materials that calculate an F0 value."),
MaterialOverrideType::F0);
overridesSubMenu->addSeparator();
QAction *resetAction = overridesSubMenu->addAction(
tr("Reset All Overrides"), this, &Edit3DWidget::onResetAllOverridesAction);
resetAction->setToolTip(tr("Reset all overrides for all splits."));
m_contextMenu->addSeparator();
}
bool Edit3DWidget::isPasteAvailable() const
@@ -422,6 +511,69 @@ void Edit3DWidget::onCreateAction()
});
}
void Edit3DWidget::onMatOverrideAction()
{
QAction *action = qobject_cast<QAction *>(sender());
if (!action || !m_view || !m_view->model())
return;
QVariantList list;
for (int i = 0; i < m_view->splitToolStates().size(); ++i) {
Edit3DView::SplitToolState state = m_view->splitToolStates()[i];
if (i == m_view->activeSplit()) {
state.matOverride = action->data().toInt();
m_view->setSplitToolState(i, state);
list.append(action->data());
} else {
list.append(state.matOverride);
}
}
view()->emitView3DAction(View3DActionType::MaterialOverride, list);
}
void Edit3DWidget::onWireframeAction()
{
QAction *action = qobject_cast<QAction *>(sender());
if (!action || !m_view || !m_view->model())
return;
QVariantList list;
for (int i = 0; i < m_view->splitToolStates().size(); ++i) {
Edit3DView::SplitToolState state = m_view->splitToolStates()[i];
if (i == m_view->activeSplit()) {
state.showWireframe = action->isChecked();
m_view->setSplitToolState(i, state);
list.append(action->isChecked());
} else {
list.append(state.showWireframe);
}
}
view()->emitView3DAction(View3DActionType::ShowWireframe, list);
}
void Edit3DWidget::onResetAllOverridesAction()
{
if (!m_view || !m_view->model())
return;
QVariantList wList;
QVariantList mList;
for (int i = 0; i < m_view->splitToolStates().size(); ++i) {
Edit3DView::SplitToolState state;
state.showWireframe = false;
state.matOverride = 0;
m_view->setSplitToolState(i, state);
wList.append(state.showWireframe);
mList.append(state.matOverride);
}
view()->emitView3DAction(View3DActionType::ShowWireframe, wList);
view()->emitView3DAction(View3DActionType::MaterialOverride, mList);
}
void Edit3DWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
{
if (m_view)
@@ -502,6 +654,15 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible());
m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled());
if (m_view) {
int idx = m_view->activeSplit();
m_wireFrameAction->setChecked(m_view->splitToolStates()[idx].showWireframe);
for (QAction *a : std::as_const(m_matOverrideActions))
a->setChecked(false);
int type = m_view->splitToolStates()[idx].matOverride;
m_matOverrideActions[type]->setChecked(true);
}
m_contextMenu->popup(mapToGlobal(pos));
}

View File

@@ -54,6 +54,9 @@ public:
private slots:
void onCreateAction();
void onMatOverrideAction();
void onWireframeAction();
void onResetAllOverridesAction();
protected:
void dragEnterEvent(QDragEnterEvent *dragEnterEvent) override;
@@ -90,6 +93,8 @@ private:
QPointer<QAction> m_alignViewAction;
QPointer<QAction> m_selectParentAction;
QPointer<QAction> m_toggleGroupAction;
QPointer<QAction> m_wireFrameAction;
QHash<int, QPointer<QAction>> m_matOverrideActions;
QPointer<QMenu> m_createSubMenu;
ModelNode m_contextMenuTarget;
QVector3D m_contextMenuPos3d;

View File

@@ -1719,6 +1719,11 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand
} else if (command.type() == PuppetToCreatorCommand::ActiveSceneChanged) {
const auto sceneState = qvariant_cast<QVariantMap>(command.data());
emitUpdateActiveScene3D(sceneState);
} else if (command.type() == PuppetToCreatorCommand::ActiveSplitChanged) {
// Active split change is a special case of active scene change
QVariantMap splitState;
splitState.insert("activeSplit", command.data());
emitUpdateActiveScene3D(splitState);
} else if (command.type() == PuppetToCreatorCommand::RenderModelNodePreviewImage) {
ImageContainer container = qvariant_cast<ImageContainer>(command.data());
QImage image = container.image();

View File

@@ -1,9 +1,9 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick 6.0
import QtQuick3D 6.0
import MouseArea3D 1.0
import QtQuick
import QtQuick3D
import MouseArea3D
Item {
id: viewRoot
@@ -17,6 +17,8 @@ Item {
property var overlayViews: [overlayView0, overlayView1, overlayView2, overlayView3]
property var cameraControls: [cameraControl0, cameraControl1, cameraControl2, cameraControl3]
property var viewRects: [viewRect0, viewRect1, viewRect2, viewRect3]
property var materialOverrides: [DebugSettings.None, DebugSettings.None, DebugSettings.None, DebugSettings.None]
property var showWireframes: [false, false, false, false]
property var activeEditView: editViews[activeSplit]
property var activeOverlayView: overlayViews[activeSplit]
property string sceneId
@@ -56,6 +58,7 @@ Item {
signal commitObjectProperty(var objects, var propNames)
signal changeObjectProperty(var objects, var propNames)
signal notifyActiveSceneChange()
signal notifyActiveSplitChange(int index)
onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective)
onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight)
@@ -68,6 +71,8 @@ Item {
onShowParticleEmitterChanged: _generalHelper.storeToolState(sceneId, "showParticleEmitter", showParticleEmitter);
onSelectionModeChanged: _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode);
onTransformModeChanged: _generalHelper.storeToolState(sceneId, "transformMode", transformMode);
onMaterialOverridesChanged: _generalHelper.storeToolState(sceneId, "matOverride", materialOverrides);
onShowWireframesChanged: _generalHelper.storeToolState(sceneId, "showWireframe", showWireframes);
onSplitViewChanged: {
_generalHelper.storeToolState(sceneId, "splitView", splitView);
_generalHelper.requestOverlayUpdate();
@@ -75,6 +80,7 @@ Item {
onActiveSplitChanged: {
_generalHelper.storeToolState(sceneId, "activeSplit", activeSplit);
cameraControls[activeSplit].forceActiveFocus();
notifyActiveSplitChange(activeSplit);
}
onActiveSceneChanged: updateActiveScene()
@@ -96,7 +102,9 @@ Item {
"gridColor": gridColor,
"importScene": activeScene,
"cameraLookAt": cameraControls[i]._lookAtPoint,
"z": 1});
"z": 1,
"sceneEnv.debugSettings.materialOverride": materialOverrides[i],
"sceneEnv.debugSettings.wireframeEnabled": showWireframes[i]});
editViews[i].usePerspective = Qt.binding(function() {return usePerspective;});
editViews[i].showSceneLight = Qt.binding(function() {return showEditLight;});
editViews[i].showGrid = Qt.binding(function() {return showGrid;});
@@ -107,6 +115,16 @@ Item {
editViews[2].cameraLookAt = Qt.binding(function() {return cameraControl2._lookAtPoint;});
editViews[3].cameraLookAt = Qt.binding(function() {return cameraControl3._lookAtPoint;});
editViews[0].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[0];});
editViews[1].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[1];});
editViews[2].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[2];});
editViews[3].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[3];});
editViews[0].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[0];});
editViews[1].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[1];});
editViews[2].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[2];});
editViews[3].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[3];});
selectionBoxCount = 0;
editViewsChanged();
return true;
@@ -340,6 +358,16 @@ Item {
activeSplit = toolStates.activeSplit;
else if (resetToDefault)
activeSplit = 0;
if ("showWireframe" in toolStates)
showWireframes = toolStates.showWireframe;
else if (resetToDefault)
showWireframes = [false, false, false, false];
if ("matOverride" in toolStates)
materialOverrides = toolStates.matOverride;
else if (resetToDefault)
materialOverrides = [DebugSettings.None, DebugSettings.None, DebugSettings.None, DebugSettings.None];
}
function storeCurrentToolStates()
@@ -357,6 +385,8 @@ Item {
_generalHelper.storeToolState(sceneId, "transformMode", transformMode);
_generalHelper.storeToolState(sceneId, "splitView", splitView)
_generalHelper.storeToolState(sceneId, "activeSplit", activeSplit)
_generalHelper.storeToolState(sceneId, "showWireframe", showWireframes)
_generalHelper.storeToolState(sceneId, "matOverride", materialOverrides)
for (var i = 0; i < 4; ++i)
cameraControls[i].storeCameraState(0);

View File

@@ -881,6 +881,12 @@ void Qt5InformationNodeInstanceServer::handleActiveSceneChange()
#endif
}
void Qt5InformationNodeInstanceServer::handleActiveSplitChange(int index)
{
nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::ActiveSplitChanged,
index});
}
void Qt5InformationNodeInstanceServer::handleToolStateChanged(const QString &sceneId,
const QString &tool,
const QVariant &toolState)
@@ -1860,6 +1866,8 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(
this, SLOT(handleObjectPropertyChange(QVariant, QVariant)));
QObject::connect(m_editView3DData.rootItem, SIGNAL(notifyActiveSceneChange()),
this, SLOT(handleActiveSceneChange()));
QObject::connect(m_editView3DData.rootItem, SIGNAL(notifyActiveSplitChange(int)),
this, SLOT(handleActiveSplitChange(int)));
QObject::connect(&m_propertyChangeTimer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout);
QObject::connect(&m_selectionChangeTimer, &QTimer::timeout,
@@ -2503,6 +2511,12 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c
case View3DActionType::SplitViewToggle:
updatedToolState.insert("splitView", command.isEnabled());
break;
case View3DActionType::ShowWireframe:
updatedToolState.insert("showWireframe", command.value().toList());
break;
case View3DActionType::MaterialOverride:
updatedToolState.insert("matOverride", command.value().toList());
break;
default:
break;

View File

@@ -68,6 +68,7 @@ private slots:
void handleObjectPropertyCommit(const QVariant &objects, const QVariant &propNames);
void handleObjectPropertyChange(const QVariant &objects, const QVariant &propNames);
void handleActiveSceneChange();
void handleActiveSplitChange(int index);
void handleToolStateChanged(const QString &sceneId, const QString &tool,
const QVariant &toolState);
void handleView3DSizeChange();