WebGL (4)


tsuda-webgl.js

プログラムに似たような記述が繰り返し出てくるので、その部分を関数呼び出しに することにしましょう。 今後のプログラムの説明でも利用するので、tsuda-webgl.jsという別ファイルを作成し、 その中に関数定義を置くことにします。

javascriptでは名前空間を分ける手段がなく変数名や関数名が衝突しやすいので、 アプリケーション固有の変数をただ一つGlobalオブジェクト中に宣言し、 その変数が指すオブジェクト中に自前の変数や関数を定義して使うとよい とされています。

tsuda-webgl.js
/**
 * Object "tsuda" for "Computer Graphics"
 */
var tsuda = {};

tsuda.initWebGL = function(id,vshader_src,fshader_src) {
    tsuda.canvas = document.getElementById(id);
    if (!tsuda.canvas || !tsuda.canvas.getContext) {
        console.log("canvas not supported"); return null;
    }
    tsuda.gl = getWebGLContext(tsuda.canvas);
    if (! tsuda.gl) {
        console.log("cannot get WebGL Context"); return null;
    }
    if (!initShaders(tsuda.gl,vshader_src,fshader_src)) {
        console.log("cannot initiate shader"); return null;
    }
    return tsuda;
};

tsuda.initArrayBuffer = function (a_name,data,num,type) {
    var buffer = tsuda.gl.createBuffer();
    if (! buffer) {
        console.log('failed to carete buffer object'); return null;
    }
    tsuda.gl.bindBuffer(tsuda.gl.ARRAY_BUFFER, buffer);
    tsuda.gl.bufferData(tsuda.gl.ARRAY_BUFFER, data, tsuda.gl.STATIC_DRAW);
    var aloc = tsuda.gl.getAttribLocation(tsuda.gl.program,a_name);
    if (aloc < 0) {
        console.log('failed to get location of ' + a_name); return null;
    }
    tsuda.gl.vertexAttribPointer(aloc,num,type,false,0,0);
    tsuda.gl.enableVertexAttribArray(aloc);
    tsuda.gl.bindBuffer(tsuda.gl.ARRAY_BUFFER, null);
    return true;
};

tsuda.setArrayBuffer = function (data) {
  if (data == null) { tsuda.gl.bindBuffer(tsuda.gl.ARRAY_BUFFER,null); return; }
  var buffer = tsuda.gl.createBuffer();
  if (! buffer) {
    console.log('failed to carete buffer object');
    return null;
  }
  tsuda.gl.bindBuffer(tsuda.gl.ARRAY_BUFFER, buffer);
  tsuda.gl.bufferData(tsuda.gl.ARRAY_BUFFER, data, tsuda.gl.STATIC_DRAW);
  return buffer;
};

tsuda.setAttributeVariable = function (a_name,size,type,stride,offset) {
    var aloc = tsuda.gl.getAttribLocation(tsuda.gl.program,a_name);
    if (aloc < 0) {
        console.log('failed to get location of ' + a_name); return null;
    }
    tsuda.gl.vertexAttribPointer(aloc,size,type,false,stride,offset);
    tsuda.gl.enableVertexAttribArray(aloc);
    return aloc;
};

tsuda.setElementArrayBuffer = function (data) {
  if (data == null) { tsuda.gl.bindBuffer(tsuda.gl.ELEMENT_ARRAY_BUFFER,null); return; }
  var buffer = tsuda.gl.createBuffer();
  if (! buffer) {
    console.log('failed to carete buffer object'); return null;
  }
  tsuda.gl.bindBuffer(tsuda.gl.ELEMENT_ARRAY_BUFFER, buffer);
  tsuda.gl.bufferData(tsuda.gl.ELEMENT_ARRAY_BUFFER, data, tsuda.gl.STATIC_DRAW);
  return buffer;
};

/**
 * new Float32Array([r, g, b, r, g, b, ...])
 * @param r, g, b: float value
 * @param n: Length of array is n*3
 * @return Float32Array
 */
tsuda.get3Float32Array = function(r,g,b,n) {
    var c = new Float32Array(n*3);
    var i;
    for (i=0; i<n; i++) { c[i*3+0]=r; c[i*3+1]=g; c[i*3+2]=b; }
    return c;
};

