Attenuated shadow volumes

Hi,

I am wondering what is the normal way shadow volumes are done for attenuated lights. It seems a great waste of fill rate to project the volumes to infinity (far clip plane). If you clipped the volume to the light’s sphere of influence it would go much faster.

I’m sure I read somewhere that JC is doing this in Doom3 but I can’t find much else in the way of a mention of it.

I have tried a few approaches one of which did have the desired effect. For each triangle that intersects the light’s sphere, classify according to how many vertices lie inside the sphere. either 0, 1, 2 or 3. Each case was a piece of code that dealt with each situation, and produced the smallest possible volume.

The problem really though is combining it with possible shadow silhouettes to really speed it up. Obviously my solution is a bit naive and there needs to be a more general one.

Thanks in advance for any help,
Richard

a good optimization is to clip your shadowvolume in screenspace.(keyword: scissor rectangle)
so you can define infinite volumes, but draw only needed parts of the volume.

do a search on this forum for:
“siluette clipping” or
“shadow volumes”

Hey thanks, that sounds like a good idea. So basically you would project your sphere onto the screen, then create a 2d bounding box in window space around it and use normal glScissor?

I just found http://www.flipcode.com/cgi-bin/msg.cgi?showThread=00005303&forum=3dtheory&id=-1
and it looks like he puts an OBB around the sphere then projects that to the screen. Is that a better way to do it?

Thanks,
Richard

if the boundingbox is axisaligned to the screen, yes… why? because its rather difficult to project a sphere… but its not to project a cube… (if its not screenaligned, it will get larger than needed due to projection, i think, never tried )

There is a post somewhere on this board which is quite recent i think where i posted some code that will compute the values that glScissor needs for doing just this. Also in that same post someone else posted some code that also computes this bounding box. His code was quite a bit different than mine but both work.

While the attenuated light does make a sphere of light, when you compute the box, you dont need to worry about a sphere. Just a circle really. A circle that has the radius of the light. Just check my code in the other post i talked about and you should see what i mean there. Well…I say circle but it may be more correct if i were to say a thin slice of the sphere right down the middle. Imagine this and it will look like a circle.

Basically what i do is compute three points on this circle which is centered at the light position and has the radius of my light and take those three points (which are of course 3D points) and put those into gluProject to project them to screen space. Take those, compute a bounding box from that, then take those bounding box points and do a few simple math ops to setup what glScissor needs. Again my code should clearly show what im trying to describe here.

-SirKnight

That won’t work SirKnight. Acutally, a sphere ( perspectively ) projected onto a plane is not a circle. I had the same error in my own code, I just use a box now ( the sphere code that I worked on would have been too expensive ).

Well poo. I forgot to take in account for perspective divide. I guess thats what i get for not trying it out first. I did the math on paper and it worked great, but i didnt realize that i was not taking into account the perspective. My math would work if it was not perspective. I see this now, thanks for the heads up on that PH! O well, i know how i can do this using a box like you say that will probably end up faster anyway.

Ok so dont worry about looking at my code on that other post and forget my other post i just made up there.

-SirKnight

Hmm. I’ve been think about this, are you sure the way i do it wont work? Maybe take a look at my code. Because i compute those points on that circle there then feed that into gluProject. I dont project anything myself before the gluProject call. Or unless this ‘light sphere’ to begin with is already not a roundish sphere. Maybe because of the perspective frustum its more like a…there is a better word for this…3d oval? heh.

-SirKnight

Ok to make your life easier here is the code i have.

