2012年3月31日土曜日

All about OpenGL ES 2.x 2/3 -3

2/3 -2 の続き

Shaders


ようこそ、グレートな3Dワールドへ!

ここまで読んできていればシェーダーがどんなものかということはイメージできているでしょう。今一度まとめてみましょう。
  • シェーダーは GLSL もしくは GLSL ES を使っている。
  • シェーダーはいつも頂点シェーダー(VSH) とフラグメントシェーダー(FSH) のペアではたらく。
  • glDrawArrays() や glDrawElements() といった描画コマンドを送るごとにシェーダーのペアは処理を実行する。
  • VSH は頂点の数だけ処理を行う。
  • FSH はオブジェクトの可視断片のそれぞれについて処理を実行する。FSH はグラフィックパイプラインにおいて Fragment Operations の前に処理されるので、その時点では OpenGL はどのオブジェクトが前面にあるかといった情報は知らない。つまり、隠れているようなオブジェクトも処理するということ。FSH の仕事は断片の最終的な色を定義すること。
  • VSH と FSH は別々にコンパイルされ、Program Object としてリンクされなければならない。コンパイルされたシェーダーは複数の Program Object に再利用することができる。ただしそれぞれの Program Object にリンクできるのは1つのシェーダーペアである。

Shader and Program Creation

では シェーダーオブジェクトについて見てみよう。他の OpenGL のオブジェクトと同様にまずは名前/IDsを付け、設定を行う。他のと違うのは「コンパイルする」という手順があるということ。シェーダーは GPU によって処理され、OpenGL に最適なバイナリフォーマットにコンパイルされる。

シェーダーの作成に関連する関数は以下のとおり。

GLuint glCreateShader(GLenum type)
GLvoid glShaderSource(GLuint shader, GLsizei count,
                      const GLchar** string, const GLint* length)
GLvoid glCompileShader(GLuint shader)
// 引数の説明は省略

名前/IDsをつけて、ソースコードを渡して、それをコンパイルする。他のコードをすでにもっているシェーダーにソースコードを渡すと元のコードは破棄される。一度コンパイルしたら処理を変えることはできない。

それぞれのシェーダーはコンパイルされたかどうかのフラグを持つ。適正にコンパイルされラバ TRUE となるので、デバッグの時にはチェックすべき箇所です。加えてログを取得するのもよいでしょう。glGetShaderiv() で状態を、glGetShaderInfoLog()で状態メッセージを取得できます。

シェーダーの名前/IDを1つのリストで管理することはようにしましょう。例えば[1]という名前/IDをもつ VSH を生成して、次に作るシェーダーの名前はおそらく[2]になるでしょうし、そのあとは[3], [4], [5],,, となっていき、名前/IDが重複することはなくなります。

シェーダーペアを作ったら、次はそれらを格納する Program Object を作ります。手順としては Program Object を生成し、何かしら(ここではシェーダーのペア)をアップロードし、"link" する。このリンクによってシェーダーペア同士をつなげ、それ自体を OpenGL のコア部分にリンクさせる。はい、ここ重要です。なぜならこの過程でシェーダーに多くの検証がなされるから。シェーダーと同じように Program Object でもリンク状態とそのログを見ることができ、そこからチェックを行うべきです。リンクが成功すればそれらは正確に動くでしょう。Probram Object の関数は以下のとおり。

GLuint glCreateProgram(void)
GLvoid glAttachShader(GLuint program, GLuint shader)
GLvoid glLinkProgram(GLuint program)

glCreateProgram() は引数を持たない。なぜなら Program Object は一種類しか存在しないから。

シェーダーの名前/IDを単一のリストで管理する、というのは覚えているかな?OpenGL はそれぞれに特有な名前/IDによってシェーダーのタイプを得しています。よって、重要なのは glAttachShader() を2回(VSH + FSH)呼ぶということ。VSH を2つアタッチしたり、3つ以上のシェーダーをアタッチしたりするとちゃんとリンクされない。

(補足:以下、Program Object によってシェーダーペアがリンクされたオブジェクトを「プログラム」と呼んでいるみたい。)

僕らはたくさんのプログラムを作ることができるが、OpenGL はどのプログラムを使うのか?OpenGL はプログラムオブジェクトのためのアームとフックは持っていない。じゃあどうやってそれを知るのか?実はここだけ例外的な処理方法であり、バインドの関数ではないが以下のメソッドを使うことによってフックと同じように使うことができる。

GLvoid glUseProgram(GLuint program)

この関数以降に書かれた glDraw*() メソッドではこのプログラムが使われることになる。glUseProgram(0) で現在のプログラムを解除する。

では実際のコードを見てみよう。

GLuint _program;
 
GLuint createShader(GLenum type, const char **source)
{
    GLuint  = glCreateShader(type);
 
    glShaderSource(name, 1, &source, NULL);
    glCompileShader(name);
 
// "DEBUG"はデバッグのためのマクロ
#if defined(DEBUG)
 
    GLint logLength;
 
    // GL_INFO_LOG_LENGTH を使う代わりに、COMPILE_STATUS も使える。
    // 僕は前者が好き。もしコンパイル成功なら長さはゼロになり、
    // もしエラーがでたらどのみち GL_INFO_LOG_LENGTH は必要なので。

    glGetShaderiv(name, GL_INFO_LOG_LENGTH, &logLength);
 
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(name, logLength, &logLength, log);
 
        // Shows the message in console.
        printf("%s",log);
 
       free(log);
    }
#endif
 
    return name;
}
 
GLuint createProgram(GLuint vertexShader, GLuint fragmentShader)
{
    GLuint name;
 
    // プログラムの名前/IDを作成
    name = glCreateProgram();
 
    // VSH と FSH を接続
    glAttachShader(name, vertexShader);
    glAttachShader(name, fragmentShader);
 
    // それらを OpenGL のコアへリンクさせる
    glLinkProgram(_name);
 
#if defined(DEBUG)
 
    GLint logLength;
 
    // この関数はシェーダーのとはちょっと違う。
    glGetProgramiv(name, GL_INFO_LOG_LENGTH, &logLength);
 
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
 
        glGetProgramInfoLog(name, logLength, &logLength, log);
 
        printf("%s",log);
 
        free(log);
    }
#endif
 
    return name;
}
 
void initProgramAndShaders()
{
    const char *vshSource = "... Vertex Shader source using SL ...";
    const char *fshSource = "... Fragment Shader source using SL ...";
 
    GLuint vsh, fsh;
 
    vsh = createShader(GL_VERTEX_SHADER, &vshSource);
    fsh = createShader(GL_FRAGMENT_SHADER, &fshSource);
 
    _program = createProgram(vsh, fsh);
 
    // シェーダーの廃棄
    // シェーダーはコンパイルされてしまええば OpenGL 内に保持される。
    glDeleteShader(vsh);
    glDeleteShader(fsh);
 
    // ここでこのプログラムを使うために _program 変数をつかえるようになった。
    // もしオブジェクト指向なプログラムとしてつくっているのなら
    // ここでクラスのインスタンス変数などに格納すること。
    // glUseProgram(_program);
}

OpenGL オブジェクトを作る関数から独立させるような形でテンプレートを作るとだいたい上のようになる。使うシェーダーとそのプログラムだけ変更すれば良い。

ここまではシェーダーとそのプログラムの作り方ここからは Shader Language (SL)、とくに GLSL ES について見ていこう。

Shader Language

SL はC言語によく似ている。変数宣言と関数定義の書き方は同じだし、if-else やらループ文の書き方も同じでマクロも書ける。SL は可能な限り最速になるように作られている。よってループや条件分岐の使用はコストが高いので注意が必要である。シェーダーは GPU によって処理され、浮動小数点での計算が最適化されているということを覚えておこう。この処理のため、SL は3D世界で使うための独特なデータ型をもつ。


