Как нарисовать несколько объектов с использованием единых буферных объектов и рендеринга экземпляров? - PullRequest
1 голос
/ 26 октября 2019

Я хочу нарисовать несколько объектов с анимацией, используя объекты Uniform buffer и Instanced рендеринг.

Я реализовал это с помощью цикла for, но хочу визуализировать их одновременно.

Вот мой код, который использует цикл for для рендеринга нескольких объектов при щелчке мыши.

Я использую четыре внешние библиотеки: webgl-utils.js, webgl-debug.js, cuon-utils. js, cuon-matrix.js. Их можно найти здесь .

"use strict";
const loc_aPosition = 1;
const VSHADER_SOURCE =
`#version 300 es
layout(location=${loc_aPosition}) in vec4 aPosition;
uniform mat4 uRotMatrix;
uniform mat4 uScaleMatrix;
uniform vec2 uOffSet;
void main() {
    gl_Position = aPosition * uScaleMatrix * uRotMatrix + vec4(uOffSet, 0, 0); 
}`;

const FSHADER_SOURCE =
`#version 300 es
precision mediump float;
out vec4 fColor;
uniform vec4 uColor;
void main() {
    fColor = uColor;
}`;


function main() {
  // Retrieve <canvas> element
  let canvas = document.getElementById('webgl');

  // Get the rendering context for WebGL
  let gl = canvas.getContext("webgl2");
  if (!gl) 
  {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // Initialize shaders
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) 
  {
    console.log('Failed to intialize shaders.');
    return;
  }
  
  const loc_uOffSet = gl.getUniformLocation(gl.program, 'uOffSet');
  const loc_uColor = gl.getUniformLocation(gl.program, 'uColor');
  const loc_uRotMatrix = gl.getUniformLocation(gl.program, 'uRotMatrix');
  const loc_uScaleMatrix = gl.getUniformLocation(gl.program, 'uScaleMatrix');

  if(!loc_uOffSet)
  {
      console.log("Failed to load uOffSet uniform variable.");
      return;
  }

  if(!loc_uColor)
  {
      console.log("Failed to load uColor uniform variable.");
      return;
  }

  if(!loc_uRotMatrix)
  {
      console.log("Failed to load uModelMatrix uniform variable.");
      return;
  }

  if(!loc_uScaleMatrix)
  {
      console.log("Falied to load uScaleMatrix uniform variable.");
      return;
  }




  let n = initVertexBuffers(gl);

  if(n < 0)
  {
    console.log('Failed to set the positions of the vertices');
    return;
  }


  // Register function (event handler) to be called on a mouse press

  canvas.onmousedown = function(ev){ click(ev, gl, canvas) };

  // Specify the color for clearing <canvas>
  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  // Clear <canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);


  let tick = function() 
  {
    animate();  // Update the rotation angle
    draw(gl, loc_uRotMatrix, loc_uOffSet, loc_uColor, loc_uScaleMatrix);   // Draw
    requestAnimationFrame(tick, canvas); // Request that the browser calls tick
  };
  tick();
}


//These are the arrays for the attributes of the stars
let g_vertices = []; 
let g_angles = [];
let g_colors = [];
let g_ages = [];
let g_scale = [];

const ANGLE_STEP = -60;


let g_last = Date.now();


function click(ev, gl, canvas) 
{
  let x = ev.clientX; // x coordinate of a mouse pointer
  let y = ev.clientY; // y coordinate of a mouse pointer
  let rect = ev.target.getBoundingClientRect();

  x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
  y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);


  // Store the coordinates and color
  g_vertices.push([x,y]);
  g_angles.push(0);
  g_ages.push(Date.now());
  g_scale.push(1);
  
  let randomPos = Math.floor(Math.random() * Math.floor(3));
  let rgba = [4];
  let randomColor = Math.random();

  for(let i = 0; i<4; i++)
  {
      rgba[i] = randomColor;
  }
  
  rgba[3] = 1.0;
  rgba[randomPos] = Math.random();
    
  g_colors.push(rgba);
}