void ProjectLightForScissor( const float radius, VECTOR lpos, float *x, float *y, float *width, float *height )
{
GLint viewport[4];
GLdouble mvmatrix[16], projmatrix[16];

double px, py, pz;

vector_t r1, r2, r3, r4;
vector_t p1, p2, p3, p4;
vector_t up( 0, 1, 0 ), right( 1, 0, 0 );
vector_t tempResult, projPt;
projPt.z = 0.0f;

vector_t vec_zero_one( 0, 1 ), vec_negone_zero( -1, 0 ), vec_zero_negone( 0, -1 );

//p1 = lpos + ( radius * ( ( cos(0) * right ) + ( sin(0) * up ) ) );
//p1 = lpos + ( right * radius );
VectorMAD( &p1, right, radius, lpos );

//p2 = lpos + ( radius * ( ( cos(PI / 2) * right ) + ( sin(PI / 2) * up ) ) );
//p2 = lpos + ( up * radius );
VectorMAD( &p2, up, radius, lpos );

//p3 = lpos + ( radius * ( ( cos(PI) * right ) + ( sin(PI) * up ) ) );
//p3 = lpos + ( -right * radius );
VectorInvert( &tempResult, right );
VectorMAD( &p3, tempResult, radius, lpos );

//p4 = lpos + ( radius * ( ( cos(3 * PI / 2) * right ) + ( sin( 3 * PI / 2) * up ) ) );
//p4 = lpos + ( radius * -up ); // Not used

//
/// Get the matricies and viewport that gluProject needs to project to window space
//
glGetIntegerv( GL_VIEWPORT, viewport );
glGetDoublev( GL_MODELVIEW_MATRIX, mvmatrix );
glGetDoublev( GL_PROJECTION_MATRIX, projmatrix );

//
/// Project points to window space
//
gluProject( p1.x, p1.y, p1.z, mvmatrix, projmatrix, viewport, &px, &py, &pz );
//r1 = VECTOR( px, py ) + ( VectorMUL( VECTOR( 0, 1 ), radius ) );
projPt.x = (float)px; projPt.y = (float)py;
VectorMAD( &r1, vec_zero_one, radius, projPt );

gluProject( p2.x, p2.y, p2.z, mvmatrix, projmatrix, viewport, &px, &py, &pz );
//r2 = VECTOR( px, py ) + ( VectorMUL( VECTOR( -1, 0 ), radius ) );
projPt.x = (float)px; projPt.y = (float)py;
VectorMAD( &r2, vec_negone_zero, radius, projPt );

gluProject( p3.x, p3.y, p3.z, mvmatrix, projmatrix, viewport, &px, &py, &pz );
//r3 = VECTOR( px, py ) + ( VectorMUL( VECTOR( 0, -1 ), radius ) );
projPt.x = (float)px; projPt.y = (float)py;
VectorMAD( &r3, vec_zero_negone, radius, projPt );

// Not used
//gluProject( p4.x, p4.y, p4.z, mvmatrix, projmatrix, viewport, px, py, pz );
//r4 = vec2_t( px, py ) + ( radius * vec2_t( 1, 0 ) );

//
/// Setup the data for glScissor
//
*x = r3.x;
*y = r3.y;

*width = r1.x - r2.x;
*height = r2.y - r3.y;
}

-SirKnight

Here’s something slightly related to the thread ( in case you’re interested ),
http://www.cs.unc.edu/~hoff/research/pxfl/sphere_deriv/persprad.html

Pretty cool page. Ok now maybe i’m just retarded but like it has been said here and on that page that when all of the math has been done on a sphere that it is not a sphere anymore but a ‘warped’ ellispe. So why when i draw a sphere with, for example the glutSphere or whatever function, that when its rendered it is still round looking and not look like a warped ellipse? The vertices generated by that function still go through all those transformations to window space and beyond too. Maybe im just stupid but i just am not seeing it here.

-SirKnight

Also something i meant to mention before is that my ‘projectlightsphere’ function was based from knoledge gained from the function that does the same thing in that quake 1 mod that adds shadows and stuff, you know the one thats on the front page of opengl.org. Just wanted to throw that in.

-SirKnight

Hrmm…now wait just a ‘cotton-pickin’ minute. The perspective view frustum is wider than it is tall. Cuz for most normal resolutions the aspect ratio is 1.333333333333333333333 Meaning of course the width is a tiny bit wider than the height. So…is this the reason a sphere projected onto a xy plane in there causes a sphere to be more elliptical? Of course if so im still confused why when i draw a sphere why it still looks round and not elliptical. Well to me it does.

