WebGL (2)


Canvas (3D, 3次元グラフィックス)

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の座標系

canvasをクリアする

教科書の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>


canvasに点を描画する

WebGLは2つのシェーダ(shader)を持っていて、描画するときにはシェーダを 利用することになります。 どちらのシェーダもプログラマブルであり、C言語によく似た GLSL (OpenGL Shading Language) で処理を記述します。どちらも必ず main という関数を定義しなくてはいけません。

画像が表示されるまでの処理の流れは、次のようになります。

  1. javascriptでWebGL関連のメソッドが実行される。
  2. 頂点シェーダが処理を行う。
  3. フラグメントシェーダが処理を行う。
  4. カラーバッファへの描画が行われる。
  5. 画像が表示される

次のHTMLは3次元座標(0,0,0)に、大きさ10の赤い点を描画します。 頂点シェーダのプログラム中の gl_Positiongl_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>


javascriptから頂点シェーダのattribute変数にデータを渡す

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>


javascriptからフラグメントシェーダのuniform変数にデータを渡す

フラグメントシェーダでは、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>


頂点ごとに変化する情報をjavascriptから頂点シェーダに渡す

WebGLで描画を行うためには、頂点の位置情報が必ず必要となります。 頂点は、ローカル座標で表現されていて、モデル座標変換やビュー座標変換、 プロジェクション座標変換を経て、最終的に画面に描画されることになります。 WebGLで、頂点の情報を扱う仕組みが「バッファオブジェクト (Buffer Object)」 です。 (以前は「頂点バッファ VBO (Vertex Buffer Object)」と呼んでいました。)

javascriptの配列で表された複数の頂点のデータを、WebGL側に まとめて受け渡すための仕組みがバッファオブジェクトです。 バッファオブジェクト内のデータは、頂点シェーダが 呼び出されるたびに頂点ごとのデータがattribute変数に関連づけられます。

javascriptからバッファオブジェクトを使って頂点シェーダに 頂点データをまとめて渡す全体の手順は以下のようになります。

  1. 頂点の情報を配列に格納する

  2. バッファオブジェクトを生成する。 gl.createBuffer()

  3. バッファオブジェクトをターゲットにバインドする。ターゲットは gl.ARRAY_BUFFERかgl.ELEMENT_ARRAY_BUFFERで、WebGL中に それぞれ1個ずつ存在している。 gl.bindBuffer()

  4. 配列からバッファオブジェクトにデータを転送する。gl.bufferData()

  5. バッファオブジェクトを頂点シェーダ内のattribute変数に関連付ける。gl.vertexAttribPointer()

  6. gl.ARRAY_BUFFERとバッファオブジェクトのバインドを解く。gl.bindBuffer(gl.ARRAY_BUFFER,null)
  7. これは必ずしも必要な操作ではありませんが、やっておくと行儀のよいプログラムになります。


  8. attribute変数でのバッファオブジェクトの割り当てを有効にする。g.enableVertexAttribArray()

バッファオブジェクトは必ずしもひとつではないことに注意して下さい。 頂点の位置情報以外にも、頂点の色に関する情報、頂点の法線の 情報などをそれぞれ頂点シェーダに渡したければ、情報ごとに バッファオブジェクトが必要になります (もちろん、まとめて渡しても構いませんが)。

バッファオブジェクトはいくつも生成できます。 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の第一引数を変更すると、描かれる図形が変化します。

js_sample11.html の変更点
*** js_sample10.html	Wed Jan 15 10:18:49 2014
--- js_sample11.html	Wed Jan 15 10:19:02 2014
***************
*** 72,78 ****
      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>
--- 72,78 ----
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  
!     gl.drawArrays(gl.LINE_LOOP, 0, n_vertices);
  }
  //]]>
    </script>

js_sample12.html の変更点
*** js_sample11.html	Wed Jan 15 10:19:02 2014
--- js_sample12.html	Wed Jan 15 10:19:07 2014
***************
*** 72,78 ****
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  
!     gl.drawArrays(gl.LINE_LOOP, 0, n_vertices);
  }
  //]]>
    </script>
--- 72,78 ----
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  
!     gl.drawArrays(gl.TRIANGLES, 0, n_vertices);
  }
  //]]>
    </script>

