• We use OBJECT COORDINATES, this means we don't apply the modelview matrix to our calculations. This has a nasty side-effect: since we want to rotate the cube, object-coordinates of the cube don't change, world-coordinates (also referred to as eye-coordinates) do. But our light-position should not be rotated with the cube, it should be just static, meaning that it's world-coordinates don't change. To compensate, we'll apply a trick commonly used in computer graphics: Instead of transforming each vertex to worldspace in advance to computing the bumps, we'll just transform the light into object-space by applying the inverse of the modelview-matrix. This is very cheap in this case since we know exactly how the modelview-matrix was built step-by-step, so an inversion can also be done step-by-step. We'll come back later to that issue.
• We calculate the current vertex c on our surface (simply by looking it up in data).
• Then we'll calculate a normal n with length 1 (We usually know n for each face of a cube!). This is important, since we can save computing time by requesting normalized vectors. Calculate the light vector v from c to the light position l
• If there's work to do, build a matrix Mn representing the orthonormal projection. This is done as f
• Calculate out texture coordinate offset by multiplying the supplied texture-coordinate directions s and t each with v and MAX_EMBOSS: ds = s*v*MAX_EMBOSS, dt=t*v*MAX_EMBOSS. Note that s, t and v are vectors while MAX_EMBOSS isn't.
• Add the offset to the texture-coordinates in pass 2.
• Fast (only needs one squareroot and a couple of MULs per vertex)!
• Looks very nice!
• This works with all surfaces, not just planes.
• This runs on all accelerators.
• Is glBegin/glEnd friendly: Does not need any "forbidden" GL-commands.
• Not fully physical correct.
• Leaves minor artefacts.
This figure shows where our vectors are located. You can get t and s by simply subtracting adjacent vertices, but be sure to have them point in the right direction and to normalize them. The blue spot marks the vertex where texCoord2f(0.0f,0.0f) is mapped to.
Let's have a look to texture-coordinate offset generation, first. The function is called SetUpBumps(), since this actually is what it does:
// Sets Up The Texture-Offsets
// n : Normal On Surface. Must Be Of Length 1
// c : Current Vertex On Surface
// l : Lightposition
// s : Direction Of s-Texture-Coordinate In Object Space (Must Be Normalized!)
// t : Direction Of t-Texture-Coordinate In Object Space (Must Be Normalized!)
void SetUpBumps(GLfloat *n, GLfloat *c, GLfloat *l, GLfloat *s, GLfloat *t) {
GLfloat v[3]; // Vertex From Current Position To Light
GLfloat lenQ; // Used To Normalize
// Calculate v From Current Vertex c To Lightposition And Normalize v
v[0]=l[0]-c[0];
v[1]=l[1]-c[1];
v[2]=l[2]-c[2];
lenQ=(GLfloat) sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
v[0]/=lenQ;
v[1]/=lenQ;
v[2]/=lenQ;
// Project v Such That We Get Two Values Along Each Texture-Coordinate Axis
c[0]=(s[0]*v[0]+s[1]*v[1]+s[2]*v[2])*MAX_EMBOSS;
c[1]=(t[0]*v[0]+t[1]*v[1]+t[2]*v[2])*MAX_EMBOSS;
Doesn't look that complicated anymore, eh? But theory is necessary to understand and control this effect. (I learned THAT myself during writing this tutorial).
I always like logos to be displayed while presentational programs are running. We'll have two of them right now. Since a call to doLogo() resets the GL_MODELVIEW-matrix, this has to be called as final rendering pass.
This function displays two logos: An OpenGL-Logo and a multitexture-Logo, if this feature is enabled. The logos are alpha-blended and are sort of semi-transparent. Since they have an alpha-channel, I blend them using GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, as suggested by all OpenGL-documentation. Since they are all co-planar, we do not have to z-sort them before. The numbers that are used for the vertices are "empirical" (a.k.a. try-and-error) to place them neatly into the screen edges. We'll have to enable blending and disable lighting to avoid nasty effects. To ensure they're in front of all, just reset the GL_MODELVIEW-matrix and set depth-function to GL_ALWAYS.
void doLogo(void) {
// MUST CALL THIS LAST!!!, Billboards The Two Logos
glDepthFunc(GL_ALWAYS);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glDisable(GL_LIGHTING);
glLoadIdentity();
glBindTexture(GL_TEXTURE_2D,glLogo);
glBegin(GL_QUADS);
glTexCoord2f(0.0f,0.0f); glVertex3f(0.23f, –0.4f,-1.0f);
glTexCoord2f(1.0f,0.0f); glVertex3f(0.53f, –0.4f,-1.0f);
glTexCoord2f(1.0f,1.0f); glVertex3f(0.53f, –0.25f,-1.0f);
glTexCoord2f(0.0f,1.0f); glVertex3f(0.23f, –0.25f,-1.0f);
glEnd();
if (useMultitexture) {
glBindTexture(GL_TEXTURE_2D,multiLogo);
glBegin(GL_QUADS);
glTexCoord2f(0.0f,0.0f); glVertex3f(-0.53f, –0.25f,-1.0f);
glTexCoord2f(1.0f,0.0f); glVertex3f(-0.33f, –0.25f,-1.0f);
glTexCoord2f(1.0f,1.0f); glVertex3f(-0.33f, –0.15f,-1.0f);
glTexCoord2f(0.0f,1.0f); glVertex3f(-0.53f, –0.15f,-1.0f);
glEnd();
}
}
Here comes the function for doing the bump mapping without multitexturing. It's a three-pass implementation. As a first step, the GL_MODELVIEW matrix is inverted by applying to the identity-matrix all steps later applied to the GL_MODELVIEW in reverse order and inverted. The result is a matrix that "undoes" the GL_MODELVIEW if applied to an object. We fetch it from OpenGL by simply using glGetFloatv(). Remember that the matrix has to be an array of 16 and that the matrix is "transposed"!
By the way: If you don't exactly know how the modelview was built, consider using world-space, since matrix-inversion is complicated and costly. But if you're doing large amounts of vertices inverting the modelview with a more generalized approach could be faster.
bool doMesh1TexelUnits(void) {
GLfloat c[4]={0.0f,0.0f,0.0f,1.0f}; // Holds Current Vertex
GLfloat n[4]={0.0f,0.0f,0.0f,1.0f}; // Normalized Normal Of Current Surface
GLfloat s[4]={0.0f,0.0f,0.0f,1.0f}; // s-Texture Coordinate Direction, Normalized
GLfloat t[4]={0.0f,0.0f,0.0f,1.0f}; // t-Texture Coordinate Direction, Normalized
GLfloat l[4]; // Holds Our Lightposition To Be Transformed Into Object Space
GLfloat Minv[16]; // Holds The Inverted Modelview Matrix To Do So
int i;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
// Build Inverse Modelview Matrix First. This Substitutes One Push/Pop With One glLoadIdentity();
// Simply Build It By Doing All Transformations Negated And In Reverse Order
glLoadIdentity();
glRotatef(-yrot,0.0f,1.0f,0.0f);