Pynote

Python、機械学習、画像処理について

Qt - OpenGL の GLSL で描画する。 その1 シェーダーをコンパイルする。

試した環境

  • Qt 5.10

資料

手順

シェーダーを作成し、リソースに追加する。

vertex シェーダーと fragment シェーダーをそれぞれ作成し、リソースに追加する。

resources.qrc

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file>fragment.glsl</file>
    <file>vertex.glsl</file>
</qresource>
</RCC>

vertex.glsl

#version 400

in vec3 VertexPosition;

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

fragment.glsl

#version 400

void main(){
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

シェーダーのソースコードをファイルから読み込む。

ソースコードの文字列からシェーダーオブジェクトを作成する関数 compileShaderFromString() 及びファイルからソースコードを読み込み、シェーダーオブジェクトを作成する関数 createShaderFromFile() を作成する。
そして、initializeGL() 内でファイル及びシェーダーの種類を指定して呼ぶ。

void MyOpenGLWidget::initializeGL()
{
    ...
    // シェーダーのソースコードを読み込む。
    GLuint vertexShaderHandle =
        createShaderFromFile(GL_VERTEX_SHADER, ":/vertex.glsl");
    GLuint fragmentShaderHandle =
        createShaderFromFile(GL_FRAGMENT_SHADER, ":/fragment.glsl");
    ...
}
/**
 * @brief ファイルからソースコードを読み込み、シェーダーをコンパイルする。
 * @param shaderType シェーダーの種類
 * @param source ソースコード
 * @return シェーダーオブジェクトのハンドラ
 */
GLuint
MyOpenGLWidget::createShaderFromFile(GLenum shaderType, const QString &filePath)
{
    // ファイルを開く。
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly))
        exit(1);

    // ファイルからソースコードを読み込む。
    QTextStream in(&file);
    QString source = in.readAll();

    return createShaderFromString(shaderType, source);
}
/**
 * @brief ソースコードからシェーダーをコンパイルする。
 * @param shaderType シェーダーの種類
 * @param source ソースコード
 * @return シェーダーオブジェクトのハンドラ
 */
GLuint MyOpenGLWidget::createShaderFromString(
    GLenum shaderType, const QString &source)
{
    ...
}

シェーダーオブジェクトを作成する。

glCreateShader() でシェーダーオブジェクトを作成する。

GLuint glCreateShader(GLenum shaderType)
  • 引数
    • shaderType: 作成するシェーダーの種類
  • 返り値
    • 作成したシェーダーオブジェクトのハンドラ
    • 成功した場合は 0 以外の整数、失敗した場合は 0
// シェーダーオブジェクトを作成する。
GLuint shaderHandle = glCreateShader(shaderType);
if (shaderHandle == 0) {
    std::cerr << "failed to create the shader object." << std::endl;
    exit(1);
}

シェーダーオブジェクトにソースコードを設定する。

glCreateShader() でシェーダーオブジェクトにソースコードを設定する。

void glShaderSource(
    GLuint shader, GLsizei count, const GLchar **string, const GLint *length)
  • 引数
    • shader: シェーダーオブジェクトのハンドラ
    • count: 渡す文字列の数
    • string: 文字列の一覧。
    • length: 各文字列の長さの一覧
      • NULL を指定した場合、すべての文字列は \0 で終わることを仮定する。
      • \0 で終わる文字列は 0 以下の値、そうでない場合は文字列の長さ (\0 を含めない) を指定する。

ソースコードの指定で文字列の配列を渡すようになっているのは、ソースコードが複数の文字列に分かれている場合に対応するためである。

// 1つの文字列から成るソースコード
const GLchar *source = "shader code";
glShaderSource(shaderHandle, 1, &source, NULL);
// 複数の文字列から成るソースコード
const GLchar *sources[] = {"part of shader code", "part of shader code"};
glShaderSource(
    shaderHandle, sizeof(sources) / sizeof(*sources), sources, NULL);
// シェーダーオブジェクトにソースコードを設定する。
const GLchar *sources[] = {qPrintable(source)};
glShaderSource(shaderHandle, 1, sources, NULL);

ソースコードコンパイルする。

glCompileShader() でシェーダーオブジェクトに設定されているソースコードコンパイルする。

void glCompileShader(GLuint shader)
  • 引数
    • shader: シェーダーオブジェクトのハンドラ
// ソースコードをコンパイルする。
glCompileShader(shaderHandle);

コンパイルに成功したかどうかはシェーダーオブジェクトのステータスに設定される。
glGetShaderiv() でこのステータスを取得できる。

void glGetShaderiv(GLuint shader, GLenum pname, GLint *params)
  • 引数
    • shader: シェーダーオブジェクトのハンドラ
    • pname: 取得するパラメータの種類
    • params: 取得するパラメータの値を格納するバッファ

コンパイルに成功したかどうかのステータスは pname 引数に GL_COMPILE_STATUS を指定することで取得できる。
result にコンパイルに成功している場合 GL_TRUE、失敗している場合 GL_FALSE が入っている。

// コンパイルに成功したかどうかを取得する。
GLint result;
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &result);

コンパイルに失敗した場合、詳しい情報がシェーダーオブジェクトのログにあるかもしれない。
glGetShaderiv() で pname 引数に GL_INFO_LOG_LENGTH でログの長さを取得することで、ログがあるかどうかを確認できる。
ログがない場合は長さ0を返す。

ログがある場合、glGetShaderInfoLog() で取得できる。

void glGetShaderInfoLog(
    GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog)
  • 引数
    • shader: シェーダーオブジェクトのハンドラ
    • maxLength: バッファの長さ
    • length: 取得するログの長さ (\0 を含めない) を格納するバッファ
    • infoLog: 取得するログを格納するバッファ
// コンパイルに失敗した場合
if (result == GL_FALSE) {
    std::cerr << "failed to compile the shader object." << std::endl;

    // ログの長さを取得する。
    GLint logLen;
    glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLen);

    // ログがある場合
    if (logLen > 0) {
        // ログを取得し、表示する。
        char *log = new char[logLen];
        glGetShaderInfoLog(shaderHandle, logLen, NULL, log);
        std::cout << log << std::endl;
        delete log;
    }
    exit(1);
}