Qt Quick 3D - 模板轮廓扩展示例
演示了如何使用QtQuick3D 渲染扩展来实现模板轮廓绘制。

本示例展示了如何利用QtQuick3D 渲染扩展来添加对模板轮廓功能的支持。
第一步是实现前端项,通过创建一个新的Render Extension 项,将所需的属性暴露给QML。在本示例中,我们暴露了3个属性:target (用于接收需要勾勒轮廓的model )、material (用于指定轮廓颜色)以及scale (用于调整轮廓大小)。
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 {};
};第二步是实现后端类Render Extension ,该类包含将由QtQuick3D 调用的代码。
对于此扩展,我们将渲染after 的内置颜色渲染通道,并且希望将其作为主渲染通道的一部分进行渲染,因此我们将在QSSGRenderExtension::stage()和QSSGRenderExtension::mode()函数中分别返回PostColor 和Main 。
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;
};接下来需要实现的函数是QSSGRenderExtension::prepareData(),该函数应收集并准备此扩展在渲染过程中将使用的数据。如果没有内容需要渲染,该函数应返回false 。
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;
}如果QSSGRenderExtension::prepareData() 返回了true ,则接下来要调用的函数是QSSGRenderExtension::prepareRender()。在此函数中,我们将为两个可渲染对象设置pipeline state ,并通过调用QSSGRenderHelpers::prepareRenderables() 告知QtQuick3D 为这些可渲染对象准备基元等。
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);
}
}
}当引擎准备好记录我们扩展的渲染调用时,它将调用虚拟函数QSSGRenderExtension::render()。在此示例中,我们可以直接为两个模型调用QSSGRenderHelpers::renderRenderables(),它们随后将以与QtQuick3D 在内部执行时完全相同的方式进行渲染,只不过这次会采用我们的设置。
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();
}
}通过将其添加到View3D's 的extensions 属性中,即可使该OutlineRenderExtension 生效。
View3D {
id: view3d
anchors.topMargin: 100
anchors.fill: parent
extensions: [ OutlineRenderExtension {
id: outlineRenderer
outlineMaterial: outlineMaterial
}
]现在,当选中一个model 时,我们只需将选中的model 设置为OutlineRenderExtension 的target ,即可使其以带轮廓的方式渲染。
MouseArea {
anchors.fill: view3d
onClicked: (mouse)=> {
let hit = view3d.pick(mouse.x, mouse.y)
outlineRenderer.target = hit.objectHit
}
}© 2026 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.