OpenGL ES(GLKit)を使って動くエフェクトを作る(その1:GLKViewを使ってみる)
iOS5になって、新たにGLKitという便利なものが追加されました。
従来ではOpenGLを使うために数十行書かなければいけなかったのを、アップルさんがうまいことまとめてくれたのを作ってくれました!ありがてぇ!
最近はアプリも飽和状態ですし、エフェクトを拘ってみたりしないと脚光を浴びることもありません。
これからはOpenGLでゴリゴリとアニメーションを書く時代が来る・・・ハズ!
ということで、GLKitを使って何かアニメーションを作ってみたいと思います。
最終目標は、ゲームのクリア画面とかでよくある、花火がドーンドーンと打ち上がってるあれを作ってみます。
必要なframeworkを追加する
必要なframeworkがいくつかあります。
projectファイルのSummry辺りからちゃちゃっと追加します。
- GLKit.framework
- OpenGLES.framework
- QuarzCore.framework
簡単に説明しますと、
GLKit : iOS5から追加。Objective-Cに対応してOpenGLを便利に使えるようになる
OpenGLES : OpenGLが使えるようになる
QuarzCore : CAEAGLLayerが使えるようになる(後述)
てな感じですかね。
GLKViewのサブクラスを作る
さっそく作っていきます。
まずはGLKViewのサブクラスです、普通にファイルを追加しちゃってください。
今回は花火ということで、FIreWorksViewクラスを作ります。 あとあとの事も考えて、FIreWorksView.mの拡張子をFIreWorksView.mmと変更しておきます。
何か描画してみる
最初に、表示したいとこにaddSubViewします。意外と忘れます。
続いてFIreWorksView.mmにコードを加えます。
こちらを参考にさせてもらいました。
#import "FireworksView.h" #import <QuartzCore/QuartzCore.h> @interface FireworksView() { GLuint vertexBufferID; GLuint colorBufferID; } @property (strong, nonatomic) GLKBaseEffect *baseEffect; @end @implementation FireworksView typedef struct { GLKVector3 position; } Vertex; // 三角の座標 static const Vertex vertices[] = { {{-0.5f, -0.5f, 0.0}}, {{ 0.5f, -0.5f, 0.0}}, {{0.2f, 0.2f, 0.0}}, {{-0.5f, 0.5f, 0.0}}, }; - (id)init { self = [super init] ; [self initView] ; return self ; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame] ; [self initView] ; return self ; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder] ; [self initView] ; return self ; } - (void)initView { // OpenGL ES2を指定 self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; // set context [EAGLContext setCurrentContext:self.context]; // 設定 CAEAGLLayer* layer = (CAEAGLLayer*)self.layer ; layer.opaque = YES; layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: @(FALSE), kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; // 三角形に白を設定 self.baseEffect = [[GLKBaseEffect alloc] init]; self.baseEffect.useConstantColor = GL_TRUE; self.baseEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f); // 透明部分(背景)を黒に glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // GPUに点の情報を送る glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); } - (void)drawRect:(CGRect)rect { // おまじない [self.baseEffect prepareToDraw]; // おまじない glClear(GL_COLOR_BUFFER_BIT); // 点の情報をbind glEnableVertexAttribArray(GLKVertexAttribPosition); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID) ; glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL); // 描画 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } @end
1つ1つ説明していくと・・・
// OpenGL ES2を指定 self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; // set context [EAGLContext setCurrentContext:self.context];
contextの初期化ですね。 contextが何なのかはそのうちわかります、たぶん。
OpenGL ES2の他にOpenGL ES1というのがあって、iPod touch 2辺りではES2のアプリは動かないのですが、まぁ放っておいてもいいでしょう。
その下のsetCurrentContext
ってのは、これもまたおまじないで。
// 設定 CAEAGLLayer* layer = (CAEAGLLayer*)self.layer ; layer.opaque = YES; layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: @(FALSE), kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
GLKViewのlayer、CAEAGLLayer
の初期化です。
透明を可能にして、1ピクセル辺りRGBAの4Byteで値を持つように指定してます。
// 三角形に白を設定 self.baseEffect = [[GLKBaseEffect alloc] init]; self.baseEffect.useConstantColor = GL_TRUE; self.baseEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f);
GLKBaseEffect
というのがミソで、GLKitでは、OpenGLに設定しなくちゃいけないいろんな事をこいつが引き受けてくれます。
描画の直前にprepareToDraw
を呼び出せば、そのGLKBaseEffect
に設定されていた内容が適応されるみたいです。
// 透明部分(背景)を黒に glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // GPUに点の情報を送る glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
背景を黒にするのは見たまんまですが、後半がちょっと特殊なんですね。
OpenGLでは、描画を高速化するために、GPUに変数を登録します。アクセスが速くなるんでしょう、きっと。
glGenBuffers
でbufferIDを作っておいて、'glBindBuffer'で登録したいもののタイプを指定、glBufferData
で実際に登録します。
後で値を引き出したり、再登録したりするときにbufferIDが必要になります。
verticesは、ファイルの上の方で書かれている頂点の位置データ
// 三角の座標 static const Vertex vertices[] = { {{-0.5f, -0.5f, 0.0}}, {{ 0.5f, -0.5f, 0.0}}, {{0.2f, 0.2f, 0.0}}, {{-0.5f, 0.5f, 0.0}}, };
を指しているのですが、普通にポインタを指定しているだけですね。他の引数はなんとなく解るかと思います。
わからない人はC言語の基礎本を読むか、まる暗記で!
// おまじない [self.baseEffect prepareToDraw]; // おまじない glClear(GL_COLOR_BUFFER_BIT);
[self.baseEffect prepareToDraw]
で事前に登録したのを引っ張りだしてくれます。便利。
glClear
はおまじない。
// 点の情報をbind glEnableVertexAttribArray(GLKVertexAttribPosition); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID) ; glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL); // 描画 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
先ほどGPUに登録しておいた値を引っ張りだして、描画命令に渡します。
glEnableVertexAttribArray
で渡す種類のデータを渡せるようにします。
glBindBuffer
で先ほどGPUに登録しておいた頂点の位置データを描画命令に渡しておいて、
glVertexAttribPointer
でそのデータが何なのかを指定します。
全部の情報が揃ったらglDrawArrays
で描画します。
初めはふーんって感じでいいと思います。
色を変える
色を変えてみます。
さっきは頂点の位置データのみをGPUに渡していましたが、今度はこれに色のデータも渡してみます。
// 三角の座標 static const Vertex vertices[] = { // 略 }; // R, G, B, A の順番 static const GLfloat colors[] = { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; // 略 // GPUに点の情報を送る // 略 // GPUに色の情報を送る glGenBuffers(1, &colorBufferID); glBindBuffer(GL_ARRAY_BUFFER, colorBufferID); glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW); // 略 // 点の情報をbind // 略 // 色の情報をbind glEnableVertexAttribArray(GLKVertexAttribColor); glBindBuffer(GL_ARRAY_BUFFER, colorBufferID) ; glVertexAttribPointer(GLKVertexAttribColor, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL) ; // 描画 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
『登録+描画の際に呼び出し』が基本の手順です。
GLKVertexAttribColor
として情報を渡しているため、色の情報として扱ってくれます。
最後に
慣れるまでちょっと大変です。いろいろいじってみるといいと思いますが、一気に2つ以上書き換えるのはオススメできません。
何が原因で描画しなくなっちゃったのか、全く検討がつかないためです。特にこのOpenGLの分野は、数字が1つ違っただけで画像がバグったり、エラーも落ちずに表示されなくなったり・・・とにかく大変です。
次は表示する物体の数を増やしてみます。