OpenGLウィンドウの例

この例では、OpenGLを使用する目的で、最小限のQWindow ベースのアプリケーションを作成する方法を示します。

Screenshot of the OpenGLWindow example

注意: これはOpenGLでQWindow を使用する方法の低レベルの例です。実際には、より高レベルのQOpenGLWindowQOpenGLWindow コンビニエンスクラスのデモについてはHello GLES3 Exampleを参照してください。

OpenGLWindowスーパークラス

OpenGLWindowクラスは実際のレンダリングを行うためにサブクラス化されるAPIとして機能します。renderNow()で即座に、またはrenderLater()でイベントループが現在のイベントバッチの処理を終えるとすぐに、render()の呼び出しを要求する関数があります。OpenGLWindowのサブクラスは、OpenGLベースのレンダリングのためにrender()を再実装するか、QPainter を使用してレンダリングするためにrender(QPainter *)を使用することができます。垂直同期が基礎となるOpenGLドライバで有効になっていると仮定して、垂直リフレッシュレートで呼び出されるrender()のためにOpenGLWindow::setAnimating(true)を使用します。

OpenGLレンダリングを行うクラスでは、OpenGL ES 2.0関数へのプラットフォームに依存しないアクセスを得るために、OpenGLWindowのようにQOpenGLFunctionsQOpenGLFunctions から継承することで、それが含むOpenGL関数が優先され、アプリケーションをOpenGL ES 2.0だけでなくOpenGLでも動作させたい場合、それらの関数の解決を心配する必要がなくなります。

class OpenGLWindow : public QWindow, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit OpenGLWindow(QWindow *parent = nullptr);
    ~OpenGLWindow();

    virtual void render(QPainter *painter);
    virtual void render();

    virtual void initialize();

    void setAnimating(bool animating);

public slots:
    void renderLater();
    void renderNow();

protected:
    bool event(QEvent *event) override;

    void exposeEvent(QExposeEvent *event) override;

private:
    bool m_animating = false;

    QOpenGLContext *m_context = nullptr;
    QOpenGLPaintDevice *m_device = nullptr;
};

ウィンドウの表面タイプは、QBackingStore を使用して、QPainter でラスターコンテンツをレンダリングするためではなく、OpenGLレンダリングのために使用されることを示すために、QSurface::OpenGLSurface に設定する必要があります。

OpenGLWindow::OpenGLWindow(QWindow *parent)
    : QWindow(parent)
{
    setSurfaceType(QWindow::OpenGLSurface);
}

必要なOpenGL初期化は、render()を最初に呼び出す前に1回呼び出されるinitialize()関数を、有効な現在のQOpenGLContextQPainter 一方、デフォルトの render() 実装は、QOpenGLPaintDevice を初期化してから render(QPainter *) を呼び出す。

void OpenGLWindow::render(QPainter *painter)
{
    Q_UNUSED(painter);
}

void OpenGLWindow::initialize()
{
}

void OpenGLWindow::render()
{
    if (!m_device)
        m_device = new QOpenGLPaintDevice;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    m_device->setSize(size() * devicePixelRatio());
    m_device->setDevicePixelRatio(devicePixelRatio());

    QPainter painter(m_device);
    render(&painter);
}

renderLater()関数は、単純にQWindow::requestUpdate()を呼び出して、システ ムが再描画する準備ができたときの更新をスケジュールする。

また、exposeイベントが発生したときにもrenderNow()を呼び出します。exposeEvent()は、画面上でのウィンドウの露出、つまり可視性が変更されたことをウィンドウに通知するものです。exposeイベントを受信すると、QWindow::isExposed ()に問い合わせ、ウィンドウが現在露出しているかどうかを調べることができます。ウィンドウが最初のexposeイベントを受信する前に、ウィンドウにレンダリングしたり、QOpenGLContext::swapBuffers()を呼び出したりしないでください。

void OpenGLWindow::renderLater()
{
    requestUpdate();
}

bool OpenGLWindow::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::UpdateRequest:
        renderNow();
        return true;
    default:
        return QWindow::event(event);
    }
}

void OpenGLWindow::exposeEvent(QExposeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
        renderNow();
}

renderNow()では、現在exposeされていない場合、exposeイベントが発生するまでレンダリングが遅延されます。まだそうしていない場合は、OpenGLWindowに設定されたのと同じQSurfaceFormatQOpenGLContext を作成し、サブクラスのためにinitialize()を呼び出し、QOpenGLFunctions スーパークラスが正しいQOpenGLContext に関連付けられるようにinitializeOpenGLFunctions()を呼び出します。いずれにしても、QOpenGLContext::makeCurrent ()を呼び出してコンテキストを現在の状態にし、render()を呼び出して実際のレンダリングを行い、最後にOpenGLWindowをパラメータとしてQOpenGLContext::swapBuffers ()を呼び出してレンダリングされたコンテンツが表示されるようにスケジュールします。

