WebGL 教科書の写経、その2

複数モデルのレンダリング

https://wgld.org/d/webgl/w016.html
教科書にあるマトリクスのライブラリなのだが、使い方がオブジェクト指向ではなく、たんなるマトリクス関数の集合体だけの使い方のように思えてきた。
そのライブラリは少しずつ自分用のライブラリに移植している。

<!DOCTYPE html>
<meta charset="utf-8">
<title>WebGL</title>

<style>
</style>

<body>
  <canvas width="1024" height="512"</canvas>
  

<!-- ※頂点シェーダ -->
<script id="vshader" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
uniform   mat4 mvpMatrix;
varying   vec4 vColor;

void main (void){
    vColor = color;
    gl_Position = mvpMatrix * vec4(position, 1.0);
}

</script>

<!-- ※フラグメントシェーダ -->
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;

varying vec4 vColor;

void main(void){
    gl_FragColor = vColor;
}


</script>


<script>
{
  class Matrix4 {

    constructor (ary = [1,0,0,0,  0,1,0,0,  0,0,1,0,  0,0,0,1]) {
      let
        F = Math.fround,
        M = new Float32Array (16);
      
      for (let i = 0; i < 16; i++)
        M[i] = F (ary[i] || 0);

      this.M = M;
    }


    multiply (mat1) {
      let
        [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = this.M,
        [A, B, C, D, E, F, G, H, I, J, K, L, M, N ,O, P] = mat1.M;

      this.M = new Float32Array ([
        A * a + B * e + C * i + D * m,
        A * b + B * f + C * j + D * n,
        A * c + B * g + C * k + D * o,
        A * d + B * h + C * l + D * p,
        E * a + F * e + G * i + H * m,
        E * b + F * f + G * j + H * n,
        E * c + F * g + G * k + H * o,
        E * d + F * h + G * l + H * p,
        I * a + J * e + K * i + L * m,
        I * b + J * f + K * j + L * n,
        I * c + J * g + K * k + L * o,
        I * d + J * h + K * l + L * p,
        M * a + N * e + O * i + P * m,
        M * b + N * f + O * j + P * n,
        M * c + N * g + O * k + P * o,
        M * d + N * h + O * l + P * p
      ]);
      
      return this;
    }


    scale ([x,y,z]) {
      let m = this.M;
      this.M = new Float32Array ([
        m[0] * x,  m[1] * x,  m[2] * x,  m[3] * x,
        m[4] * y,  m[5] * y,  m[6] * y,  m[7] * y,
        m[8] * z,  m[9] * z,  m[10]* z,  m[11]* z,
        m[12],     m[13],     m[14],     m[15]
      ]);
      
      return this;
    }


    translate ([x, y, z]) {
      let
        [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p] = this.M;
        
      this.M = new Float32Array ([
        a, b, c, d,
        e, f, g, h,
        i, j, k, l,
        a * x + e * y + i * z + m,
        b * x + f * y + j * z + n,
        c * x + g * y + k * z + o,
        d * x + h * y + l * z + p
      ]);

      return this;
    }


    rotate (mat, angle, [a, b, c]) {
      let
        sq = Math.sqrt(a * a + b * b + c * c);

      if (! sq)
        return null;
      if (sq != 1) {
        sq = 1 / sq; a *= sq; b *= sq; c *= sq; //?angle
      }

      let
        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;
      }
      return new Matrix4([
        g * s + k * t + o * u,
        h * s + l * t + p * u,
        i * s + m * t + q * u,
        j * s + n * t + r * u,
        g * v + k * w + o * x,
        h * v + l * w + p * x,
        i * v + m * w + q * x,
        j * v + n * w + r * x,
        g * y + k * z + o * A,
        h * y + l * z + p * A,
        i * y + m * z + q * A,
        j * y + n * z + r * A
      ]);
    }
    
    
    copy () {
      return new Matrix4 (this.M);
    }


    static lookAt (eye, center, up) {
      let
        [eyeX, eyeY, eyeZ] = eye,
        [upX,  upY,  upZ]  = up,
        [centerX, centerY, centerZ] = center;

      if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ)
        return (new this).identity ();

      let
        sqrt = Math.sqrt,
        z0 = eyeX - centerX,
        z1 = eyeY - centerY,
        z2 = eyeZ - centerZ,
        l = 1 / sqrt (z0 * z0 + z1 * z1 + z2 * z2);

      z0 *= l; z1 *= l; z2 *= l;

      let
        x0 = upY * z2 - upZ * z1,
        x1 = upZ * z0 - upX * z2,
        x2 = upX * z1 - upY * z0;

      l = 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;
      }

      let
        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;
      }

      return new this ([
        x0, y0, z0, 0,
        x1, y1, z1, 0,
        x2, y2, z2, 0,
        -(x0 * eyeX + x1 * eyeY + x2 * eyeZ),
        -(y0 * eyeX + y1 * eyeY + y2 * eyeZ),
        -(z0 * eyeX + z1 * eyeY + z2 * eyeZ),
        1
      ]);
    }


    static perspective (fovy, aspect, near, far) {
      let
        t = near * Math.tan (fovy * Math.PI / 360),
        r = t * aspect,
        a = r * 2, b = t * 2, c = far - near;

      return new this ([
        near * 2 / a, 0, 0, 0,
        0, near * 2 / b, 0, 0,
        0, 0, -(far + near) / c, -1,
        0, 0, -(far * near * 2) / c, 0
      ]);
    }

  }
  

  this.Matrix4 = Matrix4;
}

