複数のattribute変数で複数の三角形を描いてみる

WebGLの描画テストとしてバーテックスシェーダーに一つのattribute変数を渡して三角形を描くことができたので、続いて複数のattribute変数を使った「座標計算」と「複数の図形描画」を試してみましょう。

attribute変数を複数使うのは簡単で、前回行った「シェーダーのコードでattribute変数を宣言し、JavaScript側でWebGLにattribute変数を割り当てデータを記録したバッファを設定する」処理をattributeの数だけ繰り返すだけです。

たとえば、前回のvPosに加えてvec2型attribute変数tPosを使えるようにするならまずバーテックスシェーダーのコードでtPosをattribute変数として宣言します。JavaScript側では、バッファにデータを格納し、WebGLコンテキストのenableVertexAttribArray()を呼び出してtPosを有効化、バッファをvertexAttribPointer()でtPosのデータとして設定するわけですね。

試しに、attribute変数vPosに頂点データを格納して三角形を描く前回のテストHTMLファイルに頂点を「移動」させるattribute変数tPosを追加してみましょう。前回は、vPosの各要素の値をそのまま三角形の頂点座標として扱いましたが、今回はvPosの各要素にtPosの要素を加えて頂点の座標とします。

頂点の座標は、前回同様vec2型としますが、GLSLではvec2型同士の変数を「+」演算子を使って加算できるので、バーテックスシェーダーに以下のようなコードを設定してみました。

attribute vec2 vPos;
attribute vec2 tPos;

void main() {
	gl_Position = vec4(vPos + tPos, 0.0, 1.0);
}

これで、vPosに三角形の頂点を、tPosに頂点の移動量を設定すれば、各頂点がvPos+tPosの位置に設定されるようになります。たとえば

vPos=(0.0, 0.5, 0.5, -0.5, -0.5, -0.5)
tPos=(0.0, 0.0, 0.1, 0.1, 0.2, 0.2)

とすると、三角形の各頂点は(0 + 0 = 0, 0.5 + 0 = 0.5)、(0.5 + 0.1 = 0.6, -0.5 + 0.1 = -0.4)、(-0.5 + 0.2 = -0.3, -0.5 + 0.2 = -0.3)となるはずです。

実際に、これらの値をvPos/tPosの両attribute変数に設定するには、JavaScript側で以下のようにします。

// attribute変数vPos/tPosの番号を取得
var vPosLocation = gl_context.getAttribLocation(gl_program, 'vPos');
var tPosLocation = gl_context.getAttribLocation(gl_program, 'tPos');

//alert(vPosLocation + "/" + tPosLocation);

// vPosを有効化
gl_context.enableVertexAttribArray(vPosLocation);

// 頂点データをFloat32配列として作成
var vlist = new Float32Array(new Array(0.0, 0.5, 0.5, -0.5, -0.5, -0.5));

// バッファ作成
var vbuf = gl_context.createBuffer();
var tbuf = gl_context.createBuffer();

// vPos用バッファをバインドしてデータ領域を初期化・頂点データを転送する
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, vbuf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, vlist, gl_context.STATIC_DRAW);

// データを書き込んだバッファをvPosのデータとして設定
gl_context.vertexAttribPointer(vPosLocation, 2, gl_context.FLOAT, false, 0, 0);

// tPosを有効化
gl_context.enableVertexAttribArray(tPosLocation);

// tPos用バッファをバインドしてデータ領域を初期化・転移データを転送する
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, tbuf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, new Float32Array(new Array(0.0, 0.0, 0.1, 0.1, 0.2, 0.2)), gl_context.STATIC_DRAW);

// データを書き込んだバッファをtPosのデータとして設定
gl_context.vertexAttribPointer(tPosLocation, 2, gl_context.FLOAT, false, 0, 0);

これで前回と同じように描画すれば、シェーダーのコードでvPosの値にtPosの値が加算され、頂点が移動(三角形が変形)するわけですね。

テストHTMLを開く

次に頂点を移動させる形で「複数の三角形」を描いてみます。具体的には

  1. tPosにすべて0(移動なし)を入れ、vPosで指定した頂点に三角形を描く
  2. tPosに適当な値(各頂点の移動量)を入れ、vPos+tPosの値を頂点に三角形を描く

ことで、vPosで指定した形をそのまま移動しながら二つの三角形を描いてみましょう(tPosにすべて同じ値を設定すると、各頂点の位置関係はそのままに「平行移動」することになります)。

