WebGL = OpenGL + JavaScript
WebGLとは、OpenGLをJavaScriptから扱うためのAPIです。 Webブラウザで3D画像のレンダリングができます。
WebGLの例:https://www.google.co.jp/maps/@34.9828583,135.7588429,320a,35y,356.38h,54.89t/data=!3m1!1e3?hl=ja
OpenGLはそのままだと扱いにくいので、いまどきはUnityとかのフレームワークを使うのでしょうが、今回はそういうのは一切使わずにやってみます。
三角形を書いてみる
書かなければならないものは以下の3つです。
- HTML
- JavaScript
- GLSL ES (1.0系)
GLSLは、見た目はC言語っぽいですですが、OpenGLのシェーディング言語で、これによって反射や屈折などの表現が可能になります。
GLSLは1.0系と3.0系では文法が違うので、in/outなどを使わない古い方で書かないとエラーになります。
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aPosition;
attribute vec3 aColor;
varying lowp vec3 vColor;
void main(void) {
gl_Position = vec4(aPosition, 1.0);
vColor = aColor;
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
varying lowp vec3 vColor;
void main(void) {
gl_FragColor = vec4(vColor, 1.0);
}
</script>
<div><canvas id="main" width="256" height="256"></canvas></div>
※主要な部分のみを抜き出しています。
赤い部分が頂点シェーダ、緑の部分がフラグメントシェーダです。
頂点シェーダは受け取った座標と色をそのまま渡しているだけです(最後の1.0については後述)。フラグメントシェーダは受け取った色をそのまま表示しています(最後の1.0はアルファチャンネル)。
続いてJavaScriptのコードを順に説明します。
const canvas = document.getElementById('main');
const gl = canvas.getContext('webgl', { alpha: false, antialias: false } );
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
WebGLでは、canvasに描画するので、対象のIDを指定して取ってきます。あとの初期化のコードは決まりきったものです。
function compileShader(id, type)
{
const sh = gl.createShader(type);
gl.shaderSource(sh, document.getElementById(id).textContent);
gl.compileShader(sh);
return sh;
}
const vert = compileShader('shader-vs', gl.VERTEX_SHADER);
const frag = compileShader('shader-fs', gl.FRAGMENT_SHADER);
scine.program = gl.createProgram();
gl.attachShader(scine.program, vert);
gl.attachShader(scine.program, frag);
gl.linkProgram(scine.program);
gl.useProgram(scine.program);
scine.attr = {
position: gl.getAttribLocation(scine.program, "aPosition"),
color: gl.getAttribLocation(scine.program, "aColor"),
};
gl.enableVertexAttribArray(scine.attr.position);
gl.enableVertexAttribArray(scine.attr.color);
ごちゃごちゃしていますが、GLSLコードをコンパイル・リンクしています。 本来はエラー処理が必要ですが、説明のため省略しています。
scine.buffer = {
position: gl.createBuffer(),
color: gl.createBuffer(),
};
gl.bindBuffer(gl.ARRAY_BUFFER, scine.buffer.position);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0.5, 0, // 上
-0.5, -0.5, 0, // 左下
0.5, -0.5, 0, // 右下
]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, scine.buffer.color);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 1, 1, // ■
1, 1, 0, // ■
1, 0, 1, // ■
]), gl.STATIC_DRAW);
頂点バッファを作成してGPUに渡しています。
数値は3つで1組です。それぞれ座標(x, y, z)と色(r, g, b)を表します。
レンダリングした結果はこうなりました。色は頂点ごとに指定しますが、頂点の間の色は線形補間されます。
座標系について
OpenGLでは、画面中心が(0, 0)、画面右下が(-1, -1)、左上が(1, 1)で、画面の大きさに関わらず画面端の座標は常に同じになります。
これにZ座標が加わったのが以下の図になります。
重なったときにZ座標が大きいほうが手前に描画されます。