vImageやらCGImageやら画像処理系は色々見てきましたが、やはり最終的にはOpenGLで色々やるのがよさそう、ということでツラツラと勉強したことをメモしていきます。
いずれはしっかりシェーダとか書いてみようと思います。
ということで、まずはGLKitを使って画面に三角形を描く手順から。
ちなみに、基本的な考え方やフローはWebGLも同様です。CPU(プログラム)とGPU(シェーダ)との橋渡しを書く、というのが大きな流れでしょう。
ちょっと混乱するかもしれませんが、WebGLの流れを以前投稿しているのでよかったらそちらと比較してみてください。
大まかな流れ
OpenGLはとにかく手続きが多いです。
データを用意して使うにしてもいちいち状態を切り替えながら作業を進めていく必要があります。
これもひとえに、CPUとGPUとのデータの受け渡しが発生するから、と認識しています。
ひとつずつ処理を追っていくとすぐ頭が混乱してしまうので、おおまかに流れをまとめてから詳細に入ります。
手順
- バッファの生成(IDが割り振られる)
- 生成したバッファをバインド((1)で割り振られたIDを指定)
- データの転送
- ----------------------------------------------
- シェーダ内変数にインデックスを割り当て
- シェーダ変数を有効化
- ----------------------------------------------
- バッファをバインド((1)で割り振られたIDを指定)
- OpenGL側にバッファのポインタを通知
- ----------------------------------------------
- レンダリング
途中の線は意味のまとまりです。
バッファの生成、バインド、通知、転送などはCPUからGPUにデータを送る必要があるためとてもめんどくさいことになっています。
CPUだけなら変数宣言してそれを使っていけばいいのですが、この橋渡しがあるためにIDやらインデックスやらという、いわゆる「識別子」的なやり方でやりとりしていくわけです。
手順(関数版)
さて、上記手順を実際の関数ではどうしているか、を示します。
glGenBuffers(GLsizei n, GLuint *buffers)
glBindBuffer(GLenum target, GLuint buffer)
glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage)
- ----------------------------------------------
glEnableVertexAttribArray(GLuint index)
glBindAttribLocation(GLuint program, GLuint index, const GLchar *name)
- ----------------------------------------------
glBindBuffer(...)
glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr)
- ----------------------------------------------
glDrawArrays(GLenum mode, GLint first, GLsizei count)
GLKViewを準備する
まず、OpenGLを利用する場合はCAEAGLLayer
というレイヤーにレンダリングすることになります。それを内包している特別なビューがGLKView
というわけです。
なので、OpenGLによるレンダリング先としてGLKView
を指定します。
ちなみに、CAEAGLLayer
ってなんの略なんだろうと思ってたんですが、こちらの記事には「Embedded Apple GL
の略」と書いてありました(CAはCore Animation)。何の略か覚えておくとスペル忘れても思い出しやすいのでオススメです。
サンプルコード
// 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
にレンダリングされます。
- (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 ... 補足(関数プロトタイプ)
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
プロパティがあるので、これにマトリクスを設定することで座標変換を行うことが出来ます。
baseEffect.transform = GLKMatrix4MakeZRotation(M_PI); // Z軸に対して180度回転
projectionMatrix
射影変換行列。いわゆるカメラの見え方などを定義する行列です。
これを設定しないと縦長の画面ではポリゴンが縦に引き伸ばされてしまいます。
見え方を整えるために、この行列を指定する必要があります。
アスペクト比を保つのであれば以下のようにします。
// 縦の比率を`self.view.bounds`を元に計算する
baseEffect.transform.projectionMatrix = GLKMatrix4MakeScale(1.0, self.view.bounds.size.width / self.view.bounds.size.height, 1.0);