(リスト省略/> 元ネタサイトへ

vec 系のベクターデータはドット演算子で、もしくは[ ]演算子でそれぞれの要素にアクセスすることができる(アクセサ)。{x, y, z, w} などの表記が要素を表している。例えば、aaa.xyz はve4 型の aaa の中のはじめの3つの状態変数を表すことができる。この要素表記の順序を変えることもできる。例えば aaa.yzx というのもOK。また、{x, y, z} と {r, g, b} と同時に持ったようなものもある。重要なのはこれらをミックスもできる、ということ。例えば aaa.xrt という表記も可能。

vec4 myVec4 = vec4(0.0, 1.0, 2.0, 3.0);
vec3 myVec3;
vec2 myVec2;
 
myVec3 = myVec4.xyz;        // myVec3 = {0.0, 1.0, 2.0};
myVec3 = myVec4.zzx;        // myVec3 = {2.0, 2.0, 0.0};
myVec2 = myVec4.bg;         // myVec2 = {2.0, 1.0};
myVec4.xw = myVec2;         // myVec4 = {2.0, 1.0, 2.0, 1.0};
myVec4[1] = 5.0;            // myVec4 = {2.0, 5.0, 2.0, 1.0};

次は変換そ見てみよう。これについてはいくつか注意が必要。SL は値の最小値と最大値を定義して値の幅を定めており、これを Precision Qualifiers という。

変数宣言で使うことのできるキーワードは多くない。以下がそのリストになるが、これはメーカーによって桁数が増える場合がある。よって下のリストは SL に必要な最小レンジである。

(表省略/> 元ネタサイトへ

これらの修飾子を型宣言で使う代わりに、"precision" というキーワードを使って修飾子のデフォルトを設定することもできる。また、あまりよくないことではあるが、これらの修飾子を使って異なるデータを変換できる。例えば小数点型から整数型に変換するために、 mediump float と lowp int を使うべきであり、lowp float から変換すると -2.0 〜 2.0 にまるめられてしまうので注意しよう。埋め込み関数も使える。

precision mediump float;
precision lowp int;
 
vec4 myVec4 = vec4(0.0, 1.0, 2.0, 3.0);
ivec3 myIvec3;
mediump ivec2 myIvec2;
 
// ↓はデータ型の連携がないのでダメ
//myIvec3 = myVec4.zyx;
 
myIvec3 = ivec3(myVec4.zyx);    // This is OK.
myIvec2.x = myIvec3.y;          // This is OK.
 
myIvec2.y = 1024;
 
// ↓は大丈夫ではあるが、値の型の正確性が異なっているので
// myIvec3.x の値は型の範囲制限で256にまるめられる。
myIvec3.x = myIvec2.y;

GPU で直接に処理することによるもう1つの利点は浮動小数点の計算である。掛け算やその他の計算をごく簡単に実行することができる。行列、ベクター、小数点型は完全に互換性があり、いろいろできる。以下は例。

mat4 myMat4;
mat3 myMat3;
vec4 myVec4 = vec4(0.0, 1.0, 2.0, 3.0);
vec3 myVec3 = vec3(-1.0, -2.0, -3.0);
float myFloat = 2.0;

// mat4 は16の要素を持ち、4つの vec4 で初期化できる。
myMat4 = mat4(myVec4,myVec4,myVec4,myVec4);
 
// それぞれの要素に掛け算
myVec4 = myFloat * myVec4;

// 4x4行列と1x4行列の計算に相当。結果は vec4 型。
myVec4 = myMat4 * myVec4;
 
// アクセサを使って異なる配置のベクターを乗算
myVec4.xyz = myVec3 * myVec4.xyz;
 
// mat4 のはじめの9エレメントで mat3 を作成
myMat3 = mat3(myMat4);

// 3x3行列と1x3行列の乗算に相当。結果は vec3 型。
myVec3 = myMat3 * myVec3;

もちろんCのように配列を使って初期化することもできる。SL はどのシェーダーも void main() を持つように定めている。シェーダーの処理はここから開始する。これを持たないシェーダーはコンパイルされない。SL はインラインな言語だと覚えておこう。つまりは関数の前方宣言をするべしという話。よって void main() は大体最後になる。

では 頂点シェーダー(VSH) とフラグメントシェーダー(FSH) の作るものについてもっと見てみよう。

> 2/3-4 へ

All about OpenGL ES 2.x 2/3 -2

2/3-1 の続き

Textures 


テクスチャはいろいろ話すことがあるが、長くなりすぎないように書いてみよう。発展の内容については第3パートで書くつもり。

まずすることは画像のサイズを Power of Two (POT) に合わせること。つまり一辺の長さを2, 4, 8, 16, 32, 64, 128, 256, 512, 1024(=max)ピクセルにするということ。これは GPU が使いやすいようにするため。
(補足:実装系によっては POT でなくても OK な場合がある。iOSもそうだとか。)

もう1つ。一般に JPG や PNG, BMP などは左上の隅の頂点を (0,0) として書かれているが、OpenGL は右下隅を (0,0) として書くので、それにあわせておくこと。縦方向に反転させておけば良い。
(元ネタサイトの画像参照)

論理を簡単に言うと、OpenGL のテクスチャは以下のように使う。ある画像に対して、情報を16進数数字として取り出す。アルファの情報も取り出す。OpenGL が扱えるのは RGB および RGBA 形式。アルファは16進数数字とは別に読みだす。そしてこれらをすべてピクセルの配列データに格納する。

このピクセルの配列データ(texel ともいう)は OpenGL によって扱いやすいフォーマットとして GPU や フレームバッファの中に保管される。

ここからが少し難しい。個人的にも少し不可解。OpenGL は "Texture Units" という物を持ち、これはふつう32個ある。これは「保存されたピクセル配列」と「実際のレンダリング処理」の一時的なつながりを表す。僕らはシェーダー(特にフラグメントシェーダー)でこの Texture Units を使うことになる。1つのシェーダーは8つ(メーカーによって16) のテクスチャを使うことができる。さらに、頂点シェーダーとフラグメントシェーダーのペアが使うのも8つまでに制限されている。混乱してる?えっと、もし君がたった1つのシェーダーで Texture Units を使っているのなら8つまで使える。しかしもし君がシェーダーのペアで使っていたら8つ以上のコンバインドされたテクスチャユニットをつかうことはできない。

OpenGL はシェーダーで使うための32のテクスチャを保持できるが、シェーダーは8つまで。なんというセンス。ポイントは Texture Units は32個まで設定できるが、たくさんのシェーダーを通して使うということ。しかしもし33番目のシェーダが必要なら1番はじめの要素を削除しなければいけない。

混乱してるでしょ。わかってるって。
(補足:俺もわからん。理解したら追記します。)

ではイラストで説明してみよう。(元ネタサイトへ。)

テクスチャユニットは複数のシェーダーペアによって、いろんな時に使われる。これがよくわからない過程なのだが、Khronos (OpenGL の作成元)の視点で考えてみよう。「シェーダーはすっばらしいっのだ、ハラショー」。しかし Khornous の開発者は一方でこう言う。「GPU でもって超高速処理されてね。だけどテクスチャはさ、うーん、ほらテクスチャって CPU の側にあってデータでかいでしょ、だからシェーダになんとかして渡さないと。うーん、シェーダーみたいに GPU に直接処理するようなテクスチャの仕組みをつくって。それから GPU の上で動いている Texture Units の数を制限してしまえば。でもってGPU のキャッシュは速くてスバラシイ。だからセットアップでユーザにテクスチャを Texture Units につなげてもらって、シェーダーが利用できるようにしよう!」

普通、Texture Units はフラグメントシェーダーによって使われるが、頂点シェーダーもテクスチャを覗き込むことができる。普通の処理では無いが、ときどき便利。

まあ、覚えてほしいことは2つ。まずは glActiveTexture() でもって Texture Units を有効にして、 glBindTexture() でテクスチャの名前をバインドする。それから Texture Units を32 までにするということ。この32という数字はメーカーによって異なる場合がある。

OpenGL のテクスチャの扱いはこのくらいにしておこう。実際にするのは3つのこと。テクスチャオブジェクトを生成、それからそのテクスチャのバインドと設定。

GLvoid glGenTextures(GLsizei n, GLuint* textures)
        n: いくつテクスチャの名前/IDを生成するか
        textures: 生成したテクスチャの名前/IDの変数へのポインタ
                  複数を生成したなら配列の先頭のポインタを返す
GLvoid glBindTexture(GLenum target, GLuint texture)
        target: テクスチャの種類を指定する。以下のどちらかを指定する。
            GL_TEXTURE_2D: 2次元テクスチャのセット
            GL_TEXTURE_CUBE_MAP: 3次元テクスチャのセット
        texture: バインドするテクスチャの名前/ID

3D Texture は割愛。第3パートで。

2D テクスチャを生成したら、その設定をする。Khronos は OpenGL の中心部のことをサーバーと読んでいるが、テクスチャを定義することを彼らは"upload"する、と言っている。テクスチャデータをアップロードするために、次のメソッドを使う。

GLvoid glTexImage2D (GLenum target,
                 GLint level, GLint internalformat, GLsizei width,
                 GLsizei height, GLint border, GLenum format,
                 GLenum type, const GLvoid* pixels)
    // 引数の説明は省略

引数は多いが作りは港のクレーンのフックと同じ。このメソッドによって最後にバインドされたテクスチャに設定がなされる。

mip map も OpenGL の描画のために特有の設定。最初にテクスチャの小さめを作っておいて、最終サイズによって異なるサイズのテクスチャを使う、といってようなもの。とりあえずそんだけ。

mip map を設定したら、カラーフォーマット、画像サイズ、データフォーマットと最終的なピクセルデータのフォーマットなどを決めていく。ピクセルごとで2バイトのデータにすると良い。

Rasterize


ラスタライズは言ってみれば3次元のオブジェクトの形状を2次元に変換する処理のこと。それからフラグメントシェーダーによってばらばらな情報が可視化される。

このチュートリアルの最初の絵で Rasterize は Programmable Pipeline の単なる1つのステップとして書かれているが、どんな3Dオブジェクトもここを通ってから2Dに変換される。重要だよ。

ラスタライズはシーン内の3Dオブジェクトに発生し、フレームバッファを更新する。そしてこのプロセスにはいろんな手段でアクセすることが可能である。

Face Culing

"Culling" とは表面・裏面の可視化のこと。OpenGL は見えない面を見つけて削除する(描画しない)機能がある。簡単な3Dアプリケーションを考えてみよう。例えば床があって、床の裏は描画する必要がないとする。デフォルトでは OpenGL は平面の両面を描画してしまうだろう。これに Culling を設定してやることによって片面だけが描画されるようになる。OpenGL は頂点の配置によって表なのか裏なのかを判断する。

例えば三角形の頂点を「1つめ頂点、2つめ頂点、3つめ頂点」の順に配置した時に反時計回りならば「表」、時計回りならば「裏」と判断される。(元ネタサイト画像参照)

この機能を使うために glEnable()GL_CULL_FACE を設定し、上で説明したような頂点の並びになっていれば良い。しかし細かく設定したいなら以下のメソッドで。

GLvoid glCullFace(GLenum mode)
        mode: どの面を culling するか。以下のいずれかを設定する。
            GL_BACK : 背面描画しない。=デフォルト
            GL_FRONT: 表面描画しない。
            GL_FRONT_AND_BACK: 両面描画しない。
GLvoid glFrontFace(GLenum mode)
        mode: どの面を表面とみなすか。
            GL_CCW: 反時計回りに配置された面。=デフォルト
            GL_CW : 時計回りに配置された面。

このステップはラスタライズの第1段階であるので、これによってフラグメントシェーダーが処理を行うかどうかに影響する。
(補足:描画しない面だったらフラグメントシェーダーは処理必要なしになる。)

Per-Fragment Operation (和訳:断片ごとの処理)

もう一度 Programmable Pipelineの図で、Fragment Shader の処理過程で何が起きているのか見てみよう。(元ネタサイト画像参照)

フラグメントシェーダーと Scissor Test の間に小さな処理 "Pixel Ownership Test" が行われています。ここで「OpenGL の内部バッファ」と「EGLのカレントコンテキスト」の2つの間で ownership が決定されます。これは見えない部分なのであまり関係ない。

これまで見てきたなかで新しく目に止まるのは "Logicop" ですね。これは値を0から1.0の間に固定したり、フレームバッファに最終的な色を渡したりするような内部の処理です。これも心配しなくてOK。見るべき箇所は紫のボックス。

紫の部分の処理はデフォルトでは行われないので使いたいなら glEnable() で使うことを明示しなければならない。このメソッドの引数と、それぞれの過程の内容をいかに示す。

    Scissor Test: GL_SCISSOR_TEST – 描画域外のものを除外
    Stencil Test: GL_STENCIL_TEST - マスク。黒指定された箇所は除外
                                    ステンシルバッファが必要
    Depth Test: GL_DEPTH_TEST – カメラから見て奥行きを外れるものは除外
                                デプスバッファが必要
    Blend: GL_BLEND – カラーバッファとのブレンド
    Dither: GL_DITHER – システムによってフレームバッファに使える色は限定されている。
                        で、その色の利用を最適化するかの設定。On or Off のみ。

このうちいくつかは特有のバッファを設定するようなメソッドが必要です。しかしここでは細かい内容は言わない。重要なのはこれらをカスタマイズすることができる、ということ。

 もう一度 Programmable Pipelineの図へ戻ろう。3Dオブジェクトを描画するときに、glDraw* から始まってフレームバッファまですべてのパイプラインが実行される。しかし EGL の API があるわけではない。3Dゲームなどの複雑なシーンを考えてみよう。

君は何十のものを描画できるかもしれないが、そこには何百のオブジェクトが待っているかもしれない。1つオブジェクトを描いた時点でフレームバッファへの書き込みが開始する。もし続いて描画される3Dオブジェクトが Fragment Operation によって除外される断片をもつのならば、それらはフレームバッファに書き込まれない。しかしこれはすでにフレームバッファに書きこまれている情報が変更されはしないということは覚えておこう。最終的なイメージは多数のシェーダー、ライト、効果やオブジェクトによって生成する2次元画像です。よって、すべての3Dオブジェクトは頂点シェーダーによる処理過程を持ち、フラグメントシェーダーによる処理過程もあるということがわかるでしょう。

ラスタライズが複数のステップを持つということがわかりました。次に登場するのは最も重要なステップ、シェーダー!

> 2/3-3 へ

2012年3月30日金曜日

Objective-C のアクセス指定

Objective-C のアクセス指定がこんがらがってきたのでまとめてみます。規模は大きくならない、@public などは使わないなど切り捨てはしていますが、大体の基準としては使えると思います。といっても内容の正確性は話半分で、ということで。

参考:Apple公式日本語ドキュメント
参考:詳解 Objective-C 2.0 第3版 (to Amazon)

  • インスタンス変数については「後ろに」アンダースコアをつけるスタイルで書いています。前に入れない理由は「将来的にも余計なトラブルが避けられる」から。
  • #import など一部省略しています。
  • ARC ありき。

// File = "MyClass.h"
@interface MyClass : NSObject
{
    // デフォルトは @protected になる
    NSMutableString *name_;
    
 @private   // このクラスのみアクセスできる
    NSMutableString *namePrivate_;
    
 @protected // このオブジェクトとサブクラスからアクセスできる
    NSMutableString *nameProtected_;
    
 @public    // どこからでもアクセス可
    NSMutableString *namePublic_;
}

-(void)sayValue; // 外部からアクセス可能なメソッド
+(void)sayMaster;// クラスメソッド : MyClass alloc みたいなの。

@end

ヘッダーファイルで宣言されたメソッドは外部に公開されます。中身は .m ファイルで実装します。

ここで宣言せずに、.m ファイルに内容だけ書いた場合は外部に公開されません。ただし公開されないだけで存在するのは変わらないので呼び出しは出来てしまいます(コンパイラ警告は出る)。

C言語の範囲になりますが、static 変数とグローバル変数について復習。

// File = どこでもOK

// グローバル変数
int gloval_value = 555;       // すべてのファイルで静的に利用される。

static int staticValue = 444; // これが宣言されたファイルだけで静的に利用される。
             // (ファイルが別なら)サブクラスでは利用できない。---> クラス変数   

void printStaticValue(void)   // C言語の関数
{
    static int inDoor = 12;   // このメソッド内だけで静的に利用される
    printf("inDoor :%d", inDoor);
}

// この関数が宣言されたファイルだけで利用できる。
void static changeStaticValue(void)
{
    staticValue = staticValue * (-1);
}

「静的に利用される」ってよくわからないですね。つまりは「内容がずっと保持される」という意味です。

詳解Objective-C 2.0 曰く、クラス変数については「原則的には外部には見せないものだと考えてクラスの設計を行うべき」だそうです。もし外部からアクセスしたいならクラスメソッドのアクセサ(後述)を介して行います。

インスタンス変数へのアクセスは -> を使用して行います。

// File = "main.m"
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        MyClass *myinstance = [[MyClass alloc] initWithName:@"AAA"];

        myinstance->namePublic_ = [NSMutableString stringWithString:@"abc"];
        NSLog(@"my public :%@", myinstance->namePublic_);
    }
    return 0;
}

