Friday, March 8, 2013

OpenTK Tutorial 2 - Drawing a Triangle... the Right Way!

The previous tutorial showed us how we can draw a triangle to the screen. However, it came with a disclaimer. Even though it works, it's not the "correct" way to do things anymore. The way we sent our geometry to the GPU was what's known as "immediate mode", which isn't the newest way to do things, even if it is so very nice and simple.

In this tutorial, we'll be going for the same end goal, but we'll be doing things in a way that is more complex, but at the same time more efficient, faster, and more expandable.




We'll be starting out a lot like the previous tutorial, which I'll be referencing a few times, so if you haven't already, take a look at it.

Part 1: Setup

To start, we'll need to make a new project file, with references to OpenTK and System.Drawing, as in the previous tutorial. Name it something like OpenTKTutorial2, for convenience.

Part 2: Coding

First we need to do some of the basics from the first tutorial again.  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 OpenTKTutorial2
{
    class Game: GameWindow
    {
    }
}



Go back to Program.cs, and add the following code to the Main function:

using (Game game = new Game())
{
       game.Run(30,30);
}


This is the same code to make a window and get it to pop up for us (set to update 30 times a second and display at 30 FPS). Now we'll override onLoad again, so we can change the color and title for our window:


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

         Title = "Hello OpenTK!";

         GL.ClearColor(Color.CornflowerBlue);
}


Override onRenderFrame, like before, so it'll display our blue background:


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

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

         SwapBuffers();
}



Now we can get to the new stuff!

What we'll need to do first is create our shaders. Shaders are how modern OpenGL knows how to draw the values it's been given. We'll be working with two kinds of shaders: a vertex shader and a fragment shader. The vertex shader tells the graphics code information about the points in the shape being drawn. The fragment shader determines what color each pixel of the shape will be when it's drawn to the screen. The code we'll be using is very simple, but it'll let us draw similarly to immediate mode. OpenGL's shaders are programmed in a C-like scripting language called GLSL (However, DirectX uses a slightly different language called HLSL).

Add a text file to your project called "vs.glsl". This will store our vertex shader:


#version 330

in vec3 vPosition;
in  vec3 vColor;
out vec4 color;
uniform mat4 modelview;

void
main()
{
    gl_Position = modelview * vec4(vPosition, 1.0);

    color = vec4( vColor, 1.0);
}


(Note: for the shader files, you'll probably need to tell your IDE to copy them to your output directory. Otherwise, the program won't be able to find them!)

The first line tells the linker which version of GLSL is being used.

The "in" lines refer to variables that are different for each vertex. "out" variables are sent to the next part of the graphics pipeline, where they're interpolated to make a smooth transition across fragments. We're sending on the color for each vertex. The "vec3" type refers to a vector with three values, and "vec4" is one with four values.

There's also a "uniform" variable here, which is the same for the entire object being drawn. This will have our transformation matrix, so we can alter the vertices in our object at once. We won't be using this quite yet, but we'll be using it soon.

Our fragment shader is significantly simpler. Save the following as "fs.glsl":


#version 330

in vec4 color;
out vec4 outputColor;

void
main()
{
    outputColor = color;
}

