| TRING A Modified Carom Billiards Game |
|
|
Dynamic Cube Mapping
We have implemented two levels of dynamicism using cube maps. No matter which level is chosen, the OpenGL implementation must support the GL_ARB_texture_cube_map extension in order for the game to run with cube maps turned on. With the first level of dynamicism, we generate a cube map by placing the eye in the middle of the table and taking 6 pictures using a 90 degree FOV camera in each direction. This cube map contains only the table and scene (no balls), and is not updated throughout the game. The cube map is simply rotated, rather than regenerated, to handle the proper viewpoint. The second level of dynamicism is a GPU hog. I'd recommend it only for fast cards
(it runs on Brian's card at roughly 35 fps, but it runs on mine at less than 10 fps).
The idea is to generate cubemaps for the balls from their locations for each frame
before the render stage. This works nicely, and if you have a fast enough card to make
the game playable, you'll certainly notice the difference when you can see the
reflections of the balls in each other. The following is a decent screenshot of
the dynamic cube map effect: ![]() Some Implementation Details The first step is creating the cubemap texture. We essentially create a
texture for each of the faces of the cube and fill it with garbage from the
framebuffer. This is done in the Ball class
inside of the constructor somewhere:
/* Cube texture won't be defined until the first rendering pass */
if (useCubemaps)
{
glEnable(GL_TEXTURE_CUBE_MAP_ARB);
glGenTextures(1, &cubeTex);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, cubeTex);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
for (int ii = 0; ii < 6; ii++)
{
// Fill the texture with whatever is on the framebuffer
// right now... we don't really care until the first
// rendering pass.
glCopyTexImage2D(cubeSurfaces[ii], 0, GL_RGB8, 0, 0,
CUBEMAP_SIZE, CUBEMAP_SIZE, 0);
}
glDisable(GL_TEXTURE_CUBE_MAP_ARB);
}
Then, when it comes time for a frame to be rendered, the following function
is called 3 times (once for each ball) to update the cube map textures. If
dynamic cube mapping is on (see "Level 2" as discussed earlier), the cube mapped
textures are generated from the ball's point of view. Otherwise, they are generated
(and the function is only called once per ball) from the center of the table, with
no balls on the table.
void Tring::updateCubeMaps()
{
static int ii = 0;
renderState = CUBEMAP_UPDATE;
glReadBuffer(GL_BACK);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective (90, 1.0f, ballRadius / 2.0f, 500);
glViewport(0, 0, CUBEMAP_SIZE, CUBEMAP_SIZE);
glMatrixMode(GL_MODELVIEW);
Ball *curBall = balls[ii];
Vector ballPos = Vector(curBall->getX(), curBall->getY(), 0.0f);
GLuint cubeTex = curBall->getCubeTextureNum();
glPushMatrix();
for (int s = 0; s < 6; s++)
{
glPushMatrix();
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glEnable(GL_LIGHTING);
glLoadIdentity();
glRotatef(rot[s][0], rot[s][1], rot[s][2], rot[s][3]);
if (s == 0 || s == 1)
glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
glTranslatef(0, 0, -ballRadius);
if (dynamicCubemaps)
glTranslatef(-ballPos.getX(), -ballPos.getY(), 0);
skyBox->display(1.0f);
tringTable->display(1.0f, false, true, true);
// don't draw the balls in static cubemaps... they're ugly
if (dynamicCubemaps)
{
glTranslatef(0, 0, ballRadius);
for (int b = 0; b < 3; b++)
{
if (b != ii)
balls[b]->display();
}
}
glPopMatrix();
glFinish();
glEnable(GL_TEXTURE_CUBE_MAP_ARB);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, cubeTex);
glCopyTexSubImage2D(cubeSurfaces[s], 0, 0, 0, 0, 0,
CUBEMAP_SIZE, CUBEMAP_SIZE);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);
//glutSwapBuffers();
//sleep(1);
}
glPopMatrix();
glViewport(0, 0, windowWidth, windowHeight);
switchTo3D();
glClear(GL_DEPTH_BUFFER_BIT); // we're drawing over it all
ii = (ii + 1) % 3; // we'll update the next ball next frame
}
Then, the only trick left is rendering the cube maps. The following function is the portion of the
ball rendering code which draws the cube map over the current ball. It is essentially drawing a translucent
ball over the current ball, offset slightly in the depth buffer so that they do not overlap (hence the
glPolygonOffset calls). The tricky part is getting the cube map to align properly with the camera. To do
so, we switched the matrix mode to GL_TEXTURE and un-did all of the rotations necessary to get to this point
so that the cube map was screen-aligned again. This could be abstracted out into another function that could
reverse the rotations of any modelview matrix, but since our camera model was so simple, we were okay doing
it the way we did.
void Ball::drawShinyBall()
{
#ifndef christmas
glPushAttrib(GL_TEXTURE_BIT | GL_DEPTH_BUFFER_BIT);
#else
glActiveTextureARB(GL_TEXTURE1_ARB);
#endif
//Enable cube mapping.
glEnable(GL_TEXTURE_CUBE_MAP_ARB);
#ifdef christmas
glDisable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#endif
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
//Enable auto generation of tex-coords
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, cubeTex);
glMatrixMode(GL_TEXTURE);
glPushMatrix();
if (tring->getRenderState() == MAIN_VIEW)
{
/* We must do viewpoint correction on the cubemap
if the camera moves in the Main View */
glRotatef(-tring->getEyeAngle(), 1.0f, 0.0f, 0.0f);
}
else if (tring->getRenderState() == SHOT_VIEW)
{
/* Viewpoint correction for the shot view */
glRotatef(-tring->getShotAngle(), 0.0f, 0.0f, 1.0f);
glRotatef(180, 0, 0, 1);
glRotatef(90, 1, 0, 0);
}
#ifndef christmas
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-1.0f, -1.0f);
glDepthMask(GL_FALSE);
glDepthFunc(GL_LEQUAL);
#endif
gluSphere(quadric, radius, 30, 30);
#ifndef christmas
glDisable(GL_POLYGON_OFFSET_FILL);
#endif
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glDisable(GL_TEXTURE_2D);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);
#ifdef christmas
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);
#endif
// Reset the texture and depth info
glPopAttrib();
}
|
|
||||||||||||||