function matIV(){
	this.create = function(){
		return new Float32Array(16);
	};


	this.transpose = function(mat, dest){
		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 dest;
	};

	this.inverse = function(mat, dest){
		var 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 dest;
	};
}


{
  class Shader {
    constructor (canvas, vertex, fragment) {
      this.canvas = canvas;
      this.gl = canvas.getContext('webgl');
      this.v_shader = create_shader.call (this, vertex);
      this.f_shader = create_shader.call (this, fragment);
      this.prg = create_program.call (this);
      this.vbo = {};

      
      this.clear ();
    }
    
    
    clear (r = 0, g = 0, b = 0, a = 1) {
      let gl = this.gl;
      gl.clearColor (r, g, b, a);
      gl.clearDepth (1.0);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

      return this;
    }
    
    
    setVBO (data = [], type = 'position') {
      let
        gl = this.gl,
        attLocation = gl.getAttribLocation (this.prg, type),
        attStride = {position: 3, color: 4}[type],
        vbo = create_vbo.call (this, data);
     
      gl.bindBuffer (gl.ARRAY_BUFFER, vbo);
      gl.enableVertexAttribArray (attLocation);
      gl.vertexAttribPointer (attLocation, attStride, gl.FLOAT, false, 0, 0);
      this.vbo[type] = vbo;

      return this;
    }
    
    
    draw (mvpMatrix, flash = true) {
      let
        {gl, prg} = this,
        uniLocation = gl.getUniformLocation (prg, 'mvpMatrix');
      gl.uniformMatrix4fv (uniLocation, false, mvpMatrix);
      gl.drawArrays (gl.TRIANGLES, 0, 3);
      
      if (flash)
        gl.flush ();
      
      return this;
    }
    
    
  }
  

  const
    SHADER_TYPE = {
      'x-shader/x-vertex': 'VERTEX_SHADER',
      'x-shader/x-fragment': 'FRAGMENT_SHADER'},

    create_shader =
      function (target = null) {
        if (target) {
          let
            gl = this.gl,
            {text, type} = target,
            shader = gl.createShader (gl[SHADER_TYPE[type]]);

          gl.shaderSource (shader, text);
          gl.compileShader (shader);
          if (! gl.getShaderParameter (shader, gl.COMPILE_STATUS))
            throw new Error (['シェーダーのコンパイルエラーがありました',
              gl.getShaderInfoLog (shader)].join ('\n'))

          return shader;
        }
        throw new Error ('要素がありません');
      },


      // プログラムオブジェクトを生成しシェーダをリンクする関数
    create_program =
      function () {
        let
          gl = this.gl,
          program = gl.createProgram();

        gl.attachShader (program, this.v_shader);
        gl.attachShader (program, this.f_shader);
        gl.linkProgram (program);

        if (! gl.getProgramParameter(program, gl.LINK_STATUS))
          throw new Error (['プログラムのオブジェクトの生成に失敗しました',
            gl.getProgramInfoLog (program)].join ('\n'));

        gl.useProgram (program);
        return program;
      },


	  create_vbo =
	    function (data) {
	      let
	        gl = this.gl,
	        vbo = gl.createBuffer ();

        gl.bindBuffer (gl.ARRAY_BUFFER, vbo);
        gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (data), gl.STATIC_DRAW);
        gl.bindBuffer (gl.ARRAY_BUFFER, null);

        return vbo;
      };
      

  //_______
  
  this.Shader = Shader;
}


