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
Basic bullet movement patterns in 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
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse
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 bufferNow 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 itemAnd 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 vec4Finally 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 methodpublic 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 followingVertex 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
Thanks for reading. Here's another video of one of our cats playing to lighten up your day.
All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment or share a link on social media, not required but appreciated! :)
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? :(
ReplyDeleteOk i think i got it. Some methods were renamed i think.
DeleteBut there are some i don't know their new name is. Please help! :/
DeleteOk now i used you OpenTK.dll on Github. Problem solved!
DeleteHi there, sorry that I did not reply but I was on vacation most of August.
DeleteBut yes, I used some old version of the dll to start with as well and it didn't work until I found the correct one. For all the examples on this site, I recommend you to get the one in my github repository just to make sure that we are on the same page :)
If your development environment is Visual Studio,
Deletego Tools -> NuGet Package Manager -> NuGet Package Console
and wait for loading.
then, you should type "Install-Package OpenTK -Version 3.0.0-pre"
another wait for installing...
And... Finished! you can use your OpenTK library
I cannot run this code. NamedBufferStorage raises an exception:
ReplyDeleteUnhandled 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?
seems like something is wrong with one of your shaders. Maybe you wrote down the wrong gl version (expl. 450, but your graphics card only supports 400).
DeleteI had that error everytime :D.
I remember that one of the next tutorials are about debugging.
Hi there, sorry for the late reply but I was on vacation for the last few weeks.
DeleteIf you still haven't, please look at the http://dreamstatecoding.blogspot.se/2017/01/opengl-4-with-opentk-in-c-part-4.html that goes through error handling etc.
This can also be that you are not freeing memory in a correct way, i.e. disposing the objects that you create. You need to do that as well :)
what is the modern equivelant of "VertexArrayVertexBuffer" in newer version of OpenTK? (google is failing me)
ReplyDeleteNot sure of your question. But this article shows how to use buffers in openGL version 4+ (i.e. modern I guess)
DeleteWhat I understand there is not 1-1 mapping from older openGL versions to 4 as the whole render pipe is a little different.
Just one note, you should be using OnUnload instead of Onclose. That is how the OpenTK onsite tutor does it.
ReplyDelete