Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

OpenGL in .NET - Getting Started

4.90/5 (66 votes)
8 Apr 2014CPOL6 min read 222.5K   9.6K  
Learn how to use OpenGL in your .NET applications with SharpGL.

Introduction

Welcome to the first part of my series on OpenGL in .NET. This series will show you how to use OpenGL in your WinForms and WPF applications. We're going to be using my SharpGL library to use OpenGL from a managed application.

Image 1

What is SharpGL?

At its core, SharpGL is nothing more than a wrapper around the OpenGL API. Typically the OpenGL API is used in C or C++ applications directly, but the setup code can be quite complex - particularly if you need to use modern day extensions to OpenGL. SharpGL offers all of the OpenGL functionality and the extensions directly.

There's actually a few other things that are part of SharpGL - User Controls for WinForms and WPF that provide an OpenGL rendering surface as well as helper classes for working with more complex things like shaders and textures. There's even a scene graph that let's you build a scene in a more object orientated fashion, but these are features we'll see in later articles.

The principle behind SharpGL is this - OpenGL in .NET. If you see an OpenGL tutorial written in C or C++, you can implement it with SharpGL without thinking - because it's just wrapping OpenGL for you. It's not a new framework that you have to learn, with its own set of principles, it's just OpenGL. There are helpers for common tasks such as building shader programs, but these are all 'opt in' features - you can perfectly well stick to vanilla OpenGL.

Getting Started

Enough talk, let's get started! There are two ways to start using SharpGL - either by using a Project Template from Visual Studio Extension or by directly referencing the assembly and dropping an OpenGLControl into your application. I'll describe both, but if you're short on time the gist is this:

  1. If you're starting a new project and want the basics there, use the project templates from the Visual Studio Extension.
  2. If you have an existing project and you want to add OpenGL to it, use the Nuget packages.
We'll start with the first method.

The Visual Studio Extension

The SharpGL Visual Studio Extension adds a WinForms project template and a WPF project template to Visual Studio - very easy and quick.

To install the extension, open Visual Studio and choose 'Tools > Extensions and Updates', then search the Visual Studio Gallery for SharpGL.

Image 2

There's an extension for Visual Studio 2010 as well as Visual Studio 2012. Hit 'Download' and the extension will download and install. If for some reason you cannot find it, just go to github.com/dwmkerr/sharpgl and download the extension from the latest release.

Once the extension is installed, choose 'File > New Project'. You'll see that under the C# templates there are two new project templates - SharpGL Winforms Forms Application and SharpGL WPF Application. For this article I'm picking the Windows Forms template, but the fundamentals are the same in both cases.

Image 3

Hit F5 to run the project and see what we get by default.

Image 4

As you can see we've got a very basic scene - a coloured triangle that rotates and a framerate display. You get three functions implemented in your form's code-behind. Let's check each of them out.

The OpenGLInitialized Event Handler

The first function we've got is an event handler for the OpenGLInitialized event. This event is fired when the underlying OpenGL object is ready to use. The typical use for this event is to provide any setup logic you need. The default implementation is very basic:

C#
private void openGLControl_OpenGLInitialized(object sender, EventArgs e)
{
    //  Get the OpenGL object.
    OpenGL gl = openGLControl.OpenGL;

    //  Set the clear color.
    gl.ClearColor(0, 0, 0, 0);
}

With a reference to the OpenGL object from the OpenGLControl, we can directly call OpenGL methods such as 'glClearColor' to set the background colour of the scene.

The OpenGLDraw Event Handler

Now we can take a look at the handler for the OpenGLDraw event.

C#
private void openGLControl_OpenGLDraw(object sender, RenderEventArgs e)
{
    //  Get the OpenGL object.
    OpenGL gl = openGLControl.OpenGL;

    //  Clear the color and depth buffer.
    gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);

    //  Load the identity matrix.
    gl.LoadIdentity();

    //  Rotate around the Y axis.
    gl.Rotate(rotation, 0.0f, 1.0f, 0.0f);

    //  Draw a coloured pyramid.
    gl.Begin(OpenGL.GL_TRIANGLES);
    gl.Color(1.0f, 0.0f, 0.0f);
    gl.Vertex(0.0f, 1.0f, 0.0f);
    // a few more calls like this omitted for clarity!
    gl.End();

    //  Nudge the rotation.
    rotation += 3.0f;
}

We're seeing the same kind of process here. Get the OpenGL object, then use it as to make 'traditional' OpenGL calls.

The last event handler is a predictable one for anyone who's worked with 3D before - we've done setup and rendering, the last thing is the viewport and projection.

The Resized Event Handler

Called whenever your OpenGLControl is resized, this is when you will typically set up your projection.

private void openGLControl_Resized(object sender, EventArgs e)
{
    //  Get the OpenGL object.
    OpenGL gl = openGLControl.OpenGL;

    //  Set the projection matrix.
    gl.MatrixMode(OpenGL.GL_PROJECTION);

    //  Load the identity.
    gl.LoadIdentity();

    //  Create a perspective transformation.
    gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);

    //  Use the 'look at' helper function to position and aim the camera.
    gl.LookAt(-5, 5, -5, 0, 0, 0, 0, 1, 0);

    //  Set the modelview matrix.
    gl.MatrixMode(OpenGL.GL_MODELVIEW);
}