また、例えば @private 指定のインスタンス変数について、クラス内で同じクラスのインスタンスに対して aInstance->value と参照することができます。

また、インスタンス変数は実装部に書くこともできます。

// File = "MyClass.m"
@implementation MyClass
{
    int valueAtImpl_;
}
@end

違いは「実装部宣言だとサブクラスからも見えなくなる」ということ(ヘッダーファイルに情報が無いから)。

オブジェクト指向の立場からは @public をつけてインスタンス変数をさらけ出すことは一般におすすめされません。そこでアクセサを使用します。Setter と Getter のこと。

// File = "MyClass.h"
@interface MyClass : NSObject
{
    int value_;// .m ファイルに書いても OK
}
-(id)initWithNumber:(int)num;
-(void)setValue:(int) aValue;// Setter 宣言
-(int)value;                 // Getter 宣言
@end
// File = "MyClass.m"
@implementation MyClass

-(id)initWithNumber:(int)num
{
    self = [super init];
    if (self != nil) {
        value_ = num;
    }
    return self;
}

-(void)setValue:(int) aValue // Setter の実装
{
    value_ = aValue;
}

-(int)value // Getter の実装
{
    return value_;
}
@end

これで以下のようにアクセスできるようになる。

// File = "main.m"
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {   
        MyClass *myinstance = [[MyClass alloc] initWithNumber:12];
        
        myinstance.value = 25;
        NSLog(@"value is :%d", myinstance.value);
    }
    return 0;
}

