惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
T
Threat Research - Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
云风的 BLOG
云风的 BLOG
G
GRAHAM CLULEY
Spread Privacy
Spread Privacy
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
Arctic Wolf
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
P
Privacy International News Feed
Blog — PlanetScale
Blog — PlanetScale
Stack Overflow Blog
Stack Overflow Blog
M
MIT News - Artificial intelligence
The Register - Security
The Register - Security
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
Recent Announcements
Recent Announcements
Martin Fowler
Martin Fowler
A
About on SuperTechFans
W
WeLiveSecurity
GbyAI
GbyAI
V
Vulnerabilities – Threatpost
The GitHub Blog
The GitHub Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
Y
Y Combinator Blog
月光博客
月光博客
Scott Helme
Scott Helme
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
U
Unit 42
G
Google Developers Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threatpost
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google Online Security Blog
Google Online Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 司徒正美

WebGL Fundamentals

WebGL Using 2 or More Textures WebGL Implementing DrawImage WebGL 2D Matrices WebGL Implementing A Matrix Stack WebGL 2D Rotation WebGL 2D Scale WebGL 2D Translation WebGL - Rasterization vs 3D libraries WebGL 3D - Cameras WebGL 3D Geometry - Lathe WebGL 3D - Directional Lighting WebGL 3D - Point Lighting WebGL 3D - Normal Mapping WebGL 3D - Spot Lighting WebGL - Orthographic 3D WebGL 3D Perspective Correct Texture Mapping WebGL 3D Perspective WebGL Textures WebGL and Alpha WebGL - Animation WebGL Anti-Patterns WebGL Attributes WebGL Boilerplate WebGL - Cross Origin Images WebGL Cross Platform Issues WebGL Cubemaps WebGL 3D - Data Textures WebGL - Drawing Multiple Things WebGL Drawing Without Data WebGL Environment Maps (reflections) WebGL Fog WebGL Framebuffers WebGL Fundamentals WebGL GPGPU WebGL How It Works WebGL Image Processing Continued WebGL Image Processing WebGL Indexed Vertices WebGL - Less Code, More Fun WebGL Load Obj with Mtl WebGL Load Obj WebGL Matrices vs Math Matrices WebGL Multiple Views, Multiple Canvases WebGL Picking WebGL Planar and Perspective Projection Mapping WebGL Points, Lines, and Triangles WebGL Post Processing WebGL Precision Issues WebGL Pulling Vertices Accessing textures by pixel coordinate in WebGL2 A simple way to show the load on the GPU's vertex and fragment processing? Apply a displacement map and specular map Can anyone explain what this GLSL fragment shader is doing? Can I mute the warning about vertex attrib 0 being disabled? Create image warping effect in WebGL Creating a smudge/liquify effect How to draw Depth Sprites Determine min/max values for the entire image Don't blend a polygon that crosses itself Drawing 2D image with depth map to achieve pseudo-3D effect Drawing a heightmap Drawing layers with different points Drawing Many different models in a single draw call Drawing textured sprites with instanced drawing Efficient particle system in javascript? (WebGL) Emulating palette based graphics in WebGL FPS-like camera movement with basic matrix transformations Get the size of a point for collision checking GLSL shader to support coloring and texturing How can I compute for 500 points which of 1000 line segments is nearest to each point? How can I create a 16bit historgram of 16bit data How can I get all the uniforms and uniformBlocks How can I move the perspective vanishing point from the center of the canvas? How to Achieve Moving Line with Trail Effects How to bind an array of textures to a WebGL shader uniform? How to blend colors across 2 triangles How to combine more text drawing into fewer draw calls How to control the color between vertices How to create a torus How to detect clipped triangles in the framgment shader How to determine the average brightness in a scene? How to draw correctly textured trapezoid polygons How to fade the drawing buffer How to figure out how much GPU work to do without crashing WebGL How to get audio data into a shader How to get code completion for WebGL in Visual Studio Code How to get the 3d coordinates of a mouse click How to get pixelize effect in webgl? How to implement zoom from mouse in 2D WebGL How to import a heightmap in WebGL How to load images in the background with no jank How to make a smudge brush tool How to make WebGL canvas transparent How to optimize rendering a UI How to prevent texture bleeding with a texture atlas How to process particle positions How to read a single component with readPixels How to render large scale images like 32000x32000 How to simulate a 3D texture in WebGL How to support both WebGL and WebGL2
WebGL Optimization - Instanced Drawing
WebGLFundame · 2025-02-26 · via WebGL Fundamentals

