このページでは

シーングラフ - カスタムジオメトリ

Qt Quick のシーングラフにカスタムジオメトリを実装する方法を説明します。

このカスタムジオメトリの例では、シーングラフAPIを使用してシーングラフ用のカスタムジオメトリを構築するQQuickItem の作成方法を示します。具体的には、BezierCurve アイテムを作成し、これをCustomGeometryモジュールの一部として組み込み、QMLファイル内でこれを利用します。

ラインストリップを用いたカスタムシーングラフジオメトリでレンダリングされたベジエ曲線

BezierCurve の宣言

#include <QtQuick/QQuickItem>

class BezierCurve : public QQuickItem
{
    Q_OBJECT

    Q_PROPERTY(QPointF p1 READ p1 WRITE setP1 NOTIFY p1Changed)
    Q_PROPERTY(QPointF p2 READ p2 WRITE setP2 NOTIFY p2Changed)
    Q_PROPERTY(QPointF p3 READ p3 WRITE setP3 NOTIFY p3Changed)
    Q_PROPERTY(QPointF p4 READ p4 WRITE setP4 NOTIFY p4Changed)

    Q_PROPERTY(int segmentCount READ segmentCount WRITE setSegmentCount NOTIFY segmentCountChanged)
    QML_ELEMENT

public:
    BezierCurve(QQuickItem *parent = nullptr);
    ~BezierCurve();

    QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;

    QPointF p1() const { return m_p1; }
    QPointF p2() const { return m_p2; }
    QPointF p3() const { return m_p3; }
    QPointF p4() const { return m_p4; }

    int segmentCount() const { return m_segmentCount; }

    void setP1(const QPointF &p);
    void setP2(const QPointF &p);
    void setP3(const QPointF &p);
    void setP4(const QPointF &p);

    void setSegmentCount(int count);

signals:
    void p1Changed(const QPointF &p);
    void p2Changed(const QPointF &p);
    void p3Changed(const QPointF &p);
    void p4Changed(const QPointF &p);

    void segmentCountChanged(int count);

private:
    QPointF m_p1;
    QPointF m_p2;
    QPointF m_p3;
    QPointF m_p4;

    int m_segmentCount;
};

この項目の宣言では、QQuickItem クラスをサブクラス化し、5つのプロパティを追加しています。これらは、ベジエ曲線の4つの制御点それぞれに対応するプロパティと、曲線が分割されるセグメント数を制御するためのパラメータで構成されています。 各プロパティに対応するゲッターおよびセッター関数が用意されています。これらのプロパティはQMLでバインドできるため、変更がQMLエンジンによって検知され、適切に処理されるよう、各プロパティにノティファイア信号を設けることが推奨されます。

    QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;

QMLシーンとレンダリングシーングラフ間の同期ポイントは、仮想関数QQuickItem::updatePaintNode()であり、カスタムシーングラフロジックを持つすべてのアイテムはこの関数を実装する必要があります。

注: 多くのハードウェア構成において、シーングラフのレンダリングは 別のスレッドで行われます。したがって、シーングラフとのやり取りは、何よりもまず `QQuickItem::updatePaintNode()` 関数を通じて、制御された方法で行われることが極めて重要です。

BezierCurve の実装

BezierCurve::BezierCurve(QQuickItem *parent)
    : QQuickItem(parent)
    , m_p1(0, 0)
    , m_p2(1, 0)
    , m_p3(0, 1)
    , m_p4(1, 1)
    , m_segmentCount(32)
{
    setFlag(ItemHasContents, true);
}

BezierCurve のコンストラクタは、制御点とセグメント数のデフォルト値を設定します。ベジエ曲線は、アイテムのバウンディング矩形を基準とした正規化座標で指定されます。

また、コンストラクタはフラグQQuickItem::ItemHasContents を設定します。このフラグは、このアイテムが視覚的なコンテンツを提供し、QMLシーンとレンダリングシーングラフの同期が必要なタイミングでQQuickItem::updatePaintNode() を呼び出すことをキャンバスに伝えます。

BezierCurve::~BezierCurve() = default;

