#はじめに
three.jsを書いていて感じるのです。
毎フレーム実行しているrenderer.render(scene, camera)
の中ってどうなってるんだ。。
renderer.render(scene, camera, renderTarget)
とかやって、textureを所得して、postprocessing処理しるけど、いまいち内部何やってるのかわかってないぞ。。
three.js(pixi.js)を書くことがwebglなってるけど、違和感感じるな。。でも、あまりわかってないから、何も言えない。。
一度自分の中でWebGLについて調べて、整理してみることにしました。
今回勉強することで、three.jsでのライティングやpost processing/gpgpuなどの技術の理解を深めたり、デバイス・パフォーマンス毎における最適化などに役に立つようにしたいです。
webgl apiの説明はあまり書いてないので、ご了承ください。
#WebGLって
An Introduction to WebGL — Part 1によると、
webglのapiはOpenGL ES2のAPIをベースにしてるそうです。(webgl2はOpenGL ES3がべーすになってるそうです。)APIのチートシートはこちらから確認できます。
When programming in WebGL, you are usually aiming to render a scene of some kind. This usually includes multiple subsequent draw jobs or “calls”, each of which is carried out in the GPU through a process called the rendering pipeline.
rendering pipelineと呼ばれる処理をGPUで実行し、webglの描画を行っているそうです。
Rendering Pipeline
ざっくりと理解してるところによると、、
- vertex arraysとして頂点を配列に記述していきます。
- vertex arraysをGPU上にvertex bufferとして送ります。
- vertex shaderでvertex bufferの頂点情報からスクリーンで投影される頂点情報の計算などを行います
- 計算された頂点情報を三角形としてつなげたり、直線としてつなげたり、点としてしたりするかの処理を行います。
- rasterizerといって、頂点の情報をピクセルの情報として処理します。
- Fragment shaderでは1ピクセル毎に色ぬりを行います。
- frame bufferとしてアウトプットされたり、textureとしてレンダリングされます。
詳しく知りたい方はAn Introduction to WebGL — Part 1に詳しい説明があるので、そちらを読んでください。
とても複雑ですね、、Vertex/Fragment shaderはGLSL( Graphics Library Shader Language)で書いていきます。
GLSLについてはこちらが詳しくまとめらています。(http://qiita.com/doxas/items/5a7b6dedff4bc2ce1586 )
three.jsを書くとわかるのですが、shaderMaterialを書くとき以外シェーダは書かないのではというつっこみを入れたくなります。
three.jsは、シェーダをかけなくても基本的なことはできるようになっています。
シェーダはjsと書き方が違ったり、エラー判定がjsより厳しいので、慣れるのに時間がかかります。
初心者でもシェーダを考えず3dプログラムができることはいいのですが、WebGLがどのように動いているのかを理解しにくくなっていると思います。
three.js自体もwebglライブラリではなく、webgl/canvas/css3を使った3dライブラリです。
シェーダを考えず3dプログラムを初心者でもできる方にフォーカスするのは当然の流れですよね。
#Raw WebGLで三角形を書いてみる
source code: http://codepen.io/kenjiSpecial/pen/ENbzjp
こんな感じで書きました。
var canvas, gl, program;
var vertexShaderSource =
'attribute vec4 aPosition;\n' +
'void main(){\n' +
'gl_Position = vec4(aPosition.x, aPosition.y, 0.0, 1.0);\n' +
'}';
var fragmentShaderSource =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'void main(){\n' +
'gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n' +
'}';
function init(){
canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl = canvas.getContext('webgl');
createProgram();
initVertexBuffer();
draw();
}
// シェーダプログラムの作成
function createProgram(){
var vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if(!linked){
let error = gl.getProgramInfoLog(program);
console.error('Failed to link program:' + error);
}
gl.useProgram(program);
}
// vertex array と vertex bufferを作成
function initVertexBuffer(){
var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);
var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
var aPosition = gl.getAttribLocation(program, 'aPosition');
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPosition);
}
// シェーダをコンパイルする
function compileShader( gl, type, shaderSource ){
var shader = gl.createShader(type);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if(!compiled){
var error = gl.getShaderInfoLog(shader);
console.error('Faile to compiler shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
// 三角形を描画する
function draw(){
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
init();
一方、canvasでは
var canvas = document.createElement('canvas');
var ctx = canvs.getContext('2d');
ctx.fillStyle = '#ffff00';
ctx.beginPath();
ctx.moveTo(75,50);
ctx.lineTo(100,75);
ctx.lineTo(100,25);
ctx.fill();
canvas apiがどれだけシンプルか一目瞭然です。
シンプルな図形を描画するのにwebglの魅力はなかなか伝わりづらいです。
webglではシェーダプログラムの作成と頂点データの配列とシェーダプログラムにデータを渡すことで描画を行っています。
頂点データをシェーダプログラムに渡すところを見ていきましょう。
// vertex array と vertex bufferを作成
function initVertexBuffer(){
var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);
var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
var aPosition = gl.getAttribLocation(program, 'aPosition');
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPosition);
}
bufferを作成したりと色々面倒なことを、、と正直思います。
function initVertexBuffer(){
var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);
program.sendVerticeData(vertices, 'aPosition');
}
これぐらい簡単に書きたいですが、、そうはいきませんね。
三角形という2次元の図形を描画できたのですが、まだまだ3次元までは遠いです。
WebGLのコードを見てわかると思うのですが、どこにもcameraのようなものはありません。
次回はWebGLで立方体を書いてみたいと思います。
#参考サイト
WebGL 1.0 API Quick Reference Card