WebGL has a feature called instanced drawing. It is basically a way to draw more than one of the same thing faster than drawing each thing individually.

Note that the feature is an optional extension in WebGL1 but is apparently available in pretty much all browsers and devices.

First let's make an example that draws multiple instances of the same thing.

Starting with code similar to what we ended up with at the end of the article on orthographic projection we start with these 2 shaders

<!-- vertex shader -->
<script id="vertex-shader-3d" type="x-shader/x-vertex">
attribute vec4 a_position;
uniform mat4 matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = matrix * a_position;
}
</script>

and

<!-- fragment shader -->
<script id="fragment-shader-3d" type="x-shader/x-fragment">
precision mediump float;

uniform vec4 color;

void main() {
  gl_FragColor = color;
}
</script>

The vertex shader multiplies each vertex by a single matrix which we covered in that article as it is a fairly flexible arrangement. The fragment shader just uses a color we pass in via a uniform.

To draw we need to compile the shaders, link them together and look up the locations of the attributes and uniforms.

const program = webglUtils.createProgramFromScripts(
    gl, ['vertex-shader-3d', 'fragment-shader-3d']);

const positionLoc = gl.getAttribLocation(program, 'a_position');
const colorLoc = gl.getUniformLocation(program, 'color');
const matrixLoc = gl.getUniformLocation(program, 'matrix');

Then we need to supply data for the positions via a buffer.

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    -0.1,  0.4,
    -0.1, -0.4,
     0.1, -0.4,
     0.1, -0.4,
    -0.1,  0.4,
     0.1,  0.4,
     0.4, -0.1,
    -0.4, -0.1,
    -0.4,  0.1,
    -0.4,  0.1,
     0.4, -0.1,
     0.4,  0.1,
  ]), gl.STATIC_DRAW);
const numVertices = 12;

Let's draw 5 instances. We'll make 5 matrices and 5 colors for each instance.

const numInstances = 5;
const matrices = [
  m4.identity(),
  m4.identity(),
  m4.identity(),
  m4.identity(),
  m4.identity(),
];

const colors = [
  [ 1, 0, 0, 1, ],  // red
  [ 0, 1, 0, 1, ],  // green
  [ 0, 0, 1, 1, ],  // blue
  [ 1, 0, 1, 1, ],  // magenta
  [ 0, 1, 1, 1, ],  // cyan
];

To draw first we use the shader program, then setup the attribute, and then loop over the 5 instances, computing a new matrix for each one, setting the matrix uniform and color and then drawing.

