Sign in to follow this  
Dragon_Strike

splitting frustum for shadowmaps

Recommended Posts

im doin some experimenting on splitting the lightfrustum to fit the view frustum.. similar to cascaded shadowmaps and PSSM.. but im unsure how these techniques calculate the view frustun... so ive tried doin it myself... basicly since im using ortho projection for my light im using boxes to define the light frustum... the box is set by -near,far,size = furstumsize/2 i split the viewfrustum using 3 splitplanes... and then put a box inbetween two splitplanes and set the size to fit the viewfrustum in all cases... something like this.. Image Hosted by ImageShack.us is this the best way to do it? seems to me like alot of space is wasted... [Edited by - Dragon_Strike on June 1, 2007 2:04:59 PM]

Share this post


Link to post
Share on other sites
Damn good picture.. I'm working out the same thing on my project. Your picture seems logical in 2 dimensions, but I think if you looked at it from the camera view with the light shining down and acrost the view frustum.. you would notice regions of the frustum get excluded from light calculations.

Maybe I'm wrong but that is what I am finding.

Please keep in touch with me if you find out any solutions, I will do the same.

Or you can check up on my blog hopefully I'll have CSM's figured out by the end of next week.

-Matt

Share this post


Link to post
Share on other sites
it works fine in 3d since its centered at the midpoint... but i dont think the box thing is right since it will skip objects that might cause shadows before the view.. so i think i should move the near clip furrther away from the midpoint

Share this post


Link to post
Share on other sites
Alright, spent some time playing applying your picture to my code. There definately is some extremly wasted depth map space. Right now I'm calculating a cube shaped orthogonal depth map with the worst case radius (as illustrated in your picture). I'm also linearly dividing the view frustum depths which I'm sure is not optimal.

I'm pretty sure, from remembering what I've read and actualy playing with it, that instead of only considering the worst case, all 8 corners defining the depth map's view frustum region should be translated into the lights space so that all 6 planes of the depth projection can be set as tightly as possible.

Here are some pictures of what I've done, thanks for the inspiration and hopefully we can knock this thing out.

Photo Sharing and Video Hosting at Photobucket
Photo Sharing and Video Hosting at Photobucket
Photo Sharing and Video Hosting at Photobucket

Share this post


Link to post
Share on other sites
the problem here is that u always have to render in the direction the light is facing... is there any way u could do shadowmapping W/O rendering in the direction of the light and only use the light projectionmatrix for setting the depth...

EDIT:: i dont think that something is better then its worst case... i dont see any point in switching between good<->bad depending on the viewdir..

btw.. what shadowmap depth are u using? or would recommend?


im just wondering but would it be possible as a speed up to only update one shadowmap each frame? that would mean if u have 4 shadowmaps itd take 4 frames to update the entire shadowinformation... and itd be "4 times faster" without any noticeable visual costs... or?

[Edited by - Dragon_Strike on June 2, 2007 6:22:37 AM]

Share this post


Link to post
Share on other sites
The way that I do it is to project bounding volumes of the various splits into 2D light space and "zoom" the standard shadow projection matrix in on the relevant 2D rectangle. This seems to work pretty well in practice, although I'm sure that one could do better if they thought about it for a while.

Quote:
Original post by Dragon_Strike
the problem here is that u always have to render in the direction the light is facing... is there any way u could do shadowmapping W/O rendering in the direction of the light and only use the light projectionmatrix for setting the depth...

No - you have to render from the light's point of view to get proper occlusion.

Quote:
Original post by Dragon_Strike
im just wondering but would it be possible as a speed up to only update one shadowmap each frame? that would mean if u have 4 shadowmaps itd take 4 frames to update the entire shadowinformation... and itd be "4 times faster" without any noticeable visual costs... or?


You can certainly do this, but all of the implementations that I've seen that use such an optimization look terrible; i.e. it's very obvious. In particular split edge problems become even more obvious due to increased temporal incoherence. Such a technique will also play very poorly with multi-gpu setups like SLI or Crossfire since it will force at least one shadow map to be transferred between the GPUs every frame.

Share this post


Link to post
Share on other sites
what you want is a light view frustum that only encloses the split. It should be a rectangle that is "fixed" to the camera direction. So when you look from above in 2D space on it, you see rectangles overlapping slightly, enclosing the splits. This way you loose only the corners of the rectangles that are towards the viewer.
I haven't found the "right" way to construct a light view frustum, but I am convinced now that there is always some shadow map resolution loss involved, independently how hard you try.
If the light view frustum is closer than the viewer's frustum slice you enclose two things actually have happened:
- you lost resolution
- you draw more than you should

