LoginSignup
3
3

More than 3 years have passed since last update.

WebGLのattributeで行列(mat3, mat4)を使用する

Posted at

この記事では、WebGLのattributeで行列(mat3, mat4)を使用する方法について解説します。
具体的な使用例として、拡張機能のGeometry Instancingを使用しているときに個々のインスタンスの移動・回転・スケールを表す行列をattributeとして使用する場面を考えます。
サンプルでは、トーラスをGeometry Instancingで大量に描画し、各トーラスの移動・回転・スケールを行列形式のattributeでシェーダーに渡しています。
SampleWebGlMatAttribute.PNG

動くサンプルとソースコードは以下に置いてあります。
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の配列に合わせてvertexAttribPointerstride引数とoffset引数を適切に設定します。
ここではaInstanceMatrixmat4なので4列分、aInstanceNormalMatrixmat3なので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);

この記事ではmat3mat4のattributeを使用する方法について記述しました。
今回は使用しませんでしたが、mat2のattributeでも同様の方法が利用できると思います。

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3