Tech/LxEngine/Tutorials/Tutorial 2

Overview
The objective of this tutorial is to introduce the LxEngine Rasterizer subsystem. The direct OpenGL calls will be replaced with use of the higher-level Rasterizer subsystem - taking less code to get some useful graphics on the screen.

Concepts Introduced

 * Rasterizer subsystem
 * lambda functions

Prerequisites

 * This tutorial assumes the reader is reasonably comfortable with the material in $..$
 * This tutorial assumes a basic familiarity with computer graphics concepts, most regarding basic geometry and lighting concepts in rasterization

Results
If all goes well, the application should produce a red, green, and blue cube spinning around the origin. The tutorial can be exited at any time using the ESC key.



Headers
The first item to take note of is a change in headers. The OpenGL header is no longer included, as no direct OpenGL calls will be made any more. The LxEngine Rasterizer subsystem will be used to handle the low-level rendering instead.

The Rasterizer subsystem is a "low-level, retained mode" rasterizer: namely it allows you to create cached objects representing pieces of geometry, materials, lights, and other rasterization state. A list of these retained objects is then sent every frame to the rasterizer to draw that particular frame. It is still considered "low-level" in that, while it provides mechanisms for multi-pass rendering and global scene rendering algorithms, these must be set up by the caller.

As noted in the previous tutorial, only includes the core functionality of LxEngine. Any optional components which may not be used in every application are included via a separate header file. In this case, two extension components are being used. The Canvas View implementation and now the Rasterizer subsystem.

Changes to Renderer
The entirety of the other changes to the code from the previous tutorial come in the Renderer class!

No other changes have been made. Let's look at the particulars.

Renderer::initialize
As noted in the previous tutorial, the Renderer is derived from and has its virtual method  called once the view context has been set up. In this case, it means once OpenGL is full set up.

This tutorial is going to use the Rasterizer subsystem which is headed up by one primary class:. This class represents the primary rasterization and object creation mechanism.

As such, we add a shared_ptr to a rasterizer object as part of the Renderer class.

However, since the RasterizerGL object is based on OpenGL, it can't be properly initialized until OpenGL itself is set up properly. Therefore, we don't actually create the object until the Renderer:initialize call is made. Let's look at the first couple lines of the Renderer::initialize method:

There's not much to it. The header include introduced the RasterizerGL class into the lx0 namespace and now we create a new object of that class and initialize it once the Renderer is told to initialize itself.

Rasterizer subsystem
Conceptually, RasterizerGL is a fairly low-level rasterizer that takes a list of "items" and draws each one in the order it is given. What's an "item"? An item is a packet of enough geometry, material, and transformation information to properly make a draw call.

In addition to a list of objects to draw, the scene also needs a camera to specify where the scene is being drawn from.

Lastly, a scene usually will also need a set of lights. In this case, however, we're using a solid color shader that does not need any lighting information (it chooses the face colors based solely on the direction of the normal - a useful testing shader).

In summary, to draw a scene, we'll need:
 * A Camera
 * A list of Items, where each has...
 * Geometry
 * Material
 * Transformation

Since the objective of this tutorial is to draw a single spinning cube, the "list" of items is only length one; so only one Item is needed. However, since we want the cube to spin, we'll need some way to track the current transformation and update it. We'll track it using a 4x4 matrix.

To handle the above, we'll add the following member variables to Renderer. An Item has pointers to its geometry, material, and transform, so there's no need to track those in the Renderer class directly. We do choose to track the transformation matrix however, since we'll be updating that continually to make the cube spin. Note that LxEngine uses the OpenGL Mathematics Library (GLM) for its matrix classes.

Creating the Camera
Creating the camera is straightforward if you're familiar with OpenGL. The GLM library is used to compute a "lookAt" view matrix (in the style of gluLookAt). This is then passed into the RasterizerGL object along with a field-of-view and near/far planes to create a camera with both view and projection transformation information.

Now we have a camera specified for the scene.

Building geometry
LxEngine supports loading Blender .blend files directly, but we'll use a lower-level interface in this tutorial for demonstration purposes.

The RasterizerGL interface provides numerous means of creating chunks of geometry, however in this case, we'll simply specify the cube as a list of indexed quads. RasterizerGL will take care of all the OpenGL vertex array object creation and binding internally.

lambda functions
Note in the above example the use of a C++0x lambda function for the  convenience function. LxEngine makes extensive use of lambda functions so it's good to be aware of how they work.

Creating the Item
The Item is the set of geometry, material, and transform. Let's create it.

The transform is created from the rotation matrix (which GLM initializes to identity in the constructor).

The material is specified via a pre-packaged fragmented shader that comes with the LxEngine distribution. (NOTE: This likely should be updated. This is a somewhat confusing and incomplete API.)

The geometry was created in the prior step, so now it's a matter of assigning that variable to the Item. Remember that LxEngine uses shared_ptr<>'s throughout the system: therefore, there's little worry about ownership conventions on most LxEngine objects.

Creation Complete
That wraps up the creation of all the resources needed:
 * The RasterizerGL object
 * The CameraPtr for the scene
 * A single GeometryPtr for the cube geometry
 * An ItemPtr associating the geometry, a material, and a transform together
 * A simple 4x4 matrix to control the rotation of the cube

Rendering
Now we most on to the render function...

This tutorial is a pretty simple example. RasterizerGL is designed to handle multi-pass algorithms with graphics layering, various render state changes and overrides, and other more complicated features than a simple "just draw everything in order" loop. Therefore, to do a simply draw, there are a few extra steps required - but as you'll see in the next tutorial, these objects make it easy to accomplish more advanced effects.

Let's look at the render function and examine piece by piece:

Let's start with this is an object describing how to render the entire frame. In this case, all the defaults are used with the exception that we explicitly set what color to clear the color buffer to at the start of the frame.

A single frame may be composed of multiple passes; in our case, it's only a single pass over the scene. Therefore, we create a single object and add it to the RenderAlgorithm list of passes. The only option we set is the Camera to use for that pass.

Next, we set the list of Items to render in that frame. The first argument of is the layer number. The layers are essentially 2D overlays that are stacked up together and rendered in order - in this case it really doesn't matter since only a single object is being drawn. Using multiple layers can be useful for Heads-Up-Displays and other overlay graphics, but can also be used to partition 3D geometry to create interesting effects.

Next, we start the frame. We loop over each layer and rasterize the list of Items in that layer.

Lastly, we call endScene to mark the end of the frame.

Rotating the Cube
Rotating the cube is trivial. We set up an update implementation and use GLM to incrementally change the rotation applied to the Item. We then send a "redraw" event to force the render method to be re-run.

Conclusion
For those familiar with the basics of OpenGL and DirectX, there shouldn't be much suprising here. The Rasterizer subsystem implements a fairly standard rasterization process. The challenge is merely to get accustomed to API names and conventions to know how to quickly do what is desired.

The next tutorial gets more interesting as the Rasterizer system is used to provide interesting effects rather than simply demonstrating the basic setup of its use.

Appendix A: Feedback, Questions, & Issues
Please provide any feedback, questions, or issues on the forums.

LxEngine is in active development and in need of improvements. Suggestions for making it simpler to use are most welcome!