OpenGL ES 2.0で波紋を作る#5 格子状の板ポリゴンを作る
この章では、波紋の元になる格子状の板ポリゴンの作成について解説します。
ポイント
・板ポリゴン
・縮退三角形
板ポリゴン
ポリゴンとは、立体的な物体を表現するための多角形のことを言います。
OpenGLは三角形を利用して多角形を作成します。四角形は三角形を2つ組み合わせ、五角形は3つ組み合わせることで作成できます。
OpenGLのポリゴンも同様に、複数の三角形を組み合わせることで多角形を作成し、立体を表現します。この三角形を組み合わせて、2Dのポリゴンを作成します。ここでは、この2Dポリゴンを「板ポリゴン」と呼びます。
今回、板ポリゴンは以下の設計で作成します。
・1つの四角形を細かく分割する
・分割した四角形から、三角形を作るための頂点を算出する。
・頂点をベースに三角形を作成する
・一つ一つの三角形に色を塗る
板ポリゴン作成
前の章で作成したrepercussionsプロジェクトに対して、以下のファイルを修正します。
・Shader.vsh
・Shader.fsh
・GameViewController.m
変更箇所について解説します。
変更箇所は、背景を反転させています。
Shader.vsh
1 2 3 4 5 6 7 8 9 |
attribute vec4 attr_pos; attribute vec4 attr_color; varying lowp vec4 vary_color; void main() { gl_Position = attr_pos; vary_color = attr_color; } |
Shader.vshファイルは、頂点シェーダです。絵を描くための処理を記載します。
頂点情報は attribute で宣言した変数を利用します。attributeを宣言した attr_pos は、外部から頂点情報を設定することができます。
varying で宣言した vary_color は、頂点シェーダからフラグメントシェーダに情報を連携するための変数です。この後に呼ばれるフラグメントシェーダに色情報を連携します。
gl_Position は、OpenGLの変数です。頂点シェーダから頂点情報を受け取ります。
Shader.fsh
1 2 3 4 5 |
varying lowp vec4 vary_color; void main() { gl_FragColor = vec4(abs(vary_color.x+vary_color.x), abs(vary_color.y+vary_color.y), 1.0, 1.0); } |
Shader.fshファイルは、フラグメントシェーダです。色を塗るための処理を記載します。
フラグメントシェーダでは、頂点情報のように attribute を利用することが出来ません。
そのため、varying にて宣言した vary_color を利用し、頂点シェーダから色情報を受け取ります。
GameViewController.m
「OpenGL ES を組み込むベースを作成」 で説明した修正の必要なメソッドと宣言部について解説します。
以下の修正以外は、そのままです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#import "GameViewController.h" #import typedef struct { // X値 GLfloat x; // Y値 GLfloat y; // Z値 GLfloat z; // U値 GLfloat u; // V値 GLfloat v; } VertexData; // 分割数 static const int kDivCount = 120; // 頂点数 = Y頂点数 * X頂点数 static const int kVertexCount = (kDivCount+1) * (kDivCount+1); // 頂点インデックス数 = Y座標のインデックス数 * X座標のインデックス数 static const int kIndexCount = (kDivCount) * (3+(((kDivCount+1)-2)*2)+3); @interface GameViewController () { // レンダリング用プログラムシェーダー GLuint _program; // 頂点バッファ GLuint vertices_buffer; // インデックスバッファ GLuint indices_buffer; } @property (strong, nonatomic) EAGLContext *context; @property (strong, nonatomic) GLKBaseEffect *effect; - (void)setupGL; - (void)tearDownGL; - (BOOL)loadShaders; - (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file; - (BOOL)linkProgram:(GLuint)prog; - (BOOL)validateProgram:(GLuint)prog; @end |
クラスの宣言部では、波紋の作成に必要な構造体の宣言、定数、インスタンス変数を定義します。
VertexData は、頂点情報を格納するための構造体です。
kDivCount、kVertexCount、kIndexCount は、バッファ作成や描画時に利用するための定数です。
kDivCount は、1枚の四角形を格子状に分割するための数です。
kVertexCount は、格子状に分割した頂点の数です。
kIndexCount は、IBOで利用する頂点のインデックスの数です。
インスタンス変数として、頂点バッファとインデックスバッファの変数を定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
- (void)setupGL { [EAGLContext setCurrentContext:self.context]; [self loadShaders]; // 板ポリゴン作成 { { // 頂点バッファ { // 頂点バッファ VertexData vertrices[kVertexCount]; // 頂点間の長さ float divisions = 1.0f / kDivCount; // 頂点作成 int i = 0; for (int y = 0; y <= kDivCount; y++){ for (int x = 0; x <= kDivCount; x++){ float tx = (x * divisions) * 2 - 1.0f; float ty = (y * divisions) * 2 - 1.0f; float tz = 0.0f; vertrices[i].x = tx; vertrices[i].y = ty; vertrices[i].z = tz; vertrices[i].u = tx; vertrices[i].v = ty; i++; } } // 頂点バッファ作成 glGenBuffers(1, &vertices_buffer); assert(glGetError() == GL_NO_ERROR); assert(vertices_buffer != 0); // バインド glBindBuffer(GL_ARRAY_BUFFER, vertices_buffer); assert(glGetError() == GL_NO_ERROR); // アップロード glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData) * kVertexCount, &vertrices, GL_STATIC_DRAW); assert(glGetError() == GL_NO_ERROR); // バインド解除 glBindBuffer(GL_ARRAY_BUFFER, 0); } |
setupGLメソッドでは、描画に必要なVBO、IBOを作成します。
頂点バッファ/インデックスバッファ作成、バインド、バッファデータのアップロードは、「基礎知識2」(シェーダの効率的な描画) で解説した内容と同じです。
VBOには、頂点情報を設定します。
頂点情報は、VertexDataの配列(vertrices)を利用します。
頂点毎に頂点の座標(x、y、z)と色(u、v)を算出し、VertexDataに設定し、それを配列へ格納します。
頂点の座標は、頂点の分割数と頂点間の長さから算出します。
色は、頂点の座標を利用します。今回は、x座標とy座標をRGBカラーのRとGとして設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// インデックスバッファ { //インデックスバッファ int indices[kIndexCount]; // y座標最大値 int maxYposition = kDivCount + 1; int i = 0; for (int y = 0; y < kDivCount; y++){ for (int x = 0; x < kDivCount+1; x++){ int idx0 = x + (y * maxYposition); int idx1 = idx0 + maxYposition; if(x == 0) { // 先頭x座標の場合、縮退三角形の対応をする indices[i++] = idx0; indices[i++] = idx0; indices[i++] = idx1; } else if (x == kDivCount) { // 後尾x座標の場合、縮退三角形の対応をする indices[i++] = idx0; indices[i++] = idx1; indices[i++] = idx1; } else { indices[i++] = idx0; indices[i++] = idx1; } } } // インデックス glGenBuffers(1, &indices_buffer); assert(glGetError() == GL_NO_ERROR); assert(indices_buffer != 0); // バインド glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indices_buffer); assert(glGetError() == GL_NO_ERROR); // アップロード glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * kIndexCount, &indices, GL_STATIC_DRAW); assert(glGetError() == GL_NO_ERROR); // バインド解除 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } } } } |
IBOは、glDrawElements呼び出し際、GL_TRIANGLE_STRIPモードで描画されます。
GL_TRIANGLE_STRIPモードは、次の1点と前の2点の頂点を利用して三角形を描くモードです。
ここでは、この頂点を算出する処理を記載しています。
IBOは、頂点バッファの頂点情報のインデックスを設定します。この設定には数値の配列(indices)を利用します。
頂点情報のインデックスは、頂点の分割数を元に算出します。
現在のy座標を視点に、x座標から頂点バッファの頂点情報のインデックスを設定します。
その後、次のy座標を算出し、同じように頂点情報のインデックスを設定します。
ここでポイントになるのが、y座標が変わるタイミングです。
x座標の終わりと次のx座標の始まりが繋がるため、無駄な三角形が描画されます。
この繋がりを描画させないための処理として、縮退三角形を利用します。
縮退三角形については、後半部分で解説します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
- (void)tearDownGL { [EAGLContext setCurrentContext:self.context]; glUseProgram(0); glDeleteProgram(_program); { assert(glIsBuffer(vertices_buffer) == GL_TRUE); GLuint buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*) &buffer); assert(buffer == vertices_buffer); } glDeleteBuffers(1, &vertices_buffer); glDeleteBuffers(1, &indices_buffer); { assert(glIsBuffer(vertices_buffer) == GL_FALSE); GLuint buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*) &buffer); assert(buffer == 0); } } |
tearDownGLメソッドは、シェーダをメモリから開放する処理を記載します。
setupGLメソッドで作成したVBO、IBOを削除します。
1 2 3 4 |
- (void)update { // 処理なし } |
updataメソッドは、今回、使用しません。
不要な処理を削除します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // バッファバインド glBindBuffer(GL_ARRAY_BUFFER, vertices_buffer); // 頂点情報設定 { // シェーダのattribute変数取得 GLint attr_pos = glGetAttribLocation(_program, "attr_pos"); // シェーダ内属性のアクセス許可有効化 glEnableVertexAttribArray(attr_pos); // 利用箇所指定 glVertexAttribPointer(attr_pos, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (GLvoid *) 0); } // フラグメント情報設定 { // シェーダのattribute変数取得 GLint attr_color = glGetAttribLocation(_program, "attr_color"); // シェーダ内属性のアクセス許可有効化 glEnableVertexAttribArray(attr_color); // 利用箇所指定 glVertexAttribPointer(attr_color, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), (GLvoid *) (sizeof(float)*3)); } // インデックスバッファバインド glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indices_buffer); // シェーダー利用開始 glUseProgram(_program); assert(glGetError() == GL_NO_ERROR); // シェーダー描画 glDrawElements(GL_TRIANGLE_STRIP, kIndexCount, GL_UNSIGNED_INT, (GLvoid *) 0); assert(glGetError() == GL_NO_ERROR); } |
glkViewメソッドでは、初期化時に作成したVBOとIBOをバインドします。そして、画面に板ポリゴンを描画します。
頂点情報設定、フラグメント情報設定、描画は、「基礎知識2」(シェーダの効率的な描画) で説明した内容と同じです。
縮退三角形
今回、板ポリゴンを作成するためGL_TRIANGLE_STRIPモードで描画しています。このモードは、縮退三角形を描画しない特徴があります。
縮退三角形とは、面積が0の三角形、または、3点の座標が直線に並ぶ三角形のことを言います。
通常、GL_TRIANGLE_STRIPモードで描画すると上の図のような順番でポリゴンが描かれます。このポリゴンは、2つの三角形で四角形を描いています。
次に、1つの四角形を2分割し、8つの三角形で四角形を描きます。
ポイントは、2→5→3の三角形です。
GL_TRIANGLE_STRIPモードの仕様上、次の1点と前の2点を利用して三角形を描くため、2→5→3の三角形が描かれます。
この三角形が描かれると、波紋が綺麗に表示されなくなります。
この2→5→3の三角形を描画しないためには、どうすれば良いのでしょうか?
それを解決するのが、縮退三角形です。
縮退三角形を利用する箇所は、以下です。
1. y座標が変わるタイミングのx座標の終わり
2. y座標が変わるタイミングのx座標の始まり
修正方法は、以下を追加するだけです。
1. y座標が変わるタイミングのx座標の終わり
x座標の最後の頂点の位置に、同じ頂点を1つ追加します。
→ 以下の図のindices[6]です。
2. y座標が変わるタイミングのx座標の始まり
x座標の最初の頂点の位置に、同じ頂点を1つ追加します。
→ 以下の図のindices[8]です。
これにより縮退三角形が作成され、不要な三角形が描画されなくなります。
板ポリゴンの実行
シミュレータを起動すると、板ポリゴンが画面に描画されます。
次回は、この板ポリゴンにカメラをつけます。
カメラをつけることで、3D視点から板ポリゴンを眺めることができます。
バックナンバー
OpenGL ES2.0で波紋を作る#4 OpenGL ES を組み込むベースを作成
OpenGL ES2.0で波紋を作る#5 格子状の板ポリゴンを作る