Course list http://www.c-jump.com/bcc/
You've probably already seen skyboxes in games: skybox is a cube that
has a sky and environment texture, and
follows the camera anywhere the camera goes
Why use a box, if the sky more accurately would be depicted as a sphere?
The cube has significantly lesser numer of polygons than a sphere, so we use a cube.
Download c262_lab10.zip and unzip it under the labs subdirectory:
C:\bcc\GL262Labs\labs\c262_lab10
Open Visual Studio project
C:\bcc\GL262Labs\labs\c262_lab10\c262_lab10.sln
and compile the executable:
Build -> Build Solution
(or simply press F7.)
There is a new class named Skybox in this project. The Skybox inherits from the Model class. Typically, you use inheritance to modify or extend the behavior of the base class. The summary behavior of the skybox is:
Reverse the normals of the cube model, because the viewer is always inside the box. If normals did not reverse, the backface culling stage of OpenGL pipeline would eliminate the rendering of the skybox. Instead of designing a special cube with normals facing inward, we can control the winding order of front-facing polygons:
glFrontFace( GL_CW ); // clock-wise triangles are now front-faced //... skybox->render(); glFrontFace( GL_CCW ); // restore counter-clock-wise front-face mode
When the viewer looks around the scene, the skybox should rotate with the scene, acting as a scene backdrop.
We discussed in the past that the result of the model/view transformation is that the camera ends up sitting at the coordinate
(x, y, z) = 0, 0, 0
This is because the camera never moves; instead, all objects move and rotate around the camera. So what about the skybox? We said that it does rotate, but it should never move relative to the camera. A new member function, FPSCamera::set_skybox_uniform_view_perspective(), will handle this requirement.
Instead of a regular texture image, the Skybox::render() function has to load a cube map texture. The cube map actually is made of six texture images -- the up, down, left, right, forward, and backward view:
Loading those six images becomes a direct responsibility of the Skybox class.
The skybox shader doesn't have to worry about light or the material properties. It uses raw texture to draw the backdrop environment:
|
|
Our application needs to deal with all of the above requirements.
Once you compile and run the program, you will see a black sky.
Open the header file of the Skybox class. You should see that it inherits from the Model class.
Open the Skybox.cpp file and implement the six steps outlined in the Skybox::upload_CubeMap_images() function. In particular,
To enable cube mapping, call glEnable( GL_TEXTURE_CUBE_MAP )
To generate OpenGL texture ID which will hold the cube map, call glGenTextures() functions with parameters
glGenTextures( 1, &tex_buffer_ID );
Here,
1 indicates that we need to generate one texture ID, and
tex_buffer_ID we inherited from the base Model class, and now we are using it in the derived class.
To bind tex_buffer_ID as the current buffer, we need to make sure we specify it as a cube map:
glBindTexture( GL_TEXTURE_CUBE_MAP, tex_buffer_ID );
To load all six images into the correct place in the cube map, we use a set of constant strings such as BMP_NEG_X_FILE_PATH that have been declared earlier in this file:
load_bmp_file( BMP_NEG_X_FILE_PATH, &width, &height, &size, &temp_image_data ); glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, temp_image_data ); delete ( temp_image_data ); load_bmp_file( BMP_NEG_Y_FILE_PATH, &width, &height, &size, &temp_image_data ); glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, temp_image_data ); delete ( temp_image_data ); load_bmp_file( BMP_NEG_Z_FILE_PATH, &width, &height, &size, &temp_image_data ); glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, temp_image_data ); delete ( temp_image_data ); load_bmp_file( BMP_POS_X_FILE_PATH, &width, &height, &size, &temp_image_data ); glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, temp_image_data ); delete ( temp_image_data ); load_bmp_file( BMP_POS_Y_FILE_PATH, &width, &height, &size, &temp_image_data ); glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, temp_image_data ); delete ( temp_image_data ); load_bmp_file( BMP_POS_Z_FILE_PATH, &width, &height, &size, &temp_image_data ); glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, temp_image_data ); delete ( temp_image_data );
Notice that once each bitmap image is uploaded to OpenGL, we can (and should) delete its temporary memory.
To set the S, T, and R preferences for the texture, we need use the GL_TEXTURE_CUBE_MAP
glTexParameterf( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameterf( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexParameterf( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );
Finally, to get the current ID of the cubeMap variable in the fragment shader and store it in the Skybox object's cube_map_ID variable, we call glGetUniformLocation():
cube_map_ID = glGetUniformLocation( shader->program_ID, "cubeMap" );
Open c262_lab10.cpp and find the main() function. There are five things to do here:
To change the winding of the triangles, call glFrontFace( GL_CW )
The original skybox OBJ is designed having 0.5f units on each side. But we need a much bigger box! To make the skybox 100 times bigger, we can scale it using its model matrix as follows:
skybox->mM = glm::scale( glm::mat4( 1.0f ), glm::vec3( 100.0f, 100.0f, 100.0f ) // make cube of 50 units );
As with other models, we need to set skybox's camera perspective matrix:
camera->set_skybox_uniform_view_perspective( skybox->shader->program_ID );
Now we are ready to render the skybox: skybox->render();
We definitely need to reset the triangle winding order before continuing with other parts of the drawing scene:
glFrontFace( GL_CCW );
Open FPSCamera.cpp and make the following changes in set_skybox_uniform_view_perspective():
As it was already discussed, since the skybox movement differs from the movement of other parts of the scene (it only rotates, but never moves), we must use a separate view matrix to deal with the skybox's 3D transformations. The skybox's view matrix is stored in the new FPSCamera data member named mtx_skybox_view.
We begin by initializing the mtx_skybox_view equal to mV matrix (presuming that the mV itself has already been set by FPSCamera::set_perspective()):
mtx_skybox_view = this->mV;
Do it! Now compile and run the program. Move towards the horizon. What do you see? Is this correct? No, because we need to eliminate the movement of the skybox.
To eliminate skybox movement we set the last column of skybox's view transformation matrix, namely mtx_skybox_view[3].xyz to zero (why?):
mtx_skybox_view[3][0] = 0; mtx_skybox_view[3][1] = 0; mtx_skybox_view[3][2] = 0;
Although the above works, the following syntax is preferred:
mtx_skybox_view[3].x = 0; // this eliminates movement of the skybox mtx_skybox_view[3].y = 0; mtx_skybox_view[3].z = 0;
Note that we do not change the column's w component, beacuse it's not used by the translation math.
Compile and run the program again. The skybox behavior should now be in place.
There are no files to submit.
Demonstrate a working c262_lab10 program to your instructor.