three.jsを使うのをやめWebGLで書き直すことにする。

公開:2017-05-25 06:13
更新:2020-02-15 04:37
カテゴリ:overpass apiとthree.jsで地図を3d表示,ゲーム製作

ここまではthree.jsを使って実装してきた。

実際のところthree.jsを使うと、WebGLや行列、ベクトルは意識しなくても実装できるようになっている。ただシェーダーを直にいじるとか、低レベルな実装をする場合はthree.jsの内部構造も意識して実装しなくてはならない。だが、WebGLに比べて、APIの内部構造のドキュメントは少なく、直にソースコードを覗き、解析を行う必要がある。でもその内部構造は当然のことながら、WebGLを使って実装されているから、WebGLの知識は必要なのだ。シェーダーを書くことはなくても、例えばBufferGeometryとGeometryの違いや利点は、WebGLのVertex Buffer Objectのことを知っていないと理解できない。私の結論としては「WebGLに関する知識は相当に持っておく必要がある」だ。

私の場合それに加えて、頂点周りの行列処理や、ライティングに関する知識も乏しいため、three.jsの内部について理解を深めることは困難を極める。

そういうわけでthree.jsを使うのをやめ、APIを使って再実装することにした。 対象とするAPIはWebGL 2.0としたい。といってもいきなりの実装はできないので、知識を深めながら進めることにする。教材としてはwgld.orgを使わせていただこうと思っている。ただここで書かれているサンプルコードは、若干古いので書き直しながら進めようと思っている。結局のところwgld.orgにあるサンプルコードをES6を意識しながら書き直していくことで、知識を深めようという作戦をとることにする。

そういうわけで、まず最初に実装したのが以下である。

https://bl.ocks.org/sfpgmr/47c33ee5ac5199e126fa6a6b2f974f80

三角形ポリゴンを一枚描くという、APIのチュートリアルではほぼ定番のコードである。wgld.orgではポリゴンに色を塗る(頂点色の指定) に相当する。

ちょっとヘンテコ改造になっているけどね。。例えばminMatrix.jsなど。

あと元コードはWebGL 1.0ベースであるが、それを WebGL 2.0ベースに書き直している。シェーダーコードはHTMLに書くのではなく、jsコード中にテンプレート文字列を使って書いている。es6の現代にあっては、シェーダーコードはjsファイル中に書くべきだと私は思っている。

実をいうとここまでのレベルのコードは、DirectX + C++でコードを書いていた時代でも書けていた。たしかDirectX 11あたりまでは追っかけていたと思う。シェーダーコードについてもGLSLではなくHLSLだが書いていた。
シューティングゲーム(1)Direct3Dプリミティブの描画

ただ過去記事を振り返ると、2D描画が目的なので、3Dへの取り組みは少し触れる程度にとどまっている。
3Dは2Dよりずっと難しいからだ。ただ当時にくらべてwgld.orgなどの良コンテンツもあるので、以前よりはハードルは下がっているので、じっくり時間をかけて勉強しようと思う。と言いつつも途中で飽きる可能性は高いけど。。

もう当初のシューティングゲームを作るという目的からは外れているが、趣味なのでよしとしよう。。

動作サンプル

新しいウィンドウで開く

ソースコード・リソース

/dev/webgl2/0001/bundle.js

/dev/webgl2/0001/filter.js

function filter(f){
  if(f.match(/\.js/i)){
    if(f.match(/bundle\.js/i)) return false;
    return true;
  }
  return false;
}
module.exports = filter;

/dev/webgl2/0001/gl2.js

import Mat4 from './mat4.js';

// WebGL 2.0 APIをラッピングするクラス
class GL2 {
  constructor({ width = window.innerWidth, height = window.innerHeight, elem = document.body }) {
    // canvasエレメントを作成する
    const canvas = document.createElement('canvas');
    // canvasの幅と高さを設定
    canvas.width = width;
    canvas.height = height;
    // インスタンスに幅と高さを保存
    this.width = width;
    this.height = height;
    // canvasにidを設定
    canvas.id = 'screen';
    // elemの子要素としてcanvasを追加
    elem.appendChild(canvas);
    // インスタンスにWebGL2RenderingContextを保存
    this.context = canvas.getContext('webgl2');
    // WebGL2RenderingContextが獲得できない場合は例外を送出する。
    if (!this.context) throw new Error('このブラウザはWebGL2Contextをサポートしていません。');

  }

