ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Using OpenGL with VB.NET

by Jacek Artymiak
04/28/2003

Although VB.NET is not the first programming language that comes to mind when you are thinking of writing applications that use the OpenGL API, it is possible (and not that difficult), if you are willing to dip your toes in the warm waters of open source software. OpenGL was originally developed by SGI, and is now available in commercial and free incarnations. In case you have never heard of it, OpenGL is an industry standard multi-platform API for 3D graphics supported by virtually all of the major players in the broadly defined field of computer graphics, computer animation, multimedia, and computer games. On Windows 2000/XP you should have a copy in your C:\WINNT\System\system32 directory (look for OPENGL32.DLL). In this article, I will show you how to quickly write a basic OpenGL application in VB.NET using clever open source glue to make it all work together.

Programming Visual Basic .NET

Related Reading

Programming Visual Basic .NET
By Jesse Liberty

Basic Tools

I assume that you are using VB.NET or a full version of the Visual Studio .NET and the .NET Framework SDK.

Next, you will need a copy of the CsGL library, written by the team of developers headed by Lloyd Dupont. CsGL is not another implementation of OpenGL, but a set of wrappers for the OpenGL DLL that glue your C# or VB.NET applications to OpenGL proper. It is written with C# programmers in mind, but it works rather well with VB.NET. The library has a BSD-like license, which means that you can use it in commercial applications, if you obey its (very liberal) terms.

CsGL installation is very simple. Just download the binary distribution (I used version 1.4.1), unpack it with a tool like WinZip, and run the installation script that comes with CsGL. That's it!

Your First Project

Start Visual Studio, and choose File->New->Project. In the New Project dialog, select Visual Basic Application and Windows Application. Click OK, and you should see the default rectangular form of your application. Right-click on the form and choose View Code. You should now see this snippet of code:


Public Class Form1 Inherits System.Windows.Forms.Form

   Windows Form Designer generated code

End Class

Right-click on the References item in the Solution Explorer window and select Add Reference. In the Add Reference dialog, click on the Browse button and locate csgl.dll. Look for it in the same place where you found OPENGL32.DLL, possibly C:\WINNT\System\system32. Select it, click on the Open button in the file dialog, then click on the OK button in the Add Reference dialog. A reference to CsGL has now been added to your project.

Now let's return to the code of your application. Click on the plus (+) sign next to the "Windows Form Designer generated code" label, to expand the skeleton code. You should see this:


Public Class Form1
    Inherits System.Windows.Forms.Form

  #Region " Windows Form Designer generated code "

    Public Sub New()
      MyBase.New()

      'This call is required by the Windows Form Designer.
      InitializeComponent()

      'Add any initialization after the 
      'InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose _
                              (ByVal disposing As Boolean)
      If disposing Then
        If Not (components Is Nothing) Then
          components.Dispose()
        End If
      End If
      MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the 
    'Windows Form Designer It can be modified using the 
    'Windows Form Designer.  Do not modify it using the 
    'code editor.
    <System.Diagnostics.DebuggerStepThrough()> 
    Private Sub InitializeComponent()
      components = New System.ComponentModel.Container()
      Me.Text = "Form1"
    End Sub

  #End Region

End Class

You will now have to modify this skeleton in the following way:

This is accomplished with:


Imports CsGL.OpenGL
Public Class Form1
  Inherits System.Windows.Forms.Form
  Private view As myOpenGL.myView
  Private thrOpenGL

Next, you need to initialize view and thrOpenGL:


#Region " Windows Form Designer generated code "

  Public Sub New()
    MyBase.New()

    'This call is required by the Windows Form Designer.
    InitializeComponent()

    'Add any initialization after the InitializeComponent() call

    Me.view = New myOpenGL.myView()
    Me.view.Parent = Me
    Me.view.Dock = DockStyle.Fill
    Me.thrOpenGL = New Threading.Thread(AddressOf OpenGL_Start)
    Me.thrOpenGL.Start()

  End Sub

#End Region

When thrOpenGL is initialized, it is given a reference to the OpenGL_Start method, which takes care of refreshing the viewport:


Private Sub OpenGL_Start()
  While 1 = 1
    Me.view.Refresh()
  End While
End Sub

Once we have taken care of the start of the OpenGL thread, the next step is adding code that terminates it. This is the job of a modified version of the default Dispose() method.


'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
  If disposing Then
    If Not (components Is Nothing) Then
      components.Dispose()
    End If
  End If
  MyBase.Dispose(disposing)
  Me.thrOpenGL.Abort()
End Sub

As you can see, all that was required was an addition of a call to Me.thrOpenGL.Abort().

Next we need to add a large chunk of code that does the real work. In the myOpenGL namespace, define the class myView, which inherits CsGL.OpenGL.OpenGLControl and overrides a few of its methods, overloads some of them as needed, and defines a keyboard handler.

Namespace and class definition, and inheritance of OpenGLControl, are taken care of by these lines of code:


Namespace myOpenGL
  Public Class myView
    Inherits OpenGLControl