-SirKnight

OK well I’ve got my infinite perspective matrix, and It seems to work. Theres one thing I don’t understand though. For the projected vertices if I use a w value of 0.0f it gives flickering results all over the scene, but if I use 0.0001f it seems OK. Any idea why?

Also there are little pixel sized gaps appearing in shadows somtimes which looks bad how do I fix this?

I’m about to try the scissoring approach, thanks for explaining it.

Richard

Richard,

In some implementations, you can have LSB problems that cause some infinite geometry to be clipped. You can fix this as you describe, or by adjusting the infinite projection matrix. I suggest the latter, because the math is simpler. :slight_smile:

Thanks -
Cass

Yeah I remember talking about that problem. Cass’s demo shows how to fix this my modding the Pinf matrix by including a ‘slop.’

The ‘slop’ just makes the far plane at infinity not as touchy from like Cass said, LSB problems.

Also there are little pixel sized gaps appearing in shadows somtimes which looks bad how do I fix this?

Well I remember that in Cass’s demo that he used polygon offset. Maybe this is why? I havn’t noticed that yet but then again, my geometry is extremely simple. Just a box in a room.

Welp, back to the drawing board.

-SirKnight

[This message has been edited by SirKnight (edited 09-04-2002).]

Thanks for the tip, scaling those two value of the projection matrix by 0.9f allowed a w value of 0.0f. I do remember reading about this a while ago but thought it was a Radeon problem … obviously not :slight_smile:

I still have the problem of tiny slithers of 1-2 pixels occasionally appearing in the shadowed area but it’s not too bad.

OK, I have been extruding each polygon individually until now. I have adjacency information, but when I added a check to only extrude edges which define a possible silhouette edge and to extrude polygons that face away from the viewer, the results get mangled. Is there a trick to making the silhouette stuff work with shadow volumes? I’ve only ever managed it with the zpass approach.

Thanks,
Richard

Could you post maybe a screenshot and/or piece of your code that does this?

BTW, about the whole projecting a sphere thing. I think (now that i have gotten some sleep ) that I see why projecting the sphere, or those points on a circle like i did really, why it is kind of warped and more elliptical like. After the projection to screen (or window) space, the points and stuff are now in NDC (Normalized Device Coordinates) which is on a plane that is square, unlike how the view frustum (projective) normally looks with it being wider than it is tall. So when things are projected to a square plane like that, things will get a bit warped. It’s not that much so i dont see how that can be much of a problem. But i see how projecting a box is a bit better and faster now so i think ill just go with that. Could anyone verify what i just said there so i know im not thinking a buch of crap?

-SirKnight

I still have the problem of tiny slithers of 1-2 pixels occasionally appearing in the shadowed area but it’s not too bad.

This can happen if you create volumes seperately for each polygon. It will go away when you use the silhouette to create the volume.

OK, I have been extruding each polygon individually until now. I have adjacency information, but when I added a check to only extrude edges which define a possible silhouette edge and to extrude polygons that face away from the viewer, the results get mangled. Is there a trick to making the silhouette stuff work with shadow volumes? I’ve only ever managed it with the zpass approach.

Using z-fail instead of z-pass is almost the same. You need 3 sets of polygons, the silhouette quads ( just like in the z-pass approach ) and a front and back cap to close this open volume. The front cap is just the front-facing triangles ( wrt. the light ). The back cap is either made of the projected back-facing triangles ( wrt. the light ) OR the projected front-facing triangles inverted.

You can mix z-fail and z-pass ( a nice optimization ). This can cause some strange artifacts near the silhouette on the Radeon 8500 but not on GeForce3. I use polygon offset with the z-pass approach to solve this ( I’m not too happy about this but it works ).

[This message has been edited by PH (edited 09-04-2002).]