Well this is the most logical place I can think to start talking about the current state of SerenityGL, my OpenGL game engine. In fact, this is why progress has stopped while I rethink the design. Though writing the engine is also an academic exercise, it is primarily being built to make my first game project ‘Zoe’. For every aspect of the design I have tried to research the most elegant and up to date methods of solving the usual game programming issues.
One of the more recent things I discovered is that OpenGL 3 changed everything. I had been hearing words like Vertex Buffer Objects and Vertex Array Objects thrown around in many circles, but to be honest, didn’t really understand it all. I’ll save you the time, in the post OpenGL 3.x specification there is no immediate mode. It is gone… completely. So you can glBegin until your fingers fall off, it won’t do you much good.
Out with the old and in with the new, and a new golden age is beginning… that of the Shader! GLSL now powers almost everything in OpenGL so it’s time to get on board. The deprecated display lists, vertex arrays and glColor function calls can now go and join other culled features – namely the projection and model-view matrices. OpenGL is a busy API and doesn’t have time nor the inclination to deal with your petty matrices any more.
But fear not, this is actually a good thing. The power is back in our hands and with a little help from a library called GLM (OpenGL Mathematics) we are back in business. GLM follows the GLSL specification strictly so you should feel right at home. They mention on their site that it was designed for ease of use and simplicity and not speed. If this is an issue for you then maybe you will need to implement some of your own data structures and math functions. For SerenityGL, however, it fits the bill nicely.
Currently I am refactoring the engine to maintain its own matrices which, down the line, can be passed to shaders and used for node transformations in the scene graph. To start with I wrote a very simple class to help out:
class MatrixStack
{
public:
// glm::mat4 is constructed by default as an identity matrix
MatrixStack() { m_Stack.push( mat4() ); }
~MatrixStack() { }
mat4 & Get() { return m_Stack.top(); }
void LoadIdentity() { m_Stack.top() = mat4(); }
void Push(bool idMatrix = false)
{
if (idMatrix) {
m_Stack.push( mat4() );
} else {
mat4 & lastMatrix = m_Stack.top();
m_Stack.push( mat4(lastMatrix) );
}
}
void Pop()
{
if (m_Stack.size() > 1) // always need a matrix in the stack
m_Stack.pop();
}
private:
stack<mat4> m_Stack;
};
</mat4>
That is the entire class, simple isn’t it. SerenityGL will primarily be using this class for its Model Matrix. The Projection and View Matrix don’t need a stack and are fine just being glm::mat4‘s. Alright so now that we have that out of the way how do we put all the pieces together to handle our own matrix management. Here are the three matrices we will be dealing with:
PROJECTION MATRIX
- This is a 4×4 matrix (internally implemented by OpenGL with a 16 value array that is contiguously laid out in memory) which handles the projection for the scene. This doesn’t change often unless you have a specific need for it and is calculated using
glm::perspective.
MODEL MATRIX
- This one is aptly named as it handles the local transformations for a ‘model’. When you want to transform (scale, rotate, translate) an object you modify its model matrix.
VIEW MATRIX
- This matrix transforms everything in the ‘world‘ so that it is placed relative to the camera position. In OpenGL this used to be combined with the model matrix to become the MODELVIEW matrix but we will be handling it separately.
Before adding features to the engine, the first thing I wonder is “who should own this”. In SerenityGL I am going to place these three matrices in the Human View as it is responsible for rendering the scene and hence the object that needs to know the matrix transformation information.
As discussed the model matrix is going to be implemented with a MatrixStack which is used much like an old OpenGL matrix. I haven’t had much to do with GLSL shaders yet so I am not sure if this plan is going to work out, but if my helper class isn’t required I will be sure to come back and set the record straight. For now though it fits in well with the existing code base I have which has not yet been updated for shaders or post 3.x spec. Finally the projection and view matrix are fine just as a mat4. Here are the declarations:
// GLM Matrix Structures
MatrixStack m_ModelMatrix;
glm::mat4 m_ProjectionMatrix;
glm::mat4 m_ViewMatrix;
Now let’s have a look at how and when each of them is used. The projection matrix is calculated at the creation of the view like this:
HumanView::HumanView(float width, float height)
{
// ...
m_ProjectionMatrix = glm::perspectiveFov(VIEW_DEFAULT_FOV, width, height,
VIEW_DEFAULT_CLIPPING_NEAR, VIEW_DEFAULT_CLIPPING_FAR);
// ...
}
Since we are using our own matrices there will be some additional requirements in our render code to get everything set up. My test view currently just translates the view back a bit so we can see the action happening at the origin. This is the old code:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.f, 0.f, -50.f);
This is what it looks like now using our own matrices:
m_ViewMatrix = glm::translate(glm::mat4(), glm::vec3(0.f, 0.f, -50.f));
Now you could place this in the rendering loop but since it doesn’t change, in this example, it would be a waste of cycles, so for my simple test case I calculate this matrix ahead of time. In reality the camera would likely be moving around and thus would be transformed by a camera controller of some description. The model matrix is handled in the exact same way and can be translated, rotated or scaled to your heart’s content. Once again the model view matrix is probably going to be local to the object that’s using it, meaning that the object will modify it as required.
Ok well so far I haven’t covered VBO/VAOs or GLSL shaders so I am going to leave this post here. Over the next few days I am going to cover some of the simpler facets of SerenityGL such as the Log class, my utility header file and the INI file parser used for loading configuration values. Since every post is prettier with a picture I have scrounged one from the archives for you. Back in 2002, when I was in year 12 we were tasked with making some form of useful application in Visual Basic 6.0. So deathFrogger was born as a fun, not-true-to-original retro game. Thankfully I lost the source code to the annals of time, but I still have the binary!