複数の三角形を描くには、まずvPosに頂点の座標を設定し、tPosも0で初期化しておきます。この状態でWebGLコンテキストのdrawArrays()を呼び出せば、vPosで指定した頂点を結ぶ三角形が描かれます。

次に、tPosの各要素に移動量(今回はX座標0、Y座標0.6)を設定して同じくdrawArrays()を呼び出すと、二つ目の三角形がtPosで指定した分だけ移動して描かれます(vPosについては、一つ目の三角形描画前に設定した値がそのまま適用されます)。

二つの三角形を描くテストHTML全体のコードは、以下のとおりです。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>

<canvas id="screen" width="300" height="300" style="background: #000000;"></canvas>

<script>

var screen_elm = document.getElementById("screen");

// canvas要素のWebGLコンテキストを取得
var gl_context = screen_elm.getContext("experimental-webgl");

// バーテックス/フラグメントシェーダーを作成
var vshader = gl_context.createShader(gl_context.VERTEX_SHADER);
var fshader = gl_context.createShader(gl_context.FRAGMENT_SHADER);

// バーテックスシェーダーにソースコードを設定
gl_context.shaderSource(vshader, 'attribute vec2 vPos; attribute vec2 tPos; void main() { gl_Position = vec4(vPos + tPos, 0.0, 1.0); }');

// バーテックスシェーダーのソースをコンパイル
gl_context.compileShader(vshader);

// フラグメントシェーダーにソースコードを設定
gl_context.shaderSource(fshader, 'void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }');

// フラグメントシェーダーのソースをコンパイル
gl_context.compileShader(fshader);

// programオブジェクトを作成
var gl_program = gl_context.createProgram();

// programオブジェクトのシェーダーを設定
gl_context.attachShader(gl_program, vshader);
gl_context.attachShader(gl_program, fshader);

// シェーダーを設定したprogramをリンクし、割り当て
gl_context.linkProgram(gl_program);
gl_context.useProgram(gl_program);

// attribute変数vPos/tPosの番号を取得
var vPosLocation = gl_context.getAttribLocation(gl_program, 'vPos');
var tPosLocation = gl_context.getAttribLocation(gl_program, 'tPos');

//alert(vPosLocation + "/" + tPosLocation);

// vPosを有効化
gl_context.enableVertexAttribArray(vPosLocation);

// 頂点データをFloat32配列として作成
var vlist = new Float32Array(new Array(0.0, 0.3, 0.3, -0.3, -0.3, -0.3));

// バッファ作成
var vbuf = gl_context.createBuffer();
var tbuf = gl_context.createBuffer();

// vPos用バッファをバインドしてデータ領域を初期化・頂点データを転送する
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, vbuf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, vlist, gl_context.STATIC_DRAW);

// データを書き込んだバッファをvPosのデータとして設定
gl_context.vertexAttribPointer(vPosLocation, 2, gl_context.FLOAT, false, 0, 0);

// tPosを有効化
gl_context.enableVertexAttribArray(tPosLocation);

// tPos用バッファをバインドしてデータ領域を初期化・転移データを転送する
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, tbuf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, new Float32Array(new Array(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)), gl_context.STATIC_DRAW);

// データを書き込んだバッファをtPosのデータとして設定
gl_context.vertexAttribPointer(tPosLocation, 2, gl_context.FLOAT, false, 0, 0);

// vPosとtPosで三角形を描画
gl_context.drawArrays(gl_context.TRIANGLES, 0, 3);

// tPosを有効化
gl_context.enableVertexAttribArray(tPosLocation);

// tPos用バッファをバインドしてデータ領域を初期化・転移データを転送する
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, tbuf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, new Float32Array(new Array(0.0, 0.6, 0.0, 0.6, 0.0, 0.6)), gl_context.STATIC_DRAW);

// データを書き込んだバッファをtPosのデータとして設定
gl_context.vertexAttribPointer(tPosLocation, 2, gl_context.FLOAT, false, 0, 0);

// vPosとtPosで三角形を描画
gl_context.drawArrays(gl_context.TRIANGLES, 0, 3);

</script>

</body>
</html>

テストHTMLを開く

これで、頂点を(JavaScript側で設定する情報を元に)シェーダーのコードで移動することができるようになったので、WebGLの最初の関門「自分で行列を作って座標変換」に一歩近づきましたね。


創作プログラミングの街