2014年5月29日木曜日

シェーダー

今回はシェーダを使ってキャンバスに点を描いてみたいと思います。

前回も書いたように、WebGLは、シェーダがないと何も出力できません。できるのは前回やったようにせいぜいキャンバスを塗りつぶすぐらいです。点1つ描くのにもシェーダの記述が必要になるのです。

シェーダはGPU(グラフィスクボード)で動くプログラムで、JavaScriptからシェーダへデータを渡し、GPU側で処理して高速に描画することができます。

WebGLで使用するシェーダにはバーテックス(頂点)シェーダとフラグメントシェーダの2種類があります。WebGLのプログラミングではこの2種類のシェーダを用意する必要があります。

バーテックスシェーダは読んで字のごとく各頂点単位の処理を実行するプログラムです。WebGLでは何かを描画する時にその描画する形を面(ポリゴン)の集まりとしてデータを定義します。面の最小単位は三角形で、その三角形は3つの線から構成されます。そして線は最低2つの点から構成されます。この1つ1つの点を処理するのがバーテックスシェーダということになります。

Polygon

1つ1つの頂点の座標は通常、3次元空間におけるX,Y,Z座標で定義されます。また3次元空間の中でもどの位置からどこを見ているかによって画面に表示される物体の見え方は変わってきます。一方、出力先の画面は2次元の座標(X,Y座標)なので、画面に正しく表示するためには、3次元空間内の視点からみた内容を画面の座標(スクリーン座標)へ変換する必要があります。この変換を行うのがバーテックスシェーダの仕事です。またバーテックスシェーダでは色の計算に必要な情報を必要に応じてフラグメントシェーダへ渡す役目もあります。

Convert

フラグメントシェーダの仕事は各フラグメントの色を決定することです。バーテックスシェーダで決定された座標や頂点の色、テクスチャ等の情報を元に描画先に出力する色をフラグメント単位で決定します。フラグメントとは画面の1つのピクセルの色に関する情報をひとまとめにした単位と考えることができます。厳密にはフラグメント=ピクセルではない(フラグメントは複数のピクセルから構成される場合もあるし、複数のフラグメントで1つのピクセルを構成する場合もある)のですが、イメージ的には描画先を構成する最小単位と考えればいいと思います。

Shader

バーテックスシェーダは頂点単位で動作しますが、フラグメントシェーダはフラグメント単位で動作するので、実際に実行される回数はフラグメントシェーダの方が圧倒的に多くなります。そのため、フラグメントシェーダーに負荷のかかる計算を記述すると処理が重くなることになります。

バーテックスシェーダーで記述できるならバーテックスシェーダーで記述した方がよいということになります。バーテックスシェーダで決定された情報は頂点間で補間されてフラグメントシェーダーへ渡されます。例えばバーテックスシェーダーで頂点Aで赤、頂点Bで青の情報をフラグメントシェーダーへ渡した場合、フラグメントシェーダーが頂点Aと頂点Bの中間点を処理する時、青と赤の中間色である紫色の情報が渡されることになります。

このように非常に多くの計算が実行されることになりますが、シェーダはGPUで並列に動作することができるので、ポリゴンの全ての色を一瞬で決定することができます。そのため非常に高速に描画することができるのです。

そのシェーダーですが、どこに書くかというと、いろいろやり方はあります。htmlファイル内に記述する方法、JavaScriptに文字列として書く方法、外部ファイルに記述してJavaScriptから読み出す方法など。今回は、HTMLファイル内に記述する方法でやってみたいと思います。


バーテックスシェーダー

まず、バーテックスシェーダーです。main.htmlのheadタグ内に次のように書きます。


: <script id="shader-vs" type="x-shader/v-vertex"> attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } </script> :

scriptタグの中に処理内容を記述しています。type="x-shader/x-vertex"という書き方は別に決められたものではなく、単にブラウザがJavaScriptと解釈して実行してしまわないように慣例的にこのように書いています。

WebGLのシェーダーを記述する言語はGLSL(OpenGL Shading Language)と呼ばれるものです。構文はC言語に非常によく似ています。処理の開始時もC言語と同じくmain()関数が呼ばれます。

Attribute変数

GLSLには外部とやととりするために3種類の変数があります。これは一般的なC言語にはないものです。その変数の種類の1つが上ででてきているattributeで宣言されるAttribute変数です。

Attibute変数は頂点毎の属性を扱うもので、例えば、3次元空間の座標や、各頂点の色、テクスチャ座標、法線ベクトル等といったものがあります。頂点毎にどういった情報を持つかはフログラマが自由に決めることができます。但し扱う情報はバーテックスシェーダ内でAttribute変数として宣言する必要があります。これらのデータはJavaScript側からバーテックスシェーダへ渡すことになります。Attribute変数はフラグメントシェーダーでは参照できません。

Varying変数、Uniform変数

残りの2つの変数は、Varying変数とUniform変数です。Varying変数は、バーテックスシェーダーからフラグメントシェーダーへ値を橋渡しする時に使う変数で、Uniform変数は、Attribute変数が頂点単位に定義する値なのに対して、頂点に関係なくグローバルに参照できる値を定義する変数です。

Uniform変数はバーテックスシェーダーとフラグメントシェーダーのどちらでも参照できます。また、Varying変数も両シェーダ間の橋渡しに使用する変数なので両シェーダーから参照できますが、バーテックスシェーダーでは書き込み、フラグメントシェーダーでは読み込みのみができることになります。