//Make the BO for making stars
function initVertexBuffers(gl)
{

  let vertices = new Float32Array([    
    0, -0.2,
    -0.3, -0.4,
    0.0, 0.5,
    0.3, -0.4,
    0.0, 0.5,
    0.0, 0.3,
    -0.4, 0.3,
    0.4, 0.3,
    0.0, 0.3,   
  ]);
  let n = 9;

  //Create a buffer Object
  let posBuffer = gl.createBuffer();
  
  if(!posBuffer)
  {
    console.log('Failed to create the buffer object');
    return;
  }

  //Bind the buffer object to target
  gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
  //Write date into the buffer object
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  //Connect the assignment to a_Position variable
  gl.vertexAttribPointer(loc_aPosition, 2, gl.FLOAT, false, 0, 0);

  //Enable the assignment to a_Position variable
  gl.enableVertexAttribArray(loc_aPosition);

  return n;
}

function animate() 
{
    // Calculate the elapsed time
    let now = Date.now();
    let elapsed = now - g_last;
    g_last = now;
    // Update the current rotation angle (adjusted by the elapsed time)
    for(let i = 0; i<g_angles.length; i++)
    {
        g_angles[i] = g_angles[i] + (ANGLE_STEP * elapsed) / 1000.0;
        g_angles[i] %= 360;
        g_scale[i] *= 0.99;
    }
  }


  function draw(gl, loc_uModelMatrix, loc_uOffSet, loc_uColor, loc_uScaleMatrix) 
  {
    // Clear <canvas>
    gl.clear(gl.COLOR_BUFFER_BIT);
    let rotMatrix = new Matrix4();
    let scaleMatrix = new Matrix4();
    
    // Draw the stars
    let len = g_vertices.length;
    for(let i = 0; i < len; i++) 
    {
        if((Date.now() - g_ages[i]) / 1000 > 3.5 ) // dissapear stars about 3.5 seconds after
          continue;


        let rgba = g_colors[i]; 
        rotMatrix.setRotate(g_angles[i], 0, 0, 1);
        scaleMatrix.setScale(g_scale[i], g_scale[i], 1);


        //Set the uniform variables
        gl.uniformMatrix4fv(loc_uModelMatrix, false, rotMatrix.elements);
        gl.uniformMatrix4fv(loc_uScaleMatrix, false, scaleMatrix.elements);
        gl.uniform2f(loc_uOffSet, g_vertices[i][0], g_vertices[i][1]);
        gl.uniform4f(loc_uColor, rgba[0], rgba[1], rgba[2], rgba[3]);


        gl.drawArrays(gl.TRIANGLE_FAN, 0, 9);


        //Reset matrices for the next star
        rotMatrix.setIdentity();
        scaleMatrix.setIdentity();
    }
  }

и вот пример изображения, как это выглядит и работает:

enter image description here

Я изменилкод выше

1024 *
"use strict";
const loc_aPosition = 1;
const VSHADER_SOURCE =
`#version 300 es
layout(location=${loc_aPosition}) in vec4 aPosition;
uniform matrices
{
    mat4 uScaleMat;
    mat4 uRotMat;
    vec2 uOffSetXY;
};
void main() {
    gl_Position = aPosition * uScaleMat * uRotMat + vec4(uOffSetXY, 0, 0); 
}`;

// Fragment shader program
const FSHADER_SOURCE =
`#version 300 es
uniform colors
{
  vec4 uColorVec;
}
precision mediump float;
out vec4 fColor;
void main() {
    fColor = uColorVec;
}`;


