Objective-CでOpenGL事始め


vImageやらCGImageやら画像処理系は色々見てきましたが、やはり最終的にはOpenGLで色々やるのがよさそう、ということでツラツラと勉強したことをメモしていきます。

いずれはしっかりシェーダとか書いてみようと思います。
ということで、まずはGLKitを使って画面に三角形を描く手順から。

*ちなみに、基本的な考え方やフローはWebGLも同様です。CPU（プログラム）とGPU（シェーダ）との橋渡しを書く、というのが大きな流れでしょう。
ちょっと混乱するかもしれませんが、[WebGLの流れ](http://qiita.com/edo_m18/items/0ae86d3d680c30eb57c1)を以前投稿しているのでよかったらそちらと比較してみてください。*

---------------------------------------------

##大まかな流れ

OpenGLはとにかく手続きが多いです。
データを用意して使うにしてもいちいち状態を切り替えながら作業を進めていく必要があります。
これもひとえに、CPUとGPUとのデータの受け渡しが発生するから、と認識しています。
ひとつずつ処理を追っていくとすぐ頭が混乱してしまうので、おおまかに流れをまとめてから詳細に入ります。

###手順

1. バッファの生成（IDが割り振られる）
2. 生成したバッファをバインド（(1)で割り振られたIDを指定）
3. データの転送
4. ----------------------------------------------
5. シェーダ内変数にインデックスを割り当て
6. シェーダ変数を有効化
7. ----------------------------------------------
8. バッファをバインド（(1)で割り振られたIDを指定）
9. OpenGL側にバッファのポインタを通知
10. ----------------------------------------------
11. レンダリング

途中の線は意味のまとまりです。
バッファの生成、バインド、通知、転送などはCPUからGPUにデータを送る必要があるためとてもめんどくさいことになっています。
CPUだけなら変数宣言してそれを使っていけばいいのですが、この橋渡しがあるためにIDやらインデックスやらという、いわゆる「識別子」的なやり方でやりとりしていくわけです。

###手順（関数版）

さて、上記手順を実際の関数ではどうしているか、を示します。

1. `glGenBuffers(GLsizei n, GLuint *buffers)`
2. `glBindBuffer(GLenum target, GLuint buffer)`
3. `glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage)`
4. ----------------------------------------------
5. `glEnableVertexAttribArray(GLuint index)`
6. `glBindAttribLocation(GLuint program, GLuint index, const GLchar *name)`
7. ----------------------------------------------
8. `glBindBuffer(...)`
9. `glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr)`
10. ----------------------------------------------
11. `glDrawArrays(GLenum mode, GLint first, GLsizei count)`

[参考](http://www57.atwiki.jp/gametips/pages/29.html)

---------------------------------------------

##GLKViewを準備する

まず、OpenGLを利用する場合は`CAEAGLLayer`というレイヤーにレンダリングすることになります。それを内包している特別なビューが`GLKView`というわけです。
なので、OpenGLによるレンダリング先として`GLKView`を指定します。

*ちなみに、`CAEAGLLayer`ってなんの略なんだろうと思ってたんですが、[こちらの記事](http://katsuren.hatenablog.com/entry/2012/01/07/030821)には「`Embedded Apple GL`の略」と書いてありました（CAはCore Animation）。何の略か覚えておくとスペル忘れても思い出しやすいのでオススメです。*

###サンプルコード

```objc
// ViewController.h

@interface SampleViewController : UIViewController <GLKViewDelegate>
{
	// 頂点バッファのインデックス保持用インスタンス変数。
	// インデックス番号なので`GL uint`型
	GLuint vertexBufferID;
}

// ViewController.m

typeof struct {
	GLKVector3 position;
} Vertex;

static const Vertex vertices[] = {
	{{ 0.0, -0.5, 0.0}},
	{{ 0.5, -0.5, 0.0}},
	{{-0.5,  0.5, 0.0}},
};

- (void)viewDidLoad
{
	self.glview = [[GLKView alloc] initWithFrame:self.view.bounds];

	// OpenGL ES 2を使う
	self.glview.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    [self.view addSubview:self.glview];
    self.glview.delegate = self;


	// 生成したコンテキストをカレントコンテキストに設定
	[EAGLContext setCurrentContext:self.glview.context];

	// BaseEffectを生成
	self.baseEffect = [[GLKBaseEffect alloc] init];
	self.baseEffect.useConstantColor = GL_TRUE;
	self.baseEffect.constantColor    = GLKVector4Make(1.0, 1.0, 1.0, 1.0); // 白
	// self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeZRotation(M_PI);
	self.baseEffect.transform.projectionMatrix = GLKMatrix4MakeScale(1.0, self.glview.bounds.size.width / self.glview.bounds.size.height, 1.0); // アスペクト比を維持
	
	// クリアカラーを黒に
	// glClear関数に`GL_COLOR_BUFFER_BIT`が立てられている場合に、この設定の色で塗りつぶされる
	glClearColor(0.0, 0.0, 0.0, 1.0);

	// GPUに情報を渡すためのバッファを生成する
	// GenはGenerateの「Gen」
	glGenBuffers(1, &vertexBufferID);

	// 生成したバッファをバインド
	glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);

	// バインドしたバッファにデータを転送
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	// バッファのバインドを解除しておく
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}
```

---------------------------------------------

##GLKViewDelegateを実装する

レンダリング時、`glkView:drawInRect:`デリゲートメソッドにメッセージが送信されます。ここでレンダリングを実行することで、`CAEAGLLayer`にレンダリングされます。

```objc
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
	// レンダリング前の準備
	[self.baseEffect prepareToDraw];

	// 画面をクリア
	glClear(GL_COLOR_BUFFER_BIT);

	// 頂点位置情報を有効化
	glEnableVertexAttribArray(GLKVertexAttribPosition);

	// バッファをバインド
	glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);

	// 頂点位置情報としてバッファの位置を指定 ※1
	glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);

	// 図形を描く
	glDrawArrays(GL_TRIANGLES, 0, 3);
}
```

**※1 ... 補足（関数プロトタイプ）**

```objc
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr);
```
| 引数 | 型 | 説明 |
|--------|------|--------|
| index | GLuint | （シェーダ内の）attribute変数に割り当てられたインデックス番号 |
| size  | GLint | データの1頂点あたりの要素の数（サンプルでは三角形ポリゴンの頂点位置なので`3`） |
| type  | GLenum | 格納されているデータ型 |
| normalized | GLboolean | normalize（正規化）するかどうかのフラグ |
| stride | GLsizei | データの間隔（ひとつの配列に複数のデータを埋め込む場合があるため、その場合は登録したいデータが何bytes間隔で格納されているかを指定する） |
| *ptr | GLvoid | 頂点データ（バッファ）のポインタを指定。サンプルでは頂点位置情報しかないのでオフセットとして0を指定している。※2（ここは曖昧なので要調査） |

**※2 ... ポインタ（バッファ）について**
データを示すポインタ（バッファ）は今回は位置情報だけですが、色などいくつかの情報をひとつにまとめて提供することも可能です。
その場合に、「頂点の位置データはここから開始する」という情報を渡します。（なので今回は0）
・・だったはず。ちょっと曖昧です( ;´Д`)

---------------------------------------------

##GLKBaseEffect

AppleのClass Referenceを見ると

>The GLKBaseEffect class provides shaders that mimic many of the behaviors provided by the OpenGL ES 1.1 lighting and shading model, including materials, lighting and texturing. The base effect allows up to three lights and two textures to be applied to a scene.

と書かれています。要はOpenGL ES 1.1のライティングやシェーダ、テクスチャを模倣した仕組みを提供してくれているようです。
OpenGL ES 2では、 **本来はシェーダを書かなければならない部分を簡略化してくれるもの**、ということでしょう。
そのため、頂点情報などは自前で定義するのではなく、`glEnableVertexAttribArray(GLKVertexAttribPosition);`と、固定のようです。

###modelviewMatrix

いわゆるモデル座標空間のマトリクス。
GLKBaseEffectには`transform`プロパティがあるので、これにマトリクスを設定することで座標変換を行うことが出来ます。

```objc
baseEffect.transform = GLKMatrix4MakeZRotation(M_PI); // Z軸に対して180度回転
```

###projectionMatrix

射影変換行列。いわゆるカメラの見え方などを定義する行列です。
これを設定しないと縦長の画面ではポリゴンが縦に引き伸ばされてしまいます。
見え方を整えるために、この行列を指定する必要があります。
アスペクト比を保つのであれば以下のようにします。

```objc
// 縦の比率を`self.view.bounds`を元に計算する
baseEffect.transform.projectionMatrix = GLKMatrix4MakeScale(1.0, self.view.bounds.size.width / self.view.bounds.size.height, 1.0);
```