It just takes the color variable from before (note that it's an "in" insead of an "out" now), and sets the output to be that color.

Now that we have these shaders made, we need to tell the graphics card to use them. First, we'll need to tell OpenGL to make a new program object. This will store our shaders together in a usable form. 

First, define a variable for the program's ID (its address), outside the scope of any of the functions. We don't store the program object itself on our end of the code. We only have an address to refer to it by, as it stays on the graphics card.

int pgmID;

Make a new function in the Game class, called initProgram. In this function, we'll start with a call to the GL.CreateProgram() function, which returns the ID for a new program object, which we'll store in pgmID.

void initProgram()
{
            pgmID = GL.CreateProgram();
}

Now we're going to take a short detour to make a function that reads our shaders and adds them.

This function needs to take a filename and some information, and return the address for the shader that was created.

It should look something like this:


void loadShader(String filename,ShaderType type, int program, out int address)
{
            address = GL.CreateShader(type);
            using (StreamReader sr = new StreamReader(filename))
            {
                GL.ShaderSource(address, sr.ReadToEnd());
            }
            GL.CompileShader(address);
            GL.AttachShader(program, address);
            Console.WriteLine(GL.GetShaderInfoLog(address));
}


This creates a new shader (using a value from the ShaderType enum), loads code for it, compiles it, and adds it to our program. It also prints any errors it found to the console, which is really nice for when you make a mistake in a shader (it will also yell at you if you use deprecated code).

Now that we have this, let's add our shaders. First we'll need two more variables in our class:

        int vsID;
        int fsID;

These will store the addresses of our two shaders. Now, we'll want to use the function we made to load our shaders from the files.

Add this code to initProgram:


            loadShader("vs.glsl", ShaderType.VertexShader, pgmID, out vsID);
            loadShader("fs.glsl", ShaderType.FragmentShader, pgmID, out fsID);

Now that the shaders are added, the program needs to be linked. Like C code, the code is first compiled, then linked, so that it goes from human-readable code to the machine language needed.

After the other code add:


            GL.LinkProgram(pgmID);
            Console.WriteLine(GL.GetProgramInfoLog(pgmID));

This will link it, and tell us if we have any errors.

The shaders are now added to our program, but we need to give our program more information before it can use them properly. We have multiple inputs on our vertex shader, so we need to get their addresses to give the shader position and color information for our vertices.

Add this code to the Game class:


        int attribute_vcol;
        int attribute_vpos;
        int uniform_mview;

We're defining three variables here, to store the locations of each variable, for future reference. Later we'll need these values again, so we should keep them handy. (As a side note, I used the name "attribute" because in older versions of GLSL, instead of "in" variables, there were "attributes".) To get the addresses for each variable, we use the GL.GetAttribLocation and GL.GetUniformLocation functions. Each takes the program's ID and the name of the variable in the shader.

At the end of initProgram, add:


            attribute_vpos = GL.GetAttribLocation(pgmID, "vPosition");
            attribute_vcol = GL.GetAttribLocation(pgmID, "vColor");
            uniform_mview = GL.GetUniformLocation(pgmID, "modelview");

            if (attribute_vpos == -1 || attribute_vcol == -1 || uniform_mview == -1)
            {
                Console.WriteLine("Error binding attributes");
            }

This code will get the values we need, and also do a simple check to make sure the attributes were found.

Now our shaders and program are set up, but we need to give them something to draw. To do this, we'll be using a Vertex Buffer Object (VBO). When you use a VBO, first you need to have the graphics card create one, then bind to it and send your information. Then, when the DrawArrays function is called, the information in the buffers will be sent to the shaders and drawn to the screen.

Like with the shaders' variables, we need to store the addresses for future use:


        int vbo_position;
        int vbo_color;
        int vbo_mview;

Creating the buffers is very simple. In initProgram, add:


            GL.GenBuffers(1, out vbo_position);
            GL.GenBuffers(1, out vbo_color);
            GL.GenBuffers(1, out vbo_mview);

This generates 3 separate buffers and stores their addresses in our variables. For multiple buffers like this, there's an option for generating multiple buffers and storing them in an array, but for simplicity's sake, we're keeping them in separate ints.

These buffers are going to need some data next. The positions and colors will all be Vector3 variables, and the model view will be a Matrix4. We need to store these in an array so the data can be sent to the buffers more efficiently.

Add three more variables to the Game class:

        Vector3[] vertdata;
        Vector3[] coldata;
        Matrix4[] mviewdata;

For this example, we'll be setting these values in onLoad, along with a call to initProgram():


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

            initProgram();

            vertdata = new Vector3[] { new Vector3(-0.8f, -0.8f, 0f),
                new Vector3( 0.8f, -0.8f, 0f),
                new Vector3( 0f,  0.8f, 0f)};


            coldata = new Vector3[] { new Vector3(1f, 0f, 0f),
                new Vector3( 0f, 0f, 1f),
                new Vector3( 0f,  1f, 0f)};


            mviewdata = new Matrix4[]{
                Matrix4.Identity
            };



            Title = "Hello OpenTK!";
            GL.ClearColor(Color.CornflowerBlue);
            GL.PointSize(5f);
        }