適切なアクセサが定義されていればドット演算子で self, super のプロパティにアクセスすることもできるようになります。

// クラス内
val = self.value;
self.value = val;
val = super.value;
super.value = val;

しかしいちいち単純な Setter と Getter を書くのは面倒です(もちろん Setter で値の正当性をフィルタリングする場合などは実装は自分で書く必要があります)。とりあえずインターフェイス側を簡単にするため、宣言プロパティという仕組みを使います。

// File = "MyClass.h"
@interface MyClass : NSObject

-(id)initWithNumber:(int)num;
@property int value;

@end

「value というプロパティがある」ということを宣言する感じですかね。実装部の Setter と Getter の名前は先程作成したような名前(value & setValue:)をつけて、それがネーミングのルールにしたがっていなければいけません(ルールは省略)。

インターフェイス側の記述が簡単になりました。実装側のファイル (.m) はそのままでもOKですが、やはり1つ1つ書いていくのは面倒です。そこで、 @sythesize というキーワードを使います。

// File = "MyClass.m"
@implementation MyClass
{
    int value_;
}

@synthesize value = value_;

...
@end

この1行だけで myinstance.value という表記でインスタンス変数にアクセスできるようになります。裏側では適当な Setter と Getter が自動的に作られるようです。value_ = num; の箇所は self.value = num; と書くこともできます。なお、

@synthesize value = value_;

のイコール以下を省略出来る場合がありますが、推奨されていないようなので省略。

また、Setter で値の正当性をチェックしたり、Getter で計算で求めた値を返すような場合はやはり自前で書かなければいけません。例えば設定した値の2倍の値を返す Getter を書くとすると、以下のように @dynamic というキーワードを使用します。

// File = "MyClass.h"
@interface MyClass : NSObject

@property int value;
@property (readonly) int twoTimes;
-(id)initWithNumber:(int)num;

@end
// File = "MyClass.m"
@implementation MyClass
{
    int value_;
}
@synthesize value = value_;

@dynamic twoTimes;

-(int)twoTimes {
    return 2 * value_;
}

-(id)initWithNumber:(int)num
{
    ...
}
@end

宣言部で readonly というキーワードがありますが、これは「Getter だけしかありませんよ」という意味。この readonly のようにつけることのできるオプションは以下のようなものがあります。複数つけるならカンマ区切り。

// getter/setter 指定。実際の処理を行うメソッドを指定する。
// ただし呼び出し自体は変わらず、myinstance.value。
@property (getter=gainMaster) int value;
@property (setter=setMaster:) int value;// コロンを忘れずに

// 読み書き
@property (readonly) int value;
@property (readwrite) int value;

// 値の扱い
@property (assign) int value;
@property (retain) int value;
@property (unsafe_unretained) int value;
@property (strong) int value;
@property (weak) int value;
@property (copy) int value;

// 複数スレッド上で同時に実行されるかもしれないけど OK という意味。
@property (nonatomic) int value;

ARCを利用している場合にクラスオブジェクトに「値の扱い」のオプションを指定していないとコンパイル時に警告が表示されます。

オプションの細かな内容についてはリファレンスなどで。nonatomic は「頻繁にアクセスされるが、スレッドが競合する危険がない」場合に有効です。

追記:プライベートなアクセサを書きたい場合は実装ファイルの冒頭のクラスエクステンションに記述します。これはヘッダーファイルに書かれたものとして扱われるようです。記述例はまとめのコードで。

その他、カテゴリなどのアクセスについても情報はありますが、実装部ファイルを分割したり既存クラスを拡張したりしない限り必要ないでしょう。

それから、プライベートなメソッド名についてはサブクラスでの予期しないオーバーライドの危険性があるため、固有なプレフィックスをつけることが推奨されています。具体的には組織名を接頭語に付けるなどするらしいですが、詳しくは知りません。

まとめ。

// File = "MyClass.h"
@interface MyClass : NSObject
{
    // サブクラスでも使うならここ
    int valueSharedWithSub_;
}

// 外部に公開する値は @property キーワードでアクセサ宣言
// クラスオブジェクトはオプションを忘れずに。
@property int value;
@property(weak) MyClassB *instanceB;

