Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / WPF

Using OpenGL in a WPF Application

Rate me:
Please Sign up or sign in to vote.
4.90/5 (23 votes)
9 Oct 2011CPOL6 min read 216.3K   13.2K   57   51
Use OpenGL in a WPF application with ease!
MainWindow-Final.png

Introduction

In this article, I am going to show you the steps of how to use OpenGL to render directly into a WPF control - without any kind of fudging of window handles or WinFormsHost objects.

The first thing we'll do is create a project that does some OpenGL rendering. Following that, I'll describe the internals of how this is actually done under the hood - so if you just want to get cracking with OpenGL, you only need to read the first part of the article.

Beta Note: This article uses the SharpGL 2.0 Beta 1 release - it's a beta release so may be changed slightly by the time the full release is available.

Part 1: OpenGL Rendering in WPF

This is going to be really straightforward - the first thing to do is grab the latest version of SharpGL. SharpGL is a CLR wrapper to the OpenGL library - it supports hardware acceleration and has all core functions and extensions all the way up to the latest version of OpenGL, OpenGL 4.2.

Get the core binaries from the CodePlex downloads page. Here's the download page, you'll need the core binaries:

Or if you prefer, the core binaries can be downloaded from the link at the top of the article.

Getting Started

Create a new WPF application, called WPFOpenGL. Now add the SharpGL and SharpGL.WPF assemblies that you have downloaded as references.

SharpGL.dll contains the core OpenGL functionality. SharpGL.WPF contains a control specifically designed for your WPF applications. There is also a SharpGL.WinForms assembly that has a similar control for Windows Forms applications.

Using the OpenGLControl

At the top of the MainWindow.xaml file, add a reference to the SharpGL.WPF assembly:

XML
<Window x:Class="WPFOpenGL.MainWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:sharpGL="clr-namespace:SharpGL.WPF;assembly=SharpGL.WPF"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        
    </Grid>
</Window>  

All we're going to do now is add an OpenGL control as a child of the grid:

XML
<Grid>
    <sharpGL:OpenGLControl />
</Grid>   

Now try running the application - you'll get at least one error complaining about a missing reference to System.Design. You must make sure you re-target your application to the .NET Framework 4.0 NOT the .NET Framework 4.0 Client Profile:

TargetFramework.png

Once you have re-targeted the application, it will run up fine, but not show anything!

MainWindow-Empty.png

That's because we haven't actually done any rendering. Let's move onto that now.

Rendering with OpenGL

Go to the XAML that defines the OpenGL control, type in OpenGLDraw and press tab twice - this'll create the OpenGL Draw function:

XML
<sharpGL:OpenGLControl OpenGLDraw="OpenGLControl_OpenGLDraw" /> 
C#
private void OpenGLControl_OpenGLDraw(object sender, SharpGL.OpenGLEventArgs args)
{
} 

The following section of code looks a bit large, but is fairly basic - we're just providing the geometry and colours for a pyramid and a cube:

C#
private void OpenGLControl_OpenGLDraw(object sender, SharpGL.OpenGLEventArgs args)
{
    //  Get the OpenGL instance that's been passed to us.
    OpenGL gl = args.OpenGL;

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

    //  Reset the modelview matrix.
    gl.LoadIdentity();         

    //  Move the geometry into a fairly central position.
    gl.Translate(-1.5f, 0.0f, -6.0f);

    //  Draw a pyramid. First, rotate the modelview matrix.
    gl.Rotate(rotatePyramid, 0.0f, 1.0f, 0.0f);

    //  Start drawing triangles.
    gl.Begin(OpenGL.GL_TRIANGLES);

        gl.Color(1.0f, 0.0f, 0.0f);        
        gl.Vertex(0.0f, 1.0f, 0.0f);    
        gl.Color(0.0f, 1.0f, 0.0f);        
        gl.Vertex(-1.0f, -1.0f, 1.0f);    
        gl.Color(0.0f, 0.0f, 1.0f);        
        gl.Vertex(1.0f, -1.0f, 1.0f);    

        gl.Color(1.0f, 0.0f, 0.0f);        
        gl.Vertex(0.0f, 1.0f, 0.0f);    
        gl.Color(0.0f, 0.0f, 1.0f);        
        gl.Vertex(1.0f, -1.0f, 1.0f);    
        gl.Color(0.0f, 1.0f, 0.0f);        
        gl.Vertex(1.0f, -1.0f, -1.0f);    

        gl.Color(1.0f, 0.0f, 0.0f);        
        gl.Vertex(0.0f, 1.0f, 0.0f);    
        gl.Color(0.0f, 1.0f, 0.0f);        
        gl.Vertex(1.0f, -1.0f, -1.0f);    
        gl.Color(0.0f, 0.0f, 1.0f);        
        gl.Vertex(-1.0f, -1.0f, -1.0f);    

        gl.Color(1.0f, 0.0f, 0.0f);        
        gl.Vertex(0.0f, 1.0f, 0.0f);    
        gl.Color(0.0f, 0.0f, 1.0f);        
        gl.Vertex(-1.0f, -1.0f, -1.0f);    
        gl.Color(0.0f, 1.0f, 0.0f);        
        gl.Vertex(-1.0f, -1.0f, 1.0f);    

    gl.End();                        

    //  Reset the modelview.
    gl.LoadIdentity();

    //  Move into a more central position.
    gl.Translate(1.5f, 0.0f, -7.0f);

    //  Rotate the cube.
    gl.Rotate(rquad, 1.0f, 1.0f, 1.0f);

    //  Provide the cube colors and geometry.
    gl.Begin(OpenGL.GL_QUADS);            

        gl.Color(0.0f, 1.0f, 0.0f);            
        gl.Vertex(1.0f, 1.0f, -1.0f);        
        gl.Vertex(-1.0f, 1.0f, -1.0f);        
        gl.Vertex(-1.0f, 1.0f, 1.0f);        
        gl.Vertex(1.0f, 1.0f, 1.0f);        

        gl.Color(1.0f, 0.5f, 0.0f);            
        gl.Vertex(1.0f, -1.0f, 1.0f);        
        gl.Vertex(-1.0f, -1.0f, 1.0f);        
        gl.Vertex(-1.0f, -1.0f, -1.0f);        
        gl.Vertex(1.0f, -1.0f, -1.0f);        

        gl.Color(1.0f, 0.0f, 0.0f);            
        gl.Vertex(1.0f, 1.0f, 1.0f);        
        gl.Vertex(-1.0f, 1.0f, 1.0f);        
        gl.Vertex(-1.0f, -1.0f, 1.0f);        
        gl.Vertex(1.0f, -1.0f, 1.0f);        

        gl.Color(1.0f, 1.0f, 0.0f);            
        gl.Vertex(1.0f, -1.0f, -1.0f);        
        gl.Vertex(-1.0f, -1.0f, -1.0f);        
        gl.Vertex(-1.0f, 1.0f, -1.0f);        
        gl.Vertex(1.0f, 1.0f, -1.0f);        

        gl.Color(0.0f, 0.0f, 1.0f);            
        gl.Vertex(-1.0f, 1.0f, 1.0f);        
        gl.Vertex(-1.0f, 1.0f, -1.0f);        
        gl.Vertex(-1.0f, -1.0f, -1.0f);        
        gl.Vertex(-1.0f, -1.0f, 1.0f);        

        gl.Color(1.0f, 0.0f, 1.0f);            
        gl.Vertex(1.0f, 1.0f, -1.0f);        
        gl.Vertex(1.0f, 1.0f, 1.0f);        
        gl.Vertex(1.0f, -1.0f, 1.0f);        
        gl.Vertex(1.0f, -1.0f, -1.0f);        

    gl.End();

    //  Flush OpenGL.
    gl.Flush();

    //  Rotate the geometry a bit.
    rotatePyramid += 3.0f;
    rquad -= 3.0f;
}

float rotatePyramid = 0;
float rquad = 0; 

Hit F5 - let's see what we've got:

MainWindow-DepthProblems.png

Well, we've got the rotating pyramid and cube, but it is very apparent that we have some issues - the faces of each model are being drawn in the order they are defined and overwriting each other - the depth buffer isn't working!