function render(time) {
  time *= 0.001; // seconds

  gl.useProgram(program);

  // setup the position attribute
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.enableVertexAttribArray(positionLoc);
  gl.vertexAttribPointer(
      positionLoc,  // location
      2,            // size (num values to pull from buffer per iteration)
      gl.FLOAT,     // type of data in buffer
      false,        // normalize
      0,            // stride (0 = compute from size and type above)
      0,            // offset in buffer
  );

  matrices.forEach((mat, ndx) => {
    m4.translation(-0.5 + ndx * 0.25, 0, 0, mat);
    m4.zRotate(mat, time * (0.1 + 0.1 * ndx), mat);

    const color = colors[ndx];

    gl.uniform4fv(colorLoc, color);
    gl.uniformMatrix4fv(matrixLoc, false, mat);

    gl.drawArrays(
        gl.TRIANGLES,
        0,             // offset
        numVertices,   // num vertices per instance
    );
  });

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

Note that the matrix math library takes an optional destination matrix at the end of each matrix math function. In most articles we haven't used this feature and just let the library allocate a new matrix for us but this time we want the result to be stored in the matrices we already created.

This works and we get 5 plus symbols of different colors rotating.

That took 5 calls each to gl.uniform4v, gl.uniformMatrix4fv, and gl.drawArrays for a total of 15 WebGL calls. If our shaders were more complex, like the shaders in the article on spot lighting we'd have at least 7 calls per object, 6 calls to gl.uniformXXX and one call to gl.drawArrays. If we were drawing 400 objects that would be 2800 WebGL calls.

Instancing is a way to reduce those calls. It works by letting you tell WebGL how many times you want the same thing drawn (the number of instances). For each attribute you designate if that attribute will advance to the next value from its assigned buffer every time the vertex shader is called (the default), or only every N instances where N is usually 1.

So for example instead of supplying matrix and color from a uniform, we would instead supply them via attributes. We'd put the matrix and color for each instance in a buffer, set up the attributes to pull data from those buffers, and tell WebGL, only advance to the next value once per instance.

Let's do it!

The first thing we need to do is check for and enable this optional feature of WebGL. It's called ANGLE_instanced_arrays.

const canvas = document.querySelector('#canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
  return;
}

+const ext = gl.getExtension('ANGLE_instanced_arrays');
+if (!ext) {
+  return alert('need ANGLE_instanced_arrays');
+}

Next we'll change the shaders to use attributes for matrix and color instead of uniforms.

<!-- vertex shader -->
<script id="vertex-shader-3d" type="x-shader/x-vertex">
attribute vec4 a_position;
-uniform mat4 matrix;
+attribute vec4 color;
+attribute mat4 matrix;
+
+varying vec4 v_color;

void main() {
  // Multiply the position by the matrix.
  gl_Position = matrix * a_position;

+  // Pass the vertex color to the fragment shader.
+  v_color = color;
}
</script>

and

<!-- fragment shader -->
<script id="fragment-shader-3d" type="x-shader/x-fragment">
precision mediump float;

-uniform vec4 color;
+// Passed in from the vertex shader.
+varying vec4 v_color;

void main() {
-  gl_FragColor = color;
+  gl_FragColor = v_color;
}
</script>

attributes only work in the vertex shader so we need to get the color from an attribute in the vertex shader and pass it to the fragment shader via a varying.

Next we need to look up the locations of these attributes.

const program = webglUtils.createProgramFromScripts(
    gl, ['vertex-shader-3d', 'fragment-shader-3d']);

const positionLoc = gl.getAttribLocation(program, 'a_position');
-const colorLoc = gl.getUniformLocation(program, 'color');
-const matrixLoc = gl.getUniformLocation(program, 'matrix');
+const colorLoc = gl.getAttribLocation(program, 'color');
+const matrixLoc = gl.getAttribLocation(program, 'matrix');

Now, we need a buffer hold the matrices at will get applied to the attribute. Since a buffer is best updated in one chuck we'll put all of our matrices in the same Float32Array

// setup matrices, one per instance
const numInstances = 5;
+// make a typed array with one view per matrix
+const matrixData = new Float32Array(numInstances * 16);

We'll then make Float32Array views, one for each matrix.

-const matrices = [
-  m4.identity(),
-  m4.identity(),
-  m4.identity(),
-  m4.identity(),
-  m4.identity(),
-];
const matrices = [];
for (let i = 0; i < numInstances; ++i) {
  const byteOffsetToMatrix = i * 16 * 4;
  const numFloatsForView = 16;
  matrices.push(new Float32Array(
      matrixData.buffer,
      byteOffsetToMatrix,
      numFloatsForView));
}

This way when we want to reference the data for all the matrices we can use matrixData but when we want any individual matrix we can use matrices[ndx].

We also need to create a buffer on the GPU for this data. We only need to allocate the buffer at this point, we don't need to supply data so the 2nd parameter to gl.bufferData is a size which just allocates the buffer.

const matrixBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
// just allocate the buffer
gl.bufferData(gl.ARRAY_BUFFER, matrixData.byteLength, gl.DYNAMIC_DRAW);

Notice we passed gl.DYNAMIC_DRAW as the last parameter. This is a hint to WebGL that we're going to change this data often.

Next we need our colors also in a buffer. This data will not be changing, at least in this example, so we'll just upload the data.

-const colors = [
-  [ 1, 0, 0, 1, ],  // red
-  [ 0, 1, 0, 1, ],  // green
-  [ 0, 0, 1, 1, ],  // blue
-  [ 1, 0, 1, 1, ],  // magenta
-  [ 0, 1, 1, 1, ],  // cyan
-];
+// 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,  // red
+        0, 1, 0, 1,  // green
+        0, 0, 1, 1,  // blue
+        1, 0, 1, 1,  // magenta
+        0, 1, 1, 1,  // cyan
+      ]),
+    gl.STATIC_DRAW);