function main() {
  // Retrieve <canvas> element
  var canvas = document.getElementById('webgl');
  
  // Get the rendering context for WebGL
  var gl = canvas.getContext("webgl2");
  if (!gl) 
  {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // Initialize shaders
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) 
  {
    console.log('Failed to intialize shaders.');
    return;
  }
  
  const loc_uOffSet = gl.getUniformLocation(gl.program, 'uOffSet');
  const loc_uColor = gl.getUniformLocation(gl.program, 'uColor');
  const loc_uModelMatrix = gl.getUniformLocation(gl.program, 'uModelMatrix');
  const loc_uScaleMatrix = gl.getUniformLocation(gl.program, 'uScaleMatrix');

  if(!loc_uOffSet)
  {
      console.log("Failed to load uOffSet uniform variable.");
      return;
  }

  if(!loc_uColor)
  {
      console.log("Failed to load uColor uniform variable.");
      return;
  }

  if(!loc_uModelMatrix)
  {
      console.log("Failed to load uModelMatrix uniform variable.");
      return;
  }

  if(!loc_uScaleMatrix)
  {
      console.log("Falied to load uScaleMatrix uniform variable.");
      return;
  }



  let matR = new Matrix4();
  let matS = new Matrix4();
  let offSetXY = [];
  let colors = [];
  let prog = gl.program
  let {vao, n} = initVertexBuffers(gl);
  let {vubo, cubo, matBuffer, colorBuffer} = initUBO(gl, prog, matR, matS, offSetXY, colors);

  if(n < 0)
  {
    console.log('Failed to set the positions of the vertices');
    return;
  }


  // Register function (event handler) to be called on a mouse press
  
  canvas.onmousedown = function(ev){ click(ev, gl, canvas) };

  // Specify the color for clearing <canvas>
  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  // Clear <canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);


  let tick = function() 
  {
    animate();
    render(gl, prog, vao, n, g_stars, matBuffer, colorBuffer, matR, matS, offSetXY, colors, vubo, cubo)
    requestAnimationFrame(tick, canvas); // Request that the browser calls tick
  };
  tick();
}


let g_vertices = [];  // The array for the position of Triangle with mouse click
let g_angles = [];
let g_colors = [];
let g_ages = [];
let g_scale = [];
const ANGLE_STEP = -60;
const MAX_TRIANGLES = 30;
let g_last = Date.now();
let g_stars = 0;

function click(ev, gl, canvas) 
{
  let x = ev.clientX; // x coordinate of a mouse pointer
  let y = ev.clientY; // y coordinate of a mouse pointer
  let rect = ev.target.getBoundingClientRect();

  x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
  y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);


  // Store the coordinates and color
  g_vertices.push([x,y]);
  g_angles.push(0);
  g_ages.push(Date.now());
  g_scale.push(1);
  g_stars++;
  
  let randomPos = Math.floor(Math.random() * Math.floor(3));
  let rgba = [4];
  let randomColor = Math.random();

  for(let i = 0; i<4; i++)
  {
      rgba[i] = randomColor;
  }
  
  rgba[3] = 1.0;
  rgba[randomPos] = Math.random();
    
  g_colors.push(rgba);
}


function initVertexBuffers(gl)
{

  let vertices = new Float32Array([    
    0, -0.2,
    -0.3, -0.4,
    0.0, 0.5,
    0.3, -0.4,
    0.0, 0.5,
    0.0, 0.3,
    -0.4, 0.3,
    0.4, 0.3,
    0.0, 0.3,   
  ]);
  let n = 9;

  //Create a buffer Object
  let posBuffer = gl.createBuffer();
  let vao = gl.createVertexArray();

  
  if(!posBuffer)
  {
    console.log('Failed to create the buffer object');
    return;
  }

  gl.bindVertexArray(vao);

  //Bind the buffer object to target
  gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
  //Write date into the buffer object
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  //Connect the assignment to a_Position variable
  gl.vertexAttribPointer(loc_aPosition, 2, gl.FLOAT, false, 0, 0);

  //Enable the assignment to a_Position variable
  gl.enableVertexAttribArray(loc_aPosition);

  return {vao, n};
}