The only thing that differs here compared to the other functions is that you can see that the 'gluPerspective' function is just called directly on the OpenGL object. This methodology applies to all gl, glu and OpenGL extension functions.

The Nuget Packages

Using the Nuget packages is a quick and easy way to add SharpGL to an existing project. Let's take a simple WPF project and add some OpenGL rendering to it. Here's the existing project:

Image 5

Now we just need to add references to SharpGL and SharpGL.WPF (this would be SharpGL.WinForms for a WinForms app). The easiest way to do this is via Nuget.

Go to 'Tools > Library Package Manager > Manage Nuget Packages for Solution'. Search online for SharpGL and grab the WPF package (or the WinForms package).

Image 6

A much easier way to install Nuget packages is via the Package Manager console. Use the command:

Install-Package SharpGLforWPF 

Image 7

You would use the package 'SharpGLforWinForms' if this was for a WinForms project.

Add an OpenGLControl to the main window XAML:

XML
<Window x:Class="ExistingProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:WPF="clr-namespace:SharpGL.WPF;assembly=SharpGL.WPF" Title="Existing Project">
    <Grid>
        <DockPanel>
            <!-- content omitted for clarity -->
            
            <!-- The main opengl control. -->
            <WPF:OpenGLControl />

        </DockPanel>
    </Grid>
</Window>

Add event handlers for the key events we need to deal with - initialisation, rendering and resizing:

XML
<!-- The main opengl control. -->
<WPF:OpenGLControl 
  x:Name="openGLControl"
  OpenGLInitialized="OpenGLControl_OpenGLInitialized"  
  Resized="OpenGLControl_Resized" 
  OpenGLDraw="OpenGLControl_OpenGLDraw" /> 

We can create some basic initialisation code to make the screen black, use a perspective transformation and render a triangle as an example:

Initialisation

private void OpenGLControl_OpenGLInitialized(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
    //  Get the OpenGL object.
    OpenGL gl = openGLControl.OpenGL;

    //  Set the clear color.
    gl.ClearColor(0, 0, 0, 0);
}

Projection

private void OpenGLControl_Resized(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
    //  Get the OpenGL object.
    OpenGL gl = openGLControl.OpenGL;

    //  Set the projection matrix.
    gl.MatrixMode(OpenGL.GL_PROJECTION);

    //  Load the identity.
    gl.LoadIdentity();

    //  Create a perspective transformation.
    gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);

    //  Use the 'look at' helper function to position and aim the camera.
    gl.LookAt(-5, 5, -5, 0, 0, 0, 0, 1, 0);

    //  Set the modelview matrix.
    gl.MatrixMode(OpenGL.GL_MODELVIEW);
}

Rendering

C#
private void OpenGLControl_OpenGLDraw(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
    //  Get the OpenGL object.
    OpenGL gl = openGLControl.OpenGL;

    //  Clear the color and depth buffer.
    gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
            
    //  Draw a coloured pyramid.
    gl.Begin(OpenGL.GL_TRIANGLES);
    gl.Color(1.0f, 0.0f, 0.0f);
    // etc!
    gl.End();
} 

The result is a simple rendered pyramid.

Image 8

That's all there is to it!

Why use SharpGL?

You can call C API functions from managed code easily, pinvoke makes it a breeze. But you have to create all of the signatures for the functions. If you get it wrong it tends to go spectacularly wrong, with access violations and exceptions that are very difficult to analyse.

There are a lot of functions in the OpenGL API. Wrapping these functions takes time and can be error prone, so that's one of the reasons I made SharpGL - I was spending the time doing it and hoped others could use it.

Another reason that SharpGL can be of use over standard pinvoke is that the vast majority of OpenGL functions you can't pinvoke to - at least not by creating external method signatures. OpenGL extension functions have to be loaded dynamically at runtime - there is not a fixed entry point into a fixed DLL to get at them. This adds even more work.

On top of that, getting a Rendering Context set up can be hard - there's lots of low level Win32 code to mess around with to get pixel formats and so on working - this is repetitive and it's a pain so SharpGL does it for you.

The final reason for SharpGL is that there's lots of stuff that you typically have to do in OpenGL applications which can be a pain - loading images and so on. This is easy in .NET, so OpenGL applications in .NET tend to be a little leaner - there's less extra code for standard tasks like file management.

What's Next?

Almost everything we've used from the OpenGL API in this tutorial is actually deprecated. What we've used is 'immediate mode' functionality, where every frame we pass data to OpenGL. In the next article, I'll show you how to do 'Modern OpenGL'. We'll use Pixel Buffers for our geometry and Shaders for our rendering.

Here's a quick taster of how things look with more modern techniques:

Image 9

This is from the Cel-Shading sample which you can find at github.com/dwmkerr/sharpgl, originally written in C++ from The Little Grasshopper. In the next part we'll see how to do this with SharpGL.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)