WebGLで使う行列演算の備忘録
目次
はじめに
ライブラリを使用せず素の WebGL で書けるようになりたいと思い、最近はいろいろ勉強してます。WebGL を触るうえで行列演算は避けては通れません。WebGL 用の行列演算の既存のライブラリだと glMatrix がありますが、今回は線形代数の勉強も兼ねて自作で作ってみました。(自作といっても、ほぼほぼ OGL の数学演算系をもって来ているだけですが)
足し算などの簡単なのは飛ばして、何をやってるか忘れそうな処理について備忘録的に書いていきます。長くなるので、主に 3x3 行列をみていきます。
数学演算系のコードは以下に置いてます。
3x3 行列(Mat3)
3x3 (Mat3)行列のクラスは次のようにしてます。
演算系の関数はMat3Funcで書いています。
import * as Mat3Func from "./functions/Mat3Func";
export class Mat3 extends Array<number> {
constructor(
m00 = 1,
m01 = 0,
m02 = 0,
m10 = 0,
m11 = 1,
m12 = 0,
m20 = 0,
m21 = 0,
m22 = 1
) {
super(m00, m01, m02, m10, m11, m12, m20, m21, m22);
}
// ...
}数式で書くと次のような行列表記になります。
デフォルト値は単位行列になります。
ここからは、Mat3Func内をみていきます。
平行移動(translate)
平行移動(x,y)を表す行列 T は次のようになります。
移動後の行列は、次のような行列の掛け算で求まります。
以上をコードで書くと次のようになります。
export function translate(out: Mat3, a: Mat3, v: Vec2): Mat3 {
const a00 = a[0],
a01 = a[1],
a02 = a[2],
a10 = a[3],
a11 = a[4],
a12 = a[5],
a20 = a[6],
a21 = a[7],
a22 = a[8],
x = v[0],
y = v[1];
out[0] = a00;
out[1] = a01;
out[2] = a02;
out[3] = a10;
out[4] = a11;
out[5] = a12;
out[6] = x * a00 + y * a10 + a20;
out[7] = x * a01 + y * a11 + a21;
out[8] = x * a02 + y * a12 + a22;
return out;
}回転(rotate)
角度の回転行列は次のようになります。
回転後の行列は、次のような行列の掛け算で求まります。
計算すると
以上をコードで書くと次のようになります。
export function rotate(out: Mat3, a: Mat3, rad: number): Mat3 {
const a00 = a[0],
a01 = a[1],
a02 = a[2],
a10 = a[3],
a11 = a[4],
a12 = a[5],
a20 = a[6],
a21 = a[7],
a22 = a[8],
s = Math.sin(rad),
c = Math.cos(rad);
out[0] = c * a00 + s * a10;
out[1] = c * a01 + s * a11;
out[2] = c * a02 + s * a12;
out[3] = c * a10 - s * a00;
out[4] = c * a11 - s * a01;
out[5] = c * a12 - s * a02;
out[6] = a20;
out[7] = a21;
out[8] = a22;
return out;
}拡大(scale)
スケール行列は次のようになります。
回転後の行列は、次のような行列の掛け算で求まります。
計算すると
以上をコードで書くと次のようになります。
export function scale(out: Mat3, a: Mat3, v: Vec2): Mat3 {
const x = v[0];
const y = v[1];
out[0] = x * a[0];
out[1] = x * a[1];
out[2] = x * a[2];
out[3] = y * a[3];
out[4] = y * a[4];
out[5] = y * a[5];
out[6] = a[6];
out[7] = a[7];
out[8] = a[8];
return out;
}転置行列(transpose)
転置行列は行と列を入れ替えた行列になります。
行列 M の転置行列は次のようになります。
コードで書くと次のようになります。
export function transpose(out: Mat3, a: Mat3): Mat3 {
if (out === a) {
// 上書き前に退避
const a01 = a[1];
const a02 = a[2];
const a12 = a[5];
out[1] = a[3];
out[2] = a[6];
out[3] = a01;
out[5] = a[7];
out[6] = a02;
out[7] = a12;
} else {
out[0] = a[0];
out[1] = a[3];
out[2] = a[6];
out[3] = a[1];
out[4] = a[4];
out[5] = a[7];
out[6] = a[2];
out[7] = a[5];
out[8] = a[8];
}
return out;
}ここで、out === aの場合は、対角成分は変わらないので、それ以外の成分を入れ替えます。
行列式(determinant)
3x3 の行列式は、以下のようになります。
コードでは次のようになります。
export function determinant(a: Mat3): number {
const a00 = a[0],
a01 = a[1],
a02 = a[2],
a10 = a[3],
a11 = a[4],
a12 = a[5],
a20 = a[6],
a21 = a[7],
a22 = a[8];
return (
a00 * (a11 * a22 - a12 * a21) +
a01 * (a12 * a20 - a10 * a22) +
a02 * (a10 * a21 - a11 * a20)
);
}逆行列(inverse)
行列 M の逆行列を掛けたときに、単位行列 I になるような行列を逆行列といいます。
行列式をとすると、逆行列は次のようになります。
コードで書くと次のようになります。
export function invert(out: Mat3, a: Mat3): Mat3 {
const a00 = a[0],
a01 = a[1],
a02 = a[2],
a10 = a[3],
a11 = a[4],
a12 = a[5],
a20 = a[6],
a21 = a[7],
a22 = a[8];
const b01 = a11 * a22 - a12 * a21;
const b11 = a12 * a20 - a10 * a22;
const b21 = a10 * a21 - a11 * a20;
let det = a00 * b01 + a01 * b11 + a02 * b21;
det = 1.0 / det;
out[0] = b01 * det;
out[1] = (a02 * a21 - a01 * a22) * det;
out[2] = (a01 * a12 - a02 * a11) * det;
out[3] = b11 * det;
out[4] = (a00 * a22 - a02 * a20) * det;
out[5] = (a02 * a10 - a00 * a12) * det;
out[6] = b21 * det;
out[7] = (a01 * a20 - a00 * a21) * det;
out[8] = (a00 * a11 - a01 * a10) * det;
return out;
}4x4 行列(Mat4)
4x4 (Mat4)行列のクラスは次のようにしてます。演算系の関数はMat4Funcで書いています。
import * as Mat4Func from "./functions/Mat4Func";
export class Mat4 extends Array<number> {
constructor(
m00 = 1,
m01 = 0,
m02 = 0,
m03 = 0,
m10 = 0,
m11 = 1,
m12 = 0,
m13 = 0,
m20 = 0,
m21 = 0,
m22 = 1,
m23 = 0,
m30 = 0,
m31 = 0,
m32 = 0,
m33 = 1
) {
// 同様にsuper()する
}
// ...
}4x4 行列の演算は時間があるときに更新します。