0

I'm trying to use instancing with WebGL based on this guide. I've tried to create a simple example that draws a red triangle on the left and a blue triangle on the right. When I run it, it draws a blue triangle on the left and I don't understand why.

I've tried changing the arguments to drawArraysInstancedANGLE but that doesn't help. I've tried changing the order of lines for binding buffers and setting attribute pointers, etc. What am I doing wrong? Thanks

const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");
const ext = gl.getExtension("ANGLE_instanced_arrays");
const vert = gl.createShader(gl.VERTEX_SHADER);
const frag = gl.createShader(gl.FRAGMENT_SHADER);
const program = gl.createProgram();

gl.shaderSource(vert, `
  attribute vec4 a_position;
  attribute vec4 a_color;
  varying vec4 v_color;

  void main() {
    gl_Position = a_position;
    v_color = a_color;
  }
`);

gl.shaderSource(frag, `
  precision mediump float;
  varying vec4 v_color;

  void main() {
    gl_FragColor = v_color;
  }
`);

gl.compileShader(vert);
gl.compileShader(frag);
gl.attachShader(program, vert);
gl.attachShader(program, frag);
gl.linkProgram(program);

const a_position = gl.getAttribLocation(program, "a_position");
const buffer1 = gl.createBuffer();
const positions = [-0.5, 0, -0.5, 0.2, -0.3, 0, 0.5, 0, 0.5, 0.2, 0.3, 0];
gl.bindBuffer(gl.ARRAY_BUFFER, buffer1);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

const a_color = gl.getAttribLocation(program, "a_color");
const buffer2 = gl.createBuffer();
const colors = [1, 0, 0, 1, 0, 0, 1, 1];
gl.bindBuffer(gl.ARRAY_BUFFER, buffer2);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

gl.viewport(0, 0, 300, 300);
gl.useProgram(program);

gl.bindBuffer(gl.ARRAY_BUFFER, buffer1);
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, buffer2);
gl.enableVertexAttribArray(a_color);
gl.vertexAttribPointer(a_color, 4, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(a_color, 1);

ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 3, 2);
<canvas id="canvas" width="300" height="300"></canvas>
Chris
  • 1,501
  • 17
  • 32
  • Please read the tutorial until the end. You are missing `attribute mat4 matrix;`. The 2 instances of the triangle overlap exactly, they share the same position. – Rabbid76 Jun 01 '20 at 09:48
  • I'm not using matrices in this example. I'm explicitly setting the triangle corner positions. I don't think they share the same position. One appears on the left of the screen, the other on the right. When I don't use instancing and draw both triangles in a single drawArrays call it works fine (provided I make the color buffer data big enough). – Chris Jun 01 '20 at 09:59
  • You have misunderstood [Instancing](https://www.khronos.org/opengl/wiki/Vertex_Rendering#Instancing). Read the tutorial again. You have 2 instances of one triangle. The vertices 4 to 6 are not used at all. – Rabbid76 Jun 01 '20 at 10:08
  • 1
    Ah yes, sorry, you're right. I had misunderstood instancing. I thought it would read the first 3x2 values for the first instance then the second 3x2 values for the second but that's not how it works. Instead, it repeats the first 3x2 values. To achieve what I want, I think I need to index into a uniform array for the vertex data or put it into a texture, like this: https://stackoverflow.com/questions/55319323/in-webgl-when-instancing-geometry-is-it-possible-to-pass-per-vertex-attribute Thanks for the help. – Chris Jun 01 '20 at 10:28

1 Answers1

1

So I see you mostly already got an answer in the comments.

The code is drawing the first triangle twice, once in red, then once in blue.

But, ... you mentioned indexing a uniform array. That would be unusual

Normally you'd put per instance data in a buffer. The example on the site linked used a matrix just to be generic since a matrix well will let you do just about any math (translation, rotation, scale, 3d projection, ...) but just as an example, you could just pass in single x offset.

const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");
const ext = gl.getExtension("ANGLE_instanced_arrays");
const vert = gl.createShader(gl.VERTEX_SHADER);
const frag = gl.createShader(gl.FRAGMENT_SHADER);
const program = gl.createProgram();

gl.shaderSource(vert, `
  attribute vec4 a_position;
  attribute float a_xoffset;
  attribute vec4 a_color;
  varying vec4 v_color;

  void main() {
    gl_Position = a_position;
    gl_Position.x += a_xoffset;
    v_color = a_color;
  }
`);

gl.shaderSource(frag, `
  precision mediump float;
  varying vec4 v_color;

  void main() {
    gl_FragColor = v_color;
  }
`);

gl.compileShader(vert);
gl.compileShader(frag);
gl.attachShader(program, vert);
gl.attachShader(program, frag);
gl.linkProgram(program);

const a_position = gl.getAttribLocation(program, "a_position");
const buffer1 = gl.createBuffer();
const positions = [-0.5, 0, -0.5, 0.2, -0.3, 0];
gl.bindBuffer(gl.ARRAY_BUFFER, buffer1);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

const a_color = gl.getAttribLocation(program, "a_color");
const buffer2 = gl.createBuffer();
const colors = [1, 0, 0, 1, 0, 0, 1, 1];
gl.bindBuffer(gl.ARRAY_BUFFER, buffer2);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

const a_xoffset = gl.getAttribLocation(program, "a_xoffset");
const buffer3 = gl.createBuffer();
const xoffsets = [0, 1];
gl.bindBuffer(gl.ARRAY_BUFFER, buffer3);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(xoffsets), gl.STATIC_DRAW);

gl.viewport(0, 0, 300, 300);
gl.useProgram(program);

gl.bindBuffer(gl.ARRAY_BUFFER, buffer1);
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, buffer2);
gl.enableVertexAttribArray(a_color);
gl.vertexAttribPointer(a_color, 4, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(a_color, 1);

gl.bindBuffer(gl.ARRAY_BUFFER, buffer3);
gl.enableVertexAttribArray(a_xoffset);
gl.vertexAttribPointer(a_xoffset, 1, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(a_xoffset, 1);

ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 3, 2);
<canvas id="canvas" width="300" height="300"></canvas>

I'm just pointing that out because you have millions of values in a buffer but uniforms are far more limited so indexing a uniform for instancing is not common.

If you actually wanted different data per instance then yes you'd have to do something else.

One example is here

gman
  • 100,619
  • 31
  • 269
  • 393
  • Thanks for this! To give more context, what I'm trying to do is render several triangle fans that contain light geometry vertices (based on https://ncase.me/sight-and-light/). Each light has different properties like color, brightness, etc. My original plan was to use instancing (one per fan) and use a divisor of 1 for the properties, but that won't work. The first fan's shape is being repeated. I think, instead I'm going to try writing the geometry data to a float texture and index into it using attributes (instanceId and vertexId). It seems pretty complicated though! – Chris Jun 01 '20 at 13:41
  • 1
    Putting the data in a texture is not that complicated. The link at the bottom of the answer does that. – gman Jun 01 '20 at 13:47
  • Thanks, I managed to get it working! I'm delighted. It's running about 10x faster in my use case. Your tutorials and prolific answering of SO questions is very much appreciated. – Chris Jun 01 '20 at 18:40