{//実行スクリプト
	let
	  doc = document,
	  canvas = doc.querySelector ('canvas'),
	  shader = new Shader (
	    canvas,
	    doc.querySelector ('script[type="x-shader/x-vertex"]'),
	    doc.querySelector ('script[type="x-shader/x-fragment"]'),
	  ),
    // モデル(頂点)データ
    vertex_position = [
      0.0, 1.0, 0.0,
      1.0, 0.0, 0.0,
      -1.0, 0.0, 0.0
    ],
    vertex_color = [
      1.0, 0.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0
    ],

    // 各種行列の生成と初期化
    matrix = Matrix4.perspective (90, canvas.width / canvas.height, 0.1, 100)
             .multiply (Matrix4.lookAt ([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0]))
             .multiply (new Matrix4);
    

  shader.setVBO (vertex_position, 'position');
  shader.setVBO (vertex_color, 'color');

  shader.draw (matrix.copy ().translate ([1.5,0,0]).M, false);
  shader.draw (matrix.translate ([-1.5,0,0]).M);
}

</script>

再帰処理と移動・回転・拡大縮小

https://wgld.org/d/webgl/w017.html
マトリクスのライブラリの移植終了。
これまでの勉強がかなり役立っている。何をしているのかスルスルわかる。
移植は脳内変換でスムーズに移行中、タイマーイベントから、アニメーションに変更。

<!DOCTYPE html>
<meta charset="utf-8">
<title>WebGL</title>

<style>
</style>

<body>
  <canvas width="1024" height="512"</canvas>
  

<!-- ※頂点シェーダ -->
<script id="vshader" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
uniform   mat4 mvpMatrix;
varying   vec4 vColor;

void main (void){
    vColor = color;
    gl_Position = mvpMatrix * vec4(position, 1.0);
}

</script>

<!-- ※フラグメントシェーダ -->
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;

varying vec4 vColor;

void main(void){
    gl_FragColor = vColor;
}


</script>


