WebGLでお絵かき (1)

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座標が大きいほうが手前に描画されます。