greggman / twgl.js

A Tiny WebGL helper Library
http://twgljs.org
MIT License
2.61k stars 258 forks source link

Understanding VAO and instancing #132

Closed medakk closed 5 years ago

medakk commented 5 years ago

I'm trying to draw instanced quads, with each instance having a unique "position" and "velocity" value which is updated every frame. My initialization code has:

const programInfo = twgl.createProgramInfo(gl, [VERTEX_SHADER, FRAGMENT_SHADER]);
const buffer = new Float32Array(particleCount*4);
const quad = {
    position: [-0.5, -0.5, 0,
               +0.5, -0.5, 0,
               -0.5, +0.5, 0,
               +0.5, +0.5, 0],
    texcoord: [0, 0,
               1, 0,
               0, 1,
               1, 1],
    indices:  [0, 1, 2, 1, 3, 2],
    instancePosition: {
        numComponents: 2,
        data: buffer,
        stride: 16,
        offset: 0,
        divisor: 1,
    },
    instanceVelocity: {
        numComponents: 2,
        data: buffer,
        stride: 16,
        offset: 8,
        divisor: 1,
    },
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, quad);
const vertexArrayInfo = twgl.createVertexArrayInfo(gl, programInfo, bufferInfo);

The idea is that I am trying to use the vertex array to hold both instancePosition and instanceVelocity.

In my render loop I create a new buffer to store the updated positions and velocities, interleaved as p1.x, p1.y, v1.x, v1.y, p2.x, p2.y, ...:

const particleBuf = new Float32Array(size*4);
// (snipped) code to initialize particleBuf

const vao = vertexArrayInfo.vertexArrayObject;
gl.bindVertexArray(vao);
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, particleBuf);
gl.bindVertexArray(null);

twgl.drawBufferInfo(gl, vertexArrayInfo, gl.TRIANGLES, vertexArrayInfo.numElements, 0, size);

While this is populating the instancePosition attribute correctly, accessing the instanceVelocity attribute causes the shader to glitch out, it doesn't even draw all the instances.

Code:

I can provide more information and code if required. Any help is appreciated. Thank you.

greggman commented 5 years ago

this line https://github.com/medakk/spherro/blob/e4651214ab5ae79f42b60b9698deb5ff720d8064/www/src/renderer.js#L112 doesn't make any sense

greggman commented 5 years ago

I don't know that's the issue but this article might help

greggman commented 5 years ago

Okay, this isn't really the place to ask people to debug your code. It's a place to report bugs in twgl You should really post on Stack Overflow but I guess I'm feeling generous (though I would have answered on stack overflow)

BTW: it would be really useful if you'd have made a working example somewhere. See below.

  1. If you want to share buffers across attributes then you have to make the buffer yourself and pass it in as a buffer. Might be able to fix in twgl but it's not currently how twgl works so....

this code

        const buffer = new Float32Array(particleCount*4);
        const quad = {
            position: [-0.5, -0.5, 0,
                       +0.5, -0.5, 0,
                       -0.5, +0.5, 0,
                       +0.5, +0.5, 0],
            texcoord: [0, 0,
                       1, 0,
                       0, 1,
                       1, 1],
            indices:  [0, 1, 2, 1, 3, 2],
            instancePosition: {
                numComponents: 2,
                data: buffer,
                stride: 16,
                offset: 0,
                divisor: 1,
            },
            instanceVelocity: {
                numComponents: 2,
                data: buffer,
                stride: 16,
                offset: 8,
                divisor: 1,
            },
        };
        const bufferInfo = twgl.createBufferInfoFromArrays(gl, quad);

Does not make a shared WebGLBuffer for instancePosition and instanceVelocity. Instead it makes 2 separate WebGLBuffers, and fills each with the contents of the Float32Array buffer

What you want is this

        const buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, particleCount*4*4, gl.DYNAMIC_DRAW);

        const quad = {
            position: [-0.5, -0.5, 0,
                       +0.5, -0.5, 0,
                       -0.5, +0.5, 0,
                       +0.5, +0.5, 0],
            texcoord: [0, 0,
                       1, 0,
                       0, 1,
                       1, 1],
            indices:  [0, 1, 2, 1, 3, 2],
            instancePosition: {
                numComponents: 2,
                buffer: buffer,
                stride: 16,
                offset: 0,
                divisor: 1,
            },
            instanceVelocity: {
                numComponents: 2,
                buffer: buffer,
                stride: 16,
                offset: 8,
                divisor: 1,
            },
        };
        const bufferInfo = twgl.createBufferInfoFromArrays(gl, quad);

Effectively you make your own WebGLBuffer and then pass it into twgl referenced in each attribute you want it shared.

Next up this code

        const vao = vertexArrayInfo.vertexArrayObject;
        gl.bindVertexArray(vao);
            gl.bufferSubData(gl.ARRAY_BUFFER, 0, particleBuf);

Doesn't make any sense. Which buffer is bound to ARRAY_BUFFER is undefined here. We can guess that it's the last buffer made by twgl.createBufferInfoFromArrays but it's not normal to guess and that state is not part of the vertex array object (vao).

To do it manually you need access to the buffer we created above. You can look that up in the bufferInfo or you and communicate it some other way. I added to your glInfo as buffer so

        gl.bindBuffer(gl.ARRAY_BUFFER, this.glInfo.buffer);
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, particleBuf);

You also unset the vao after that

       gl.bindVertexArray(null);

        twgl.drawBufferInfo(gl, vertexArrayInfo, gl.TRIANGLES, vertexArrayInfo.numElements, 0, size);

But the VAO contains all the attribute state so you don't want to clear it.

        const vao = vertexArrayInfo.vertexArrayObject;
        gl.bindVertexArray(vao);
        twgl.drawBufferInfo(gl, vertexArrayInfo, gl.TRIANGLES, vertexArrayInfo.numElements, 0, size);

Here's a working example?

https://jsfiddle.net/greggman/Lms7guoh/

But please ask this kind of question on stackoverflow I monitor it and so am likely to answer there

medakk commented 5 years ago

I'll use stackoverflow for questions of this kind in the future.

To be clear:

Thank you for the detailed response.

greggman commented 5 years ago

Just to clarify your clarification

greggman commented 5 years ago

Let me also add, since it looks like your planning on using wasm, I'd suggest you separate position and velocity from whatever other data you're using inside wasm so that you can skip the copy you're doing in JavaScript. In other words, make it possible to do this

    const particles = new Float32Array(memory.buffer, particlesPtr, size * stride);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.glInfo.buffer);
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, particles);
medakk commented 5 years ago

Thanks. I re-wrote that bit to directly a send a buffer for WebGL to consume.