Well, just as we have an event for doing OpenGL drawing, there's one for doing OpenGL initialisation. Here's how we do some initialisation of OpenGL first. Handle the OpenGLInitialized event of the OpenGL control:

XML
<sharpGL:OpenGLControl OpenGLDraw="OpenGLControl_OpenGLDraw" 
	OpenGLInitialized="OpenGLControl_OpenGLInitialized" />  

We get the function below in the code-behind - now just enable the depth test functionality:

C#
private void OpenGLControl_OpenGLInitialized(object sender, OpenGLEventArgs args)
{
    //  Enable the OpenGL depth testing functionality.
    args.OpenGL.Enable(OpenGL.GL_DEPTH_TEST);
} 

Bingo! As a bit of a performance check, we can add 'DrawFPS="True"' to our OpenGLControl XAML to see the frame rate:

MainWindow-Final.png

Just be aware, the Draw Time is correct, the FPS is what could be used with such a draw time, not what is actually being used. The default FPS is 28, but there is a FrameRate property of the OpenGLControl that you can set to whatever you want.

Projections

The OpenGLControl will by default create a basic perspective transformation for the projection matrix, however in any real world app, you'll want to do your own. Do perspective transformations in the Resized event of the control, as below:

XML
<sharpGL:OpenGLControl 
    OpenGLDraw="OpenGLControl_OpenGLDraw" 
    OpenGLInitialized="OpenGLControl_OpenGLInitialized"
    DrawFPS="True" 
    Resized="OpenGLControl_Resized" />     

..and the code behind...

