Experimental book pages. Create them here and move them to real book when done.

:)

/

/

/

/

A New User's Blog

[The following are the author's opinions and are not official OpenTK documentation.]

Why Are You Here?

OpenTK is all about C# and OpenGL. If you are doing C++ or Java you may want to try elsewhere. C# is a managed language and OpenTK works with the other managed languages too. But this is mainly about C#.

You want OpenGL for 3d graphics. You want the software to be

If I'm not mistaken, your only choice is OpenGL.

There could be other libraries that bind OpenGL to C# (actually there are). OpenTK is attractive because it is pretty well integrated into the managed programming language paradigm. It is also very nice that it includes OpenAL giving you access to the sound buffers. (Apparently DirectSound is deprecated, making OpenAL an even better choice.)

OK, let's get going.

Installation

To get started with OpenTK you are probably going to need an IDE (Intergrated Development Environment) which serves as an editor and keeps track of include files (or assemblies as they're called). Essentially this will be MonoDevelop or Visual Studio. There is more information in the official documentation. I am using Visual Studio and doing cross platform testing with Ubuntu.

To make sure things are working right, try this

using System;
using System.IO;
 
class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("Hello World!");
    Console.WriteLine("This is a C# console application.");
  }
}

On Windows the executable HelloWorld.exe runs immediately by double clicking on the file. On Ubuntu you must execute using

mono HelloWorld.exe

This program should run without recompilation on any platform equipped with Mono or .NET.

You are also probably going to need a GUI. Both Microsoft and Mono offer more than one set of libraries. I am partial to Windows Forms because I'm windows-centric, but GTK is very good and I like it too. Here is a GUI sample

using System;
using System.Windows.Forms;
 
public class HelloWorld : Form
{
  static public void Main()
  {
    Application.Run(new HelloWorld());
  }
 
  public HelloWorld()
  {
    Text = "Hello World";
  }
}

This one ran immediately on Windows. I had to tell Ubuntu to download the Windows Forms package from the Mono depository, then the example worked perfectly.

Now that your IDE is working, you need to install OpenTK. I downloaded a file called opentk-0.9.9-3.zip and expanded it to C:\. Installation very easy. Just tell Visual Studio to use the assembly

C:\opentk-0.9.9-3\Binaries\OpenTK\Release\OpenTK.dll

This is explained in detail in the official documentation.

Now we want to see whether this works at all. Here's the sample code:

using System;
using System.IO;
 
using OpenTK;
 
namespace ConsoleApplication2
{
  class Program
  {
    static void Main(string[] args)
    {
      var devices = OpenTK.DisplayDevice.AvailableDisplays;
      foreach (var device in OpenTK.DisplayDevice.AvailableDisplays)
      {
        Console.WriteLine(device);
      }
    }
  }
}

Yes. It gives the correct output

Primary: {X=0,Y=0,Width=1920,Height=1200}x32@60Hz (41 modes available)
Press any key to continue . . .

You will notice that the official documentation is completely messed up on these function calls. [Fixed now, see note below.] In general the best way to sort these things out is to take a look at the source code which is quite well organized and easy to read. You can find that here

C:\opentk-0.9.9-3\Source\OpenTK

I was lucky and noticed the DisplayDevice class immediately.

Now I will pause here to check this on Ubuntu.

I copied the executable and OpenTK.dll to my Ubuntu system. It worked perfectly. My laptop actually has more video modes than my desktop.

Next step is setting up an OpenGL sample window.

OpenGL Windows

OpenGL is basically a procedural language in an event-driven universe. The old plotter had instructions like move pen, pen down, draw line, and pen up. You would run a computation, draw a picture, then end the program. OpenGL is structured in much the same way, though it is vastly more powerful. A standard instruction might be to draw a colored triangle in proper 3d perspective with some lighting on it. Since OpenGL has this simple structure it is widely portable.

