wgld.orgの管理人であるdoxasさん主催のWebGLスクールの第三回目です。
今回はGLSLによるシェーダの記述の基礎を固める内容でした。
前回のまとめ
第一回 WebGLスクール 「WebGLの概念」
第二回 WebGLスクール 「WebGLの手続きと手順」
シェーダのおさらい
- シェーダには 頂点シェーダ と フラグメントシェーダ がある
- 頂点シェーダはジオメトリパイプラインで使用される
- フラグメントシェーダはフラグメントパイプラインで使用される
シェーダの流れ
javascript
↓
頂点シェーダ
↓
フラグメントシェーダ
↓
描画
javascriptからデータ(頂点データと汎用データ)を頂点シェーダに送る。汎用データはjavascriptからフラグメントシェーダに直接送ることも可能。頂点シェーダで処理したデータをフラグメントシェーダに送る。フラグメントシェーダで処理をし、ディスプレイに描画という流れ。
シェーダを管理する プラグラムオブジェクト が重要なポイント
プログラムオブジェクト
javascriptなどのアプリケーションから送られてくるデータをまず初めに受け取り、javascriptとシェーダ間、頂点シェーダとフラグメントシェーダ間の管理をしてくれる。
javascriptから頂点シェーダを介さずフラグメントシェーダに汎用データを送ることができるのは、プログラムオブジェクトが間に入ってくれているおかげ。
頂点データは頂点シェーダでしか受け取ることができない。
頂点属性
頂点属性は頂点の位置、色、法線などの情報を持った VBOとしてバインドされた でデータで、頂点シェーダでのみ受け取ることができる。各頂点ごとに固有にデータを持っていて、必ず 位置情報 は持っていなくてはならない。
頂点属性にはどんな情報でも好きに付加することができる。
attribute変数
頂点属性に関するものは、 attribute というキーワードが使われる。頂点シェーダでは、アプリケーションから送らてきたデータを参照するのに attribute変数を使います。
attribute vec3 position;
コードを見て分かるように、attribute変数であることを示すように、変数宣言にattribute修飾子を使用する。
順番は 修飾子、変数の型、変数名
uniform変数
attribute変数は頂点属性に関するデータを扱えるのに対し、 uniform変数 は汎用的なデータをシェーダに送る際に使用する。
uniform mat4 mvpMatrix;
変数宣言にuniform修飾子を使用する。
頂点データの時と同じく 修飾子、変数の型、変数名 で記述する
シェーダ間の連携
今まではjavascriptとシェーダ間のやりとりでした。
GLSLには varying変数 と呼ばれているものを使って、シェーダ間(頂点シェーダとフラグメントシェーダ)でデータをやり取りすることもできる。
varying変数
シェーダ間でデータのやり取りができる変数。
シェーダ間で変数名、データ型を統一しなくてはならない。
頂点シェーダで以下のように記述したら
varying vec4 vColor;
フラグメントシェーダでも同じように記述する。
varying vec4 vColor;
シェーダの実行順序
頂点シェーダとフラグメントシェーダには、実行順序がある。
順序を理解し varying変数 を記述する必要があります。
シェーダの実行順序は 必ず頂点シェーダ、フラグメントシェーダ の順番! どうやっても逆の流れにはできないそうです。
GLSLの基礎
main関数
GLSLには必ず main という関数が必要。レンダリングの際にmain関数が実行される。
void main(){
~ 処理 ~
}
コメントの書き方
GLSLにおいてのコメントの記述方法はjavascriptと同じく、一行コメントが//
で複数行コメントが/**/
のようです。
// 1行コメント用
/*
複数行コメント用
複数行コメント用
*/
型について
GLSLでは、 整数と浮動小数点数を区別 します。
整数を表す場合はint
、浮動小数点数を表す場合はfloat
で宣言する必要があります。
以下のような記述はエラーになります。
int i = 1.0;
float f = 1;
float f = 1.0 + 1;
vec系変数
vecというのはvector(ベクトル)を表す型です。
vec系の変数には、2~4までの範囲があります。
vec2(1.0,1.0);
vec3(1.0,1.0,1.0);
vec4(1.0,1.0,1.0,1.0);
2~4はデータの数を表していて、vec系変数はすべてfloat
のため以下の記述はエラーになります。
vec2(1,1);
vec系変数には、いろいろな記述方法があります。
vec3 vector1 = vec3(1.0); // 全部に 1.0 が入る
vec3 vector2 = vec3(vec2(0.0), 1.0); // 実際には (0.0, 0.0, 1.0) になる
vec4 vector3 = vec4(vector2.xy, 1.0, 1.0); // 実際には (0.0, 0.0, 1.0, 1.0) になる
mat系変数
vec系と似ていて、matはMartix(マトリックス)を表しています。
mat系はvec系と同じくfloat
なため整数は扱えません。mat系も2~4の範囲まであり、すべてが正方行列。
mat系の特徴は、vec系の変数と掛け合わせることができることです。
掛け合わせる場合は、vec系変数とmat系変数の末尾の数字を合わせる必要があります。
※このあたりは使っているうちに自然と覚えられるとのこと。
精度修飾子
フラグメントシェーダには、精度修飾子と呼ばれるものがあります。
precision mediump float;
これはどのくらいの精度でデータを扱うかと指定する文でprecision
修飾子で宣言する。精度修飾子は以下の3種類がある。
- lowp
- mediump
- highp
上記のコードでは、float
にmediump
の精度を求めるということになるそうです。フラグメントシェーダでは、highp
を指定すると負荷が結構かかりコンパイルエラーになることが多いらしい。
頂点シェーダでは、highp
の精度をGPUが求めているため、精度修飾子を宣言しないそうです。
GLSLのビルトイン
GLSLには様々なビルトイン変数があります。
代表的なビルトインの変数にはgl_
という接頭子が付くものがある。
頂点シェーダ限定のビルトイン変数
ビルトイン変数には頂点シェーダでしか使えない限定的なものがある。
以下の2種類のビルトイン変数が頂点シェーダ限定です。
vec4 gl_Position //全ての変換を完了した頂点座標
float gl_PointSize //頂点を点として描画する際の点の大きさ
頂点シェーダにおいて gl_Positionは必須です
フラグメントシェーダ限定のビルトイン変数
フラグメントシェーダでしかビルトイン変数もあります。
頂点シェーダは2種類でしたが、フラグメントシェーダにはいろいろなビルトイン変数があるそう。
vec4 gl_FragCoord //入力専用、バッファ上の位置(ピクセル)
vec4 gl_FragColor //出力専用、最終的にスクリーンに描かれる色
GLSLを調べるときには、公式のチートシートがありそれを見るのが一番いいらしいです。
GLSL公式チートシート
プリミティブ形状
頂点によって描かれる形状のことをプリミティブ形状と呼びます。
WebGLで、点、線、三角形以外の図形を描画するためにはどうすればいいのか。これを解決するためにはプリミティブ形状をドローコールの引数で指定してあげます。
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.drawArrays
のgl.TRIANGLES
がプリミティブ形状の指定です。
WebGLで使用できるプリミティブ形状
- gl.POINTS
- gl.LINES, gl.LINE_STRIP, gl.LINE_LOOP
- gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN
例えば頂点が4つあり、プリミティブ形状にgl_LINES
を指定すると0と1、2と3の頂点を結んだ線が2本描かれます。この状態からプリミティブ形状にgl_LINES_STRIP
を指定すると、1と2を結んだ線が描かれます。
効率よく頂点を利用する
効率よく頂点を利用するというのは、より少ない頂点数で目的の図形を描画するということです。
効率よく頂点を利用するために、 インデックスバッファ という方法を用いる。
IBO (Index Buffer Object)
インデックスバッファーでは、頂点をどのような順番で結ぶかを指定できる。
頂点属性と同じように、インデックスデータを格納したオブジェクトを用意し、事前にシェーダに送る必要がある。
インデックスバッファの描画
インデックスバッファのモデルを描画するためには、ドローコールも専用のものを使い必要がある。また、頂点属性と同じようにjavascriptなどのアプリケーション側でIBOをシェーダに送る必要があります。
IBOを生成する関数は以下のような感じ
function create_ibo(data){
// バッファオブジェクトの生成
var ibo = gl.createBuffer();
// バッファをバインドする
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
// バッファにデータをセット
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW);
// バッファのバインドを無効化
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
// 生成したIBOを返して終了
return ibo;
}
インデックスバッファのドローコール
インデックスバッファを利用する場合のドローコールは以下のような記述になります。
gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
第一引数にプリミティブ形状、第二引数にIBOを格納した配列の長さを指定する。
ピラミッドのレンダリング
今まで習った内容を踏まえて、ピラミッドの描画に挑戦してみました。
ピラミッド描画のポイントは以下の3点。
- 紙に書いてイメージを掴む
- 大きな数値を使わない
- 置く場所に気をつける
なんとか描画できましたが、ピラミッドの形をちゃんとイメージできるまでは難しかったです。
感想
今回はシェーダの基本的な変数の宣言方法や、頂点の形状の指定などを学びました。基礎の基礎のような内容だとは思うのですが、まだいろいろ理解できていない部分が多いのでたくさん繰り返して慣れていきたいと思います。
ピラミットの描画では、「奥行き」という考えが出てきて3Dプログラミングらしさやっとでてきました。ちゃんと描画できたときには結構感動でした。
少しづつではありますが、なんとなく分かってきてはいるのでこの調子で次回も頑張ります。
続き
第四回 WebGLスクール 「行列とクォータニオンについて知る」
第五回 WebGLスクール 「ライティングの基本」
第六回 WebGLスクール 「テクスチャで画像データを使用する」
第七回 WebGLスクール 「ブレンドファクターとアルファブレンディング」
第八回 WebGLスクール 「シェーダエフェクトテクニック」
第十回 WebGLスクール 「ポストエフェクトテクニック」
第十一回 WebGLスクール「キューブ環境マッピング」