<script>
{
  class Matrix4 {

    constructor (ary = [1,0,0,0,  0,1,0,0,  0,0,1,0,  0,0,0,1]) {
      let
        F = Math.fround,
        M = new Float32Array (16);
      
      for (let i = 0; i < 16; i++)
        M[i] = F (ary[i] || 0);

      this.M = M;
    }


    multiply (mat1) {
      let
        [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = this.M,
        [A, B, C, D, E, F, G, H, I, J, K, L, M, N ,O, P] = mat1.M;

      this.M = new Float32Array ([
        A * a + B * e + C * i + D * m,
        A * b + B * f + C * j + D * n,
        A * c + B * g + C * k + D * o,
        A * d + B * h + C * l + D * p,
        E * a + F * e + G * i + H * m,
        E * b + F * f + G * j + H * n,
        E * c + F * g + G * k + H * o,
        E * d + F * h + G * l + H * p,
        I * a + J * e + K * i + L * m,
        I * b + J * f + K * j + L * n,
        I * c + J * g + K * k + L * o,
        I * d + J * h + K * l + L * p,
        M * a + N * e + O * i + P * m,
        M * b + N * f + O * j + P * n,
        M * c + N * g + O * k + P * o,
        M * d + N * h + O * l + P * p
      ]);
      
      return this;
    }


    scale ([x,y,z]) {
      let [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M;
      this.M = new Float32Array ([
        a * x, b * x, c * x, d * x,
        e * y, f * y, g * y, h * y,
        i * z, j * z, k * z, l * z,
        m, n, o, p
      ]);
      
      return this;
    }


    translate ([x, y, z]) {
      let
        [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p] = this.M;
        
      this.M = new Float32Array ([
        a, b, c, d,
        e, f, g, h,
        i, j, k, l,
        a * x + e * y + i * z + m,
        b * x + f * y + j * z + n,
        c * x + g * y + k * z + o,
        d * x + h * y + l * z + p
      ]);

      return this;
    }


    rotate (angle, [a, b, c]) {//a,b,cは多分ベクトルだろう。後で四元数で回せるから
      let
        sq = Math.sqrt(a * a + b * b + c * c);

      if (! sq)
        throw new Error ('ベクトルが値が0です');
      if (sq != 1)//一応これが高速化?
        sq = 1 / sq; a *= sq; b *= sq; c *= sq; //単位ベクトルにしている

      let
        d = Math.sin (angle),
        e = Math.cos (angle),
        f = 1 - e,
        [g,h,i,j,k,l,m,n,o,p,q,r, B,C,D,E] = this.M,
        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;

      this.M = new Float32Array ([
        g * s + k * t + o * u,
        h * s + l * t + p * u,
        i * s + m * t + q * u,
        j * s + n * t + r * u,
        g * v + k * w + o * x,
        h * v + l * w + p * x,
        i * v + m * w + q * x,
        j * v + n * w + r * x,
        g * y + k * z + o * A,
        h * y + l * z + p * A,
        i * y + m * z + q * A,
        j * y + n * z + r * A,
        B, C, D,E
      ]);
      
      return this;
    }
    

	  inverse (mat, dest) {
	    let
	      [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M,
        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);

      this.M = new Float32Array ([
        ( f * B - g * A + h * z) * ivd,
        (-b * B + c * A - d * z) * ivd,
        ( n * v - o * u + p * t) * ivd,
        (-j * v + k * u - l * t) * ivd,
        (-e * B + g * y - h * x) * ivd,
        ( a * B - c * y + d * x) * ivd,
        (-m * v + o * s - p * r) * ivd,
        ( i * v - k * s + l * r) * ivd,
        ( e * A - f * y + h * w) * ivd,
        (-a * A + b * y - d * w) * ivd,
        ( m * u - n * s + p * q) * ivd,
        (-i * u + j * s - l * q) * ivd,
        (-e * z + f * x - g * w) * ivd,
        ( a * z - b * x + c * w) * ivd,
        (-m * t + n * r - o * q) * ivd,
        ( i * t - j * r + k * q) * ivd
      ]);
      
      return this;
	  };
    
    
    
    copy () {
      return new Matrix4 (this.M);
    }
    
    transpose () {
      return new Matrix4 (this.M);    
    }


    static lookAt (eye, center, up) {
      let
        [eyeX, eyeY, eyeZ] = eye,
        [upX,  upY,  upZ]  = up,
        [centerX, centerY, centerZ] = center;

      if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ)
        return (new this).identity ();

      let
        sqrt = Math.sqrt,
        z0 = eyeX - centerX,
        z1 = eyeY - centerY,
        z2 = eyeZ - centerZ,
        l = 1 / sqrt (z0 * z0 + z1 * z1 + z2 * z2);

      z0 *= l; z1 *= l; z2 *= l;

      let
        x0 = upY * z2 - upZ * z1,
        x1 = upZ * z0 - upX * z2,
        x2 = upX * z1 - upY * z0;

      l = 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;
      }

      let
        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;
      }

      return new this ([
        x0, y0, z0, 0,
        x1, y1, z1, 0,
        x2, y2, z2, 0,
        -(x0 * eyeX + x1 * eyeY + x2 * eyeZ),
        -(y0 * eyeX + y1 * eyeY + y2 * eyeZ),
        -(z0 * eyeX + z1 * eyeY + z2 * eyeZ),
        1
      ]);
    }


    static perspective (fovy, aspect, near, far) {
      let
        t = near * Math.tan (fovy * Math.PI / 360),
        r = t * aspect,
        a = r * 2, b = t * 2, c = far - near;

      return new this ([
        near * 2 / a, 0, 0, 0,
        0, near * 2 / b, 0, 0,
        0, 0, -(far + near) / c, -1,
        0, 0, -(far * near * 2) / c, 0
      ]);
    }

  }
  

  this.Matrix4 = Matrix4;
}


