OpenGL ES 2.0 その02

1日1HaloWars状態のMonshoです。

さすがに慣れてきたのでヒロイックでも余裕です。

そろそろいろいろ縛りプレーでもしようかな。

GLES2.0プログラムその02はシェーダコンパイルです。

なぜいきなりシェーダなのかというと、GLES2.0には固定シェーダがないからです。

単色トライアングルを出すだけでもシェーダを書かなければいけません。

GLES2.0のシェーダは頂点シェーダとフラグメントシェーダに分かれています。

頂点シェーダはそのままの意味で、フラグメントシェーダはピクセルシェーダのことです。

ピクセルシェーダといった方がわかりやすいかもしれませんが、ここではフラグメントシェーダと言うことにしましょう。

今回使用するシェーダプログラムは以下の通りです。

// 頂点シェーダコード

attribute highp vec4 myVertex;

uniform mediump mat4 myPMVMatrix;

void main(void)

{

    gl_Position = myPMVMatrix * myVertex;

}

// フラグメントシェーダコード

void main (void)

{

    gl_FragColor = vec4(1.0, 1.0, 0.66 ,1.0);

}

簡単に説明しましょう。

頂点シェーダは入力頂点座標 myVertex を入力行列 myPMVMatrix で変換しています。

これを gl_Position に出力していますが、この変数は出力頂点座標となります。ここからポリゴンが画面のどこに描画されるか計算するわけです。

フラグメントシェーダは gl_FragColor に定数カラーを出力しています。実際に画面に出てくる色がこの変数です。

このコードは外部のテキストファイルに書き込んでおいてもかまいません。最終的にプログラム内部で文字列としてメモリに格納されていればOKです。

テキストファイルを読み込む場合、文字列の最後が '' で終わるようにしておきましょう。当たり前のことですが、結構忘れやすいです。>忘れたやつがここにいるしね

これらは文字列としてメモリに格納されている状態とします。ここからシェーダプログラムを作成します。

まず、各シェーダコードをコンパイルします。

コンパイルは頂点シェーダもフラグメントシェーダも同じ要領で行えます。なので、ここでは頂点シェーダをコンパイルするコードのみ提示します。

const char* pVertexCode;  // ここにシェーダコードが入っていると思いねぇ

GLuint hShader;

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

hShader = glCreateShader( GL_VERTEX_SHADER );

// シェーダコードを読み込む

// シェーダオブジェクト、コードの数、シェーダコード、シェーダコードの長さの配列

glShaderSource( hShader, 1, &pVertexCode, NULL );

// シェーダコードをコンパイルする

glCompileShader( hShader );

順番に見ていきましょう。

シェーダコードは pVertexCode に入っているものと考えてください。

まず、glCreateShader() でシェーダを作成し、そのハンドルを取得します。

引数はシェーダタイプで、GL_VERTEX_SHADER なら頂点シェーダ、GL_FRAGMENT_SHADER ならフラグメントシェーダです。フラグメントシェーダをコンパイルする場合、この引数を変えるだけでOKです。

次に glShaderSource() でシェーダコードを登録します。複数のシェーダコードを読み込むこともできるらしいのですが、ハンドルは1つでシェーダコードは複数ってどういう状況なんでしょう?

普通はハンドル1つにシェーダコード1つでしょうし、複数のシェーダを読み込むことはないと思います。なので、第2引数は 1 でOKです。

第3引数はシェーダコード。文字列の先頭アドレスではなく、格納場所のポインタなのは複数のシェーダを読み込むためです。

第4引数はシェーダコードの文字数ですが、'' で終わっているなら必要ありません。終わっていないならやはり文字数のポインタを指定します。

で、glCompileShader()コンパイルします。が、この関数は戻り値が void なのでうまくいったのかどうかはここではわかりません。

というか、OpenGLのほとんどの関数は戻り値が void なので、エラーは別途関数を用いて取得する必要があります。

大半の関数のエラーは glGetError() で取得できるのですが、シェーダコンパイルについてはエラーメッセージもあるのでそんなに単純ではありません。以下がコンパイルエラーの検出方法です。

// コンパイルが成功しているかどうか調べる

GLint bShaderCompiled;

glGetShaderiv( hShader, GL_COMPILE_STATUS, &bShaderCompiled );

if( !bShaderCompiled )

{

    // コンパイルエラー

    // エラーログのメッセージ長を取得

    int  nLogLength, nCharsWritten;

    glGetShaderiv( hShader, GL_INFO_LOG_LENGTH, &nLogLength );

    // エラーログを取得

    char* pInfoLog = new char[ nLogLength ];

    glGetShaderInfoLog( hShader, nLogLength, &nCharsWritten, pInfoLog );

    // エラーログ出力

    printf( "%s

", pInfoLog );

    delete pInfoLog;

}

glGetShaderiv() は指定のシェーダハンドルに関するいろいろな情報を取得できる関数です。

最初に GL_COMPILE_STATUS を指定しています。これでコンパイルが成功しているかどうかを取得します。値が 0 ならコンパイルは失敗です。

次に GL_INFO_LOG_LENGTH でエラーログの文字列の長さを取得しています。この長さは文字列の終端も含めた文字数が入ってきます。

この文字数でエラーログの格納領域を作成したら glGetShaderInfoLog() でエラーログを文字列として取得できます。適当に表示しましょう。

第3引数で実際に書き込まれたログの文字数が取得できるのですが、あまり必要なものとは思えませんね。

このようにして頂点シェーダとフラグメントシェーダをコンパイルすることに成功したら、今度は2つのシェーダをリンクして一つのプログラムを作成します。

GLuint hProgram;

// シェーダプログラムを作成する

hProgram = glCreateProgram();

// フラグメント、頂点シェーダをアタッチする

glAttachShader( hProgram, hFragment );

glAttachShader( hProgram, hVertex );

// プログラムをリンクする

glLinkProgram( hProgram );

// リンクに成功したかどうか調べる

GLint bLinked;

glGetProgramiv( hProgram, GL_LINK_STATUS, &bLinked );

if( !bLinked )

{

    // リンクエラー

    // エラーログのメッセージ長を取得

    int  nLogLength, nCharsWritten;

    glGetProgramiv( hProgram, GL_INFO_LOG_LENGTH, &nLogLength );

    // エラーログを取得

    char* pInfoLog = new char[ nLogLength ];

    glGetProgramInfoLog( hProgram, nLogLength, &nCharsWritten, pInfoLog );

    // エラーログ出力

    printf( "%s

", pInfoLog );

    delete pInfoLog;

}

まず、glCreateProgram() でシェーダプログラムを作成します。

次に、glAttachShader() を使って頂点シェーダとフラグメントシェーダをアタッチします。

glLinkProgram() でリンクすれば終了です。簡単ですね。

こちらもシェーダコンパイルと同様にエラーログを取得できます。やり方もシェーダコンパイルとほぼ同じです。関数名とかがちょっと違うだけですね。

作成したプログラムを実際に使用するには以下の関数を呼び出せばOKです。

glUseProgram( hProgram );

これでプログラムが使用できるようになります。

と、このようにそれほど難しい流れではないので、適当に関数化なりクラス化しておくと楽でしょう。

次回はやっとポリゴン描画に行けます。トライアングルを出すだけですけどね。