function animate() 
{
    // Calculate the elapsed time
    let now = Date.now();
    let elapsed = now - g_last;
    g_last = now;
    // Update the current rotation angle (adjusted by the elapsed time)
    for(let i = 0; i<g_angles.length; i++)
    {
        g_angles[i] = g_angles[i] + (ANGLE_STEP * elapsed) / 1000.0;
        g_angles[i] %= 360;
        g_scale[i] *= 0.99;
    }
  }


  function draw(gl, loc_uModelMatrix, loc_uOffSet, loc_uColor, loc_uScaleMatrix) 
  {
    // Clear <canvas>
    gl.clear(gl.COLOR_BUFFER_BIT);
    let rotMatrix = new Matrix4();
    let scaleMatrix = new Matrix4();
    
    // Draw the rectangle
    let len = g_vertices.length;
    for(let i = 0; i < len; i++) 
    {
        if((Date.now() - g_ages[i]) / 1000 > 3.5 )
          continue;
        let rgba = g_colors[i];

        rotMatrix.setRotate(g_angles[i], 0, 0, 1);
        scaleMatrix.setScale(g_scale[i], g_scale[i], 1);

        gl.uniformMatrix4fv(loc_uModelMatrix, false, rotMatrix.elements);
        gl.uniformMatrix4fv(loc_uScaleMatrix, false, scaleMatrix.elements);
        gl.uniform2f(loc_uOffSet, g_vertices[i][0], g_vertices[i][1]);
        gl.uniform4f(loc_uColor, rgba[0], rgba[1], rgba[2], rgba[3]);

        gl.drawArrays(gl.TRIANGLE_FAN, 0, 9);

        
        rotMatrix.setIdentity();
        scaleMatrix.setIdentity();
    }
  }

  function initUBO(gl, prog, matR, matS, offSetXY, colors)
  {
    let vertexBinding_Matrices = 1;
    let fragBinding_Colors = 2;

    let vubo = gl.createBuffer();
    let cubo = gl.createBuffer();
    gl.bindBufferBase(gl.UNIFORM_BUFFER, vertexBinding_Matrices, vubo);
    gl.bindBufferBase(gl.UNIFORM_BUFFER, fragBinding_Colors, cubo);

    let idx_uniform_block1 = gl.getUniformBlockIndex(prog, 'matrices');
    let idx_uniform_block2 = gl.getUniformBlockIndex(prog, 'colors');

    gl.uniformBlockBinding(prog, idx_uniform_block1, vertexBinding_Matrices);
    gl.uniformBlockBinding(prog, idx_uniform_block2, fragBinding_Colors);
    
    let FSIZE = 4;

    let matBuffer = new ArrayBuffer(FSIZE * 16 * 2  + FSIZE * 2);

    matR.elements = new Float32Array(matBuffer, 0, 16);
    matS.elements = new Float32Array(matBuffer, FSIZE * 16, 16);
    offSetXY = new Float32Array(matBuffer, FSIZE * 16 * 2, 2);

    gl.bindBuffer(gl.UNIFORM_BUFFER, vubo);
    gl.bufferData(gl.UNIFORM_BUFFER, FSIZE * 16 * 2  + FSIZE * 2, gl.DYNAMIC_DRAW);
    gl.bindBuffer(gl.UNIFORM_BUFFER, null);


    let colorBuffer = new ArrayBuffer(FSIZE * 4);

    colors = new Float32Array(colorBuffer, 0, 4);

    gl.bindBuffer(gl.UNIFORM_BUFFER, cubo);
    gl.bufferData(gl.UNIFORM_BUFFER, FSIZE * 4, gl.DYNAMIC_DRAW);
    gl.bindBuffer(gl.UNIFORM_BUFFER, null);


    return {vubo, cubo, matBuffer, colorBuffer};
  }


  function render(gl, prog, vao, n, g_stars, matBuffer, colorBuffer, matR, matS, offSetXY, colors, vubo, cubo)
  {

  }

все же есть много вещей, чтобы это исправить ... Мне просто интересно, иду ли я по правильному пути или нет ...

Есть ли примеры использования убосов и рендеринга экземпляров?

ps Поскольку я не являюсь носителем английского языка, могут существовать опечатки как в этом вопросе, так и в коде ...

1 Ответ

1 голос
/ 27 октября 2019

Унифицированный буфер Объекты - это просто другой способ установки униформы. Они не помогают с экземплярами. Это просто способ «потенциально» настроить форму быстрее, чем использовать gl.uniformXXX. Например, если у вас есть шейдер, который принимает ambient, diffuse, specular, shininess в качестве входных данных, вы можете создать унифицированный блок, содержащий эти 4 униформы. Затем вы можете создать один единый буферный объект для каждого материала, все 4 из этих настроек, вы можете установить разные материалы с помощью одной функции WebGL

