Sign in to follow this  

Need help with shadow volume rendering

This topic is 4276 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am currently trying to get volumetric shadowing into an old, free 3D game (Descent 2). I have followed a paper from NVidia (Everitt/Kilgard) on this. The result looks like this: Somehow the stencil buffer operations do not work as intended. I have no clue why. Please someone help me with this. Here are some instruction to d/l and compile the program (PM me for game data): Source code: http://www.descent2.de/resources/d2x-xl-src.1.7.0.zip The initialization calls to OpenGL are in arch/ogl/ogl_render.c::OglStartFrame(). OpenGL settings are controlled by gameStates.render.nShadowPass (1: render mine, very dimly lit, 2: render shadows, 3: render mine fully lit) The mine rendering calls are in main/render.c::RenderFrame() Object rendering calls are in main/object.c::RenderObject() Lo-res model rendering is in 3d/interp.c. Hi-res model rendering is in main/oof.c. The rendering code itself is pretty self-explanatory, I believe. A little background on how D2 handles the rendering: D2 sets its own projection matrices up and doesn't use OpenGL for this (I have added some rudimentary code to change this, but that is by no means done). Find the data in the global variable viewInfo. D2 uses fixed point arithmetic and multiplies every coordinate with 65536. For convenience, viewInfo contains a few members prefixed with 'f' containing coordinates and values in the range [0.0f .. 1.0f]. If D2 renders a model, it calls G3StartInstanceMatrix() to set up the proper transformation matrix for the model (to offset and rotate it properly). Light positions are kept in gameData.render.lightInfo. gameData.render.pLight holds a pointer to the current light during shadow rendering. When a shadow is rendered, the light position is transformed using the current view transformation data (viewInfo.position and viewInfo.view), so that the shadow volume faces can be computed. You can set arch/ogl/ogl_render::SHADOW_TEST to 1 to have D2X-XL render the shadow volume as semitransparent body instead of doing a regular shadow render. This is just for controlling the general operation of the code. If you also set main/interp.c::SHADOW_TEST to 1, D2X-XL will project the shadow volume to the controlling light, so that you can control proper light positioning. If you set main/interp.c::CONTOUR_OUTLINE to 1, D2X-XL will render a wireframe model of the edges it believes to form the models silhouette. Silhouette edges are those edges between two faces where one model polygon faces the light source and the other one does not. The facing is determined by main/interp.c::G3CheckLightFacing(). Sounds simple enough, but somehow silhouette edge determination doesn't seem to work properly. I cannot figure why. From what I have seen from the D2 code, it should work. The shadow volume faces are quads formed by two points of a silhouette edge, and these two points projected to infinity as seen from the controlling light. I haven't set up an infinity matrix (didn't work right away), but simply multiplied them with a sufficiently big value (aptly named INFINITY). It looks like some shadow polygons are blotting out others, leaving gaps in the shadow projection. Particulary the front cap doesn't seem to be rendered properly. To compile the entire stuff, also get the SDL source code from my site. To turn off SDL_mixer usage, set main/digi.h::USE_SDL_MIXER to 0; thus you will not have to install SDL_mixer. [Edited by - karx11erx on April 3, 2006 11:36:33 AM]

Share this post


Link to post
Share on other sites
You might have better luck getting some help if you do a couple things to help us out:


  1. Explain what we are looking at. It's hard to tell what's wrong with your shadow volume if I don't know how it's supposed to look when it's working. I really can't tell where the casting object in that screen shot is. A description of the picture, or another picture altogether, would probably help.

  2. Provide relevant snippets of code. Although it is possible, I doubt that anyone is going to download the code, setup the 3rd party libraries, get all that code to compile, then find all of the related code to help you with your problem. I would recommend putting the shadow-related code (edge calculation, rendering, etc) in some code blocks so that we can more easily determine if there is something wrong with the code.


I cannot yet think of any obvious solutions. Are you sure you're always defining the vertices of the extended quads in the proper order? Have you tried disabling back-face-culling to see what happens?