OpenGLコンテキストを使用したフレームのレンダリングが、レンダリングするサーフェスをパラメータとして指定してQOpenGLContext::makeCurrent ()を呼び出すことで開始されると、OpenGLコマンドを発行できるようになります。コマンドは、システムのOpenGLヘッダーも含む<qopengl.h>をインクルードして直接発行するか、QOpenGLFunctions を使用して発行することができます。 は、利便性のために継承するか、QOpenGLContext::functions() を使用してアクセスすることができます。QOpenGLFunctions は、OpenGL ES 2.0とデスクトップOpenGLの両方でまだ標準ではないすべてのOpenGL ES 2.0レベルのOpenGLコールにアクセスできます。OpenGLとOpenGL ES APIの詳細については、公式のOpenGL Registryと Khronos OpenGL ES API Registryを参照してください。

アニメーションがOpenGLWindow::setAnimating(true)で有効になっている場合、renderLater()を呼び出して、別の更新要求をスケジュールします。

void OpenGLWindow::renderNow()
{
    if (!isExposed())
        return;

    bool needsInitialize = false;

    if (!m_context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();

        needsInitialize = true;
    }

    m_context->makeCurrent(this);

    if (needsInitialize) {
        initializeOpenGLFunctions();
        initialize();
    }

    render();

    m_context->swapBuffers(this);

    if (m_animating)
        renderLater();
}

アニメーションを有効にすると、次のコード・スニペットに示すように、更新要求もスケジュールされます。

void OpenGLWindow::setAnimating(bool animating)
{
    m_animating = animating;

    if (animating)
        renderLater();
}

OpenGLレンダリングのサブクラスの例

ここでは、回転する三角形をOpenGLでレンダリングする方法を示すために、OpenGLWindowをサブクラスにしています。QOpenGLFunctions を間接的にサブクラス化することで、すべてのOpenGL ES 2.0レベルの機能にアクセスできるようになります。

class TriangleWindow : public OpenGLWindow
{
public:
    using OpenGLWindow::OpenGLWindow;

    void initialize() override;
    void render() override;

private:
    GLint m_matrixUniform = 0;
    QOpenGLBuffer m_vbo;
    QOpenGLShaderProgram *m_program = nullptr;
    int m_frame = 0;
};

メイン関数では、QGuiApplication を初期化し、TriangleOpenGLWindowをインスタンス化します。デフォルトのジオメトリと同様に、4サンプルのマルチサンプル・アンチエイリアシングを指定するQSurfaceFormat 。アニメーションを使用したいので、上記のsetAnimating()関数をtrueの引数で呼び出します。

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

    QSurfaceFormat format;
    format.setSamples(16);

    TriangleWindow window;
    window.setFormat(format);
    window.resize(640, 480);
    window.show();

    window.setAnimating(true);

    return app.exec();
}

次のコード・スニペットは、この例で使用したOpenGLシェーダ・プログラムです。頂点シェーダーとフラグメント・シェーダーは比較的単純で、頂点変換と補間された頂点カラーリングを行います。

static const char *vertexShaderSource = "attribute highp vec4 posAttr;\n"
                                        "attribute lowp vec4 colAttr;\n"
                                        "varying lowp vec4 col;\n"
                                        "uniform highp mat4 matrix;\n"
                                        "void main() {\n"
                                        "   col = colAttr;\n"
                                        "   gl_Position = matrix * posAttr;\n"
                                        "}\n";

static const char *fragmentShaderSource = "varying lowp vec4 col;\n"
                                          "void main() {\n"
                                          "   gl_FragColor = col;\n"
                                          "}\n";

以下は、シェーダーをロードし、シェーダープログラムを初期化するコードです。生のOpenGLの代わりにQOpenGLShaderProgram を使用することで、デスクトップOpenGLのhighp、mediump、lowpの修飾子を取り除いた利便性が得られます。アトリビュートとユニフォームの位置をメンバ変数に格納し、フレームごとに位置のルックアップを行う必要がないようにしています。

void TriangleWindow::initialize()
{
    static const GLfloat vertices_colors[] = { +0.0f, +0.707f, 1.0f, 0.0f, 0.0f,
                                               -0.5f, -0.500f, 0.0f, 1.0f, 0.0f,
                                               +0.5f, -0.500f, 0.0f, 0.0f, 1.0f };

    m_vbo.create();
    m_vbo.bind();
    m_vbo.allocate(vertices_colors, sizeof(vertices_colors));

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), nullptr);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat),
                          reinterpret_cast<void *>(2 * sizeof(GLfloat)));

    m_program = new QOpenGLShaderProgram(this);
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    m_program->bindAttributeLocation("posAttr", 0);
    m_program->bindAttributeLocation("colAttr", 1);
    m_program->link();
    m_program->bind();

    m_matrixUniform = m_program->uniformLocation("matrix");
    Q_ASSERT(m_matrixUniform != -1);
}

最後に、render()関数を紹介します。OpenGLを使ってビューポートを設定し、背景を消去して、回転する三角形をレンダリングします。

void TriangleWindow::render()
{
    const qreal retinaScale = devicePixelRatio();
    glViewport(0, 0, width() * retinaScale, height() * retinaScale);

    glClear(GL_COLOR_BUFFER_BIT);

    m_program->bind();

    QMatrix4x4 matrix;
    matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
    matrix.translate(0, 0, -2);
    matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0);

    m_program->setUniformValue(m_matrixUniform, matrix);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    m_program->release();

    ++m_frame;
}

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

©2024 The Qt Company Ltd. ここに含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。