The problem with OpenGL is the implementation details which are generally lumped into the question "How do I create an OpenGL window?" The details include creating a graphics context, handling OS level events like window resizing and resolution and aspect ratio adjustments to the monitors, and providing some sort of clock events for animations. It is the job of OpenTK to hide the dirty implementation details so that any code you write will work the same on all flavors of OS X, Windows and Linux. [As an example of just how difficult this is, the current OpenGL SuperBible 4th edition devotes 4 out of 22 chapters to setting up OpenGL on various platforms.]

Here are the types of OpenGL windows (or graphics contexts) that I would like to be able to create.

  1. A full screen area in which graphics instructions are sent directly to the graphics card for maximum speed.
  2. A rectangular black window (with no frame, title bar, or menus) somewhere on the screen. Using this I could cover the screen with a set of graphics windows all doing different things. Of course I would like to get some rendering speed benefit for the user interface concessions.
  3. A graphics window with standard frame allowing menus, resizing, minimization and so on. The client area would generate OpenGL graphics according to user instructions, for example, like Photoshop.
  4. An OpenGL graphics context inside any Windows control. For example, different OpenGL graphics sequences simultaneously animating on multiple buttons. This is not so exotic and it is likely that OpenTK can already do this beyond your wildest dreams.

I found two basic ways to make windows using OpenTK.

First, you can utilize the Windows Forms libraries to create a window, then insert a graphics context in that window using GLControl. You should also be able to insert the GLControl into basically any control in Windows Forms. This approach is most promising for cases 3 and 4 above.

Second, you can create a window with none of the overhead of Windows Forms (or any other GUI) using GameWindow. GameWindow essentially uses the native window capability of the operating system which is encapsulated in its parent class NativeWindow. To this is added a graphics context using the GraphicsContext class which makes OpenGL calls possible. The GameWindow is quite elaborate and may be more powerful than what you need, but it appears to be simple to use. Using a GameWindow is the best approach to cases 1 and 2 above.

Code for these window types follows.

Full Screen OpenGL Window

Here is the code for a full screen OpenGL window. It uses the powerful GameWindow class. I think the most significant feature is that it does not use Windows Forms. This means you do not need to include the Windows.Forms assembly. It also means that you are responsible for creating everything that appears inside the window using OpenGL.

using System;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
 
class Program
{
 
  static void Main(string[] args)
  {
    using(MyWindow N = new MyWindow())
    {
        N.Run(100.0);
    }
  }
 
}
 
// This class is a powerful window that can be created without using any GUI library.
public class MyWindow : GameWindow
{
  public MyWindow()
    : base()
  {
    Console.WriteLine("Press any key to exit.");
    KeyPress += HandleKeyPressEvent;
    Load += HandleOnLoadEvent;
    RenderFrame += HandleRenderEvent;
  }
 
  public void HandleKeyPressEvent(object sender, KeyPressEventArgs e)
  {
    Exit();
  }
 
  public void HandleOnLoadEvent(object sender, EventArgs e)
  {
    WindowBorder = WindowBorder.Hidden;
    WindowState = WindowState.Fullscreen;
    GL.ClearColor(new Color4(0.1, 0, 0, 1));
  }
 
  public void HandleRenderEvent(object sender, FrameEventArgs e)
  {
    GL.Clear(ClearBufferMask.DepthBufferBit | 
                     ClearBufferMask.ColorBufferBit | 
                     ClearBufferMask.AccumBufferBit | 
                     ClearBufferMask.StencilBufferBit);
    SwapBuffers();
  }
 
}

Since I am coding my projects as console applications in Visual Studio, I had to explicitly include the System.Drawing assembly to get this to compile. It is needed to support the Color4 class (see C:\opentk-0.9.9-3\Source\OpenTK\Graphics\Color4.cs). [see Comment below on this issue]

The main program creates a GameWindow, starts the rendering loop, then finally disposes of any system-dependent (non-managed) baggage before closing.

I used the NativeWindow properties to get rid of the window border and make the window full screen. You'll find the WindowBorder and WindowState classes in C:\opentk-0.9.9-3\Source\OpenTK.