With the data stored, we can now send it to the buffers. We'll need to add another override, this time for the OnUpdateFrame function. The first thing we need to do is bind to the buffer:


            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);


This tells OpenGL that we'll be using that buffer if we send any data to it. Next, we'll actually send the data:


            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);


This code tells it that we're sending the contents of vertdata, which is (vertdata.Length * Vector3.SizeInBytes) bytes in length, to the buffer. Finally, we'll need to tell it to use this buffer (the last one bound to) for the vPosition variable, which will take 3 floats:

            GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);


         
So, all together for both variables is:


            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);
            GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);



            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_color);
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(coldata.Length * Vector3.SizeInBytes), coldata, BufferUsageHint.StaticDraw);
            GL.VertexAttribPointer(attribute_vcol, 3, VertexAttribPointerType.Float, true, 0, 0);

We'll also need to send the model view matrix, which uses a different function:


            GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]);

Finally, we'll want to clear the buffer binding and set it up to use the program with our shaders:


            GL.UseProgram(pgmID);



            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

We're almost done! Now we have our data, our shaders, and the data being sent to the graphics card, but we still need to draw it. In our OnRenderFrame function, first we'll need to tell it to use the variables we want:


            GL.EnableVertexAttribArray(attribute_vpos);
            GL.EnableVertexAttribArray(attribute_vcol);

Then we tell it how to draw them:


            GL.DrawArrays(PrimitiveType.Triangles, 0, 3);

Then we do a bit to keep things clean:


            GL.DisableVertexAttribArray(attribute_vpos);
            GL.DisableVertexAttribArray(attribute_vcol);

            GL.Flush();

So, all together, this function is:


        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.Viewport(0, 0, Width, Height);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.Enable(EnableCap.DepthTest);<


            GL.EnableVertexAttribArray(attribute_vpos);
            GL.EnableVertexAttribArray(attribute_vcol);

<
            GL.DrawArrays(BeginMode.Triangles, 0, 3);


            GL.DisableVertexAttribArray(attribute_vpos);
            GL.DisableVertexAttribArray(attribute_vcol);


            GL.Flush();
            SwapBuffers();
        }

Now, if you run this, you'll see that after all this work, we're (more or less) back where we were before.