/**
 * Normal Vectors (each vertex is contained by one triangle)
 * @param vertices: flat array of vertice
 * @param stride: size of information of a vertex
 * @param offset: offset to position data
 * @return Float32Array
 */
tsuda.normals = function(vertices,stride,offset) {
  if (stride == undefined || stride == 0) stride = 3;
  if (offset == undefined) offset = 0;
  var nV = vertices.length / stride; // number of vertices
  var nF = nV / 3; // number of triangles
  var ans = new Float32Array(nV*3); // normal vectors
  var i;
  for (i=0; i<nF; i++) {
    var ai = 3*i, bi = 3*i + 1, ci = 3*i + 2; // index of vertices
    var ap=ai*stride+offset, bp=bi*stride+offset, cp=ci*stride+offset;
    var ax=vertices[ap+0], ay=vertices[ap+1], az=vertices[ap+2];
    var bx=vertices[bp+0], by=vertices[bp+1], bz=vertices[bp+2];
    var cx=vertices[cp+0], cy=vertices[cp+1], cz=vertices[cp+2];
    var n = tsuda.normalVector(ax,ay,az,bx,by,bz,cx,cy,cz);
    ans[ai*3+0] = ans[bi*3+0] = ans[ci*3+0] = n[0]; // x
    ans[ai*3+1] = ans[bi*3+1] = ans[ci*3+1] = n[1]; // y
    ans[ai*3+2] = ans[bi*3+2] = ans[ci*3+2] = n[2]; // z
  }
  return ans;
};

/**
 * Normal Vectors (average of normal vectors of the vertex's adjacent triangles)
 * @param vertices: flat array of vertice
 * @param indices: flat array of indices
 * @param stride: size of one vertex information
 * @param offset: offset to position data in one vertex information
 * @return Float32Array
 */
tsuda.indexNormals = function(vertices,indices,stride,offset) {
  if (stride == undefined || stride == 0) stride = 3;
  if (offset == undefined) offset = 0;
  var nV = vertices.length / stride; // number of vertices
  var nF = indices.length / 3; // number of triangles
  var vNormals = []; // normal vectors of vertices
  var count = []; // number of triangles which contain the vertex
  var i;
  for (i = 0; i < nV; i++) {
    vNormals[i] = [0,0,0];
    count[i] = 0;
  }
  for (i = 0; i < nF; i++) {
    var ai = indices[i*3+0], bi = indices[i*3+1], ci = indices[i*3+2];
    var ap = ai*stride+offset, bp=bi*stride+offset, cp=ci*stride+offset;
    var ax = vertices[ap+0], ay = vertices[ap+1], az = vertices[ap+2];
    var bx = vertices[bp+0], by = vertices[bp+1], bz = vertices[bp+2];
    var cx = vertices[cp+0], cy = vertices[cp+1], cz = vertices[cp+2];
    var n = tsuda.normalVector(ax,ay,az,bx,by,bz,cx,cy,cz);
    vNormals[ai] = tsuda.vadd(vNormals[ai],n); count[ai]++;
    vNormals[bi] = tsuda.vadd(vNormals[bi],n); count[bi]++;
    vNormals[ci] = tsuda.vadd(vNormals[ci],n); count[ci]++;
  }
  var ans = new Float32Array(nV*3);
  for (i = 0; i < nV; i++) {
    vNormals[i] = tsuda.vscale(vNormals[i],1/count[i]);
    ans[3*i+0] = vNormals[i][0];
    ans[3*i+1] = vNormals[i][1];
    ans[3*i+2] = vNormals[i][2];
  }
  return ans;
};

/**
 * Normal Vector
 * @param ax, ay, az: vector a
 * @param bx, by, bz: vector b
 * @param cx, cy, cz: vector c
 * @return Array of float
 */
tsuda.normalVector = function(ax,ay,az,bx,by,bz,cx,cy,cz) {
  if (arguments.length == 3) // ax, ay, ax: vector
    return tsuda.normalVector(ax[0],ax[1],ax[2],ay[0],ay[1],ay[2],az[0],az[1],az[2]);
  var v = tsuda.vsub(ax,ay,az,bx,by,bz);
  var u = tsuda.vsub(ax,ay,az,cx,cy,cz);
  var n = tsuda.vcross(v,u);
  var len = tsuda.vlength(n);
  n = tsuda.vscale(n,1/len);
  return n;
};
/**
 * Cross product
 * @param vx, vy, vz: vector v
 * @param ux, uy, uv: vecotr u
 * @return Array of float
 */