BezierCurve クラスにはクリーンアップが必要なデータメンバーがないため、デストラクタは何も行いません。なお、レンダリングシーングラフはシーングラフ自体によって管理されており、場合によっては別のスレッドで処理される可能性があるため、QQuickItem クラス内でQSGNode の参照を保持したり、明示的にクリーンアップを試みたりしてはなりません。

void BezierCurve::setP1(const QPointF &p)
{
    if (p == m_p1)
        return;

    m_p1 = p;
    emit p1Changed(p);
    update();
}

p1 プロパティのセッター関数は、値が変更されていないかどうかを確認し、変更されていない場合は早期に処理を終了します。 その後、内部値を更新し、changedシグナルを発行します。続いて、QQuickItem::update()関数を呼び出し、このオブジェクトの状態が変更されたこと、およびレンダリングシーングラフとの同期が必要であることをレンダリングシーングラフに通知します。update()の呼び出しは、後ほどQQuickItem::updatePaintNode()の呼び出しにつながります。

その他のプロパティセッターも同様の動作をするため、この例では省略しています。

QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    QSGGeometryNode *node = nullptr;
    QSGGeometry *geometry = nullptr;

    if (!oldNode) {
        node = new QSGGeometryNode;

updatePaintNode() 関数は、QML シーンの状態とレンダリングシーングラフを同期させるための主要な統合ポイントです。この関数には、前回の関数呼び出しで返されたインスタンスであるQSGNode が渡されます。関数が初めて呼び出された際は null となるため、この時点でQSGGeometryNode を作成し、そこにジオメトリとマテリアルを設定します。

        geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
        geometry->setLineWidth(2);
        geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry);

次に、ジオメトリを作成してノードに追加します。QSGGeometry コンストラクタの最初の引数は、頂点型の定義であり、「属性セット」と呼ばれます。 QMLでよく使用されるグラフィックスは、いくつかの一般的な標準属性セットを中心に構成されているため、これらはデフォルトで用意されています。ここでは、x座標用とy座標用の2つのfloatを持つ「Point2D」属性セットを使用します。2番目の引数は頂点数です。

カスタム属性セットを作成することも可能ですが、この例ではその説明は省略します。

ジオメトリのメモリ管理に関して特別な要件はないため、QSGGeometryNode がジオメトリを所有するように指定します。

割り当てを最小限に抑え、メモリの断片化を軽減し、パフォーマンスを向上させるために、ジオメトリをQSGGeometryNode のサブクラスのメンバとすることも可能です。その場合は、QSGGeometryNode::OwnsGeometryフラグを設定する必要はありません。

        auto *material = new QSGFlatColorMaterial;
        material->setColor(QColor(255, 0, 0));
        node->setMaterial(material);
        node->setFlag(QSGNode::OwnsMaterial);

シーングラフ API は、よく使われるマテリアル実装をいくつか提供しています。この例では、QSGFlatColorMaterial を使用します。これは、ジオメトリで定義された形状を単色で塗りつぶします。ここでも、マテリアルの所有権をノードに渡すことで、シーングラフによってクリーンアップされるようにしています。

    } else {
        node = static_cast<QSGGeometryNode *>(oldNode);
        geometry = node->geometry();
        geometry->allocate(m_segmentCount);
    }

QMLアイテムが変更され、既存のノードのジオメトリのみを変更したい場合は、oldNode をQSGGeometryNode インスタンスにキャストし、そのジオメトリを抽出します。セグメント数が変更された場合は、QSGGeometry::allocate()を呼び出し、正しい数の頂点があることを確認します。

    QSizeF itemSize = size();
    QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
    for (int i = 0; i < m_segmentCount; ++i) {
        qreal t = i / qreal(m_segmentCount - 1);
        qreal invt = 1 - t;

        QPointF pos = invt * invt * invt * m_p1
                    + 3 * invt * invt * t * m_p2
                    + 3 * invt * t * t * m_p3
                    + t * t * t * m_p4;

        float x = pos.x() * itemSize.width();
        float y = pos.y() * itemSize.height();

        vertices[i].set(x, y);
    }
    node->markDirty(QSGNode::DirtyGeometry);