The later can be solved by involving culling planes that are parallel to the viewer's view frustum. This way you can cull out objects that would lie in the wasted corners of the light view frustum.

- Wolf

Share this post


Link to post
Share on other sites
It should be noted that if you combine a splitting scheme with a warping scheme (like TSM, LiPSM, etc) you can probably get more uniform usage of the shadow map(s). That said I've found the latter to be a bit overkill in most of my tests, but it will of course depend on your scene.

Share this post


Link to post
Share on other sites
Great picture, Dragon_Strike.

I'm in the process of implementing the exact same thing (I'm sure we're all doing this type of shadowing these days). I agree that finding the optimal light frustum for a given split is not well addressed by much of the literature. Seems you've found a method that will work in all cases, but is unsatisfyingly suboptimal. I'm in the same boat.

It occurs to me that by rotating the light's orthogonal frustum about the Z axis you could find an orientation which would allow it to be shrunk some, but I have no idea how to determine that optimal rotation.

And of course, as noted in the PSSM paper, it may be worth looking into fitting the light frustum to the bounding volume of the shadow casters in the split, rather than the split frustum itself.

Share this post


Link to post
Share on other sites
Dragon_strike, you would not be switching between the worst and non worse scenerio, it would be the same algorithm the whole time which in the worse case could produce the above projections, but in the best case would provide much tighter bounds.

Also right now I'm setting my ortho near and far planes to -radius, radius, essentially creating a cube around the shadowed region. massive waste of depth since my shadow maps start WAYYY in the sky.. I think I'm going to add a "shadow safe height" that the depth map frustums will never exceed as it's not necessary.

Share this post


Link to post
Share on other sites
thx for all the suggestions...

the main bottle neck here is the problem with multiple renderpasses... would it be possible to render to all 3 shadowsmaps in a single pass with multiple rendertargets?

Share this post


Link to post
Share on other sites
Quote:
Original post by Dragon_Strike
the main bottle neck here is the problem with multiple renderpasses... would it be possible to render to all 3 shadowsmaps in a single pass with multiple rendertargets?

In DX10 using instancing or geometry shader cloning and the output render target index you can do this. You cannot in DX9 because you need to actually transform and rasterize the geometry differently each time.

I'm not sure how much more efficient it will be to do it in a single pass... arguably the CPU overhead of rendering a shadow map is pretty low anyways (few, if any state changes, and low draw call cost in DX10).

Share this post


Link to post
Share on other sites
Here's some progress I've made, currently I project the 8 points of the view frusum shadow region into the light's space and set my orthogonal bounds to that.

Improvements: In the code you will notice I was going to clamp the depth map depth range to a world max and min. I decided to skip this and wait until I set the depth map bounds based on light space culled geometry. Or posibly upgrade to some perspective/warped light projection.

Image:


Video: Click (google video)

Source:

