2D games are a thing of the past. They were the only possible option in times when the processing power of the computers or game consoles was not enough to support real 3D drawing and calculations. Modern systems on the other hand are so powerful that even the not optimized programs perform well. So far we have done some basic OpenGL programming focusing on 2D drawing. Isn't it a good time to step into 3D game programming? It is a new world full of challenges.
In the previous samples we were locking the viewer at a standard position and fixed the view point at the beginning of the axes. This was done with the next code.
glViewport (0, 0, (GLsizei)(width), (GLsizei)(height)); // reset the current viewport glMatrixMode (GL_PROJECTION); // select the projection matrix glLoadIdentity (); // reset the projection matrix gluPerspective (45.0f, (float)(width)/(float)(height), 1.0f, 1000.0f); // calculate the aspect ratio of the window glMatrixMode (GL_MODELVIEW); // select the modelview matrix glClearColor (0.0f, .0f, 0.0f, 1.f); // black background glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear screen and depth buffer glLoadIdentity(); // reset the current modelview matrix< glTranslatef(0,0,-80); // move the drawing surface apart
In order to understand the way OpenGL handles drawing we must talk about some basic things first. Most if not all of the books about OpenGL have a very good but rather technical description of what is going on when you draw in three dimensions. I do not want to add this article to this long list. I would rather like to give you some simple guidelines to help start creating 3D environments in your games and applications. For in depth description you can refer to the book 'OpenGL SuperBible' which is for me the basic source of reference.
The OpenGL approach is very simple. You can imagine it like the stage of a theatre. The system draws only what is on the stage and ignores everything else. Then is the viewer position, which determines the perspective.
Setting up the stage is the first step we have to make. The stage is a simple 'box'. It has a so called near plane, an imaginary surface where the curtain of a theatre stage is and, the far plane which is at the deepest end where the background is drawn. All we have to do is tell the OpenGL engine is the distance from our position to the near plane, the far plane and the extends of the near plane. This information is enough for the system to all the necessary calculations involved with the perspective drawing of the objects within the clipping volume. Notice that the stage was set with the distance from the viewer and NOT its actual position. The viewer position will determine the stage position.
Now that we have set the stage it is time to tell where we stand. At this step we set our position, the points we are looking at and our orientation. This means we can turn our attention to a certain point on the stage or incline our head (even turn upside down). Try experimenting with the 'look-at' and 'up' parameters and see the results.
Let's get a closer look at the steps described above.
The basic concept is the same as was in the two dimensional drawing. We start by specifying the portion of the screen we shall use, the viewport in technical terminology. then comes the stage setting. This is done by initializing the projection parameters and setting the frustum or the perspective. In order to encapsulate these we create a simple class called clf_stage. This holds the viewport and the frustum parameters. then we create another class called clf_camera. This is used to hold the viewer parameters, position orientation etc.
Here is the code of the clf_stage class.
class clf_stage{ public: // constructor, initialize member variables clf_stage(){ left = bottom = 0; // OpenGL puts origin at the bottom width = 640; // VGA size window height = 480; near_plane = 1.f; // anything closer than 1 will not be displayed far_plane = 100.f; // as well as anything farther than 100 perspective_angle = 30; // as well as anything that is more than 15 degrees to the left or right, 30 degrees edge to edge } ~clf_stage(){ } // function that sets the extents and location of the viewport void resize(int l, int b, int w, int h) { left = l; bottom = b; width = w; height = h; } // function to set perspective angle near and far planes void set_perspective(float angle, float np, float fp) { perspective_angle = angle; near_plane = np; far_plane = fp; } // called when we start drawing to perform scene setup void prepare() { glViewport (left, bottom, (GLsizei)(width), (GLsizei)(height)); // reset the current viewport glMatrixMode (GL_PROJECTION); // select the projection matrix glLoadIdentity (); // and reset it float w = (float)width; // for precision use float float h = (float)height; // the two paths are exactly the same (internally) // in the first we calculate the dimensions of the near plane visible area // which requires some elementary but sometimes forgotten trigonometry // the second is provided by the utility functions in OpenGL #if 1 float r = w / h; float persp_angle = perspective_angle/2.f; // half of the perspective angle goes to one side float ys = near_plane*(float)tan(persp_angle*PI/180.f); float xs = ys*r; glFrustum( -xs, xs, -ys, ys, near_plane, far_plane ); #else gluPerspective (perspective_angle, w/h, near_plane, far_plane); // calculate the aspect ratio of the window #endif } int left, bottom; int width, height; float near_plane, far_plane; float perspective_angle; };
Some trigonometry is needed in order to calculate the extents of the near plane, in order to call glFrustum. I prefer using the perspective angle when defining the 'stage' because I find it easier to visualize it and I know what to expect to see on the screen.
Now the clf_camera class code.
class cCamera{ public: clf_vector3D m_pos; clf_vector3D m_look; clf_vector3D m_up; cCamera(){ // setup a default viewer setup(clf_vector3D(0,0,3), // position viewer clf_vector3D(0,0,0), // setup the point of interest (where we are looking at) clf_vector3D(0,1,0)); // which way is up } ~cCamera(){ } // function to set the viewer void setup(clf_vector3D& pos, clf_vector3D& lookat, clf_vector3D& up){ m_pos = pos; m_look = lookat; m_up = up; } // position the viewer void move_to(clf_vector3D& pos){ m_pos = pos; } // set the point of interest (where we are looking at) void look_at(clf_vector3D& lookat){ m_look = lookat; } // set the orientation void orient(clf_vector3D& up){ m_up = up; } // apply the transformations according to the location, orientation and view point void apply(){ // in order to do the following in pure OpenGL we have to do a lot of matrix math // it is not so difficult but the code is lengthy and does not add anything to our understanding // on the contrary the following is very clear and neat gluLookAt(m_pos.x, m_pos.y, m_pos.z, // viewer position m_look.x, m_look.y, m_look.z, // focus point m_up.x, m_up.y, m_up.z); // up direction } };
You can download the whole sample project from this link.