NeHe 1 OpenGL Tutorial

From SDL.NET

Table of contents

Introduction

Written by Paul Aspinall.

The original source for all the NeHe tutorials is at: NeHe Productions (http://nehe.gamedev.net). This is lesson 1, the original can be found at NeHe Lesson 1 (http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=01). The complete source code for both VB.NET and C# is at NeHe_1_OpenGL_Tutorial:Code.


Also, this was put together with Visual Studio 2005. At some point later, I will add SharpDevelop (#D) differences to this tutorial. Although, if you're using #D on a regular basis, chances are, you will know what to do anyway.


There are many, many differences with this first lesson compared to the NeHe lesson, simply because SDL.NET does all the hard work for you. When possible, I will be using NeHe's explanations.

Setup The Project

First of all, you will need to create an EMPTY C# project. If you don't know how to do that, then you should be learning C#, and not OpenGL. The reason for having a blank project is because there is that with other automatically created projects, resources are added that you will simply not use.


Right click on the References folder in the Solution Explorer, and choose Add Reference... Once the NET components are listed, scroll down and first of all, choose:

	System.dll

If you are accessing SDL.NET as dlls then click on Browse and then choose the files:

	SdlDotNet.dll				(version 6.0.0 or higher)


This adds the SDL framework for OpenGL to work with. The next two files to reference are the Tao OpenGL files. These aren't registered, but are distributed with SDL.NET. They are located in the sdldotnet\lib\net-2.0 directory (for those with default installations you need to browse to C:\Program Files\SdlDotNet\lib):

	Tao.OpenGl.dll

Code

Basics

Now that your project references are set up, we can finally start and create a new class. Click on the Project Menu, and choose "Add New Item...". The item dialog box appears, and then choose "Class". Give the item an appropriate name, such as "NeHe001.cs". Now we have a shiny new class to play with.


First of all, add the following lines above the Public Class line so we can access the libraries.

using System;

using SdlDotNet.Core;
using SdlDotNet.Input;
using SdlDotNet.Graphics;
using Tao.OpenGl;

Now we will diverge from NeHe lesson 1, and plough onwards with SDL goodness. Set up the variables and constants that will be needed by the entire program. For neatness, we will be using a #Region to keep all global variables together.

public class NeHe001

	#Region "Variables"


These set the resolution and bit depth of the program:

       //Width of screen
       int width = 640;
       //Height of screen
       int height = 480;
       // Bits per pixel of screen
       int bpp = 16;
       

We are creating a new surface, and as it is heading for the screen, we will call that variable 'screen'. This is our main (and in this case only) surface.

       // Surface to render on
       Surface screen;
	#End Region


To make this an executable, you will need a public static void Main() in every project This is the entry point to start it.

	public static void Main()


Declare a new instance of this class to we can start to use it:

	NeHe001 t = new NeHe001();

Initialize the program:

       t.Reshape();
       t.InitGL();

Run the main game loop:

	Events.Run();


When a new instance of this class is declared, the constructor is automatically called, so from here we can set up everything that we need.

As this is not entirely a strict old fashioned games routine, we need to add the event handlers. At the moment, we will add two that are almost always together: KeyDown and Quit

Events

This section explains how to manually add the event handlers.

In C# it is written like:

Events.Quit += new EventHandler<QuitEventArgs>(this.Quit);)


It is best to be forewarned with the knowledge of both methods. If you look through the other SDL.NET examples, the C# method is always used. This will help you to translate between the two languages when coding.

protected void Initialize()
{
    // Sets keyboard events
    Events.KeyboardDown += new EventHandler<KeyboardEventArgs>(this.KeyDown);
    // Sets the ticker to update OpenGL Context
    Events.Tick += new EventHandler<TickEventArgs>(this.Tick);
    Events.Quit += new EventHandler<QuitEventArgs>(this.Quit);

Set the initial screen to windowed mode, with a frame border.

    screen = Video.SetVideoMode(width, height, true, true);

OpenGL Rendering

The job of the next section of code is to resize the OpenGL scene whenever the window (assuming you are using a Window rather than fullscreen mode) has been resized. Even if you are not able to resize the window (for example, you're in fullscreen mode), this routine will still be called at least once when the program is first run to set up our perspective view. The OpenGL scene will be resized based on the width and height of the window it's being displayed in.

	protected virtual void Reshape(float distance)
       

Reset The Current Viewport to the surface dimensions

	{
           // Reset The Current Viewport
           Gl.glViewport(0, 0, width, height);

The following lines set the screen up for a perspective view. Meaning things in the distance get smaller. This creates a realistic looking scene. The perspective is calculated with a 45 degree viewing angle based on the windows width and height.


The 0.1f, 100.0f is the starting point and ending point for how deep we can draw into the screen.


  • glMatrixMode(GL_PROJECTION) indicates that the next 2 lines of code will affect the projection matrix. The projection matrix is responsible for adding perspective to our scene.
  • glLoadIdentity() is similar to a reset. It restores the selected matrix to it's original state. After glLoadIdentity() has been called we set up our perspective view for the scene.
  • glMatrixMode(GL_MODELVIEW) indicates that any new transformations will affect the modelview matrix. The modelview matrix is where our object information is stored. Lastly we reset the modelview matrix.


Don't worry if you don't understand this stuff, I will be explaining it all in later tutorials. Just know that it HAS to be done if you want a nice perspective scene.

           // Select The Projection Matrix
           Gl.glMatrixMode(Gl.GL_PROJECTION);
           // Reset The Projection Matrix
           Gl.glLoadIdentity();
           // Calculate The Aspect Ratio Of The Window
           Glu.gluPerspective(45.0F, (width / (float)height), 0.1F, distance);
           // Select The Modelview Matrix
           Gl.glMatrixMode(Gl.GL_MODELVIEW);
           // Reset The Modelview Matrix
           Gl.glLoadIdentity();
       }


In the next section of code we do all of the setup for OpenGL. We set what color to clear the screen to, we turn on the depth buffer, enable smooth shading, etc. This routine will not be called until the OpenGL Window has been created.

	protected void InitGL()
       {


The next line enables smooth shading. Smooth shading blends colors nicely across a polygon, and smoothes out lighting. I will explain smooth shading in more detail in another tutorial.

		Gl.glShadeModel(Gl.GL_SMOOTH);


The following line sets the color of the screen when it clears. If you don't know how colors work, I'll quickly explain. The color values range from 0.0f to 1.0f. 0.0f being the darkest and 1.0f being the brightest. The first parameter after glClearColor is the Red Intensity, the second parameter is for Green and the third is for Blue. The closer the number is to 1.0f, the brighter that specific color will be. The last number is an Alpha value. When it comes to clearing the screen, we wont worry about the 4th number. For now leave it at 0.0f. I will explain its use in another tutorial.


You create different colors by mixing the three primary colors for light (red, green, blue). Hope you learned primaries in school. So, if you had glClearColor(0.0f,0.0f,1.0f,0.0f) you would be clearing the screen to a bright blue. If you had glClearColor(0.5f,0.0f,0.0f,0.0f) you would be clearing the screen to a medium red. Not bright (1.0f) and not dark (0.0f). To make a white background, you would set all the colors as high as possible (1.0f). To make a black background you would set all the colors to as low as possible (0.0f).

           // Black Background
	    Gl.glClearColor(0.0F, 0.0F, 0.0F, 0.5F);


The next three lines have to do with the Depth Buffer. Think of the depth buffer as layers into the screen. The depth buffer keeps track of how deep objects are into the screen. We won't really be using the depth buffer in this program, but just about every OpenGL program that draws on the screen in 3D will use the depth buffer. It sorts out which object to draw first so that a square you drew behind a circle doesn't end up on top of the circle. The depth buffer is a very important part of OpenGL.

	    // Depth Buffer Setup
           Gl.glClearDepth(1.0F);
           // Enables Depth Testing
           Gl.glEnable(Gl.GL_DEPTH_TEST);
           // The Type Of Depth Testing To Do
           Gl.glDepthFunc(Gl.GL_LEQUAL);

Next we tell OpenGL we want the best perspective correction to be done. This causes a very tiny performance hit, but makes the perspective view look a bit better.

           // Really Nice Perspective Calculations
           Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);
       }

This section is where all of your drawing code will go. Anything you plan to display on the screen will go in this section of code. Each tutorial after this one will add code to this section of the program. If you already have an understanding of OpenGL, you can try creating basic shapes by adding OpenGL code below glLoadIdentity(). If you're new to OpenGL, wait for my next tutorial. For now all we will do is clear the screen to the color we previously decided on, clear the depth buffer and reset the scene. We wont draw anything yet.

	protected void DrawGLScene()
       {
           // Clear Screen And Depth Buffer
           Gl.glClear((Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT));
           // Reset The Current Modelview Matrix
           Gl.glLoadIdentity();
       }


Note that this next method is the call to SDL.NET to actually draw the surface.

       private void Tick(object sender, TickEventArgs e)
       {
           DrawGLScene();
           Video.GLSwapBuffers();
       }

Input

These next two sections are the event handlers. For the KeyDown event, we scan two possible key presses: Escape and F1.

  • If the Escape key is pressed, then we quit the event loop.
  • If the F1 key is pressed, this toggles between Windowed mode and Fullscreen mode. Note that whenever there is surface change (size, shape, etc) then the Reshape function needs to be called to keep the OpenGL scene in perspective.
       private void KeyDown(object sender, KeyboardEventArgs e)
       {
           switch (e.Key)
           {
               case Key.Escape:
                   // Will stop the app loop
                   Events.QuitApplication();
                   break;
               case Key.F1:
                   // Toggle fullscreen
                   if ((screen.FullScreen))
                   {
                       screen = Video.SetVideoMode(width, height, true, true, true);
                       this.WindowAttributes();
                   }
                   else
                   {
                       screen = Video.SetVideoMode(width, height, true, true);
                   }
                   Reshape();
                   break;
           }
       }

The quit event is called when the application is closed by other methods, such as pressing the X close button on the window, To make sure that the game loop quits, instead of the window just closing down, this has to set the fini varible to false, otherwise, the game loop will continue regardless, and there would be no way of actually stopping the application. In other words, this event HAS to be in.

       private void Quit(object sender, QuitEventArgs e)
       {
           Events.QuitApplication();
       }

Main

This is the final home stretch now. This final section is the game loop that is called from the Main routine after the class has been initialised.

	public static void Main()
       {
           NeHe001 t = new NeHe001();
           t.Reshape();


Initialise OpenGL and set up the window.

           t.InitGL();
           

Here is the famous game loop that we have been talking about. This will keep on going until the Quit event is generated. That is until we either close the window or press the Escape key.

		Events.Run();
           }

Conclusion

And that's it, the tutorial is finally over! In this tutorial I have tried to explain in as much detail, every step involved in setting up, and creating a fullscreen OpenGL program of your own, that will exit when the ESC key is pressed and monitor if the window is active or not.


If you have comments or questions please email me. If you feel I have incorrectly commented something or that the code could be done better in some sections, please let me know. I want to make the best OpenGL/SDL.NET tutorials I can and I'm interested in hearing your feedback.