前提
『マルチプラットフォームのためのOpenGL ES入門 基礎編―Android/iOS対応グラフィックスプログラミング』
の抜粋メモです。11章の内容。
コードについては断片のみなので、本書を読んでください。
読み込みごとにテクスチャを切り替える
サンプル:
// 2枚一気にテクスチャを作成する
glGenTextures(2, extension->texture_id);
assert(extension->texutre_id[0] != 0);
assert(extension->texutre_id[1] != 0);
assert(glGetError() == GL_NO_ERROR);
extension->texture_id[] に、2枚のテクスチャIDを格納し、一括で生成している。
特に難しいところはないが、glBindTexture を複数回行うので、「今テクスチャユニットにはどのテクスチャがバインドされているか」を常に意識する設計が必要。
テクスチャのフィルタ等のパラメータは テクスチャごとに設定可能。サンプルでは Mipmap, ラッピングともに別々のテクスチャを適用している。
void sample_TextureUse2_rendering(GLApplication *app) {
// サンプルアプリ用データを取り出す
Extension_TextureUse2 *extension = (Extension_TextureUse2*) app->extension;
glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// attr_posを有効にする
glEnableVertexAttribArray(extension->attr_pos);
glEnableVertexAttribArray(extension->attr_uv);
// unif_textureへテクスチャを設定する
glUniform1i(extension->unif_texture, 0);
// このブロックはカリングを含めて正しい順番で頂点が定義されていることをチェックします
#if 0 /* カリングチェック */
{
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
}
#endif /* カリングチェック */
{
// 左のポリゴンを描画
{
const GLfloat position[] = {
// triangle 0
// v0(left top)
-0.95f, 0.75f,
// v1(left bottom)
-0.95f, -0.75f,
// v2(right top)
-0.05f, 0.75f,
// v3(right bottom)
-0.05f, -0.75f, };
// 0.0〜1.0の範囲を設定
const GLfloat uv[] = {
// v0(left top)
0.0f, 0.0f,
// v1(left bottom)
0.0f, 1.0f,
// v2(right top)
1.0f, 0.0f,
// v3(right bottom)
1.0f, 1.0f, };
glVertexAttribPointer(extension->attr_uv, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) uv);
glVertexAttribPointer(extension->attr_pos, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) position);
// テクスチャを関連付ける
glBindTexture(GL_TEXTURE_2D, extension->texture_id[0]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
// 右へポリゴンを描画
{
const GLfloat position[] = {
// v0(left top)
0.05f, 0.75f,
// v1(left bottom)
0.05f, -0.75f,
// v2(right top)
0.95f, 0.75f,
// v3(right bottom)
0.95f, -0.75f, };
// -1.0f〜2.0fの範囲を設定
const GLfloat uv[] = {
// v0(left top)
-1.0f, -1.0f,
// v1(left bottom)
-1.0f, 2.0f,
// v2(right top)
2.0f, -1.0f,
// v3(right bottom)
2.0f, 2.0f, };
glVertexAttribPointer(extension->attr_uv, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) uv);
glVertexAttribPointer(extension->attr_pos, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) position);
// テクスチャを関連付ける
glBindTexture(GL_TEXTURE_2D, extension->texture_id[1]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
// バックバッファをフロントバッファへ転送する。プラットフォームごとに内部の実装が異なる。
ES20_postFrontBuffer(app);
}
glUniform1i を利用し、描画時にテクスチャユニット0番のテクスチャを参照させるように設定し、その「0番テクスチャユニットにバインドされているテクスチャを切り替えることで描画時に利用されるテクスチャを切り替え」ている。
テクスチャ読み込み用補助関数
typedef struct {
/** 画像幅 */
int width;
/** 画像高さ */
int height;
/** GL 側のテクスチャ ID */
GLuint id;
} Texture;
Texture* Texture_load(GLApplicatoin* app, const char* file_name, const int pixel_format)
void Texture_free(Texture* texture)
描画ごとに異なるテクスチャユニットを利用する
OpenGL ES 2.0 は、テクスチャユニットを最低でも8個以上備えている。次のサンプル(sample_load_texture_2unit.c)では、0番と1番のユニットにバインドすることで、glBindtexture の呼び出しを減らしている。
// 頂点シェーダーを用意する
{
const GLchar *vertex_shader_source =
//
"attribute mediump vec4 attr_pos;"
"attribute mediump vec2 attr_uv;"
"varying mediump vec2 vary_uv;"
"void main() {"
" gl_Position = attr_pos;"
" vary_uv = attr_uv;"
"}";
const GLchar *fragment_shader_source =
//
"uniform sampler2D texture;"
"varying mediump vec2 vary_uv;"
"void main() {"
" gl_FragColor = texture2D(texture, vary_uv);"
"}";
// コンパイルとリンクを行う
extension->shader_program = Shader_createProgramFromSource(vertex_shader_source, fragment_shader_source);
}
// attributeを取り出す
{
extension->attr_pos = glGetAttribLocation(extension->shader_program, "attr_pos");
assert(extension->attr_pos >= 0);
extension->attr_uv = glGetAttribLocation(extension->shader_program, "attr_uv");
assert(extension->attr_uv >= 0);
}
// uniformを取り出す
{
extension->unif_texture = glGetUniformLocation(extension->shader_program, "texture");
assert(extension->unif_texture >= 0);
}
// GPUが搭載しているテクスチャユニット数を調べる
{
GLint textureUnits = 0;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &textureUnits);
__logf("GPU Texture Units %d", textureUnits);
}
{
// テクスチャをそれぞれ読み込む
{
extension->textures[0] = Texture_load(app, "texture_rgb_512x512.png", TEXTURE_RAW_RGBA8);
assert(extension->textures[0] != NULL);
}
{
extension->textures[1] = Texture_load(app, "texture_rgb_256x256.png", TEXTURE_RAW_RGB8);
assert(extension->textures[1] != NULL);
}
// テクスチャを0番ユニットにバインドする
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, extension->textures[0]->id);
// テクスチャを1番ユニットにバインドする
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, extension->textures[1]->id);
}
// シェーダーの利用を開始する
glUseProgram(extension->shader_program);
assert(glGetError() == GL_NO_ERROR);
glActiveTexture
glActiveTexture コマンドは、Context が操作対象とするテクスチャユニットを切り替える。
void glActiveTexture(GLenum texture)
- texture : テクスチャユニット番号(GL_TEXTURE0, GL_TEXTURE1,,,GL_TEXTURE32)
glBindTexture や glTexParameteri 等のテクスチャに関するコマンドは、 glActiveTexture コマンドによって指定されたテクスチャユニットに対して、もしくはバインドされているテクスチャに対して操作が行われる。
GPU ごとにいくつのテクスチャユニットが搭載されているかは、GL_MAX_TEXTURE_IMAGE_UNITS を問い合わせることで調べられる。
GLint textureUnit = 0;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &textureUnits);
描画直前に glUniform1i コマンドを呼び出し、sampler2D 型の変数に目的のテクスチャが何番のテクスチャユニットにバインドされているかを教えている。
// サンプルアプリ用データを取り出す
Extension_2UnitTexture *extension = (Extension_2UnitTexture*) app->extension;
glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// attr_posを有効にする
glEnableVertexAttribArray(extension->attr_pos);
glEnableVertexAttribArray(extension->attr_uv);
// 四角形描画
{
// -1.0f〜2.0fの範囲を設定
const GLfloat uv[] = {
// v0(left top)
-1.0f, -1.0f,
// v1(left bottom)
-1.0f, 2.0f,
// v2(right top)
2.0f, -1.0f,
// v3(right bottom)
2.0f, 2.0f, };
glVertexAttribPointer(extension->attr_uv, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) uv);
// 左のポリゴンを描画
{
const GLfloat position[] = {
// v0(left top)
-0.95f, 0.75f,
// v1(left bottom)
-0.95f, -0.75f,
// v2(right top)
-0.05f, 0.75f,
// v3(right bottom)
-0.05f, -0.75f, };
glVertexAttribPointer(extension->attr_pos, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) position);
// unif_textureへ0番ユニットを設定する
glUniform1i(extension->unif_texture, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
// 右のポリゴンを描画
{
const GLfloat position[] = {
// v0(left top)
0.05f, 0.75f,
// v1(left bottom)
0.05f, -0.75f,
// v2(right top)
0.95f, 0.75f,
// v3(right bottom)
0.95f, -0.75f, };
glVertexAttribPointer(extension->attr_pos, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*) position);
// unif_textureへ1番ユニットを設定する
glUniform1i(extension->unif_texture, 1);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
// バックバッファをフロントバッファへ転送する。プラットフォームごとに内部の実装が異なる。
ES20_postFrontBuffer(app);
同時に複数のテクスチャユニットを利用する
複数のユニットを利用する一番のメリットは、 複数のテクスチャの合成をリアルタイムに簡単に行えること。
たとえばサンプル(sample_load_texture_channels.c)では、写真と白黒の マスク画像 を合成し、マスクの形に描画を行っている。
// 頂点シェーダ
const GLchar *vertex_shader_source =
//
"attribute mediump vec4 attr_pos;"
"attribute mediump vec2 attr_uv;"
"varying mediump vec2 vary_uv;"
"void main() {"
" gl_Position = attr_pos;"
" vary_uv = attr_uv;"
"}";
// 型抜きフラグメントシェーダ
const GLchar *fragment_shader_source =
//
"uniform sampler2D tex_color;"
"uniform sampler2D tex_mask;"
"varying mediump vec2 vary_uv;"
"void main() {"
" if(texture2D(tex_mask, vary_uv).r == 0.0) {"
" discard;"
" }"
" gl_FragColor = texture2D(tex_color, vary_uv);"
"}";
GLSL文法:ifキーワード
tex_color が色情報のテクスチャで、tex_mask がマスクのテクスチャ。if の意味は見たまんま。
ベクトル型の特殊なアクセス
if (texture2D(tex_mask, vary_uv).r == 0.0) {
discard;
}
tex_mask のピクセルを読み取り r 要素をチェック。texture2D の戻り値は テクセルの色 であり、その型は vec4 であり、vec4 は xyzw のメンバを持つが、ここでは r でアクセスしている。
OpenGL ES のベクトル型は、シェーダー記述を用意にするために様々な便宜が図られており、その一つが 要素名の別名アクセスになる。
vec4の場合、
- x, y, z, w (ベクトル要素)
- r, g, b, a (色アクセス向け別名)
- s, t, p, q (テクスチャアクセス向け別名)
- [0], [1], [2], [3] (配列アクセス向け別名)
なので、ここでは r でも x でも s や [0] でアクセスしても同じ要素を見ている。
GLSL文法:discard 命令
GLSL ES の特殊鋼分の一つ。関数や式ではなく、discard: と記述するだけ。
サンプルでは「ピクセルの色が黒かった場合」に discard 命令を発行している。これは 描画されないピクセル であり、これが実行されると「このピクセルは不要である」ことが GPU に伝えられる。命令を受けた GPU はフラグメントシェーダの実行を中断し、そのピクセルの描画をキャンセルする。最終的にそのピクセルは描画されずに ポリゴンに穴を開ける ことができる。