  // shaderSrcとtypeでWebGLShaderを作り返す
  createShader(shaderSrc, type) {
    // WebGL2RenderingContextコンテキスト
    const context = this.context;
    // WebGLShaderの生成
    const shader = context.createShader(type);
    // WebGLShaderにシェーダーソースコードをセットする
    context.shaderSource(shader, shaderSrc);
    // シェーダーソースコードをコンパイルする
    context.compileShader(shader);
    // シェーダーのコンパイルが失敗した場合は例外を送出する
    if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) {
      throw new Error(context.getShaderInfoLog(shader));
    }
    return shader;
  }

  // 2つのWebGLShader(頂点シェーダ・フラグメントシェーダ)
  // からWebGLProgramを作り返す
  createProgram(vs, fs) {
    // WebGL2RenderingContextコンテキスト
    const context = this.context;
    // WebGLProgramの作成
    const program = context.createProgram();
    // 頂点シェーダをアタッチする
    context.attachShader(program, vs);
    // フラグメントシェーダをアタッチする
    context.attachShader(program, fs);
    // シェーダーをリンクする
    context.linkProgram(program);
    // リンクが失敗したら例外を送出する
    if (!context.getProgramParameter(program, context.LINK_STATUS)) {
      throw new Error(context.getProgramInfoLog(program));
    }
    return program;
  }

  // 現在のレンダリング・ステートで使用するWebGLProgramを指定する
  useProgram(program) {
    this.context.useProgram(program);
  }

  // WebGLBuffer(Vetex Buffer) を作成し、データをセットし返す
  createBuffer(data) {
    // WebGL2RenderingContextコンテキスト
    const context = this.context;
    // WebGLBufferを作成する
    const buffer = context.createBuffer();
    // WebGLBufferのバインド
    context.bindBuffer(context.ARRAY_BUFFER, buffer);
    // dataのセット
    context.bufferData(context.ARRAY_BUFFER, data, context.STATIC_DRAW);
    // バインドの解除
    context.bindBuffer(context.ARRAY_BUFFER, null);
    return buffer;
  }
  
  // WebGLBufferをlocationで指定したアトリビュートに割り当てる
  setAttribute(buffer,location,size,type = this.context.FLOAT,normalize =false,byteStride = 0,byteOffset = 0){
    // WebGL2RenderingContextコンテキスト
    const context = this.context;
    context.bindBuffer(context.ARRAY_BUFFER, buffer);
    // locationのattributeを有効にする
    context.enableVertexAttribArray(location);
    // locationのattributeにWebGLBufferを割り当てる
    context.vertexAttribPointer(location, size, type, normalize, byteStride, byteOffset);    
  }

  /// WebGLVertexArrayObjectを生成し、返す
  createVertexArray({dataArray,dataTypes,bufferTypes, locations, sizes, indexBuffer,indexBufferType}){
      // WebGL2RenderingContextコンテキスト
      const context = this.context;
      // WebGLVertexArrayObjectを生成する
      const vao = context.createVertexArray();
      // WebGLVertexArrayObjectをバインドする
      context.bindVertexArray(vao);
      // 配列データからWebGLBufferを作成し、attributeに割り当てる
      for(let i = 0,e = dataArray.length;i < e;++i){
          // WebGLBufferの生成
          let vbo = context.createBuffer();
          // WebGLBufferのバインド
          context.bindBuffer(context.ARRAY_BUFFER, vbo);
          // WebGLBufferにデータを設定する
          context.bufferData(context.ARRAY_BUFFER, dataArray[i], bufferTypes ? bufferTypes[i] : context.STATIC_DRAW);
          // locationのattributeを有効にする
          context.enableVertexAttribArray(locations[i]);
          // locationのattributeにWebGLBufferを割り当てる
          context.vertexAttribPointer(locations[i], sizes[i], dataTypes ? dataTypes[i] : context.FLOAT, false, 0, 0);
      }
      // インデックスバッファーの生成
      if(indexBuffer){
          // WebGLBufferの作成
          let ibo = context.createBuffer();
          // WebGLBufferのバインド
          context.bindBuffer(context.ELEMENT_ARRAY_BUFFER, ibo);
          // WebGLBufferにインデックスデータを引き渡す
          context.bufferData(context.ELEMENT_ARRAY_BUFFER, new UInt32Array(iboData), indexBufferType ? indexBufferType : context.STATIC_DRAW);
      }
      // WebGLVertexArrayObjectのバインドを解除
      context.bindVertexArray(null);
      return vao;
  }

  setMatrix({model,view,projection}){
    this.model = model ? model : this.model;
    this.view = view ? view : this.view;
    this.projection = projection ? projection : this.projection;
    this.mvp = this.model.multiply(this.projection.multiply(this.view)); 
  }

  resize(){
    this.width = window.innerWidth;
    this.height = window.innerHeight;
    this.setMatrix({projection:Mat4.createPerspective({fovy:90, aspect:this.width / this.height, near:0.1, far:100})});
  }

  // uniform変数に値を設定する
  setUniformValue(prg,attributeName,value)
  {
    // uniform変数の位置を取得する
    var uniLocation = this.context.getUniformLocation(prg, 'mvpMatrix');
    // uniform変数に値を設定する
    this.context.uniformMatrix4fv(uniLocation, false, value);
  }

}

