WebGLを使ってみる―2次元ガウス分布を描画―
WebGLとは、3次元コンピュータグラッフィックを実現するOpenGLを、 ウェブブラウザ上でプラグインなしで利用するための仕様です。 HTML5 + Javascript で利用することができます。 WebGLの最大の特徴は、PCのグラフィックカードに直接描画データを送ることが出来るため、 見た目にも重そうな3次元グラフィックを高速に描画することができるとのことです。 ただし、今のところ閲覧可能なブラウザは限られています(利用可能なブラウザについてはこちらをご覧ください)。
2次元ガウス分布の描画
WebGL の使い方を勉強がてら、2次元ガウス分布を描画てみます。
【2次元ガウス分布についての参考ページ】
■ 1軸ガウシアンによる電子パルスの拡散
■ gnuplot 3次元カラーマップで補間(interpolate)
WebGLによる描画結果
WebGLに対応しているブラウザでは、下のインラインフレーム内で2次元ガウス分布が移動してのが確認できると思います。※静止画では寂しいので、視点をLookAt関数を用いて動かしています。
HTML5+Javascript プログラムソース
本プログラムソースは、下記のページをかなり参考にして勉強させていただいております。
→Hack The WebGL (WebGL勉強会)
※注意:使い始めたばかりなので、WebGLの関数を開発者の意図通り使っていない可能性があります。
<html> <head> <title>Learning WebGL — lesson 3</title> <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> <script type="text/javascript" src="glMatrix-0.9.5.min.js"></script> <script type="text/javascript" src="webgl-utils.js"></script> <!--シェーダ言語--> <script id="shader-fs" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor; void main(void) { gl_FragColor = vColor; } </script> <script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; attribute vec4 aVertexColor; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; varying vec4 vColor; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vColor = aVertexColor; } </script> <script> var gl; function initGL(canvas) { try { gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; } catch (e) { } if (!gl) { //alert("Could not initialise WebGL, sorry :-("); document.getElementById("errer").innerHTML = '<p style="text-align:center;font-size:small; color:red">お使いの環境ではWebGLはご利用いただけません。<br />WebGLに対応していない方のためにGIFファイルを以下に用意しました。<br /><br /><img src="http://www.natural-science.or.jp/images/20120201-1.gif" alt="WEBGLデモ" /></p>'; } } function getShader(gl, id) { var shaderScript = document.getElementById(id); if (!shaderScript) { return null; } var str = ""; var k = shaderScript.firstChild; while (k) { if (k.nodeType == 3) { str += k.textContent; } k = k.nextSibling; } var shader; if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); } else { return null; } gl.shaderSource(shader, str); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert(gl.getShaderInfoLog(shader)); return null; } return shader; } var shaderProgram; function initShaders() { var fragmentShader = getShader(gl, "shader-fs"); var vertexShader = getShader(gl, "shader-vs"); shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Could not initialise shaders"); } gl.useProgram(shaderProgram); shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); } var mvMatrix = mat4.create(); var mvMatrixStack = []; var pMatrix = mat4.create(); function mvPushMatrix() { var copy = mat4.create(); mat4.set(mvMatrix, copy); mvMatrixStack.push(copy); } function mvPopMatrix() { if (mvMatrixStack.length == 0) { throw "Invalid popMatrix!"; } mvMatrix = mvMatrixStack.pop(); } function setMatrixUniforms() { gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix); gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix); } function degToRad(degrees) { return degrees * Math.PI / 180; } //2次元ガウス分布 var N = 100; //縦と横の格子点の数 → 総格子点の数:N*N var sigma = 5; //ガウス分布の分散 var A = 0; //z軸の値 var d = 2/N; //格子点の間隔 var ary = new Array(N); //2次元ガウス分布用の2次元配列の初期化 var eyeX = 0; //視点のx座標 var eyeX_max = 1.0; //視点のx座標の最大値 for (var i = 0; i < N; i++) { ary[i] = new Array(N); } //直方体バッファー var latticeVertexPositionBuffer; var latticeVertexColorBuffer; var latticeVertexIndexBuffer; function initBuffers() { /////////////////////////////////////////////// //2次元格子点 /////////////////////////////////////////////// //格子点の座標 ////////////////////////////////// latticeVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, latticeVertexPositionBuffer); var vertices = new Array(3*N*N); //全ての格子点の座標を格納する配列 for (var i = 0; i < N; i++){ for (var j = 0; j < N; j++){ var x = (i-N/2) * d; //(i,j)番目の格子点のx座標 var y = (j-N/2) * d; //(i,j)番目の格子点のx座標 ary[i][j] = Math.exp(-sigma * (Math.pow(x,2)+Math.pow(y,2))); //カウス分布 vertices [3*(N*i+j)] = x; //(i,j)番目の格子点のx座標を代入 vertices [3*(N*i+j)+1] = y; //(i,j)番目の格子点のy座標を代入 vertices [3*(N*i+j)+2] = A*ary[i][j]; //(i,j)番目の格子点のz座標を代入 } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); latticeVertexPositionBuffer.itemSize = 3; //x,y,zの3成分 latticeVertexPositionBuffer.numItems = N*N; //格子点の総数 //直方体の頂点カラー /////////////////////////////////////////////// latticeVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, latticeVertexColorBuffer); var colors = new Array(4*N*N); //すべての格子点のRGBAを格納する配列 for (var i = 0; i < N; i++){ for (var j = 0; j < N; j++){ colors [4*(N*i+j)] = ary[i][j]-ary[i][j]; //R(赤) colors [4*(N*i+j)+1] = ary[i][j]; //G(緑) colors [4*(N*i+j)+2] = ary[i][j]+ary[i][j]; //B(青) colors [4*(N*i+j)+3] = 1; //A(透明度) } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); //latticeVertexColorBuffer を設定 latticeVertexColorBuffer.itemSize = 4; //RGBAの4つ latticeVertexColorBuffer.numItems = N*N; //格子点の数 //格子点のインデックスバッファー /////////////////////////////////////////////// latticeVertexIndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, latticeVertexIndexBuffer); var latticeVertexIndices = new Array(2*(N-1)*(N-1)); //頂点インデックスを格納する配列(正方格子N-1個×2) //「2」は正方格子を2つの三角形に分割 for (var i = 0; i < N-1; i++){ for (var j = 0; j < N-1; j++){ latticeVertexIndices[6*((N-1)*i+j)] = N*i+j; //三角形1の頂点1 latticeVertexIndices[6*((N-1)*i+j)+1] = N*i+(j+1); //三角形1の頂点2 latticeVertexIndices[6*((N-1)*i+j)+2] = N*(i+1)+j; //三角形1の頂点3 latticeVertexIndices[6*((N-1)*i+j)+3] = N*i+(j+1); //三角形2の頂点1 latticeVertexIndices[6*((N-1)*i+j)+4] = N*(i+1)+j; //三角形2の頂点2 latticeVertexIndices[6*((N-1)*i+j)+5] = N*(i+1)+(j+1); //三角形2の頂点3 } } gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(latticeVertexIndices), gl.STATIC_DRAW); latticeVertexIndexBuffer.itemSize = 1; latticeVertexIndexBuffer.numItems = (N-1)*(N-1)*3*2; //(正方格子の数:(N-1)*(N-1))*(三角形の頂点数:3)×(正方格子の分割数 2) } function drawScene() { gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix); /////////////////////////////////////////////// //四面体の描画 /////////////////////////////////////////////// mat4.identity(mvMatrix); //lookAtも利用可能 mat4.lookAt( vec3.create([eyeX, 1.5, 1.5]), // 視点の位置座標 vec3.create([eyeX, 0, 0]), // 目標の位置座標 vec3.create([0,0,1]), // 画面上の指定 mvMatrix ); /////////////////////////////////////////////// //格子点の描画 /////////////////////////////////////////////// mvPushMatrix(); gl.bindBuffer(gl.ARRAY_BUFFER, latticeVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, latticeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, latticeVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, latticeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, latticeVertexIndexBuffer); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, latticeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);//インデックスバッファーを使って描画する mvPopMatrix(); } var flag = 0; function animate() { if(flag == 0) eyeX += (eyeX_max - eyeX)/100; else eyeX += (-eyeX_max - eyeX)/100; if(eyeX > eyeX_max*0.5) { flag = 1; eyeX =eyeX_max*0.5; } if(eyeX < -eyeX_max*0.5) { flag = 0; eyeX =-eyeX_max*0.5; } } function tick() { drawScene(); animate(); requestAnimFrame(tick); } function webGLStart() { var canvas = document.getElementById("glcanvas"); initGL(canvas); initShaders(); initBuffers(); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.DEPTH_TEST); tick(); } </script> </head> <body onload="webGLStart();"> <a href="http://learningwebgl.com/blog/?p=239"><< Back to Lesson 3</a><br /> <canvas id="lesson03-canvas" style="border: none;" width="500" height="500"></canvas> <br/> <a href="http://learningwebgl.com/blog/?p=239"><< Back to Lesson 3</a><br /> </body> </html>
Google ChromeのJavascript デバッガ
参考までに、プログラミングする場合、デバッガが必要となりますが、Google Chromeに標準でついている デバッガが良かったのです。