2012年3月31日土曜日

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 へ

0 件のコメント:

コメントを投稿