씬 그래프 - 커스텀 머티리얼
Qt Quick 씬 그래프에서 커스텀 머티리얼을 구현하는 방법을 보여줍니다.
커스텀 머티리얼 예제는 커스텀 버텍스 및 프래그먼트 셰이더가 있는 머티리얼을 사용하여 렌더링되는 항목을 구현하는 방법을 보여줍니다.
셰이더 및 머티리얼
주요 기능은 조각 셰이더에 있습니다.
// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #version 440 layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 fragColor; // uniform block: 84 bytes layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; // offset 0 float qt_Opacity; // offset 64 float zoom; // offset 68 vec2 center; // offset 72 int limit; // offset 80 } ubuf; void main() { vec4 color1 = vec4(1.0, 0.85, 0.55, 1); vec4 color2 = vec4(0.226, 0.0, 0.615, 1); float aspect_ratio = -ubuf.qt_Matrix[0][0]/ubuf.qt_Matrix[1][1]; vec2 z, c; c.x = (vTexCoord.x - 0.5) / ubuf.zoom + ubuf.center.x; c.y = aspect_ratio * (vTexCoord.y - 0.5) / ubuf.zoom + ubuf.center.y; int iLast; z = c; for (int i = 0; i < 1000000; i++) { if (i >= ubuf.limit) { iLast = i; break; } float x = (z.x * z.x - z.y * z.y) + c.x; float y = (z.y * z.x + z.x * z.y) + c.y; if ((x * x + y * y) > 4.0) { iLast = i; break; } z.x = x; z.y = y; } if (iLast == ubuf.limit) { fragColor = vec4(0.0, 0.0, 0.0, 1.0); } else { float f = (iLast * 1.0) / ubuf.limit; fragColor = mix(color1, color2, sqrt(f)); } }
조각 셰이더와 버텍스 셰이더는 QSGMaterialShader 서브클래스로 결합됩니다.
class CustomShader : public QSGMaterialShader { public: CustomShader() { setShaderFileName(VertexStage, QLatin1String(":/scenegraph/custommaterial/shaders/mandelbrot.vert.qsb")); setShaderFileName(FragmentStage, QLatin1String(":/scenegraph/custommaterial/shaders/mandelbrot.frag.qsb")); } bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; };
QSGMaterial 서브클래스는 렌더링 상태와 함께 셰이더를 캡슐화합니다. 이 예제에서는 셰이더 유니폼에 해당하는 상태 정보를 추가합니다. 머티리얼은 QSGMaterial::createShader()를 다시 구현하여 셰이더를 생성합니다.
class CustomMaterial : public QSGMaterial { public: CustomMaterial(); QSGMaterialType *type() const override; int compare(const QSGMaterial *other) const override; QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override { return new CustomShader; } struct { float center[2]; float zoom; int limit; bool dirty; } uniforms; };
유니폼 데이터를 업데이트하려면 QSGMaterialShader::updateUniformData()를 다시 구현합니다.
bool CustomShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { bool changed = false; QByteArray *buf = state.uniformData(); Q_ASSERT(buf->size() >= 84); if (state.isMatrixDirty()) { const QMatrix4x4 m = state.combinedMatrix(); memcpy(buf->data(), m.constData(), 64); changed = true; } if (state.isOpacityDirty()) { const float opacity = state.opacity(); memcpy(buf->data() + 64, &opacity, 4); changed = true; } auto *customMaterial = static_cast<CustomMaterial *>(newMaterial); if (oldMaterial != newMaterial || customMaterial->uniforms.dirty) { memcpy(buf->data() + 68, &customMaterial->uniforms.zoom, 4); memcpy(buf->data() + 72, &customMaterial->uniforms.center, 8); memcpy(buf->data() + 80, &customMaterial->uniforms.limit, 4); customMaterial->uniforms.dirty = false; changed = true; } return changed; }
아이템 및 노드
새 머티리얼을 보여주기 위해 커스텀 아이템을 만듭니다:
#include <QQuickItem> class CustomItem : public QQuickItem { Q_OBJECT Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) Q_PROPERTY(int iterationLimit READ iterationLimit WRITE setIterationLimit NOTIFY iterationLimitChanged) Q_PROPERTY(QPointF center READ center WRITE setCenter NOTIFY centerChanged) QML_ELEMENT public: explicit CustomItem(QQuickItem *parent = nullptr); qreal zoom() const { return m_zoom; } int iterationLimit() const { return m_limit; } QPointF center() const { return m_center; } public slots: void setZoom(qreal zoom); void setIterationLimit(int iterationLimit); void setCenter(QPointF center); signals: void zoomChanged(qreal zoom); void iterationLimitChanged(int iterationLimit); void centerChanged(QPointF center); protected: QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override; void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; private: bool m_geometryChanged = true; qreal m_zoom; bool m_zoomChanged = true; int m_limit; bool m_limitChanged = true; QPointF m_center; bool m_centerChanged = true; };
CustomItem 선언은 QML에 노출하려는 유니폼에 해당하는 세 가지 프로퍼티를 추가합니다.
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) Q_PROPERTY(int iterationLimit READ iterationLimit WRITE setIterationLimit NOTIFY iterationLimitChanged) Q_PROPERTY(QPointF center READ center WRITE setCenter NOTIFY centerChanged)
모든 사용자 정의 Qt Quick 항목과 마찬가지로 구현은 두 개로 나뉩니다. GUI 스레드에 있는 CustomItem
외에도 렌더 스레드에 있는 QSGNode 서브클래스를 생성합니다.
class CustomNode : public QSGGeometryNode { public: CustomNode() { auto *m = new CustomMaterial; setMaterial(m); setFlag(OwnsMaterial, true); QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); QSGGeometry::updateTexturedRectGeometry(g, QRect(), QRect()); setGeometry(g); setFlag(OwnsGeometry, true); } void setRect(const QRectF &bounds) { QSGGeometry::updateTexturedRectGeometry(geometry(), bounds, QRectF(0, 0, 1, 1)); markDirty(QSGNode::DirtyGeometry); } void setZoom(qreal zoom) { auto *m = static_cast<CustomMaterial *>(material()); m->uniforms.zoom = zoom; m->uniforms.dirty = true; markDirty(DirtyMaterial); } void setLimit(int limit) { auto *m = static_cast<CustomMaterial *>(material()); m->uniforms.limit = limit; m->uniforms.dirty = true; markDirty(DirtyMaterial); } void setCenter(const QPointF ¢er) { auto *m = static_cast<CustomMaterial *>(material()); m->uniforms.center[0] = center.x(); m->uniforms.center[1] = center.y(); m->uniforms.dirty = true; markDirty(DirtyMaterial); } };
노드는 머티리얼의 인스턴스를 소유하고 머티리얼의 상태를 업데이트하는 로직을 가지고 있습니다. 이 항목은 해당 QML 프로퍼티를 유지합니다. 항목과 머티리얼이 서로 다른 스레드에 있기 때문에 머티리얼의 정보를 복제해야 합니다.
void CustomItem::setZoom(qreal zoom) { if (qFuzzyCompare(m_zoom, zoom)) return; m_zoom = zoom; m_zoomChanged = true; emit zoomChanged(m_zoom); update(); } void CustomItem::setIterationLimit(int limit) { if (m_limit == limit) return; m_limit = limit; m_limitChanged = true; emit iterationLimitChanged(m_limit); update(); } void CustomItem::setCenter(QPointF center) { if (m_center == center) return; m_center = center; m_centerChanged = true; emit centerChanged(m_center); update(); }
정보는 QQuickItem::updatePaintNode()를 다시 구현하여 항목에서 씬 그래프로 복사됩니다. 함수가 호출될 때 두 스레드는 동기화 지점에 있으므로 두 클래스 모두에 액세스해도 안전합니다.
QSGNode *CustomItem::updatePaintNode(QSGNode *old, UpdatePaintNodeData *) { auto *node = static_cast<CustomNode *>(old); if (!node) node = new CustomNode; if (m_geometryChanged) node->setRect(boundingRect()); m_geometryChanged = false; if (m_zoomChanged) node->setZoom(m_zoom); m_zoomChanged = false; if (m_limitChanged) node->setLimit(m_limit); m_limitChanged = false; if (m_centerChanged) node->setCenter(m_center); m_centerChanged = false; return node; }
나머지 예제
이 애플리케이션은 간단한 QML 애플리케이션으로, QGuiApplication 및 QQuickView 에서 .qml 파일을 전달합니다.
QML 파일에서 루트를 채우기 위해 앵커링하는 사용자 정의 항목을 만듭니다.
CustomItem { property real t: 1 anchors.fill: parent center: Qt.point(-0.748, 0.1); iterationLimit: 3 * (zoom + 30) zoom: t * t / 10 NumberAnimation on t { from: 1 to: 60 duration: 30*1000; running: true loops: Animation.Infinite } }
예제를 좀 더 흥미롭게 만들기 위해 애니메이션을 추가하여 줌 레벨과 반복 제한을 변경합니다. 중심은 일정하게 유지됩니다.
© 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.