Saturday, February 16, 2013

OpenTK Tutorial 1 - Opening Windows and Drawing a Triangle

I'm writing this tutorial because the actual OpenTK documentation is sorely lacking. They have a good explanation for how to install it (which I recommend following before this, since it doesn't need to be rewritten), but that's it. There's a "Learn OpenTK in 15'" tutorial, but it's not great. You do make something simple, and it does take under 15 minutes, but they just hand you the code instead of having you create any of it. You don't learn much about what the code does, or why things are done a certain way. I'm going to try to write a better one (using mostly their code).

(As another note, this is technically using a deprecated way to do it, but I'm basing this on their example)




                                         

Part 1: Setup

First, install OpenTK. It's a fairly straightforward process, and the installer makes it very simple.

Now, open your .NET IDE (integrated development environment) of choice that uses C#. I use Visual Studio, but MonoDevelop and SharpDevelop should both work just fine.

The next part will actually follow from the official OpenTK documentation:
Create a new project in your .NET IDE (don't have a .NET IDE? Check out MonoDevelop or Visual Studio Express). Make it of type "Console Application". In the "Solution Explorer" pane, rightclick "References" and select to "Add Reference". In the Browse tab, select OpenTK and click OK.
OpenTK depends on System.Drawing, so you also need to add a reference to System.Drawing.dll. In the "Solution Explorer" pane, rightclick "References" and select to "Add Reference". In the .NET tab, select System.Drawing and click OK.
Part 2: Coding

Now we have a basically empty project. What we'll want to do first is to open the Program.cs file. Right now it's a basic class, doing nothing. We'll keep it around for our Main function. To start displaying anything,  we need to make a sub-class of the GameWindow type. Add a new class called "Game". Make it a subclass of GameWindow (you'll need to add a using directive for OpenTK to use the class).

You should have something like this:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;

namespace OpenTKTutorial1
{

    class Game: GameWindow
    {

    }
}



Now let's see our first visible bit of progress. Go back to Program.cs, and add the following code to the Main function:


using (Game game = new Game())
{

       game.Run(30.0);

}


If you run this code, you'll see a window pop up!




It's blank and boring, but it's definitely progress.

The Run method of the GameWindow has multiple overloads. With a single float parameter, Run will give your window 30 UpdateFrame events a second, and as many RenderFrame events per second as the computer will process.

Now, let's make that window do something more interesting. In the Game class, you have a few methods you can override to add new functionality. The first one we'll override is onLoad. If you're using Visual Studio (I can't say for sure about other IDEs), just typing in "override" and adding a space will give you a list of the methods in the GameWindow class available for overriding. The basic form for this method will be:



protected override void OnLoad(EventArgs e)
{
         base.OnLoad(e);
}



The first thing we'll change is the title of the window. The code for this is incredibly simple:



Title = "Hello OpenTK!";



At the same time, we can also change the background to be something besides plain white. Again, this code is very simple:



GL.ClearColor(Color.CornflowerBlue);


(Note: to add this, you'll need to have using directives for OpenTK.Graphics.OpenGL and System.Drawing)

OpenTK also gives us options to specify a color by its components, but for simplicity's sake I'm using a preset color from the built-in C# Color class.

So now, the OnLoad method should look like this:



protected override void OnLoad(EventArgs e)
{
         base.OnLoad(e);

         Title = "Hello OpenTK!";

         GL.ClearColor(Color.CornflowerBlue);
}




We have to do one more thing before we'll see this color change, though. Add another override for the OnRenderFrame method, with the following code:



protected override void OnRenderFrame(FrameEventArgs e)
{
         base.OnRenderFrame(e);

         GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

         SwapBuffers();
}



We'll be expanding on this later, but right now you can run it again and see our changes:





Now the background is blue, and the title is what we want it to be.

Next, we will add code to handle when the window is resized properly. OpenGL needs to be told how to adjust for the new window size, so we need some code that handles it.



protected override void OnResize(EventArgs e)
{

         base.OnResize(e);

         GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);

         Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);

        GL.MatrixMode(MatrixMode.Projection);

        GL.LoadMatrix(ref projection);
}