tsuda.vcross = function(vx,vy,vz,ux,uy,uz) {
  if (arguments.length == 2) // vx, vy: vector
    return tsuda.vcross(vx[0],vx[1],vx[2],vy[0],vy[1],vy[2]);
  var p = [];
  p[0] = vy * uz - vz * uy;
  p[1] = vz * ux - vx * uz;
  p[2] = vx * uy - vy * ux;
  return p;
};

/**
 * Dot product
 * @param vx, vy, vz: vector v
 * @param ux, uy, uv: vecotr u
 * @return float
 */
tsuda.vdot = function(vx,vy,vz,ux,uy,uz) {
  if (arguments.length == 2) // vx, vy: vector
    return tsuda.vdot(vx[0],vx[1],vx[2],vy[0],vy[1],vy[2]);
  return vx * ux + vy * uy + vz * uz;
};

/**
 * Vector Length
 * @param x, y, z: vector
 * @return float
 */
tsuda.vlength = function(x,y,z) {
  if (arguments.length == 1) return tsuda.vlength(x[0],x[1],x[2]); //x: vector
  return Math.sqrt(tsuda.vdot(x,y,z,x,y,z));
};

/**
 * Vector Add
 * @param vx, vy, vz: vector v
 * @param ux, uy, uv: vecotr u
 * @return Array of float
 */
tsuda.vadd = function(vx,vy,vz,ux,uy,uz) {
  if (arguments.length == 2) // vx, vy: vector
    return tsuda.vadd(vx[0],vx[1],vx[2],vy[0],vy[1],vy[2]);
  var p = [];
  p[0] = vx + ux;
  p[1] = vy + uy;
  p[2] = vz + uz;
  return p;
};

/**
 * Vector Subtract
 * @param vx, vy, vz: vector v
 * @param ux, uy, uv: vecotr u
 * @return Array of float
 */
tsuda.vsub = function(vx,vy,vz,ux,uy,uz) {
  if (arguments.length == 2) // vx, vy: vector
    return tsuda.vsub(vx[0],vx[1],vx[2],vy[0],vy[1],vy[2]);
  var p = [];
  p[0] = vx - ux;
  p[1] = vy - uy;
  p[2] = vz - uz;
  return p;
};

/**
 * Vector Scale
 * @param vx, vy, vz: vector v
 * @param s: float
 * @return Array of float
 */
tsuda.vscale = function(vx,vy,vz,s) {
  if (arguments.length == 2) // vx:vector, vy:float
    return tsuda.vscale(vx[0],vx[1],vx[2],vy);
  var p = [];
  p[0] = vx * s;
  p[1] = vy * s;
  p[2] = vz * s;
  return p;
};

/**
 * str
 * @param vx,vy,vz: vector v
 * @return String
 */
tsuda.vstr = function(vx,vy,vz) {
  if (arguments.length == 1)
    return tsuda.vstr(vx[0],vx[1],vx[2]);
  else if (arguments.length == 2)
    return tsuda.vstr(vx[vy+0],vx[vy+1],vx[vy+2]);
  return vx+" "+vy+" "+vz;
};

/**
 * Data
 */