詳しくは今後これらを使う時に説明します。

書き方


attribute vec4 a_Position;

上の記述は、a_Positionという名前のvec4型のAttribute変数を宣言しています。a_Positionという名前は別になんでも構いません。ここでは、Attribute変数ということが分かりやすい様に、a_というプレフィックスをつけて、頂点座標ということでPositionと合わせて、a_Positionという名前にしています。

vec4は4つの浮動小数からなるベクトル型です。WebGLは3次元空間のデータを扱うので線形代数学でよく扱われるベクトルや行列計算で扱う型と計算用の関数が予め用意されています。この辺も普通のC言語とは異なる特徴です。ベクトル型としてはvec4の他に浮動小数成分を2つもつvec2と、3つもつvec3という型もあります。

main関数の中では、gl_Positionという変数と、gl_PointSizeという変数にそれぞれ値を設定しています。これらの変数の名前は予約されているもので、必ずこの名前を使わなければいけません。

バーテックスシェーダーでは必ずgl_Positionに点の座標を設定してやる必要があります。この座標は3次現空間上の座標ではなく、画面に表示するために座標変換した後の座標です。ここでは、a_Positionの値をそのまま渡しています。つまり、a_Positionには、画面に表示するための座標をJavaScript側から渡す必要があるということです。もちろんバーテックスシェーダーの中で計算して計算結果をgl_Positionに設定することもできます。

gl_PointSizeは点のサイズを指定するもので、こちらは必ずしも値を設定する必要はありません。今回は点の描画を行うので10.0という値を設定しています。gl_PointSizeに渡す値もAttribute変数にしてもよかったのですが、今回は説明を簡単にするために定数をシェーダーの中で設定するようにしました。

ところで、gl_Positionはvec4型の変数です。画面に表示するための座標なのでvec2型でもよさそうなものですが、実際には4つの成分を持ったベクトルを渡すことになっています。これには幾つか理由があります。ひとつはバーテックスシェーダの処理が終わったあとも奥行きの情報が必要なためです。WebGLで描画する場合、通常は視点の手前、又は奥にある全ての物体を描画するようなことはせず範囲を決めてその範囲内にある物体だけを描画するようにします。また複数の物体はそれぞれの奥行きの前後によって見えるもの(描画が必要な部分)と見えないもの(描画が不要な部分)が決まってきます。その決定はバーテックスシェーダーの処理が終わった後にWebGLで処理されますが、そのためにまだこの時点でも点の奥行きを示す座標が必要なためです。

最後の四つ目の成分は、同時座標と呼ばれるもので、通常wを使って表現され(x, y, z, w)といった書き方がされます。

座標が同時座標を使って表現される場合は、実際の座標は、各軸の成分をwで割った(x/w, y/w, z/w)になります。つまり、(10, 20, 30, 1)と、(20, 40, 60, 2)は同じ座標を示すことになります。またwが0の場合、無限遠の座標を示すことになり、これは例えば太陽からの光のように無限の彼方から一様に注ぐ光の方向を定義するために使われたりします。ただ通常は、wに1を使います。vec2やvec3の型の変数をvec4へ代入した場合も、wの値として1が自動的に補完されます。

バーテックスシェーダーを通過した頂点データ(wが1でない場合、wで割った結果の値)は、X、Y, Zの座標がそれぞれ-1〜1の範囲に収まっている頂点のみが描画対象となります。この範囲を超える部分はフラグメントシェーダーへ渡されることなく切り捨てられることになります。

またシェーダの中で頂点の計算、例えば座標の移動や回転、拡大縮小といった操作をする場合に、ベクトルと4x4の行列のかけ算による計算を行いますが、そのためには、ベクトルが4つの成分を持っていた方が都合がいいのです。4x4行列と計算するためにはベクトルも4つの成分を持っている必要があるからです。

今回は、gl_Positionにa_Positionの値をそのまま渡すようにしたため、a_Positionもvec4型にしてあります。


フラグメントシェーダー

次はフラグメントシェーダーです。バーテックスシェーダーに続けて次のように書きます。


: <script id="shader-fs" type="x-shader/x-fragment"> void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } </script> :

書き方

scriptタグのtypeは、バーテックスシェーダーと異なり、"x-shader/x-fragment"となっています。これも別に決まっているわけではなく慣例的な書き方です。

フラグメントシェーダーの中で必ずやらなければならないことは、gl_FragColorへ色の値を設定することです。gl_FragColorもWebGLで予約されたvec4型の変数で、ここでは、(1.0, 0.0, 0.0, 1.0)という値を固定で渡しています。これは、RGBAのRとAに1.0を設定し、GとBに0.0を設定するという意味です。各成分に設定する値は0.0〜1.0の範囲で、0.0が最小、1.0が最大となります。つまり、ここでは完全に不透明な赤色を設定していることになります。

以上が、JavaScriptから渡された座標位置に10ピクセルの赤い点を描画するためのシェーダーです。


次はJavaScript側から、頂点データ(Attribute変数)をシェーダー側へ渡し、実際に描画することになりますが、その前に、シェーダーをコンパイルする必要があります。

長くなったので次回へ続きます。

0 件のコメント:

コメントを投稿