2012年4月4日水曜日

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

2/3 -3 の続き

ここからはメソッドの説明が多いのようなので概念的なところだけにします。長いし。

Vertex and Fragment Structures


まずはこの図を見てみよう。(元ネタサイトへ)ここでは Attributes, Uniforms, 変数(定義済み変数)、ビルトイン関数を紹介します。

頂点シェーダー(VSH) はいつも1つ以上の Attributes を持つ。Attributes は3Dオブジェクトの頂点を生成するために使われ、頂点ごとにのみ定義される。頂点の最終的な位置を決めるためには OpenGL の変数 gl_Position や gl_PointSize を、フラグメントシェーダーなら gj_FlagColor なんかを使うことになる。

Attributes, Uniforms, 変数は GPU と CPU の間の橋渡しをするものだ。僕らはレンダリングをする( = glDraw* メソッドを使用する)まえに、VSH などに Attributes の設定(?)を渡したいこともあるはずだ。その設定は頂点によって違うかもしれないし、同じかもしれない。デフォルトでは OpenGL の programmable pipeline は最低でも8つの Attributes の設定をすることができる。

一方 FSH には直接には設定をすることはできないが、VSH に Verying を設定して、これを FSH に設定を流しこむことはできる。しかしあまり一般的ではない。こちらも最低8つの
Veryings の設定をすることができる。

シェーダーにアクセスするためのもう1つの手段は Uniforms を使うことだが、名前が示すとおりこれらはすべてのシェーダーの処理において一定の値が用いられる。最もよく使われるのは sampler だ。覚えてるかな?テクスチャを保持するために使われるユニットだ。(補足:この和訳では省略した Shader Language の項のリストにデータ型として載っている。)中身としては  int 型だが、つくりは特別。サポートされる最低サイズはシェーダーによって異なり、VSH:128、FSH:16。

次は定義済み変数 (=Built-in Variables)。僕らはシェーダーに対してそれらを設定しなければならない。例えば VSH には頂点の最終的な位置を決めるが、これには gl_Position やら gl_PointSize なんかを使う。gl_PointSize は FSH に対して「それぞれの頂点がいくつの頂点に影響するか」を設定する。簡単に言うと3Dのテンのスクリーンでのサイズ。これはパーティクル(炎など)を作るとき非常に役に立つ。VSH の gl_FrontFacing は面がどちらを向いているかが書いてある読み込み専用の変数。

FSH で使われるのは gl_FragColor という変数。(gl_FragData という場合もあるが省略。) FSH の読込み専用変数は gl_FrontFacing, gl_FragCoord, gl_PointCoord の3つがある。gl_FragCoord はvec4 型のデータでウィンドウに対する相対座標を表す。gl_PointCoord はテクスチャ座標を取得するのに使う。

最も重要なことは「ビルトイン変数への書き込みは最終的な値になる」ということ。よって僕らは何度も gl_Position を書き換えたりすることはできない。gl_FragColor もまたしかり。

(ビルトイン変数のリスト:省略)
(VSH の実際のコード:省略)
(FSH の実際のコード:省略)

では OpenGL の API に戻って Attributes や Uniforms などの設定に戻ってみてみよう。先程書いたように、Varyings に関しては直接コントロールすることはできないので主に VSH の処理の中で Attributes を通してこれにアクセスすることになる。

Setting the Attributes and Uniforms


シェーダーの中の変数を特定するために、Program Object はその変数の位置(=indexと同義)を決めている。Attributes と Uniforms が設定された位置がわかれば僕らはそれを使って値を設定できる。

Uniforms を設定するためには、Program Object がリンクしてからシェーダー内の名前に応じた欲しい Uniform の位置をゲットする。

Attributes を設定するためには2通りの方法があるが「Program Object  がリンクしたあとに設定位置を取得する」のが一般的。

「glBind何たら = 欲しいコンテナ」という書き方をするが、ここでは港のクレーンは実際的なコンテナを持ってくるわけではない。Program Object 内部の処理過程に関連する箇所に bind (つなげる)という意味になり、Attributes の名前によって Program Object の内部位置につなげる。1つめのメソッドは以下。
GLvoid glBindAttribLocation(GLuint program, GLuint index, const GLchar* name)

このメソッドは Program Object が生成してから、かつリンクの前に呼ばれなければいけない。というのは実は Program Object の生成段階であるので、おすすめしない。次の関数を使うほうが良い。

次は Attributes と Uniforms の位置をリンク処理の後に取得する方法。どっちの方法をやるにしろ、それぞれのシェーダーへの位置は僕らは自前でキープしておかなければいけない(後で使うから)。
GLint glGetAttribLocation(GLuint program, const GLchar* name)
GLint glGetUniformLocation(GLuint program, const GLchar* name)
(補足:name にはシェーダープログラムで使用する uniform 変数名を指定する。これは末尾が null である文字列でなければ行けない。変数名はスペースを含んではならない。)

 一度位置を取得してしまえば、あとはいつでも値を設定することができる。28の関数を使って定数や動的変数の設定を行うことができる。動的な変数を設定するときにはそれを有効にしておく必要がある。Uniforms と動的な変数の違いが何かだって?良い質問だ!例えば GL_FRONT_AND_BACK という役に立たない定数を OpenGL が使い続けているのが僕には理解出来ない。それにメモリの使い方も同じだし、パフォーマンスも同じ。意味ねーっす。結論としては、「動的な値には動的変数だけを使って、もし一定の値なら Uniforms を使おう」ってとこかな。

