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
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

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.



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! :)

20 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
    4. for this tutorial you can use this code
      GL.ShaderSource(vertexShader,
      "#version 440 core " +
      "void main(void){" +
      "gl_Position = vec4(0.25, -0.25, 0.5, 1.0);" +
      "}"
      );

      Delete
  2. Very good tutorials! I love them (and your cats :D)!

    ReplyDelete
  3. I can't seem to render the red dot. I don't think shader version #440 is playing nicely. I"m trying to do this with VS on Mac OSX. I've tried #130 but no luck. There are some other examples online that render triangles and setup a modelView and that works. But i'd like to tryhttps://dreamstatecoding.blogspot.com/logout?d=https://www.blogger.com/logout-redirect.g?blogID%3D1265125331765205173%26postID%3D7577036090966248799 to get your stuff working as well.

    ReplyDelete
    Replies
    1. Have you tried to download the solution from github? From part 5 and forward at https://github.com/eowind/dreamstatecoding
      This tutorial series uses openGL 4 specific APIs so I dont think that 130 would work as it uses legacy APIs (openGL 3.0 to be specific)

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. I have a question.
    In gl_Position = vec4(0.25, -0.25, 0.5, 1.0);,
    What do "0.5" and "1.0" mean?

    I (probably) know that the first two things are x and y each.
    But what about the others?

    I changed them to 0 each, then it didn't work!

    Sorry for my bad English, from S. Korea.

    ReplyDelete
    Replies
    1. Hi there, the 0.5 is the Z axis, i.e. depth. for more details on Z axis look at https://learnopengl.com/Getting-started/Coordinate-Systems and scroll down to the Right-handed system part.
      The 1.0 is W, the recommendation is to use W = 1 when starting out. For detailed information, check out https://www.tomdalling.com/blog/modern-opengl/explaining-homogenous-coordinates-and-projective-geometry/

      Delete
  6. NOT WORK

    GL.ShaderSource(vertexShader, File.ReadAllText(@"Components\Shaders\vertexShader.vert"));

    ReplyDelete
    Replies
    1. Hi there, you need to save the vertex shader from the post into a text file that is placed in the folder specified by that line.
      If you have trouble with that, try downloading the source from github in a later post of this tutorial!

      Delete
  7. how to create
    vertexShader.vert and fragmentShader.frag
    files in VS2017 please.

    ReplyDelete
    Replies
    1. I think the easiest is probably to create a new code file and rename it and replace the content.
      Just remember to set the properties of the file to 'copy if newer' in Visual Studio so that it gets copied to your output folder during the build.

      Delete
  8. GL.BindVertexArray and GL.GenVertexArrays methods do not exist in OpenTK.
    I am using OpenTK nuget version '3.0.1'.
    I also tried downloading latest release of OpenTK from github:
    https://github.com/opentk/opentk/releases/tag/v3.0.0
    But the method is still not found

    ReplyDelete
    Replies
    1. Ok, after downloading the release i found out that the both methods 'GL.BindVertexArray' and 'GL.GenVertexArrays' were placed in ES30 namespace:

      OpenTK.Graphics.ES30.GL.GenVertexArrays();
      OpenTK.Graphics.ES30.GL.BindVertexArray();

      Hope this helps someone else :D

      Delete
  9. Hello, for some reason this line:
    int vertexShader = GL.CreateShader(ShaderType.VertexShader);

    ...throws the exception: System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

    ReplyDelete
  10. Hello,
    I followed this to a T. However for some reason the

    private int CreateProgram() //is throwing an error. MainWindow.CreateProgram(): not all code paths
    return a value.

    ReplyDelete