问题
I want to draw multiple objects with animations by using Uniform buffer objets and Instanced rendering..
I've implemented this with a for loop but I want to render them at one time.
Here is my code which using for loop to render multiple objects when mouse is clicked..
I am using four external libraries which are webgl-utils.js, webgl-debug.js, cuon-utils.js, cuon-matrix.js. These can be found here.
"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();
}
}
and here is the sample image how it looks and works:
I modified above code like this
"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)
{
}
still there are lots of things to fix this... I just wonder whether I am going on a right track or not...
is there any examples using ubos and instanced rendering?
p.s Since I am not a native English speaker, so there may exist typos both this question and the code...
回答1:
Uniform Buffer Objects are just a different way to set uniforms. They don't help with instancing. They are just a way to "potentially" set uniforms faster than using gl.uniformXXX
. For example if you have a shader that takes ambient
, diffuse
, specular
, shininess
as inputs you could make a uniform block that contains those 4 uniforms. You could then create one uniform buffer object for each material, all 4 of those settings, you can set different material with one WebGL function
gl.bindBufferRange(gl.UNIFORM_BUFFER, blockIndex, materialBuffer, [offset], [length]);
Instead of 4 WebGL calls
gl.uniform3fv(ambientLocation, ...);
gl.uniform3fv(diffuseLocation, ...);
gl.uniform3fv(specularLocation, ...);
gl.uniform1f(shininessLocation, ...);
So, uniform buffer objects don't help with instancing. All they do is help with setting uinforms possibly quicker. I say "possibly" because while I suspect doing the above, pre-creating at init time a uniform buffer object and at render time binding it to a uniforn block is certainly faster than calling gl.uniform
4 times. On the other hand if the values of the uniforms change every frame then you need to update the buffer by calling gl.bufferSubData
with the new values. That may or maybe not be faster than just calling gl.uniform
. I haven't profiled it. There is more overhead in WebGL than OpenGL. It's probably faster the more uniforms you have.
This answer shows an example of using uniform buffer objects.
In any case the important point is they are just a faster way of setting uniforms. They don't enable new functionality.
Instanced Drawing does provide new functionality. The ability to draw multiple things with 1 draw call and have some of your attributes only updated once per instance instead of once per vertex like they normally do.
So, in order to use instanced drawing you generally have to set up some attribute with data that will change once per instance. The most obvious is a mat4
attrtibute to provide a different matrix per instance. Of course if you want different colors for each instance you'll need to provide attributes for colors as well.
Any attribute you want to only change once per instance instead of once per vertex like normal you call
gl.vertexAttribDivisor(attributeLocation, 1);
The 1 in the statement above means only advance the attribute to the next value every 1 instance. Putting a 2 there would mean the attribute only advances after 2 instances etc. 0 = do the normal thing and advance every vertex.
Example:
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>
To animate them you'd need to update the buffer data for the matrixBuffer
each frame by calling 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>
来源:https://stackoverflow.com/questions/58568278/how-to-draw-multiple-objects-by-using-uniform-buffer-objects-and-instanced-rende