This code just tells OpenGL where the window is, and how we want it to draw to it. Right now it's unimportant, but when we're actually drawing something, or making a real game, it'll be very important to handle resizing.

Let's go back to the OnRenderFrame method and actually draw something. The first thing we need to do is to tell OpenGL which direction we're looking at. Because we'll be actually making something in 3D, the direction the camera faces is important.

All the code for the next section will go in OnRenderFrame, before the call to SwapBuffers(). This is because we need to draw something before we can swap the buffers. For the uninitiated, we are working with a "double buffered" setup. When we want to draw to the screen, we first draw to a "buffer", which is later "swapped" to the screen contents. This ensures that everything is drawn on the screen how we want it before the screen is updated.

The following code will have it looking on the plane we'll draw the triangle on.



            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);

            GL.MatrixMode(MatrixMode.Modelview);

            GL.LoadMatrix(ref modelview);

Now we'll want to draw the triangle itself. The first step is to tell OpenGL we want to draw something. We do this with the GL.Begin function. This takes a single parameter, which is the drawing mode to use. There are options to draw quadrilaterals, triangles, points, polygons, and "strips". We'll just be using a triangle, so the code we need is:




            GL.Begin(BeginMode.Triangles);


The BeginMode class has constants for all the different primitive types we have available:


from http://www.opentk.com/doc/chapter/2/opengl/geometry/primitives



Now that we've told it how we want to draw, we need to give it the vertices for our shape. To do this, we use the GL.Vertex3 function. It takes three floats as coordinates for a single point in 3D space.




            GL.Vertex3(-1.0f, -1.0f, 4.0f);

            GL.Vertex3(1.0f, -1.0f, 4.0f);

            GL.Vertex3(0.0f, 1.0f, 4.0f);

After the vertices have been sent to the graphics card, we need to tell OpenGL that we're done drawing:


            GL.End();

If you run this code, you'll see your first triangle:



Let's go a step further. With the GL.Color3 function, we can set the color the triangle is drawn with. You call it before sending the position of the vertex, like this:

            GL.Color3(1.0f, 0.0f, 0.0f); 
            GL.Vertex3(-1.0f, -1.0f, 4.0f);

            GL.Color3(0.0f, 1.0f, 0.0f);
            GL.Vertex3(1.0f, -1.0f, 4.0f);

            GL.Color3(0.0f, 0.0f, 1.0f);
            GL.Vertex3(0.0f, 1.0f, 4.0f);


If you run that, you'll see the following:



Why is it a gradient across the triangle? That's because of the default shaders being used. Shaders allow for many really amazing effects, such as bump-mapping, lighting, phong, etc. The default ones simply interpolate between the values given to them for color and position when they draw the vertices. Shaders are a much more complex feature (even using their own scripting language), so obviously they're possibly content for a future tutorial.