export default GL2;

/dev/webgl2/0001/index.html

<!doctype html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>WebGL 2.0</title>
    <link rel="stylesheet" href="./dat.gui.css" />
    <script src="./dat.gui.min.js"></script>
    <script type="text/javascript" src="./bundle.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }

        #container {
            position: relative;
        }

        #info {
            left: 25vw;
            top: 50vh;
            width: 50vw;
            position: absolute;
            background: white;
            color: black;
            opacity: 0.5;
            margin: auto;
            text-align: center;
            font-size: 3vw;
        }

        canvas#screen {
            width: 100vw;
            height: 100vh;
            margin: 0;
            padding: 0;
        }

        .error {
            color:red;
            font-size: 16px;
        }
    </style>
</head>

<body>
    <div id="container">
        <div id="info"></div>
    </div>
</body>

</html>

/dev/webgl2/0001/main.js

"use strict"
// 4x4 行列クラス
import Mat4 from './mat4.js';
// WebGL2.0 ラッパー
import GL2 from './gl2.js';

function loadTexture(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.addEventListener('load', () => {
      resolve(img);
    });
    img.src = src;
  });
}

// 頂点シェーダ
const vertexShader =
`#version 300 es
layout (location = 0) in vec3 position;
layout (location = 1) in vec4 color;
uniform   mat4 mvpMatrix;
out vec4 oColor;
void main(void){
	gl_Position =  mvpMatrix * vec4(position,1.0);
  oColor = color;
}
`;

// フラグメントシェーダ
const fragmentShader =
`#version 300 es
precision highp float;
out vec4 color;
in vec4 oColor;
void main(void){
 	color = oColor;
}
`;

document.addEventListener('DOMContentLoaded', () => {

  // ローディング中の情報表示
  const info = document.getElementById('info');
  info.innerText = 'ロード中...しばらくお待ちください。';

  // GL2クラスの生成
  let gl2;
  try {
    gl2 = new GL2({ elem: document.getElementById('container') });
  } catch (e) {
    info = e.message;
    return;
  }

  // gl2オブジェクトからコンテキストを取り出す
  const ctx = gl2.context;
  // 画面クリアカラーの設定
  ctx.clearColor(0.0 /* Red */, 0.0 /* Green */, 0.0 /* Blue */, 1.0 /* Alpha */);
  // 深度バッファのクリア値の設定 
  ctx.clearDepth(1.0);
  // 画面クリア
  ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT);

  // 頂点シェーダの作成
  const vs = gl2.createShader(vertexShader, ctx.VERTEX_SHADER);
  // フラグメントシェーダの作成
  const fs = gl2.createShader(fragmentShader, ctx.FRAGMENT_SHADER);

  // プログラムの作成 
  const prg = gl2.createProgram(vs, fs);
  // 現在のレンダリングステートにプログラムを割り当てる
  gl2.useProgram(prg);

  // 頂点情報
  const vertices = new Float32Array([
    0.0, 1.0, 0.0,
    1.0, 0.0, 0.0,
    -1.0, 0.0, 0.0
  ]);

  // 色情報
  const colors = new Float32Array([
    0.0 /* Red */, 1.0 /* Green */, 0.0 /* Blue */, 1.0 /* Alpha */,
    0.0, 0.0, 1.0, 1.0,
    1.0, 0.0, 0.0, 1.0,
  ]);

  // Vertex Array Objectの生成
  const vao = gl2.createVertexArray({
    dataArray:[vertices,colors],
    locations:[0,1],
    sizes:[3,4]
  });

  // Vertex Array Objectのバインド
  ctx.bindVertexArray(vao);

  // 各種行列の生成と初期化
  gl2.setMatrix({
    model:new Mat4().identity(),
    view:Mat4.createLookAt({eye:[0.0, 1.0, 3.0], center:[0, 0, 0], up:[0, 1, 0]}),
    projection: Mat4.createPerspective({fovy:90, aspect:gl2.width / gl2.height, near:0.1, far:100})
  });

  gl2.setUniformValue(prg,'mvpMatrix',gl2.mvp.matrix);
  render();

  // windows resize時の処理
  window.addEventListener('resize', () => {
    if (gl2) {
      gl2.resize();
      gl2.setUniformValue(prg,'mvpMatrix',gl2.mvp.matrix);
      render();
    }
  });

  info.style.display = 'none';

  // レンダリング処理
  function render() {
    ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT);
    ctx.drawArrays(ctx.TRIANGLES, 0, 3);
    ctx.flush();
  }

  // レンダリングループ
  function renderLoop() {
    render();
    requestAnimationFrame(renderLoop); // ループ処理
  }

});



