もう少しだけシェーダーの使い方を解説したいと思います。
前回は図形の変換に用いる行列をシェーダーのソースコードの中に書いていましたが、普段プログラムを作る時にそうすることは稀です。外部から設定できるようにして、状況に応じて値を変化させていくことがほとんどです。
ということで、前回のサンプルをベースに次のサンプルを作ってみました。
※Lesson08のみ特別編ということで、<script></script>
以外の部分も変更してあります。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebGL sample08-1</title>
<script type="text/javascript" src="mat.js"></script>
<script type="text/javascript">
var gl;
var mat;
var posVAttrib;
var matUniform;
function start()
{
var canvas = document.getElementById('canvas');
initGL(canvas);
if(gl){
initShader();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
mat = mat4_identity();
drawScene();
}
}
function initGL(canvas){
gl = null;
try{
gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
}
catch(e){
}
if(!gl){
alert('Unable to Initialize WebGL.');
}
}
function initShader()
{
// Vertex shader
var vshader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vshader, 'attribute vec3 pos; uniform mat4 mat; void main(void){ gl_Position = mat * vec4(pos, 1.0); }');
gl.compileShader(vshader);
if(!gl.getShaderParameter(vshader, gl.COMPILE_STATUS)){
alert(gl.getShaderInfoLog(vshader));
return;
}
// Fragment shader
var fshader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fshader, 'void main(void){ gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); }');
gl.compileShader(fshader);
if(!gl.getShaderParameter(fshader, gl.COMPILE_STATUS)){
alert(gl.getShaderInfoLog(fshader));
return;
}
// Create shader program
var program = gl.createProgram();
gl.attachShader(program, fshader);
gl.attachShader(program, vshader);
gl.linkProgram(program);
if(!gl.getProgramParameter(program, gl.LINK_STATUS)){
alert(gl.getProgramInfoLog(program));
return;
}
gl.useProgram(program);
// attribute
posVAttrib = gl.getAttribLocation(program, 'pos');
gl.enableVertexAttribArray(posVAttrib);
// uniform
matUniform = gl.getUniformLocation(program, 'mat');
}
function drawScene(){
gl.clear(gl.COLOR_BUFFER_BIT);
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
var vertices = new Float32Array([0.0, 0.8, 0.0, -0.8, -0.8, 0.0, 0.8, -0.8, 0.0]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(posVAttrib, 3, gl.FLOAT, false, 0, 0);
gl.uniformMatrix4fv(matUniform, false, mat);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.flush();
}
function translate_x()
{
var x = Number(document.getElementById('translate_x').value);
if(x){
var m = mat4_translate(x, 0, 0);
mat = mat4_multiply(mat, m);
drawScene();
}
}
function translate_y()
{
var y = Number(document.getElementById('translate_y').value);
if(y){
var m = mat4_translate(0, y, 0);
mat = mat4_multiply(mat, m);
drawScene();
}
}
function translate_z()
{
var z = Number(document.getElementById('translate_z').value);
if(z){
var m = mat4_translate(0, 0, z);
mat = mat4_multiply(mat, m);
drawScene();
}
}
function rotate_x()
{
var t = Number(document.getElementById('rotate_x').value);
if(t){
var m = mat4_rotate_x(t * Math.PI / 180);
mat = mat4_multiply(mat, m);
drawScene();
}
}
function rotate_y()
{
var t = Number(document.getElementById('rotate_y').value);
if(t){
var m = mat4_rotate_y(t * Math.PI / 180);
mat = mat4_multiply(mat, m);
drawScene();
}
}
function rotate_z()
{
var t = Number(document.getElementById('rotate_z').value);
if(t){
var m = mat4_rotate_z(t * Math.PI / 180);
mat = mat4_multiply(mat, m);
drawScene();
}
}
</script>
</head>
<body onload="start();">
<canvas id="canvas" width="512" height="512"></canvas>
<hr>
<table>
<tr><th>x軸方向に平行移動</th><td><input type="number" id="translate_x" step="0.1" value="0.1"><button onclick="translate_x();">送信</button></td></tr>
<tr><th>y軸方向に平行移動</th><td><input type="number" id="translate_y" step="0.1" value="0.1"><button onclick="translate_y();">送信</button></td></tr>
<tr><th>z軸方向に平行移動</th><td><input type="number" id="translate_z" step="0.1" value="0.1"><button onclick="translate_z();">送信</button></td></tr>
<tr><th>x軸で回転</th><td><input type="number" id="rotate_x" step="5" value="10"><button onclick="rotate_x();">送信</button></td></tr>
<tr><th>y軸で回転</th><td><input type="number" id="rotate_y" step="5" value="10"><button onclick="rotate_y();">送信</button></td></tr>
<tr><th>z軸で回転</th><td><input type="number" id="rotate_z" step="5" value="10"><button onclick="rotate_z();">送信</button></td></tr>
</table>
</body>
</html>
フォームに数字を入力して送信ボタンをクリックすることで三角形の座標変換を確認できます。変換は繰り返し行えますので、30度回転した後に0.1平行移動したり、0.5平行移動した後に60度回転するなど、実際に座標変換を試してみてください。
※<canvas>
の外に飛び出すような大きな値を入力すると黒だけの画面になります。ご注意ください。
実装について少しだけ解説しておきます。
今回のプログラムの注目はmat
という4×4行列を保存する変数です。平行移動や回転のボタンがクリックされる度にtranslate_x
やrotate_x
などの関数が実行され、そこで行列が対応する変換行列に書き変えられることになります。そして、その行列が頂点シェーダーに渡され、三角形が変換された形で表示されることになります。
言葉で説明すると難しいですが、フォームにいろいろな値を入力してみながら、Lesson05の行列と見比べてみてください。