今までiPhoneでOpenGLアプリケーションを作るときに面倒だった要素の一つとして、テクスチャのロードの問題がありました。DirectX等はテクスチャを使いたい際に画像ファイルを指定するだけでそこから自動でテクスチャを生成してくれたりするのですが、OpenGLの場合はそういった機能は用意されていないので、iPhoneの場合も自分で画像ファイルを展開してからビットマップの配列に変換してテクスチャを生成する必要がありました。GLKitではテクスチャローダーというクラスが用意されていて、ファイルを指定するだけでテクスチャを使えるようになりました。便利。
なお、後述の通りこの部分に関しては公式ドキュメントと実際のSDKのAPIの仕様に差異があるため、不確かな情報となっています(2011.10.16現在)。十分に注意してください。
GLKTextureLoader
これがファイルからテクスチャを生成してくれるクラスです。非同期で読み込んだりMipmapの設定ができたり、いくつかメソッド/オプションがありますが、とりあえずわかりやすく同期でファイルからテクスチャをロードするには+[GLKTextureLoader textureWithContentsOfFile:options:error:]を使ってみましょう。
OpenGL GameテンプレートのsetupGLの中で読み込んでみます。わかりやすさ優先で、読み込みオプションやエラーハンドリングは今回は実装しないので、とりあえずoptionsとerrorにはnilを入れておきます。
- (void)setupGL { [EAGLContext setCurrentContext:self.context]; [self loadShaders]; self.effect = [[GLKBaseEffect alloc] init]; self.effect.light0.enabled = GL_TRUE; self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f, 1.0f); // Load texture NSString* filePath = [[NSBundle mainBundle] pathForResource:@"nantekottai256" ofType:@"png"]; GLKTextureInfo* textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:nil error:nil]; if (textureInfo) { NSLog(@"Texture loaded successfully. name = %d size = (%d x %d)", textureInfo.name, textureInfo.width, textureInfo.height); } ...
読み込んだファイル: nantekottai256.png (256 x 256)
GLKTextureInfo
GLKTextureLoaderは、読み込んだテクスチャの情報が含まれているGLKTextureInfoクラスのインスタンスを返すのですが、使ってみるとわかりますが正式リリースされたSDKと公式ドキュメントは説明されている内容に差異があります。
どちらが正式な仕様なのかわかりませんが、とりあえずSDKの実際の実装を基に説明すると、このGLKTextureInfoクラスのうち実際にこの後使うのはnameプロパティです。これは、自前でプロパティを生成したときにglGenTextures()等で割り当てたIDと同様に、OpenGL上でそのテクスチャを示すIDとして扱われ、後でGLKBaseEffectで描画時のテクスチャを指定する際にもこのプロパティを使います。なお、公式資料で言及されているわけではないので不確かな情報ですが、GLKitにはテクスチャを削除するためのクラスは特に用意されていないようなので、読み込んだテクスチャを削除する場合はglDeleteTexturesを使ってこのIDを指定して削除することになりそうです。
GLKTextureInfoには他にもテクスチャの縦横サイズを表すwidth, heightプロパティ等があります。なお、基本的にはGLKTextureLoaderを使う場合でも読み込むファイルの縦横は2のn乗のサイズになっている必要があります。
GLKBaseEffectを使ったテクスチャ描画
GLKBaseEffectにはテクスチャに関するプロパティがいくつか用意されていて、それらを使って描画する物体にテクスチャを貼付ける事ができます。しかし、ここにも公式ドキュメントと実際のAPIに差異があります。
公式ドキュメントにはtextureEnabledというプロパティがあり、これがGL_TRUEの時にテクスチャが有効になるという記述があるのですが、実際のSDKにはこのプロパティがありません。ドキュメントによると、デフォルトの値がそもそもGL_TRUEになっているようなのですが、実際このプロパティを明示的にセットしなくてもテクスチャの機能を有効にすることができます。
GLKBaseEffectでは一つの描画に対して二つまでテクスチャを設定することができるようになっています(texture2d0とtexture2d1)。それらのテクスチャの描画順番をtextureOrderプロパティで変更することもできたりするのですが、とりあえずややこしいので今回はテクスチャを一個だけ使います。
GLKEffectPropertyTexture
texture2d0とtexture2d1はGLKEffectPropertyTextureクラスのプロパティですが、read-onlyで既にGLKBaseEffectのインスタンスを作ったときにそれぞれインスタンスが設定されているので、使いたいときはこれらのインスタンスの中のプロパティを書き換える形で使います。なお、ここのプロパティ名にもドキュメントとの差異があります。
先ほどテクスチャローダで読み込んだテクスチャをセットするには、下記のようなコードを書きます。
self.effect.texture2d0.enabled = GL_TRUE; self.effect.texture2d0.name = textureInfo.name;
マッピングの設定
GLKitを使った場合でも、当然ながらテクスチャを使った描画ではマッピング情報を含めなければいけません。OpenGL Gameテンプレートの場合はgCubeVertexData配列を
// positionX, positionY, positionZ, normalX, normalY, normalZ,
と並んでいるところに
// positionX, positionY, positionZ, normalX, normalY, normalZ, textureU, textureV,
とuv座標を加えましょう。※要素の数が増えるのでgCubeVertexData配列のサイズを書き直すのを忘れずに。
最後にテクスチャマッピングのデータを転送します。またマッピング情報が増えた事により一頂点あたりのデータ量が変更されたので、頂点座標と法線ベクトルデータのストライドも24->32 (float=4byte * 8要素(座標XYZ+法線XYZ+マッピングUV))に変更します。マッピングの情報は6要素(座標XYZ+法線XYZ)の後にあるのでオフセット位置は4 * 6=24になります。転送先はGLKVertexAttribTexCoord0にします。texture2d1を使う場合はGLKVertexAttribTexCoord1を使います。
なお、OpenGL Gameテンプレートにあるもう一方のShaderを使った描画はそっちも書き換えないと要素数が変わった事によって問題がおきてしまうので、とりあえずコメントアウトしておくことをお勧めします。
glEnableVertexAttribArray(GLKVertexAttribPosition); glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 32, BUFFER_OFFSET(0)); glEnableVertexAttribArray(GLKVertexAttribNormal); glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 32, BUFFER_OFFSET(12)); glEnableVertexAttribArray(GLKVertexAttribTexCoord0); glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 32, BUFFER_OFFSET(24));
結論
上の方法で使える事は確認できたけど、公式ドキュメントとSDKの差異が早く修正されてほしいです。
「GLKitのテクスチャ」への1件のフィードバック