Qt GUI で OpenGL 描画を効率化する:QOpenGLExtraFunctions::glDrawElementsBaseVertex() の詳細解説とサンプルコード


QOpenGLExtraFunctions::glDrawElementsBaseVertex() 関数は、OpenGL ES 3.1以降で導入された新しい描画機能の一つです。この関数は、インデックス配列とベース頂点インデックスを使用して、頂点バッファ内の特定の範囲から頂点データをレンダリングすることを可能にします。従来の glDrawElements() 関数と比較して、以下の利点があります。

  • インスタンス化のサポート: ベース頂点インデックスを使用して、複数のインスタンスを効率的に描画することができます。
  • 柔軟性の向上: 特定の頂点範囲のみを描画できるので、より効率的な描画が可能になります。

関数詳細

void glDrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei basevertex);
  • basevertex: 頂点バッファ内のベース頂点インデックスを指定します。この値は、indices 配列内のインデックス値に追加されます。
  • indices: インデックスデータへのポインタを指定します。
  • type: インデックスデータの型を指定します。有効な値は、GL_UNSIGNED_BYTEGL_UNSIGNED_SHORTGL_UNSIGNED_INT などがあります。
  • count: 描画するプリミティブの数を指定します。
  • mode: 描画プリミティブの種類を指定します。有効な値は、GL_POINTSGL_LINESGL_LINE_STRIPGL_TRIANGLESGL_TRIANGLE_STRIPGL_PATCHES などがあります。

// 頂点データ
GLfloat vertices[] = {
    // ...
};

// インデックスデータ
GLuint indices[] = {
    // ...
};

// 描画
glDrawElementsBaseVertex(GL_TRIANGLES, 10, GL_UNSIGNED_INT, indices, 0);

この例では、indices 配列内のインデックス値に 0 を加えた値を使用して、頂点バッファ内の最初の 10 個の頂点から三角形を描画します。

  • この関数は、コア OpenGL API の一部ではありません。Qt フレームワークによって提供される拡張機能です。
  • QOpenGLExtraFunctions::glDrawElementsBaseVertex() 関数は、OpenGL ES 3.1以降でしか使用できません。
  • Qt は、クロスプラットフォーム GUI プログラミングフレームワークです。
  • OpenGL ES は、組み込みデバイスやモバイルデバイス向けの OpenGL の軽量版です。
  • QOpenGLExtraFunctions クラスは、OpenGL ES 3.0、3.1、3.2 API へのアクセスを提供する Qt フレームワークのクラスです。


#include <QApplication>
#include <QGLWidget>

class GLWidget : public QGLWidget {
public:
    GLWidget(QWidget *parent = 0);

protected:
    void initializeGL();
    void paintGL();

private:
    QOpenGLFunctions *functions;
    GLuint vbo;
    GLuint ibo;
    GLfloat vertices[9];
    GLuint indices[6];
};

GLWidget::GLWidget(QWidget *parent) : QGLWidget(parent) {
    functions = new QOpenGLFunctions(this);
}

void GLWidget::initializeGL() {
    // OpenGL ES 3.1以降を初期化する
    functions->initializeOpenGLFunctions();

    // 頂点データを作成する
    vertices[0] = -0.5f; vertices[1] = -0.5f; vertices[2] = 0.0f;
    vertices[3] = 0.5f; vertices[4] = -0.5f; vertices[5] = 0.0f;
    vertices[6] = 0.0f; vertices[7] = 0.5f; vertices[8] = 0.0f;

    // インデックスデータを作成する
    indices[0] = 0; indices[1] = 1; indices[2] = 2;
    indices[3] = 3; indices[4] = 4; indices[5] = 2;

    // 頂点バッファオブジェクト (VBO) を作成する
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // インデックスバッファオブジェクト (IBO) を作成する
    glGenBuffers(1, &ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // シェーダープログラムを初期化する
    // ...

    // その他の初期化を行う
    // ...
}

void GLWidget::paintGL() {
    // 描画処理を行う
    // ...

    // 三角形を描画する
    glDrawElementsBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void *)0, 0);
}

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    GLWidget widget;
    widget.show();

    return app.exec();
}