加えて、Uniform に定数を設定するには2つの理由がある。Uniforms は 頂点シェーダーで128回使えるが、Attributes は8回しか使えない* 。そして Attributes は配列データでないということ。これについては後で説明する。デフォルトでは OpenGL は Attributes を定数として使っているが、本来は動的なものとして利用される。
(* 原文:Uniforms can be used 128 times in the vertex shader but the attributes just 8 times)

とにかく、その動的な Attributes と Uniforms と役に立たない 定数 Attributes を見てみよう。Uniforms はどんなデータ型でも、構造体でも、どんな形の配列でも扱うことができる。では関数をば。

GLvoid glUniform{1234}{if}(GLint location, T value[N])
GLvoid glUniform{1234}{if}v(GLint location, GLsizei count, const T* value)
GLvoid glUniformMatrix{234}fv(GLint location, GLsizei count,
                              GLboolean transpose, const GLfloat* value)

わあってる、わかってる。1ずつ説明させてくれ。

{1234}や{if}という表記はそのなかのいずれかを書くことを表す。 vとfvの箇所はそのまま書く。[N]の箇所については{1234}で指定した数だけ引数を書くことを表す。まあつまりは以下の19ってこと。

glUniform1i(GLint location, GLint x)
glUniform1f(GLint location, GLfloat x)
glUniform2i(GLint location, GLint x, GLint y)
glUniform2f(GLint location, GLfloat x, GLfloat y)
glUniform3i(GLint location, GLint x, GLint y, GLint z)
glUniform3f(GLint location, GLfloat x, GLfloat y, GLfloat z)
glUniform4i(GLint location, GLint x, GLint y, GLint z, GLint w)
glUniform4f(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
glUniform1iv(GLint location, GLsizei count, const GLint* v)
glUniform1fv(GLint location, GLsizei count, const GLfloat* v)
glUniform2iv(GLint location, GLsizei count, const GLint* v)
glUniform2fv(GLint location, GLsizei count, const GLfloat* v)
glUniform3iv(GLint location, GLsizei count, const GLint* v)
glUniform3fv(GLint location, GLsizei count, const GLfloat* v)
glUniform4iv(GLint location, GLsizei count, const GLint* v)
glUniform4fv(GLint location, GLsizei count, const GLfloat* v)
glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)

どっひゃーって感じだけど、実はそんなに難しくない。
  • 設定したい値の数を{1234}から選ぶ
  • 配列を設定したかったらvを選ぶ
  • マトリクス型のデータを設定するならfvを選ぶ
  • int データを設定するならi, floatならfを選ぶ

で、重要なのは2点。両方のシェーダーでおなじ Uniform が使用される。2つめは重要で、Uniforms が現在使われている Program Object に対して設定されるということ。よって僕らは Uniforms やら Attributes やらを設定する前にプログラムをスタートしなければならない。glUseProgram に欲しいIDを渡して呼べば良い。

では次、Attributes への値設定。Attributes が読めるのは float, vec2, vec3, vec4, mat2, mat3, mat4 のデータ型のみ。配列や構造体としては宣言できない。

GLvoid glVertexAttrib{1234}f(GLuint index, GLfloat value[N])
GLvoid glVertexAttrib{1234}fv(GLuint index, const GLfloat* values)
GLvoid glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized,
                             GLsizei stride, const GLvoid* ptr)

名前付けのルールは Uniforms の時と同じような感じだね。上の2つは定数を設定し、3つめは動的な値を設定する関数。

ここで面倒なのは定数はシェーダーによってのデフォルトの処理であるということ。動的な値を設定するのならば一時的にこれらの変更を有効にしなければならない。動的な値は頂点ごとに設定される。動的値の有効/無効を切り替えるためには以下のメソッドを使用する。

GLvoid glEnableVertexAttribArray(GLuint index)
GLvoid glDisableVertexAttribArray(GLuint index)

glVertexAttribPointer() を使って設定する前に、glEnableVertexAttribArray() でもって有効にしないとならない。

前に示した VSH と FSH のコードを使うときに、次のようなプログラムで値を設定することができる。

(Attributes と Uniforms の設定をするコード:省略)

例示するために enabled したり disabled したりしています。しかしながら先に行ったように、enabled や disabled したりするのは OpenGL にとって非常にコストの高い処理です。よってそれらは1回だけその位置を取得したときに行うのがよさそうです。


2/3 -5 へ


コードは元ネタサイトを見てくださいね。今回は直訳ざっくりな箇所が多いかもしれません。が、書いている本人は仕組みの裏側がわかってきたきがするのですこし楽しいです。

0 件のコメント:

コメントを投稿