//    v6----- v5
//   /|      /|
//  v1------v0|
//  | |     | |
//  | |v7---|-|v4
//  |/      |/
//  v2------v3
tsuda.cube = {};
tsuda.cube.vertices = new Float32Array([
  // x, y, z
  1.0,  1.0,  1.0,  // 0 v0 :front  
 -1.0,  1.0,  1.0,  // 1 v1	      
 -1.0, -1.0,  1.0,  // 2 v2	      
  1.0, -1.0,  1.0,  // 3 v3	      
			    							      
  1.0,  1.0,  1.0,  // 4 v0 :right  
  1.0, -1.0,  1.0,  // 5 v3	      
  1.0, -1.0, -1.0,  // 6 v4	      
  1.0,  1.0, -1.0,  // 7 v5	      
			    							      
  1.0,  1.0, -1.0,  // 8 v5 :back   
  1.0, -1.0, -1.0,  // 9 v4	      
 -1.0, -1.0, -1.0,  //10 v7	      
 -1.0,  1.0, -1.0,  //11 v6	      
			    							      
 -1.0,  1.0,  1.0,  //12 v1 :left   
 -1.0,  1.0, -1.0,  //13 v6	      
 -1.0, -1.0, -1.0,  //14 v7	      
 -1.0, -1.0,  1.0,  //15 v2	      
			    							      
  1.0,  1.0,  1.0,  //16 v0 :top    
  1.0,  1.0, -1.0,  //17 v5	      
 -1.0,  1.0, -1.0,  //18 v6	      
 -1.0,  1.0,  1.0,  //19 v1	      
			    							      
 -1.0, -1.0,  1.0,  //20 v2 :bottom 
 -1.0, -1.0, -1.0,  //21 v7	      
  1.0, -1.0, -1.0,  //22 v4	      
  1.0, -1.0,  1.0   //23 v3         
]);
tsuda.cube.indices = new Uint8Array([
   0, 1, 2,   0, 2, 3,    // front
   4, 5, 6,   4, 6, 7,    // right
   8, 9,10,   8,10,11,    // back
  12,13,14,  12,14,15,    // left
  16,17,18,  16,18,19,    // top
  20,21,22,  20,22,23     // bottom
]);
tsuda.cube.texCoord = new Float32Array([
  1.0, 1.0,  // v0 :front
  0.0, 1.0,  // v1
  0.0, 0.0,  // v2
  1.0, 0.0,  // v3

  0.0, 1.0,  // v0 :right
  0.0, 0.0,  // v3
  1.0, 0.0,  // v4
  1.0, 1.0,  // v5

  0.0, 1.0,  // v5 :back
  0.0, 0.0,  // v4
  1.0, 0.0,  // v7
  1.0, 1.0,  // v6

  1.0, 1.0,  // v1 :left
  0.0, 1.0,  // v6
  0.0, 0.0,  // v7
  1.0, 0.0,  // v2

  1.0, 0.0,  // v0 :top
  1.0, 1.0,  // v5
  0.0, 1.0,  // v6
  0.0, 0.0,  // v1

  0.0, 1.0,  // v2 :bottom
  0.0, 0.0,  // v7
  1.0, 0.0,  // v4
  1.0, 1.0,  // v3
]);

//       v0\
//    //  |  \
//  | /v4--\--\v3
//  |/      |/
//  v1------v2
tsuda.pyramid = {};
tsuda.pyramid.vertices = new Float32Array([
   // x, y, z, r, g, b
   0.0,  2.0,  0.0,  // 0 v0 :front
   1.0,  0.0,  1.0,  // 1 v1
   1.0,  0.0, -1.0,  // 2 v2

   0.0,  2.0,  0.0,  // 3 v0 :right
   1.0,  0.0, -1.0,  // 4 v2
  -1.0,  0.0, -1.0,  // 5 v3

   0.0,  2.0,  0.0,  // 6 v0 :back
  -1.0,  0.0, -1.0,  // 7 v3
  -1.0,  0.0,  1.0,  // 8 v4

   0.0,  2.0,  0.0,  // 9 v0 :left
  -1.0,  0.0,  1.0,  //10 v4
   1.0,  0.0,  1.0,  //11 v1

   1.0,  0.0,  1.0,  //12 v1 :bottom
  -1.0,  0.0,  1.0,  //13 v4
  -1.0,  0.0, -1.0,  //14 v3
   1.0,  0.0, -1.0   //15 v2
]);
tsuda.pyramid.indices = new Uint8Array([
  0, 1, 2,   // forward
  3, 4, 5,    // right
  6, 7, 8,    // back
  9,10,11,    // left
 12,13,14,  12,14,15    // bottom
]);
tsuda.pyramid.texCoord = new Float32Array([
  0.5, 1.0,  // v0 :front
  0.0, 0.0,  // v1
  1.0, 0.0,  // v2

  0.5, 1.0,  // v0 :right
  0.0, 0.0,  // v2
  1.0, 0.0,  // v3

  0.5, 1.0,  // v0 :back
  0.0, 0.0,  // v3
  1.0, 0.0,  // v4

  0.5, 1.0,  // v0 :left
  0.0, 0.0,  // v3
  1.0, 0.0,  // v4

  0.0, 1.0,  // v1 :bottom
  0.0, 0.0,  // v4
  1.0, 0.0,  // v3
  1.0, 1.0   // v2
]);

