Wednesday, February 1, 2017

OpenGL 4 with OpenTK in C# Part 5: Buffers and Triangle


In this post we will look at how to access buffers in OpenGL from your application code to provide your shaders with vertex data and to draw our first triangle.

This is part 5 of my series on OpenGL4 with OpenTK.
For other posts in this series:
OpenGL 4 with OpenTK in C# Part 1: Initialize the GameWindow
OpenGL 4 with OpenTK in C# Part 2: Compiling shaders and linking them
OpenGL 4 with OpenTK in C# Part 3: Passing data to shaders
OpenGL 4 with OpenTK in C# Part 4: Refactoring and adding error handling
OpenGL 4 with OpenTK in C# Part 5: Buffers and Triangle
OpenGL 4 with OpenTK in C# Part 6: Rotations and Movement of objects
OpenGL 4 with OpenTK in C# Part 7: Vectors and Matrices
OpenGL 4 with OpenTK in C# Part 8: Drawing multiple objects
OpenGL 4 with OpenTK in C# Part 9: Texturing
OpenGL 4 with OpenTK in C# Part 10: Asteroid Invaders
OpenGL 4 with OpenTK in C# Part 11: Mipmap
OpenGL 4 with OpenTK in C# Part 12: Basic Moveable Camera
OpenGL 4 with OpenTK in C# Part 13: IcoSphere
OpenGL 4 with OpenTK in C# Part 14: Basic Text

As stated in the previous post, I am in no way an expert in OpenGL. I write these posts as a way to learn and if someone else finds these posts useful then all the better :)
If you think that the progress is slow, then know that I am a slow learner :P
This part will build upon the game window and shaders from the previous post.

Initialize a buffer

Some of the methods used here require OpenGL 4.5.0 to work. Please update your graphic card drivers if you have any issues and make sure you are running on the correct graphics card and not the integrated one.

First off lets define a struct to hold our vertex position and color.
public struct Vertex
{
    public const int Size = (4 + 4) * 4; // size of struct in bytes

    private readonly Vector4 _position;
    private readonly Color4 _color;

    public Vertex(Vector4 position, Color4 color)
    {
        _position = position;
        _color = color;
    }
}
The above struct matches the Vertex Shader, i.e. that it has a Vec4 for Position and Vec4 for Color. The Vector4 and Color4 are provided by OpenTK.

In the previous posts we have used a pretty static VertexArray just because it is needed to be able to draw anything. So lets create a vertex array and a buffer.
    _vertexArray = GL.GenVertexArray();
    _buffer = GL.GenBuffer();

    GL.BindVertexArray(_vertexArray);
    GL.BindBuffer(BufferTarget.ArrayBuffer, _buffer);
Next step is to tell OpenGL how we will be using this new buffer
    GL.NamedBufferStorage(
        _buffer,
        Vertex.Size*vertices.Length,        // the size needed by this buffer
        vertices,                           // data to initialize with
        BufferStorageFlags.MapWriteBit);    // at this point we will only write to the buffer
Now we need to provide information on where to find the attribute data for the shader, i.e. position and color. Starting with Position:
    GL.VertexArrayAttribBinding(_vertexArray, 0, 0);
    GL.EnableVertexArrayAttrib(_vertexArray, 0);
    GL.VertexArrayAttribFormat(
        _vertexArray,
        0,                      // attribute index, from the shader location = 0
        4,                      // size of attribute, vec4
        VertexAttribType.Float, // contains floats
        false,                  // does not need to be normalized as it is already, floats ignore this flag anyway
        0);                     // relative offset, first item
And then the color
    GL.VertexArrayAttribBinding(_vertexArray, 1, 0);
    GL.EnableVertexArrayAttrib(_vertexArray, 1);
    GL.VertexArrayAttribFormat(
        _vertexArray,
        1,                      // attribute index, from the shader location = 1
        4,                      // size of attribute, vec4
        VertexAttribType.Float, // contains floats
        false,                  // does not need to be normalized as it is already, floats ignore this flag anyway
        16);                     // relative offset after a vec4
Finally we link this together using the following command.
    GL.VertexArrayVertexBuffer(_vertexArray, 0, _buffer, IntPtr.Zero, Vertex.Size);

Rendering this all with:
    GL.BindVertexArray(_vertexArray);
    GL.DrawArrays(PrimitiveType.Triangles, 0, 3);


Refactoring to a RenderObject class

