Qt Quick 3D - 스텐실 윤곽선 확장 기능 예시
QtQuick3D 렌더링 확장 기능을 사용하여 스텐실 아웃라인을 구현하는 방법을 보여줍니다.

이 예제는 QtQuick3D 렌더링 확장 기능을 사용하여 스텐실 윤곽선 기능을 지원하는 방법을 보여줍니다.
첫 번째 단계는 QML에 필요한 속성을 노출하는 새로운 Render Extension 항목을 생성하여 프론트엔드 항목을 구현하는 것입니다. 이 예제에서는 3개의 속성을 노출합니다. 윤곽선을 그릴 대상인 model 를 받는 target, 윤곽선에 사용할 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();
}
}OutlineRenderExtension 는 View3D's 의 extensions 속성에 추가함으로써 활성화됩니다.
View3D {
id: view3d
anchors.topMargin: 100
anchors.fill: parent
extensions: [ OutlineRenderExtension {
id: outlineRenderer
outlineMaterial: outlineMaterial
}
]이제 model 가 선택되면, OutlineRenderExtension 가 윤곽선과 함께 렌더링되도록 선택된 model 를 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.