gl.bindBufferRange(gl.UNIFORM_BUFFER, blockIndex, materialBuffer, [offset], [length]);

Вместо 4 вызовов WebGL

gl.uniform3fv(ambientLocation, ...);
gl.uniform3fv(diffuseLocation, ...);
gl.uniform3fv(specularLocation, ...);
gl.uniform1f(shininessLocation, ...);

Таким образом, объекты единого буфера не помогают в создании экземпляров. Все, что они делают, это помогают с настройкой информации, возможно, быстрее. Я говорю «возможно», потому что, хотя я подозреваю, что при выполнении вышеизложенного, предварительное создание во время инициализации единого буферного объекта и во время рендеринга привязка его к блоку Uniforn, безусловно, быстрее, чем вызов gl.uniform 4 раза. С другой стороны, если значения униформ меняются в каждом кадре, необходимо обновить буфер, вызвав gl.bufferSubData с новыми значениями. Это может быть или не быть быстрее, чем просто позвонить gl.uniform. Я не профилировал это. В WebGL больше накладных расходов, чем в OpenGL. Это, вероятно, быстрее, чем больше униформ у вас есть.

В этом ответе показан пример использования унифицированных буферных объектов.

В любом случае важно отметить, что они просто быстрееспособ установки формы. Они не поддерживают новую функциональность.

Instanced Drawing действительно предоставляет новые функциональные возможности. Возможность рисовать несколько вещей одним вызовом отрисовки и обновлять некоторые из ваших атрибутов только один раз для каждого экземпляра, а не один раз для каждой вершины, как они обычно делают.

Итак, чтобы использовать экземплярное рисование, вы обычно должны установитьдо некоторого атрибута с данными, которые будут меняться один раз за экземпляр. Наиболее очевидным является атрибут mat4 для предоставления другой матрицы для каждого экземпляра. Конечно, если вы хотите использовать разные цвета для каждого экземпляра, вам нужно будет также предоставить атрибуты для цветов.

Любой атрибут, который вы хотите изменить только один раз для экземпляра, а не один раз для каждой вершины, как обычно, вы вызываете

  gl.vertexAttribDivisor(attributeLocation, 1);

1 в приведенном выше утверждении означает, что только атрибут 1 переходит к следующему значению каждый 1 экземпляр. Помещение 2 означает, что атрибут продвигается только после 2-х экземпляров и т. Д. 0 = делать обычные вещи и продвигать каждую вершину.

Пример:

const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) alert('need WebGL2');

const positionLoc = 0;
const matrixLoc = 1; // note: mat4 attributes take 4 indices
const colorLoc = 5;


const vs = `#version 300 es
layout(location = ${positionLoc}) in vec4 position;

layout(location = ${matrixLoc}) in mat4 modelMatrix;  // we'll make this per instance
layout(location = ${colorLoc}) in vec4 color;  // we'll make this per instance

uniform mat4 viewProjection;

out vec4 v_color;

void main () {
  gl_Position = viewProjection * modelMatrix * position;
  v_color = color;
}
`;

