Qt Quick 3D - Beispiel für eine Schablonenumriss-Erweiterung
Zeigt, wie die QtQuick3D Render-Erweiterung verwendet werden kann, um Schablonenumrisse zu implementieren.
Dieses Beispiel zeigt, wie die QtQuick3D Render Extensions verwendet werden können, um die Unterstützung für Schablonenumrisse hinzuzufügen.
Der erste Schritt besteht darin, das Front-End-Element zu implementieren, indem ein neues Render Extension -Element erstellt wird, das die benötigten Eigenschaften für QML bereitstellt. In diesem Beispiel stellen wir 3 Eigenschaften zur Verfügung, eine target
, die die model, die wir für die Kontur verwenden wollen, die material, die wir für die Kontur verwenden wollen, und einen scale
Wert für die Einstellung der Größe der Kontur nimmt.
class OutlineRenderExtension : public QQuick3DRenderExtension { Q_OBJECT Q_PROPERTY(QQuick3DObject * target READ target WRITE setTarget NOTIFY targetChanged) Q_PROPERTY(QQuick3DObject * outlineMaterial READ outlineMaterial WRITE setOutlineMaterial NOTIFY outlineMaterialChanged) Q_PROPERTY(float outlineScale READ outlineScale WRITE setOutlineScale NOTIFY outlineScaleChanged) QML_ELEMENT public: OutlineRenderExtension() = default; ~OutlineRenderExtension() override; float outlineScale() const; void setOutlineScale(float newOutlineScale); QQuick3DObject *target() const; void setTarget(QQuick3DObject *newTarget); QQuick3DObject *outlineMaterial() const; void setOutlineMaterial(QQuick3DObject *newOutlineMaterial); signals: void outlineColorChanged(); void outlineScaleChanged(); void targetChanged(); void outlineMaterialChanged(); protected: QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; private: enum Dirty : quint8 { Target = 1 << 0, OutlineMaterial = 1 << 1, OutlineScale = 1 << 2 }; using DirtyT = std::underlying_type_t<Dirty>; void markDirty(Dirty v); QPointer<QQuick3DObject> m_target; QPointer<QQuick3DObject> m_outlineMaterial; float m_outlineScale = 1.05f; DirtyT m_dirtyFlag {}; };
Der zweite Schritt ist die Implementierung der Back-End-Klasse Render Extension. Diese Klasse enthält den Code, der von QtQuick3D ausgeführt wird.
Für diese Erweiterung werden wir after
den eingebauten Farbpass rendern, und wir werden als Teil des Haupt-Renderpasses rendern wollen, also werden wir PostColor und Main in unseren QSSGRenderExtension::stage() bzw. QSSGRenderExtension::mode() Funktionen zurückgeben.
class OutlineRenderer : public QSSGRenderExtension { public: OutlineRenderer() = default; bool prepareData(QSSGFrameData &data) override; void prepareRender(QSSGFrameData &data) override; void render(QSSGFrameData &data) override; void resetForFrame() override; RenderMode mode() const override { return RenderMode::Main; } RenderStage stage() const override { return RenderStage::PostColor; }; QSSGPrepContextId stencilPrepContext { QSSGPrepContextId::Invalid }; QSSGPrepContextId outlinePrepContext { QSSGPrepContextId::Invalid }; QSSGPrepResultId stencilPrepResult { QSSGPrepResultId::Invalid }; QSSGPrepResultId outlinePrepResult { QSSGPrepResultId::Invalid }; QPointer<QQuick3DObject> model; QSSGNodeId modelId { QSSGNodeId::Invalid }; QPointer<QQuick3DObject> material; QSSGResourceId outlineMaterialId {}; float outlineScale = 1.05f; QSSGRenderablesId stencilRenderables; QSSGRenderablesId outlineRenderables; };
Die nächste Funktion, die implementiert werden muss, ist QSSGRenderExtension::prepareData(). Diese Funktion sollte die Daten sammeln und einrichten, die diese Erweiterung zum Rendern verwenden wird. Wenn es nichts zu rendern gibt, sollte diese Funktion false
zurückgeben.
bool OutlineRenderer::prepareData(QSSGFrameData &data) { // Make sure we have a model and a material. if (!model || !material) return false; modelId = QQuick3DExtensionHelpers::getNodeId(*model); if (modelId == QSSGNodeId::Invalid) return false; outlineMaterialId = QQuick3DExtensionHelpers::getResourceId(*material); if (outlineMaterialId == QSSGResourceId::Invalid) return false; // This is the active camera for the scene (the camera used to render the QtQuick3D scene) QSSGCameraId camera = data.activeCamera(); if (camera == QSSGCameraId::Invalid) return false; // We are going to render the same renderable(s) twice so we need to create two contexts. stencilPrepContext = QSSGRenderHelpers::prepareForRender(data, *this, camera, 0); outlinePrepContext = QSSGRenderHelpers::prepareForRender(data, *this, camera, 1); // Create the renderables for the target model. One for the original with stencil write, and one for the outline model. // Note that we 'Steal' the model here, that tells QtQuick3D that we'll take over the rendering of the model. stencilRenderables = QSSGRenderHelpers::createRenderables(data, stencilPrepContext, { modelId }, QSSGRenderHelpers::CreateFlag::Steal); outlineRenderables = QSSGRenderHelpers::createRenderables(data, outlinePrepContext, { modelId }); // Now we can start setting the data for our models. // Here we set a material and a scale for the outline QSSGModelHelpers::setModelMaterials(data, outlineRenderables, modelId, { outlineMaterialId }); QMatrix4x4 globalTransform = QSSGModelHelpers::getGlobalTransform(data, modelId); globalTransform.scale(outlineScale); QSSGModelHelpers::setGlobalTransform(data, outlineRenderables, modelId, globalTransform); // When all changes are done, we need to commit the changes. stencilPrepResult = QSSGRenderHelpers::commit(data, stencilPrepContext, stencilRenderables); outlinePrepResult = QSSGRenderHelpers::commit(data, outlinePrepContext, outlineRenderables); // If there's something to be rendered we return true. const bool dataReady = (stencilPrepResult != QSSGPrepResultId::Invalid && outlinePrepResult != QSSGPrepResultId::Invalid); return dataReady; }
Wenn QSSGRenderExtension::prepareData() true
zurückgegeben hat, ist die nächste Funktion, die aufgerufen werden muss, QSSGRenderExtension::prepareRender(). In dieser Funktion richten wir die pipeline state für unsere beiden Renderables ein und weisen QtQuick3D an, die Primitive usw. für die Renderables vorzubereiten, indem wir QSSGRenderHelpers::prepareRenderables() aufrufen.
void OutlineRenderer::prepareRender(QSSGFrameData &data) { Q_ASSERT(modelId != QSSGNodeId::Invalid); Q_ASSERT(stencilPrepResult != QSSGPrepResultId::Invalid && outlinePrepResult != QSSGPrepResultId::Invalid); const auto &ctx = data.contextInterface(); if (const auto &rhiCtx = ctx->rhiContext()) { const QSSGRhiGraphicsPipelineState basePs = data.getPipelineState(); QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor(); const int samples = rhiCtx->mainPassSampleCount(); { // Original model - Write to the stencil buffer. QSSGRhiGraphicsPipelineState ps = basePs; ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef, QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled }; ps.stencilWriteMask = 0xff; ps.stencilRef = 1; ps.samples = samples; ps.cullMode = QRhiGraphicsPipeline::Back; ps.stencilOpFrontState = { QRhiGraphicsPipeline::Keep, QRhiGraphicsPipeline::Keep, QRhiGraphicsPipeline::Replace, QRhiGraphicsPipeline::Always }; QSSGRenderHelpers::prepareRenderables(data, stencilPrepResult, rpDesc, ps); } { // Scaled version - Only draw outside the original. QSSGRhiGraphicsPipelineState ps = basePs; ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef, QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled }; ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false); ps.stencilWriteMask = 0; ps.stencilRef = 1; ps.cullMode = QRhiGraphicsPipeline::Back; ps.stencilOpFrontState = { QRhiGraphicsPipeline::Keep, QRhiGraphicsPipeline::Keep, QRhiGraphicsPipeline::Replace, QRhiGraphicsPipeline::NotEqual }; QSSGRenderHelpers::prepareRenderables(data, outlinePrepResult, rpDesc, ps); } } }
Wenn die Engine bereit ist, die Rendering-Aufrufe für unsere Erweiterung aufzuzeichnen, wird sie die virtuelle Funktion QSSGRenderExtension::render() aufrufen. In diesem Beispiel können wir einfach QSSGRenderHelpers::renderRenderables() für die beiden Modelle aufrufen. Sie werden dann genauso gerendert, wie es QtQuick3D intern getan hätte, nur diesmal mit unseren Einstellungen.
void OutlineRenderer::render(QSSGFrameData &data) { Q_ASSERT(stencilPrepResult != QSSGPrepResultId::Invalid); const auto &ctx = data.contextInterface(); if (const auto &rhiCtx = ctx->rhiContext()) { QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); cb->debugMarkBegin(QByteArrayLiteral("Stencil outline pass")); QSSGRenderHelpers::renderRenderables(data, stencilPrepResult); QSSGRenderHelpers::renderRenderables(data, outlinePrepResult); cb->debugMarkEnd(); } }
Die OutlineRenderExtension
wird aktiv gemacht, indem sie der View3D's extensions Eigenschaft hinzugefügt wird.
View3D { id: view3d anchors.topMargin: 100 anchors.fill: parent extensions: [ OutlineRenderExtension { id: outlineRenderer outlineMaterial: outlineMaterial } ]
Wenn nun ein model
ausgewählt wird, müssen wir nur noch das ausgewählte model
als target
für das OutlineRenderExtension
festlegen, damit es mit einem Umriss gerendert wird.
MouseArea { anchors.fill: view3d onClicked: (mouse)=> { let hit = view3d.pick(mouse.x, mouse.y) outlineRenderer.target = hit.objectHit } }
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.