この記事では、WebGLのattributeで行列(mat3
, mat4
)を使用する方法について解説します。
具体的な使用例として、拡張機能のGeometry Instancingを使用しているときに個々のインスタンスの移動・回転・スケールを表す行列をattributeとして使用する場面を考えます。
サンプルでは、トーラスをGeometry Instancingで大量に描画し、各トーラスの移動・回転・スケールを行列形式のattributeでシェーダーに渡しています。
動くサンプルとソースコードは以下に置いてあります。
aadebdeb/SampleWebGlMatAttribute: Sample of matrix attributes in WebGL
このサンプルでは、数学ライブラリとしてglMatrixを使用しています。
今回使用する頂点シェーダーは以下のようになります。インスタンスごとのモデル行列aInstanceMatrix
と法線をオブジェクト座標からワールド座標に変換する行列aInstanceNormalMatrix
が行列形式のattributeになります。
attribute vec3 aPosition;
attribute vec3 aNormal;
attribute mat4 aInstanceMatrix; // インスタンスのモデル行列
attribute mat3 aInstanceNormalMatrix; // インスタンスの法線モデル行列
attribute vec3 aInstanceColor; // インスタンスの色
uniform mat4 uViewProjectionMatrix;
varying vec3 vColor;
varying vec3 vWorldNormal;
void main() {
gl_Position = uViewProjectionMatrix * aInstanceMatrix * vec4(aPosition, 1.0);
vColor = aInstanceColor;
vWorldNormal = aInstanceNormalMatrix * aNormal;
}
JavaScript側では、最初にデータの生成を行います。ランダムに移動・回転・スケールを決定した行列(instanceMat
)をインスタンスごとに生成して、その行列を一つの配列(instanceMatArray
)にまとめます。その配列をもとにVBOを作成します。
const instanceMatArray = [];
const instanceNormalMatArray = [];
for (let i = 0; i < torusNum; ++i) {
const instanceMat = mat4.create(); // Float32Array
const rotation = quat.create();
quat.fromEuler(rotation, 360 * Math.random(), 360 * Math.random(), 360 * Math.random());
const translation = vec3.fromValues(50.0 * (2.0 * Math.random() - 1.0), 50.0 * (2.0 * Math.random() - 1.0), 50.0 * (2.0 * Math.random() - 1.0));
const scale = vec3.fromValues(0.5 + 1.5 * Math.random(), 0.5 + 1.5 * Math.random(), 0.5 + 1.5 * Math.random());
mat4.fromRotationTranslationScale(instanceMat, rotation, translation, scale);
instanceMatArray.push(...instanceMat);
const instanceNormalMat = mat3.create(); // Float32Array、法線モデル行列はモデル行列の逆行列の転置長列
mat3.fromMat4(instanceNormalMat, instanceMat);
mat3.transpose(instanceNormalMat, instanceNormalMat);
mat3.invert(instanceNormalMat, instanceNormalMat);
instanceNormalMatArray.push(...instanceNormalMat);
}
const instanceMatVbo = createVbo(gl, new Float32Array(instanceMatArray));
const instanceNormalMatVbo = createVbo(gl, new Float32Array(instanceNormalMatArray));
作成したVBOをattributeとして設定していきます。行列のattributeの場合は行列の各列ごとに設定を行います。getAttribLocation
で取得したインデックスが行列の1列目用、その次のインデックスが2列目用、3つ目が3列目用...というようになります。さきほど生成したVBOの配列に合わせてvertexAttribPointer
のstride
引数とoffset
引数を適切に設定します。
ここではaInstanceMatrix
がmat4
なので4列分、aInstanceNormalMatrix
がmat3
なので3列分の設定を行っています。
const extInstancedArray = gl.getExtension('ANGLE_instanced_arrays')
function getAttribLocs(gl, program, names) {
const locs = new Map();
names.forEach(name => locs.set(name, gl.getAttribLocation(program, name)));
return locs;
}
const attribLocs = getAttribLocs(gl, program, ['aPosition', 'aNormal', 'aInstanceMatrix', 'aInstanceNormalMatrix', 'aInstanceColor']);
gl.bindBuffer(gl.ARRAY_BUFFER, instanceMatVbo);
gl.enableVertexAttribArray(attribLocs.get('aInstanceMatrix'));
gl.enableVertexAttribArray(attribLocs.get('aInstanceMatrix') + 1);
gl.enableVertexAttribArray(attribLocs.get('aInstanceMatrix') + 2);
gl.enableVertexAttribArray(attribLocs.get('aInstanceMatrix') + 3);
gl.vertexAttribPointer(attribLocs.get('aInstanceMatrix'), 4, gl.FLOAT, false, 64, 0); // 64 = 16 * 4byte
gl.vertexAttribPointer(attribLocs.get('aInstanceMatrix') + 1, 4, gl.FLOAT, false, 64, 16);
gl.vertexAttribPointer(attribLocs.get('aInstanceMatrix') + 2, 4, gl.FLOAT, false, 64, 32);
gl.vertexAttribPointer(attribLocs.get('aInstanceMatrix') + 3, 4, gl.FLOAT, false, 64, 48);
extInstancedArray.vertexAttribDivisorANGLE(attribLocs.get('aInstanceMatrix'), 1);
extInstancedArray.vertexAttribDivisorANGLE(attribLocs.get('aInstanceMatrix') + 1, 1);
extInstancedArray.vertexAttribDivisorANGLE(attribLocs.get('aInstanceMatrix') + 2, 1);
extInstancedArray.vertexAttribDivisorANGLE(attribLocs.get('aInstanceMatrix') + 3, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, instanceNormalMatVbo);
gl.enableVertexAttribArray(attribLocs.get('aInstanceNormalMatrix'));
gl.enableVertexAttribArray(attribLocs.get('aInstanceNormalMatrix') + 1);
gl.enableVertexAttribArray(attribLocs.get('aInstanceNormalMatrix') + 2);
gl.vertexAttribPointer(attribLocs.get('aInstanceNormalMatrix'), 3, gl.FLOAT, false, 36, 0); // 36 = 9 * 4byte
gl.vertexAttribPointer(attribLocs.get('aInstanceNormalMatrix') + 1, 3, gl.FLOAT, false, 48, 12);
gl.vertexAttribPointer(attribLocs.get('aInstanceNormalMatrix') + 2, 3, gl.FLOAT, false, 48, 24);
extInstancedArray.vertexAttribDivisorANGLE(attribLocs.get('aInstanceNormalMatrix'), 1);
extInstancedArray.vertexAttribDivisorANGLE(attribLocs.get('aInstanceNormalMatrix') + 1, 1);
extInstancedArray.vertexAttribDivisorANGLE(attribLocs.get('aInstanceNormalMatrix') + 2, 1);
準備が終わったら、drawElementsInstancedANGLE
で描画を実行します。
extInstancedArray.drawElementsInstancedANGLE(gl.TRIANGLES, torus.indices.length, gl.UNSIGNED_SHORT, 0, torusNum);
この記事ではmat3
、mat4
のattributeを使用する方法について記述しました。
今回は使用しませんでしたが、mat2
のattributeでも同様の方法が利用できると思います。