씬 그래프 - 사용자 정의 지오메트리
Qt Quick 의 씬 그래프에서 사용자 정의 지오메트리를 구현하는 방법을 보여줍니다.
이 사용자 정의 지오메트리 예제는 씬 그래프 API를 사용하여 씬 그래프용 사용자 정의 지오메트리를 구축하는 ‘ QQuickItem ’를 만드는 방법을 보여줍니다. 이를 위해 ‘ BezierCurve ’ 항목을 생성하고, 이를 ‘CustomGeometry’ 모듈의 일부로 만든 다음 QML 파일에서 이를 활용합니다.

베지어 곡선 선언
#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 ` 클래스를 상속받으며, 다섯 가지 속성을 추가합니다. 베지어 곡선의 네 제어점 각각에 대한 속성과, 곡선이 세분화되는 세그먼트 수를 제어하는 매개변수가 포함됩니다. 각 속성에 대해 대응하는 게터 및 세터 함수가 있습니다. 이러한 속성은 QML에서 바인딩될 수 있으므로, 변경 사항이 QML 엔진에 반영되어 적절히 사용될 수 있도록 각 속성에 대한 알림 신호를 제공하는 것이 바람직합니다.
QML 장면과 렌더링 장면 그래프 간의 동기화 지점은 ` QQuickItem::updatePaintNode()` 가상 함수이며, 사용자 정의 장면 그래프 로직을 가진 모든 항목은 이 함수를 구현해야 합니다.
참고: 많은 하드웨어 구성에서 씬그래프는 별도의 스레드에서 렌더링됩니다. 따라서 씬 그래프와의 상호작용은 통제된 방식으로, 무엇보다도 QQuickItem::updatePaintNode() 함수를 통해 이루어져야 합니다.
베지어 곡선 구현
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 생성자의 첫 번째 인자는 “속성 세트(attribute set)”라고 불리는 정점 유형의 정의입니다. QML에서 자주 사용되는 그래픽은 몇 가지 일반적인 표준 속성 집합을 중심으로 이루어지기 때문에, 이러한 속성 집합들은 기본적으로 제공됩니다. 여기서는 x 좌표와 y 좌표를 각각 나타내는 두 개의 부동 소수점 수를 갖는 Point2D 속성 집합을 사용합니다. 두 번째 인수는 정점 개수입니다.
사용자 정의 속성 집합도 생성할 수 있지만, 이 예제에서는 다루지 않습니다.
지오메트리 메모리 관리에 대한 특별한 요구 사항이 없으므로, ` 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);지오메트리를 채우려면 먼저 지오메트리에서 정점 배열을 추출합니다. 기본 속성 세트 중 하나를 사용하고 있으므로, 편의 함수인 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();
}이 애플리케이션은 간단한 QML 애플리케이션으로, ` QGuiApplication `와 ` QQuickView `를 통해 .qml 파일을 전달합니다.
QML_ELEMENTBezierCurve 항목을 사용하려면 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)
}예제를 좀 더 흥미롭게 만들기 위해 곡선의 두 제어점을 변경하는 애니메이션을 추가합니다. 끝점은 변경되지 않습니다.
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")
}
}마지막으로 예제가 보여주는 내용을 간략히 설명하는 짧은 텍스트를 겹쳐 표시합니다.
© 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.