At draw time instead of looping over each instance, setting the matrix and color uniforms, and then calling draw we'll first compute the matrix for each instance.

// update all the matrices
matrices.forEach((mat, ndx) => {
  m4.translation(-0.5 + ndx * 0.25, 0, 0, mat);
  m4.zRotate(mat, time * (0.1 + 0.1 * ndx), mat);

-  const color = colors[ndx];
-
-  gl.uniform4fv(colorLoc, color);
-  gl.uniformMatrix4fv(matrixLoc, false, mat);
-
-  gl.drawArrays(
-      gl.TRIANGLES,
-      0,             // offset
-      numVertices,   // num vertices per instance
-  );
});

Because our matrix library takes an optional destination matrix and because our matrices are just Float32Array views into the same larger Float32Array, when we're done all the matrix data is ready to upload to the GPU directly.

// upload the new matrix data
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, matrixData);

Now we need to set up the attributes for the matrices and colors. The matrix attribute is a mat4. A mat4 actually uses 4 attribute slots.

const bytesPerMatrix = 4 * 16;
for (let i = 0; i < 4; ++i) {
  const loc = matrixLoc + i;
  gl.enableVertexAttribArray(loc);
  // note the stride and offset
  const offset = i * 16;  // 4 floats per row, 4 bytes per float
  gl.vertexAttribPointer(
      loc,              // location
      4,                // size (num values to pull from buffer per iteration)
      gl.FLOAT,         // type of data in buffer
      false,            // normalize
      bytesPerMatrix,   // stride, num bytes to advance to get to next set of values
      offset,           // offset in buffer
  );
  // this line says this attribute only changes for each 1 instance
  ext.vertexAttribDivisorANGLE(loc, 1);
}

The most important point relative to instanced drawing is the call to ext.vertexAttribDivisorANGLE. It sets this attribute to only advance to the next value once per instance. This means the matrix attributes will use the first matrix for every vertex for the first instance, the second matrix for the second instance and so on.

We also need to setup the color attribute

// set attribute for color
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
// this line says this attribute only changes for each 1 instance
ext.vertexAttribDivisorANGLE(colorLoc, 1);

It's important to remember, now that we've set the divisor on these 2 attributes if we want to use them to draw something else we'd need to set them back to their default of 0 or, better we should use vertex array objects.

Finally we can draw all instances in a single draw call.

ext.drawArraysInstancedANGLE(
  gl.TRIANGLES,
  0,             // offset
  numVertices,   // num vertices per instance
  numInstances,  // num instances
);

In the example above we had 3 WebGL calls per shape * 5 shapes which were 15 calls total. We now have just 2 calls for all 5 shapes, one to upload the matrices, another to draw. There is a some setup for the colors and matrices to start drawing. We could move that setup from render time to init time by using a vertex array object.

I feel like this should go without saying but I then again maybe it's only obvious to me because I've done this too much. The code above does not take into account the aspect of the canvas. It does not use a projection matrix or a view matrix. It was meant only to demonstrate instanced drawing. If you wanted a projection and/or a view matrix we could add the calculation to JavaScript. That would mean more work for JavaScript. The more obvious way would be to add one or two more uniforms to the vertex shader.

<!-- vertex shader -->
<script id="vertex-shader-3d" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 color;
attribute mat4 matrix;
+uniform mat4 projection;
+uniform mat4 view;

varying vec4 v_color;

void main() {
  // Multiply the position by the matrix.
-  gl_Position = matrix * a_position;
+  gl_Position = projection * view * matrix * a_position;

  // Pass the vertex color to the fragment shader.
  v_color = color;
}
</script>

and then look up their locations at init time

const positionLoc = gl.getAttribLocation(program, 'a_position');
const colorLoc = gl.getAttribLocation(program, 'color');
const matrixLoc = gl.getAttribLocation(program, 'matrix');
+const projectionLoc = gl.getUniformLocation(program, 'projection');
+const viewLoc = gl.getUniformLocation(program, 'view');

and set them appropriately at render time.

gl.useProgram(program);

+// set the view and projection matrices since
+// they are shared by all instances
+const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+gl.uniformMatrix4fv(projectionLoc, false,
+    m4.orthographic(-aspect, aspect, -1, 1, -1, 1));
+gl.uniformMatrix4fv(viewLoc, false, m4.zRotation(time * .1));