-(id)initWithNumber:(int)num;
-(void)sayValue; // 外部に公開するメソッド
+(void)sayMaster;// クラスメソッド

@end
// File = "MyClass.m"
@interface MyClass ()

// クラスエクステンション部
@property (nonatomic, strong) NSString *message;

@end

@implementation MyClass
{
    // このクラスにプライベートな値はここ
    int value_;

    __weak MyClassB *instanceB_;
    __strong NSString *message_;
    // @property のオプションと適合した宣言になっていること
    // (ただし違っていても警告は出ない。)
}

// クラス変数 : 外部からアクセスさせるならクラスメソッドからのアクセサで。
static int onlyThisClass = 123;

// グローバル変数 : プレフィックス推奨。
int RACMasterValue = 555;

// 自前処理でなければ @synthesize 書いてアクセサ実装
@synthesize value = value_;
@synthesize instanceB = instanceB_;
@synthesize message = message_;

-(id)initWithNumber:(int)num
{
    // 外部公開メソッドの実装
    self = [super init];
    if (self != nil) {
        value_ = num;
    }
    return self;
}

-(void)sayValue {
    ...// 外部公開メソッドの実装
}

+(void)sayMaster {
    ...// クラスメソッドの実装
}

-(void)funcInner {
    ...// プライベートなメソッド(サブクラスも見えない)
}

@end

プライベートなメソッドで実現できるので static 関数はいれてません。外部からアクセスされないという点ではプライベートなメソッドよりも安全性は上ですが、クラス内部のインスタンス変数などにはアクセスできないので、使い所はせまいでしょう。

@public は使うべきではないと書きましたが、構造体様クラスでは @public でも良いでしょう。ARC を使っていると構造体(+ 共用体)はクラスオブジェクトのメンバを持てないという制約があるからです。もちろんそのクラスにアクセサを作っても良いですが。

それから、Setter と Getter を通すことによるオーバーヘッド(実行速度の低下)については問題にならない程度だそうです。自前処理がなければインスタンス変数にロックだけ加味されて直通状態でコンパイルされるのではないでしょうか。一般的には生産性の面からは利用すべき。


以上。


考えて書いたお陰で頭の中でまとまった感じがします。Java やらそっち系統でなれていると理解するのに時間がかかります。プライベートなメソッドは見えないけど呼び出せるというあたりの「緩さ」が Objective-C らしいといえば「らしい」のでしょうか。

間違いなどありましたら、ご指摘おねがいしマス。

2012年3月25日日曜日

Khronos EGL and Apple EAGL

Open GL の「ESの」詳しいドキュメントがあったので、2ページ目補足ページを読みがてらのメモ。
元ネタ:Khronos EGL and Apple EAGL


話題は EGL と、Apple によって実装された EGL の API である EAGL である。OpenGL のアプリケーションがどのようにデバイスの描画に関わっているのかを解説する。

Objective-C および iOS にフォーカスしてます。他のデバイスでも同じように書けますが。


At a glance



実際にスクリーンに対して描画処理を担当するのは OpenGL ではなく、Khronos group が作った EGL API である。EGL はデバイスメーカーによって作りが違うので注意。


Setup EGL API


まず最初に「どこに内容を描画するか」を EGL に教える。
eglGetDisplay メソッド。EGLDisplay data type を返す。デフォルトのスクリーンならば EGL_DEFAULT_DISPLAY を引数で与える。

次は eglInitialize メソッドで初期化。


その次にいろいろ設定(色フォーマット、色、サイズ、透過などなど)をするわけだが、eglGetConfigseglChooseConfigs などの多数の便利メソッドが提供されているのでこれらを使えば良い。

最後にスクリーンの表に描画するのか、裏(オフスクリーン面)に描画するのかを指定する。しかしオフスクリーン面に書きたいのなら、OpenGL の API が提供するフレームバッファーに描画したほうが速い。

以上はプラットフォームごとの性質。ここからは OpenGL とのつながりを見ていく。


EGL Context


ここまでで EGL は描画機器への接続は完了したが、OpenGL との設定をしなければいけない。EGL は2つのフレームバッファを持っていて、OpenGL の描画を適正化している。

EGL にどこに「僕らが描画したい OpenGL のバッファ」があるかということを教えてやる。具体的には EGL Context をつくって、それを「現在の context」として設定すればOK。context は複数作成可能。設定してしまえば OpenGL で使っている subsequentな(?)フレームバッファが EGL context によって利用される。これらの設定は eglCreateContext() および eglmakeCurrent() のメソッドで行う。

ねえねえ、混乱してる?大丈夫さ。

ここで重要なのは「EGL context」が OpenGL とのつながりだ、ということを理解しておけばOK。


Rendering with EGL Context


設定が完了したのであとはバッファーを切り替えればOK。切替え?

EGLの1面が画像を表示しているとき、もう1面は裏で待機していて新しいレンダリングがされる。次の切替えタイミングでこの裏面が表に出て、表示される。それまで表だった面は裏になり初期化sれる(元ネタサイトの画像参照のこと)。

この手法はアプリケーションのパフォーマンスを向上させる。なぜなら描画コマンドの発行のたびに本当の画面が描画されるわけではないし、デバイスに直接よりもバッファに書きこむほうが速い。面の切り換えは eglSwapBuffers() メソッドで行われる。

手順のまとめ
  1. EGL API をメーカーから提供されるディスプレイと surface で初期化
  2. 僕らが決めた設定をセット
  3. context をつくって、それを現在の context として設定
  4. EGL に画面を切替えさせる


EAGL – The Apple’s EGL API


もっとも重要なことは「Apple は直接的な描画を許していない」ということ。Frame Buffer と Render Buffer を通しての作業ということになる。

なぜ?Cocoa Frameword は全く別種の言語であり、ルールが超厳しい。そして Apple は EGL の API  も Cocoa のルールに従うように修正した。

EAGL を使い、color render buffer を作って、これに出力を書き込む。そしてこのバッファを Apple 謹製の CAEAGLLayer と呼ばれるレイヤーに描画する。それから、context にこのバッファを発行するように頼む必要がある* 。実際にはこの命令はバッファーの切替えのようなものである。
(* 原文:ask the context to present that color render buffer.)


Setup EAGL API


まずは IUIView のサブクラスとして作り、 Core Animation layer を切り替える。具体的には以下のようなコードを書く。これは EGL API の初期化と同義。
+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

次は各種設定
- (id) initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame]))
    {
        // Assign a variable to be our CAEAGLLayer temporary.
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)[self layer];
 
        // Construct a dictionary with our configurations.
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:NO],
                             kEAGLDrawablePropertyRetainedBacking,
                             kEAGLColorFormatRGB565,
                             kEAGLDrawablePropertyColorFormat,
                             nil];
 
        // Set the properties to CAEALLayer.
        [eaglLayer setOpaque:YES];
        [eaglLayer setDrawableProperties:dict];
 
        // Other initializations...
    }
 
    return self;
}


EAGL Context


次のステップもまた、Apple が EGL API を Cocoa Framework に適正化してしまったよ。あらまあ。EAGL API は2つのobjective-C のクラスを提供する。すなわち、EAGLContext と EGLContext である。

EGL からはいろいろ変わってしまったが、手順は簡単。OpenGL のバージョンを指定して EAGLContext を初期化すればOK。EGLDisplay やら EGLContext などの処理はやらなくて良い。
@interface CustomView : UIView
{
    EAGLContext *_context;
}
 
@end
 
@implementation CustomView
...
 
- (id) initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame]))
    {
        // Other initializations...
 
        // Create a EAGLContext using the OpenGL ES 2.x.
        _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
 
        // Make our new context, the current context.
        [EAGLContext setCurrentContext:_context];
    }
 
    return self;
}

