プログラムに似たような記述が繰り返し出てくるので、その部分を関数呼び出しに することにしましょう。 今後のプログラムの説明でも利用するので、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 を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> |
面が平行光線によって照らされる場合を考えましょう。 この場合は面の明るさを計算するために、面の法線が必要となります。 面を構成する頂点の法線方向は面の法線方向と一致し、 複数の面に含まれる頂点の場合は法線は各面の法線の平均になる、 と考えます。
面によって元の色が異なるとシェーディングの効果がわかりにくいので、 同じ色(0,1,0)にしてみます。
視点の位置を変更してみると、光が当たっていない面が真っ黒であることがわかります。 これは環境光(ほこりなどによって散乱された間接光)の影響を考慮していないからです。
環境光を考慮するプログラムに変更します。 ここでは環境光が(0.2, 0.2, 0.2)で各面を照らしているものとします。