開発のヒホ

iOSとかAndroidとかのアプリを開発するのに四苦八苦するブログ

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にコードを加えます。
こちらを参考にさせてもらいました。

GLKit ー ハローOpenGL 三角形を書いてみる

#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つ違っただけで画像がバグったり、エラーも落ちずに表示されなくなったり・・・とにかく大変です。

 次は表示する物体の数を増やしてみます。

 続き → OpenGL ES(GLKit)を使って動くエフェクトを作る(その2:花火の火花みたいなのを作る)