C#
private void OpenGLControl_Resized(object sender, OpenGLEventArgs args)
        {
            // Get the OpenGL instance.
            OpenGL gl = args.OpenGL;
 
            // Load and clear the projection matrix.
            gl.MatrixMode(OpenGL.GL_PROJECTION);
            gl.LoadIdentity();
 
            // Perform a perspective transformation
            gl.Perspective(45.0f, (float)gl.RenderContextProvider.Width / 
				(float)gl.RenderContextProvider.Height,
                0.1f, 100.0f); 

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

The RenderContextProvider is an object used internally to abstract the internals of how an OpenGL Render context is managed. It provides the pixel width and height of the render surface. It is described in more detail in Part 2.

Conclusion

This example shows how to use some simple OpenGL functions to perform some simple rendering. SharpGL 2.0 actually has every major OpenGL extension included and all core functionality up to OpenGL 4.2, so you can do some seriously cool stuff with it.

Part 2: How Does It Work?

Typically when OpenGL drawing is performed, it is rendered against a Native Win32 window handle's device context. In fact, this is essentially required to do any kind of OpenGL drawing.

There is another way - a device context can be created that draws to a DIB (Device Independent Bitmap) which removes the need for a window. We can then directly draw the DIB bits to the WPF control. However, there is a serious limitation to this - drawing to a DIB is never hardware accelerated, it always uses the native OpenGL 1.1 drivers included with Windows. Not only is it not hardware accelerated, it also doesn't support any modern extensions.

So how do we draw to memory (so we can draw to WPF) without a window? The best way is to use an OpenGL Framebuffer object. The OpenGL Framebuffer is an extension that allows drawing to occur to memory, rather than to a window. It allows for some very advanced features such as rendering the depth components of a scene directly to a texture, but will also allow us to render without a window.

Really without a window? No. To create an instance of OpenGL with access to extensions (including the framebuffer extension that we require) we STILL need to create the OpenGL render context from a Device Context associated with a double buffered window. So internally SharpGL creates a hidden window, creates a render context from it, and then redirects drawing to a framebuffer. After every frame is drawn, the contents of the framebuffer are drawn to the OpenGLControl - meaning we have an OpenGL control that can be hardware accelerated, support extensions and DOESN'T suffer from airspace issues associated with just dropping in a WinFormsHost!

The Render Context Provider

This is some fairly complicated logic (creating the framebuffer, etc.), the purpose of which is just to create an OpenGL render context. SharpGL does in fact support rendering to a DIB, or a Native Window, or even to a Hidden Window (which in Windows XP can then be blitted to the screen). As each way of rendering is different, we have the concept of a RenderContextProvider - an object that will handle the internals of creating, resizing and cleaning up an OpenGL render context and its supporting objects. This is why in the earlier example, we used the RenderContextProvider property of the OpenGL object to get the pixel width and height.

Further Reading

If you're interested in finding out more about SharpGL, here are some useful links:

History

  • 9th October, 2011: Initial post

License

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


Written By
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

Comments and Discussions

 
AnswerRe: public void DrawText3D(string faceName, float fontSize, float deviation, float extrusion, string text); Pin
Dave Kerr27-Feb-13 21:24
mentorDave Kerr27-Feb-13 21:24 
GeneralRe: public void DrawText3D(string faceName, float fontSize, float deviation, float extrusion, string text); Pin
yangyangdaren8-Mar-13 2:10
yangyangdaren8-Mar-13 2:10 
GeneralRe: public void DrawText3D(string faceName, float fontSize, float deviation, float extrusion, string text); Pin
Dave Kerr8-Mar-13 2:42
mentorDave Kerr8-Mar-13 2:42 
QuestionHow to use funcitons in "glut.h"? Pin
Meijie.Lv20-Oct-12 22:37
Meijie.Lv20-Oct-12 22:37 
AnswerRe: How to use funcitons in "glut.h"? Pin
Dave Kerr3-Nov-12 23:02
mentorDave Kerr3-Nov-12 23:02 
GeneralMy 5 Pin
Amir Mahfoozi29-Sep-12 20:36
Amir Mahfoozi29-Sep-12 20:36 
GeneralRe: My 5 Pin
Dave Kerr29-Sep-12 23:06
mentorDave Kerr29-Sep-12 23:06 
Questionhow to open stencil buffer in the SharpGL Pin
hzfangbo29-Aug-12 20:43
hzfangbo29-Aug-12 20:43 
GeneralMy vote of 5 Pin
Madhan Mohan Reddy P6-Jun-12 1:19
professionalMadhan Mohan Reddy P6-Jun-12 1:19 
QuestionIntPtr qobj Pin
Member 191519325-Mar-12 9:11
Member 191519325-Mar-12 9:11 
QuestionGreat article! Pin
Member 84577954-Dec-11 0:56
Member 84577954-Dec-11 0:56 
AnswerRe: Great article! Pin
Dave Kerr4-Dec-11 20:06
mentorDave Kerr4-Dec-11 20:06 
GeneralMy vote of 5 Pin
VHGN16-Oct-11 23:58
VHGN16-Oct-11 23:58 
GeneralRe: My vote of 5 Pin
Dave Kerr21-Oct-11 7:12
mentorDave Kerr21-Oct-11 7:12 
GeneralMy vote of 5 Pin
Poiuy Terry10-Oct-11 1:09
Poiuy Terry10-Oct-11 1:09 
GeneralRe: My vote of 5 Pin
Dave Kerr10-Oct-11 5:50
mentorDave Kerr10-Oct-11 5:50 
GeneralRe: My vote of 5 Pin
Poiuy Terry10-Oct-11 6:21
Poiuy Terry10-Oct-11 6:21 
GeneralRe: My vote of 5 Pin
DaveyGun6-Jan-12 8:40
DaveyGun6-Jan-12 8:40 
GeneralRe: My vote of 5 Pin
Dave Kerr6-Jan-12 10:39
mentorDave Kerr6-Jan-12 10:39 
GeneralRe: My vote of 5 Pin
DaveyGun10-Jan-12 8:07
DaveyGun10-Jan-12 8:07 
GeneralMy vote of 5 Pin
Paul Conrad9-Oct-11 11:53
professionalPaul Conrad9-Oct-11 11:53 
GeneralRe: My vote of 5 Pin
Dave Kerr9-Oct-11 22:20
mentorDave Kerr9-Oct-11 22:20 
GeneralMy vote of 5 Pin
softramesh9-Oct-11 8:44
softramesh9-Oct-11 8:44 
GeneralRe: My vote of 5 Pin
Dave Kerr9-Oct-11 11:30
mentorDave Kerr9-Oct-11 11:30 
GeneralMy vote of 5 Pin
Sergio Andrés Gutiérrez Rojas9-Oct-11 8:19
Sergio Andrés Gutiérrez Rojas9-Oct-11 8:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.