As this is quite a lot of code, I decided to create a class for this called RenderObject. The above code for initiating goes into the constructor and the render code into the Render method
public class RenderObject : IDisposable
{
    private bool _initialized;
    private readonly int _vertexArray;
    private readonly int _buffer;
    private readonly int _verticeCount;
    public RenderObject(Vertex[] vertices)
    {
        _verticeCount = vertices.Length;
 
  // create vertex array and buffer here
  
        _initialized = true;
    }
    public void Render()
    {
        GL.BindVertexArray(_vertexArray);
        GL.DrawArrays(PrimitiveType.Triangles, 0, _verticeCount);
    }
        
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_initialized)
            {
                GL.DeleteVertexArray(_vertexArray);
                GL.DeleteBuffer(_buffer);
                _initialized = false;
            }
        }
    }
}

This will hopefully help us further down the road when we want to add more objects to the scene.
This gives us the following changes in our GameWindow starting the OnLoad method:
private List<RenderObject> _renderObjects = new List<RenderObject>(); 
protected override void OnLoad(EventArgs e)
{
 Vertex[] vertices =
 {
  new Vertex(new Vector4(-0.25f, 0.25f, 0.5f, 1-0f), Color4.HotPink),
  new Vertex(new Vector4( 0.0f, -0.25f, 0.5f, 1-0f), Color4.HotPink),
  new Vertex(new Vector4( 0.25f, 0.25f, 0.5f, 1-0f), Color4.HotPink), 
 };
 _renderObjects.Add(new RenderObject(vertices));

 CursorVisible = true;

 _program = CreateProgram();
 GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
 GL.PatchParameter(PatchParameterInt.PatchVertices, 3);
 Closed += OnClosed;
}
So we replaced the VertexBuffer initialization with initialization of a render object list instead. The example vertice array should result in a pink triangle on the screen similar to the title picture of this post.

The OnExit method is changed to dispose the render objects that have been initialized by our code
public override void Exit()
{
 Debug.WriteLine("Exit called");
 foreach(var obj in _renderObjects)
  obj.Dispose();
 GL.DeleteProgram(_program);
 base.Exit();
}

And finally our OnRenderFrame code loops over the render objects and calls their independent Render methods to get them on the screen.
protected override void OnRenderFrame(FrameEventArgs e)
{
 _time += e.Time;
 Title = $"{_title}: (Vsync: {VSync}) FPS: {1f / e.Time:0}";
 Color4 backColor;
 backColor.A = 1.0f;
 backColor.R = 0.1f;
 backColor.G = 0.1f;
 backColor.B = 0.3f;
 GL.ClearColor(backColor);
 GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

 GL.UseProgram(_program);
 foreach(var renderObject in _renderObjects)
  renderObject.Render();
 SwapBuffers();
}

Shaders used

The shaders used in this post are the following
Vertex Shader
#version 450 core

layout (location = 0) in vec4 position;
layout(location = 1) in vec4 color;
out vec4 vs_color;

void main(void)
{
 gl_Position = position;
 vs_color = color;
}
Fragment Shader
#version 450 core
in vec4 vs_color;
out vec4 color;

void main(void)
{
 color = vs_color;
}

For the full source at the end of part 5, go to: https://github.com/eowind/dreamstatecoding

Hope this helps someone out there :)
Thanks for reading. Here's another video of one of our cats playing to lighten up your day.
Until next time: Work to Live, Don’t Live to Work

5 comments:

  1. Somehow `GL.NamedBufferStorage` and other functions won't show up. I use OpenGL 4.0.0 and I imported `OpenTK.Graphics.OpenGL4`. What's wrong? :(

    ReplyDelete
    Replies
    1. Ok i think i got it. Some methods were renamed i think.

      Delete
    2. But there are some i don't know their new name is. Please help! :/

      Delete
    3. Ok now i used you OpenTK.dll on Github. Problem solved!

      Delete
  2. I cannot run this code. NamedBufferStorage raises an exception:

    Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
    at OpenTK.Graphics.OpenGL.GL.NamedBufferStorage[T2](Int32 buffer, Int32 size, T2[] data, BufferStorageFlags flags)
    at Minecraft.RenderObject..ctor(Vertex[] vertices) in d:\users\user\documents\visual studio 2017\Projects\Minecraft\Minecraft\RenderObject.cs:line 24
    at Minecraft.Game.OnLoad(EventArgs e) in d:\users\user\documents\visual studio 2017\Projects\Minecraft\Minecraft\Program.cs:line 42
    at OpenTK.GameWindow.Run(Double updates_per_second, Double frames_per_second)
    at Minecraft.Program.Main(String[] args) in d:\users\user\documents\visual studio 2017\Projects\Minecraft\Minecraft\Program.cs:line 189

    Can someone please help me?

    ReplyDelete