...
@end


Rendering with EAGL Context


本当はここで Frame Buffer をつくる段階なのだが、ここでは省略する。つぎのパート(2ページ目)で説明するので。

レンダリングは簡単。
[_context presentRenderbuffer:GL_RENDERBUFFER];
でもって描画面の切替えが行われる。

GL_RENDERBUFFER は定数で、内部でだけ使われる。このメソッドは描画準備が完了した段階で呼ばれるべきメソッドである。

以上でおおよそ完了。OOP が好きなら UIView のサブクラスを作るなりすること。


Conclusion


いろいろ書いてきたけど覚えておいて欲しいのは
「メーカーの EGL の説明をちゃんと聞いてね」
ということ。ここまで見てきたように、メーカーによって EGL の実装はかなり異なります。


以上。



たしかに今まで iPhone のサンプルコードを見たり、OpenGL のコードと照らし合わせたりしてきましたが、やっと違いがわかりました。たいていのサイトだと「OpenGL はプラットフォームを移動しても変わらないので良い!」としか書いてませんからね。

All about OpenGL ES 2.x 2/3

Open GL の「ESの」詳しいドキュメントがあったので、読みがてらのメモの2ページ目
1ページ目メモ:All about OpenGL ES 2.x 1/3
元ネタ:bd-interactively All about OpenGL ES 2.x – (part 2/3)

原文とあわせてお読みください。和訳の正確性と内容の信憑性については話半分ということで。


著者はこのチュートリアルの1つめが思っていたよりもビッグな話題になり、とてもびっくりしたということを冒頭で述べています。そりゃ、いい文章だもの。


At a glance


コード類はC、もしくは Objective-C で解説する。元ネタサイトからダウンロードできるのはiOS、とくに iPhone アプリのプロジェクトファイル。第1回チュートリアルの pdf もおいてある。

一般に OpenGL のメソッド名には"gl"もしくは"gl."のプレフィックスがつく。また、OpenGL が使用する型も独自に定義されている(GLfloat, GLiknt など)。これはマルチプラットフォーム対応のため。型については OpenGL の元ネタサイトに一覧表がある。

OpenGL ES は64bit のデータ型をサポートしていない。OpenGL ES の独自型を使えば大丈夫。

OpenGL の描画の各ステップについて解説を行う(元ネタサイトにそのイメージ画像あり)。しかしその前に EGL の説明を文章があるのでそちらを読んで欲しい。

> 補足ページヘ


Primitives


Primitives の構成要素の基礎となるのは vertex、つまり頂点情報。x, y, z の3つの数値を持つ。また、軽量化のために数値の羅列で表現される。
GLfloat point3D = {1.0 ,0.0, 0.5};
GLfloat line3D = {0.5, 0.5, 0.5, 1.0, 1.0, 1.0};
GLfloat triangle3D = {0.0, 0.0, 0.0, 0.5, 1.0, 0.0, 1.0, 0.0, 0.0};

線と三角形のデータでは区切りもないが、1つ目の数字はx座標, 次はy、次はz, x, y, z, x, y z,,,というように OpenGL が勝手に処理してくれる。

ちなみに、3Dモデルデータを読み込むような関数は(著者はあると思っていたが)無い。

じゃあ3Dモデルデータを僕らの OpenGL アプリケーションに導入するにはどうするかというと、サードパーティ製のエンジン使うのが一番簡単。もしくは .h ファイルとして書きだす。

で、(元ネタの)著者がシンプルは Objective-C ベースの 3Dエンジンをつくたのでよろしく!モデルデータの形式変更などが行える。
http://nineveh.gl


Meshes and Lines Optimization


ここで、3D空間の立方体について見てみよう。先に触れたようにポリゴン(三角形)の集合である。よって以下のように表現できる。
GLfloat cube3D[] =
{
    0.50,-0.50,-0.50,   // vertex 1
    0.50,-0.50,0.50,    // vertex 2
    -0.50,-0.50,0.50,   // vertex 3
    -0.50,-0.50,-0.50,  // vertex 4
    0.50,0.50,-0.50,    // vertex 5
    -0.50,0.50,-0.50,   // vertex 6
    0.50,0.50,0.50,     // vertex 7
    -0.50,0.50,0.50     // vertex 8
}

数値の精度についてはOpenGLは問題としない。ただ、メモリーの消費を考えると小数点2ケタぐらいを推奨する。

一般にメッシュは頂点・テクスチャ座標・法線の3つの情報を持つ。これらをまとめてもつような配列を考えると以下のようになる。これは Array of Structures と呼ばれる。
GLfloat cube3D[] =
{
    0.50,-0.50,-0.50,   // vertex 1
    0.00,0.33,          // texture coordinate 1
    1.00,0.00,0.00      // normal 1
    0.50,-0.50,0.50,    // vertex 2
    0.33,0.66,          // texture coordinate 2
    0.00,1.00,0.00      // normal 2
    ...
}

この手法でどんな種類の頂点でも書くことができる。単体のデータ型(ここでは GLfloat
 型)の1つの情報としてまとめなければいけないかと聞かれたらそのとおり。後で説明する。


Buffers


BufferObject は僕らの Primitive の配列のための理想的なデータ形式である。前に説明したように2つあって、頂点配列(vertexes buffer object = VBO) とインデックス配列 (Indeces buffer object = IBO)である。

ひとたび、もともとのデータから VBO を作ってしまえば元のデータは破棄することができる。OpenGL がデータをコピーして BO として保持するからである。BO は法線のデータ・テクスチャ座標のデータ・Structure のデータなどどんな種類のデータも保持できる。

IBO のデータ型は GLubyte かもしくは GLushort が一般的。たまに GLuintもあるが、オプションと考えるべきだろう。

さて、これらのバッファーを作っていこう。

FrameBuffer と RenderBuffer の作り方はほぼ同じ。手順としては1つ以上の名前 or ID を作り、それをバッファーオブジェクトにつなげる。それからプロパティを定義してデータをそこに入れる。以下のメソッドを使う。メソッドのリファレンスは省略。

GLvoid glGenBuffers(GLsizei n, GLuint* buffers)
GLvoid glBindBuffer(GLenum target, GLuint buffer)

港のクレーンの話に戻ると、BufferObject が持っているフックは2つある。すなわち、GL_ARRAY_BUFFER と GL_ELEMENT_ARRAY_BUFFER の2つである。設定メソッドは以下の2つ。

GLvoid glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)
GLvoid glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data)

1つめは BufferObject とその設定の内容をセットする。ここで引数に GL_DYNAMIC_DRAW を選択したのなら、それはバッファーオブジェクトの更新を後回しにして、glBufferSubData メソッドを使うことになる。

このメソッドは以前に定義された BufferObject のサイズの中限定で、一部を書き換えるというもの。でも著者は個人的には GL_DYNAMIC_DRAW は使いたくないらしい。結局シェーダーでできる機能なので(?)。

BufferObject が出来上がったらあとは好きなやつをつなげる(bind)だけ。ただし僕らが bind できるのは一度に一種類の BufferObject だけ。ある BufferObject が bind された状態になったら、すべての描画コマンドはその BufferObject を使うことになる。使い終わったら bind 状態を解除すること。


> 2/3-2 へ

All about OpenGL ES 2.x 1/3

Open GL の「ESの」詳しいドキュメントがあったので、読みがてらのメモです。
元ネタ:bd-interactively All about OpenGL ES 2.x – (part 1/3)
> part 2/3

原文とあわせてお読みください。和訳の正確性と内容の信憑性については話半分ということで。