37 comments:

  1. Thanks for this! I am just starting to get into OpenGL and I found the OpenTK website/documentation examples to be somewhat lacking in the explanation department. I appreciate your breakdown and walkthrough of the examples. I would love to see the same thing for the rest of the examples along with corresponding explanations of how/why they work.

    ReplyDelete
    Replies
    1. Thanks!

      Another tutorial should be coming soon, this time explaining how to do this example "the right way", with VBOs and shaders.

      Delete
  2. Thank you so much for this tutorial. At the moment I'm reading the OpenGL programming guide 1.1 the red book and trying to translate the C example into C#. The first example in the book stump me
    int main(int argc, char** argv)
    {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(250, 250);
    glutInitWindowPosition(100, 100);
    glutCreateWindow(“hello”);
    init();
    glutDisplayFunc(display);
    glutMainLoop();
    return 0; /* ISO C requires main to return int. */
    }

    I'm banging my head trying to figure out the OpenTK equivalent to those glut commands!!!

    Thanks for this great tutorial that helped me understand the basic structure of programming with OpenTK.

    It is very hard to find quality OpenTK tutorial right now.

    ReplyDelete
    Replies
    1. That's one of the big annoying parts of trying to convert C++ examples to C# in OpenTK.

      (Some of these are off the top of my head, but they should do the trick)

      glutInit obviously won't be necessary anymore, since OpenTK will handle that itself. glutInitDisplayMode is a bit trickier, though. There's a different base constructor for GameWindow you can call that, in addition to the size, also takes a GraphicsMode.

      glutCreateWindow, glutInitWindowSize and glutInitWindowPosition are handled through the Title, ClientSize and Location properties of the GameWindow class.

      glutDisplayFunc is just setting the same event handler that we override in OnRenderFrame.

      glutMainLoop is basically the same as the Run method in the GameWindow class, since both will start rendering and updating your window.

      Delete
  3. Thanks a lot for this tutorial!

    ReplyDelete
  4. nice tutorial. ty for it. it was a good starting point for me. :)

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Thanks for tutorial. It is exactly what I'm looked for.

    ReplyDelete
  8. Thanks a lot for the tutorial
    Hope you are willing to make more of them

    ReplyDelete
  9. I was able to do everything in your tutorial right up to the GL.ClearColor command in our OnLoad Override method. Everything is exactly as you said verbatim. What's going wrong? One thing I can tell you is that GL doesn't exist in the context when I try to type it in. Same thing goes for Color. Am I missing includes? I have everything you mention above, so I must admit I'm stumped.

    ReplyDelete
    Replies
    1. You most likely need to have using directives (includes) for OpenTK.Graphics.OpenGL and System.Drawing. If you're using Visual Studio 2010 or newer, there may be an option to add them for you that appears after hovering over the error.

      Delete
    2. This error is caused by not calling the System.Drwaing dll in the code, here is what fixed the error for me

      GL.ClearColor(System.Drawing.Color.CornflowerBlue);

      Delete
  10. Hello. Thank you for the tutorial, but I am running into problems. I have added using System.Drawing; yet I am running into problems. Color and Rectangle from the System.Drawing are not being recognized.
    Here is an example error
    'System.Drawing.Rectangle' could be found (are you missing a using directive or an assembly reference?)

    ReplyDelete
    Replies
    1. Hi Darius,

      Please double-check that the System.Drawing.dll reference was added to the project itself (I'd expect a different error from it missing, but it's worth a shot). If that doesn't help, can you please send a copy of your Game.cs file (through pastebin, preferably)?

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

    ReplyDelete
  12. Hi,
    I'm getting warning and there is no triangle in my output window, 'OpenTK.Graphics.OpenGL.GL.Begin(OpenTK.Graphics.OpenGL.BeginMode)' is obsolete: 'Use PrimitiveType overload instead'

    Could you please tell me how to use PrimitiveType

    ReplyDelete
    Replies
    1. Replace
      OpenTK.Graphics.OpenGL.BeginMode.Triangles
      with
      OpenTK.Graphics.OpenGL.PrimitiveType.Triangles

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

    ReplyDelete
  14. I've got to say not only is your tutorial not any better than the opentk documentation(cause you don't exactly explain things all too well, and you don't even provide complete source code for comparison) but as I test your code step by step it doesn't even work.

    ReplyDelete
    Replies
    1. If you're having issues, please make a comment with what you're having an issue with.

      Delete
    2. I've already gotten a running project without issues by following a different tutorial(sadly it's not a series) and I definitely forgot to mention that I definitely appreciate you making these tutorials, I'm simply saying that if you didn't post the code snippets in the sort of format that you do, the tutorials would be far easier to follow, especially by c# and/or openGL newbies. 'course this all is simply my personal opinion.

      and an example of what I'm talking about:

      after having written the:
      using (Game game = new Game())
      {

      game.Run(30.0);

      }

      snippet I was left entirely unsure whether I should add onload inside of it or after it because of you saying the following(wording):
      "
      In the Game class, you have a few methods you can override to add new functionality. The first one we'll override is onLoad.
      "

      Delete
    3. probably should add though, that this was my first time ever of using a override, as far as my memory serves me.

      Delete
  15. So I don't know if you will reply to this or not, being slightly old (I am a necromancer after all :P)

    I am getting an error on Mac saying that System.Drawing.KnownColors is triggering a System.TypeInitalizationException. Is this something to do with my Mac, or the OpenTK install that I have?

    -Thanks
    Anonymous Astronaut

    ReplyDelete
    Replies
    1. Hi,

      It seems to be a Mono issue. Do you have any information about the inner exception(s)? Some searching found http://www.mono-project.com/docs/advanced/pinvoke/dllnotfoundexception/ if it all boils down to a System.DllNotFoundException.

      Delete
    2. Thanks for the reply, I was searching for the wrong thing and you pointed me in the right direction. Thanks again!

      Delete
  16. hi, i get an error. OpenGL4.GL doesn'T contain a defintion for MatrixMode.. what am I doing wrong?

    ReplyDelete
    Replies
    1. Hi,

      You'll need to use OpenTK.Graphics.OpenGL instead of OpenTK.Graphics.OpenGL4.

      Delete
  17. Hello. I am enjoying your tutorial as it is my first time writing any program for OpenTK. I am using Visual Studio Express 2010 C# on a Windows XP machine. I grabbed the binaries and sources for opentk-2010-10-06.exe as the latest and greatest OpenTK crashes on my XP machine. (No surprise there.) I have newer machines but I still enjoy trying to get things to work on this XP machine and older Ubuntu Linux as well. The latest and greatest machines don't often allow you to compile and run old software ... unless you count the latest Java VM that still bytecode compiles and runs a DigSim Java program from 1996.

    But anyways ... I digress. I started typing in your excellent tutorial. The only problem that I had with it so far was that it wasn't rendering anything into the window other than the title bar and border. The interior of the window captured and displayed anything that was behind it such as Visual Studio or Windows Explorer. If I dragged the window around, it would take all of those background graphics with it.

    To solve the issue, I took code from myOpenTK\1.0\Source\Examples\OpenTK\GameWindow\GameWindowSimple.cs and added it to the code from your tutorial that I had typed in. Once I got it drawing correctly, I systematically removed more and more of that code until I got to the EXACT code that made it your window display as you say it should display.

    That was ...
    protected override void OnRenderFrame(FrameEventArgs e)
    {
    this.SwapBuffers();
    }

    So I am working through any odd behaviors that might be due to me working with XP and legacy OpenTK.

    Your tutorial has saved me a huge amount of time trying to get initial success with OpenTK. I am trying to get to where I can figure out why some of the Icarus test programs aren't working and well as learn more about Icarus and some other C# software that uses OpenTK.

    ReplyDelete
  18. I am unable to display the triangle for vertices other than the ones you used. So if I want to display any triangle, what parameters should I tune!!
    I have given the following vertices (for eg)

    GL.Vertex3(-1.0, 3.0, 0.0);
    GL.Vertex3(2.0, 3.0, 0.0);
    GL.Vertex3(2.0, 6.0, 0.0);
    Thanks!!

    ReplyDelete
    Replies
    1. Hi,

      The Z coordinate for each of those vertices needs to be larger (in the tutorial, I used 4.0). The camera in the project is set to have a near clipping plane of 1.0, which means that it cannot see objects closer than that.

      Delete
  19. Hi,

    Quick question.

    Why are the triangle coordiantes in the X axis reversed from that of the normal cartesian system?

    Thanks.

    ReplyDelete
    Replies
    1. Oh, got it.
      Right-hand rule.

      Delete
  20. How can I create GameWindow title in UTF-8 encoding (for example Cyrillic [Russian] alphabeth). Unicode string has identifying and outting as Win-1251.

    ReplyDelete