WebGLを用いて、canvas上に3Dグラフィックスを描画することにしましょう。
これ以降は、WebGLの基本関数以外に教科書のCDにある webgl-utils.js, webgl-debug.js, cuon-utils.js を利用するプログラムになります。
3次元座標系ではデフォルトで視点は(0,0,0)にあり、視線はz方向の負の方向を向いています。
y | | /---------x / / z |
widthで指定した値
(0,0)+-----------(640,0)
|
|
|
|
y(0,400)heightで指定した値
|
|
| 3次元座標系 | canvasの座標系 | canvas要素上のWebGLの座標系 |
教科書のCDにある webgl-utils.js, webgl-debug.js, cuon-utils.js を用いた 単純なプログラムを見てみましょう。
gl.clear(buffer) --- 指定されたバッファをクリアする。
引数 bufferの値は3種類
gl.COLOR_BUFFER_BIT カラーバッファをgl.clearColor()で設定した色で塗りつぶす。
gl.DEPTH_BUFFER_BIT デプスーバッファをgl.clearDepth()で設定した値にする。
gl.STENCIL_BUFFER_BIT ステンシルバッファをgl.clearStencil()で設定した値にする。
| js_sample06.html |
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script language="JavaScript" type="text/javascript">
//<![CDATA
function main() {
var canvas = document.getElementById("mycanvas");
if (!canvas || !canvas.getContext) {
console.log("canvas not supported"); return;
}
var gl = getWebGLContext(canvas); // cuon-utils.js
if (!gl) {
console.log("cannot get WebGL Context"); return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
gl.clear(gl.COLOR_BUFFER_BIT);
}
//]]>
</script>
</head>
<body onload="main()">
<canvas id="mycanvas" width="320" height="240">
Your browser does not support Canvas TAB.
</canvas>
</body>
</html>
|

WebGLは2つのシェーダ(shader)を持っていて、描画するときにはシェーダを 利用することになります。 どちらのシェーダもプログラマブルであり、C言語によく似た GLSL (OpenGL Shading Language) で処理を記述します。どちらも必ず main という関数を定義しなくてはいけません。
頂点ごとの処理をするシェーダ。 点に関する全ての情報(頂点の位置情報、頂点が持つ法線、テクスチャ座標、頂点の色など) を渡すことができる。頂点の位置情報が必須で、attribute変数に入れて gl_Positionという組込変数に渡す必要がある。
フラグメント(画面上のピクセル)ごとの処理をするシェーダ。 画面上の各ピクセルにどんな色を出力すればいいのかを決定する。
画像が表示されるまでの処理の流れは、次のようになります。
次のHTMLは3次元座標(0,0,0)に、大きさ10の赤い点を描画します。 頂点シェーダのプログラム中の gl_Positionと gl_PointSize、 およびフラグメントシェーダのプログラム中のgl_FragColor はそれぞれ組込み変数です。
| js_sample07.html |
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script language="JavaScript" type="text/javascript">
//<![CDATA
var VSHADER_SOURCE = "\
void main() {\n\
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n\
gl_PointSize = 10.0;\n\
}\n\
";
var FSHADER_SOURCE = "\
void main() {\n\
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n\
}\n\
";
function main() {
var canvas = document.getElementById("mycanvas");
if (!canvas || !canvas.getContext) {
console.log("canvas not supported"); return;
}
var gl = getWebGLContext(canvas); // cuon-utils.js
if (!gl) {
console.log("cannot get WebGL Context"); return;
}
if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
console.log("cannot initiate shader"); return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
}
//]]>
</script>
</head>
<body onload="main()">
<canvas id="mycanvas" width="320" height="240">
Your browser does not support Canvas TAB.
</canvas>
</body>
</html>
|

GLSLの変数には属性があって、理解しておくべきなのは次の3種類。
GLSLの変数の型で、理解しておくべきなのは次のもの。
| js_sample08.html |
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script language="JavaScript" type="text/javascript">
//<![CDATA
var VSHADER_SOURCE = "\
attribute vec4 a_Position;\n\
attribute float a_PointSize;\n\
void main() {\n\
gl_Position = a_Position;\n\
gl_PointSize = a_PointSize;\n\
}\n\
";
var FSHADER_SOURCE = "\
void main() {\n\
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n\
}\n\
";
function main() {
var canvas = document.getElementById("mycanvas");
if (!canvas || !canvas.getContext) {
console.log("canvas not supported"); return;
}
var gl = getWebGLContext(canvas); // cuon-utils.js
if (!gl) {
console.log("cannot get WebGL Context"); return;
}
if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
console.log("cannot initiate shader"); return;
}
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
if (a_Position < 0) {
console.log('failed to get location of a_Position');
return;
}
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
if (a_PointSize < 0) {
console.log('failed to get location of a_PointSize');
return;
}
gl.vertexAttrib1f(a_PointSize, 10.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
}
//]]>
</script>
</head>
<body onload="main()">
<canvas id="mycanvas" width="320" height="240">
Your browser does not support Canvas TAB.
</canvas>
</body>
</html>
|

フラグメントシェーダでは、floatの精度を指定しておく必要があります。
pricision mediump float; // mediump
| js_sample09.html |
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script language="JavaScript" type="text/javascript">
//<![CDATA
var VSHADER_SOURCE = "\
attribute vec4 a_Position;\n\
attribute float a_PointSize;\n\
void main() {\n\
gl_Position = a_Position;\n\
gl_PointSize = a_PointSize;\n\
}\n\
";
var FSHADER_SOURCE = "\
precision mediump float;\n\
uniform vec4 u_FragColor;\n\
void main() {\n\
gl_FragColor = u_FragColor;\n\
}\n\
";
function main() {
var canvas = document.getElementById("mycanvas");
if (!canvas || !canvas.getContext) {
console.log("canvas not supported"); return;
}
var gl = getWebGLContext(canvas); // cuon-utils.js
if (!gl) {
console.log("cannot get WebGL Context"); return;
}
if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
console.log("cannot initiate shader"); return;
}
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
if (a_Position < 0) {
console.log('failed to get location of a_Position');
return;
}
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
if (a_PointSize < 0) {
console.log('failed to get location of a_PointSize');
return;
}
gl.vertexAttrib1f(a_PointSize, 10.0);
var u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor');
if (! u_FragColor) {
console.log('failed to get location of u_FragColor');
return;
}
gl.uniform4f(u_FragColor, 1.0, 1.0, 0.0, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
}
//]]>
</script>
</head>
<body onload="main()">
<canvas id="mycanvas" width="320" height="240">
Your browser does not support Canvas TAB.
</canvas>
</body>
</html>
|

WebGLで描画を行うためには、頂点の位置情報が必ず必要となります。 頂点は、ローカル座標で表現されていて、モデル座標変換やビュー座標変換、 プロジェクション座標変換を経て、最終的に画面に描画されることになります。 WebGLで、頂点の情報を扱う仕組みが「バッファオブジェクト (Buffer Object)」 です。 (以前は「頂点バッファ VBO (Vertex Buffer Object)」と呼んでいました。)
javascriptの配列で表された複数の頂点のデータを、WebGL側に まとめて受け渡すための仕組みがバッファオブジェクトです。 バッファオブジェクト内のデータは、頂点シェーダが 呼び出されるたびに頂点ごとのデータがattribute変数に関連づけられます。
javascriptからバッファオブジェクトを使って頂点シェーダに 頂点データをまとめて渡す全体の手順は以下のようになります。





これは必ずしも必要な操作ではありませんが、やっておくと行儀のよいプログラムになります。


バッファオブジェクトは必ずしもひとつではないことに注意して下さい。 頂点の位置情報以外にも、頂点の色に関する情報、頂点の法線の 情報などをそれぞれ頂点シェーダに渡したければ、情報ごとに バッファオブジェクトが必要になります (もちろん、まとめて渡しても構いませんが)。
バッファオブジェクトはいくつも生成できます。 gl.ARRAY_BUFFERターゲットは1つなので、複数のバッファオブジェクトを 扱う場合は、javascriptでバッファオブジェクトをターゲットに バインドし直しながらデータをWebGLに転送しattribute変数への 関連付けを行います。
gl.craeteBuffer() バッファオブジェクトを生成する [戻り値] 作成されたバッファオブジェクト (エラーの場合null) |
gl.bindBuffer(target,buffer)
バッファオブジェクトを有効化し、ターゲットにバンドする。
[引数]
target gl.ARRAY_BUFFER または gl.ELEMENT_ARRAY_BUFFER
前者は「頂点データ」、後者は「インデックス」の場合
buffer バッファオブジェクト
[戻り値] なし
|
gl.bufferData(target, data, usage)
targetにバインドされたバッファオブジェクトにdataの内容を転送する(書き込む)。
[引数]
target gl.ARRAY_BUFFER または gl.ELEMENT_ARRAY_BUFFER
data 配列データ(型付き配列)
型付き配列の種類は以下の通り。
Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array,
Float32Array, Float64Array
usage データの使われかたのヒント。
gl.STATIC_DRAW 書き込みは1回で、内容は変更されない。頻繁に使用される。
gl.STREAM_DRAW 書き込みは1回で、内容は変更されない。数回使用される。
gl.DYNAMIC_DRAW 書き込みが複数回で内容が変更される。頻繁に使用される。
[戻り値] なし
|
gl.vertexAttribPointer(location,size,type,normalized,stride,offset)
gl.ARRAY_BUFFERにバインドされたバッファオブジェクトを、locationで指定された
attribute変数に関連付ける。バッファオブジェクトのデータ形式も指定する。
[引数]
location attribute変数の格納場所
size データ1頂点あたりの要素数(1〜4)
type データの形式
gl.UNSIGNED_BYTE, gl.SHORT, gl.UNSIGNED_SHORT,
gl.INT, gl.UNSIGNED_INT, gl.FLOAT
normalized データを[0,1]か[-1,1]の間に正規化するか指定する(整数の場合)
stride データ間隔
offset データ開始のオフセット(バイト数)
[戻り値] なし
|
gl.enableVertexAttribArray(location) locationで指定されたattribute変数へのバッファオブジェクトの割り当てを有効にする [引数] location attribute変数の格納場所 [戻り値] なし |
gl.drawArrays(mode,first,count)
頂点シェーダを起動し,modeで指定した図形の描画を行う。
[引数]
mode 描画する図形の形状。
gl.POINTS, gl.LINES, gl.LINE_STRIP, gl.LINE_LOOP, gl.TRIANGLES,
gl.TRIANGLES_STRIP, gl.TRIANGLE_FAN
first 描画を開始する頂点の番数
count 描画する頂点の個数
[戻り値] なし
|

| js_sample10.html |
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script language="JavaScript" type="text/javascript">
//<![CDATA
var VSHADER_SOURCE = "\
attribute vec4 a_Position;\n\
attribute float a_PointSize;\n\
void main() {\n\
gl_Position = a_Position;\n\
gl_PointSize = a_PointSize;\n\
}\n\
";
var FSHADER_SOURCE = "\
precision mediump float;\n\
uniform vec4 u_FragColor;\n\
void main() {\n\
gl_FragColor = u_FragColor;\n\
}\n\
";
function main() {
var canvas = document.getElementById("mycanvas");
if (!canvas || !canvas.getContext) {
console.log("canvas not supported"); return;
}
var gl = getWebGLContext(canvas); // cuon-utils.js
if (!gl) {
console.log("cannot get WebGL Context"); return;
}
if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
console.log("cannot initiate shader"); return;
}
var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);
var size = 2;
var n_vertices = vertices.length / size;
var vertexBuffer = gl.createBuffer();
if (! vertexBuffer) {
console.log('cannot create buffer'); return;
}
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
if (a_Position < 0) {
console.log('failed to get location of a_Position'); return;
}
gl.vertexAttribPointer(a_Position,size,gl.FLOAT,false,0,0);
gl.bindBuffer(gl.ARRAY_BUFFER,null);
gl.enableVertexAttribArray(a_Position);
var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
if (a_PointSize < 0) {
console.log('failed to get location of a_PointSize'); return;
}
gl.vertexAttrib1f(a_PointSize, 10.0);
var u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor');
if (! u_FragColor) {
console.log('failed to get location of u_FragColor'); return;
}
gl.uniform4f(u_FragColor, 1.0, 1.0, 0.0, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, n_vertices);
}
//]]>
</script>
</head>
<body onload="main()">
<canvas id="mycanvas" width="320" height="240">
Your browser does not support Canvas TAB.
</canvas>
</body>
</html>
|