追記:
この記事を書いたあとで元ネタサイトのコメント欄で、和訳の要約を自分のサイトで公開していい?と質問したところ。
Sure I liked it.
とお返事をいただきました。感謝ですね。

At a glance


OpenGL とは
  • さまざまなプラットフォーム、OSで使用できるグラフィックライブラリ
  • windows, mac, Linux
  • c, c++, Java, Perl, Javascript etc.
  • OpenGLES2.0 はPlaystation, Android, Nokia などに使われている。

著者は2004年の OpenGL2.0 のリリースで感激したらしい。 DirectX は嫌い。


3D world


3次元空間の視覚化とは視覚の再現である。perpectives*、消失点、歪み、深度、焦点、field of view。field of view、単眼/複眼、凹型/凸型レンズなどを理解すると良い。
* perspectives の間違い?

3Dとは3つの次元をもつから3Dである。うわ、何をする、落ち着け。ここで重要なのは「2Dに対して+1の次元を持つ」ということ。

例えば平面上で物体を45度回転するのは簡単。しかし3次元空間ではX軸回転してもいいし、Y軸でも斜めでも良い。当然、軸によって回転後の状態は異なることになる。

つまり次元が「+1」されたということによってかなり複雑になるってこと。

それから「時間」という概念を加えて考えると、3Dは4Dにもなりうる。


OpenGL into the 3D world


OpenGL がどのように動いているか、ということを説明する。

まずは港にあるクレーンを思い浮かべよう(元ネタサイトの写真をみるべし)。港にはコンテナがたくさんあり、なかには crate(木箱?)が入っている。
  • 港全体が OpenGL
  • コンテナが OpenGL オブジェクト (= Textures, Shaders, Meshes etc)
  • コンテナ内の crate は OpenGL を使って僕らが作ったもの (= our instance)
  • ポートクレーンは僕らが使える OpenGL の API

僕らが OpenGL のメソッドを呼び出すのはクレーンに指令をあたえるようなもの。
  • クレーンがコンテナをつかみ、
  • 持ち上げ、
  • そのままコンテナの中でいろんな処理をおこなう。
  • 処理が終わったらコンテナは港のどこかにまた下ろされる。

僕らは港には直接アクセスすることはできないし、コンテナの中身を変えることもできない。できるのはクレーンに指令を出すことだけ

不便なだけに見えるが、そんなことは無い。クレーンは強力で1秒間に何千何万回の指令をこなすことができる。また、 State Machine pattern で設計されているので僕らは余計な情報をもっておく必要がない(コンテナとして預けている)。IDを1つ持っておけば(クレーンの例で言うとコンテナを区別できれば)良いので楽チンである。


How OpenGL works


コンピューターには CPU と GPU がある。CPUはコンピューターや機器の計算処理装置であり、GPU (Graphics Processing Unit)はコンピュータや機器のグラフィックカードである。画像処理は大変なので、GPU がこれを担当していて、おかげで CPU の負担は軽減されている。

CPUに比べて GPU は浮動小数点の扱いにたけている。グラフィックカードの良し悪しで3Dゲームの動きが変わるのはこのため。

といってもOpenGL が完全に GPU の上に乗っかっているわけではなく、モノとしてはハードとは別物。OpenGL が使えるかどうかはそのグラフィックカードが OpenGL に対応しているかによる。


OpenGL’s Logic


プロの3Dソフトウェアは OpenGL でもって超スーパーウルトラ複雑奇妙奇天烈である。その OpenGL のロジックが処理できるのはいくつかのこと。
  • Primitives
  • バッファ
  • ラスタライズ
 マジ?3つだけ?

そう。ではこれらについて1つ1つみていこう。ちなみに2DだとしてもZ深度をゼロとした3Dで扱うことが可能。

・Primitives(和訳:原始的な、基本の)


プリミティブとは基本の形のこと。以下の3つをつかうことができる。
  • 3D Point:3次元上の1点 (x, y, z)
    → およびパーティクルに使用される。
  • 3D Line:1本の線(3D Point x2個で構成される)
    → 3D Vector に利用される。
  • 3D Triangle:1個の三角形(3D Point x3個で構成される)
    → メッシュ(≒立体の物体)の1面として利用される。
OpenGL のバージョンによっては四角形も使えるが、OpenGL ES ではパフォーマンスのため使えない。

・バッファ


簡単に言うと「一時的で最適なデータ倉庫」。OpenGL は3種のバッファをもつ。
  • Frame Buffer
  • Render Buffer
  • Buffer Object

レンダリングの時に、僕らは最終的なイメージをディスプレイに直接送ることもできるし、Frame Buffer に送ることもできる。では Frame Buffer はただの保管庫かというとそうではない。

最終的なイメージは1つのデータから作られるのではないことがほとんど。3Dオブジェクトならオブジェクトの深度もあるし、交差もしているかもしれない。Frame Buffer は画像の集合のように、頂点ごとのデータを保持している。

Render Buffer は一時的な1イメージのデータ倉庫。Frame Buffer は Render Buffer の集合である。Render Buffer にはいくつかの種類がある。
  • Color Render Buffer:最終的な「色」を表すイメージ
  • Depth Render Buffer:最終的なZ方向の深度(奥行き)の情報
  • Stencil Render Buffer:可視部分を制限するオブジェクト(マスクのように)。黒と白のイメージで構成される

Buffer Object は OpenGL がサーバーサイドと呼ぶ保管庫である*。他のバッファと異なり、データとして保持される期間が長い。3D オブジェクトの最適化された情報をもち、その種類は Structures(構造)と Indices(複:index)に分けられる。
(* 原文:Buffer Objects is a storage which OpenGL calls “server-side”)

Structures は頂点配列やテクスチャ座標配列などの3Dオブジェクトを描画するための情報のリスト。Indices は例えば3Dオブジェクトのある面がどうやって構成されているかを表す。

たとえば3次元空間の立方体について考える(元ネタサイトの画像をみるべし)。1つ1つの面は四角形だが OpenGL は扱えないので三角形にして、全部で12の三角形で描くことになる。

<以下意訳あり>
ここで、
  • 1つめの面は頂点Aと頂点Bと頂点Cをつないだ面
  • 2つめの面は頂点Bと頂点Cと頂点Dをつないだ面
  • 3つめの面は頂点Dと頂点Eと頂点Fをつないだ面
  • ・・・・・
とデータを持つこともできるが、効率が悪い。例えば1つめと2つめの面は線分BCを共有しており、それぞれの面が頂点の情報をそっくりそのまま持つのは無駄。

ではどうするかというと、頂点は頂点だけの情報リストとして持ち、面はその点情報のどれを使うかということだけを記録する。
vertex0 = new Vertex(20,20);
vertex1 = new Vertex(40,20);
vertex2 = new Vertex(40,40);
vertex3 = new Vertex(20,40);

vtxList = [vertex1, vertex2, vertex3, vertex4];

tri0 = new Triangle(0,1,2);
tri1 = new Triangle(1,2,3);
以上のコードで tri0 は vtxList 内の 0, 1, 2にある要素、つまりvertex0, vertex1, vertex2 で構成されることを表すことができる。ここでは三角形が2つだけなのでかえってデータ量は多くなっているが、これが膨大な量になったときは言わずもがな。

OpenGL の実際では以下のようになる。
vtxList = [vertex1, vertex2, vertex3, vertex4];
triList = [0,1,2,1,2,3]; 
この配列のデータが3で区切られて利用される。つまり1つめの面は vtxList 内の0, 1, 2の頂点で構成され、2つめの面はvtxList 内の1, 2, 3の頂点で構成される、となる。

ここでバッファの話に戻ると、vtxList が Structures、triList が Indeces となる。

・ラスタライズ

ラスタライズとは3次元のすべての情報(頂点・座標・数学など)を2次元へ変換する処理である。スクリーンは2次元だからね。