{
  class Shader {
    constructor (canvas, vertex, fragment) {
      this.canvas = canvas;
      this.gl = canvas.getContext('webgl');
      this.v_shader = create_shader.call (this, vertex);
      this.f_shader = create_shader.call (this, fragment);
      this.prg = create_program.call (this);
      this.vbo = {};

      
      this.clear ();
    }
    
    
    clear (r = 0, g = 0, b = 0, a = 1) {
      let gl = this.gl;
      gl.clearColor (r, g, b, a);
      gl.clearDepth (1.0);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

      return this;
    }
    
    
    setVBO (data = [], type = 'position') {
      let
        gl = this.gl,
        attLocation = gl.getAttribLocation (this.prg, type),
        attStride = {position: 3, color: 4}[type],
        vbo = create_vbo.call (this, data);
     
      gl.bindBuffer (gl.ARRAY_BUFFER, vbo);
      gl.enableVertexAttribArray (attLocation);
      gl.vertexAttribPointer (attLocation, attStride, gl.FLOAT, false, 0, 0);
      this.vbo[type] = vbo;

      return this;
    }
    
    
    draw (mvpMatrix, flash = true) {
      let
        {gl, prg} = this,
        uniLocation = gl.getUniformLocation (prg, 'mvpMatrix');
      gl.uniformMatrix4fv (uniLocation, false, mvpMatrix);
      gl.drawArrays (gl.TRIANGLES, 0, 3);
      
      if (flash)
        gl.flush ();
      
      return this;
    }
    
    
  }
  

  const
    SHADER_TYPE = {
      'x-shader/x-vertex': 'VERTEX_SHADER',
      'x-shader/x-fragment': 'FRAGMENT_SHADER'},

    create_shader =
      function (target = null) {
        if (target) {
          let
            gl = this.gl,
            {text, type} = target,
            shader = gl.createShader (gl[SHADER_TYPE[type]]);

          gl.shaderSource (shader, text);
          gl.compileShader (shader);
          if (! gl.getShaderParameter (shader, gl.COMPILE_STATUS))
            throw new Error (['シェーダーのコンパイルエラーがありました',
              gl.getShaderInfoLog (shader)].join ('\n'))

          return shader;
        }
        throw new Error ('要素がありません');
      },


      // プログラムオブジェクトを生成しシェーダをリンクする関数
    create_program =
      function () {
        let
          gl = this.gl,
          program = gl.createProgram();

        gl.attachShader (program, this.v_shader);
        gl.attachShader (program, this.f_shader);
        gl.linkProgram (program);

        if (! gl.getProgramParameter(program, gl.LINK_STATUS))
          throw new Error (['プログラムのオブジェクトの生成に失敗しました',
            gl.getProgramInfoLog (program)].join ('\n'));

        gl.useProgram (program);
        return program;
      },


	  create_vbo =
	    function (data) {
	      let
	        gl = this.gl,
	        vbo = gl.createBuffer ();

        gl.bindBuffer (gl.ARRAY_BUFFER, vbo);
        gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (data), gl.STATIC_DRAW);
        gl.bindBuffer (gl.ARRAY_BUFFER, null);

        return vbo;
      };
      

  //_______
  
  this.Shader = Shader;
}


{//実行スクリプト
	let
	  doc = document,
	  canvas = doc.querySelector ('canvas'),
	  shader = new Shader (
	    canvas,
	    doc.querySelector ('script[type="x-shader/x-vertex"]'),
	    doc.querySelector ('script[type="x-shader/x-fragment"]'),
	  ),
    // モデル(頂点)データ
    vertex_position = [
      0.0, 1.0, 0.0,
      1.0, 0.0, 0.0,
      -1.0, 0.0, 0.0
    ],
    vertex_color = [
      1.0, 0.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0
    ],

    // 各種行列の生成と初期化
    matrix = Matrix4.perspective (90, canvas.width / canvas.height, 0.1, 100)
             .multiply (Matrix4.lookAt ([0.0, 1.0, 3.0], [0, 0, 0], [0, 1, 0]))
             .multiply (new Matrix4);
    


  shader.setVBO (vertex_position, 'position');
  shader.setVBO (vertex_color, 'color');


  //_____
  
  let
    start = + new Date,
    deg = Math.PI / 180,
    {sin, cos} = Math;
    
  const loop = function (timeStamp) {
    let
      count = (timeStamp - start) / 30,
      rad = (count % 360) * deg;
    
    
    shader.clear ();
    
    // model-1 円の軌道を描き移動
    let
      x = cos (rad),
      y = sin (rad),
      m1 = (new Matrix4).translate ([x, y + 1, 0]),
      m1_Matrix = matrix.copy ().multiply (m1);
    
    // model-2 Y軸を中心に回転
    let
      m2 = (new Matrix4).translate ([1,-1,0]).rotate (rad, [0,1,0]),
      m2_Matrix = matrix.copy ().multiply (m2);
    
    // model-3 拡大縮小
    let
      s = sin (rad) + 1,
      m3 = (new Matrix4).translate ([-1,-1,0]).scale ([s,s,0]),
      m3_Matrix = matrix.copy ().multiply (m3);


    shader.draw (m1_Matrix.M);
    shader.draw (m2_Matrix.M);
    shader.draw (m3_Matrix.M);
  
    requestAnimationFrame (loop);
  }
  
  loop ();
}