void cViewable::ConfigureDepthMaps(cDepthMap *maps, int mapcnt, float *lightdir)
{
//shadow targets
float vec[] = {0,0,0}, vec2[3];
Vector temp, targ, corners[8];
float clampedfar = min(farplane, 500);;
float range = clampedfar - nearplane;
float *splits = 0;

splits = new float[mapcnt+1];

Matrix view, light;

for(int i = 0; i <= mapcnt; i++)
{
float expo = i/(float)mapcnt; //3 steps: 0, .33, .66, 1
float val10 = pow(10, expo); //10^expo , 1, 2.13, 4.57, 10
float newinterp = (val10 - 1.0f) / 9.0f; //scale [1,10] to [0,1]
splits[i] = nearplane - range * newinterp;
}
for(int i = 0; i < mapcnt; i++)
{
//far corners of shadow region
vec2[2] = splits[i+1]; //depth
vec2[1] = splits[i+1] * tang; //height
vec2[0] = splits[i+1] * tang * ratio; //width
corners[0].set(vec2);
vec2[2] = splits[i+1]; //depth
vec2[1] = splits[i+1] * tang; //height
vec2[0] = -splits[i+1] * tang * ratio; //width
corners[1].set(vec2);
vec2[2] = splits[i+1]; //depth
vec2[1] = -splits[i+1] * tang; //height
vec2[0] = -splits[i+1] * tang * ratio; //width
corners[2].set(vec2);
vec2[2] = splits[i+1]; //depth
vec2[1] = -splits[i+1] * tang; //height
vec2[0] = splits[i+1] * tang * ratio; //width
corners[3].set(vec2);
//near cornes of shadow region
vec2[2] = splits[i]; //depth
vec2[1] = splits[i] * tang; //height
vec2[0] = splits[i] * tang * ratio; //width
corners[4].set(vec2);
vec2[2] = splits[i]; //depth
vec2[1] = splits[i] * tang; //height
vec2[0] = -splits[i] * tang * ratio; //width
corners[5].set(vec2);
vec2[2] = splits[i]; //depth
vec2[1] = -splits[i] * tang; //height
vec2[0] = -splits[i] * tang * ratio; //width
corners[6].set(vec2);
vec2[2] = splits[i]; //depth
vec2[1] = -splits[i] * tang; //height
vec2[0] = splits[i] * tang * ratio; //width
corners[7].set(vec2);

//target
vec[0] = 0; //right down the pipe
vec[1] = 0; //height at center
vec[2] = (splits[i+1] + splits[i]) / 2.0; //-Z
targ.set(vec);

//move targets to world coordinates
view.set(transposeMatrix);
targ.transform(view);

//set map at target
maps[i].SetTranslation(targ.m_vector);
maps[i].SetRollToAxis(lightdir);

light.set(maps[i].inverseT);

//move points into light space
float maxx = 0; //get x range
float minx = 0;
float maxy = 0; //y range
float miny = 0;
float maxd = 0; //depth range
float mind = 0;

//float maxheight = 0;
//float minheight = 0;


for(int p = 0; p < 8; p++)
{
corners[p].transform(view); //tranform to world space

//corners[p].m_vector[1] = max(minheight, min(maxheight, corners[p].m_vector[1]));

corners[p].transform(light);

if (corners[p].m_vector[0] < minx) minx = corners[p].m_vector[0];
if (corners[p].m_vector[0] > maxx) maxx = corners[p].m_vector[0];

if (corners[p].m_vector[1] < miny) miny = corners[p].m_vector[1];
if (corners[p].m_vector[1] > maxy) maxy = corners[p].m_vector[1];

if (corners[p].m_vector[2] < mind) mind = corners[p].m_vector[2];
if (corners[p].m_vector[2] > maxd) maxd = corners[p].m_vector[2];
}

maps[i].SetSize(minx, maxx, miny, maxy);
maps[i].ChangeFarPlane(mind);
maps[i].ChangeNearPlane(maxd);
}

delete[] splits;
}





Share this post


Link to post
Share on other sites
im not all that good with matrix math.. even worse when its in code.. could u explain to me how u calculate the maxx,minx,maxy,miny bounds for the ortho projection?

u first find the 8 points defining the view volume to be included... then multiply these by the light's modelviewmatrix and get the max coordinates from these.. or something like that?

Share this post


Link to post
Share on other sites
Quote:
Original post by Dragon_Strike
u first find the 8 points defining the view volume to be included... then multiply these by the light's modelviewmatrix and get the max coordinates from these.. or something like that?


Close, you must multiply the points by the light's *inverse* modelview matrix, to transform the points into light space. Then yes, just find the range of coordinates in that space and set your ortho rectangle around them.

Just to clarify, what I mean by inverse modelview, would be the inverse translation and rotation of the light. You could use gluLookAt to generate this, though I do mine manually.

Share this post


Link to post
Share on other sites
Quote:
Original post by honayboyz
Quote:
Original post by Dragon_Strike
u first find the 8 points defining the view volume to be included... then multiply these by the light's modelviewmatrix and get the max coordinates from these.. or something like that?


Close, you must multiply the points by the light's *inverse* modelview matrix, to transform the points into light space. Then yes, just find the range of coordinates in that space and set your ortho rectangle around them.

Just to clarify, what I mean by inverse modelview, would be the inverse translation and rotation of the light. You could use gluLookAt to generate this, though I do mine manually.


glulookat and glGetDoublev(GL_MODELVIEW_MATRIX, ViewMatrix); gives the inverse modelview then...?

Share this post


Link to post
Share on other sites
Quote:
warping scheme
AndyTX: warping usually increases depth aliasing ... only works with probability based shadows as long as they do not have light bleeding issues. This is the standard argument of game developers to not use any warping.

Share this post


Link to post
Share on other sites
Quote:
Original post by wolf
Quote:
warping scheme
AndyTX: warping usually increases depth aliasing ... only works with probability based shadows as long as they do not have light bleeding issues. This is the standard argument of game developers to not use any warping.

I mean spatial (x/y) warping not depth warping. That shouldn't affect depth aliasing at all and is entirely unrelated to filtering (light bleeding, probabilistic, etc). The only thing to note is that if you warp the shadow map you also need to warp the filter kernels... this is automatically handled if you're using hardware filtering or derivatives, but it needs to be done properly if you're - for example - prefiltering the shadow map.

Depth warping is interesting in its own right, but it's unrelated to this discussion.

