Wednesday, January 25, 2017

OpenGL 4 with OpenTK in C# Part 2: Compiling shaders and linking them


Now that we have a game window, lets 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
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, if you want to have all the theory behind why things are done in a certain way I recommend reading OpenGL SuperBible, Seventh Edition :)
This part will build upon the game window from the previous post.

Updated 2017-05-17
Clarified how to use the shaders described in this article.

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 the 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. Also bind the Closed event to a handler and call Exit() manually from there to ensure that cleanup is triggered when user closes the application.

private int _program;
protected override void OnLoad(EventArgs e)
{
    CursorVisible = true;
    _program = CompileShaders();
    Closed += OnClosed;
}
private void OnClosed(object sender, EventArgs eventArgs)
{
    Exit();
}

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. Copy paste into a text editor and save as vertexShader.vert in a subfolder called shaders. Change the properties of the file to Copy Always so that it ends up in your build folder.

#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. Copy paste into a text editor and save as fragmentShader.frag in the same folder as the vertex shader and change to copy always here as well!

#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, lets 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);
    Closed += OnClosed;
}

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.UseProgramGL.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 video of 2 of our cats fighting.

Until next time: Work to Live, Don’t Live to Work

4 comments:

  1. Where to i apply the code for the VertexShader and FragmentShader

    ReplyDelete
    Replies
    1. and where do i find vertexShader.vert and fragmentShader.vert as it keeps giving me an error that it isn't found

      Delete
    2. Hi there.
      You are on the track for the answer. The VertexShader portion is to be saved in a text file called vertexShader.vert and the FragmentShader part it saved in a file called fragmentShader.frag.
      If you do not get it working, please look in the github repository for part 4:
      https://github.com/eowind/dreamstatecoding
      Hope this helps!

      Delete
    3. Sorry for the late reply. Thanks for the help, i managed to get it working now!

      Delete