</script>

インデックスバッファによる描画

https://wgld.org/d/webgl/w018.html

まだまだ余裕。

<!DOCTYPE html>
<meta charset="utf-8">
<title>WebGL</title>

<style>
</style>

<body>
  <canvas width="1024" height="512"</canvas>
  

<!-- ※頂点シェーダ -->
<script id="vshader" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec4 color;
uniform   mat4 mvpMatrix;
varying   vec4 vColor;

void main (void){
    vColor = color;
    gl_Position = mvpMatrix * vec4(position, 1.0);
}

</script>

<!-- ※フラグメントシェーダ -->
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;

varying vec4 vColor;

void main(void){
    gl_FragColor = vColor;
}


</script>


<script>
{
  class Matrix4 {

    constructor (ary = [1,0,0,0,  0,1,0,0,  0,0,1,0,  0,0,0,1]) {
      let
        F = Math.fround,
        M = new Float32Array (16);
      
      for (let i = 0; i < 16; i++)
        M[i] = F (ary[i] || 0);

      this.M = M;
    }


    multiply (mat1) {
      let
        [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = this.M,
        [A, B, C, D, E, F, G, H, I, J, K, L, M, N ,O, P] = mat1.M;

      this.M = new Float32Array ([
        A * a + B * e + C * i + D * m,
        A * b + B * f + C * j + D * n,
        A * c + B * g + C * k + D * o,
        A * d + B * h + C * l + D * p,
        E * a + F * e + G * i + H * m,
        E * b + F * f + G * j + H * n,
        E * c + F * g + G * k + H * o,
        E * d + F * h + G * l + H * p,
        I * a + J * e + K * i + L * m,
        I * b + J * f + K * j + L * n,
        I * c + J * g + K * k + L * o,
        I * d + J * h + K * l + L * p,
        M * a + N * e + O * i + P * m,
        M * b + N * f + O * j + P * n,
        M * c + N * g + O * k + P * o,
        M * d + N * h + O * l + P * p
      ]);
      
      return this;
    }


    scale ([x,y,z]) {
      let [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M;
      this.M = new Float32Array ([
        a * x, b * x, c * x, d * x,
        e * y, f * y, g * y, h * y,
        i * z, j * z, k * z, l * z,
        m, n, o, p
      ]);
      
      return this;
    }


    translate ([x, y, z]) {
      let
        [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p] = this.M;
        
      this.M = new Float32Array ([
        a, b, c, d,
        e, f, g, h,
        i, j, k, l,
        a * x + e * y + i * z + m,
        b * x + f * y + j * z + n,
        c * x + g * y + k * z + o,
        d * x + h * y + l * z + p
      ]);

      return this;
    }


    rotate (angle, [a, b, c]) {//a,b,cは多分ベクトルだろう。後で四元数で回せるから
      let
        sq = Math.sqrt(a * a + b * b + c * c);

      if (! sq)
        throw new Error ('ベクトルが値が0です');
      if (sq != 1)//一応これが高速化?
        sq = 1 / sq; a *= sq; b *= sq; c *= sq; //単位ベクトルにしている

      let
        d = Math.sin (angle),
        e = Math.cos (angle),
        f = 1 - e,
        [g,h,i,j,k,l,m,n,o,p,q,r, B,C,D,E] = this.M,
        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;

      this.M = new Float32Array ([
        g * s + k * t + o * u,
        h * s + l * t + p * u,
        i * s + m * t + q * u,
        j * s + n * t + r * u,
        g * v + k * w + o * x,
        h * v + l * w + p * x,
        i * v + m * w + q * x,
        j * v + n * w + r * x,
        g * y + k * z + o * A,
        h * y + l * z + p * A,
        i * y + m * z + q * A,
        j * y + n * z + r * A,
        B, C, D,E
      ]);
      
      return this;
    }
    

	  inverse (mat, dest) {
	    let
	      [a,b,c,d, e,f,g,h, i,j,k,l, m,n,o,p] = this.M,
        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);

      this.M = new Float32Array ([
        ( f * B - g * A + h * z) * ivd,
        (-b * B + c * A - d * z) * ivd,
        ( n * v - o * u + p * t) * ivd,
        (-j * v + k * u - l * t) * ivd,
        (-e * B + g * y - h * x) * ivd,
        ( a * B - c * y + d * x) * ivd,
        (-m * v + o * s - p * r) * ivd,
        ( i * v - k * s + l * r) * ivd,
        ( e * A - f * y + h * w) * ivd,
        (-a * A + b * y - d * w) * ivd,
        ( m * u - n * s + p * q) * ivd,
        (-i * u + j * s - l * q) * ivd,
        (-e * z + f * x - g * w) * ivd,
        ( a * z - b * x + c * w) * ivd,
        (-m * t + n * r - o * q) * ivd,
        ( i * t - j * r + k * q) * ivd
      ]);
      
      return this;
	  };
    
    
    
    copy () {
      return new Matrix4 (this.M);
    }
    
    transpose () {
      return new Matrix4 (this.M);    
    }


    static lookAt (eye, center, up) {
      let
        [eyeX, eyeY, eyeZ] = eye,
        [upX,  upY,  upZ]  = up,
        [centerX, centerY, centerZ] = center;

      if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ)
        return (new this).identity ();

      let
        sqrt = Math.sqrt,
        z0 = eyeX - centerX,
        z1 = eyeY - centerY,
        z2 = eyeZ - centerZ,
        l = 1 / sqrt (z0 * z0 + z1 * z1 + z2 * z2);

      z0 *= l; z1 *= l; z2 *= l;

      let
        x0 = upY * z2 - upZ * z1,
        x1 = upZ * z0 - upX * z2,
        x2 = upX * z1 - upY * z0;

      l = 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;
      }

      let
        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;
      }

      return new this ([
        x0, y0, z0, 0,
        x1, y1, z1, 0,
        x2, y2, z2, 0,
        -(x0 * eyeX + x1 * eyeY + x2 * eyeZ),
        -(y0 * eyeX + y1 * eyeY + y2 * eyeZ),
        -(z0 * eyeX + z1 * eyeY + z2 * eyeZ),
        1
      ]);
    }


    static perspective (fovy, aspect, near, far) {
      let
        t = near * Math.tan (fovy * Math.PI / 360),
        r = t * aspect,
        a = r * 2, b = t * 2, c = far - near;

      return new this ([
        near * 2 / a, 0, 0, 0,
        0, near * 2 / b, 0, 0,
        0, 0, -(far + near) / c, -1,
        0, 0, -(far * near * 2) / c, 0
      ]);
    }

  }
  

  this.Matrix4 = Matrix4;
}


