シーングラフ - カスタムマテリアル
Qt Quick Scene Graph でカスタムマテリアルを実装する方法を示します。
カスタムマテリアルの例では、カスタム頂点シェーダとフラグメントシェーダを持つマテリアルを使用してレンダリングされるアイテムの実装方法を示しています。
シェーダとマテリアル
主な機能はフラグメントシェーダにあります。
// 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に公開したいユニフォームに対応する3つのプロパティを追加します。
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()を再実装します。この関数が呼ばれたとき、2つのスレッドは同期ポイントにあるので、両方のクラスにアクセスしても安全です。
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を作成しています。
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.