/* softshadow2.c - by Simon Hui, 3Dfx Interactive */ /* Soft shadows using a shadow texture per polygon. Based on an algorithm */ /* described by Paul Heckbert and Michael Herf of CMU; see their web site */ /* http://www.cs.cmu.edu/ph/shadow.html for details. */ /* */ /* This program shows two methods of using precomputed, per-polygon textures */ /* to display soft shadows. The first method is a simplified version of */ /* Heckbert and Herf's algorithm: for each polygon a texture is created that */ /* encodes the full radiance, including illumination and shadows, of the */ /* polygon. The texture is created in a preprocessing step by rendering the */ /* entire scene onto the polygon from the point of view of the light. The */ /* advantage of this method is that the scene can be rerendered quickly (if */ /* only the eye moves and the scene is static), since all lighting effects */ /* have been precomputed and encoded in the texture. This method requires */ /* GL_RGB textures. */ /* */ /* The second method uses the texture as an occlusion map: the texels */ /* encode only the amount of occlusion by shadowing objects, not the full */ /* radiance. The texture is then used to modulate the lighting of the */ /* polygon during the rendering pass. This has the disadvantage of */ /* requiring OpenGL lighting during scene rendering, but it does retain some */ /* of the benefit of the first method in that all shadow effects are */ /* precomputed. This method requires GL_LUMINANCE textures. */ /* */ /* The reason for including the occlusion map method is that some OpenGL */ /* implementations support GL_RGB textures with low color resolution, */ /* resulting in noticeable banding when using radiance maps. However, these */ /* implementations may support a higher color resolution for GL_LUMINANCE */ /* textures. */ /* */ /* To use occlusion maps instead of rediance maps, run this program with */ /* "-o" on the command line. */ #include #include #include #include #if !defined(GL_VERSION_1_1) && !defined(GL_VERSION_1_2) #define glBindTexture glBindTextureEXT #define glCopyTexImage2D glCopyTexImage2DEXT #endif /* whether to use radiance maps or occlusion maps */ static GLboolean radianceMap = GL_TRUE; static GLint winxsize = 480, winysize = 480; static GLint texxsize = 128, texysize = 128; /* texture object names */ const GLuint floorTexture = 1; const GLuint shadowTextures = 2; static GLfloat lightpos[4] = { 70.f, 70.f, -320.f, 1.f }; /* number of shadow textures to make and use */ static GLint numShadowTex; /* list of polygons that have shadow textures */ GLfloat pts[][4][3] = { /* floor */ -100.f, -100.f, -320.f, -100.f, -100.f, -520.f, 100.f, -100.f, -320.f, 100.f, -100.f, -520.f, /* left wall */ -100.f, -100.f, -320.f, -100.f, 100.f, -320.f, -100.f, -100.f, -520.f, -100.f, 100.f, -520.f, /* back wall */ -100.f, -100.f, -520.f, -100.f, 100.f, -520.f, 100.f, -100.f, -520.f, 100.f, 100.f, -520.f, /* right wall */ 100.f, -100.f, -520.f, 100.f, 100.f, -520.f, 100.f, -100.f, -320.f, 100.f, 100.f, -320.f, /* ceiling */ -100.f, 100.f, -520.f, -100.f, 100.f, -320.f, 100.f, 100.f, -520.f, 100.f, 100.f, -320.f, /* blue panel */ -60.f, -40.f, -400.f, -60.f, 70.f, -400.f, -30.f, -40.f, -480.f, -30.f, 70.f, -480.f, /* yellow panel */ -40.f, -50.f, -400.f, -40.f, 50.f, -400.f, -10.f, -50.f, -450.f, -10.f, 50.f, -450.f, /* red panel */ -20.f, -60.f, -400.f, -20.f, 30.f, -400.f, 10.f, -60.f, -420.f, 10.f, 30.f, -420.f, /* green panel */ 0.f, -70.f, -400.f, 0.f, 10.f, -400.f, 30.f, -70.f, -395.f, 30.f, 10.f, -395.f, }; GLfloat materials[][4] = { 1.0f, 1.0f, 1.0f, 1.0f, /* floor */ 1.0f, 1.0f, 1.0f, 1.0f, /* left wall */ 1.0f, 1.0f, 1.0f, 1.0f, /* back wall */ 1.0f, 1.0f, 1.0f, 1.0f, /* right wall */ 1.0f, 1.0f, 1.0f, 1.0f, /* ceiling */ 0.2f, 0.5f, 1.0f, 1.0f, /* blue panel */ 1.0f, 0.6f, 0.0f, 1.0f, /* yellow panel */ 1.0f, 0.2f, 0.2f, 1.0f, /* red panel */ 0.3f, 0.9f, 0.6f, 1.0f, /* green panel */ }; /* some simple vector utility routines */ void vcopy(GLfloat a[3], GLfloat b[3]) { b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; } void vnormalize(GLfloat v[3]) { float m = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); v[0] /= m; v[1] /= m; v[2] /= m; } void vadd(GLfloat a[3], GLfloat b[3], GLfloat c[3]) { c[0] = a[0] + b[0]; c[1] = a[1] + b[1]; c[2] = a[2] + b[2]; } void vsub(GLfloat a[3], GLfloat b[3], GLfloat c[3]) { c[0] = a[0] - b[0]; c[1] = a[1] - b[1]; c[2] = a[2] - b[2]; } void vcross(GLfloat a[3], GLfloat b[3], GLfloat c[3]) { c[0] = a[1] * b[2] - a[2] * b[1]; c[1] = -(a[0] * b[2] - a[2] * b[0]); c[2] = a[0] * b[1] - a[1] * b[0]; } float vdot(GLfloat a[3], GLfloat b[3]) { return (a[0]*b[0] + a[1]*b[1] + a[2]*b[2]); } void findNormal(GLfloat pts[][3], GLfloat normal[3]) { GLfloat a[3], b[3]; vsub(pts[1], pts[0], a); vsub(pts[2], pts[0], b); vcross(b, a, normal); vnormalize(normal); } static GLfloat origin[4] = { 0.f, 0.f, 0.f, 1.f }; static GLfloat black[4] = { 0.f, 0.f, 0.f, 1.f }; static GLfloat ambient[4] = { 0.2f, 0.2f, 0.2f, 1.f }; void make_shadow_texture(int index, GLfloat eyept[3], GLfloat dx, GLfloat dy) { GLfloat xaxis[3], yaxis[3], zaxis[3]; GLfloat cov[3]; /* center of view */ GLfloat pte[3]; /* plane to eye */ GLfloat eye[3]; GLfloat tmp[3], normal[3], dist; GLfloat (*qpts)[3] = pts[index]; GLfloat left, right, bottom, top; GLfloat znear = 10.f, zfar = 600.f; GLint n; /* For simplicity, we don't compute the transformation matrix described */ /* in Heckbert and Herf's paper. The transformation and frustum used */ /* here is much simpler. */ vcopy(eyept, eye); vsub(qpts[1], qpts[0], yaxis); vsub(qpts[2], qpts[0], xaxis); vcross(yaxis, xaxis, zaxis); vnormalize(zaxis); vnormalize(xaxis); /* x-axis of eye coord system, in object space */ vnormalize(yaxis); /* y-axis of eye coord system, in object space */ /* jitter the eyepoint */ eye[0] += xaxis[0] * dx; eye[1] += xaxis[1] * dx; eye[2] += xaxis[2] * dx; eye[0] += yaxis[0] * dy; eye[1] += yaxis[1] * dy; eye[2] += yaxis[2] * dy; /* center of view is just eyepoint offset in direction of normal */ vadd(eye, zaxis, cov); /* set up viewing matrix */ glPushMatrix(); glLoadIdentity(); gluLookAt(eye[0], eye[1], eye[2], cov[0], cov[1], cov[2], yaxis[0], yaxis[1], yaxis[2]); /* compute a frustum that just encloses the polygon */ vsub(qpts[0], eye, tmp); /* from eye to 0th vertex */ left = vdot(tmp, xaxis); vsub(qpts[2], eye, tmp); /* from eye to 2nd vertex */ right = vdot(tmp, xaxis); vsub(qpts[0], eye, tmp); /* from eye to 0th vertex */ bottom = vdot(tmp, yaxis); vsub(qpts[1], eye, tmp); /* from eye to 1st vertex */ top = vdot(tmp, yaxis); /* scale the frustum values based on the distance to the polygon */ vsub(qpts[0], eye, pte); dist = fabs(vdot(zaxis, pte)); left *= (znear/dist); right *= (znear/dist); bottom *= (znear/dist); top *= (znear/dist); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glFrustum(left, right, bottom, top, znear, zfar); glMatrixMode(GL_MODELVIEW); if (radianceMap) { glEnable(GL_LIGHTING); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materials[index]); } else { glDisable(GL_LIGHTING); } glDisable(GL_TEXTURE_2D); for (n=0; n < numShadowTex; n++) { qpts = pts[n]; if (radianceMap) { glColor3f(1.f, 1.f, 1.f); findNormal(qpts, normal); glNormal3fv(normal); if (n == index) { /* draw this poly with ambient and diffuse lighting */ glEnable(GL_LIGHT0); } else { /* draw other polys with ambient lighting only */ glDisable(GL_LIGHT0); } } else { if (n == index) { /* this poly has full intensity, no occlusion */ glColor3f(1.f, 1.f, 1.f); } else { /* all other polys just occlude the light */ glColor3f(0.f, 0.f, 0.f); } } glBegin(GL_TRIANGLE_STRIP); glVertex3fv(qpts[0]); glVertex3fv(qpts[1]); glVertex3fv(qpts[2]); glVertex3fv(qpts[3]); glEnd(); } glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } void make_all_shadow_textures(float eye[3], float dx, float dy) { GLint texPerRow; GLint n; GLfloat x, y; texPerRow = (winxsize / texxsize); for (n=0; n < numShadowTex; n++) { y = (n / texPerRow) * texysize; x = (n % texPerRow) * texxsize; glViewport(x, y, texxsize, texysize); make_shadow_texture(n, eye, dx, dy); } glViewport(0, 0, winxsize, winysize); } void store_all_shadow_textures(void) { GLint texPerRow; GLint n, x, y; GLubyte *texbuf; texbuf = (GLubyte *) malloc(texxsize * texysize * sizeof(int)); /* how many shadow textures can fit in the window */ texPerRow = (winxsize / texxsize); for (n=0; n < numShadowTex; n++) { GLenum format; x = (n % texPerRow) * texxsize; y = (n / texPerRow) * texysize; glBindTexture(GL_TEXTURE_2D, shadowTextures + n); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); if (radianceMap) { format = GL_RGB; } else { format = GL_LUMINANCE; } glCopyTexImage2D(GL_TEXTURE_2D, 0, format, x, y, texxsize, texysize, 0); } free(texbuf); } /* menu choices */ enum { NOSHADOWS, SOFTSHADOWS, HARDSHADOWS, VIEWTEXTURE, VIEWSCENE, QUIT }; GLint shadowMode = HARDSHADOWS; GLboolean viewTextures = GL_FALSE; void redraw(void) { GLint n; GLfloat normal[3]; GLfloat (*qpts)[3]; glPushMatrix(); glLoadIdentity(); if (radianceMap && (shadowMode != NOSHADOWS)) { glLightfv(GL_LIGHT0, GL_POSITION, origin); } else { glLightfv(GL_LIGHT0, GL_POSITION, lightpos); } glPopMatrix(); if (shadowMode == SOFTSHADOWS) { GLfloat jitterSize; GLfloat dx, dy; GLint numSteps, i, j; /* size of the area to jitter the light in */ jitterSize = 15.0; /* number of times along x and y to jitter */ numSteps = 5; glClear(GL_ACCUM_BUFFER_BIT); for (j=0; j < numSteps; j++) { for (i=0; i < numSteps; i++) { /* compute jitter amount, centering the jitter steps around zero */ dx = (i - (numSteps - 1.0) / 2.0) / (numSteps - 1.0) * jitterSize; dy = (j - (numSteps - 1.0) / 2.0) / (numSteps - 1.0) * jitterSize; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); make_all_shadow_textures(lightpos, dx, dy); glAccum(GL_ACCUM, 1.0 / (numSteps * numSteps)); if (viewTextures) { glutSwapBuffers(); } } } glAccum(GL_RETURN, 1.0); store_all_shadow_textures(); } else if (shadowMode == HARDSHADOWS) { /* make shadow textures from just one frame */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT); make_all_shadow_textures(lightpos, 0, 0); store_all_shadow_textures(); if (viewTextures) { glutSwapBuffers(); } } if (viewTextures) { glutSwapBuffers(); return; } glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glLoadIdentity(); glColor3f(1.f, 1.f, 1.f); if (shadowMode == NOSHADOWS) { glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glDisable(GL_TEXTURE_2D); for (n=0; n < numShadowTex; n++) { qpts = pts[n]; findNormal(qpts, normal); glNormal3fv(normal); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materials[n]); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0,0); glVertex3fv(qpts[0]); glTexCoord2f(0,1); glVertex3fv(qpts[1]); glTexCoord2f(1,0); glVertex3fv(qpts[2]); glTexCoord2f(1,1); glVertex3fv(qpts[3]); glEnd(); } } else { glEnable(GL_TEXTURE_2D); if (radianceMap) { glDisable(GL_LIGHTING); for (n=0; n < numShadowTex; n++) { qpts = pts[n]; glBindTexture(GL_TEXTURE_2D, shadowTextures + n); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0,0); glVertex3fv(qpts[0]); glTexCoord2f(0,1); glVertex3fv(qpts[1]); glTexCoord2f(1,0); glVertex3fv(qpts[2]); glTexCoord2f(1,1); glVertex3fv(qpts[3]); glEnd(); } } else { /* Unfortunately, using the texture as an occlusion map requires two */ /* passes: one in which the occlusion map modulates the diffuse */ /* lighting, and one in which the ambient lighting is added in. It's */ /* incorrect to modulate the ambient lighting, but if the result is */ /* acceptable to you, you can include it in the first pass and */ /* omit the second pass. */ /* draw only with diffuse light, modulating it with the texture */ glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, black); for (n=0; n < numShadowTex; n++) { qpts = pts[n]; findNormal(qpts, normal); glNormal3fv(normal); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materials[n]); glBindTexture(GL_TEXTURE_2D, shadowTextures + n); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0,0); glVertex3fv(qpts[0]); glTexCoord2f(0,1); glVertex3fv(qpts[1]); glTexCoord2f(1,0); glVertex3fv(qpts[2]); glTexCoord2f(1,1); glVertex3fv(qpts[3]); glEnd(); } /* add in the ambient lighting */ glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); glDepthFunc(GL_LEQUAL); for (n=0; n < numShadowTex; n++) { qpts = pts[n]; glColor4f(ambient[0] * materials[n][0], ambient[1] * materials[n][1], ambient[2] * materials[n][2], ambient[3] * materials[n][3]); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0,0); glVertex3fv(qpts[0]); glTexCoord2f(0,1); glVertex3fv(qpts[1]); glTexCoord2f(1,0); glVertex3fv(qpts[2]); glTexCoord2f(1,1); glVertex3fv(qpts[3]); glEnd(); } /* restore the ambient colors to their defaults */ glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); } } /* blend in the checkerboard floor */ glEnable(GL_BLEND); glBlendFunc(GL_ZERO, GL_SRC_COLOR); glDepthFunc(GL_LEQUAL); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, floorTexture); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materials[0]); glTranslatef(0.0f, 0.05f, 0.0f); glColor3f(1.f, 1.f, 1.f); glBegin(GL_TRIANGLE_STRIP); glNormal3f(0.f, 1.f, 0.f); glTexCoord2f(0.f, 0.f); glVertex3fv(pts[0][0]); glTexCoord2f(0.f, 1.f); glVertex3fv(pts[0][1]); glTexCoord2f(1.f, 0.f); glVertex3fv(pts[0][2]); glTexCoord2f(1.f, 1.f); glVertex3fv(pts[0][3]); glEnd(); /* undo some state settings that we did above */ glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); glDepthFunc(GL_LESS); glTranslatef(0.0f, -0.05f, 0.0f); glutSwapBuffers(); } void menu(int mode) { switch (mode) { case NOSHADOWS: case SOFTSHADOWS: case HARDSHADOWS: shadowMode = mode; break; case VIEWTEXTURE: viewTextures = GL_TRUE; break; case VIEWSCENE: viewTextures = GL_FALSE; break; case QUIT: exit(0); } glutPostRedisplay(); } /* Make a checkerboard texture for the floor. */ GLfloat * make_texture(int maxs, int maxt) { GLint s, t; static GLfloat *texture; texture = (GLfloat *) malloc(maxs * maxt * sizeof(GLfloat)); for (t = 0; t < maxt; t++) { for (s = 0; s < maxs; s++) { texture[s + maxs * t] = ((s >> 4) & 0x1) ^ ((t >> 4) & 0x1); } } return texture; } /* ARGSUSED1 */ void keyboard(unsigned char key, int x, int y) { if (key == 27) /* ESC */ exit(0); } int main(int argc, char *argv[]) { GLfloat *tex; GLint i; for (i = 1; i < argc; ++i) { if (!strcmp("-o", argv[i])) { /* use textures as occlusion maps rather than radiance maps */ radianceMap = GL_FALSE; } } glutInit(&argc, argv); glutInitWindowSize(winxsize, winysize); glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_ACCUM | GLUT_SINGLE); (void) glutCreateWindow("soft shadows"); glutDisplayFunc(redraw); glutKeyboardFunc(keyboard); glutCreateMenu(menu); glutAddMenuEntry("No Shadows", NOSHADOWS); glutAddMenuEntry("Soft Shadows", SOFTSHADOWS); glutAddMenuEntry("Hard Shadows", HARDSHADOWS); glutAddMenuEntry("View Textures", VIEWTEXTURE); glutAddMenuEntry("View Scene", VIEWSCENE); glutAddMenuEntry("Quit", QUIT); glutAttachMenu(GLUT_RIGHT_BUTTON); /* set up perspective projection */ glMatrixMode(GL_PROJECTION); glFrustum(-30., 30., -30., 30., 100., 640.); glMatrixMode(GL_MODELVIEW); /* turn on features */ glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glCullFace(GL_BACK); glLightfv(GL_LIGHT0, GL_AMBIENT, black); /* number of shadow textures to make */ numShadowTex = sizeof(pts) / sizeof(pts[0]); tex = make_texture(texxsize, texysize); glBindTexture(GL_TEXTURE_2D, floorTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 1, texxsize, texysize, 0, GL_RED, GL_FLOAT, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); free(tex); glutMainLoop(); return 0; /* ANSI C requires main to return int. */ }