Munus

background

WebGLで使う行列演算の備忘録

目次

はじめに#

ライブラリを使用せず素の WebGL で書けるようになりたいと思い、最近はいろいろ勉強してます。WebGL を触るうえで行列演算は避けては通れません。WebGL 用の行列演算の既存のライブラリだと glMatrix がありますが、今回は線形代数の勉強も兼ねて自作で作ってみました。(自作といっても、ほぼほぼ OGL の数学演算系をもって来ているだけですが)

足し算などの簡単なのは飛ばして、何をやってるか忘れそうな処理について備忘録的に書いていきます。長くなるので、主に 3x3 行列をみていきます。

数学演算系のコードは以下に置いてます。

webgl-study-note/src/lib/webgl/math at main · nono-k/webgl-study-note
Contribute to nono-k/webgl-study-note development by creating an account on GitHub.
webgl-study-note/src/lib/webgl/math at main · nono-k/webgl-study-note favicon
github.com
webgl-study-note/src/lib/webgl/math at main · nono-k/webgl-study-note

3x3 行列(Mat3)#

3x3 (Mat3)行列のクラスは次のようにしてます。
演算系の関数はMat3Funcで書いています。

Mat3.ts
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);
  }
  // ...
}

数式で書くと次のような行列表記になります。

M=[m00m10m20m01m11m21m02m12m22]M = \begin{bmatrix} m_{00} & m_{10} & m_{20} \\ m_{01} & m_{11} & m_{21} \\ m_{02} & m_{12} & m_{22} \end{bmatrix}

デフォルト値は単位行列になります。

I=[100010001]I = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

ここからは、Mat3Func内をみていきます。

平行移動(translate)#

平行移動(x,y)を表す行列 T は次のようになります。

T=[10x01y001]T = \begin{bmatrix} 1 & 0 & x \\ 0 & 1 & y \\ 0 & 0 & 1 \end{bmatrix}

移動後の行列MM'は、次のような行列の掛け算で求まります。

M=MT=[m00m10m20m01m11m21m02m12m22][10x01y001]=[m00m10m00x+m10y+m20m01m11m01x+m11y+m21m02m12m02x+m12y+m22]M' = M \cdot T = \begin{bmatrix} m_{00} & m_{10} & m_{20} \\ m_{01} & m_{11} & m_{21} \\ m_{02} & m_{12} & m_{22} \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & x \\ 0 & 1 & y \\ 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} m_{00} & m_{10} & m_{00}x + m_{10}y + m_{20}\\ m_{01} & m_{11} & m_{01}x + m_{11}y + m_{21} \\ m_{02} & m_{12} & m_{02}x + m_{12}y + m_{22} \end{bmatrix}

以上をコードで書くと次のようになります。

translate
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)#

角度θ\thetaの回転行列R(θ)R(\theta)は次のようになります。

R(θ)=[cosθsinθ0sinθcosθ0001]R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}

回転後の行列MM'は、次のような行列の掛け算で求まります。

M=MR(θ)=[m00m10m20m01m11m21m02m12m22][cosθsinθ0sinθcosθ0001]M' = M \cdot R(\theta) = \begin{bmatrix} m_{00} & m_{10} & m_{20} \\ m_{01} & m_{11} & m_{21} \\ m_{02} & m_{12} & m_{22} \end{bmatrix} \cdot \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}

計算すると

M=[m00cosθ+m10sinθm00sinθ+m10cosθm20m01cosθ+m11sinθm01sinθ+m11cosθm21m02cosθ+m12sinθm02sinθ+m12cosθm22]M' = \begin{bmatrix} m_{00}\cos\theta + m_{10}\sin\theta & -m_{00}\sin\theta + m_{10}\cos\theta & m_{20} \\ m_{01}\cos\theta + m_{11}\sin\theta & -m_{01}\sin\theta + m_{11}\cos\theta & m_{21} \\ m_{02}\cos\theta + m_{12}\sin\theta & -m_{02}\sin\theta + m_{12}\cos\theta & m_{22} \end{bmatrix}

以上をコードで書くと次のようになります。

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)#

スケール行列SSは次のようになります。

S=[sx000sy0001]S = \begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix}

回転後の行列MM'は、次のような行列の掛け算で求まります。

M=MS[m00m10m20m01m11m21m02m12m22][sx000sy0001]M' = M \cdot S \begin{bmatrix} m_{00} & m_{10} & m_{20} \\ m_{01} & m_{11} & m_{21} \\ m_{02} & m_{12} & m_{22} \end{bmatrix} \cdot \begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix}

計算すると

M=[m00sxm10sym20m01sxm11sym21m02sxm12sym22]M' = \begin{bmatrix} m_{00}s_x & m_{10}s_y & m_{20} \\ m_{01}s_x & m_{11}s_y & m_{21} \\ m_{02}s_x & m_{12}s_y & m_{22} \end{bmatrix}

以上をコードで書くと次のようになります。

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 の転置行列は次のようになります。

M=[m00m10m20m01m11m21m02m12m22]MT=[m00m01m02m10m11m12m20m21m22]M = \begin{bmatrix} m_{00} & m_{10} & m_{20} \\ m_{01} & m_{11} & m_{21} \\ m_{02} & m_{12} & m_{22} \end{bmatrix} \Rightarrow M^T = \begin{bmatrix} m_{00} & m_{01} & m_{02} \\ m_{10} & m_{11} & m_{12} \\ m_{20} & m_{21} & m_{22} \end{bmatrix}

コードで書くと次のようになります。

transpose
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 の行列式は、以下のようになります。

det(M)=m00(m11m22m12m21)m01(m12m20m10m22)+m02(m10m21m11m20)det(M) = m_{00}(m_{11}m_{22} - m_{12}m_{21}) - m_{01}(m_{12}m_{20} - m_{10}m_{22}) + m_{02}(m_{10}m_{21} - m_{11}m_{20})

コードでは次のようになります。

determinant
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 の逆行列M1M^{-1}を掛けたときに、単位行列 I になるような行列M1M^{-1}を逆行列といいます。

MM1=IM \cdot M^{-1} = I

行列式をdetdetとすると、逆行列M1M^{-1}は次のようになります。

M11det[(m11m22m12m21)(m20m12m10m22)(m10m21m20m11)(m02m21m01m22)(m00m22m02m20)(m20m01m00m21)(m01m12m02m11)(m02m10m00m12)(m00m11m01m10)]M^{-1} \frac{1}{det} \begin{bmatrix} (m_{11}m_{22} - m_{12}m_{21}) & (m_{20}m_{12} - m_{10}m_{22}) & (m_{10}m_{21} - m_{20}m_{11}) \\ (m_{02}m_{21} - m_{01}m_{22}) & (m_{00}m_{22} - m_{02}m_{20}) & (m_{20}m_{01} - m_{00}m_{21}) \\ (m_{01}m_{12} - m_{02}m_{11}) & (m_{02}m_{10} - m_{00}m_{12}) & (m_{00}m_{11} - m_{01}m_{10}) \end{bmatrix}

コードで書くと次のようになります。

inverse
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で書いています。

Mat4
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 行列の演算は時間があるときに更新します。

PR