66 comments:

  1. Could you please make one with an interleaved array?

    ReplyDelete
  2. Great tutorial for a beginner trying to get his head around C# and OpenGL. Thanks for taking the time to share your knowledge with us and look forward to further tutorials on using OpenGL.

    ReplyDelete
  3. Am getting an error. Had to change version to 130 for glsl files and add "precision highp float" on top of fs.glsl to get them to compile. Now the program is giving Invalid operation on "GL.UniformMatrix4(uniform_mview, true, ref mviewdata[0]);"

    ReplyDelete
    Replies
    1. Different version have different syntax.

      you need this :-

      varying vec4 color;

      void main()
      {
      gl_FragColor = color;
      }

      and :-

      attribute vec3 vPosition;
      attribute vec3 vColor;
      varying vec4 color;
      uniform mat4 modelview;


      void main()
      {
      gl_Position = modelview * vec4(vPosition, 1.0);

      color = vec4(vColor, 1.0);
      }

      to run properly

      Delete
  4. Can you make an example with 2d Text and 3d stuff?

    ReplyDelete
  5. The program is compiled and runs without errors but there is a problem tho.

    Warning 1
    OpenTK.Graphics.OpenGL.GL.DrawArrays(OpenTK.Graphics.OpenGL.BeginMode, int, int)' is obsolete: 'Use PrimitiveType overload instead.

    When I get the Game Window up, it will not draw the triangle.. What have I missed? I seem to find no solution at this point... Would love the help!

    ReplyDelete
    Replies
    1. My fault. I'm tired and was not following with the tutorial. Just adding though that the error about "StreamReader" is fixed by adding "using System.IO;" at the top. It's a great tutorial!

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

    ReplyDelete
  7. With OpenTK 1.1 in vs2013 on win8.1 I was getting an AccessViolationException following this tutorial. Turns out, when you use Run(30.0) it sets the update rate slower than the render rate. This causes OnRenderFrame to run before OnUpdateFrame so the buffers aren't bound yet. I just ran Run() (sets update/render rates to max) and it fixed my issue. Great tutorial otherwise! Thanks!

    ReplyDelete
    Replies
    1. Thank you, actually. I'm going to be updating all the tutorials to use 1.1 (which should be out very, very soon), so knowing all the bits that'll break in the new version is very nice.

      Delete
    2. This comment has been removed by a blog administrator.

      Delete
  8. I am getting Attempted to read or write protected memory. This is often an indication that other memory is corrupt. Did I copy down the code wrong or am I doing something else wrong?

    public class TriangleAdvanced : GameWindow
    {

    int pgmID;
    int vsID;
    int fsID;
    int attribute_vcol;
    int attribute_vpos;
    int uniform_mview;
    int vbo_position;
    int vbo_color;
    int vbo_mview;
    Vector3[] vertdata;
    Vector3[] coldata;
    Matrix4[] mviewdata;

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

    initProgram();

    vertdata = new Vector3[] { new Vector3(-0.8f, -0.8f, 0f),
    new Vector3( 0.8f, -0.8f, 0f),
    new Vector3( 0f, 0.8f, 0f)};


    coldata = new Vector3[] { new Vector3(1f, 0f, 0f),
    new Vector3( 0f, 0f, 1f),
    new Vector3( 0f, 1f, 0f)};


    mviewdata = new Matrix4[]{
    Matrix4.Identity
    };



    Title = "Hello OpenTK!";
    GL.ClearColor(Color.CornflowerBlue);
    GL.PointSize(5f);
    }

    protected override void OnRenderFrame(FrameEventArgs e)
    {
    base.OnRenderFrame(e);
    GL.Viewport(0, 0, Width, Height);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    GL.Enable(EnableCap.DepthTest);


    GL.EnableVertexAttribArray(attribute_vpos);
    GL.EnableVertexAttribArray(attribute_vcol);


    GL.DrawArrays(PrimitiveType.Triangles, 0, 3);


    GL.DisableVertexAttribArray(attribute_vpos);
    GL.DisableVertexAttribArray(attribute_vcol);


    GL.Flush();
    SwapBuffers();
    }

    protected override void OnUpdateFrame(FrameEventArgs e)
    {
    base.OnUpdateFrame(e);
    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);
    GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);
    GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);



    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_color);
    GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(coldata.Length * Vector3.SizeInBytes), coldata, BufferUsageHint.StaticDraw);
    GL.VertexAttribPointer(attribute_vcol, 3, VertexAttribPointerType.Float, true, 0, 0);
    }

    void initProgram()
    {
    pgmID = GL.CreateProgram();
    loadShader("vs.glsl", ShaderType.VertexShader, pgmID, out vsID);
    loadShader("fs.glsl", ShaderType.FragmentShader, pgmID, out fsID);
    GL.LinkProgram(pgmID);
    Console.WriteLine(GL.GetProgramInfoLog(pgmID));

    attribute_vpos = GL.GetAttribLocation(pgmID, "vPosition");
    attribute_vcol = GL.GetAttribLocation(pgmID, "vColor");
    uniform_mview = GL.GetUniformLocation(pgmID, "modelview");

    if (attribute_vpos == -1 || attribute_vcol == -1 || uniform_mview == -1)
    {
    Console.WriteLine("Error binding attributes");
    }

    GL.GenBuffers(1, out vbo_position);
    GL.GenBuffers(1, out vbo_color);
    GL.GenBuffers(1, out vbo_mview);

    }

    void loadShader(String filename,ShaderType type, int program, out int address)
    {
    address = GL.CreateShader(type);
    using (StreamReader sr = new StreamReader(filename))
    {
    GL.ShaderSource(address, sr.ReadToEnd());
    }
    GL.CompileShader(address);
    GL.AttachShader(program, address);
    Console.WriteLine(GL.GetShaderInfoLog(address));
    }



    }

    ReplyDelete
    Replies
    1. The code is missing a line, "GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]);", in OnUpdateFrame. This alone doesn't seem to fix the mistake, though.

      Can you post your Program.cs file too (preferably to a service like PasteBin so it's more readable)?

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

      Delete
  9. Im trying to understand the "GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);" for 2 days.
    ... i really dont understand why the use of it.
    And the memory aloccation occours where after the bind?
    i tested with the GL.IsBuffer(vbo_position)... and it returns true after the bind.. but why?

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

    ReplyDelete
  11. Sorry, but none of you tutorials seems to work.

    ReplyDelete
    Replies
    1. Can you be more specific about the issues you're having? A lot of other people don't seem to be having any issues with the tutorials.

      Delete
    2. "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

      At pgmID = GL.CreateProgram(); I'm getting above error.

      Could you please post the entire code for the tutorials?

      Delete
    3. I have not had any issues, the tutorial is great. I believe you have a chair and keyboard error. As in the problem lies between the chair and the keyboard.

      Delete
    4. There is no guarantee that the update method will execute / have finished executing before the render method. The memory access exception is caused by OpenGL trying to render data which hasn't been given to it. If you don't have this concurrency issue, changing "game.Run(30,30);" to "game.Run(30);" (you might want to enable vsync) might give it to you.
      You can fix it by adding a boolean variable to the class; setting it to be true at the end of the update method; and only executing the middle 5 lines of the render method if the variable has been set to be true.

      I'm also concerned that were the update method actively changing the data to be drawn that the render method may draw the new triangles with the old colours as there's nothing to ensure that the update is not in an intermediate state before the render occurs. This is worse in later tutorials as it may attempt to render the new vertex data with the old indices.
      All the variables read by the render method that the update method changes need to be bundled up into a struct or class so that the update method can switch the reference to them once it's finished updating - making the change an atomic action. You could use a double buffering like strategy where the update method updates one struct/class, and the render reads from another.

      Delete
    5. Hello Anonymous,

      I haven't seen any issues in the later tutorials, but they haven't been getting that complex. Using a struct to hold the information is definitely a good idea.

      Feel free to email me (neokabuto@gmail.com), or comment here with a link, if you'd like to contribute code (it'll push the changes up in the schedule). I've always intended to do a part of the tutorial for fixing some minor issues and adding some optimizations before things get much bigger.

      Delete
    6. I've been looking into this a little more. Both OnRenderFrame and OnUpdateFrame run in the same thread. The issue occurrs if the OnRenderFrame happens before the first OnUpdateFrame.

      Since it's actually a single thread calling both those methods then you don't need to worry about race conditions - you only need to be sure that the variables the OnRenderFrame accesses are valid when it uses them - making the struct flipping unnecessarry.

      Of course if you moved to having a second thread for loading buffers with its own graphics context sharing objects with the main one, then you'd need to worry about race conditions and communicating groups of variables with atomic actions.

      I'm not sure I'd be qualified to contribute as I'm learning OpenTK. Additionally my code is different to yours as I was taking bits of this tutorial series and others. On that note it might be helpful if you posted zip files (or used github) with the complete project and code at the end of each tutorial so people can see how theirs differs. Though I understand if you're not doing this to encourage people to work through everything.

      Delete
  12. These tutorials are great. However, I am not able to draw the triangle ("Error binding attributes"). I had to chance the GLSL version to 130 as well.

    this is the code: http://pastebin.com/8hNRKfRD

    It seems like the vsID and fsID values are never used, this seems incorrect? Perhaps you would be willing to show some insight into these issues? Thank you :)

    ReplyDelete
    Replies
    1. Hi,

      On line 94, you have GetAttribLocation instead of GetUniformLocation. This seems to be the only issue with the code.

      Delete
  13. Why do you use false as "normalized" parameter for GL.VertexAttribPointer(attribute_vpos,....) and true for GL.VertexAttribPointer(attribute_vcol,...) ? The data types both look the same to me.

    ReplyDelete
  14. gettin an error on line
    GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]);
    invalid operation

    ReplyDelete
    Replies
    1. Update: Problem Fixed by updating graphics driver to latest version
      GTS 360M, Driver Version: 337.88
      OpenTK Version: 1.1

      Delete
  15. I'm getting this same error, but am already running up-to-date graphics drivers: GeForce 337.88. Anyone else have a solution to this problem?

    ReplyDelete
    Replies
    1. Syndog here with an update...

      I went through the tutorial again from scratch, just to make sure I didn't miss anything. I tried running it on my laptop and in a VM on my desktop, still the same Illegal Argument occurs at the call to "GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]);".

      Finally, I tried commenting out any line of code that references the modelview matrix, both in C# and in GLSL. It runs without a hitch now, but I suspect this absent matrix will be a problem as I move on to later tutorials. :(

      Delete
    2. Woops, I meant "Invalid Operation", not "Illegal Argument".

      Delete
  16. Code: http://pastebin.com/ta51kDc4
    Error:An unhandled exception of type 'System.AccessViolationException' occurred in OpenTK.dll

    Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

    Could you kindly tell me what I should do?

    ReplyDelete
    Replies
    1. Double check your code (if you haven't already). Chances are you missed a step. I did, and had the same problems until I reread everything and realized something was missing.

      Delete
    2. Hi Mark,

      Can you please tell me which step you missed? I'd really like to make sure the tutorial is as clear as possible, and that would really help.

      Delete
    3. It's been a while since I last visited your page. I'll have to redo everything to see if I can rediscover what I missed.

      Delete
  17. Great tutorial! I managed to get it to work inside a GLControl instead of GameWindow. Just need to add some code to get it functioning with timed updates.

    ReplyDelete
    Replies
    1. Hi, I'd love to hear how you got this working using GLControl as it seems GLControl is missing the OnUpdateFrame functionality? Any chance you could post how you did these "timed updates?"

      Delete
  18. Aaand you forgot:

    using System.IO;

    which is necessary for the StreamReader

    ReplyDelete
    Replies
    1. and here's my working example for anybody whos still got problems:
      http://pastebin.com/m2jYbEm9

      Delete
  19. Kabuto, thank you for sharing this tutorial. I followed every step but got a blinking image. The only thing I've done differently, besides running the GameWindow in a thread in secondary (extended) device (projector), was to change the "PrimitiveType.Triangles" for "BeginMode.Triangles" because the first was not acessible in "System.Data.Metadata.Edm" namespace. Here's the code: http://pastebin.com/E9NeXARv

    ReplyDelete
    Replies
    1. Hi José,

      The PrimitiveType class for this tutorial is actually in an OpenTK namespace, not System.Data.Metadata.Edm (BeginMode should work fine), but the blinking image is likely caused by the extra SwapBuffers() call in OnRenderFrame().

      Delete
  20. Hi, for those who have problem with AccessViolationException:
    http://pastebin.com/3yDNDTWg
    I've moved buffer binding OnUpdateFrame - i think is working parallel with other part of code and this is why we have problem, I've also moved
    GL.UseProgram(pgmID);
    right after
    GL.LinkProgram(pgmID);
    I'm using OpenTK 1.1.16 and it's working.
    BTW thanks for tutorial!

    ReplyDelete
    Replies
    1. Hi Rafał,

      Thanks for the feedback. I'll work on changing that in the actual tutorial when I get a chance.

      Delete
  21. i would like to do some programming tutorial on blogspot. Unfortunately, I cant make the code appear properly like what you did with your code snippet. How do you make blogspot display your code properly?

    ReplyDelete
    Replies
    1. Hi Syaiful,

      I use SyntaxHighlighter by Alex Gorbatchev: http://alexgorbatchev.com/SyntaxHighlighter/ . It works pretty well, but Blogger itself gives me a few issues. Make sure to use HTML entities for angle brackets and quotes to avoid any issues with Blogger's code validation (if you have a "List", it will try to close it as a "List", which isn't exactly what we'd want).

      Delete
  22. Hi. great job. I use VS2010, with no problem. Thank you. Abdol

    ReplyDelete
  23. My programe throws an AccessViolationException when excuting GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
    I'm not sure what's the problem, please help. Here's my Game Class:

    class Game : GameWindow
    {
    protected override void OnLoad(EventArgs e)
    {
    base.OnLoad(e);

    initProgram();

    vertdata = new Vector3[] { new Vector3(-0.8f, -0.8f, 0f),
    new Vector3( 0.8f, -0.8f, 0f),
    new Vector3( 0f, 0.8f, 0f)};


    coldata = new Vector3[] { new Vector3(1f, 0f, 0f),
    new Vector3( 0f, 0f, 1f),
    new Vector3( 0f, 1f, 0f)};


    mviewdata = new Matrix4[]{
    Matrix4.Identity
    };



    Title = "Hello OpenTK!";
    GL.ClearColor(Color.CornflowerBlue);
    GL.PointSize(5f);
    }

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

    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);
    GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);
    GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);

    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_color);
    GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(coldata.Length * Vector3.SizeInBytes), coldata, BufferUsageHint.StaticDraw);
    GL.VertexAttribPointer(attribute_vcol, 3, VertexAttribPointerType.Float, true, 0, 0);

    GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]);

    GL.UseProgram(pgmID);

    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
    }

    protected override void OnRenderFrame(FrameEventArgs e)
    {
    base.OnRenderFrame(e);
    GL.Viewport(0, 0, Width, Height);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    GL.Enable(EnableCap.DepthTest);


    GL.EnableVertexAttribArray(attribute_vpos);
    GL.EnableVertexAttribArray(attribute_vcol);


    GL.DrawArrays(PrimitiveType.Triangles, 0, 3);


    GL.DisableVertexAttribArray(attribute_vpos);
    GL.DisableVertexAttribArray(attribute_vcol);


    GL.Flush();
    SwapBuffers();
    }

    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);
    }

    int pgmID;
    int vsID;
    int fsID;

    int attribute_vcol;
    int attribute_vpos;
    int uniform_mview;

    int vbo_position;
    int vbo_color;
    int vbo_mview;

    Vector3[] vertdata;
    Vector3[] coldata;
    Matrix4[] mviewdata;

    void initProgram()
    {
    pgmID = GL.CreateProgram();
    loadShader("../../vs.glsl", ShaderType.VertexShader, pgmID, out vsID);
    loadShader("../../fs.glsl", ShaderType.FragmentShader, pgmID, out fsID);
    GL.LinkProgram(pgmID);
    Console.WriteLine(GL.GetProgramInfoLog(pgmID));

    attribute_vpos = GL.GetAttribLocation(pgmID, "vPosition");
    attribute_vcol = GL.GetAttribLocation(pgmID, "vColor");
    uniform_mview = GL.GetUniformLocation(pgmID, "modelview");

    if (attribute_vpos == -1 || attribute_vcol == -1 || uniform_mview == -1)
    {
    Console.WriteLine("Error binding attributes");
    }

    GL.GenBuffers(1, out vbo_position);
    GL.GenBuffers(1, out vbo_color);
    GL.GenBuffers(1, out vbo_mview);
    }

    void loadShader(String filename,ShaderType type, int program, out int address)
    {
    address = GL.CreateShader(type);
    using (StreamReader sr = new StreamReader(filename))
    {
    GL.ShaderSource(address, sr.ReadToEnd());
    }
    GL.CompileShader(address);
    GL.AttachShader(program, address);
    Console.WriteLine(GL.GetShaderInfoLog(address));
    }


    }

    ReplyDelete
    Replies
    1. Hi Steven,

      Can you please post your Program.cs file too (please use something like PasteBin so it doesn't stretch the comment section out too much more)?

      Delete
  24. Thanks you so much Noe for your tutorials, it's really a great job !

    As i encounter some problems to obtain the colored triangle, i give here my discover, may be usefull for someone.

    I use Monodevelop on a MacBookPro and accepted version for vs.glsl and fs.glsl files, seems to be only version 120!

    If someone have the solution to be able to work with version 330, please give it.

    Here is files that work with my configuration :

    vs.glsl file:
    #version 120
    attribute vec3 vPosition;
    attribute vec3 vColor;
    varying vec4 color;
    uniform mat4 modelview;

    void main()
    {
    gl_Position = modelview * vec4(vPosition, 1.0) ;

    color = vec4( vColor, -1.0);
    }


    fs.glsl file:
    #version 120
    varying vec4 color;
    void main()
    {
    gl_FragColor = color;
    }


    ReplyDelete
    Replies
    1. I don't have a Mac to test on, but according to Apple, all MacBook Pros should support at least OpenGL 3.3. This might be an issue in the defaults for OpenTK's GameWindow constructor. Their official documentation seems to imply that it defaults to OpenGL 2.1, but definitely isn't behaving like that for me on Windows.

      Try adding the code in this paste to the Game class: http://pastebin.com/PMdDxGSS . It specifically creates a context with a newer version.

      Delete
    2. Thanks for your answer, but this doesn't resolve the issue.
      As it's not blocking for now, i'll continue to learn with your tutorials.

      Delete
    3. On a MacBook Pro also, using the constructor code from the pastebin above DOES allow glsl above 1.2 to be compiled, but it creates a new error:

      OpenTK.Graphics.GraphicsErrorException

      On this line:

      GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);

      Seems this is a MacOS specific problem. Like the user above, I managed to proceed with your tutorials by modifying the shader code to 110. Until I hit tutorial 8 that is, where I was unable to create a working 110 version. If you could address this that would be amazing! Thanks man

      Delete
  25. It almost draws but I have to comment out GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]); due to "invalid operation" errors

    when the window pops up, there is no triangle in it.

    this is my code:
    http://pastebin.com/DeJZwqGP

    ReplyDelete
    Replies
    1. Hi Aidan,

      I had a similar problem with the triangle not showing. When I uncommented GL.UseProgram(pgID), no triangle showed. When it was commented out, my triangle showed but it looked weird and had no color. In the end I found that moving GL.UseProgram(pgID) before your call to GL.UniformMatrix4() solved the problem. https://www.opengl.org/wiki/GLSL_:_common_mistakes has some extra information on this. Maybe this solves your problem?

      Delete
  26. Added flag to ensure at least 1 update before render and all worked fine.
    Thanks, really appreciate the tutorials.

    ReplyDelete
  27. I am have some errors with the System.IO functon and theres some errors like it cant find "vs.glsl" and its really bugging me and i have tried your code via github and it worked just fine and i dont know why its not working, Thanks for the awesome tutorials and they are just amazing bro GG

    ReplyDelete
    Replies
    1. Did you make sure to set them to copy to the output directory?

      Delete
    2. The code that is generating the error is "using (StreamReader sr = new StreamReader(filename))"

      and do you mean by putting the file directory in the address and the filename like vs.glsl like that?

      Thanks

      Delete
    3. In the Solution Explorer, you should be able to change the properties of the vs.glsl file (you'll want to do it to fs.glsl as well) to set "Copy to Output Directory" to "Copy Always", which will put the shader file in the folder with the executable, allowing it to find it.

      Delete
    4. Thank you!!! it works! <3

      Delete
  28. In the LoadShader funtion on the line
    using (StreamReader sr = new StreamReader(filename))

    I keep getting this error
    An unhandled exception of type 'System.IO.FileNotFoundException' occurred in mscorlib.dll

    Can Anyone Help?

    ReplyDelete
    Replies
    1. Can you check that all of your shaders are set to copy to the output directory?

      Delete
  29. Why do we need shaders if everything worked before?

    ReplyDelete
    Replies
    1. The old "fixed-function pipeline" worked (and works on some really old hardware), and it was a lot easier, but it has some major limitations. Shaders allow for some amazing effects that weren't possible (at least not as efficiently) in the old way.

      The other thing to note is that the old way is officially deprecated, so there's no guarantee that it'll stick around (although it's unlikely to be gone anytime soon, since that would break so much older software).

      Delete
  30. Hi Kabuto, I just want to say first, I love these tuts man. You've helped me so much to understand how OpenTK works. There are just a few problems for me. I've narrowed them down to a few things.

    First, the lines GL.EnableVertexAttribArray(Game.attribute_vpos); and GL.EnableVertexAttribArray(Game.attribute_vcol);, make my textured quads go invisible.

    Second, GL.UseProgram(Game.pgmID); makes my camera fix on one point. I can't move around my camera like I could earlier. I can't give a lot of information for this, because I can't see a lot from the angle I'm stuck to.

    Yes, I'm pretty sure that I've done this right. Followed the tut exactly, please help.

    ReplyDelete