Have you ever wanted to draw the horizon in your game? Have you ever wandered how they can make distant things look so nice and still not taking too much processing power? Well I had. And after some research (quite a lot actually, information was quite limited at the time) I came across the answer. The initial solution was refined as more and more information was coming and more and more applications were using it. So here is the solution to drawing the horizon.
The solution is based on the fact that the human eye sees everything flat when the distance is too long. When two objects are very far from us we cannot tell which one is closer to us and this makes everything look like a painting. This painting is what we are drawing on the screen.
The technique is called 'skybox' because we are actually drawing a cubic box to represent the horizon. All we need is a series of photographs picturing what we see in any of the six directions, East, West, North, South, Up and Down. These pictures are used as textures at the inside of the box and we have our horizon. The only thing you have to take care of is the pictures you will use. They must be panoramic in order to have the continuous look so that it feels natural as you look around.
In order to simplify the use of skyboxes we created a small class. When our game initializes we load the six textures and then we render it. You will notice that in the sample program we have only two calls to member functions of the skybox.
clf_skybox g_skybox; // declaration of the skybox variable ........... // load the texture images in init function g_skybox.load_textures("skybox/up.tga", "skybox/dn.tga", "skybox/ft.tga", "skybox/bk.tga", "skybox/lt.tga", "skybox/rt.tga"); ........... // and draw the skybox when rendering our scene g_skybox.render(g_cam.vLocation.x, g_cam.vLocation.y, g_cam.vLocation.z);
Using the skybox is very simple. Now let's see how it works from inside.
First we have the 'load_textures' function which stores the six image filenames, loads the textures and generates the draw list for drawing. There is one thing you must take care when creating skyboxes. The size of the skybox defined in the constructor. It must be large enough compared to the near plane of the OpenGL stage and small enough for the far plane. Then it must be far enough for the visual effects to be noticeable. For the given sample the depth of the fog effect.
bool clf_skybox::load_textures(const char* top, const char* bottom, const char* front, const char* back, const char* left, const char* right) { m_textureNames[SKY_TOP] = std::string(top); m_textureNames[SKY_BOTTOM] = std::string(bottom); m_textureNames[SKY_EAST] = std::string(front); m_textureNames[SKY_WEST] = std::string(back); m_textureNames[SKY_NORTH] = std::string(left); m_textureNames[SKY_SOUTH] = std::string(right); clf_reset(); return true; } void clf_skybox::clf_reset() { glPushAttrib(GL_ALL_ATTRIB_BITS); m_textures[SKY_TOP] = clf_loadTexture(m_textureNames[SKY_TOP].c_str()); m_textures[SKY_BOTTOM] = clf_loadTexture(m_textureNames[SKY_BOTTOM].c_str()); m_textures[SKY_EAST] = clf_loadTexture(m_textureNames[SKY_EAST].c_str()); m_textures[SKY_WEST] = clf_loadTexture(m_textureNames[SKY_WEST].c_str()); m_textures[SKY_NORTH] = clf_loadTexture(m_textureNames[SKY_NORTH].c_str()); m_textures[SKY_SOUTH] = clf_loadTexture(m_textureNames[SKY_SOUTH].c_str()); glPopAttrib(); draw_list = glGenLists(1); glNewList(draw_list, GL_COMPILE); build_draw_list(); glEndList(); } void clf_skybox::build_draw_list() { glPushAttrib(GL_ALL_ATTRIB_BITS); glDisable(GL_DEPTH_TEST); glDisable (GL_LIGHTING); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); dFloat padd; padd = 2.0e-4f; // uncommenting this will make a perfect sky box texture stitch, unfortunately // this option ios not supported by all version of opengl glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // the cube will just be drawn as six quads for the sake of simplicity // for each face, we specify the quad's normal (for lighting), then // specify the quad's 4 vertices's and associated texture coordinates glBindTexture(GL_TEXTURE_2D, m_textures[SKY_EAST]); glBegin(GL_QUADS); glTexCoord2f(0.0f + padd, 1.0f - padd); glVertex3f(-m_size.x, m_size.y, m_size.z); glTexCoord2f(1.0f - padd, 1.0f - padd); glVertex3f( m_size.x, m_size.y, m_size.z); glTexCoord2f(1.0f - padd, 0.0f + padd); glVertex3f( m_size.x, -m_size.y, m_size.z); glTexCoord2f(0.0f + padd, 0.0f + padd); glVertex3f(-m_size.x, -m_size.y, m_size.z); glEnd(); ............................................ draw all faces glPopAttrib(); } void clf_skybox::render(dFloat cameraX, dFloat cameraY, dFloat cameraZ) { glPushMatrix(); // Move the skybox so that it's centered on the camera. glTranslatef(cameraX, cameraY, cameraZ); glCallList(draw_list); glPopMatrix(); }