Friday, June 22, 2012

OpenGL Camera Class Tutorial Part 4: Projection and Modelview Matrices

Introduction

This tutorial will be fairly short, but it is what makes the FreeCamera class work. A lot of the variables I defined in the previous tutorials will be used here to set the OpenGL modelview and projection matrices.

The Projection Matrix

The first method, LoadProjectionMatrix, sets the projection matrix and defines the view volume in OpenGL as the FreeCamera class represents it. It is very simple. It has no arguments and no return value. It simply sets the matrix mode to the projection matrix, loads the identity matrix, and calls the GLFrustum function which multiplies the matrix by another that represents the frustum view volume. I set the matrix mode and load the identity for convenience here, but, if I wanted more control, I could remove the GLMatrixMode and GLLoadIdentity commands. Here is the implementation:
public void LoadProjectionMatrix()
{
  GL.MatrixMode(MatrixMode.Projection);
  GL.LoadIdentity();
  GL.Frustum(m_left, m_right, m_bottom, m_top, m_near, m_far);
}

The Modelview Matrix

The second method, LoadModelviewMatrix, sets the modelview matrix and defines how objects are rendered relative to the camera in OpenGL. For more information about how this works, look at this link about the modelview matrix (I've formatted the code below to look similar to a matrix definition. Because I enter the values in the matrix constructor serially from 1-16, the rows and columns are switched and it looks mirrored down the diagonal compared to the modelview matrix link). The method is also very simple. I define a modelview matrix based on the U, V, and N vectors, set the matrix mode to the modelview matrix, and I load the matrix. Once again, I could remove the command to set the matrix mode if I wanted more control, but I like setting it in this method for convenience. Here is the implementation:
public void LoadModelviewMatrix()
{
Matrix4 m;
  m = new Matrix4(
    U.X, V.X, N.X, 0.0f,
    U.Y, V.Y, N.Y, 0.0f,
    U.Z, V.Z, N.Z, 0.0f,
    -Vector3.Dot(Position, U), -Vector3.Dot(Position, V), -Vector3.Dot(Position, N), 1.0f);
  GL.MatrixMode(MatrixMode.Modelview);
  GL.LoadMatrix(ref m);
}

FreeCamera Example Usage

What's better than having a FreeCamera class? Using it! I have a C# Windows Forms application that I'm working on with OpenTK. I use a series of event handlers to control the OpenGL scene. Here are the event handlers:
Form Load: First, I've declared a FreeCamera instance to be used for all of these methods. This method initializes the OpenGL and camera states. I set some temporary values for things I don't know, but in the SetViewVolume I know I want the near plane distance to be 0.1 and the far plane to be 100 with a perspective of 30 degrees. I also initialize the position to be near, but off of the origin and have it look at the origin.
private FreeCamera freeCamera = new FreeCamera();

private void Form_Load(object sender, EventArgs e)
{
  freeCamera.SetViewVolume(new System.Drawing.Size(800, 600), 0.1f, 100.0f, 30.0f);
  freeCamera.Position = new Vector3(0.5f, 1, 1);
  freeCamera.LookAt(new Vector3(0, 0, 0), new Vector3(0, 1, 0));
}
Application Idle: This method renders the scene. I just clear the color and depth buffers, load the modelview matrix from the FreeCamera, then swap the buffers.

void Application_Idle(object sender, EventArgs e)
{
  GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
  freeCamera.LoadModelViewMatrix();
  //Draw Scene


  //End Draw Scene
  glControl.SwapBuffers();
}
GLControl Resize: This method gets called when the OpenGL control gets resized. I want to reset the viewport when this happens and set the projection matrix due to changes in the size of the screen. It is pretty simple: I set the window size, load the viewport, and then load the projection matrix.
private void glControl_Resize(object sender, EventArgs e)
{
  freeCamera.SetWindowSize(new System.Drawing.Size(glControl.Width, glControl.Height));
  freeCamera.LoadViewport();
  freeCamera.LoadProjectionMatrix();
}
GLControl KeyDown: This is where it gets fun. I've added a handler for when a user presses keys. I have the standard W-A-S-D controls for looking around using the FreeCamera Pitch and Yaw methods, supplemented with Q and E for barrel-rolling using the FreeCamera Roll method.
private void glControl_KeyDown(object sender, KeyEventArgs e)
{
  switch (e.KeyCode)
  {
  case Keys.A:
    freeCamera.Yaw(-1.0f);
    break;
  case Keys.D:
    freeCamera.Yaw(1.0f);
    break;
  case Keys.S:
    freeCamera.Pitch(-1.0f);
    break;
  case Keys.W:
    freeCamera.Pitch(1.0f);
    break;
  case Keys.Q:
    freeCamera.Roll(1.0f);
    break;
  case Keys.E:
    freeCamera.Roll(-1.0f);
    break;
  }
}
GLControl PreviewKeyDown: I would have placed these in the previous method, but C# Windows Forms doesn't call KeyDown when special keys are called that can navigate on the forms, such as the arrow keys. I can still capture those keys with the PreviewKeyDown method, however. I've added moving forward and backward with the Up / Down arrow keys and moving left and right with the Left / Right arrow keys. All of this is accomplished with the FreeCamera.Slide method.
private void glControl_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
  switch (e.KeyCode)
  {
  case Keys.Down:
    freeCamera.Slide(new Vector3(0, 0, 0.05f));
    break;
  case Keys.Up:
    freeCamera.Slide(new Vector3(0, 0, -0.05f));
    break;
  case Keys.Left:
    freeCamera.Slide(new Vector3(-0.05f, 0, 0));
    break;
  case Keys.Right:
    freeCamera.Slide(new Vector3(0.05f, 0, 0));
    break;
  }
}

Conclusion

The methods above provide all the interfaces I need to position and orient a camera in an OpenGL scene in almost any way imaginable. Using the FreeCamera that I've described and built in these tutorials has helped me get over many of the initial hurdles OpenGL throws at beginners (I include myself in that group). Hopefully these tutorials can help get others off the ground and running. I have one more tutorial, and that is an additional utility to the FreeCamera class: getting a pick ray. See the next tutorial: Part 5.