LookAtCubeElement2.htmlの書き直し

LookAtCubeElement2.html をtsuda-webgl.js 中の関数を使うように書き直すと LookAtCubeElement4.html になります。

汎用性を高めるため、頂点の位置情報と色の情報を別の配列に入れています。

LookAtCubeElement4.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 src="lib/cuon-matrix.js"></script>
  <script src="tsuda-webgl.js"></script>
  <script language="JavaScript" type="text/javascript">
    //<![CDATA
var VSHADER_SOURCE = "\
attribute vec4 a_Position;\n\
attribute vec4 a_Color;\n\
uniform mat4 u_MvpMatrix;\n\
varying vec4 v_Color;\n\
void main() {\n\
    gl_Position = u_MvpMatrix * a_Position;\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() {
    var app = tsuda.initWebGL("mycanvas",VSHADER_SOURCE,FSHADER_SOURCE);
    if (!app) return;
    var canvas = app.canvas;
    var gl = app.gl;

    //    v6----- v5
    //   /|      /|
    //  v1------v0|
    //  | |     | |
    //  | |v7---|-|v4
    //  |/      |/
    //  v2------v3
    var vertices = tsuda.cube.vertices;
    var indices = tsuda.cube.indices;
    var colors = new Float32Array([
      1.0,  0.0,  0.0,  // 0 v0 :front  
      1.0,  0.0,  0.0,  // 1 v1       
      1.0,  0.0,  0.0,  // 2 v2       
      1.0,  0.0,  0.0,  // 3 v3       
                                                              
      0.0,  1.0,  0.0,  // 4 v0 :right  
      0.0,  1.0,  0.0,  // 5 v3       
      0.0,  1.0,  0.0,  // 6 v4       
      0.0,  1.0,  0.0,  // 7 v5       
                                                              
      0.0,  0.0,  1.0,  // 8 v5 :back   
      0.0,  0.0,  1.0,  // 9 v4       
      0.0,  0.0,  1.0,  //10 v7       
      0.0,  0.0,  1.0,  //11 v6       
                                                              
      1.0,  1.0,  0.0,  //12 v1 :left   
      1.0,  1.0,  0.0,  //13 v6       
      1.0,  1.0,  0.0,  //14 v7       
      1.0,  1.0,  0.0,  //15 v2       
                                                              
      0.0,  1.0,  1.0,  //16 v0 :top    
      0.0,  1.0,  1.0,  //17 v5       
      0.0,  1.0,  1.0,  //18 v6       
      0.0,  1.0,  1.0,  //19 v1       
                                                              
      1.0,  0.0,  1.0,  //20 v2 :bottom 
      1.0,  0.0,  1.0,  //21 v7       
      1.0,  0.0,  1.0,  //22 v4       
      1.0,  0.0,  1.0   //23 v3         
    ]);

    if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;
    if (! tsuda.initArrayBuffer('a_Color',colors,3,gl.FLOAT)) return;

    if (! tsuda.setElementArrayBuffer(indices)) return;

    var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
    if (! u_MvpMatrix) {
        console.log('failed to get location of u_MvpMatrix');
        return;
    }

    var modelMatrix = new Matrix4();
    var viewMatrix = new Matrix4();
    var projMatrix = new Matrix4();
    var mvpMatrix = new Matrix4();

    viewMatrix.setLookAt(3.0, 5.0, 5.0, 0, 0, 0, 0, 1, 0);
    projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
    mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
    gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);

    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.drawElements(gl.TRIANGLES, indices.length,gl.UNSIGNED_BYTE,0);
}
//]]>
  </script>
</head>
<body onload="main()">
  <canvas id="mycanvas" width="320" height="240">
    Your browser does not support Canvas TAB.
  </canvas>
</body>
</html>


平行光源

面が平行光線によって照らされる場合を考えましょう。 この場合は面の明るさを計算するために、面の法線が必要となります。 面を構成する頂点の法線方向は面の法線方向と一致し、 複数の面に含まれる頂点の場合は法線は各面の法線の平均になる、 と考えます。