/dev/webgl2/0001/mat4.js

// ------------------------------------------------------------------------------------------------
// mat4.js
// doxasさんのminMatrix.jsをES6バージョンに改造したもの
// https://wgld.org/d/library/l001.html
// ------------------------------------------------------------------------------------------------

class Mat4 {
  constructor(m = new Float32Array(16)) {
    this.matrix = m;
  }

  identity() {
    const dest = this.matrix;
    dest[0] = 1; dest[1] = 0; dest[2] = 0; dest[3] = 0;
    dest[4] = 0; dest[5] = 1; dest[6] = 0; dest[7] = 0;
    dest[8] = 0; dest[9] = 0; dest[10] = 1; dest[11] = 0;
    dest[12] = 0; dest[13] = 0; dest[14] = 0; dest[15] = 1;
    return this;
  }

  multiply(matrix, destMatrix = new Mat4()) {
    const mat1 = this.matrix, mat2 = matrix.matrix;
    const dest = destMatrix.matrix;
    const a = mat1[0], b = mat1[1], c = mat1[2], d = mat1[3],
      e = mat1[4], f = mat1[5], g = mat1[6], h = mat1[7],
      i = mat1[8], j = mat1[9], k = mat1[10], l = mat1[11],
      m = mat1[12], n = mat1[13], o = mat1[14], p = mat1[15],

      A = mat2[0], B = mat2[1], C = mat2[2], D = mat2[3],
      E = mat2[4], F = mat2[5], G = mat2[6], H = mat2[7],
      I = mat2[8], J = mat2[9], K = mat2[10], L = mat2[11],
      M = mat2[12], N = mat2[13], O = mat2[14], P = mat2[15];

    dest[0] = A * a + B * e + C * i + D * m;
    dest[1] = A * b + B * f + C * j + D * n;
    dest[2] = A * c + B * g + C * k + D * o;
    dest[3] = A * d + B * h + C * l + D * p;
    dest[4] = E * a + F * e + G * i + H * m;
    dest[5] = E * b + F * f + G * j + H * n;
    dest[6] = E * c + F * g + G * k + H * o;
    dest[7] = E * d + F * h + G * l + H * p;
    dest[8] = I * a + J * e + K * i + L * m;
    dest[9] = I * b + J * f + K * j + L * n;
    dest[10] = I * c + J * g + K * k + L * o;
    dest[11] = I * d + J * h + K * l + L * p;
    dest[12] = M * a + N * e + O * i + P * m;
    dest[13] = M * b + N * f + O * j + P * n;
    dest[14] = M * c + N * g + O * k + P * o;
    dest[15] = M * d + N * h + O * l + P * p;
    return destMatrix;
  };