const fs = `#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 outColor;
void main() {
  outColor = v_color;
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const viewProjectionLoc = gl.getUniformLocation(program, 'viewProjection');

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([    
    0, 0.2,
    -0.2, -0.2,
    0.2, -0.2,
  ]), gl.STATIC_DRAW);
  
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);

const deg = v => v * Math.PI / 180;

// setup matrixes, one per instance
const matrixBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([    
    ...m4.rotateZ(m4.translation([-0.5, 0, 0]), deg(10)),
    ...m4.rotateZ(m4.translation([-0.25, 0, 0]), deg(20)),
    ...m4.rotateZ(m4.translation([0, 0, 0]), deg(30)),
    ...m4.rotateZ(m4.translation([0.25, 0, 0]), deg(40)),
    ...m4.rotateZ(m4.translation([0.5, 0, 0]), deg(50)),
  ]), gl.DYNAMIC_DRAW);
 
// set all 4 attributes for matrix
for (let i = 0; i < 4; ++i) {
  gl.enableVertexAttribArray(matrixLoc + i);
  // note the stride and offset
  gl.vertexAttribPointer(matrixLoc + i, 4, gl.FLOAT, false, 64, i * 16);
  // this line says this attribute only changes for each 1 instance
  gl.vertexAttribDivisor(matrixLoc + i, 1);
}
 
// setup colors, one per instance
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([    
    1, 0, 0, 1,
    0, 1, 0, 1,
    0, 0, 1, 1,
    1, 0, 1, 1,
    0, 1, 1, 1,
  ]), gl.DYNAMIC_DRAW);
 
// set all attribute for color
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
// this line says this attribute only changes for each 1 instance
gl.vertexAttribDivisor(colorLoc, 1);

gl.useProgram(program);
gl.uniformMatrix4fv(viewProjectionLoc, false, m4.identity());
gl.drawArraysInstanced(
  gl.TRIANGLES,
  0,  // offset
  3,  // num vertices per instance
  5,  // num instances
);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

Для их анимации вам необходимо обновить данные буфера для matrixBuffer каждого кадра, вызвав gl.bufferSubData

const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) alert('need WebGL2');

const positionLoc = 0;
const matrixLoc = 1; // note: mat4 attributes take 4 indices
const colorLoc = 5;


const vs = `#version 300 es
layout(location = ${positionLoc}) in vec4 position;

layout(location = ${matrixLoc}) in mat4 modelMatrix;  // we'll make this per instance
layout(location = ${colorLoc}) in vec4 color;  // we'll make this per instance

uniform mat4 viewProjection;

out vec4 v_color;

void main () {
  gl_Position = viewProjection * modelMatrix * position;
  v_color = color;
}
`;

const fs = `#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 outColor;
void main() {
  outColor = v_color;
}
`;

const program = twgl.createProgram(gl, [vs, fs]);
const viewProjectionLoc = gl.getUniformLocation(program, 'viewProjection');

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([    
    0, 0.2,
    -0.2, -0.2,
    0.2, -0.2,
  ]), gl.STATIC_DRAW);
  
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);

const deg = v => v * Math.PI / 180;

// setup matrixes, one per instance
const numInstances = 5;
const matrixBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
// just allocate the buffer
gl.bufferData(gl.ARRAY_BUFFER, numInstances * 16 * 4, gl.DYNAMIC_DRAW);
 
// set all 4 attributes for matrix
for (let i = 0; i < 4; ++i) {
  gl.enableVertexAttribArray(matrixLoc + i);
  // note the stride and offset
  gl.vertexAttribPointer(matrixLoc + i, 4, gl.FLOAT, false, 64, i * 16);
  // this line says this attribute only changes for each 1 instance
  gl.vertexAttribDivisor(matrixLoc + i, 1);
}
 
// setup colors, one per instance
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([    
    1, 0, 0, 1,
    0, 1, 0, 1,
    0, 0, 1, 1,
    1, 0, 1, 1,
    0, 1, 1, 1,
  ]), gl.DYNAMIC_DRAW);
 
// set all attribute for color
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
// this line says this attribute only changes for each 1 instance
gl.vertexAttribDivisor(colorLoc, 1);

// make a typed array with one view per matrix
const matrixData = new Float32Array(numInstances * 16);
const matrices = [];
for (let i = 0; i < numInstances; ++i) {
  matrices.push(new Float32Array(matrixData.buffer, i * 16 * 4, 16));
}

function render(time) {
  time *= 0.001; // seconds
  
  // update all the matrices
  matrices.forEach((mat, ndx) => {
    m4.translation([-0.5 + ndx * 0.25, 0, 0], mat);
    m4.rotateZ(mat, time * (0.1 + 0.1 * ndx), mat);
  });
  
  // upload the new matrix data
  gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
  gl.bufferSubData(gl.ARRAY_BUFFER, 0, matrixData);

  gl.bindVertexArray(vao);
  gl.useProgram(program);
  gl.uniformMatrix4fv(viewProjectionLoc, false, m4.identity());
  gl.drawArraysInstanced(
    gl.TRIANGLES,
    0,  // offset
    3,  // num vertices per instance
    numInstances,  // num instances
  );
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
...