wgld.orgの管理人であるdoxasさん主催のWebGLスクールの第二回目です。
今回はWebGLを使って描画されるまでの流れを、実際にコードを見ながら解説してもらいました。
描画するまでに実にたくさんの処理が行われている。正直ほとんど理解できていないかもしれない・・・
前回のまとめ
初期化から描画までのおおまかな流れ
WebGLで画面に描画するまでの流れは以下の様になります。
※順番は多少前後しても問題ないそう。
1,シェーダーの生成とリンク
2,頂点データの準備
3,行列の生成とリンク
4,シェーダへのデータ転送
5,全体設定とドローコール
シェーダーの生成とリンク
まずは、WebGLの2種類のシェーダ 頂点シェーダ と フラグメントシェーダ のソースコードを記述する
シェーダはJavaScriptで文字列データとして扱う
scriptタグ内に記述する
<script id="vs" type="x-shader/x-vertex">
~ シェーダの処理を記述 ~
</script>
id属性とtype属性を設定する。
type="x-shader/x-vertex" はシェーダ専用の値ではなく、ブラウザにJavaScriptの記述として認識されてしまわないようにするため。 type="hoge/hoge" でもなんでもいい。
シェーダのソースを取得するには以下のように記述します。
var vs = document.getElementById("vs").textContent;
シェーダのコンパイル
WebGL独自のメソッドを使ってコンパイルしていく。
- シェーダオブジェクトを作成
- ソースコードを割り当て、タイプを指定しコンパイル
- 成功したかは getShaderParameter で調べる
- 失敗したかは getShaderInfoLog で調べる
コンパイル用の関数を作成しコンパイルする。
function create_shader(source, type){
// シェーダオブジェクトの生成 (生成)
var shader = gl.createShader(type);
// ソースの割り当て (登録)
gl.shaderSource(shader, source);
// コンパイル
gl.compileShader(shader);
//動作チェック
if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
// 成功
return shader;
}else{
// 失敗
alert(gl.getShaderInfoLog(shader));
}
}
// 使用法
vs = document.getElementById('vs').textContent;
create_shader(vs, gl.VERTEX_SHADER);
こんな感じのコードでコンパイルを行うようです。う〜んむずい。
ちなみに。成功時のshader変数をconsole.logで確認してみると、 WebGLShader というオブジェクトが返ってきて、
失敗時には missing shaders というアラートが表示されました。
動作チェックif文の部分で gl.COMPILE_STATUS というのが出てきましが、これはWebGLの 組み込み定数 というやつらしいです。(正直まだよくわからない)
シェーダとシェーダを繋げてリンクさせる
レンダリングパイプラインでは、 頂点シェーダ から フラグメントシェーダ にデータが渡されます。
そのため2つのシェーダは、連動して動いているので対になっていなければならないようです。
プログラムオブジェクトというものを使って、リンクさせます。
プログラムオブジェクト
- プログラムオブジェクトを作成
- それぞれのシェーダを割り当てる
- 成功したかは getProgramParameter で調べる
- 失敗したかは getProgramInfoLog で調べる
- programオブジェクトを選択状態にするには useProgram を使用する (選択状態ってなんだ?)
コンパイルの時と同じように、プログラムオブジェクト用の関数を作成する。
function create_program(vs, fs){
// オブジェクトの生成 (生成)
var program = gl.createProgram();
// シェーダの割り当て (登録)
gl.attachShader(program, vs);
gl.attachShader(program, fs);
// シェーダをリンク (利用)
gl.linkProgram(program);
// 動作チェック
if(gl.getProgramParameter(program, gl.LINK_STATUS)){
// 成功
gl.useProgram(program);
return program;
}else{
// 失敗
alert(gl.getProgramInfoLog(program));
return null;
}
}
// 使用方
// 頂点シェーダとフラグメントシェーダの生成
var vShader = create_shader(vs, gl.VERTEX_SHADER);
var fShader = create_shader(fs, gl.FRAGMENT_SHADER);
create_program(vShader, fShader);
ちなみに。こちらでは、成功時のprogram変数をconsole.logで確認してみると WebGLProgram というオブジェクトが返ってきた。
頂点データの準備
まだ、描画はされない。頂点データを準備する。
頂点データはただの配列を使えばいいから簡単。
// モデル(頂点)データ
var vPosition = [
0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
];
頂点データを用意するだけならこれだけなので楽勝。
しかし、これを直接GPUに送り込むことはできない。
頂点データ専用のオブジェクトにする必要がある。それが VBO と呼ばれているもの。
VBO (vertex buffer object)
VBOは頂点データを格納したWebGL特有のオブジェクト。
- バッファオブジェクトの生成
- バッファオブジェクトをバインドする
- データをセットする
function create_vbo(data){
// オブジェクトの生成
var vbo = gl.createBuffer();
// バインド
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
// データをセット
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
// バインドを無効化
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// 生成した VBO を返して終了
return vbo;
}
gl.bindBuffer()は、呼び出した時点でGPUに結びつき(?)処理が開始されるみたいです。
Float32Array はJavaScriptに比較的最近追加された 型付き配列 と呼ばれ、バイナリデータを扱えてWebGLではとても活躍するそう。
Typed Arrays: ブラウザでバイナリデータを扱う
ちなみに。vbo変数をconsole.logで確認してみると、 WebGLBuffer というオブジェクトが返ってきた。
行列の生成とリンク
まだまだ、描画はされない。行列の生成を行う。
行列とは、数字が並んだ構造という数学の概念(わからん)
例えば、下記のようなものらしい。
1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
行数と列数が同じになっている行列を 正方行列 と呼び、WebGLでは4×4の正方行列を使うことが多いようです。
行列をスクラッチで記述するのは、とても大変なのでライブラリに頼ったほうが良い。とのこと。
今回の講座では、講師であるdoxasさん作のライブラリ minMatrix.js を使う。
基本的な行列の計算やクォータニオンという数学の概念(ん??)を扱えるライブラリだそうです。
minMatrix.js リファレンス
シェーダーへのデータ転送
まだまだまだ、描画はされない。。。 GPUに対してデータを転送します。
シェーダに送られるデータは大きく分けて2つ。
- モデルの頂点データ
- シェーダで利用する汎用的なデータ
モデルの頂点データ
モデルの頂点データをシェーダに送るためには、 VBO を使います。
シェーダ側では変数ごとにインデックス番号が振られ識別されているそうです。
シェーダがコンパイル(shaderオブジェクト)され、リンク(programオブジェクト)された段階で割当てられる。
JavaScript側から インデックス情報を指定して シェーダにデータを送る必要がある。
// プログラムオブジェクトの生成とリンク
var prg = create_program(vShader, fShader);
// インデックスの取得
var attLocation = gl.getAttribLocation(prg, 'position');
// attributeの要素数
var attStride = 3;
リンク(programオブジェクト)を生成し、positionという変数のインデックス番号を取得しています。
頂点属性のデータをシェーダに送る
頂点のデータ(VBO)をシェーダに送るには、VBOをバインドする必要がある。
この辺から混乱してくる。
// VBOの生成
var vbo = create_vbo(vPosition);
// VBOをバインド
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
// attribute属性を有効にする
gl.enableVertexAttribArray(attLocation);
// attribute属性を登録
gl.vertexAttribPointer(attLocation, attStride, gl.FLOAT, false, 0, 0);
uniformロケーションの取得
頂点データではなく、汎用的なデータを送る場合もロケーション情報が必要なります。
汎用的なデータとは色とか形とかだろうか・・・?
// プログラムオブジェクトの生成とリンク
var prg = create_program(vShader, fShader);
// uniformLocationの取得
var uniLocation = gl.getUniformLocation(prg, 'mvpMatrix');
全体設定とドローコール
もう少しでレンダリング!!ドローコールってなんだ?
スクリーンの大きさ指定
レンダリングを行うスクリーンの大きさは通常canvasの大きさのサイズになる。
指定する場合は、 viewport メソッドを使う。
var c = ducument.getElementById("canvas");
gl.viewport(0, 0, c.width, c.height);
0.0は原点。
スクリーンの初期化
どのような色でリセットするか、 深度値 をどのようにリセットするかを設定できる。
※深度値は頂点の奥行き情報
どちらかだけをクリアすると、バグの原因となるので色と深度値は両方クリアする。
// canvasを初期化する色を設定する rgba
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// canvasを初期化する際の深度を設定する
// なぜ1.0なのかは後々わかるらしい。
gl.clearDepth(1.0);
ドローコール
ドローコールは処理が適用され、描画が行われること。
ついに描画です!!長いよ!
// 三角を0から3個分の頂点で描く
gl.drawArrays(gl.TRIANGLES, 0, 3);
上記の命令でGPUでレンダリングがはしる。(正確にはフレームバッファという領域で)
ドローコール完了後に、画面更新メソッド flush というのを実行する
gl.flush();
描画できた!
まとめ
登場したWebGL特有オブジェクト
- shaderオブジェクト
- シェーダをコンパイルするもの
- programオブジェクト
- 2つのシェーダ(頂点シェーダとフラグメントシェーダ)をリンクさせるもの
- VBO
- 頂点データを専用のオブジェクトにするもの
行列
- 3Dプログラミンでは重宝される
- 計算が難解なので、ライブラリに頼ろう
データをシェーダに送る
- JSからシェーダにデータを送るためには、ロケーション情報(インデックス)が必要
- 頂点データ(VBO)をシェーダに送る場合はバインド、要素数の指定、インデックス情報の取得などが必要
- 汎用的なデータを送る場合は、頂点データとは違うメソッドで送る必要がある。
リセットとレンダリング
- レンダリング前に色や深度をリセットする
- ドローコールでレンダリングがはしる
- flushでスクリーンに描画される
今回使ったglメソッド
メソッド名 | 機能 |
---|---|
gl.createShader | shaderオブジェクトの生成 |
gl.shaderSource | shaderオブジェクトにソースを割り当てる |
gl.compileShader | shaderオブジェクトをコンパイルする |
gl.getShaderParameter | shaderオブジェクトが正しくコンパイルされたか |
gl.getShaderInfoLog | shaderオブジェクトコンパイルエラー時 |
gl.createProgram | programオブジェクトの生成 |
gl.attachShader | programオブジェクトにシェーダを割り当てる |
gl.linkProgram | シェーダをリンクさせる |
gl.getProgramParameter | シェーダのリンク正しく行われたか |
gl.useProgram | programオブジェクトを有効にする |
gl.getProgramInfoLog | programオブジェクトエラー時 |
gl.createBuffer | bufferオブジェクトの生成 |
gl.bindBuffer | bufferをバインドする |
gl.bufferData | bufferにデータをセットする |
gl.bindBuffer | bufferのバインドを無効化させる |
gl.getAttribLocation | 頂点データのロケーションを取得 |
gl.enableVertexAttribArray | attribute属性を有効化 |
gl.vertexAttribPointer | attribute属性を登録 |
gl.getUniformLocation | uniformLocation(汎用データのロケーション)の取得 |
gl.clearColor | 初期化する色の設定 |
gl.clearDepth | 初期化する深度値の設定 |
gl.drawArrays | ドローコールの発行 |
gl.flush | GPUに描かれているものがスクリーンに出る |
感想
今回の講座では、スクリーンに三角形を描画しましたが、まぁ〜描画までが長かった。
最小構成のコードでこの冗長さとのことなので、最低でも今日の内容は理解しておかないとWebGLを使えるようにならなそう。
正直この流れを覚えるのだけでもかなり時間がかかると思う。。。
これは大変だ。でも実際に描画するようになると結構面白いので、次回以降も頑張って行きたい。
続き
第三回 WebGLスクール 「シェーダの基礎」
第四回 WebGLスクール 「行列とクォータニオンについて知る」
第五回 WebGLスクール 「ライティングの基本」
第六回 WebGLスクール 「テクスチャで画像データを使用する」
第七回 WebGLスクール 「ブレンドファクターとアルファブレンディング」
第八回 WebGLスクール 「シェーダエフェクトテクニック」
第十回 WebGLスクール 「ポストエフェクトテクニック」
第十一回 WebGLスクール「キューブ環境マッピング」