ParallelLightCube.html の変更点
*** LookAtCubeElement4.html	Thu Jul  3 16:08:07 2014
--- ParallelLightCube.html	Thu Jul  3 16:06:59 2014
***************
*** 13,23 ****
  var VSHADER_SOURCE = "\
  attribute vec4 a_Position;\n\
  attribute vec4 a_Color;\n\
  uniform mat4 u_MvpMatrix;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
      gl_Position = u_MvpMatrix * a_Position;\n\
!     v_Color = a_Color;\n\
  }\n\
  ";
  var FSHADER_SOURCE = "\
--- 13,29 ----
  var VSHADER_SOURCE = "\
  attribute vec4 a_Position;\n\
  attribute vec4 a_Color;\n\
+ attribute vec4 a_Normal;\n\
  uniform mat4 u_MvpMatrix;\n\
+ uniform vec3 u_LightColor;\n\
+ uniform vec3 u_LightDirection;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
      gl_Position = u_MvpMatrix * a_Position;\n\
!     vec3 normal = normalize(a_Normal.xyz);\n\
!     float nDotL = max(dot(u_LightDirection,normal),0.0);\n\
!     vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n\
!     v_Color = vec4(diffuse,a_Color.a);\n\
  }\n\
  ";
  var FSHADER_SOURCE = "\
***************
*** 73,90 ****
        1.0,  0.0,  1.0,  //22 v4       
        1.0,  0.0,  1.0   //23 v3         
      ]);
  
      if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;
      if (! tsuda.initArrayBuffer('a_Color',colors,3,gl.FLOAT)) return;
  
      if (! tsuda.setElementArrayBuffer(indices)) return;
  
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
!     if (! u_MvpMatrix) {
!         console.log('failed to get location of u_MvpMatrix');
          return;
      }
  
      var modelMatrix = new Matrix4();
      var viewMatrix = new Matrix4();
      var projMatrix = new Matrix4();
--- 79,105 ----
        1.0,  0.0,  1.0,  //22 v4       
        1.0,  0.0,  1.0   //23 v3         
      ]);
+     var normals = tsuda.indexNormals(vertices,indices);
  
      if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;
      if (! tsuda.initArrayBuffer('a_Color',colors,3,gl.FLOAT)) return;
+     if (! tsuda.initArrayBuffer('a_Normal',normals,3,gl.FLOAT)) return;
  
      if (! tsuda.setElementArrayBuffer(indices)) return;
  
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
!     var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
!     var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
!     if (! u_MvpMatrix || ! u_LightColor || ! u_LightDirection) {
!         console.log('failed to get location of uniform variable');
          return;
      }
  
+     gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
+     var lightDirection = new Vector3([1.0, 0.8, 0.5]); // world coordinates
+     lightDirection.normalize();
+     gl.uniform3fv(u_LightDirection, lightDirection.elements);
+ 
      var modelMatrix = new Matrix4();
      var viewMatrix = new Matrix4();
      var projMatrix = new Matrix4();

面によって元の色が異なるとシェーディングの効果がわかりにくいので、 同じ色(0,1,0)にしてみます。

ParallelLightCube2.html の変更点
*** ParallelLightCube.html	Thu Jul  3 16:06:59 2014
--- ParallelLightCube2.html	Tue Jul  1 18:28:01 2014
***************
*** 39,84 ****
      var canvas = app.canvas;
      var gl = app.gl;
  
-     //    v6----- v5
-     //   /|      /|
-     //  v1------v0|
-     //  | |     | |
-     //  | |v7---|-|v4
-     //  |/      |/
-     //  v2------v3
      var vertices = tsuda.cube.vertices;
      var indices = tsuda.cube.indices;
