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.
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:
- If you're starting a new project and want the basics there, use the project templates from the Visual Studio Extension.
- 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.
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.
Hit F5 to run the project and see what we get by default.
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:
private void openGLControl_OpenGLInitialized(object sender, EventArgs e)
{
OpenGL gl = openGLControl.OpenGL;
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.
private void openGLControl_OpenGLDraw(object sender, RenderEventArgs e)
{
OpenGL gl = openGLControl.OpenGL;
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
gl.LoadIdentity();
gl.Rotate(rotation, 0.0f, 1.0f, 0.0f);
gl.Begin(OpenGL.GL_TRIANGLES);
gl.Color(1.0f, 0.0f, 0.0f);
gl.Vertex(0.0f, 1.0f, 0.0f);
gl.End();
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)
{
OpenGL gl = openGLControl.OpenGL;
gl.MatrixMode(OpenGL.GL_PROJECTION);
gl.LoadIdentity();
gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);
gl.LookAt(-5, 5, -5, 0, 0, 0, 0, 1, 0);
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:
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).
A much easier way to install Nuget packages is via the Package Manager console. Use the command:
Install-Package SharpGLforWPF
You would use the package 'SharpGLforWinForms' if this was for a WinForms project.
Add an OpenGLControl to the main window XAML:
<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>
<WPF:OpenGLControl />
</DockPanel>
</Grid>
</Window>
Add event handlers for the key events we need to deal with - initialisation, rendering and resizing:
<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)
{
OpenGL gl = openGLControl.OpenGL;
gl.ClearColor(0, 0, 0, 0);
}
Projection
private void OpenGLControl_Resized(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
OpenGL gl = openGLControl.OpenGL;
gl.MatrixMode(OpenGL.GL_PROJECTION);
gl.LoadIdentity();
gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);
gl.LookAt(-5, 5, -5, 0, 0, 0, 0, 1, 0);
gl.MatrixMode(OpenGL.GL_MODELVIEW);
}
Rendering
private void OpenGLControl_OpenGLDraw(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
OpenGL gl = openGLControl.OpenGL;
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
gl.Begin(OpenGL.GL_TRIANGLES);
gl.Color(1.0f, 0.0f, 0.0f);
gl.End();
}
The result is a simple rendered pyramid.
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:
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.