The next step is a bit unusual, because it defines constants that are normally found in the gl.h and glu.h header files. For some reason, I could not access them in the same way as the methods and classes of CsGL, but I found a way around it. I run these commands to create code that can be put directly into the Private Enum GLConstants ... End Enum block:


$ awk '/#define/ { print $2 " = 
                    &H" substr($3, 3) }' gl.h > glconst
$ awk '/#define/ { print $2 " = 
                    &H" substr($3, 3) }' glu.h > gluconst

For this example, the list of constants should look like this:


Private Enum GLConstants
  GL_COLOR_BUFFER_BIT = &H4000
  GL_DEPTH_BUFFER_BIT = &H100
  GL_SMOOTH = &H1D01
  GL_DEPTH_TEST = &HB71
  GL_LEQUAL = &H203
  GL_PERSPECTIVE_CORRECTION_HINT = &HC50
  GL_NICEST = &H1102
  GL_PROJECTION = &H1701
  GL_MODELVIEW = &H1701
  GL_POLYGON = &H9
End Enum

If you don't have an AWK interpreter installed on your machine, you can find it in at least three places: cygwin, unxutils, or Windows Services for UNIX. Some of these packages may contain the gawk interpreter, which is the GNU implementation of AWK that can be used instead of awk. You will also need the gl.h and glu.h header files, which you can get from SGI or from Mesa.

Next we override glDraw(). This is the place where we add code that actually draws something. Readers of this book, will recognize Example 1-1 from the "Red Book." Note how constants are used and when they're converted into UInt32.


Public Overrides Sub glDraw()
  GL.glClearColor(0.0, 0.0, 0.0, 0.0)
  GL.glClear( _
    Convert.ToUInt32( _
      GLConstants.GL_COLOR_BUFFER_BIT Or _
      GLConstants.GL_DEPTH_BUFFER_BIT))
  GL.glLoadIdentity()
  '
  ' Fun begins
  '
  GL.glColor3f(1.0, 1.0, 1.0)
  GL.glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0)
  GL.glBegin(Convert.ToUInt32(GLConstants.GL_POLYGON))
  GL.glVertex3f(0.25, 0.25, 0.0)
  GL.glVertex3f(0.75, 0.25, 0.0)
  GL.glVertex3f(0.75, 0.75, 0.0)
  GL.glVertex3f(0.25, 0.75, 0.0)
  GL.glEnd()
  GL.glFlush()
  '
End Sub

Then we override InitGLContext().


Protected Overrides Sub InitGLContext()
  GL.glShadeModel(Convert.ToUInt32(GLConstants.GL_SMOOTH))
  GL.glClearColor(0.0, 0.0, 0.0, 0.5)
  GL.glClearDepth(1.0)
  GL.glEnable(Convert.ToUInt32(GLConstants.GL_DEPTH_TEST))
  GL.glDepthFunc(Convert.ToUInt32(GLConstants.GL_LEQUAL))
  GL.glHint( _
    Convert.ToUInt32( _
       GLConstants.GL_PERSPECTIVE_CORRECTION_HINT), _
    Convert.ToUInt32(GLConstants.GL_NICEST))
End Sub

If we want to automatically resize the OpenGL viewport, we'll need to overload OnSizeChanged().


Protected Overloads Sub OnSizeChanged(ByVal e)
  Me.OnSizeChanged(e)
  Dim s As Size
  Dim aspect_ratio As Double
  s = Me.Size
  aspect_ratio = s.Width / s.Height
  GL.glMatrixMode(Convert.ToUInt32(GLConstants.GL_PROJECTION))
  GL.glLoadIdentity()
  GL.gluPerspective(45.0, aspect_ratio, 0.1, 100.0)
  GL.glMatrixMode(Convert.ToUInt32(GLConstants.GL_MODELVIEW))
  GL.glLoadIdentity()
End Sub

Finally, we need to add a keyboard event handler. This one watches the Escape key, but you can easily extend it to handle other keystrokes.


Protected Sub myView_OnKeyDown(ByVal Sender As Object, _
       ByVal kea As System.Windows.Forms.KeyEventArgs) _
                 Handles MyBase.KeyDown
  If (kea.KeyCode = Keys.Escape) Then
    Application.Exit()
  End If
End Sub

Well, that's it! Your first VB.NET OpenGL application looks now like this:

Imports CsGL.OpenGL
  Public Class Form1
      Inherits System.Windows.Forms.Form
      Private view As myOpenGL.myView
      Private thrOpenGL

  #Region " Windows Form Designer generated code "

    Public Sub New()
      MyBase.New()

      'This call is required by the Windows Form Designer.
      InitializeComponent()

      'Add any initialization after the InitializeComponent() call

      Me.view = New myOpenGL.myView()
      Me.view.Parent = Me
      Me.view.Dock = DockStyle.Fill
      Me.thrOpenGL = New Threading.Thread(AddressOf OpenGL_Start)
      Me.thrOpenGL.Start()

    End Sub

    Private Sub OpenGL_Start()
      While 1 = 1
        Me.view.Refresh()
      End While
    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose( _
                                 ByVal disposing As Boolean)
      If disposing Then
        If Not (components Is Nothing) Then
          components.Dispose()
        End If
      End If
      MyBase.Dispose(disposing)
      Me.thrOpenGL.Abort()
    End Sub

    'Required by the Windows Form Designer
    Private components As _
                    System.ComponentModel.IContainer

    'NOTE: The following procedure is required 
    'by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
      components = New System.ComponentModel.Container()
      Me.Text = "Form1"
    End Sub

  #End Region
  End Class