js_sample13.html の変更点
*** js_sample12.html	Wed Jan 15 10:19:07 2014
--- js_sample13.html	Wed Jan 15 10:19:12 2014
***************
*** 37,45 ****
      }
  
      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();
--- 37,48 ----
      }
  
      var vertices = new Float32Array([
!         -0.5,  0.5, 0.0,
!         -0.5, -0.5, 0.0,
!          0.5,  0.5, 0.0,
!          0.5, -0.5, 0.0
      ]);
!     var size = 3;
      var n_vertices = vertices.length / size;
  
      var vertexBuffer = gl.createBuffer();
***************
*** 72,78 ****
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  
!     gl.drawArrays(gl.TRIANGLES, 0, n_vertices);
  }
  //]]>
    </script>
--- 75,81 ----
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  
!     gl.drawArrays(gl.TRIANGLE_STRIP, 0, n_vertices);
  }
  //]]>
    </script>


javascriptで複数のバッファオブジェクトを使う

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

js_sample14.html の変更点
*** js_sample10.html	Wed Jan 15 10:18:49 2014
--- js_sample14.html	Wed Jan 15 10:18:02 2014
***************
*** 57,67 ****
      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) {
--- 57,79 ----
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
      gl.enableVertexAttribArray(a_Position);
  
+     var sizes = new Float32Array([
+         10.0, 20.0, 30.0
+     ]);
+     var sizeBuffer = gl.createBuffer();
+     if (! sizeBuffer) {
+         console.log('cannot create buffer'); return;
+     }
+     gl.bindBuffer(gl.ARRAY_BUFFER,sizeBuffer);
+     gl.bufferData(gl.ARRAY_BUFFER,sizes,gl.STATIC_DRAW);
+ 
      var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
      if (a_PointSize < 0) {
          console.log('failed to get location of a_PointSize'); return;
      }
!     gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0);
!     gl.enableVertexAttribArray(a_PointSize);
!     gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
      var u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor');
      if (! u_FragColor) {


頂点ごとに変化する情報をjavascriptからフラグメントシェーダに渡す

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

js_sample15.html の変更点
*** js_sample14.html	Wed Jan 15 10:18:02 2014
--- js_sample15.html	Wed Jan 15 15:32:04 2014
***************
*** 11,26 ****
  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() {
--- 11,29 ----
  var VSHADER_SOURCE = "\
  attribute vec4 a_Position;\n\
  attribute float a_PointSize;\n\
+ attribute vec4 a_Color;\n\
+ varying vec4 v_Color;\n\
  void main() {\n\
      gl_Position = a_Position;\n\
      gl_PointSize = a_PointSize;\n\
+     v_Color = a_Color;\n\
  }\n\
  ";
  var FSHADER_SOURCE = "\
  precision mediump float;\n\
! varying vec4 v_Color;\n\
  void main() {\n\
!     gl_FragColor = v_Color;\n\
  }\n\
  ";
  function main() {
***************
*** 75,85 ****
      gl.enableVertexAttribArray(a_PointSize);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
!     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);
--- 78,103 ----
      gl.enableVertexAttribArray(a_PointSize);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
!     var colors = new Float32Array([
!         1.0, 0.0, 0.0,
!         0.0, 1.0, 0.0,
!         0.0, 0.0, 1.0
!     ]);
!     var colorBuffer = gl.createBuffer();
!     if (! colorBuffer) {
!         console.log('cannot create buffer'); return;
!     }
!     gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer);
!     gl.bufferData(gl.ARRAY_BUFFER,colors,gl.STATIC_DRAW);
! 
!     var a_Color = gl.getAttribLocation(gl.program,'a_Color');
!     if (a_Color < 0) {
!         console.log('failed to get location of a_Color'); return;
      }
!     gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
!     gl.enableVertexAttribArray(a_Color);
!     gl.bindBuffer(gl.ARRAY_BUFFER,null);
! 
  
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);


頂点ごとに変化する情報をまとめて頂点シェーダに渡す

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

js_sample16.html の変更点
*** js_sample15.html	Wed Jan 15 15:32:04 2014
--- js_sample16.html	Thu Jun 11 13:22:02 2015
***************
*** 40,48 ****
      }
  
      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();