In any case I tend to agree that spatial warping is overkill for most cases, but it's a technique that's available and the benefits and trade-off are discussed in depth in the "Warping and Partitioning for Low Error Shadow Maps" paper.

Share this post


Link to post
Share on other sites
ok ive tried calculating the view frusutm points from the lightsmodelviewmatrix... its ok.. but its not right.. this is how it looks...

Image Hosted by ImageShack.us

as u can see it doesnt incldue the entire viewfrustum...

the big yellow sphere is the lightpos... the 8 green spheres are the viewpoints that are to be bound...

here is the code

>




mat4 ModelView(ViewMatrix[0],ViewMatrix[1],ViewMatrix[2],ViewMatrix[3],
ViewMatrix[4],ViewMatrix[5],ViewMatrix[6],ViewMatrix[7],
ViewMatrix[8],ViewMatrix[9],ViewMatrix[10],ViewMatrix[11],
ViewMatrix[12],ViewMatrix[13],ViewMatrix[14],ViewMatrix[15]);

// FurstumPoints[] n 0 -> 7, gives the 8 poitns to be bound
for (int n = 0; n < 8; n++)
{
RenderSphere(FurstumPoints[n], 5.0f, 10,10);
FurstumPoints[n] = FurstumPoints[n] * ModelView;

}

maxx = 0;
minx = 0;
maxy = 0;
miny = 0;
maxz = 0;
minz = 0;

for (int n = 0; n < 8; n++)
{
maxx = max(maxx, FurstumPoints[n].x);
minx = min(minx, FurstumPoints[n].x);
maxy = max(maxy, FurstumPoints[n].y);
miny = min(miny, FurstumPoints[n].y);
maxz = max(maxz, FurstumPoints[n].z);
minz = min(minz, FurstumPoints[n].z);
}





float Matrix[16];

mat4(float _0, float _1, float _2, float _3,
float _4, float _5, float _6, float _7,
float _8, float _9, float _10, float _11,
float _12, float _13, float _14, float _15)
{
Matrix[0] = _0; Matrix[1] = _1; Matrix[2] = _2; Matrix[3] = _3;
Matrix[4] = _4; Matrix[5] = _5; Matrix[6] = _6; Matrix[7] = _7;
Matrix[8] = _8; Matrix[9] = _9; Matrix[10] = _10; Matrix[11] = _11;
Matrix[12] = _12; Matrix[13] = _13; Matrix[14] = _14; Matrix[15] = _15;
}

mat4 operator*(const mat4& mat) const
{
const float* m1 = this->Matrix;
const float* m2 = mat.Matrix;

return mat4(

(m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3]),
(m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3]),
(m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3]),
(m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3]),

(m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7]),
(m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7]),
(m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7]),
(m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7]),

(m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11]),
(m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11]),
(m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11]),
(m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11]),

(m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15]),
(m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15]),
(m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15]),
(m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15]));
}


Share this post


Link to post
Share on other sites
Quote:
Original post by Dragon_Strike
ok ive tried calculating the view frusutm points from the lightsmodelviewmatrix... its ok.. but its not right.. this is how it looks...

You need to actually PROJECT the points into shadow map space (and divide by w) before computing the screen-space extents. Code is available in the various PSSM demos.

Share this post


Link to post
Share on other sites
well that gets me into some problems.. if i project it that would mean

result = (vec4(vertex,1.0) * modelview * projection)
result /= result.w

however if i use the current projection matrix i get the values in the range of [-1,1] (i think) of the current projection... how do i calculate the new projection from that?


for (int n = 0; n < 8; n++)
{
//RenderSphere(FurstumPoints[n], 5.0f, 10,10);
FurstumPoints[n] = FurstumPoints[n] * ModelView;
FurstumPoints[n] = FurstumPoints[n] * Projection;
FurstumPoints[n] /= FurstumPoints[n].w;
FurstumPoints[n] *= 200.0f; /7 some nice value to scale up with == WRONG
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Dragon_Strike
result = (vec4(vertex,1.0) * modelview * projection)
result /= result.w

however if i use the current projection matrix i get the values in the range of [-1,1] (i think) of the current projection... how do i calculate the new projection from that?

Note that you need to check for clipping to the near frustum BEFORE DIVIDING BY W. This is all standard stuff that the hardware does so there's plenty of documentation.

You end up with bounds in the [-1, 1] range for X and Y, from which you can compute a scale an offset (relative to the full [-1, 1] range), then construct a scale/offset matrix and compose it with your current projection matrix.

Seriously this is all implemented in the PSSM demos, so I really suggest go taking a look at that code - it's fairly well documented.

Share this post


Link to post
Share on other sites

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