  scale(vec, destMatrix = new Mat4()) {
    const mat = this.matrix;
    const dest = destMatrix.matrix;

    dest[0] = mat[0] * vec[0];
    dest[1] = mat[1] * vec[0];
    dest[2] = mat[2] * vec[0];
    dest[3] = mat[3] * vec[0];
    dest[4] = mat[4] * vec[1];
    dest[5] = mat[5] * vec[1];
    dest[6] = mat[6] * vec[1];
    dest[7] = mat[7] * vec[1];
    dest[8] = mat[8] * vec[2];
    dest[9] = mat[9] * vec[2];
    dest[10] = mat[10] * vec[2];
    dest[11] = mat[11] * vec[2];
    dest[12] = mat[12];
    dest[13] = mat[13];
    dest[14] = mat[14];
    dest[15] = mat[15];
    return destMatrix;
  };

  translate(vec, destMatrix = new Mat4()) {
    const mat = this.matrix;
    const dest = destMatrix.matrix;

    dest[0] = mat[0]; dest[1] = mat[1]; dest[2] = mat[2]; dest[3] = mat[3];
    dest[4] = mat[4]; dest[5] = mat[5]; dest[6] = mat[6]; dest[7] = mat[7];
    dest[8] = mat[8]; dest[9] = mat[9]; dest[10] = mat[10]; dest[11] = mat[11];
    dest[12] = mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2] + mat[12];
    dest[13] = mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2] + mat[13];
    dest[14] = mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2] + mat[14];
    dest[15] = mat[3] * vec[0] + mat[7] * vec[1] + mat[11] * vec[2] + mat[15];
    return destMatrix;
  };

  rotate(angle, axis, destMatrix = new Mat4()) {
    const dest = destMatrix.matrix;
    const mat = this.matrix;
    var sq = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
    if (!sq) { return null; }
    var a = axis[0], b = axis[1], c = axis[2];
    if (sq != 1) { sq = 1 / sq; a *= sq; b *= sq; c *= sq; }
    var d = Math.sin(angle), e = Math.cos(angle), f = 1 - e,
      g = mat[0], h = mat[1], i = mat[2], j = mat[3],
      k = mat[4], l = mat[5], m = mat[6], n = mat[7],
      o = mat[8], p = mat[9], q = mat[10], r = mat[11],
      s = a * a * f + e,
      t = b * a * f + c * d,
      u = c * a * f - b * d,
      v = a * b * f - c * d,
      w = b * b * f + e,
      x = c * b * f + a * d,
      y = a * c * f + b * d,
      z = b * c * f - a * d,
      A = c * c * f + e;
    if (angle) {
      if (mat != dest) {
        dest[12] = mat[12]; dest[13] = mat[13];
        dest[14] = mat[14]; dest[15] = mat[15];
      }
    } else {
      dest = mat;
    }
    dest[0] = g * s + k * t + o * u;
    dest[1] = h * s + l * t + p * u;
    dest[2] = i * s + m * t + q * u;
    dest[3] = j * s + n * t + r * u;
    dest[4] = g * v + k * w + o * x;
    dest[5] = h * v + l * w + p * x;
    dest[6] = i * v + m * w + q * x;
    dest[7] = j * v + n * w + r * x;
    dest[8] = g * y + k * z + o * A;
    dest[9] = h * y + l * z + p * A;
    dest[10] = i * y + m * z + q * A;
    dest[11] = j * y + n * z + r * A;
    return destMatrix;
  };

  static createLookAt({eye, center, up, destMatrix = new Mat4()}) {
    const dest = destMatrix.matrix;
    const eyeX = eye[0], eyeY = eye[1], eyeZ = eye[2],
      upX = up[0], upY = up[1], upZ = up[2],
      centerX = center[0], centerY = center[1], centerZ = center[2];
    if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ) 
    { 
      return destMatrix.identity(); 
    }

    let x0, x1, x2, y0, y1, y2, z0, z1, z2, l;
    z0 = eyeX - center[0];
    z1 = eyeY - center[1];
    z2 = eyeZ - center[2];
    l = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
    z0 *= l; z1 *= l; z2 *= l;
    x0 = upY * z2 - upZ * z1;
    x1 = upZ * z0 - upX * z2;
    x2 = upX * z1 - upY * z0;
    l = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
    if (!l) {
      x0 = 0; x1 = 0; x2 = 0;
    } else {
      l = 1 / l;
      x0 *= l; x1 *= l; x2 *= l;
    }
    y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0;
    l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
    if (!l) {
      y0 = 0; y1 = 0; y2 = 0;
    } else {
      l = 1 / l;
      y0 *= l; y1 *= l; y2 *= l;
    }
    dest[0] = x0; dest[1] = y0; dest[2] = z0; dest[3] = 0;
    dest[4] = x1; dest[5] = y1; dest[6] = z1; dest[7] = 0;
    dest[8] = x2; dest[9] = y2; dest[10] = z2; dest[11] = 0;
    dest[12] = -(x0 * eyeX + x1 * eyeY + x2 * eyeZ);
    dest[13] = -(y0 * eyeX + y1 * eyeY + y2 * eyeZ);
    dest[14] = -(z0 * eyeX + z1 * eyeY + z2 * eyeZ);
    dest[15] = 1;
    return destMatrix;
  };

  static createPerspective({fovy, aspect, near, far, destMatrix = new Mat4()}) {
    const dest = destMatrix.matrix;
    const t = near * Math.tan(fovy * Math.PI / 360);
    const r = t * aspect;
    const a = r * 2, b = t * 2, c = far - near;
    dest[0] = near * 2 / a;
    dest[1] = 0;
    dest[2] = 0;
    dest[3] = 0;
    dest[4] = 0;
    dest[5] = near * 2 / b;
    dest[6] = 0;
    dest[7] = 0;
    dest[8] = 0;
    dest[9] = 0;
    dest[10] = -(far + near) / c;
    dest[11] = -1;
    dest[12] = 0;
    dest[13] = 0;
    dest[14] = -(far * near * 2) / c;
    dest[15] = 0;
    return destMatrix;
  };

  transpose(destMatrix = new Mat4()) {
    const mat = this.matrix;
    const dest = destMatrix.matrix;
    dest[0] = mat[0]; dest[1] = mat[4];
    dest[2] = mat[8]; dest[3] = mat[12];
    dest[4] = mat[1]; dest[5] = mat[5];
    dest[6] = mat[9]; dest[7] = mat[13];
    dest[8] = mat[2]; dest[9] = mat[6];
    dest[10] = mat[10]; dest[11] = mat[14];
    dest[12] = mat[3]; dest[13] = mat[7];
    dest[14] = mat[11]; dest[15] = mat[15];
    return destMatrix;
  };

  inverse(destMatrix = new Mat4()) {
    const dest = destMatrix.matrix;
    const mat = this.matrix;
    const a = mat[0], b = mat[1], c = mat[2], d = mat[3],
      e = mat[4], f = mat[5], g = mat[6], h = mat[7],
      i = mat[8], j = mat[9], k = mat[10], l = mat[11],
      m = mat[12], n = mat[13], o = mat[14], p = mat[15],
      q = a * f - b * e, r = a * g - c * e,
      s = a * h - d * e, t = b * g - c * f,
      u = b * h - d * f, v = c * h - d * g,
      w = i * n - j * m, x = i * o - k * m,
      y = i * p - l * m, z = j * o - k * n,
      A = j * p - l * n, B = k * p - l * o,
      ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w);
    dest[0] = (f * B - g * A + h * z) * ivd;
    dest[1] = (-b * B + c * A - d * z) * ivd;
    dest[2] = (n * v - o * u + p * t) * ivd;
    dest[3] = (-j * v + k * u - l * t) * ivd;
    dest[4] = (-e * B + g * y - h * x) * ivd;
    dest[5] = (a * B - c * y + d * x) * ivd;
    dest[6] = (-m * v + o * s - p * r) * ivd;
    dest[7] = (i * v - k * s + l * r) * ivd;
    dest[8] = (e * A - f * y + h * w) * ivd;
    dest[9] = (-a * A + b * y - d * w) * ivd;
    dest[10] = (m * u - n * s + p * q) * ivd;
    dest[11] = (-i * u + j * s - l * q) * ivd;
    dest[12] = (-e * z + f * x - g * w) * ivd;
    dest[13] = (a * z - b * x + c * w) * ivd;
    dest[14] = (-m * t + n * r - o * q) * ivd;
    dest[15] = (i * t - j * r + k * q) * ivd;
    return destMatrix;
  };
}

export default Mat4;

/dev/webgl2/0001/package.json

/dev/webgl2/0001/readme.md

## WebGL2の学習

wgld.orgのソースコードをWebGL2ベースに書き換えてみる
まずは三角形を描画してみる。

/dev/webgl2/0001/rollup.config.js

// rollup.config.js
export default {
  entry: 'main.js',
  format:'iife',
  dest: 'bundle.js' // equivalent to --output
};

/dev/webgl2/0001/thumbnail.png