OpenGL ESは、iphone上で2Dおよび3Dグラフィックス・プログラミングを可能にする低レベルAPIです。
cocos2d、sparrow、corona、unityのフレームワークを触ったことがある人なら、それらがすべてOpenGLで作られていることがわかるでしょう。
ほとんどのプログラマーは、OpenGLを直接呼び出す代わりに、これらのフレームワークを使うことを選択します。
このチュートリアルは、より良いスタートを切るために書かれたものです。
この連載では、ハローワールドのようなアプリを、実用的で簡単な実験を通して作ることができます。
大まかな流れは以下の通り:
-簡単なOpenGLアプリの作成
-頂点シェーダーとフラグメントシェーダーのコンパイルと実行
-頂点バッファを持つ単純な矩形をスクリーンにレンダリングします。
-投影とモデルビューの変換を使用します。
-奥行きテストが可能な3Dオブジェクトをレンダリングします。
説明
私はOpenGLの専門家ではないので、これは完全に独学です。もし間違っているところがあれば、遠慮なく指摘してください。
OpenGL ES1.0 とOpenGL ES2.0
まず最初に理解しなければならないのは、OpenGL ES 1.0と2.0の違いです。
どう違うのですか?とても違うとしか言いようがありません。
[]
OpenGL ES 1.0:
固定パイプラインハードウェアの場合、内蔵関数でライト、頂点、カラー、カメラなどを設定します。
OpenGL ES2.0:
プログラマブル・パイプライン・ハードウェアの場合、この設計に基づいた組み込み関数で地獄を見ることができますが、同時に、あらゆる関数を自分で書かなければなりません。
「なんてこった」と思われるかもしれません。これが2.0の使い方?
でも、2.0では1.0にはできないクールなことができます。
opengles 2.0では、下のようなクールな照明と影のエフェクトを作成することも可能です:
OpenGL ES2.0は、iphone 3GS+、iPod Touch 3G+、ipadのすべてのバージョンでしか動作しません。ありがたいことに、今ではほとんどのユーザーがその範囲に入っています。
始めましょう。
Xcodeには、OpenGL ES用のプロジェクト・テンプレートが付属していますが、このテンプレートは、それ自身で多くのコードを作成するため、初心者にとっては混乱する可能性があります。
ですから、自分で段階を追って書くことで、どのように機能するのかをよりよく理解することができます。
Xcodeを起動し、新しいプロジェクトを作成し、Window-based Applicationを選択して、ゼロから始めましょう。
Next」をクリックし、プロジェクト名を「HelloOpenGL」とし、「Next」をクリックし、ディレクトリを選択して「Create」をクリックします。
CMD+R、ビルド、実行。空白の画面が表示されます。
ご覧のように、Windowベースのテンプレートはビューもビューコントローラも何もないプロジェクトを作成します。必須のUIWindowだけが含まれています。
サブクラスUIViewを選択し、Nextをクリックし、OpenGLView.m.という名前を付け、Saveをクリックします。
次に、このOpenGLView.mファイルの下にたくさんのコードを追加しなければなりません。
1) 必要なフレームワークの追加
追加: OpenGLES.frameworkおよびQuartzCore.framework
2) OpenGLView.hの修正
OpenGLのHeaderを導入し、後で使用するインスタンス変数を作成します。
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
@interface OpenGLView : UIView {
CAEAGLLayer* _eaglLayer;
EAGLContext* _context;
GLuint _colorRenderBuffer;
}
@end
3) レイヤクラスを CAEAGLLayer に設定します。
+ (Class)layerClass {
return [CAEAGLLayer class];
}
OpenGLコンテンツを表示するには、そのデフォルトレイヤーを特別なレイヤーに設定する必要があります。これは、layerClassメソッドを直接オーバーライドすることで行います。
4) レイヤーを不透明に設定します。
- (void)setupLayer {
_eaglLayer = (CAEAGLLayer*) self.layer;
_eaglLayer.opaque = YES;
}
なぜなら、デフォルトではCALayersは透明だからです。そして、透明なレイヤーは、特にOpenGLレイヤーのパフォーマンスに非常に負荷をかけます。
5) OpenGLコンテキストの作成
- (void)setupContext {
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
_context = [[EAGLContext alloc] initWithAPI:api];
if (!_context) {
NSLog(@"Failed to initialize OpenGLES 2.0 context");
exit(1);
}
if (![EAGLContext setCurrentContext:_context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
}
OpenGLに何をさせるにしても、必ずこの EAGLContext必要になります。
コンテキストを作成するときに、どのバージョンのAPIを使用するかを宣言する必要があります。
6) レンダーバッファの作成
- (void)setupRenderBuffer {
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
レンダーバッファは、レンダリングされたイメージを保持するOpenGLオブジェクトです。
レンダーバッファがカラーバッファとして参照されることがあります。
レンダーバッファを作成する3つのステップ:
1.glGenRenderbuffers新しいレンダーバッファオブジェクトを作成します。レンダーバッファをマークするために一意の整数が返されます。
2.glBindRenderbuffer OpenGLに伝えます:後でGL_RENDERBUFFERを参照するところで、実際には_colourRenderBufferを使いたいのですが、これは実際に、定義されたバッファオブジェクトがどのOpenGLオブジェクトに属するかをOpenGLに伝えます。
3.最後に、レンダーバッファ用の領域が割り当てられます。 renderbufferStorage
7) フレームバッファの作成
- (void)setupFrameBuffer {
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, _colorRenderBuffer);
}
フレームバッファを作成する最初の2つのステップは、レンダーバッファを作成するのとよく似ています。
そして最後のステップ glFramebufferRenderbuffer 少し新しいものです。これは、フレームバッファのGL_COLOR_ATTACHMENT0位置に、先に作成したバッファレンダーをアタッチすることができます。
8) スクリーンのクリーニング
- (void)render {
glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
すべての頂点とシェーダーを処理した後、できるだけ早く画面に何かを表示するには、画面を少しきれいにして別の色を表示します。
ここで、RGBの各色の範囲は0から1なので、それぞれを255で割る必要があります。
それぞれの動きの内訳は以下の通り:
1.glClearColor 呼び出し、RGBカラーと透明度を設定します。
2.この "カラーフィル "アクションを実行するためにglClear呼び出します。たくさんのバッファがあるので、GL_COLOR_BUFFER_BITを使って、どのバッファをクリアするかを宣言する必要があることを覚えておいてください。
9) OpenGLView.mを修正します。
// Replace initWithFrame with this
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupLayer];
[self setupContext];
[self setupRenderBuffer];
[self setupFrameBuffer];
[self render];
}
return self;
}
// Replace dealloc method with this
- (void)dealloc
{
[_context release];
_context = nil;
[super dealloc];
}
10) App DelegateとOpenGLViewの接続
HelloOpenGLAppDelegate.hで次のように変更してください。
// At top of file
#import "OpenGLView.h"
// Inside @interface
OpenGLView* _glView;
// After @interface
@property (nonatomic, retain) IBOutlet OpenGLView *glView;
次に.mファイルを修正します:
// At top of file
@synthesize glView=_glView;
// At top of application:didFinishLaunchingWithOptions
CGRect screenBounds = [[UIScreen mainScreen] bounds];
self.glView = [[[OpenGLView alloc] initWithFrame:screenBounds] autorelease];
[self.window addSubview:_glView];
// In dealloc
[_glView release];
すべてがうまくいけば、画面に新しいVIEWが表示されます。
これがOpenGLの世界です。
シェーダーの追加:バーテックス・シェーダーとフラグメント・シェーダー
OpenGL ES2.0の世界では、シーン内のあらゆる種類のジオメトリをレンダリングするには、シェーダと呼ばれる2つの小さなプログラムを作成する必要があります。
シェーダーはGLSLというC言語に似た言語で書かれています。
この世界には2種類のシェーダーがあります:
-頂点シェーダー - シーン内の各頂点に対して呼び出す必要がある「頂点シェーダー」と呼ばれるプロシージャ。単純なシーンをレンダリングするとしましょう:各コーナーに1つの頂点がある長方形。頂点シェーダは 4 回呼び出されます。ライティング、ジオメトリ変換などの計算を担当します。最終的な頂点位置は導出され、下のフラグメントシェーダに必要なデータを提供します。
-フラグメントシェーダー - シーン内のほぼすべてのピクセルに対して呼び出されるプロシージャで、「フラグメントシェーダー」と呼ばれます。単純なシーンでは、先ほど説明した矩形。フラグメントシェーダは、この矩形で覆われた各ピクセルに対して1回呼び出されます。ライティングを計算し、さらに重要なこととして、各ピクセルの最終的な色を計算するのは、フラグメントシェーダの責任です。
以下に簡単な例を示します。
SimpleVertex.glsl 保存をクリックします。
このファイルを開き、以下のコードを追加します:
attribute vec4 Position; // 1
attribute vec4 SourceColor; // 2
varying vec4 DestinationColor; // 3
void main(void) { // 4
DestinationColor = SourceColor; // 5
gl_Position = Position; // 6
}
行ごとの解析:
1 "attribute" は、シェーダが "Position" という変数を受け取ることを宣言します。この変数の型は "vec4" です。この変数の型は "vec4 "で、4分割ベクトルであることを意味します。
2 上と同じですが、ここでは頂点のカラー変数が渡されます。
3 この変数には "attribute" キーワードがありません。これは、この変数がフラグメントシェーダに渡される引数であるアウトゴーイング変数であることを示しています。v varying "キーワードは、頂点間の各ピクセルの色が頂点の色に基づいて平滑化されることを示しています。
言葉で理解するのは難しい、百聞は一見にしかず:
正確には、上の頂点から100分の55、下の頂点から100分の45の位置にある点です。つまり、遷移によって、このピクセルの色を決定することができます。
4 各シェーダはC言語と同じようにmainから始まります。
5 ターゲットカラーを設定 = 変数に渡す: SourceColor
6 gl_Position は組み込みのパスアウト変数です。頂点シェーダで設定しなければならない変数です。論理演算は行われません。
簡単な頂点シェーダは以上です。 次に、簡単なフラグメントシェーダを作成します。
新しい空のファイルを作成します:
FileNew File... Select iOSOther-Empty
名前: SimpleFragment.glsl 保存します。
このファイルを開き、以下のコードを追加します:
varying lowp vec4 DestinationColor; // 1
void main(void) { // 2
gl_FragColor = DestinationColor; // 3
}
以下解析:
1 これは頂点シェーダから渡される変数で、頂点シェーダで定義されているも のと同じです。フラグメントシェーダでは、計算に精度を与える必要があります。性能上の理由から、常に最低精度を使用するのがよい習慣です。ここでは最低精度に設定します。必要であれば、medp や highp に設定することもできます。
2.これもメインから。
ここでも、最初に何も変更することなく、頂点シェーダから直接値が取得されます。
悪くないでしょ?次に、これらのシェーダーを適用してアプリを作成します。
頂点シェーダとフラグメントシェーダのコンパイル
これまでのところ、xcodeはこれら2つのファイルをアプリケーションバンドルにコピーするだけです。また、実行時にこれらのシェーダーをコンパイルして実行する必要があります。
驚かれるかもしれません。なぜアプリの実行時にコードをコンパイルするのですか?
この利点は、シェーダーが特定のグラフィックチップに依存する必要がないことです。
以下の動的にコンパイルされたコードを追加し、OpenGLView.mを開きます。
initWithFrame: メソッドの上に追加します:
- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
// 1
NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName
ofType:@"glsl"];
NSError* error;
NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath
encoding:NSUTF8StringEncoding error:&error];
if (!shaderString) {
NSLog(@"Error loading shader: %@", error.localizedDescription);
exit(1);
}
// 2
GLuint shaderHandle = glCreateShader(shaderType);
// 3
constchar* shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = [shaderString length];
glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
// 4
glCompileShader(shaderHandle);
// 5
GLint compileSuccess;
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
if (compileSuccess == GL_FALSE) {
GLchar messages;
glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
exit(1);
}
return shaderHandle;
}
以下解析:
1 これは、NSBundle内のファイルを見つけるためのUIKitプログラミングの標準的な使い方です。これには慣れているはずです。
3glCreateShader 呼び出し、OpenGL にシェーダーのソースコードを取得させます。これはまた、NSStringをC-stringに変換します。
4 最後に、glCompileShader を呼び出して実行時にシェーダをコンパイルし ます。
5 私たちは皆プログラマであり、プログラムがあれば失敗があり、プログラマがいればデバッグがあり、コンパイルに失敗した場合、なぜ問題が発生したのかを調べるための情報が必要です。 glCompileShader glGetShaderInfoLogは エラー情報を画面に出力します。
また、バーテックスシェーダとフラグメントシェーダをコンパイルするために必要な手順もあります。
- リンクしてください。
- OpenGLにプログラムを呼び出すように指示し、いくつかのポインタなどを渡します。
compileShader:メソッドの下に、次のコードを追加します。
- (void)compileShaders {
// 1
GLuint vertexShader = [self compileShader:@"SimpleVertex"
withType:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShader:@"SimpleFragment"
withType:GL_FRAGMENT_SHADER];
// 2
GLuint programHandle = glCreateProgram();
glAttachShader(programHandle, vertexShader);
glAttachShader(programHandle, fragmentShader);
glLinkProgram(programHandle);
// 3
GLint linkSuccess;
glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
GLchar messages;
glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
exit(1);
}
// 4
glUseProgram(programHandle);
// 5
_positionSlot = glGetAttribLocation(programHandle, "Position");
_colorSlot = glGetAttribLocation(programHandle, "SourceColor");
glEnableVertexAttribArray(_positionSlot);
glEnableVertexAttribArray(_colorSlot);
}
内訳は以下の通り:
1 このメソッドを使って、頂点シェーダとフラグメントシェーダをそれぞれコンパイルする、先ほど書いたダイナミックコンパイルメソッドを呼び出します。
2glCreateProgram glAttachShader glLinkProgram は、頂点シェーダとフラグメントシェーダを完全なプログラ ムに接続するために呼び出されます。
3 glGetProgramivglAttachShader エラーをチェックし、メッセージを出力します。
4 glLinkProgram 呼び出し、OpenGLに実際にプログラムを実行させます。
5 最後に、 glGetProgramiv 、頂点シェーダの入力変数 へのポインタを取得します。このポインタは後で使用できます。また、このデータを有効にするために glEnableVertexAttribArrayを呼び出します。
最後のステップは2つ:
1 initWithFrameメソッドで、renderの呼び出しに以下を追加します:
[self compileShaders];
2 OpenGLView.hの@interfaceに2つの変数を追加します:
GLuint _positionSlot;
GLuint _colorSlot;
コンパイル走れ
グリーンスクリーンがきちんと見えるなら、先に書いたコードがうまく機能している証拠です。
この単純な長方形の頂点データを作成します!
ここでは、以下のように画面上に正方形を描画することを意図しています:
OpenGLで図形をレンダリングするときに常に注意しなければならないのは、直接レンダリングできるのは三角形だけで、長方形などの他の図形はレンダリングできないということです。つまり、正方形をレンダリングするには、2つの三角形に分ける必要があります。
図はそれぞれ頂点と、頂点によって形成される三角形を示しています。
OpenGL ES 2.0の利点の1つは、頂点をスタイル別に管理できることです。
OpenGLView.mファイルを開き、以下のように矩形情報を追跡するためのいくつかの配列とともに、純粋なC構造体を作成します:
typedef struct {
float Position[3];
float Color[4];
} Vertex;
const Vertex Vertices[] = {
{{1, -1, 0}, {1, 0, 0, 1}},
{{1, 1, 0}, {0, 1, 0, 1}},
{{-1, 1, 0}, {0, 0, 1, 1}},
{{-1, -1, 0}, {0, 0, 0, 1}}
};
const GLubyte Indices[] = {
0, 1, 2,
2, 3, 0
};
このコードが行うこと
1 すべての頂点情報を管理するための構造体 Vertex
2は、上記のこの頂点構造体の型の配列を定義します。
3 三角形の頂点を表す配列。
データの準備ができたので、OpenGLにデータを渡してみましょう。
頂点バッファオブジェクトの作成
OpenGLにデータを渡す最良の方法は、Vertex Bufferオブジェクトを使うことです。
基本的には、頂点データをキャッシュするためのOpenGLオブジェクトです。データは何らかの関数を呼び出すことでOpenGLランドに送られます。
頂点キャッシュには2つのタイプがあります。1つは各頂点の情報を追跡するためのもので、もう1つは各三角形を構成するインデックスの情報を追跡するためのものです。
initWithFrameの下に、コードを追加します:
[self setupVBOs];
このセットアップVBOの定義は以下の通りです:
- (void)setupVBOs {
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
GLuint indexBuffer;
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}
ご覧の通り、実はとてもシンプルです。実際に使われているパターンでもあります。
glGenBuffers - 頂点バッファ・オブジェクトを作成します.
glBindBuffer – 告诉OpenGLvertexBuffer 是指GL_ARRAY_BUFFER
glBufferData - OpenGL-land にデータを渡す関数です.
このパターンをどこで使ったか覚えていますか?もう一度フレームバッファのビットを見てみましょうか?
頂点データを画面に描画する新しいレンダリングメソッドを持つ新しいシェーダの準備はすべて整いました。
レンダーを次のコードに置き換えてください:
- (void)render {
glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// 1
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
// 2
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex), 0);
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE,
sizeof(Vertex), (GLvoid*) (sizeof(float) *3));
// 3
glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]),
GL_UNSIGNED_BYTE, 0);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
1glGetProgramInfoLog 、レンダリングに使用するUIViewの部分を設定します。この例では画面全体を指定しています。しかし、より小さい部分を使用したい場合は、これらのパラメータを変更することができます。
2glUseProgram、頂点シェーダの 2 つの入力パラメー タに適切な 2 つの値を設定します。
この2番目の段落は、それがどのように機能するかを真剣に見てもらうための非常に重要な方法です:
-最初のパラメータで、この属性の名前を宣言し、glGetAttribLocationと呼びます。
-2番目のパラメータは、このプロパティがいくつの値で構成されるかを定義します。例えば、position は 3 個の浮動小数点数で構成され、color は 4 個の浮動小数点数です。
-3つ目は、それぞれの値がどのような型であるかを宣言することです。
-4枚目、うーん......いつも偽りでちょうどいいんです。
-5番目はストライドのサイズです。これは各頂点のデータのサイズを表す方法です。つまり、単純にsizeofを渡してコンパイラに計算させることができます。
-最も良いのは、このデータ構造のオフセットで、この構造内の値の取得を開始する場所を示します。positionの値が最初に来るので、0を渡すだけです。そして色はpositionの直後のデータで、positionのサイズはfloat3個分のサイズなので、3 * sizeof(float)から始まります。
戻ってコードを続けてください:
また、よく観察することも重要な方法です:
-最初のパラメータは、グラフィックのレンダリングにどの機能を使用するかを宣言します。GL_LINE_STRIP と GL_TRIANGLE_FAN がありますが、 GL_TRIANGLE が最もよく使われます。
-2つ目は、レンダリングするグラフィックの数をレンダラーに指示します。その数を計算するにはCコードを使用します。ここでは、配列のバイトサイズをindice型のサイズで割って求めています。
-3つ目は、各インデックスにおけるインデックスの種類を示すものです。
-最後のものは、公式ドキュメントでは、indexへのポインタと書かれています。しかし、ここではVBOが使われているので、indexの配列を通してアクセスできるので、ここでは必要ありません。
コンパイルして実行すると、こんな画面になります。
なぜこの矩形が画面いっぱいになるのか不思議に思うかもしれません。デフォルトでは、OpenGLの「カメラ」はz軸を向くように配置されています。
もちろん、投影やカメラのコントロール方法については後でお話しします。
投影の追加
3Dイメージを2Dスクリーンに表示するには、グラフィックスに投影変換を行う必要があります:
基本的には、人間の目の原理を模倣するためです。遠い平面と近い平面を設定し、両方の平面において、近い平面に近いイメージは縮小されて小さく見え、遠い平面に近いイメージは結果として大きく見えます。
SimpleVertex.glslを開き、変更を加えます:
// Add right before the main
uniform mat4 Projection;
// Modify gl_Position line as follows
gl_Position = Projection * Position;
これは projection という入力変数を追加するもので、uniform キーワードは、これが頂点ごとに変化する値ではなく、すべての頂点に適用される定数になることを示しています。
mat4は4X4の行列を意味します。しかし、マトリックスの数学は、ここで解析するにはあまりにも大きなテーマです。ですから、ここでは、拡大・縮小、回転、歪みのために使われると仮定してください。
Position(位置)にProjection(投影)マトリクスを掛け合わせ、最終的な位置値を得ます。
線形代数」と呼ばれるものです。学生時代にはほとんど忘れていました。
実際、数学も単なるツールで、そのツールはこれまでのタレントが扱ってきたものですから、使い方を知っておくのはいいことです。
cocos3dの作者、ビル・ホリングス。彼は完全な3Dフィーチャー・フレームワークを書き、cocos2dに統合しました。それはともかく、Cocos3dにはObjective-Cのvectorとmatrixライブラリが含まれているので、このプロジェクトでも十分使えます。
ここでは、/.ip
OpenGLView.hにインスタンス変数を追加:
GLuint _projectionUniform;
次に、OpenGLView.mファイルに追加します。
// Add to top of file
#import "CC3GLMatrix.h"
// Add to bottom of compileShaders
_projectionUniform = glGetUniformLocation(programHandle, "Projection");
// Add to render, right before the call to glViewport
CC3GLMatrix *projection = [CC3GLMatrix matrix];
float h =4.0f* self.frame.size.height / self.frame.size.width;
[projection populateFromFrustumLeft:-2 andRight:2 andBottom:-h/2 andTop:h/2 andNear:4 andFar:10];
glUniformMatrix4fv(_projectionUniform, 1, 0, projection.glMatrix);
// Modify vertices so they are within projection near/far planes
const Vertex Vertices[] = {
{{1, -1, -7}, {1, 0, 0, 1}},
{{1, 1, -7}, {0, 1, 0, 1}},
{{-1, 1, -7}, {0, 0, 1, 1}},
{{-1, -1, -7}, {0, 0, 0, 1}}
};
-次に、数学ライブラリを使って投影行列を作成します。この行列を作るときに、座標と、近いスクリーンと遠いスクリーンの位置を指定すると、よりシンプルになります。
-このCC3GLMatrixクラスは、行列をOpenGLの配列フォーマットに変換する便利なメソッド glViewportいます。
最後に、z座標が-7.5mmになるように頂点データを変更します。
コンパイルして実行すると、正方形が少し遠くに見えるはずです。
動いて回転してみてください!
グラフィックを変更するために、常に頂点配列を修正しなければならないのは煩わしいでしょう。
そして、それこそが変換行列の役目なのです。
先ほど、投影行列に適用される頂点配列は、グラフを移動させる目的で変更されました。それを適用するために、変形、拡大、縮小、回転行列を作ってみてはどうでしょうか。モデルビュー」トランスフォームと呼んでください。
SimpleVertex.glslに戻ります。
// Add right after the Projection uniform
uniform mat4 Modelview;
// Modify the gl_Position line
gl_Position = Projection * Modelview * Position;
Uniform行列をもう1つ追加するだけです。ちなみにgl_Positionに適用します。
次にOpenGLView.hに行き、変数を追加します。
GLuint _modelViewUniform;
OpenGLView.mを修正してください:
// Add to end of compileShaders
_modelViewUniform = glGetUniformLocation(programHandle, "Modelview");
// Add to render, right before call to glViewport
CC3GLMatrix *modelView = [CC3GLMatrix matrix];
[modelView populateFromTranslation:CC3VectorMake(sin(CACurrentMediaTime()), 0, -7)];
glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix);
// Revert vertices back to z-value 0
const Vertex Vertices[] = {
{{1, -1, 0}, {1, 0, 0, 1}},
{{1, 1, 0}, {0, 1, 0, 1}},
{{-1, 1, 0}, {0, 0, 1, 1}},
{{-1, -1, 0}, {0, 0, 0, 1}}
};
-モデルビューユニフォームの入力変数を取得します。
-cocos3d数学ライブラリを使用して、変換に読み込む新しい行列を作成します。
変換はz軸上で-7移動していますが、なぜsin(現在時刻)なのですか?
sin()は-1から1までの関数です。これは1サイクルPIです。そうすると、約3.14秒ごとに、この関数は-1から1まで循環します。
頂点構造を戻し、z座標を0.5に戻します。
コンパイルして実行すると、zを0に戻しても、真ん中にこの四角が見えます。
え?動かない?
もちろん、レンダー・メソッドを1回呼び出すだけです。
次に、各フレームで1回ずつ呼び出して確認します。
レンダリングとCADisplayLink
理想的には、OpenGLのレンダリング頻度は画面のリフレッシュ頻度と同じにしたいものです。
幸い、AppleはCADisplayLinkのクラスを提供しています。これは素晴らしい機能なので、すぐに使ってください。
OpenGLView.mファイルで、以下を変更します:
// Add new method before init
- (void)setupDisplayLink {
CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
// Modify render method to take a parameter
- (void)render:(CADisplayLink*)displayLink {
// Remove call to render in initWithFrame and replace it with the following
[self setupDisplayLink];
これは、CADisplayLinkがフレームごとにrenderメソッドを呼び出すことで、グラフが周期的にsin()シェイプされているように見えます。これで、正方形が前後左右に動きます。
力を入れずに紡ぐ
形を回転させてスタイリッシュに。
次にOpenGLView.hに行き、メンバ変数を追加します。
float _currentRotation;
_currentRotation += displayLink.duration *90;
[modelView rotateBy:CC3VectorMake(_currentRotation, _currentRotation, 0)];
-1秒間に90度ずつ回転する_currentRotationというfloatを追加しました。
-そのモデルのビュー行列を修正することによって回転を追加します。
-回転はX軸とY軸に作用し、Z軸には作用しません。
コンパイルして実行すると、非常にスタイリッシュな反転3D効果が表示されます。
苦労せずに3Dキューブに変身?
まだ回転する表面なので、2.5次元に過ぎません。これを3Dに変換します。
そして新しいものを追加します:
const Vertex Vertices[] = {
{{1, -1, 0}, {1, 0, 0, 1}},
{{1, 1, 0}, {1, 0, 0, 1}},
{{-1, 1, 0}, {0, 1, 0, 1}},
{{-1, -1, 0}, {0, 1, 0, 1}},
{{1, -1, -1}, {1, 0, 0, 1}},
{{1, 1, -1}, {1, 0, 0, 1}},
{{-1, 1, -1}, {0, 1, 0, 1}},
{{-1, -1, -1}, {0, 1, 0, 1}}
};
const GLubyte Indices[] = {
// Front
0, 1, 2,
2, 3, 0,
// Back
4, 6, 5,
4, 7, 6,
// Left
2, 7, 3,
7, 6, 2,
// Right
0, 4, 1,
4, 1, 5,
// Top
6, 2, 1,
1, 6, 5,
// Bottom
0, 3, 7,
0, 7, 4
};
コンパイルして実行すると、正方形が表示されます。
でも、広場の中が見えるので、広場が偽物のように感じられることがあります。
これはOpenGLがz軸上のピクセルを追跡できるようにするものです。こうすることで、そのピクセルの前に何もない場合にのみ、そのピクセルを描画します。
OpenGLView.hを開き、メンバ変数を追加します。
GLuint _depthRenderBuffer;
OpenGLView.mでは
// Add new method right after setupRenderBuffer
- (void)setupDepthBuffer {
glGenRenderbuffers(1, &_depthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height);
}
// Add to end of setupFrameBuffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);
// In the render method, replace the call to glClear with the following
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
// Add to initWithFrame, right before call to setupRenderBuffer
[self setupDepthBuffer];
-レンダーメソッドでは、更新ごとに深度バッファをクリアし、深度テストを有効にします。
コンパイルして実行すると、このチュートリアルの最終結果が表示されます。
OpenGL ES2.0を使用したキューブのセレクション。
ここから先は?
このチュートリアルのコードはこちらから入手できます。
これはOpenGLのためのチュートリアルです。
ちなみに、このチュートリアルを書いた理由は、ここ数週間で最も投票数が多かったからです。毎週新しいチュートリアルがあります。