しかしこれをやるかやらないかは機器メーカーの勝手。OpenGL を管理している Khronos group は OpenGL のためにルール(EGL という API)を提供しているが、メーカーはこれを改変することができる。

よって OpenGL の API で直接に Frame Buffer に書きこむのではなく、各メーカーが決めているルールに従って描画処理を書く必要がある。このルール(= API) はメーカーから提供される。



OpenGL’s pipelines


ここで2つのパイプライン
  • Programmable pipeline:プログラム可能なパイプライン
  • fixed pipeline:固定パイプライン
がある。Programmable pipeline はまじで大変。

Programmable pipeline では開発者はカメラ・ライト・材質・効果などすべてのことを設定しなければならない。そしてこの設定はかの有名なシェーダーで行うことができる。Programmable pipeline はシェーダーだと捉えられることが多い。

Programmable pipeline はコードとしては断片的だが GPU の複雑な計算に直接につながっている。複雑ってどういうことかというと、あるピクセルの色を決めようとしたら、テクスチャの情報・ライトの情報(色、角度、位置、強さetc)・カメラの情報(色、角度、位置etc)、・・・といったように多数の情報が必要になる。でもってこれを設定しなければならん、と。

fixed pipeline はこれらのすべての情報をまとめて僕らに提供する。

シェーダーの設定にはC言語によく似た言語、OpenGL Shader Language (GLSL) を使う。Open GL ES は機能制限があり、OpenGL ES Shader Language (GLSL ES or ESSL) という言語になる。構文などは同じで使える範囲が異なるだけ。

シェーダーは頂点シェーダー(VS, or VSH) とフラグメントシェーダー(FSH) の2つではたらく。

頂点シェーダーはそれぞれの頂点で実行される小さなプログラム。頂点の最終的な位置を決める処理を行う。立方体ならその頂点の数だけ、つまり8回処理が行われる。

頂点自体の空間上の位置に加えて視点(カメラ)の情報も決定する必要がある。フラグメントシェーダーにいくつかの情報を渡さなければいけないが、僕らは頂点シェーダーを通さなければフラグメントシェーダーにアクセスすることができない。


立方体を眺めてみよう。立方体には8つの角と6つの面があるが、頑張っても同時に見ることが出来るのは7つの頂点とそれから構成された3つの面だけ。この「どのように見えるか」を処理するのがフラグメントシェーダーの仕事。テクスチャ・カメラ・ライト・重なりなどの要素をすべて考慮してスクリーン上のピクセルの色を決定する。

頂点シェーダーとフラグメントシェーダーが一緒に(1体1対応で)動いているということを知っておくこと。 結局のところ、 OpenGL のプログラムは頂点シェーダーとフラグメントシェーダーのコンパイルされた1対のものであって、それ以上のものではない。



Conclusion(まとめ)

  1. OpenGL のロジック部分は3つの Primitives から構成されている。
  2. fixed pipeline は重いしでかい。機能が固定されている。Programmable pipeline は簡単で速くて、いろいろ設定もできる。
  3. Programmable pipeline はシェーダーと同義語。頂点シェーダーとフラグメントシェーダーの2つで構成され、最終的にはプログラムの中にコンパイルされる。

と、このように理解するのは簡単だが、学ぶとなると、、、うーん。

次の2パートで実際のコーディングなどを見ていくが、もう1つの OpenGL のコンセプトを紹介しよう。


OpenGL’s Error API


港の例で紹介したように、OpenGL の内部に直接アクセスすることはできない。もしその内部でエラーが起こったらアプリケーションは止まってしまう。

この内部エラーを知るために、OpenGL から API が提供されている。2つの種類があって、1つはエラーがあるかを返すもの。もう1つはエラー内容を知るためのもの。

「チェック → エラー内容チェック」の流れを必要な箇所(バッファやシェーダの設定時など)できちんと行いましょう。


以上。


この著者さんは良い意味で OpenGL love なギークだなあという印象。でもって一般に面倒だと言われる Programmable pipelineを強烈プッシュ。

英語を流し読みする感覚がぼちぼち戻って来ましたが、次のパートは超膨大なのでどうしようかと考え中です。

"storage" の良い和訳ないですかねえ。保管庫にしてしまいましたが。

2012年3月23日金曜日

Xcode4 Console Project

XCode4 について読んできましたが、日本語ドキュメントも新しいものはXcode4.3ベースで書かれているようです。
Developer 日本語サイト: https://developer.apple.com/jp/resources/

これを見るかぎりiOS以外のドキュメントについては英語しかないみたい。

iOS関連の第1段階のチュートリアルとしてはiOSドキュメントの
  •  初めての iOS アプリケーション
  •  2つ目のiOSアプリケーション:ストーリーボード
  •  3つ目のiOSアプリケーション:iCloud
がよさそうです。pdfです。

全く関係ありませんが、このドキュメントがおいてあるページのフォントがおかしいんです。コピペしたら表示がおかしくて、アプリの「プ」の「フ」と「゜」が分離してました。なんですかね、これ。

それはさて置き、今回はコンソールで動くアプリケーションの作成をメモしてみます。Objective-C を書くだけなら iOS やら何やらは必要ありません。ソースコード内容の解説などはしておりません。内容の正確性については話半分ということで。

参考にした情報:http://d.hatena.ne.jp/keimina/20101006/1286351265
Xcode バージョン:4.3.1

プロジェクトの作成


Xcode を開いて File > New > Project

Create a new Xcode project を選択


サイドバーで Application 選択 > Command Line Tool を選択


プロジェクト名・適当な固有値を設定
Type は "C"(で Objecive-C も動作します)
一番下のチェックはいわゆるスマートポインタを使うかどうか。今回はチェック。


プロジェクトファイルを作成する場所を指定して Create で作成されます。
  • この時、下部のチェックを入れると

    Git リポジトリというものが作成されます。復元ポイントみたいなものです。知識があれば使って損は無いです(といっても自分はバージョン管理ツールを使った事無いですが・・・。)

開いた時点で main.m というファイルにコードが書かれています。

画面左上のRunで処理開始。Debug Area の右ウインドウに "Hello, World!" と表示されれば成功です。この main.m 内の main() が処理のエントリーポイントになります。


クラスの作成


これだけでは少し寂しいので簡単なメッセージを出力するクラスを追加してみましょう。

右側 utility area の下部ウインドウを File Template Library 表示にして、Objective-C class のアイコンをドラッグして左側 Navigation area へドロップ。


ダイアログが表示されるので適当な情報を入力して Create。


ヘッダーファイルと実装ファイルが作成されます。


Navigation ウィンドウで MyClass.h を選択し、コードを表示。デフォルトでテンプレ的なものが書いてあります。


インスタンス変数としてvalue、 initWithNumber と sayNumber の2つのメソッドを書きます。以下のようにコードを修正し、保存します。


MyClass.m を表示。以下のように実装を書いて保存。

initWithNumber() がコンストラクタだと思えばよいです。sayNumber() は自分が持っている数値(value)を表示します。Objective-C の細かいところについては各自。

main.m を表示し、以下のように修正。

Auto reference counting 関連については間違いがあるかも。これを実行するだけなら問題ないですが。

左上のRunを押して、デバッグウインドウに以下のように表示されれば成功。



以上。


あとは各自でクラス作ったり、処理を入力して組み合わせていけばOKです。

Xcode4.3 の情報が少なくて苦労しますね。コンソールアプリならXcode を使わずに、コマンドラインからやってもいいですが、Xcode の機能を覚えたいなら以上のようにXcode のプロジェクトとして作っていろいろやる方がよいでしょう。

CUI はなかなか理解されない今日この頃ですね。MS-DOS って知ってますか?