{
  class Shader {
    constructor (canvas, vertex, fragment) {
      this.canvas = canvas;
      this.gl = canvas.getContext('webgl');
      this.v_shader = create_shader.call (this, vertex);
      this.f_shader = create_shader.call (this, fragment);
      this.prg = create_program.call (this);
      this.vbo = {};

      
      this.clear ();
    }
    
    
    clear (r = 0, g = 0, b = 0, a = 1) {
      let gl = this.gl;
      gl.clearColor (r, g, b, a);
      gl.clearDepth (1.0);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

      return this;
    }
    
    
    setVBO (data = [], type = 'position') {
      let
        gl = this.gl,
        attLocation = gl.getAttribLocation (this.prg, type),
        attStride = {position: 3, color: 4}[type],
        vbo = create_vbo.call (this, data);
     
      gl.bindBuffer (gl.ARRAY_BUFFER, vbo);
      gl.enableVertexAttribArray (attLocation);
      gl.vertexAttribPointer (attLocation, attStride, gl.FLOAT, false, 0, 0);
      this.vbo[type] = vbo;

      return this;
    }
    
    
    setIBO (data = []) {
      let
        gl = this.gl,
        ibo = create_ibo.call (this, data);

      gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, ibo);
      this.ibo = ibo;
      
      return this;

    }
    
    
    draw (mvpMatrix, vertex_index, flash = true) {
      let
        {gl, prg} = this,
        uniLocation = gl.getUniformLocation (prg, 'mvpMatrix');
      gl.uniformMatrix4fv (uniLocation, false, mvpMatrix);
//      gl.drawArrays (gl.TRIANGLES, 0, 3);
      gl.drawElements (gl.TRIANGLES, vertex_index.length, gl.UNSIGNED_SHORT, 0);
      
      if (flash)
        gl.flush ();
      
      return this;
    }
    
    
  }
  

  const
    SHADER_TYPE = {
      'x-shader/x-vertex': 'VERTEX_SHADER',
      'x-shader/x-fragment': 'FRAGMENT_SHADER'},

    create_shader =
      function (target = null) {
        if (target) {
          let
            gl = this.gl,
            {text, type} = target,
            shader = gl.createShader (gl[SHADER_TYPE[type]]);

          gl.shaderSource (shader, text);
          gl.compileShader (shader);
          if (! gl.getShaderParameter (shader, gl.COMPILE_STATUS))
            throw new Error (['シェーダーのコンパイルエラーがありました',
              gl.getShaderInfoLog (shader)].join ('\n'))

          return shader;
        }
        throw new Error ('要素がありません');
      },


      // プログラムオブジェクトを生成しシェーダをリンクする関数
    create_program =
      function () {
        let
          gl = this.gl,
          program = gl.createProgram();

        gl.attachShader (program, this.v_shader);
        gl.attachShader (program, this.f_shader);
        gl.linkProgram (program);

        if (! gl.getProgramParameter(program, gl.LINK_STATUS))
          throw new Error (['プログラムのオブジェクトの生成に失敗しました',
            gl.getProgramInfoLog (program)].join ('\n'));

        gl.useProgram (program);
        return program;
      },


	  create_vbo =
	    function (data) {
	      let
	        gl = this.gl,
	        vbo = gl.createBuffer ();

        gl.bindBuffer (gl.ARRAY_BUFFER, vbo);
        gl.bufferData (gl.ARRAY_BUFFER, new Float32Array (data), gl.STATIC_DRAW);
        gl.bindBuffer (gl.ARRAY_BUFFER, null);

        return vbo;
      },
    
    
    create_ibo =
      function (data) {
        let
          gl = this.gl,
          ibo = gl.createBuffer ();

        gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, ibo);
        gl.bufferData (gl.ELEMENT_ARRAY_BUFFER, new Int16Array (data), gl.STATIC_DRAW);
        gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);

        return ibo;
      };
      

  //_______
  
  this.Shader = Shader;
}