Namespace myOpenGL
  Public Class myView
      Inherits OpenGLControl
    Private Enum GLConstants
      GL_COLOR_BUFFER_BIT = &H4000
      GL_DEPTH_BUFFER_BIT = &H100
      GL_SMOOTH = &H1D01
      GL_DEPTH_TEST = &HB71
      GL_LEQUAL = &H203
      GL_PERSPECTIVE_CORRECTION_HINT = &HC50
      GL_NICEST = &H1102
      GL_PROJECTION = &H1701
      GL_MODELVIEW = &H1701
      GL_POLYGON = &H9
    End Enum

    Public Overrides Sub glDraw()
      GL.glClearColor(0.0, 0.0, 0.0, 0.0)
      GL.glClear( _
        Convert.ToUInt32( _
        GLConstants.GL_COLOR_BUFFER_BIT Or _ 
        GLConstants.GL_DEPTH_BUFFER_BIT))
      GL.glLoadIdentity()
      '
      ' Fun begins
      '
      GL.glColor3f(1.0, 1.0, 1.0)
      GL.glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0)
      GL.glBegin(Convert.ToUInt32(GLConstants.GL_POLYGON))
      GL.glVertex3f(0.25, 0.25, 0.0)
      GL.glVertex3f(0.75, 0.25, 0.0)
      GL.glVertex3f(0.75, 0.75, 0.0)
      GL.glVertex3f(0.25, 0.75, 0.0)
      GL.glEnd()
      GL.glFlush()
      '
    End Sub

    Protected Overrides Sub InitGLContext()
      GL.glShadeModel(Convert.ToUInt32(GLConstants.GL_SMOOTH))
      GL.glClearColor(0.0, 0.0, 0.0, 0.5)
      GL.glClearDepth(1.0)
      GL.glEnable(Convert.ToUInt32(GLConstants.GL_DEPTH_TEST))
      GL.glDepthFunc(Convert.ToUInt32(GLConstants.GL_LEQUAL))
      GL.glHint( _
        Convert.ToUInt32( _
          GLConstants.GL_PERSPECTIVE_CORRECTION_HINT), _
          Convert.ToUInt32(GLConstants.GL_NICEST))

    End Sub

    Protected Overloads Sub OnSizeChanged(ByVal e)
      Me.OnSizeChanged(e)
      Dim s As Size
      Dim aspect_ratio As Double
      s = Me.Size
      aspect_ratio = s.Width / s.Height
      GL.glMatrixMode(Convert.ToUInt32(GLConstants.GL_PROJECTION))
      GL.glLoadIdentity()
      GL.gluPerspective(45.0, aspect_ratio, 0.1, 100.0)
      GL.glMatrixMode(Convert.ToUInt32(GLConstants.GL_MODELVIEW))
      GL.glLoadIdentity()
    End Sub

    Protected Sub myView_OnKeyDown(ByVal Sender As Object, _
               ByVal kea As System.Windows.Forms.KeyEventArgs) _
               Handles MyBase.KeyDown
      If (kea.KeyCode = Keys.Escape) Then
        Application.Exit()
      End If
    End Sub
  End Class
End Namespace

Now, all you have to do is build this code (Ctrl+Shift+B) and run it with F5. What you should see is a white rectangle on a black background, as the "Red Book" describes it. For more information about which CsGL method does what and how to override or overload them, see the source of CsGL, available from the project's site.

Essential Resources

The best place to learn about CsGL is the project's site and mailing list. There you will find information about licensing, FAQs, and more.

As for OpenGL itself, you should not venture into writing OpenGL applications without the OpenGL Reference Manual (AKA the "Blue Book") and the OpenGL Programming Guide (AKA the "Red Book"). They are the ABCs of OpenGL. A few more titles are available, but these two should answer most of your questions. There are also quite a few online resources; I strongly recommend the famous NeHe tutorials. Links to more information for developers can be found on the official OpenGL site, in their developer section.

And if you are struggling with VB.NET or NET in general, have a look at O'Reilly's .NET section.

Best of luck hacking your OpenGL code in VB.NET!

Jacek Artymiak started his adventure with computers in 1986 with Sinclair ZX Spectrum. He's been using various commercial and Open Source Unix systems since 1991. Today, Jacek runs devGuide.net, writes and teaches about Open Source software and security, and tries to make things happen.


Return to ONDotnet.com

Copyright © 2009 O'Reilly Media, Inc.