--- 40,54 ----
      }
  
      var vertices = new Float32Array([
!          // x, y, z, size, r, g, b
!          0.0,  0.5, 0.0, 10.0, 1.0, 0.0, 0.0,
!         -0.5, -0.5, 0.0, 20.0, 0.0, 1.0, 0.0,
!          0.5, -0.5, 0.0, 30.0, 0.0, 0.0, 1.0
      ]);
!     var xyz_size = 3;
!     var s_size = 1;
!     var c_size = 3;
!     var size = xyz_size + s_size + c_size;
      var n_vertices = vertices.length / size;
  
      var vertexBuffer = gl.createBuffer();
***************
*** 56,104 ****
      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 sizes = new Float32Array([
-         10.0, 20.0, 30.0
-     ]);
-     var sizeBuffer = gl.createBuffer();
-     if (! sizeBuffer) {
-         console.log('cannot create buffer'); return;
-     }
-     gl.bindBuffer(gl.ARRAY_BUFFER,sizeBuffer);
-     gl.bufferData(gl.ARRAY_BUFFER,sizes,gl.STATIC_DRAW);
- 
      var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
      if (a_PointSize < 0) {
          console.log('failed to get location of a_PointSize'); return;
      }
!     gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(a_PointSize);
-     gl.bindBuffer(gl.ARRAY_BUFFER,null);
- 
-     var colors = new Float32Array([
-         1.0, 0.0, 0.0,
-         0.0, 1.0, 0.0,
-         0.0, 0.0, 1.0
-     ]);
-     var colorBuffer = gl.createBuffer();
-     if (! colorBuffer) {
-         console.log('cannot create buffer'); return;
-     }
-     gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer);
-     gl.bufferData(gl.ARRAY_BUFFER,colors,gl.STATIC_DRAW);
  
      var a_Color = gl.getAttribLocation(gl.program,'a_Color');
      if (a_Color < 0) {
          console.log('failed to get location of a_Color'); return;
      }
!     gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(a_Color);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
- 
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  
--- 62,90 ----
      if (a_Position < 0) {
          console.log('failed to get location of a_Position'); return;
      }
!     gl.vertexAttribPointer(a_Position,xyz_size,gl.FLOAT,false,
!                            vertices.BYTES_PER_ELEMENT * size,0);
      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.vertexAttribPointer(a_PointSize, s_size, gl.FLOAT, false,
!                            vertices.BYTES_PER_ELEMENT*size,
!                            vertices.BYTES_PER_ELEMENT*xyz_size);
      gl.enableVertexAttribArray(a_PointSize);
  
      var a_Color = gl.getAttribLocation(gl.program,'a_Color');
      if (a_Color < 0) {
          console.log('failed to get location of a_Color'); return;
      }
!     gl.vertexAttribPointer(a_Color, c_size, gl.FLOAT, false,
!                            vertices.BYTES_PER_ELEMENT*size,
!                            vertices.BYTES_PER_ELEMENT*(xyz_size+s_size));
      gl.enableVertexAttribArray(a_Color);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  


図形を増やす

複数の3角形を書いてみます。 三角形の頂点の大きさの情報は必要ないのでa_PointSizeは削除しています。

この例では、視点からの図形の前後関係が正しく表示されずに、 後から書いた図形が前に描かれていることに注意しましょう。

js_sample17.html の変更点
*** js_sample16.html	Thu Jun 11 13:22:02 2015
--- js_sample17.html	Thu Jun 11 13:22:12 2015
***************
*** 10,21 ****
      //<![CDATA
  var VSHADER_SOURCE = "\
  attribute vec4 a_Position;\n\
- attribute float a_PointSize;\n\
  attribute vec4 a_Color;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
      gl_Position = a_Position;\n\
-     gl_PointSize = a_PointSize;\n\
      v_Color = a_Color;\n\
  }\n\
  ";
--- 10,19 ----
***************
*** 40,54 ****
      }
  
      var vertices = new Float32Array([
!          // x, y, z, size, r, g, b
!          0.0,  0.5, 0.0, 10.0, 1.0, 0.0, 0.0,
!         -0.5, -0.5, 0.0, 20.0, 0.0, 1.0, 0.0,
!          0.5, -0.5, 0.0, 30.0, 0.0, 0.0, 1.0
      ]);
      var xyz_size = 3;