{//実行スクリプト
	let
	  doc = document,
	  canvas = doc.querySelector ('canvas'),
	  shader = new Shader (
	    canvas,
	    doc.querySelector ('script[type="x-shader/x-vertex"]'),
	    doc.querySelector ('script[type="x-shader/x-fragment"]'),
	  ),
    // モデル(頂点)データ
    vertex_position = [
      0.0, 1.0, 0.0,
      1.0, 0.0, 0.0,
      -1.0, 0.0, 0.0,
      0.0, -1.0, 0.0
    ],
    vertex_color = [
      1.0, 0.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0,
      1.0, 1.0, 1.0, 1.0
    ],
    vertex_index = [
      0, 1, 2,
      1, 2, 3
    ],

    // 各種行列の生成と初期化
    matrix = Matrix4.perspective (45, canvas.width / canvas.height, 0.1, 100)
             .multiply (Matrix4.lookAt ([0, 0, 5.0], [0, 0, 0], [0, 1, 0]))
             .multiply (new Matrix4);
    

  shader.setVBO (vertex_position, 'position');
  shader.setVBO (vertex_color, 'color');
  shader.setIBO (vertex_index);

  //_____
  
  let
    start = + new Date,
    deg = Math.PI / 180,
    {sin, cos} = Math;
    
  const loop = function (timeStamp) {
    let
      count = (timeStamp - start) / 30,
      rad = (count % 360) * deg;
    
    
    shader.clear ();

    let
      m = (new Matrix4).rotate (rad, [0,1,0]),
      mMatrix = matrix.copy ().multiply (m);
    
    shader.draw (mMatrix.M, vertex_index);
  
    requestAnimationFrame (loop);
  }
  
  loop ();
}

</script>