Now that we have a game window, let's look at loading and compiling shaders and getting things on screen.
This is part 2 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
As stated in the previous post, I am in no way an expert in OpenGL. I write these posts as a way to learn, basically by reading up on examples in a book (OpenGL SuperBible, Seventh Edition) and then converting them to OpenTK. I really recommend the book if you want to know how it actually works and why things are done. :)
This part will build upon the game window from the previous post.
Compiling Shaders and Linking Them
I will not go into details about shaders in this post as I do not really know much of them yet, maybe I'll write about them in detail in the future. At this point, I just want to be able to use them.
The following function does just that. It tells OpenGL to create a new shader object with the
GL.CreateShader
method. It then populates that shader object with the shader source code from a file with the GL.ShaderSource
call. Using System.IO.File.ReadAllText
to load the contents of the shader file and then a call to GL.CompileShader
to compile it.
The minimum shaders needed to get something to the screen are the VertexShader
and the FragmentShader
, so the function loads both.
After that, we create a new program by calling GL.CreateProgram
, attach the shaders with GL.AttachShader
and then link the program with GL.LinkProgram
. Pretty straight forward.
After the linking, it is OK to remove the shaders by calling GL.DetachShader
and GL.DeleteShader
. Always keep a tidy shop. :)
private int CompileShaders()
{
var vertexShader = GL.CreateShader(ShaderType.VertexShader);
GL.ShaderSource(vertexShader,
File.ReadAllText(@"Components\Shaders\vertexShader.vert"));
GL.CompileShader(vertexShader);
var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fragmentShader,
File.ReadAllText(@"Components\Shaders\fragmentShader.frag"));
GL.CompileShader(fragmentShader);
var program = GL.CreateProgram();
GL.AttachShader(program, vertexShader);
GL.AttachShader(program, fragmentShader);
GL.LinkProgram(program);
GL.DetachShader(program, vertexShader);
GL.DetachShader(program, fragmentShader);
GL.DeleteShader(vertexShader);
GL.DeleteShader(fragmentShader);
return program;
}
Call this from the OnLoad
method and store the program id in a member variable in the gamewindow so that we can find it later.
private int _program;
protected override void OnLoad(EventArgs e)
{
CursorVisible = true;
_program = CompileShaders();
}
And a little cleanup in the OnExit
method:
public override void Exit()
{
GL.DeleteProgram(_program);
base.Exit();
}
And when executed, the screen is still dark blue. So nothing.
Drawing Things
So... evidently we need shaders. So I'll just provide two simple ones.
VertexShader
A basic Vertex Shader that sets the position of a vertex.
#version 440 core
void main(void)
{
gl_Position = vec4( 0.25, -0.25, 0.5, 1.0);
}
FragmentShader
A basic Fragment Shader that sets the color of the fragment.
#version 440 core
out vec4 color;
void main(void)
{
color = vec4(1.0, 0.0, 0.0, 1.0);
}
Changes To Our Program
In the OnLoad
method, let's add a vertex buffer initialization so that we can bind it so that we can use it.
private int _program;
private int _vertexArray;
protected override void OnLoad(EventArgs e)
{
CursorVisible = true;
_program = CompileShaders();
GL.GenVertexArrays(1, out _vertexArray);
GL.BindVertexArray(_vertexArray);
}
More cleanup in our OnExit
method.
public override void Exit()
{
GL.DeleteVertexArrays(1, ref _vertexArray);
GL.DeleteProgram(_program);
base.Exit();
}
And then to actually do some output on the screen in OnRenderFrame
method by adding GL.UseProgram
, GL.DrawArrays
and GL.PointSize
. The last so that the point is bigger then just 1 pixel.
protected override void OnRenderFrame(FrameEventArgs e)
{
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);
GL.DrawArrays(PrimitiveType.Points, 0, 1);
GL.PointSize(10);
SwapBuffers();
}
This should give an output similar to the header (without the white text, i.e., a red dot on blue background).
If for some reason the shader doesn't work after compiling, check the versions.
Hope this helps someone out there. :)
Thanks for reading. Here's a GIF of 2 of our cats fighting. (Full video at https://youtu.be/p9fTlyCnwgg.)
Until next time: Work to Live, Don’t Live to Work
CodeProject