Szenengraph – Benutzerdefinierte Geometrie
Zeigt, wie eine benutzerdefinierte Geometrie im Qt Quick -Szenenbaum implementiert wird.
Das Beispiel zur benutzerdefinierten Geometrie zeigt, wie man eine „ QQuickItem “ erstellt, die mithilfe der Scene-Graph-API eine benutzerdefinierte Geometrie für den Scene Graph aufbaut. Dazu wird ein „ BezierCurve “-Element erstellt, das in das „CustomGeometry“-Modul integriert und in einer QML-Datei verwendet wird.

Deklaration von „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;
};Die Item-Deklaration ist eine Unterklasse der Klasse „ QQuickItem “ und fügt fünf Eigenschaften hinzu: jeweils eine für jeden der vier Kontrollpunkte der Bézier-Kurve sowie einen Parameter zur Steuerung der Anzahl der Segmente, in die die Kurve unterteilt wird. Für jede dieser Eigenschaften gibt es entsprechende Getter- und Setter-Funktionen. Da diese Eigenschaften in QML gebunden werden können, ist es außerdem empfehlenswert, für jede von ihnen Notifier-Signale zu definieren, damit Änderungen von der QML-Engine erfasst und entsprechend verarbeitet werden.
Der Synchronisationspunkt zwischen der QML-Szene und dem Rendering-Szenengraphen ist die virtuelle Funktion ` QQuickItem::updatePaintNode()`, die alle Elemente mit benutzerdefinierter Szenengraph-Logik implementieren müssen.
Hinweis: Der Szenengraph wird auf vielen Hardwarekonfigurationen in einem separaten Thread gerendert. Daher ist es entscheidend, dass die Interaktion mit dem Szenengraphen auf kontrollierte Weise erfolgt, in erster Linie über die Funktion ` QQuickItem::updatePaintNode()`.
Implementierung von `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);
}Der Konstruktor von `BezierCurve` legt Standardwerte für die Kontrollpunkte und die Anzahl der Segmente fest. Die Bézier-Kurve wird in normierten Koordinaten relativ zum Begrenzungsrechteck des Elements angegeben.
Der Konstruktor setzt außerdem das Flag ` QQuickItem::ItemHasContents`. Dieses Flag teilt dem Canvas mit, dass dieses Element visuellen Inhalt bereitstellt und die Funktion ` QQuickItem::updatePaintNode()` aufruft, sobald die QML-Szene mit dem Rendering-Szenenbaum synchronisiert werden muss.
BezierCurve::~BezierCurve() = default;Die Klasse `BezierCurve` verfügt über keine Datenelemente, die bereinigt werden müssen, sodass der Destruktor keine Aktion ausführt. Es ist erwähnenswert, dass der Rendering-Szenenbaum vom Szenenbaum selbst verwaltet wird, möglicherweise in einem anderen Thread; daher sollte man niemals ` QSGNode `-Referenzen in der Klasse ` QQuickItem ` behalten oder versuchen, diese explizit zu bereinigen.
void BezierCurve::setP1(const QPointF &p)
{
if (p == m_p1)
return;
m_p1 = p;
emit p1Changed(p);
update();
}Die Setter-Funktion für die Eigenschaft `p1` prüft, ob der Wert unverändert ist, und bricht in diesem Fall vorzeitig ab. Anschließend aktualisiert sie den internen Wert und sendet das Signal „changed“ aus. Danach ruft sie die Funktion ` QQuickItem::update()` auf, die den Rendering-Szenenbaum darüber informiert, dass sich der Zustand dieses Objekts geändert hat und mit dem Rendering-Szenenbaum synchronisiert werden muss. Ein Aufruf von `update()` führt zu einem späteren Zeitpunkt zu einem Aufruf von ` QQuickItem::updatePaintNode()`.
Die anderen Eigenschaftssetter verhalten sich analog und werden in diesem Beispiel weggelassen.
QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGGeometryNode *node = nullptr;
QSGGeometry *geometry = nullptr;
if (!oldNode) {
node = new QSGGeometryNode;Die Funktion `updatePaintNode()` ist der primäre Integrationspunkt für die Synchronisierung des Zustands der QML-Szene mit dem Rendering-Szenengraphen. Der Funktion wird ein ` QSGNode ` übergeben, bei dem es sich um die Instanz handelt, die beim letzten Aufruf der Funktion zurückgegeben wurde. Beim ersten Aufruf der Funktion ist dieser Wert `null`, und wir erstellen unseren ` QSGGeometryNode `, den wir mit Geometrie und einem Material füllen werden.
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
geometry->setLineWidth(2);
geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);Anschließend erstellen wir die Geometrie und fügen sie dem Knoten hinzu. Das erste Argument des Konstruktors von ` QSGGeometry ` ist eine Definition des Vertex-Typs, die als „Attributsatz“ bezeichnet wird. Da sich die in QML häufig verwendete Grafik auf einige wenige gängige Standard-Attributsätze konzentriert, werden diese standardmäßig bereitgestellt. Hier verwenden wir den Attributsatz „Point2D“, der zwei Float-Werte enthält, einen für die x-Koordinaten und einen für die y-Koordinaten. Das zweite Argument ist die Anzahl der Scheitelpunkte.
Es können auch benutzerdefinierte Attributsätze erstellt werden, was jedoch in diesem Beispiel nicht behandelt wird.
Da wir keine besonderen Anforderungen an die Speicherverwaltung der Geometrie haben, legen wir fest, dass die ` QSGGeometryNode ` Eigentümer der Geometrie sein soll.
Um Zuweisungen zu minimieren, die Speicherfragmentierung zu verringern und die Leistung zu verbessern, wäre es auch möglich, die Geometrie zu einem Mitglied einer Unterklasse von „ QSGGeometryNode “ zu machen; in diesem Fall hätten wir das Flag „QSGGeometryNode::OwnsGeometry“ nicht gesetzt.
auto *material = new QSGFlatColorMaterial;
material->setColor(QColor(255, 0, 0));
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);Die Szenengraph-API stellt einige häufig verwendete Materialimplementierungen bereit. In diesem Beispiel verwenden wir das „ QSGFlatColorMaterial “, das die durch die Geometrie definierte Form mit einer Volltonfarbe ausfüllt. Auch hier übertragen wir die Eigentümerschaft des Materials an den Knoten, damit es vom Szenengraphen bereinigt werden kann.
} else {
node = static_cast<QSGGeometryNode *>(oldNode);
geometry = node->geometry();
geometry->allocate(m_segmentCount);
}Für den Fall, dass sich das QML-Element geändert hat und wir lediglich die Geometrie des bestehenden Knotens anpassen möchten, wandeln wir das ` oldNode ` in eine ` QSGGeometryNode `-Instanz um und extrahieren deren Geometrie. Falls sich die Segmentanzahl geändert hat, rufen wir ` QSGGeometry::allocate()` auf, um sicherzustellen, dass die richtige Anzahl an Scheitelpunkten vorhanden ist.
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);Um die Geometrie zu füllen, extrahieren wir zunächst das Scheitelpunkt-Array daraus. Da wir einen der Standard-Attributsätze verwenden, können wir die Hilfsfunktion ` QSGGeometry::vertexDataAsPoint2D()` nutzen. Anschließend durchlaufen wir jedes Segment, berechnen dessen Position und schreiben diesen Wert in den Scheitelpunkt.
return node;
}Am Ende der Funktion geben wir den Knoten zurück, damit der Szenengraph ihn rendern kann.
Einstiegspunkt der Anwendung
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();
}Die Anwendung ist eine einfache QML-Anwendung mit einem ` QGuiApplication ` und einem ` QQuickView `, an die wir eine .qml-Datei übergeben.
QML_ELEMENTUm das „BezierCurve“-Element nutzen zu können, müssen wir es mithilfe des Makros „ QML_ELEMENT “ in der QML-Engine registrieren. Dadurch erhält es den Namen „BezierCurve“ und wird Teil des Moduls „ CustomGeometry 1.0 “, wie in den Build-Dateien des Projekts definiert:
# 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 += targetDa die Bézier-Kurve als Linienstreifen gezeichnet wird, legen wir fest, dass die Ansicht mehrfach abgetastet werden soll, um ein Anti-Aliasing zu erzielen. Dies ist zwar nicht erforderlich, sorgt jedoch dafür, dass das Element auf Hardware, die dies unterstützt, etwas ansprechender aussieht. Multisampling ist standardmäßig nicht aktiviert, da es häufig zu einem höheren Speicherverbrauch führt.
Verwendung des Elements
import QtQuick
import CustomGeometryUnsere .qml-Datei importiert das Modul „ QtQuick 2.0 “, um die Standardtypen abzurufen, sowie unser eigenes Modul „ CustomGeometry 1.0 “, das unsere neu erstellten „BezierCurve“-Objekte enthält.
Item {
width: 300
height: 200
BezierCurve {
id: line
anchors.fill: parent
anchors.margins: 20Anschließend erstellen wir unser Root-Element und eine Instanz der „BezierCurve“, die wir so verankern, dass sie das Root-Element ausfüllt.
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)
}Um das Beispiel etwas interessanter zu gestalten, fügen wir eine Animation hinzu, um die beiden Kontrollpunkte der Kurve zu verändern. Die Endpunkte bleiben unverändert.
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")
}
}Abschließend fügen wir einen kurzen Text ein, der beschreibt, was das Beispiel veranschaulicht.
© 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.