ついにiOS5がリリースされました。iOS5で新たに加わった機能は沢山ありますが、OpenGL周りではGLKitフレームワークが新たに加わり、プロジェクトテンプレートもGLKitを使ったものに変更されたので、GLKitに慣れる第一歩として早速そのプロジェクトテンプレートを解読していきたいと思います。
プロジェクト構成
OpenGL Gameテンプレートは主に下記のようなファイル/クラスから構成されています。
- AppDelegate
- ViewController
- Shader.fsh
- Shader.vsh
それぞれの中身について詳しく見ていきましょう。
AppDelegate (*AppDelegate.h / *AppDelegate.m)
よくあるアプリケーションデリゲートのクラスです。デフォルトの状態では、アプリケーション起動時に呼ばれるapplication:didFinishLaunchingWithOptions:メソッドのみ中身が実装されており、その中でViewControllerのインスタンス生成が行われています。
特にこのクラスの中身は重要ではありません。
ViewController (*ViewController.h / *ViewController.m)
OpenGLに関する実装の肝となるクラスです。GLKViewControllerを継承しています。シェーダー自体の実装はShader.fsh, Shader.vshに記述されていますが、それ以外の殆どの処理がこのクラスに記述されています。
GLKViewController
GLKViewControllerはOpenGLを使った画面描画を行うのに必要な基本的な機能をあらかじめ備えているビューコントローラで、これを使うと大分面倒な処理の実装を省略できます。ランループの機能も備えていて、指定したfpsで画面をアップデートするように実装することができます。テンプレートのプロジェクトの場合、delegateが指定されていないので、フレームのアップデート時には自身のupdateメソッドが呼ばれます。
初期化
ViewControllerクラスではビューがロードされたタイミングで呼ばれるviewDidLoadメソッドがオーバーライドされています。iOS4のOpenGL ES ApplicationのテンプレートではES1, ES2の両方の生成コードが記述されていましたが、iOS5のテンプレートではES2のみのコードだけが記述されています。ES1も使おうと思えば使えますが、やはり時代はES2なのですね。
メインとなるOpenGL関連の初期化の処理は、viewDidLoadの中からさらに呼ばれるsetupGLメソッドに実装されています。setupGL内の初期化の流れをおおまかに説明すると、
- シェーダーの準備(loadShadersメソッドに実装)
- エフェクトの準備
- VBOの準備
という流れになっています。
エフェクト(GLKBaseEffect)の基礎
ES2はES1に比べるとかなり機能が違っていて、基本的に互換性がない部分も多く、ES1で今までゲームを作ってきた人には取っ付きにくい部分があります。GLKBaseEffectはシェーダーを自分で書く事なく、ライティング、表面素材の設定等をES1.1で実装していた頃と「にたような感覚で」実現させてくれるクラスです。すばらしい!
テンプレートのアプリケーションを起動すると、画面上に二つのオブジェクトが描画されるのですが、このうちの片方はGLKBaseEffectを使って描画しており、もう片方は通常のシェーダーを使って描画されています。
ViewControllerにはeffectという名前のエフェクトのインスタンスが一つ用意されています。サンプルではsetupGLメソッド内でインスタンスが生成され、ライティングの設定が行われています。
self.effect = [[GLKBaseEffect alloc] init]; self.effect.light0.enabled = GL_TRUE; self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f, 1.0f);
視点の設定やテクスチャの設定もエフェクトを使って行えます。それは描画のタイミングでまた見ていきます。
更新
前述の通り、GLKViewControllerはランループの構造を備えていて画面を更新するタイミングでコード内の所定のメソッドが呼び出されます。このメソッドにはdelegateを使って実装するパターンと、ViewControllerの方に実装するパターンの二つがありますが、テンプレートのプログラムでは後者の形で実装されています。
ViewControllerにはupdateメソッドがあり、この中で適宜オブジェクトやカメラの移動などを行いアニメーションを実現します。GLKViewControllerにはtimeSinceLastUpdateというプロパティがあって、前回画面を更新してからの経過時間(NSTimeInterval型)を取得することができます。
サンプルプロジェクトでは設定されていませんが、GLKViewControllerにはpreferredFramesPerSecondというプロパティがあり、指定したFPS(を目安)で更新のループを回してもらうようにすることも可能です。
エフェクト(視点の設定)
updateメソッド内では再びエフェクトが登場しています。ここでは、視点の設定が行われています。ES1系でおなじみのProjection, Modelviewのマトリクスをそれぞれ設定することができます。
self.effect.transform.projectionMatrix = projectionMatrix; self.effect.transform.modelviewMatrix = modelViewMatrix;
視野/視体積の設定
iOSではGLU/GLUTが使えないのでES1で視野の設定をする時にはglFrustumf等を使って設定をする必要があり、直感的ではなかったのですが、GLKitには視野を設定するための便利なメソッドも用意されています。サンプルでは視野角が65度になるような設定を作っています。
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f);
平行移動/回転/拡大縮小
また、glTranslatef, glRotatef, glScalefに該当する関数もGLKitに用意されていて、それらが使われています。残念ながらglPushMatrix, glPopMatrixに該当するような仕組みはないです。
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -1.5f); modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, _rotation, 1.0f, 1.0f, 1.0f); modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix);
画面の描画
実際の描画の処理はglkView:drawInRect:メソッドの中で実装されています。エフェクトを使っての描画と、エフェクトを使わない描画の二つのコードが記述されています。
エフェクトを使う場合は、そのエフェクトを有効にしたい範囲の直前でprepareToDrawメソッドを呼び出してエフェクトを有効にしています。
[self.effect prepareToDraw]; glDrawArrays(GL_TRIANGLES, 0, 36);
Shader.fsh / Shader.vsh
この二つのファイルはシェーダーですが、今回は特に重要ではないので省略します。ちなみに、エフェクトを使って描画をする場合はこれらのファイルは不要になります。
結論
プロジェクトテンプレートはGLKitを使って描画するコードと使わないで描画するコードが両方記述されているため、若干長くて読みにくい印象を受けてしまうのですが、GLKitを使っている部分だけみていくと、かなりシンプルでわかりやすいコードになっていることがわかります。特にES1系を使ってきたユーザには、非常に馴染みやすい実装になっていると思いますし、新しくGLの勉強をはじめるユーザにとっても大きくハードルが下がるものだと思うので、是非GLKitを活用していきたいですね。