It's really hard to tell from the screen shot if the faces are really being extended to infinity (most of the polygons look fairly small) - are you sure you're properly multiplying them? And for that matter, multiplying the proper verts?

I assume you are using z-fail, because you are capping the shadow volumes. Have you tried using the normal [z-pass?] algorithm and disabling the front/back caps, to see if the rest of your polygons are being drawn correctly?

That's all I can think of for now. Good luck [smile].

Share this post


Link to post
Share on other sites
It would be easier to debug into the application and see what's going on, and it's easily set up, but oh well, I never thought anybody would do that. :(

Screen shot with shadowing faces (blue: model, orange: shadow volume):



Screen shot with object:



Some faces aren't stenciled (e.g. the ship's thrusters).

Code setting up OpenGL:


#define GL_INFINITY 0

void OglStartFrame (int bFlat, int bResetColorBuf)
{
if (gameStates.render.nShadowPass) {
#if GL_INFINITY
float infProj [4][4]; //projection to infinity
#endif
float fAmbient;

if (gameStates.render.nShadowPass == 1) { //render unlit scene
#if GL_INFINITY
glMatrixMode (GL_PROJECTION);
memset (infProj, 0, sizeof (infProj));
infProj [1][1] = 1.0f / (float) tan (gameStates.render.glFOV);
infProj [0][0] = infProj [1][1] / (float) gameStates.render.glAspect;
infProj [3][2] = -0.02f; // -2 * near
infProj [2][2] =
infProj [2][3] = -1.0f;
glLoadMatrixf ((float *) infProj);
#endif
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glEnable (GL_DEPTH_TEST);
glDepthFunc (GL_LESS);
glDisable (GL_CULL_FACE);
glCullFace (GL_FRONT); //Weird, huh? Well, D2 renders everything reverse ...
if (gameStates.render.bGlLighting) { //for optional hardware lighting
glEnable (GL_LIGHTING);
glDisable (GL_LIGHT0);
}
}
else if (gameStates.render.nShadowPass == 2) { //render occluders
if (gameStates.render.bGlLighting) {
fAmbient = 0.0f;
glLightModelfv (GL_LIGHT_MODEL_AMBIENT, &fAmbient);
}
glColorMask (0,0,0,0);
glDepthMask (0);
glEnable (GL_BLEND);
glBlendFunc (GL_ONE, GL_ONE);
glEnable (GL_STENCIL_TEST);
//glClearStencil (0);
glClear (GL_STENCIL_BUFFER_BIT);
glStencilMask (~0);
glStencilFunc (GL_ALWAYS, 0, ~0);
//glEnable (GL_POLYGON_OFFSET_FILL);
//glPolygonOffset (1.0f, 2.0f);
}
else { //render final lit scene
//glDisable (GL_POLYGON_OFFSET_FILL);
if (gameStates.render.bAltShadows) { //always FALSE
glStencilFunc (GL_NOTEQUAL, 0, ~0);
glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE);
}
else {
glStencilFunc (GL_EQUAL, 0, ~0);
#if 0
glStencilOp (GL_KEEP, GL_KEEP, GL_INCR); //problem: multitexturing fails
#else
glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
#endif
}
glColorMask (1,1,1,1);
glDepthFunc (GL_EQUAL);
glCullFace (GL_FRONT);
if (gameStates.render.bGlLighting) {
glEnable (GL_LIGHT0);
glLightfv (GL_LIGHT0, GL_POSITION, gameData.render.pLight->glPos);
}
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
}
else {
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();//clear matrix
OglSetFOV (gameStates.render.glFOV);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
#if 0
glOrtho (0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
glScalef (1.0f, -1.0f, 1.0f);
glTranslatef (0.0f, -1.0f, 0.0f);
#endif

OGL_VIEWPORT (grd_curcanv->cv_bitmap.bm_x, grd_curcanv->cv_bitmap.bm_y, Canvas_width, Canvas_height);
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_SMOOTH);
glClear (bResetColorBuf ? (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) : GL_DEPTH_BUFFER_BIT);
if (gameStates.ogl.bEnableScissor) {
glScissor (
grd_curcanv->cv_bitmap.bm_x,
grd_curscreen->sc_canvas.cv_bitmap.bm_h - grd_curcanv->cv_bitmap.bm_y - Canvas_height,
Canvas_width,
Canvas_height);
glEnable (GL_SCISSOR_TEST);
}
else
glDisable (GL_SCISSOR_TEST);
if (bAntiAliasingOk && bAntiAliasing)
glEnable (GL_MULTISAMPLE_ARB);
#ifdef OGL_ZBUF //enable depth buffer
if (bFlat) {
glDisable (GL_DEPTH_TEST);
glDisable (GL_ALPHA_TEST);
glDisable (GL_CULL_FACE);
}
else if (!gameOpts->legacy.bZBuf) {
glEnable (GL_CULL_FACE);
glCullFace (GL_FRONT); //Weird, huh? Well, D2 renders everything reverse ...
glDepthMask (1);
glEnable (GL_DEPTH_TEST);
if (glIsEnabled (GL_DEPTH_TEST)) {
glDepthFunc (GL_LESS);
glEnable (GL_ALPHA_TEST);
if (glIsEnabled (GL_ALPHA_TEST))
glAlphaFunc (GL_GEQUAL, (float) 0.01);
else {
gameOpts->legacy.bZBuf = 1;
glDisable (GL_DEPTH_TEST);
}
}
else
gameOpts->legacy.bZBuf = 1;
}
#endif
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
}












Getting all edges of a poly model:


int G3FindPolyModelEdge (tPOF_subObject *pso, int v0, int v1)
{
int i;
tPOF_edge h;

for (i = 0; i < pso->edges.nEdges; i++) {
h = pso->edges.pEdges [i];
if (((h.v0 == v0) && (h.v1 == v1)) || ((h.v0 == v1) && (h.v1 == v0)))
return i;
}
return -1;
}

//------------------------------------------------------------------------------

int G3AddPolyModelEdge (tPOF_subObject *pso, tPOF_face *pf, int v0, int v1)
{
int b, i = G3FindPolyModelEdge (pso, v0, v1);
tPOF_edge *pe;

if (b = i < 0)
i = pso->edges.nEdges++;
pe = pso->edges.pEdges + i;
if (pe->pf0 && pe->pf1) {
tPOF_edge *ph = pso->edges.pEdges + pso->edges.nEdges++;
ph->v0 = v0;
ph->v1 = v1;
ph->pf0 = pe->pf0;
ph->pf1 = pf;
ph = pso->edges.pEdges + pso->edges.nEdges++;
ph->v0 = v0;
ph->v1 = v1;
ph->pf1 = pe->pf1;
ph->pf0 = pf;
}
else {
pe->v0 = v0;
pe->v1 = v1;
if (pe->pf0)
pe->pf1 = pf;
else
pe->pf0 = pf;
}
return i;
}

//------------------------------------------------------------------------------

int G3GetPolyModelEdges (tPOF_object *po)
{
short i, j, k, v0, v1, v2 = -1;
tPOF_subObject *pso = po->subObjs.pSubObjs;
tPOF_face *pf;
short *pv;

po->edges.nEdges = 0;
for (i = po->subObjs.nSubObjs; i; i--, pso++) {
pso->edges.pEdges = po->edges.pEdges + po->edges.nEdges;
for (j = pso->faces.nFaces, pf = pso->faces.pFaces; j; j--, pf++) {
for (k = 0, pv = pf->pVerts; k < pf->nVerts; k++, pv++) {
v1 = v2;
v2 = *pv;
if (k)
G3AddPolyModelEdge (pso, pf, v1, v2);
else
v0 = v2;
}
G3AddPolyModelEdge (pso, pf, v2, v0);
}
po->edges.nEdges += pso->edges.nEdges;
}
return 1;
}





Getting the silhouette edges:


inline int G3CheckLightFacing (vms_vector *pv, vms_vector *pNorm)
{
tOOF_vector n, v, h;

OOF_VecVms2Oof (&n, pNorm); //converting fix point data to float here
OOF_VecVms2Oof (&v, pv);
return OOF_VecMul (OOF_VecSub (&h, &vLightPos, &v), &n) > 0;
}

//------------------------------------------------------------------------------

int G3GetPolyModelSilhouette (tPOF_object *po, tPOF_subObject *pso)
{
short h, i, j, k;
tPOF_edge *pe;
tPOF_face *pf;
vms_vector *pv, n;
short *pfv;
int f0, f1;

pv = po->pvVerts; //the actual vertices (x,y,z)
for (h = j = k = 0, i = pso->edges.nEdges, pe = pso->edges.pEdges; i; i--, pe++)
if (pe->pf0 && pe->pf1) {
pf = pe->pf0;
pfv = pf->pVerts; //pf->pVerts holds index values into the vertex list po->pvVerts
f0 = pf->bFacingLight = G3CheckLightFacing (pv + *pfv, vm_vec_normal (&n, pv + pfv [0], pv + pfv [1], pv + pfv [2]));
pf = pe->pf1;
pfv = pf->pVerts;
f1 = pf->bFacingLight = G3CheckLightFacing (pv + *pf->pVerts, vm_vec_normal (&n, pv + pfv [0], pv + pfv [1], pv + pfv [2]));
if (pe->bContour = (f0 != f1))
h++;
}
else if (pe->pf0 || pe->pf1) {
if (!(pf = pe->pf0))
pf = pe->pf1;
pfv = pf->pVerts;
pf->bFacingLight = G3CheckLightFacing (pv + *pf->pVerts, vm_vec_normal (&n, pv + pfv [0], pv + pfv [1], pv + pfv [2]));
pe->bContour = 1;
h++;
j++;
}
else
k++;
return pso->edges.nContourEdges = h;
}





Rendering the shadow volume (I also tried a non-culling one-pass version):


#define INFINITY 10000.0f //far clip plane is at 6553.6f

int G3RenderSubModelShadowVolume (tPOF_object *po, tPOF_subObject *pso, int nCullFace)
{
tOOF_vector *pvf, fv0, fv1;
tPOF_edge *pe;
short i;

pvf = po->pvVertsf;
if (nCullFace) {
glCullFace (GL_BACK); //Descent 2 renders everything reverse - don't blame me :P
glStencilOp (GL_KEEP, GL_DECR_WRAP, GL_KEEP);
}
else {
glCullFace (GL_FRONT);
glStencilOp (GL_KEEP, GL_INCR_WRAP, GL_KEEP);
}
glBegin (GL_QUADS);
i = pso->edges.nContourEdges;
for (pe = pso->edges.pEdges; i; pe++)
if (pe->bContour) {
i--;
fv1 = pvf [pe->v1];
glVertex3f (fv1.x, fv1.y, -fv1.z);
fv0 = pvf [pe->v0];
glVertex3f (fv0.x, fv0.y, -fv0.z);
OOF_VecDec (&fv0, &vLightPos);
OOF_VecScale (&fv0, INFINITY / OOF_VecMag (&fv0));
glVertex3f (fv0.x, fv0.y, -fv0.z);
OOF_VecDec (&fv1, &vLightPos);
OOF_VecScale (&fv1, INFINITY / OOF_VecMag (&fv1));
glVertex3f (fv1.x, fv1.y, -fv1.z);
}
glEnd ();
return 1;
}





Rendering the shadow caps:


int G3RenderSubModelShadowCaps (tPOF_object *po, tPOF_subObject *pso, int bCullFront)
{
tOOF_vector *pvf, *phvf, fv0;
tPOF_face *pf;
short *pfv, i, j;

pvf = po->pvVertsf;
glEnable (GL_CULL_FACE);
if (bCullFront) {
glCullFace (GL_BACK);
glStencilOp (GL_KEEP, GL_DECR_WRAP, GL_KEEP);
}
else {
glCullFace (GL_FRONT);
glStencilOp (GL_KEEP, GL_INCR_WRAP, GL_KEEP);
}
for (i = pso->faces.nFaces, pf = pso->faces.pFaces; i; i--, pf++) {
pfv = pf->pVerts;
if (pf->bFacingLight) {
glBegin (GL_TRIANGLE_FAN);
for (j = pf->nVerts, pfv = pf->pVerts; j; j--, pfv++) {
phvf = pvf + *pfv;
glVertex3f (phvf->x, phvf->y, -phvf->z);
}
glEnd ();
}
else {
glBegin (GL_TRIANGLE_FAN);
for (j = pf->nVerts, pfv = pf->pVerts; j; j--, pfv++) {
OOF_VecSub (&fv0, pvf + *pfv, &vLightPos);
OOF_VecScale (&fv0, INFINITY / OOF_VecMag (&fv0));
glVertex3f (fv0.x, fv0.y, -fv0.z);
}
glEnd ();
}
}
return 1;
}





Rendering the models shadow (by submodels):


//------------------------------------------------------------------------------

int G3DrawSubModelShadow (tPOF_object *po, tPOF_subObject *pso, int bCullFront)
{
int h, i;

h = pso - po->subObjs.pSubObjs;
for (i = 0; i < po->subObjs.nSubObjs; i++)
if (po->subObjs.pSubObjs [i].nParent == h)
G3DrawSubModelShadow (po, po->subObjs.pSubObjs + i, bCullFront);
G3GetPolyModelSilhouette (po, pso);
return
G3RenderSubModelShadowVolume (po, pso, 0) &&
G3RenderSubModelShadowCaps (po, pso, 0) &&
G3RenderSubModelShadowVolume (po, pso, 1) &&
G3RenderSubModelShadowCaps (po, pso, 1);
#endif
}

//------------------------------------------------------------------------------

int G3DrawPolyModelShadow (object *objP, void *modelP, vms_angvec *pAnimAngles, tPOF_object *po)
{
int j;
vms_vector *pv, vh;
tOOF_vector *pvf;
short *pnl;
int bCullFront;

//the following code gets the model data from some really whacked format to
//something I can handle. po->nState = 0: Unitialized, 1: allocated, no data yet
//2: Initialized, need only to read projected vertices from source model data every frame
G3StartInstanceMatrix (&objP->pos, &objP->orient); //setup proper model projection
if (!(po->nState || G3AllocPolyModelItems (modelP, po)))
return 0;
if (po->nState == 1) {
po->nVerts = 0;
//po->faces.nFaces = 0;
G3GetPolyModelItems (modelP, pAnimAngles, po, 1);
G3GetPolyModelEdges (po);
po->nState = 2;
}
po->nVerts = 0;
G3GetPolyModelItems (modelP, pAnimAngles, po, 0);
G3DoneInstance ();

glActiveTexture (GL_TEXTURE0_ARB);
glEnable (GL_TEXTURE_2D);
glColor4f (shadowColor.red, shadowColor.green, shadowColor.blue, shadowColor.alpha); //for testing
pnl = gameData.render.nNearestLights [objP->segnum];
//loop over lights closest to this object
for (gameData.render.nLight = 0;
(gameData.render.nLight < gameOpts->render.nMaxLights) && (*pnl >= 0);
gameData.render.nLight++, pnl++) {
gameData.render.pLight = gameData.render.lightInfo + *pnl;
//project the light position according to viewer position and facing
G3RotatePointToVec (&vmsLightPos, &gameData.render.pLight->pos);
OOF_VecVms2Oof (&vLightPos, &vmsLightPos);
//convert all model vertices from fix point to float for convenience
for (j = po->nVerts, pv = po->pvVerts, pvf = po->pvVertsf; j; j--, pv++, pvf++) {
pvf->x = f2fl (pv->x); //f2fl divides by 65536.0f
pvf->y = f2fl (pv->y);
pvf->z = f2fl (pv->z);
}
G3DrawSubModelShadow (po, po->subObjs.pSubObjs);
}
glDisable (GL_TEXTURE_2D);
return 1;
}





[Edited by - karx11erx on April 4, 2006 4:06:15 AM]

Share this post


Link to post
Share on other sites

This topic is 4276 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this