Search code examples
c#openglopentk

How to render instances using the new OperGL / OpenTk APIs


I'm trying to put together information from this tutorial (Advanced-OpenGL/Instancing) and these answers (How to render using 2 VBO) (New API Clarification) in order to render instances of a square, giving the model matrix for each instance to the shader through an ArrayBuffer. The code I ended with is the following. I sliced and tested any part, and the problem seems to be the model matrix itself is not passed correctly to the shader. I'm using OpenTK in Visual Studio.

For simplicity and debugging, the pool contains just a single square, so I don't still have divisor problems or other funny things I still don't cope with.

My vertex data arrays contain the 3 floats for position and 4 floats for color (stride = 7 time float size).

My results with the attached code are:

  • if I remove the imodel multiplication in the vertex shader, I get exactly what I expect, a red square (rendered as 2 triangles) with a green border (rendered as a line loop).
  • if I change the shader and I multiply by the model matrix, I get a red line above the center of the screen which is changing its length over time. The animation makes sense because the simulation is rotating the square, so the angle updates regularly and thus the model matrix calculated changes. Another great result because I'm actually sending dynamic data to the shader. Howvere I can't have my original square rotated and translated.

Any clue? Thanks a lot.

Vertex Shader:

#version 430 core
layout (location = 0) in vec3  aPos;
layout (location = 1) in vec4  aCol;
layout (location = 2) in mat4 imodel;
out vec4 fColor;
uniform mat4 view;
uniform mat4 projection;

void main() {
    fColor = aCol;
    gl_Position = vec4(aPos, 1.0) * imodel * view * projection;
}

Fragment Shader:

#version 430 core
in vec4 fColor;
out vec4 FragColor;

void main() {
    FragColor = fColor;
}

OnLoad snippet (initialization):

InstanceVBO = GL.GenBuffer();
GL.GenBuffers(2, VBO);

GL.BindBuffer(BufferTarget.ArrayBuffer, VBO[0]);
GL.BufferData(BufferTarget.ArrayBuffer,
    7 * LineLoopVertCount  * sizeof(float), 
    LineLoopVertData, BufferUsageHint.StaticDraw);

GL.BindBuffer(BufferTarget.ArrayBuffer, VBO[1]);
GL.BufferData(BufferTarget.ArrayBuffer,
    7 * TrianglesVertCount * sizeof(float),
    TrianglesVertData, BufferUsageHint.StaticDraw);

GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

// VAO SETUP

VAO = GL.GenVertexArray();
GL.BindVertexArray(VAO);

// Position
GL.EnableVertexAttribArray(0);
GL.VertexAttribFormat(0, 3, VertexAttribType.Float, false, 0);
GL.VertexArrayAttribBinding(VAO, 0, 0);

// COlor
GL.EnableVertexAttribArray(1);
GL.VertexAttribFormat(1, 4, VertexAttribType.Float, false, 3 * sizeof(float));
GL.VertexArrayAttribBinding(VAO, 1, 0);

int vec4Size = 4;
GL.EnableVertexAttribArray(2);
GL.VertexAttribFormat(2, 4, VertexAttribType.Float, false, 0 * vec4Size * sizeof(float));
GL.VertexAttribFormat(3, 4, VertexAttribType.Float, false, 1 * vec4Size * sizeof(float));
GL.VertexAttribFormat(4, 4, VertexAttribType.Float, false, 2 * vec4Size * sizeof(float));
GL.VertexAttribFormat(5, 4, VertexAttribType.Float, false, 3 * vec4Size * sizeof(float));

GL.VertexAttribDivisor(2, 1);
GL.VertexAttribDivisor(3, 1);
GL.VertexAttribDivisor(4, 1);
GL.VertexAttribDivisor(5, 1);

GL.VertexArrayAttribBinding(VAO, 2, 1);

GL.BindVertexArray(0);

OnFrameRender snippet:

shader.Use();
shader.SetMatrix4("view", cameraViewMatrix);
shader.SetMatrix4("projection", cameraProjectionMatrix);

int mat4Size = 16;

for (int i = 0; i < simulation.poolCount; i++)
{
    modelMatrix[i] = Matrix4.CreateFromAxisAngle(
        this.RotationAxis, simulation.pool[i].Angle);

    modelMatrix[i] = matrix[i] * Matrix4.CreateTranslation(new Vector3(
        simulation.pool[i].Position.X,
        simulation.pool[i].Position.Y,
        0f));

    //modelMatrix[i] = Matrix4.Identity;
}

// Copy model matrices into the VBO
// ----------------------------------------
GL.BindBuffer(BufferTarget.ArrayBuffer, InstanceVBO);
GL.BufferData(BufferTarget.ArrayBuffer,
    simulation.poolCount * mat4Size * sizeof(float),
    modelMatrix, BufferUsageHint.DynamicDraw);
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
// ----------------------------------------

GL.BindVertexArray(VAO);

GL.BindVertexBuffer(1, InstanceVBO, IntPtr.Zero, mat4Size * sizeof(float));

GL.BindVertexBuffer(0, VBO[0], IntPtr.Zero, 7 * sizeof(float));
GL.DrawArraysInstanced(PrimitiveType.LineLoop, 0, LineLoopVertCount, simulation.poolCount);

GL.BindVertexBuffer(0, lifeFormVBO[1], IntPtr.Zero, lifeFormTrianglesFStride * sizeof(float));
GL.DrawArraysInstanced(PrimitiveType.Triangles, 0, TrianglesVertCount, simulation.poolCount);

GL.BindVertexArray(0);

Solution

  • There is a lot wrong here.

    First, you don't enable any of the attribute arrays after 2, even though your shader says that you're reading 3-5 too. Similarly, you don't set the attribute binding for any of the arrays after 2.

    But your bigger problem is that you use glVertexAttribDivisor. That's the wrong function for what you're trying to do. That's the old API for setting the divisor.

    In separate attribute format, the divisor is part of the buffer binding, not the vertex attribute. So the divisor needs to be set with glVertexBindingDivisor, and the index it is given is the index you intend to bind the buffer to. Which should be 1.

    So presumably, your code should look like:

    int vec4Size = 4;
    for(int ix = 0; ix < 4; ++ix)
    {
      int attribIx = 2 + ix;
      GL.EnableVertexAttribArray(attribIx);
      GL.VertexAttribFormat(attribIx, 4, VertexAttribType.Float, false, ix * vec4Size * sizeof(float));
      GL.VertexArrayAttribBinding(VAO, attribIx, 1); //All use the same buffer binding
    }
    
    GL.VertexBindingDivisor(1, 1);