このコードは、以下の手順で動作します。

  1. initializeGL() 関数は、OpenGL ES 3.1以降を初期化し、頂点データとインデックスデータを作成し、VBO と IBO を作成し、シェーダープログラムを初期化します。
  2. paintGL() 関数は、描画処理を行い、glDrawElementsBaseVertex() 関数を使用して三角形を描画します。
  • シェーダープログラムの初期化は、このコードでは省略されています。
  • OpenGL ES 3.1以降をサポートするグラフィックカードが必要です。
  • このコードは、Qt Creator 4.15.2 と Qt 5.15.2 でコンパイルして実行することを想定しています。


QOpenGLExtraFunctions::glDrawElementsBaseVertex() 関数の代替方法として、以下の方法が考えられます。

glDrawElements() 関数を使用する

glDrawElements() 関数は、OpenGL ES 2.0以降でサポートされている従来の描画関数です。この関数は、glDrawElementsBaseVertex() 関数と同じようにインデックス配列と頂点バッファを使用して頂点データをレンダリングすることができます。

// 頂点データ
GLfloat vertices[] = {
    // ...
};

// インデックスデータ
GLuint indices[] = {
    // ...
};

// 描画
glDrawElements(GL_TRIANGLES, 10, GL_UNSIGNED_INT, indices);

この例では、indices 配列内のインデックス値を使用して、頂点バッファ内の最初の 10 個の頂点から三角形を描画します。

glDrawRangeElements() 関数を使用する

glDrawRangeElements() 関数は、OpenGL ES 3.0以降でサポートされている描画関数です。この関数は、glDrawElements() 関数と似ていますが、頂点バッファ内の特定の範囲から頂点データをレンダリングすることができます。

// 頂点データ
GLfloat vertices[] = {
    // ...
};

// インデックスデータ
GLuint indices[] = {
    // ...
};

// 描画
glDrawRangeElements(GL_TRIANGLES, 0, 9, 10, GL_UNSIGNED_INT, indices);

この例では、indices 配列内のインデックス値 0 から 9 までの値を使用して、頂点バッファ内の最初の 10 個の頂点から三角形を描画します。

頂点シェーダーを使用する

頂点シェーダーを使用して、頂点バッファ内の特定の範囲から頂点データをレンダリングすることができます。この方法は、より柔軟性と制御性がありますが、複雑さも高くなります。

// 頂点シェーダー
#version 330

layout (location = 0) in vec3 aPos;

void main() {
    gl_Position = vec4(aPos, 1.0);
}

// フラグメントシェーダー
#version 330

out vec4 FragColor;

void main() {
    FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

// 描画
glDrawElements(GL_TRIANGLES, 10, GL_UNSIGNED_INT, indices);

この例では、頂点シェーダーを使用して、頂点バッファ内の最初の 10 個の頂点から三角形を描画します。

インスタンス化を使用する

インスタンス化を使用して、複数のインスタンスを効率的に描画することができます。この方法は、複数のオブジェクトを同じ頂点データを使用してレンダリングする場合に有効です。

// 頂点データ
GLfloat vertices[] = {
    // ...
};

// インデックスデータ
GLuint indices[] = {
    // ...
};

// インスタンスデータ
GLfloat instances[] = {
    // ...
};

// 描画
glDrawElementsInstanced(GL_TRIANGLES, 10, GL_UNSIGNED_INT, indices, 10, GL_FLOAT, instances);

この例では、instances 配列内の 10 個のインスタンスに対して、indices 配列内のインデックス値を使用して頂点バッファ内の最初の 10 個の頂点から三角形を描画します。

  • 複数のインスタンスを効率的に描画する必要がある場合は、インスタンス化を使用する必要があります。
  • より柔軟性と制御性が必要な場合は、頂点シェーダーを使用する必要があります。
  • 頂点バッファ内の特定の範囲から頂点データをレンダリングする必要がある場合は、glDrawRangeElements() 関数を使用する必要があります。
  • 古いバージョンの OpenGL ES または OpenGL を使用している場合は、glDrawElements() 関数を使用する必要があります。