0

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);
MaxC
  • 885
  • 7
  • 21

2 Answers2

3

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);
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Yeah thank you, this is what I was looking for. Now the square is rendered. However, there is something still I'm missing because the translation part of the matrix gets distorted while the rotation works as expected. Since the translation data is in the 4th column of the matrix, I think there is something not working properly with that column. Using the same matrix and sending it via uniform, the square translate as expected. – MaxC Jul 30 '22 at 15:54
  • I figured out the problem. When matrices go to the shader via VBO, for some unknown reason are transposed. When matrices are passed as uniform, they stay straight. I have to manually transpose back. Is there any way to avoid this useless double transposing which for sure it costs ops? – MaxC Jul 31 '22 at 12:51
0

In opentk the matrices are sent by columns not by rows as usual so it is necessary to invert the rows by columns, you can do it like this.

private Matrix4[] TransposeMatrix(Matrix4[] inputModel)
{
    var outputModel = new Matrix4[inputModel.Length];
    for(int i = 0; i < inputModel.Length; i++)
    {
        outputModel[i].Row0 = inputModel[i].Column0;
        outputModel[i].Row1 = inputModel[i].Column1;
        outputModel[i].Row2 = inputModel[i].Column2;
        outputModel[i].Row3 = inputModel[i].Column3;
    }
    return outputModel;
}
  • thanks for your answer. Any clue why uniform matrices are sent correctly while VBO aren't? – MaxC Jan 27 '23 at 22:16