シーングラフ - QML 環境下での OpenGL
Qt Quick シーンの下でOpenGLをレンダリングする方法を示します。

「QML環境下でのOpenGL」サンプルでは、アプリケーションがQQuickWindow::beforeRendering()シグナルを利用して、Qt Quick シーンの下にカスタムOpenGLコンテンツを描画する方法を示しています。このシグナルは、各フレームの開始時、シーングラフがレンダリングを開始する前に発火するため、このシグナルに応答して行われるOpenGLの描画呼び出しは、すべてQt Quick アイテムの下に重ねて描画されます。
一方、Qt Quick シーンの上にOpenGLコンテンツをレンダリングしたいアプリケーションは、QQuickWindow::afterRendering()シグナルに接続することでこれを実現できます。
この例では、QML に公開された値が OpenGL レンダリングに影響を与える仕組みについても確認します。QML ファイル内の `NumberAnimation ` を使用してしきい値をアニメーション化し、この値は「スクイクル」を描画する OpenGL シェーダープログラムで使用されます。
この例は、多くの点で「Direct3D 11 Under QML」、「Metal Under QML」、および「Vulkan Under QML」の各例と同等であり、いずれも異なるネイティブAPIを経由しているだけで、同じカスタムコンテンツをレンダリングしています。
class Squircle : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged)
QML_ELEMENT
public:
Squircle();
qreal t() const { return m_t; }
void setT(qreal t);
signals:
void tChanged();
public slots:
void sync();
void cleanup();
private slots:
void handleWindowChanged(QQuickWindow *win);
private:
void releaseResources() override;
qreal m_t;
SquircleRenderer *m_renderer;
};まず、QMLに公開できるオブジェクトが必要です。これはQQuickItem のサブクラスであるため、QQuickItem::window()に簡単にアクセスできます。QML_ELEMENT マクロを使用して、これをQMLに公開します。
class SquircleRenderer : public QObject, protected QOpenGLFunctions
{
Q_OBJECT
public:
~SquircleRenderer();
void setT(qreal t) { m_t = t; }
void setViewportSize(const QSize &size) { m_viewportSize = size; }
void setWindow(QQuickWindow *window) { m_window = window; }
public slots:
void init();
void paint();
private:
QSize m_viewportSize;
qreal m_t = 0.0;
QOpenGLShaderProgram *m_program = nullptr;
QQuickWindow *m_window = nullptr;
QOpenGLBuffer m_vbo;
};次に、レンダリングを処理するオブジェクトが必要です。このインスタンスは、QQuickItem から分離する必要があります。なぜなら、アイテムは GUI スレッド上に存在しますが、レンダリングはレンダリングスレッド上で行われる可能性があるからです。QQuickWindow::beforeRendering() に接続したいので、レンダラーをQObject にします。レンダラーには、GUI スレッドとは独立して、必要なすべての状態のコピーが含まれています。
注: 2つのオブジェクトを1つに統合しようとはしないでください 。レンダリングスレッドがレンダリングを行っている最中に、GUIスレッド上でQQuickItemが削除される可能性があります。
それでは、実装に移りましょう。
Squircle::Squircle()
: m_t(0)
, m_renderer(nullptr)
{
connect(this, &QQuickItem::windowChanged, this, &Squircle::handleWindowChanged);
}Squircle クラスのコンストラクタでは、単に値を初期化し、レンダラーの準備に使用するウィンドウのchangedシグナルに接続します。
void Squircle::handleWindowChanged(QQuickWindow *win)
{
if (win) {
connect(win, &QQuickWindow::beforeSynchronizing, this, &Squircle::sync, Qt::DirectConnection);
connect(win, &QQuickWindow::sceneGraphInvalidated, this, &Squircle::cleanup, Qt::DirectConnection);ウィンドウが取得できたら、QQuickWindow::beforeSynchronizing() シグナルに接続します。このシグナルを使用して、レンダラーを作成し、状態を安全にコピーします。また、レンダラーのクリーンアップを処理するために、QQuickWindow::sceneGraphInvalidated() シグナルにも接続します。
注: SquircleオブジェクトはGUIスレッドに親和性があり、 シグナルはレンダリングスレッドから発せられるため 、Qt::DirectConnection を使用して接続を行うことが極めて重要です。そうしないと、OpenGLコンテキストが存在しない状態で、スロットが誤ったスレッドで呼び出されてしまいます。
// Ensure we start with cleared to black. The squircle's blend mode relies on this.
win->setColor(Qt::black);
}
}シーングラフのデフォルトの挙動は、レンダリングの前にフレームバッファをクリアすることです。このクリア処理がキューに入れられた後に独自のレンダリングコードを挿入するため、これ自体は問題ありません。ただし、希望の色(黒)にクリアされるようにしてください。
void Squircle::sync()
{
if (!m_renderer) {
m_renderer = new SquircleRenderer();
connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::init, Qt::DirectConnection);
connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::paint, Qt::DirectConnection);
}
m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio());
m_renderer->setT(m_t);
m_renderer->setWindow(window());
}sync() 関数を使用してレンダラーを初期化し、アイテム内の状態をレンダラーにコピーします。レンダラーが作成されたら、QQuickWindow::beforeRendering()およびQQuickWindow::beforeRenderPassRecording()を、レンダラーのinit() およびpaint() スロットに接続します。
注: QQuickWindow::beforeSynchronizing() シグナルは 、GUI スレッドがブロックされている間にレンダリングスレッド上で発火するため、追加の保護措置を講じることなく、単に値をコピーしても安全です。
void Squircle::cleanup()
{
delete m_renderer;
m_renderer = nullptr;
}
class CleanupJob : public QRunnable
{
public:
CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { }
void run() override { delete m_renderer; }
private:
SquircleRenderer *m_renderer;
};
void Squircle::releaseResources()
{
window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage);
m_renderer = nullptr;
}
SquircleRenderer::~SquircleRenderer()
{
delete m_program;
}cleanup() 関数内では、レンダラーを破棄し、それによってレンダラー自身のリソースがクリーンアップされます。これに加え、QQuickWindow::releaseResources()を再実装しています。これは、sceneGraphInvalidated()シグナルに単に接続するだけでは、すべてのケースを処理するには不十分であるためです。
void Squircle::setT(qreal t)
{
if (t == m_t)
return;
m_t = t;
emit tChanged();
if (window())
window()->update();
}t の値が変更された際、QQuickItem::update()ではなくQQuickWindow::update()を呼び出します。これは、前者の呼び出しにより、前回のフレーム以降シーングラフに変更がなかった場合でも、ウィンドウ全体が強制的に再描画されるためです。
void SquircleRenderer::init()
{
if (!m_program) {
QSGRendererInterface *rif = m_window->rendererInterface();
Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::OpenGL);
initializeOpenGLFunctions();
const float values[] = { -1, -1, 1, -1, -1, 1, 1, 1 };
m_vbo.create();
m_vbo.bind();
m_vbo.allocate(values, sizeof(values));
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
m_program = new QOpenGLShaderProgram();
m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
"attribute highp vec4 vertices;"
"varying highp vec2 coords;"
"void main() {"
" gl_Position = vertices;"
" coords = vertices.xy;"
"}");
m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
"uniform lowp float t;"
"varying highp vec2 coords;"
"void main() {"
" lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));"
" i = smoothstep(t - 0.8, t + 0.8, i);"
" i = floor(i * 20.) / 20.;"
" gl_FragColor = vec4(coords * .5 + .5, i, i);"
"}");
m_program->bindAttributeLocation("vertices", 0);
m_program->link();
}
}SquircleRendererのinit() 関数では、まだ初期化されていない場合は、まずシェーダープログラムを初期化します。スロットが呼び出された時点で、そのスレッドではOpenGLコンテキストがアクティブになっています。
void SquircleRenderer::paint()
{
// Play nice with the RHI. Not strictly needed when the scenegraph uses
// OpenGL directly.
m_window->beginExternalCommands();
m_vbo.bind();
m_program->bind();
m_program->setUniformValue("t", (float)m_t);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
glViewport(0, 0, m_viewportSize.width(), m_viewportSize.height());
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(0);
m_program->release();
m_window->endExternalCommands();
}paint() では、シェーダープログラムを使用してスクイクルを描画します。
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:///scenegraph/openglunderqml/main.qml"));
view.show();
return QGuiApplication::exec();
}アプリケーションのmain() 関数では、QQuickView をインスタンス化し、main.qml ファイルを起動します。
import QtQuick
import OpenGLUnderQML
Item {
width: 320
height: 480
Squircle {
SequentialAnimation on t {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
}main() 関数内で登録した名前で、Squircle QML型をインポートします。その後、それをインスタンス化し、そのt プロパティ上で実行中のNumberAnimation を作成します。
Rectangle {
color: Qt.rgba(1, 1, 1, 0.7)
radius: 10
border.width: 1
border.color: "white"
anchors.fill: label
anchors.margins: -10
}
Text {
id: label
color: "black"
wrapMode: Text.WordWrap
text: qsTr("The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML")
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 20
}
}さらに、Qt Quick シーンの下で実際にOpenGLによるレンダリングが行われていることが一目でわかるよう、短い説明テキストを重ねて表示します。
© 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.