/* Copyright (c) Mark J. Kilgard, 1997. */ /* This program is freely distributable without licensing fees and is provided without guarantee or warrantee expressed or implied. This program is -not- in the public domain. */ /* Unix compile line: cc -o shadowfun shadowfun.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm */ /* THIS PROGRAM REQUIRES GLU 1.2. If you have IRIX 5.3, you need patch 1449 (the IRIX 5.3 GLU 1.2 functionality patch) or its successor to use this program. GLU 1.2 is standard on IRIX 6.2 and later. */ /* This program demonstrates a light source and object of arbitrary geometry casing a shadow on arbitary geometry. The program uses OpenGL's feedback, stencil, and boundary tessellation support. */ #include #include #include #include #include #ifdef GLU_VERSION_1_2 /* Win32 calling conventions. */ #ifndef CALLBACK #define CALLBACK #endif /* Some files do not define M_PI... */ #ifndef M_PI #define M_PI 3.14159265358979323846 #endif const float uniquePassThroughValue = 34567.0; #define SmallerOf(a,b) ((a) < (b) ? (a) : (b)) int stencilBits; /* Display list names. */ enum { /* Display lists should start at 1, not 0. */ DL_BALL = 1, DL_CONE, DL_LIGHT, DL_SHADOW_VOLUME, DL_SPHERE, DL_ICO, DL_TORUS, DL_CUBE, DL_SHADOW_VOLUME_TOP, DL_BASE_SHADOW_VOLUME }; /* Menu option names. */ enum { /* Important for objectMaxRadius array that the shape enums appear first in this list. */ M_TORUS, M_CUBE, M_SPHERE, M_ICO, M_DOUBLE_TORUS, M_ANGLE, M_BOUNDARY, M_NO_SHADOW, M_NO_LIGHT, M_FRONT_VOLUME, M_BACK_VOLUME, M_SHADOW, M_LIGHT_SOURCE_VIEW, M_NORMAL_VIEW, M_SPIN, M_SWING, M_STOP }; /* Coordinates. */ enum { X, Y, Z }; const int TEXDIM = 64; int shape; GLfloat maxRadius; int renderMode = M_SHADOW; int view = M_NORMAL_VIEW; int renderBoundary = 0; GLfloat angle = 0.0; int frontFace = 1; int rotatingObject = 1; int swingingLight = 1; float swingTime = M_PI / 2.0; GLfloat lightDiffuse[4] = {1.0, 0.0, 0.0, 1.0}; GLfloat lightPos[4] = {60.0, 50.0, -350.0, 1.0}; GLfloat objectPos[4] = {40.0, 30.0, -360.0, 1.0}; GLfloat sceneEyePos[4] = {0.0, 0.0, 0.0, 0.0}; struct VertexHolder { struct VertexHolder *next; GLfloat v[2]; }; typedef struct _ShadowVolumeMemoryPool { /* Reference count because ShadowVolumeMemoryPool's can be shared between multiple ShadowVolumeState's. */ int refcnt; GLUtesselator *tess; GLfloat viewScale; /* Memory used for GLU tessellator combine callbacks. */ GLfloat *combineList; int combineListSize; int combineNext; struct VertexHolder *excessList; } ShadowVolumeMemoryPool; typedef struct _ShadowVolumeState { ShadowVolumeMemoryPool *pool; GLfloat shadowProjectionDistance; GLfloat extentScale; /* Scratch variables used during GLU tessellator callbacks. */ int saveFirst; GLfloat *firstVertex; } ShadowVolumeState; ShadowVolumeState *svs; static void CALLBACK begin(GLenum type, void *shadowVolumeState) { ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState; assert(type == GL_LINE_LOOP); if (renderBoundary) { glBegin(type); } else { svs->saveFirst = 1; glBegin(GL_TRIANGLE_FAN); glColor3f(0, 1, 0); glVertex3f(0.0, 0.0, 0.0); } } static void CALLBACK vertex(void *data, void *shadowVolumeState) { ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState; GLfloat *v = data; if (renderBoundary) { glVertex2fv(v); } else { if (svs->saveFirst) { svs->firstVertex = v; svs->saveFirst = 0; } glColor3f(0, 0, 1); glVertex3f(svs->extentScale * v[X], svs->extentScale * v[Y], svs->shadowProjectionDistance); } } static void CALLBACK end(void *shadowVolumeState) { ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState; if (!renderBoundary) { glColor3f(0, 0, 1); glVertex3f(svs->extentScale * svs->firstVertex[X], svs->extentScale * svs->firstVertex[Y], svs->shadowProjectionDistance); } glEnd(); } static void freeExcessList(ShadowVolumeMemoryPool * pool) { struct VertexHolder *holder, *next; holder = pool->excessList; while (holder) { next = holder->next; free(holder); holder = next; } pool->excessList = NULL; } /* ARGSUSED1 */ static void CALLBACK combine(GLdouble coords[3], void *d[4], GLfloat w[4], void **dataOut, void *shadowVolumeState) { ShadowVolumeState *svs = (ShadowVolumeState *) shadowVolumeState; ShadowVolumeMemoryPool *pool = svs->pool; struct VertexHolder *holder; GLfloat *newCoords; if (pool->combineNext >= pool->combineListSize) { holder = (struct VertexHolder *) malloc(sizeof(struct VertexHolder)); holder->next = pool->excessList; pool->excessList = holder; newCoords = holder->v; } else { newCoords = &pool->combineList[pool->combineNext * 2]; } newCoords[0] = coords[0]; newCoords[1] = coords[1]; *dataOut = newCoords; pool->combineNext++; } static void CALLBACK error(GLenum errno) { printf("ERROR: %s\n", gluErrorString(errno)); } static void processFeedback(GLint size, GLfloat * buffer, ShadowVolumeState * svs) { ShadowVolumeMemoryPool *pool = svs->pool; GLfloat *loc, *end, *eyeLoc; GLdouble v[3]; int token, nvertices, i; GLfloat passThroughToken; int watchingForEyePos; if (pool->combineNext > pool->combineListSize) { freeExcessList(pool); pool->combineListSize = pool->combineNext; pool->combineList = realloc(pool->combineList, sizeof(GLfloat) * 2 * pool->combineListSize); } pool->combineNext = 0; watchingForEyePos = 0; eyeLoc = NULL; glColor3f(1, 1, 1); gluTessBeginPolygon(pool->tess, svs); loc = buffer; end = buffer + size; while (loc < end) { token = *loc; loc++; switch (token) { case GL_POLYGON_TOKEN: nvertices = *loc; loc++; assert(nvertices >= 3); gluTessBeginContour(pool->tess); for (i = 0; i < nvertices; i++) { v[0] = loc[0]; v[1] = loc[1]; v[2] = 0.0; gluTessVertex(pool->tess, v, loc); loc += 2; } gluTessEndContour(pool->tess); break; case GL_PASS_THROUGH_TOKEN: passThroughToken = *loc; if (passThroughToken == uniquePassThroughValue) { watchingForEyePos = !watchingForEyePos; } else { /* Ignore everything else. */ fprintf(stderr, "ERROR: Unexpected feedback token 0x%x (%d).\n", token, token); } loc++; break; case GL_POINT_TOKEN: if (watchingForEyePos) { fprintf(stderr, "WARNING: Eye point possibly within the shadow volume.\n"); fprintf(stderr, " Program should be improved to handle this.\n"); /* XXX Write code to handle this case. You would need to determine if the point was instead any of the returned boundary polyons. Once you found that you were really in the clipping volume, then I haven't quite thought about what you do. */ eyeLoc = loc; watchingForEyePos = 0; } else { /* Ignore everything else. */ fprintf(stderr, "ERROR: Unexpected feedback token 0x%x (%d).\n", token, token); } loc += 2; break; default: /* Ignore everything else. */ fprintf(stderr, "ERROR: Unexpected feedback token 0x%x (%d).\n", token, token); } } gluTessEndPolygon(pool->tess); if (eyeLoc && renderBoundary) { glColor3f(0, 1, 0); glPointSize(7.0); glBegin(GL_POINTS); glVertex2fv(eyeLoc); glEnd(); } } /* Three element vector dot product. */ static GLfloat vdot(const GLfloat * v1, const GLfloat * v2) { return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } /* Three element vector cross product. */ static void vcross(const GLfloat * v1, const GLfloat * v2, GLfloat * cross) { assert(v1 != cross && v2 != cross); cross[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]); cross[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]); cross[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]); } void svsFreeShadowVolumeState(ShadowVolumeState * svs) { if (svs->pool) { svs->pool->refcnt--; if (svs->pool->refcnt == 0) { if (svs->pool->excessList) { freeExcessList(svs->pool); } if (svs->pool->combineList) { free(svs->pool->combineList); } if (svs->pool->tess) { gluDeleteTess(svs->pool->tess); } free(svs->pool); } } free(svs); } ShadowVolumeState * svsCreateShadowVolumeState(GLfloat shadowProjectionDistance, ShadowVolumeState * shareSVS) { ShadowVolumeState *svs; ShadowVolumeMemoryPool *pool; GLUtesselator *tess; svs = (ShadowVolumeState *) malloc(sizeof(ShadowVolumeState)); if (svs == NULL) { return NULL; } svs->pool = NULL; if (shareSVS == NULL) { pool = (ShadowVolumeMemoryPool *) malloc(sizeof(ShadowVolumeMemoryPool)); if (pool == NULL) { svsFreeShadowVolumeState(svs); return NULL; } pool->refcnt = 1; pool->excessList = NULL; pool->combineList = NULL; pool->combineListSize = 0; pool->combineNext = 0; pool->tess = NULL; svs->pool = pool; tess = gluNewTess(); if (tess == NULL) { svsFreeShadowVolumeState(svs); return NULL; } gluTessProperty(tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE); gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (void (CALLBACK*)()) begin); gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (void (CALLBACK*)()) vertex); gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (void (CALLBACK*)()) combine); gluTessCallback(tess, GLU_TESS_END_DATA, (void (CALLBACK*)()) end); gluTessCallback(tess, GLU_TESS_ERROR, (void (CALLBACK*)()) error); pool->tess = tess; } else { pool = shareSVS->pool; pool->refcnt++; } svs->pool = pool; svs->shadowProjectionDistance = shadowProjectionDistance; return svs; } int svsGenerateShadowVolume(ShadowVolumeState * svs, void (*renderFunc) (void), int feedbackBufferSizeGuess, GLfloat maxRadius, GLfloat lightPos[3], GLfloat objectPos[3], GLfloat eyePos[3]) { static GLfloat unit[3] = {0.0, 0.0, 1.0}; static GLfloat *feedbackBuffer = NULL; static int bufferSize = 0; GLfloat axis[3], lightDelta[3], eyeDelta[3]; GLfloat nnear, ffar; /* Avoid Intel C keywords. Grumble. */ GLfloat lightDistance, eyeDistance, angle, fieldOfViewRatio, fieldOfViewAngle, topScale, viewScale; GLint returned; if (svs->pool->viewScale == 0.0) { GLfloat maxViewSize[2]; glGetFloatv(GL_MAX_VIEWPORT_DIMS, maxViewSize); printf("max viewport = %gx%g\n", maxViewSize[0], maxViewSize[1]); svs->pool->viewScale = SmallerOf(maxViewSize[0], maxViewSize[1]) / 2.0; } viewScale = svs->pool->viewScale; if (bufferSize > feedbackBufferSizeGuess) { feedbackBufferSizeGuess = bufferSize; } /* Calculate the light's distance from the object being shadowed. */ lightDelta[X] = objectPos[X] - lightPos[X]; lightDelta[Y] = objectPos[Y] - lightPos[Y]; lightDelta[Z] = objectPos[Z] - lightPos[Z]; lightDistance = sqrt(lightDelta[X] * lightDelta[X] + lightDelta[Y] * lightDelta[Y] + lightDelta[Z] * lightDelta[Z]); /* Determine the appropriate field of view. We want to use as narrow a field of view as possible to not waste resolution, but not narrower than the object. Add 50% extra slop. */ fieldOfViewRatio = maxRadius / lightDistance; if (fieldOfViewRatio > 0.99) { fprintf(stderr, "WARNING: Clamping FOV to 164 degrees for determining shadow boundary.\n"); fprintf(stderr, " Light distance = %g, object maxmium radius = %g\n", lightDistance, maxRadius); /* 2*asin(0.99) ~= 164 degrees. */ fieldOfViewRatio = 0.99; } /* Pre-compute scaling factors for the near and far extent of the shadow volume. */ svs->extentScale = svs->shadowProjectionDistance * fieldOfViewRatio / viewScale; glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); nnear = 0.5 * (lightDistance - maxRadius); if (nnear < 0.0001) { fprintf(stderr, "WARNING: Clamping near clip plane to 0.0001 because light source too near.\n"); fprintf(stderr, " Light distance = %g, object maxmium radius = %g\n", lightDistance, maxRadius); nnear = 0.0001; } ffar = 2.0 * (lightDistance + maxRadius); if (eyePos) { eyeDelta[X] = eyePos[X] - lightPos[X]; eyeDelta[Y] = eyePos[Y] - lightPos[Y]; eyeDelta[Z] = eyePos[Z] - lightPos[Z]; eyeDistance = 1.05 * sqrt(eyeDelta[X] * eyeDelta[X] + eyeDelta[Y] * eyeDelta[Y] + eyeDelta[Z] * eyeDelta[Z]); if (eyeDistance > ffar) { ffar = eyeDistance; } } fieldOfViewAngle = 2.0 * asin(fieldOfViewRatio) * 180 / M_PI; gluPerspective(fieldOfViewAngle, 1.0, nnear, ffar); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); /* XXX Need to update "up vector". Degenerate when light directly above or below the object. */ gluLookAt(lightPos[X], lightPos[Y], lightPos[Z], objectPos[X], objectPos[Y], objectPos[Z], 0.0, 1.0, 0.0); /* up is in positive Y direction */ glPushAttrib(GL_VIEWPORT_BIT); glViewport(-viewScale, -viewScale, 2 * viewScale, 2 * viewScale); doFeedback: /* XXX Careful, some systems still don't understand realloc of NULL. */ if (bufferSize < feedbackBufferSizeGuess) { bufferSize = feedbackBufferSizeGuess; /* XXX Add 32 words of slop (an extra cache line) to end for buggy hardware that uses DMA to return feedback results but that sometimes overrun the buffer. Yuck. */ feedbackBuffer = realloc(feedbackBuffer, bufferSize * sizeof(GLfloat) + 32 * 4); } glFeedbackBuffer(bufferSize, GL_2D, feedbackBuffer); (void) glRenderMode(GL_FEEDBACK); (*renderFunc) (); /* Render the eye position. The eye position is "bracketed" by unique pass through tokens. These bracketing pass through tokens let us determine if the eye position was clipped or not. This helps us determine whether the eye position is possibly within the shadow volume or not. If the point is clipped, the eye position is not in the shadow volume. If the point is not clipped, a more complicated test is necessary to determine if the eye position is really in the shadow volume or not. See processFeedback. */ if (eyePos) { glPassThrough(uniquePassThroughValue); glBegin(GL_POINTS); glVertex3fv(eyePos); glEnd(); glPassThrough(uniquePassThroughValue); } returned = glRenderMode(GL_RENDER); #if 0 if (returned == -1) { #else /* XXX RealityEngine workaround. */ if (returned == -1 || returned == feedbackBufferSizeGuess) { #endif feedbackBufferSizeGuess = feedbackBufferSizeGuess + (feedbackBufferSizeGuess >> 1); goto doFeedback; /* Try again with larger feedback buffer. */ } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); /* Restore viewport. */ if (renderBoundary) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(-viewScale, viewScale, -viewScale, viewScale); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); processFeedback(returned, feedbackBuffer, svs); } else { glNewList(DL_BASE_SHADOW_VOLUME, GL_COMPILE); vcross(unit, lightDelta, axis); angle = acos(vdot(unit, lightDelta) / lightDistance) * 180.0 / M_PI; glRotatef(angle, axis[X], axis[Y], axis[Z]); processFeedback(returned, feedbackBuffer, svs); glEndList(); glNewList(DL_SHADOW_VOLUME, GL_COMPILE); glPushMatrix(); glTranslatef(lightPos[X], lightPos[Y], lightPos[Z]); glCallList(DL_BASE_SHADOW_VOLUME); glPopMatrix(); glEndList(); glNewList(DL_SHADOW_VOLUME_TOP, GL_COMPILE); glPushMatrix(); glTranslatef(lightPos[X], lightPos[Y], lightPos[Z]); topScale = (lightDistance + maxRadius) / svs->shadowProjectionDistance; glScalef(topScale, topScale, topScale); glCallList(DL_BASE_SHADOW_VOLUME); glPopMatrix(); glEndList(); } return returned; } GLfloat objectMaxRadius[] = { 8.0 + 2.0, /* M_TORUS */ 12.0 / 2.0 * 1.142, /* M_CUBE */ 8.0, /* M_SPHERE */ 8.0, /* M_ICO */ 8.0 + 2.0, /* M_DOUBLE_TORUS */ }; void renderShadowingObject(void) { static int torusList = 0, cubeList = 0, sphereList = 0, icoList = 0; glPushMatrix(); glTranslatef(objectPos[X], objectPos[Y], objectPos[Z]); glRotatef(angle, 1.0, 0.3, 0.0); switch (shape) { case M_TORUS: if (torusList) { glCallList(torusList); } else { torusList = DL_TORUS; glNewList(torusList, GL_COMPILE_AND_EXECUTE); glutSolidTorus(2.0, 8.0, 8, 15); glEndList(); } break; case M_CUBE: if (cubeList) { glCallList(cubeList); } else { cubeList = DL_CUBE; glNewList(cubeList, GL_COMPILE_AND_EXECUTE); glutSolidCube(12.0); glEndList(); } break; case M_SPHERE: if (sphereList) { glCallList(sphereList); } else { sphereList = DL_SPHERE; glNewList(sphereList, GL_COMPILE_AND_EXECUTE); glutSolidSphere(8.0, 10, 10); glEndList(); } break; case M_ICO: if (icoList) { glCallList(icoList); } else { icoList = DL_ICO; glNewList(icoList, GL_COMPILE_AND_EXECUTE); glEnable(GL_NORMALIZE); glPushMatrix(); glScalef(8.0, 8.0, 8.0); glutSolidIcosahedron(); glPopMatrix(); glDisable(GL_NORMALIZE); glEndList(); } break; case M_DOUBLE_TORUS: if (torusList) { glCallList(torusList); } else { torusList = DL_TORUS; glNewList(torusList, GL_COMPILE_AND_EXECUTE); glutSolidTorus(2.0, 8.0, 8, 15); glEndList(); } glRotatef(90, 0, 1, 0); glCallList(torusList); break; } glPopMatrix(); } void sphere(void) { glPushMatrix(); glTranslatef(60.0, -50.0, -400.0); glCallList(DL_BALL); glPopMatrix(); } void cone(void) { glPushMatrix(); glTranslatef(-40.0, -40.0, -400.0); glCallList(DL_CONE); glPopMatrix(); } void scene(void) { /* material properties for objects in scene */ static GLfloat wall_mat[] = {1.0, 1.0, 1.0, 1.0}; static GLfloat shad_mat[] = {1.0, 0.1, 0.1, 1.0}; glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (view == M_LIGHT_SOURCE_VIEW) { gluPerspective(45.0, 1.0, 0.5, 600.0); } else { gluPerspective(33.0, 1.0, 10.0, 600.0); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (view == M_LIGHT_SOURCE_VIEW) { gluLookAt(lightPos[X], lightPos[Y], lightPos[Z], objectPos[X], objectPos[Y], objectPos[Z], 0.0, 1.0, 0.); /* up is in positive Y direction */ } else { gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.); /* up is in positive Y direction */ } /* Place light 0 in the right place. */ glLightfv(GL_LIGHT0, GL_POSITION, lightPos); /* Note: wall verticies are ordered so they are all front facing this lets me do back face culling to speed things up. */ glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, wall_mat); /* Floor with checkerboard texture. */ glEnable(GL_TEXTURE_2D); glColor3f(0, 0, 0); /* Since we want to turn texturing on for floor only, we have to make floor a separate glBegin()/glEnd() sequence. You can't turn texturing on and off between begin and end calls */ glBegin(GL_QUADS); glNormal3f(0.0, 1.0, 0.0); glTexCoord2i(0, 0); glVertex3f(-100.0, -100.0, -320.0); glTexCoord2i(4, 0); glVertex3f(100.0, -100.0, -320.0); glTexCoord2i(4, 4); glVertex3f(100.0, -100.0, -520.0); glTexCoord2i(0, 4); glVertex3f(-100.0, -100.0, -520.0); glEnd(); glDisable(GL_TEXTURE_2D); /* Walls. */ glBegin(GL_QUADS); /* Left wall. */ glNormal3f(1.0, 0.0, 0.0); glVertex3f(-100.0, -100.0, -320.0); glVertex3f(-100.0, -100.0, -520.0); glVertex3f(-100.0, 100.0, -520.0); glVertex3f(-100.0, 100.0, -320.0); /* Right wall. */ glNormal3f(-1.0, 0.0, 0.0); glVertex3f(100.0, -100.0, -320.0); glVertex3f(100.0, 100.0, -320.0); glVertex3f(100.0, 100.0, -520.0); glVertex3f(100.0, -100.0, -520.0); /* Ceiling. */ glNormal3f(0.0, -1.0, 0.0); glVertex3f(-100.0, 100.0, -320.0); glVertex3f(-100.0, 100.0, -520.0); glVertex3f(100.0, 100.0, -520.0); glVertex3f(100.0, 100.0, -320.0); /* Back wall. */ glNormal3f(0.0, 0.0, 1.0); glVertex3f(-100.0, -100.0, -520.0); glVertex3f(100.0, -100.0, -520.0); glVertex3f(100.0, 100.0, -520.0); glVertex3f(-100.0, 100.0, -520.0); glEnd(); cone(); sphere(); glPushMatrix(); glTranslatef(lightPos[X], lightPos[Y], lightPos[Z]); glCallList(DL_LIGHT); glPopMatrix(); /* Draw shadowing object. */ glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, shad_mat); renderShadowingObject(); } void generateShadowVolume(void) { GLfloat *eyePos; if (view == M_LIGHT_SOURCE_VIEW) { eyePos = lightPos; } else { eyePos = sceneEyePos; } /* XXX The 2048 feedbackBufferGuessSize is large enough to workaround the Octane/Impact bug where if the feedback buffer is under 2048 entries, a buggy hardware feedback path is used. 2048 forces the (bug free) software path. This bug is fixed in IRIX 6.5. */ svsGenerateShadowVolume(svs, renderShadowingObject, 2048, maxRadius, lightPos, objectPos, eyePos); } void display(void) { if (renderBoundary) { glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_LIGHTING); glDisable(GL_DEPTH_TEST); generateShadowVolume(); glEnable(GL_LIGHTING); } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); switch (renderMode) { case M_NO_SHADOW: glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHT0); scene(); break; case M_NO_LIGHT: /* Render scene without the light source enabled (conceptually, the entire scene is "in the shadow"). */ glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHT0); scene(); break; case M_FRONT_VOLUME: case M_BACK_VOLUME: generateShadowVolume(); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHT0); scene(); if (frontFace) { glFrontFace(GL_CW); } else { glFrontFace(GL_CCW); } glCallList(DL_SHADOW_VOLUME); glFrontFace(GL_CCW); break; case M_SHADOW: /* Construct DL_SHADOW_VOLUME display list for the scene's current shadow volume. */ generateShadowVolume(); /* 1st scene pass. */ glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHT0); scene(); /* 1st shadow volume pass: Enable stencil to increment the stencil value of pixels that pass the depth test when drawing the front facing polygons of the shadow volume. Do not update the depth buffer while rendering the shadow volume. */ glDisable(GL_LIGHTING); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glEnable(GL_STENCIL_TEST); glDepthMask(GL_FALSE); glStencilFunc(GL_ALWAYS, 0, 0); glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); glCullFace(GL_FRONT); glCallList(DL_SHADOW_VOLUME); /* 2nd shadow volume pass: Now, draw the back facing polygons of the shadow volume except decrement pixels that pass the depth test. Again, do not update the depth buffer. */ glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); glCullFace(GL_BACK); glCallList(DL_SHADOW_VOLUME); glDisable(GL_CULL_FACE); glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO); glCallList(DL_SHADOW_VOLUME_TOP); glEnable(GL_CULL_FACE); /* Now, pixels that lie within the shadow volume are tagged with a one stencil value. Empty shadowed regions of the shadow volume get incremented, then decremented, to resolve to a net zero stencil value. */ /* 2nd scene pass (render shadowed region): Re-enable update of the depth and color buffer (use GL_LEQUAL for depth buffer so we can over-write depth values again with color. Switch back to backface culling and disable the light source. Only update pixels with a stencil value of one (shadowed). */ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); #if 0 glDepthFunc(GL_EQUAL); #else glDepthMask(GL_TRUE); glDepthFunc(GL_LEQUAL); #endif glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glStencilFunc(GL_EQUAL, 1, 1); glDisable(GL_LIGHT0); glEnable(GL_LIGHTING); scene(); /* Put state back to sane modes. */ glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); glDisable(GL_STENCIL_TEST); break; } } glutSwapBuffers(); } void idle(void) { if (rotatingObject) { angle += 10.0; } if (swingingLight) { swingTime += 0.05; lightPos[X] += 2 * cos(swingTime); } glutPostRedisplay(); } void menu(int value) { switch (value) { case M_TORUS: case M_CUBE: case M_SPHERE: case M_ICO: case M_DOUBLE_TORUS: shape = value; maxRadius = objectMaxRadius[value]; glutPostRedisplay(); break; case M_ANGLE: angle += 10.0; glutPostRedisplay(); break; case M_BOUNDARY: renderBoundary = 1; glutPostRedisplay(); break; case M_FRONT_VOLUME: case M_BACK_VOLUME: frontFace = (value == M_FRONT_VOLUME); /* FALLTHROUGH */ case M_NO_SHADOW: case M_NO_LIGHT: case M_SHADOW: renderBoundary = 0; renderMode = value; glutPostRedisplay(); break; case M_LIGHT_SOURCE_VIEW: case M_NORMAL_VIEW: view = value; glutPostRedisplay(); break; case M_STOP: swingingLight = 0; rotatingObject = 0; glutIdleFunc(NULL); break; case M_SPIN: rotatingObject = 1; glutIdleFunc(idle); break; case M_SWING: swingingLight = 1; glutIdleFunc(idle); break; case 666: svsFreeShadowVolumeState(svs); exit(0); /* NOTREACHED */ break; } } void visible(int vis) { if (vis == GLUT_VISIBLE && swingingLight && rotatingObject) { glutIdleFunc(idle); } else { glutIdleFunc(NULL); } } /* ARGSUSED1 */ void key(unsigned char c, int x, int y) { switch (c) { case 27: /* Escape. */ svsFreeShadowVolumeState(svs); exit(0); break; case 13: /* Return. */ swingingLight = !swingingLight; swingTime = M_PI / 2.0; break; case ' ': /* Space. */ if (rotatingObject || swingingLight) { rotatingObject = 0; swingingLight = 0; glutIdleFunc(NULL); } else { rotatingObject = 1; swingingLight = 1; glutIdleFunc(idle); } break; } } /* ARGSUSED1 */ void special(int key, int x, int y) { switch (key) { case GLUT_KEY_HOME: frontFace = !frontFace; glutPostRedisplay(); break; case GLUT_KEY_UP: lightPos[Y] += 10.0; glutPostRedisplay(); break; case GLUT_KEY_DOWN: lightPos[Y] -= 10.0; glutPostRedisplay(); break; case GLUT_KEY_PAGE_UP: lightPos[Z] += 10.0; glutPostRedisplay(); break; case GLUT_KEY_PAGE_DOWN: lightPos[Z] -= 10.0; glutPostRedisplay(); break; case GLUT_KEY_RIGHT: lightPos[X] += 10.0; glutPostRedisplay(); break; case GLUT_KEY_LEFT: lightPos[X] -= 10.0; glutPostRedisplay(); break; } } /* Create a single component checkboard texture map. */ GLfloat * makeTexture(int maxs, int maxt) { int 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; } void initScene(void) { GLfloat *tex; GLUquadricObj *qobj; static GLfloat sphere_mat[] = {1.0, 0.5, 0.0, 1.0}; static GLfloat cone_mat[] = {0.0, 0.5, 1.0, 1.0}; /* Turn on features. */ glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_CULL_FACE); /* remove back faces to speed things up */ glCullFace(GL_BACK); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); /* Make display lists for sphere and cone; for efficiency. */ qobj = gluNewQuadric(); glNewList(DL_BALL, GL_COMPILE); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, sphere_mat); gluSphere(qobj, 20.0, 20, 20); glEndList(); glNewList(DL_CONE, GL_COMPILE); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cone_mat); glRotatef(-90.0, 1.0, 0.0, 0.0); gluDisk(qobj, 0.0, 20.0, 20, 1); gluCylinder(qobj, 20.0, 0.0, 60.0, 20, 20); glEndList(); glNewList(DL_LIGHT, GL_COMPILE); glDisable(GL_LIGHTING); glColor3f(0.9, 0.9, 0.6); gluSphere(qobj, 5.0, 20, 20); glEnable(GL_LIGHTING); glEndList(); gluDeleteQuadric(qobj); /* load pattern for current 2d texture */ tex = makeTexture(TEXDIM, TEXDIM); glTexImage2D(GL_TEXTURE_2D, 0, 1, TEXDIM, TEXDIM, 0, GL_RED, GL_FLOAT, tex); free(tex); } int main(int argc, char **argv) { int shapeMenu, viewMenu, actionMenu, renderModeMenu; svs = svsCreateShadowVolumeState(1000.0, NULL); glutInitDisplayString("stencil>=1 rgb double depth samples"); glutInit(&argc, argv); glutCreateWindow("shadowfun"); stencilBits = glutGet(GLUT_WINDOW_STENCIL_SIZE); printf("bits of stencil = %d\n", stencilBits); glutDisplayFunc(display); glutVisibilityFunc(visible); glutSpecialFunc(special); glutKeyboardFunc(key); initScene(); shapeMenu = glutCreateMenu(menu); glutAddMenuEntry("Torus", M_TORUS); glutAddMenuEntry("Cube", M_CUBE); glutAddMenuEntry("Sphere", M_SPHERE); glutAddMenuEntry("Icosahedron", M_ICO); glutAddMenuEntry("Double Torus", M_DOUBLE_TORUS); viewMenu = glutCreateMenu(menu); glutAddMenuEntry("Normal view", M_NORMAL_VIEW); glutAddMenuEntry("Light source view", M_LIGHT_SOURCE_VIEW); renderModeMenu = glutCreateMenu(menu); glutAddMenuEntry("With shadow", M_SHADOW); glutAddMenuEntry("With front shadow volume", M_FRONT_VOLUME); glutAddMenuEntry("With back shadow volume", M_BACK_VOLUME); glutAddMenuEntry("Without shadow", M_NO_SHADOW); glutAddMenuEntry("Without light", M_NO_LIGHT); glutAddMenuEntry("2D shadow boundary", M_BOUNDARY); actionMenu = glutCreateMenu(menu); glutAddMenuEntry("Spin object", M_SPIN); glutAddMenuEntry("Swing light", M_SWING); glutAddMenuEntry("Stop", M_STOP); glutCreateMenu(menu); glutAddSubMenu("Object shape", shapeMenu); glutAddSubMenu("Viewpoint", viewMenu); glutAddSubMenu("Render mode", renderModeMenu); glutAddSubMenu("Action", actionMenu); glutAddMenuEntry("Step rotate", M_ANGLE); glutAddMenuEntry("Quit", 666); glutAttachMenu(GLUT_RIGHT_BUTTON); menu(M_DOUBLE_TORUS); glutMainLoop(); return 0; /* ANSI C requires main to return int. */ } #else int main(int argc, char** argv) { fprintf(stderr, "This program requires the new tesselator API in GLU 1.2.\n"); fprintf(stderr, "Your GLU library does not support this new interface, sorry.\n"); return 0; } #endif /* GLU_VERSION_1_2 */