BanditBloodwyn
BanditBloodwyn

Reputation: 59

OpenTK (OpenGL) How to correctly use BufferSubData and DrawElements with multiple Objects

I' trying to implement a renderer which uses only one VBO and one EBO to store all object vertices and indices. For that, I found some tutorials and finally came up with a rather good result.

The problem is that it only works properly with ONE single object. As soon as I want to add another one, the rendering shows weird behaviour.

Can you help me with this?

You can find the full code here: https://github.com/BanditBloodwyn/TerritorySimulator. The important classes are:

This is the initializing method in the Renderer:

    public void Initialize(GLShape[] shapeArray)
    {
        Shapes = shapeArray;

        GL.Enable(EnableCap.DepthTest);
        GL.ClearColor(0.0f, 0.0f, 0.10f, 1.0f);

        InitializeBuffers(Shapes);
        InitializeVertexArrayObject(Shapes);
        SetupShader();
        BindBuffers();
    }

The submethods look like this.

    private void InitializeBuffers(GLShape[] shapeArray)
    {
        int vertexBufferSize = shapeArray.Sum(shape => shape.VertexBufferSize);
        int indexBufferSize = shapeArray.Sum(shape => shape.IndexBufferSize);

        // Vertex buffer
        vertexBufferObject = GL.GenBuffer();
        GL.BindBuffer(BufferTarget.ArrayBuffer, vertexBufferObject);
        GL.BufferData(BufferTarget.ArrayBuffer, vertexBufferSize, (IntPtr)0, BufferUsageHint.StaticDraw);

        IntPtr offset = (IntPtr)0;
        foreach (GLShape shape in shapeArray)
        {
            GL.BufferSubData(BufferTarget.ArrayBuffer, offset, shape.VertexBufferSize, shape.Vertices);
            offset += shape.VertexBufferSize;
        }

        // Element buffer
        elementBufferObject = GL.GenBuffer();
        GL.BindBuffer(BufferTarget.ElementArrayBuffer, elementBufferObject);
        GL.BufferData(BufferTarget.ElementArrayBuffer, indexBufferSize, (IntPtr)0, BufferUsageHint.StaticDraw);

        offset = (IntPtr)0;
        foreach (GLShape shape in shapeArray)
        {
            GL.BufferSubData(BufferTarget.ElementArrayBuffer, offset, shape.IndexBufferSize, shape.Indices);
            offset += shape.IndexBufferSize;
        }
    }

    private void InitializeVertexArrayObject(GLShape[] shapeArray)
    {
        foreach (GLShape shape in shapeArray)
        {
            shape.VertexArrayObject = GL.GenVertexArray();
            GL.BindVertexArray(shape.VertexArrayObject);
        }
    }

    private void SetupShader()
    {
        // shader
        string vertexPath = Path.Combine(Environment.CurrentDirectory, @"GLSL\", "Vertex.vert");
        string fragmentPath = Path.Combine(Environment.CurrentDirectory, @"GLSL\", "Fragment.frag");
        shader = new Shader(vertexPath, fragmentPath);
        shader.Use();

        int vertexLocation = shader.GetAttribLocation("aPosition");
        GL.EnableVertexAttribArray(vertexLocation);
        GL.VertexAttribPointer(
            vertexLocation, 
            3, 
            VertexAttribPointerType.Float, 
            false, 
            5 * sizeof(float), 
            0);

        int texCoordLocation = shader.GetAttribLocation("aTexCoord");
        GL.EnableVertexAttribArray(texCoordLocation);
        GL.VertexAttribPointer(
            texCoordLocation, 
            2, 
            VertexAttribPointerType.Float, 
            false, 
            5 * sizeof(float), 
            3 * sizeof(float));

        shader.SetInt("texture0", 0);
        shader.SetInt("texture1", 1);
    }

    private void BindBuffers()
    {
        GL.BindBuffer(BufferTarget.ArrayBuffer, vertexBufferObject);
        GL.BindBuffer(BufferTarget.ElementArrayBuffer, elementBufferObject);
    }

The render function itself looks like this.

    public void Render()
    {
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        if (Shapes == null || Shapes.Length == 0)
            return;

        IntPtr offset = (IntPtr)0;
        foreach (GLShape shape in Shapes)
        {
            foreach (var texture in shape.Textures)
            {
                if (LayerConfiguration.ShowEarthTexture)
                    texture.Key.Use(texture.Value);
                else
                    texture.Key.MakeTransparent(texture.Value);
            }

            ApplyModelTransforms(shape, out Matrix4 model);
            shader.SetMatrix4("model", model);
            shader.SetMatrix4("view", Camera.GetViewMatrix());

            GL.DrawElements(PrimitiveType.Triangles, shape.Indices.Length, DrawElementsType.UnsignedInt, offset);
            offset += shape.IndexBufferSize;
        }

        shader.SetMatrix4("projection", Camera.GetProjectionMatrix());
        shader.Use();
    }

Upvotes: 1

Views: 972

Answers (1)

Rabbid76
Rabbid76

Reputation: 210918

As soon as I want to add another one, the rendering shows weird behavior

Of course, because the indices for the 2nd and following objects are wrong. You need to add the sum of the vertices of the previous meshes to the indexes. To the indices of the first mesh you have to add 0, to the indices of the 2nd mesh you have to add the number of vertices of the 1st mesh, to the indices of the 3rd mesh you have to add the sum of the vertices of the 1st and 2nd mesh, ...

offset = (IntPtr)0;
uint firstVertexIndex = 0;
foreach (GLShape shape in shapeArray)
{
    var indexArray = shape.Indices.Select(index => index + firstVertexIndex).ToArray();
                
    GL.BufferSubData(
        BufferTarget.ElementArrayBuffer, offset, shape.IndexBufferSize, indexArray);
    offset += shape.IndexBufferSize;
    firstVertexIndex += (uint)(shape.VertexBufferSize / (5 * sizeof(float)));
}

Complete method InitializeBuffers:

private void InitializeBuffers(GLShape[] shapeArray)
{
    int vertexBufferSize = shapeArray.Sum(shape => shape.VertexBufferSize);
    int indexBufferSize = shapeArray.Sum(shape => shape.IndexBufferSize);

    // Vertex buffer
    vertexBufferObject = GL.GenBuffer();
    GL.BindBuffer(BufferTarget.ArrayBuffer, vertexBufferObject);
    GL.BufferData(BufferTarget.ArrayBuffer, vertexBufferSize, (IntPtr)0, BufferUsageHint.StaticDraw);

    IntPtr offset = (IntPtr)0;
    foreach (GLShape shape in shapeArray)
    {
        GL.BufferSubData(BufferTarget.ArrayBuffer, offset, shape.VertexBufferSize, shape.Vertices);
        offset += shape.VertexBufferSize;
    }

    // Element buffer
    elementBufferObject = GL.GenBuffer();
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, elementBufferObject);
    GL.BufferData(BufferTarget.ElementArrayBuffer, indexBufferSize, (IntPtr)0, BufferUsageHint.StaticDraw);

    offset = (IntPtr)0;
    uint firstVertexIndex = 0;
    foreach (GLShape shape in shapeArray)
    {
        var indexArray = shape.Indices.Select(index => index + firstVertexIndex).ToArray();
                
        GL.BufferSubData(BufferTarget.ElementArrayBuffer, offset, shape.IndexBufferSize, indexArray);
        offset += shape.IndexBufferSize;
        firstVertexIndex += (uint)(shape.VertexBufferSize / (5 * sizeof(float)));
    }
}

Upvotes: 1

Related Questions