-     var s_size = 1;
      var c_size = 3;
!     var size = xyz_size + s_size + c_size;
      var n_vertices = vertices.length / size;
  
      var vertexBuffer = gl.createBuffer();
--- 38,59 ----
      }
  
      var vertices = new Float32Array([
!          // x, y, z, r, g, b
!          0.0,  0.5, -0.1, 1.0, 0.0, 0.0,
!         -0.5, -0.5, -0.1, 1.0, 0.0, 0.0,
!          0.5, -0.5, -0.1, 1.0, 0.0, 0.0,
! 
!          0.1,  0.5, -0.3, 0.0, 1.0, 0.0,
!         -0.4, -0.5, -0.3, 0.0, 1.0, 0.0,
!          0.6, -0.5, -0.3, 0.0, 1.0, 0.0,
! 
!          0.2,  0.5, -0.2, 0.0, 0.0, 1.0,
!         -0.3, -0.5, -0.2, 0.0, 0.0, 1.0,
!          0.7, -0.5, -0.2, 0.0, 0.0, 1.0
      ]);
      var xyz_size = 3;
      var c_size = 3;
!     var size = xyz_size + c_size;
      var n_vertices = vertices.length / size;
  
      var vertexBuffer = gl.createBuffer();
***************
*** 66,94 ****
                             vertices.BYTES_PER_ELEMENT * size,0);
      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.vertexAttribPointer(a_PointSize, s_size, gl.FLOAT, false,
-                            vertices.BYTES_PER_ELEMENT*size,
-                            vertices.BYTES_PER_ELEMENT*xyz_size);
-     gl.enableVertexAttribArray(a_PointSize);
- 
      var a_Color = gl.getAttribLocation(gl.program,'a_Color');
      if (a_Color < 0) {
          console.log('failed to get location of a_Color'); return;
      }
      gl.vertexAttribPointer(a_Color, c_size, gl.FLOAT, false,
                             vertices.BYTES_PER_ELEMENT*size,
!                            vertices.BYTES_PER_ELEMENT*(xyz_size+s_size));
      gl.enableVertexAttribArray(a_Color);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
      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>
--- 71,90 ----
                             vertices.BYTES_PER_ELEMENT * size,0);
      gl.enableVertexAttribArray(a_Position);
  
      var a_Color = gl.getAttribLocation(gl.program,'a_Color');
      if (a_Color < 0) {
          console.log('failed to get location of a_Color'); return;
      }
      gl.vertexAttribPointer(a_Color, c_size, gl.FLOAT, false,
                             vertices.BYTES_PER_ELEMENT*size,
!                            vertices.BYTES_PER_ELEMENT*xyz_size);
      gl.enableVertexAttribArray(a_Color);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
      gl.clear(gl.COLOR_BUFFER_BIT);
  
!     gl.drawArrays(gl.TRIANGLES, 0, n_vertices);
  }
  //]]>
    </script>


隠面消去を有効にする。

図形の前後関係を正しく描くために、隠面消去を行うようにしましょう。 そのためには、次の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が遠いという意味です。 本来、フラグメントシェーダには、 視野座標変換した後のフラグメントの座標が送られてくるので、 このような扱いになっています。

js_sample18.html の変更点
*** js_sample17.html	Thu Jun 11 13:22:12 2015
--- js_sample18.html	Sun Jun 22 10:33:17 2014
***************
*** 81,88 ****
      gl.enableVertexAttribArray(a_Color);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
!     gl.clear(gl.COLOR_BUFFER_BIT);
  
      gl.drawArrays(gl.TRIANGLES, 0, n_vertices);
  }
--- 81,91 ----
      gl.enableVertexAttribArray(a_Color);
      gl.bindBuffer(gl.ARRAY_BUFFER,null);
  
+     gl.enable(gl.DEPTH_TEST);
+     gl.depthFunc(gl.LEQUAL);
+ 
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
!     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  
      gl.drawArrays(gl.TRIANGLES, 0, n_vertices);
  }