A lot of this code depends on event handling which is a basic, but difficult, feature of C#. You can find out about the available events by looking in C:\opentk-0.9.9-3\Source\OpenTK\GameWindow.cs (Load and RenderFrame) and C:\opentk-0.9.9-3\Source\OpenTK\NativeWindow.cs (KeyPress). Particular attention should be paid to the UpdateFrame event which I didn't need for this example.

The SwapBuffers call is necessary because double buffering is built into the GameWindow class. (BTW, that's a good thing.)

When I ran this on Windows Xp SP3 it worked mostly OK. The background shows up as a dark red. A minor issue was the cursor which seemed to want to remain as an hourglass, not an arrow. I am quite happy that the Winkey is ignored.

This code does not work on Ubuntu. At the moment I'm trying to determine whether the problem resides with the code or with the user.

GL 3.1 tutorial [Work In Progress]

Hello and welcome to this OpenGL 3.1 tutorial. It is designed as a quickstart into "modern" OpenGL. It is not meant as a math or shading tutorial (or even a proper OpenGL tutorial, mind you). Instead I've put together a small codebase that does something you can actually see and work with. Think of it as "hello, cube", a graphic version of "hello, world"
With version 3.0 of OpenGL a new deprecation model has been introduced. According to this deprecation model, the biggest change is the removal of the fixed function pipeline and it's related state. If you're starting off with OpenGL then you shouldn't bother with this deprecated functionality at all. If you've worked with the older GL way of things and you're asking yourself "Why remove it and yet do it manually afterwards?" then the answer is simple: more flexibility and an increased ease of use overall. You'll realise that as your project gets bigger.
If you want to see this example running, launch OpenTK's examples browser and run the OpenGL 3.0 example (not available on 0.9.9.0 and older).
Ok, enough talking let's get started.
To compile and run this tutorial you will need (the latest version of/version 0.9.9.0 of) the OpenTK library. To keep things easy we will use the GameWindow class which takes care of proper context and window creation, provides render and resize events to hook on and so forth.

using System;
using System.Diagnostics;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Math;
 
namespace OpenGL3
{
    public class HelloGL3: GameWindow
    {

1. Shaders
As stated above, OpenGL no longer provides a default "you needn't mess with" algorithm for processing vertices and fragments. Instead, we have to take care of these computations.
http://en.wikipedia.org/wiki/Graphics_pipeline
http://en.wikipedia.org/wiki/GLSL
1.1 Vertex Shader
The vertex shader operates on every vertex and it's responsible for processing vertex positions, normals, texture coordinates and so forth. This processing includes: model transformations (translation, rotating, scaling and shearing of the 3D model), per-vertex lighting (determining what and how much light reaches the vertex) and viewing transformations (transforming the vertices into camera space). Per-vertex computed data is usually interpolated through the primitive and can be accessed by the fragment shader for per-pixel computations in a later stage of the pipeline.

        string vertexShaderSource = @"
            #version 140
 
            // object space to camera space transformation
            uniform mat4 modelview_matrix;            
 
            // camera space to clip coordinates
            uniform mat4 projection_matrix;
 
 
            // incoming vertex position
            in vec3 vertex_position;
 
            // incoming vertex normal
            in vec3 vertex_normal;
 
            // transformed vertex normal
            out vec3 normal;
 
            void main(void)
            {
              //not a proper transformation if modelview_matrix involves non-uniform scaling
              normal = ( modelview_matrix * vec4( vertex_normal, 0 ) ).xyz;
 
              // transforming the incoming vertex position
              gl_Position = projection_matrix * modelview_matrix * vec4( vertex_position, 1 );
            }";

1.2 Fragment Shader
The fragment shader operates on fragments. It is responsible for coloring the fragments that make up the primitives in the scene. This coloring can take into account fragment position, surface normal, light positions and colors and so on.

        string fragmentShaderSource = @"
            #version 140
 
            precision highp float;
 
            const vec3 ambient = vec3( 0.1, 0.1, 0.1 );
            const vec3 lightVecNormalized = normalize( vec3( 0.5, 0.5, 2 ) );
            const vec3 lightColor = vec3( 1.0, 0.8, 0.2 );
 
            in vec3 normal;
 
            out vec4 out_frag_color;
 
            void main(void)
            {
              float diffuse = clamp( dot( lightVecNormalized, normalize( normal ) ), 0.0, 1.0 );
              out_frag_color = vec4( ambient + diffuse * lightColor, 1.0 );
            }";

Declaring variables for the different OpenGL objects

        int vertexShaderHandle,
            fragmentShaderHandle,
            shaderProgramHandle,
            modelviewMatrixLocation,
            projectionMatrixLocation,
            positionVboHandle,
            normalVboHandle,
            indicesVboHandle;
 
        Matrix4 projectionMatrix, modelviewMatrix;
 
        Vector3[] positionVboData = new Vector3[]{
            new Vector3(-1.0f, -1.0f,  1.0f),
            new Vector3( 1.0f, -1.0f,  1.0f),
            new Vector3( 1.0f,  1.0f,  1.0f),
            new Vector3(-1.0f,  1.0f,  1.0f),
            new Vector3(-1.0f, -1.0f, -1.0f),
            new Vector3( 1.0f, -1.0f, -1.0f), 
            new Vector3( 1.0f,  1.0f, -1.0f),
            new Vector3(-1.0f,  1.0f, -1.0f) };
 
        uint[] indicesVboData = new uint[]{
                // front face
                0, 1, 2, 2, 3, 0,
                // top face
                3, 2, 6, 6, 7, 3,
                // back face
                7, 6, 5, 5, 4, 7,
                // left face
                4, 0, 3, 3, 7, 4,
                // bottom face
                0, 1, 5, 5, 4, 0,
                // right face
                1, 5, 6, 6, 2, 1, };

Now, let's create our window.

        public HelloGL3()
            : base( 640, 480, // width, height
            new GraphicsMode( new ColorFormat( 8, 8, 8, 8 ), 16 ), "OpenGL 3.1 Example", 0,
            DisplayDevice.Default, 3, 1, // use the default display device, request a 3.1 OpenGL context
            GraphicsContextFlags.Debug ) //this will help us track down bugs
        {
        /*}
 
        public override void OnLoad( EventArgs e )
        {
            base.OnLoad(e);*/                        
            CreateShaders(); 
            CreateProgram();
            GL.UseProgram( shaderProgramHandle );
 
            QueryMatrixLocations();
 
            float widthToHeight = ClientSize.Width / ( float )ClientSize.Height;
            SetProjectionMatrix( Matrix4.Perspective( 1.3f, widthToHeight, 1, 20 ) );
 
            SetModelviewMatrix( Matrix4.RotateX( 0.5f ) * Matrix4.CreateTranslation( 0, 0, -4 ) );
 
            LoadVertexPositions();
            LoadVertexNormals();
            LoadIndexer();
 
            // Other state
            GL.Enable( EnableCap.DepthTest );
            GL.ClearColor( 0, 0.1f, 0.4f, 1 );
        }

Before attaching the shaders to the program object we need to compile them.

        private void CreateShaders()
        {
            vertexShaderHandle = GL.CreateShader( ShaderType.VertexShader );
            fragmentShaderHandle = GL.CreateShader( ShaderType.FragmentShader );
 
            GL.ShaderSource( vertexShaderHandle, vertexShaderSource );
            GL.ShaderSource( fragmentShaderHandle, fragmentShaderSource );
 
            GL.CompileShader( vertexShaderHandle );
            GL.CompileShader( fragmentShaderHandle );
        }

This method creates the program and attaches the vertex and fragment shaders. We check for errors after the LinkProgram(...) command.

        private void CreateProgram()
        {
            shaderProgramHandle = GL.CreateProgram();
 
            GL.AttachShader( shaderProgramHandle, vertexShaderHandle );
            GL.AttachShader( shaderProgramHandle, fragmentShaderHandle );
 
            GL.LinkProgram( shaderProgramHandle );
 
            string programInfoLog;
            GL.GetProgramInfoLog( shaderProgramHandle, out programInfoLog );
            Debug.WriteLine( programInfoLog );
        }

In OpenGL 3.1 there is no matrix state. So if we want to do transformations we have to upload the projection and modelview matrices to the shaders ourselves. This can be done using uniform variables. These variables are set on the client side (i.e. our application) and can be read in the shaders. For more information check the "4.3.5 Uniform" chapter of the OpenGL Shading Language specification at khronos registry.
A thorough explanation of the matrices used can be found here.

        private void QueryMatrixLocations()
        {
            projectionMatrixLocation = GL.GetUniformLocation( shaderProgramHandle, "projection_matrix" );
            modelviewMatrixLocation = GL.GetUniformLocation( shaderProgramHandle, "modelview_matrix" );
        }
 
        private void SetModelviewMatrix( Matrix4 matrix )
        {
            modelviewMatrix = matrix;
            GL.UniformMatrix4( modelviewMatrixLocation, false, ref modelviewMatrix );
        }
 
        private void SetProjectionMatrix( Matrix4 matrix )
        {
            projectionMatrix = matrix;
            GL.UniformMatrix4( projectionMatrixLocation, false, ref projectionMatrix );
        }
 
        private void LoadVertexPositions()
        {
            GL.GenBuffers( 1, out positionVboHandle );
            GL.BindBuffer( BufferTarget.ArrayBuffer, positionVboHandle );
            GL.BufferData<Vector3>( BufferTarget.ArrayBuffer,
                new IntPtr( positionVboData.Length * Vector3.SizeInBytes ),
                positionVboData, BufferUsageHint.StaticDraw );
 
            GL.EnableVertexAttribArray( 0 );
            GL.BindAttribLocation( shaderProgramHandle, 0, "vertex_position" );
            GL.VertexAttribPointer( 0, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0 );            
        }
 
        private void LoadVertexNormals()
        {
            GL.GenBuffers( 1, out normalVboHandle );
            GL.BindBuffer( BufferTarget.ArrayBuffer, normalVboHandle );
            GL.BufferData<Vector3>( BufferTarget.ArrayBuffer,
                new IntPtr( positionVboData.Length * Vector3.SizeInBytes ),
                positionVboData, BufferUsageHint.StaticDraw );
 
            GL.EnableVertexAttribArray( 1 );            
            GL.BindAttribLocation( shaderProgramHandle, 1, "vertex_normal" );            
            GL.VertexAttribPointer( 1, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0 );
        }
 
        private void LoadIndexer()
        {
            GL.GenBuffers( 1, out indicesVboHandle );
            GL.BindBuffer( BufferTarget.ElementArrayBuffer, indicesVboHandle );
            GL.BufferData<uint>( BufferTarget.ElementArrayBuffer, 
                new IntPtr( indicesVboData.Length * Vector3.SizeInBytes ),
                indicesVboData, BufferUsageHint.StaticDraw );
        }
 
        protected override void OnUpdateFrame( FrameEventArgs e )
        {
            SetModelviewMatrix( Matrix4.RotateY( ( float )e.Time ) * modelviewMatrix );
 
            if( Keyboard[ OpenTK.Input.Key.Escape ] )
                Exit();
        }
 
        protected override void OnRenderFrame( FrameEventArgs e )
        {
            GL.Viewport( 0, 0, Width, Height );
            GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
 
            GL.DrawElements( BeginMode.Triangles, indicesVboData.Length,
                DrawElementsType.UnsignedInt, IntPtr.Zero );
 
            GL.Flush();
            SwapBuffers();
        }
 
        protected override void OnResize( EventArgs e )
        {
            float widthToHeight = ClientSize.Width / ( float )ClientSize.Height;
            SetProjectionMatrix( Matrix4.Perspective( 1.3f, widthToHeight, 1, 20 ) );
        }
    }
 
    public class Program
    {
        [STAThread]
        public static void Main()
        {
            using( HelloGL3 win = new HelloGL3() )
            {
                string version = GL.GetString( StringName.Version );
                if( version.StartsWith( "3.1" ) ) win.Run();
                else Debug.WriteLine( "Requested OpenGL version not available." );
            }
        }
    }
}