WebGL

第三回 WebGLスクール 「シェーダの基礎」

More than 3 years have passed since last update.

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

上記のコードでは、floatmediumpの精度を求めるということになるそうです。フラグメントシェーダでは、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.drawArraysgl.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スクール「キューブ環境マッピング」