gl.drawArraysの第一引数を変更すると、描かれる図形が変化します。



複数のバッファオブジェクトを使う場合は、 javascript側で作成した複数のバッファオブジェクトを、 WebGL側の1つしかないBUFFER_ARRAYに順番に切り替えながら バインドして、データを転送していきます。

点ごとに変化する情報をフラグメントシェーダに伝えるには、 javascriptから頂点シェーダにはattribute変数で渡して、 頂点シェーダ内でvarying 変数に代入することで伝えます。

点ごとに変化する情報をひとまとめにしてバッファオブジェクトにいれて WebGLに転送しておき、attribute変数に渡すときに stride(1かたまりのデータのサイズ)や オフセットを指定することもできます。

複数の3角形を書いてみます。 三角形の頂点の大きさの情報は必要ないのでa_PointSizeは削除しています。
この例では、視点からの図形の前後関係が正しく表示されずに、 後から書いた図形が前に描かれていることに注意しましょう。

図形の前後関係を正しく描くために、隠面消去を行うようにしましょう。 そのためには、次の2つのことを行います。
gl.enable(cap) --- capで指定された機能を有効にする。
引数 cap ---有効にする機能
gl.DEPTH_TEST 隠面消去
gl.BLEND ブレンディング
gl.POLYGON_OFFSET_FILL ポリゴン・オフセット
など
デフォルトの視点は(0,0,0)で視線方向は(0,0,-1)ですが、 なぜかz座標が小さい方が手前に描かれています。 この理由は、面の前後関係を扱うためにWebGLで使っている 深度バッファ(Depth Buffer)においては、 「大きな値ほど遠い」ことを意味しているからです。 これは通常の座標系とは逆になります。
深度バッファに保持される値(Z値と呼ぶことがあります) はフラグメントのz座標の値から計算される値です。 WebGLでは深度バッファにZ値として0から1までの値を保持しており、 0が近く1が遠いという意味です。 本来、フラグメントシェーダには、 視野座標変換した後のフラグメントの座標が送られてくるので、 このような扱いになっています。