ジオメトリを埋めるには、まずそこから頂点配列を抽出します。デフォルトの属性セットの1つを使用しているため、便利関数QSGGeometry::vertexDataAsPoint2D()を利用できます。次に、各セグメントを順に処理してその位置を計算し、その値を頂点に書き込みます。

    return node;
}

関数の最後で、シーングラフがレンダリングできるようにノードを返します。

アプリケーションのエントリポイント

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);

    QQuickView view;
    QSurfaceFormat format = view.format();
    format.setSamples(16);
    view.setFormat(format);
    view.setSource(QUrl("qrc:///scenegraph/customgeometry/main.qml"));
    view.show();

    return app.exec();
}

このアプリケーションは、QGuiApplication とQQuickView を持ち、.qmlファイルを渡す、シンプルなQMLアプリケーションです。

    QML_ELEMENT

BezierCurveアイテムを利用するには、QML_ELEMENT マクロを使用してQMLエンジンに登録する必要があります。これにより、BezierCurveという名前が付けられ、プロジェクトのビルドファイルで定義されている通り、CustomGeometry 1.0 モジュールの一部となります:

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

cmake_minimum_required(VERSION 3.16)
project(customgeometry_declarative LANGUAGES CXX)

find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick)

qt_standard_project_setup()

qt_add_executable(customgeometry_declarative WIN32 MACOSX_BUNDLE
    beziercurve.cpp beziercurve.h
    main.cpp
)

target_link_libraries(customgeometry_declarative PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Quick
)

qt_add_qml_module(customgeometry_declarative
    URI CustomGeometry
    QML_FILES main.qml
    RESOURCE_PREFIX /scenegraph/customgeometry
    NO_RESOURCE_TARGET_PATH
)

install(TARGETS customgeometry_declarative
    BUNDLE  DESTINATION .
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

qt_generate_deploy_qml_app_script(
    TARGET customgeometry_declarative
    OUTPUT_SCRIPT deploy_script
    NO_UNSUPPORTED_PLATFORM_ERROR
    DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})
TARGET = customgeometry
QT += quick

CONFIG += qmltypes
QML_IMPORT_NAME = CustomGeometry
QML_IMPORT_MAJOR_VERSION = 1

SOURCES += \
    main.cpp \
    beziercurve.cpp

HEADERS += \
    beziercurve.h

RESOURCES += customgeometry.qrc

target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/customgeometry
INSTALLS += target

ベジエ曲線は線分として描画されるため、アンチエイリアシングを行うためにビューをマルチサンプリングするように指定します。これは必須ではありませんが、対応しているハードウェアではアイテムの見栄えが若干良くなります。マルチサンプリングは、メモリ使用量が増加することが多いため、デフォルトでは有効になっていません。

Itemの使用方法

import QtQuick
import CustomGeometry

この .qml ファイルでは、標準の型を取得するために `QtQuick 2.0 ` モジュールをインポートするとともに、新しく作成した `BezierCurve` オブジェクトを含む独自の `CustomGeometry 1.0 ` モジュールもインポートしています。

Item {
    width: 300
    height: 200

    BezierCurve {
        id: line
        anchors.fill: parent
        anchors.margins: 20

次に、ルートアイテムとBezierCurveのインスタンスを作成し、ルートアイテムを埋めるように配置します。

        property real t
        SequentialAnimation on t {
            NumberAnimation { to: 1; duration: 2000; easing.type: Easing.InOutQuad }
            NumberAnimation { to: 0; duration: 2000; easing.type: Easing.InOutQuad }
            loops: Animation.Infinite
        }

        p2: Qt.point(t, 1 - t)
        p3: Qt.point(1 - t, t)
    }

例をもう少し面白くするために、曲線の2つの制御点を変更するアニメーションを追加します。端点は変更されません。

    Text {
        anchors.bottom: line.bottom

        x: 20
        width: parent.width - 40
        wrapMode: Text.WordWrap

        text: qsTr("This curve is a custom scene graph item, implemented using line strips")
    }
}

最後に、この例が何を示しているかを説明する短いテキストを重ねます。

サンプルプロジェクト @ code.qt.io

© 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.