!     var colors = new Float32Array([
!       1.0,  0.0,  0.0,  // 0 v0 :front  
!       1.0,  0.0,  0.0,  // 1 v1       
!       1.0,  0.0,  0.0,  // 2 v2       
!       1.0,  0.0,  0.0,  // 3 v3       
!                                                               
!       0.0,  1.0,  0.0,  // 4 v0 :right  
!       0.0,  1.0,  0.0,  // 5 v3       
!       0.0,  1.0,  0.0,  // 6 v4       
!       0.0,  1.0,  0.0,  // 7 v5       
!                                                               
!       0.0,  0.0,  1.0,  // 8 v5 :back   
!       0.0,  0.0,  1.0,  // 9 v4       
!       0.0,  0.0,  1.0,  //10 v7       
!       0.0,  0.0,  1.0,  //11 v6       
!                                                               
!       1.0,  1.0,  0.0,  //12 v1 :left   
!       1.0,  1.0,  0.0,  //13 v6       
!       1.0,  1.0,  0.0,  //14 v7       
!       1.0,  1.0,  0.0,  //15 v2       
!                                                               
!       0.0,  1.0,  1.0,  //16 v0 :top    
!       0.0,  1.0,  1.0,  //17 v5       
!       0.0,  1.0,  1.0,  //18 v6       
!       0.0,  1.0,  1.0,  //19 v1       
!                                                               
!       1.0,  0.0,  1.0,  //20 v2 :bottom 
!       1.0,  0.0,  1.0,  //21 v7       
!       1.0,  0.0,  1.0,  //22 v4       
!       1.0,  0.0,  1.0   //23 v3         
!     ]);
      var normals = tsuda.indexNormals(vertices,indices);
  
      if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;
--- 39,47 ----
      var canvas = app.canvas;
      var gl = app.gl;
  
      var vertices = tsuda.cube.vertices;
      var indices = tsuda.cube.indices;
!     var colors = tsuda.get3Float32Array(0.0, 1.0, 0.0, vertices.length);
      var normals = tsuda.indexNormals(vertices,indices);
  
      if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;

視点の位置を変更してみると、光が当たっていない面が真っ黒であることがわかります。 これは環境光(ほこりなどによって散乱された間接光)の影響を考慮していないからです。

ParallelLightCube3.html の変更点
*** ParallelLightCube2.html	Tue Jul  1 18:28:01 2014
--- ParallelLightCube3.html	Tue Jul  1 18:28:26 2014
***************
*** 70,72 ****
  
!     viewMatrix.setLookAt(3.0, 5.0, 5.0, 0, 0, 0, 0, 1, 0);
      projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
--- 70,72 ----
  
!     viewMatrix.setLookAt(-3.0, 5.0, 5.0, 0, 0, 0, 0, 1, 0);
      projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);

環境光を考慮するプログラムに変更します。 ここでは環境光が(0.2, 0.2, 0.2)で各面を照らしているものとします。

ParallelLightCube4.html の変更点
*** ParallelLightCube3.html	Tue Jul  1 18:28:26 2014
--- ParallelLightCube4.html	Tue Jul  1 18:28:45 2014
***************
*** 17,29 ****
  uniform mat4 u_MvpMatrix;\n\
  uniform vec3 u_LightColor;\n\
  uniform vec3 u_LightDirection;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
      gl_Position = u_MvpMatrix * a_Position;\n\
      vec3 normal = normalize(a_Normal.xyz);\n\
      float nDotL = max(dot(u_LightDirection,normal),0.0);\n\
      vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n\
!     v_Color = vec4(diffuse,a_Color.a);\n\
  }\n\
  ";
  var FSHADER_SOURCE = "\
--- 17,31 ----
  uniform mat4 u_MvpMatrix;\n\
  uniform vec3 u_LightColor;\n\
  uniform vec3 u_LightDirection;\n\
+ uniform vec3 u_AmbientLight;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
      gl_Position = u_MvpMatrix * a_Position;\n\
      vec3 normal = normalize(a_Normal.xyz);\n\
      float nDotL = max(dot(u_LightDirection,normal),0.0);\n\
      vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n\
!     vec3 ambient = u_AmbientLight * a_Color.rgb;\n\
!     v_Color = vec4(diffuse+ambient,a_Color.a);\n\
  }\n\
  ";
  var FSHADER_SOURCE = "\
***************
*** 53,59 ****
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
      var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
      var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
!     if (! u_MvpMatrix || ! u_LightColor || ! u_LightDirection) {
          console.log('failed to get location of uniform variable');
          return;
      }
--- 55,62 ----
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
      var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
      var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
!     var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
!     if (! u_MvpMatrix || ! u_LightColor || ! u_LightDirection || ! u_AmbientLight) {
          console.log('failed to get location of uniform variable');
          return;
      }
***************
*** 62,67 ****
--- 65,71 ----
      var lightDirection = new Vector3([1.0, 0.8, 0.5]); // world coordinates
      lightDirection.normalize();
      gl.uniform3fv(u_LightDirection, lightDirection.elements);
+     gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
  
      var modelMatrix = new Matrix4();
      var viewMatrix = new Matrix4();