今回はテクスチャを張ってみたいと思います。また単に画面に三角形ではなく、立方体を出してみます。そのために今回は GLM というライブラリを使用することにしました。これは行列演算のために使用しています。
GLM
GLMを用いて、World, View, Projection 行列を生成するコードは次のようになります。
glm::mat4 proj = glm::perspective( 30.0f, 640.0f/480.0f, 1.0f, 500.0f ); glm::mat4 view = glm::lookAt ( glm::vec3(0.0,-5.0f,15.0f), glm::vec3(0.0f,0.0f,0.0f), glm::vec3(0.0f,1.0f,0.0f) ); glm::mat4 world= glm::rotate( glm::mat4(1.0f), (float)angle, glm::vec3(0.0f,1.0f,0.0f) ); pvw = proj * view * world;
mat4(1.0) という初期化で単位行列となるようです。上記のコードは使い方さえわかれば問題ないと思います。
これらの乗算結果である pvw 行列は Uniform Block としてシェーダーに転送してやります。
立方体の準備
Vertex Buffer Object(VBO)を用意して、頂点バッファとインデックスバッファを準備します。テクスチャも張るためUV座標も含めて、以下のようにデータを用意しています。
Vertex cubeVertices[] = { { -1.0f, 1.0f, 1.0f, 0xFFFFFFFFu, 0.0f, 0.0f }, { -1.0f,-1.0f, 1.0f, 0xFF0000FFu, 0.0f, 1.0f }, { +1.0f, 1.0f, 1.0f, 0xFFFF0000u, 1.0f, 0.0f }, { +1.0f,-1.0f, 1.0f, 0xFFFF00FFu, 1.0f, 1.0f }, { +1.0f, 1.0f, 1.0f, 0xFFFFFFFFu, 0.0f, 0.0f }, { +1.0f,-1.0f, 1.0f, 0xFFFFFFFFu, 0.0f, 1.0f }, { +1.0f, 1.0f,-1.0f, 0xFFFFFFFFu, 1.0f, 0.0f }, { +1.0f,-1.0f,-1.0f, 0xFFFFFFFFu, 1.0f, 1.0f }, { -1.0f, 1.0f,-1.0f, 0xFF008000u, 0.0f, 0.0f }, { -1.0f,-1.0f,-1.0f, 0xFF808000u, 0.0f, 1.0f }, { -1.0f, 1.0f, 1.0f, 0xFF008000u, 1.0f, 0.0f }, { -1.0f,-1.0f, 1.0f, 0xFF808000u, 1.0f, 1.0f }, { +1.0f, 1.0f, -1.0f, 0xFFFF0000u, 0.0f, 0.0f }, { +1.0f,-1.0f, -1.0f, 0xFFFF0000u, 0.0f, 1.0f }, { -1.0f, 1.0f, -1.0f, 0xFFFF0000u, 1.0f, 0.0f }, { -1.0f,-1.0f, -1.0f, 0xFFFF0000u, 1.0f, 1.0f }, { -1.0f, 1.0f, -1.0f, 0xFF8080F0u, 0.0f, 0.0f }, { -1.0f, 1.0f, 1.0f, 0xFF8080F0u, 0.0f, 1.0f }, { 1.0f, 1.0f, -1.0f, 0xFF8080F0u, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 0xFF8080F0u, 1.0f, 1.0f }, {-1.0f, -1.0f, 1.0f, 0xFF00FFFFu, 0.0f, 0.0f }, {-1.0f, -1.0f,-1.0f, 0xFF00FFFFu, 0.0f, 1.0f }, { 1.0f, -1.0f, 1.0f, 0xFF00FFFFu, 1.0f, 0.0f }, { 1.0f, -1.0f,-1.0f, 0xFF00FFFFu, 1.0f, 1.0f }, }; uint16_t cubeIndices[] = { 0, 1, 2, 2,1,3, 4, 5, 6, 6,5,7, 8, 9,10, 10,9,11, 12, 13, 14, 14,13,15, 16, 17, 18, 18, 17, 19, 20, 21, 22, 22, 21, 23, };
描画するときには、VAOと用意したインデックスバッファをバインドして描画を実行します。
glBindVertexArray( vao ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo ); // ibo = インデックスバッファとしてのVertexBufferObject // テクスチャ、サンプラのバインド (後述) // 描画 glDrawElements( ... );
テクスチャとサンプラオブジェクト
以前からテクスチャオブジェクトはありましたが、最近のOpenGLではさらにサンプラーオブジェクトが追加されています。これは従来のテクスチャオブジェクトに含まれていた拡大縮小フィルタの設定やラッピングモードの指定が分離されたものとなっているようです。この設定を指定したテクスチャ番号にセットすることで有効化するようです。
サンプラーオブジェクトの生成&値のセットはこんな感じです。
glGenSamplers( 1, &sampler ); glSamplerParameteri( sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glSamplerParameteri( sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glSamplerParameteri( sampler, GL_TEXTURE_WRAP_S, GL_REPEAT ); glSamplerParameteri( sampler, GL_TEXTURE_WRAP_T, GL_REPEAT );
このサンプラーオブジェクトの設定について、バインドしなくても値の設定ができるところが驚きました。いい方向に変わりましたね。
使うときにはこんな感じでセットします。
glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, tex ); glBindSampler( 0, sampler );
シェーダーコード
シェーダーコード(GLSL)の方は単にテクスチャを使うときと変更なしです。
// vertex shader #version 140 in vec4 in_position; in vec4 in_color; in vec2 in_uv0; out vec4 vsout_col; out vec2 vsout_uv0; uniform VertexBlock { mat4 pvw; vec4 col; }; void main( void ) { gl_Position = pvw * in_position; vsout_col = in_color * col; vsout_uv0 = in_uv0; } // fragment shader #version 140 in vec4 vsout_col; in vec2 vsout_uv0; out vec4 out_color0; uniform sampler2D tex0; void main(void) { out_color0 = texture2D( tex0, vsout_uv0 ) * vsout_col; }
シェーダーの作成時(ShaderProgram を glCreateProgram直後)では、テクスチャ番号0を使うという意味で、tex0 には 0 を指定しています。
例: glUniform1i( glGetUniformLocation( program, “tex0” ), 0 );
結果
テクスチャを張って描画してみた図が以下のようになります。
まとめ
ここまでのテクスチャ付きポリゴン描画までの流れをまとめてみるとこのような流れで近年のOpenGLは描画するということになるとおもいます。
- VertexBufferObject(VBO)を作成して、頂点バッファ&インデックスバッファを用意する
- VertexArrayObject(VAO)を作成して頂点バッファのデータ形式をセットアップする
- テクスチャオブジェクトとサンプラーオブジェクトを用意する
- シェーダープログラムを用意する。このときin/outを使用して、attributeやvarying修飾は使用しない.
- 描画の前で、シェーダーをセットしてVAOやサンプラーオブジェクト、テクスチャオブジェクトをバインドする
- 描画命令の実行
ずいぶんとDirectX 10/11世代の描画手順と並んできた、似通ってきたような印象を受けました。
(DirectX11はかじり程度しか知らないのですが・・・)