# A Robust Alternative to Perspective Shadow Mapping

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

## Recommended Posts

##### Share on other sites
Have you tried it? Is there maybe a demo available? I'm interested how much of an improvement you achieved with this.

The technique you described sounds similar to a method I developed last year. The main difference is a) you distort both texture axises seperated, I use a combined distortion method and b) I use a different distortion formula.

I wrote an article on the method. Unfortunatly, it's in german. I haven't found time to translate it, yet. I don't know how far babelfish got in the meantime but maybe you want to give it a try: http://www.softgames.de/developia/viewarticle.php?cid=27884

The main points in short:

A function to describe how "good" the resolution should be. Could be any, but for the sake of simplicity, parametrization and integration I chose

x = 1 / (t + 1/k)

t is the distance from the viewer at the shadow texture, x is the desired relative shadow resolution there. x==k for t=0 and x~=1 for t=1. The constant k can be used to scale the effect, good values are 50 to 100.

Because this function describes the "density" of texels, we have to integrate over it to get an actual distortion function. This results in

V(x) = ln(k * t + 1)

It's a nice function with a rapid ascend at beginning and a low slope at the end. It has to be normalized, though, so it ends exactly at 1.

V'(x) = ln(k * t + 1) / ln(k + 1)

This is our final distortion function. It maps an undistorted, "plain" shadow map texture coord to a distorted texture coord. Because of the rapid ascend at the beginning, much texture space is used close to the distortion origin, improving the texture resolution there. With increasing distance from the distortion centre, the slope of the function gets lower and lower. Close to the end (t == 1) the slope is much lower than 1, thus lowering the texture resolution there. So this distortion function basically increases the shadow map resolution close to the viewer at the cost of lowering the resolution for most scene parts farther away from the viewer.

You simply use it in the vertex shader a) when rendering shadows and b) when rendering the scene with the light. The vertex shader fragment looks like this:
// calculate texture coords and depth just like uniform shadow mappingfloat3 shadowTextureCoords = CalculateShadowTextureCoords();// subtract the distortion origin (where the viewer resides) from itfloat2 texDiff = shadowTextureCoords.xy - gShadowViewerPos.xy; // calculate undistorted distance from viewer position at the texturefloat t = length( texDiff) * 2.0f;// do the distortion: calculate new t from old t,// then divide by old t because it's contained twice as length of texDiff// The formula is ln( k*t + 1) / ln( k + 1) // gShadowViewerPos.z is "k", gShadowViewerPos.w is "1 / ln( k + 1)" float nt = gShadowViewerPos.w * log( gShadowViewerPos.z * t + 1) / t; // transform to absolute distorted shadow texture coords shadowTextureCoords.xy = gShadowViewerPos.xy + texDiff * nt;

Works well over here. We have a view distance of 1000 virtual meters and a shadow map of 2048x2048. With plain uniform shadow mapping this would result in appr. 2 shadow pixels per meter. With the distortion described above (k == 100) we get an improvement factor of 20, increasing the resolution to 40 shadow pixels per meter. And the improvement is completely independend from the view direction and the light direction.

Downsides:

* additional vertex processing time. Less than 1% here so not much of an issue.

* the distortion is NONLINEAR.

Thats a problem, because the interpolation of values at the graphics board are linear. For long face edges, the interpolated shadow texture coords at the middle of the edge differs from the real distorted value that would be calculated for this position. In our game, this is mostly no problem as the scene is tesselated enough close to the player. But for example a billboard tree casting shadow from a hundred meters behind distorts quite heavily. It looks like the shadow bends outwards in direction of the camera. You have to make sure that everything casting or receiving shadows is well tesselated when close to the player. In our world, edges should not be longer than about 2 meters before the distortion gets visible.

Well... sorry for that quick breakdown. I should really do an more detailed paper on this method. It works quite well for us, except for the distortion problem stated above. I'm curious if your method would do better at this topic.

Pictures available if needed.

Bye, Thomas

##### Share on other sites

I only choose to distort the axes separately because I thought the length operation would take too much time in a shader. I would actually prefer to do it the way you do, because as I do it objects at certain angles from the viewer receive improper texture space. I will write up your version and compare speeds.

Also, thank you for providing another equation. I spent a day looking for other possible "mappings" that would do what I want. I will try your way and see if there is any advantage/disadvantage.

Finally, my version also suffers from the interpolation artifacts when the surface isn't tesselated sufficiently. While this doesn't really matter as far as models go (since they are so tesselated these days), it is a shame when it comes to ground surfaces, because it would be nice to get away with low vertex counts. After all, one of the best things about per-pixel lighting is the reduction in necessary vertices to achieve realism. I am still working out the math to fix this, my current idea is to pass in another value to the pixel shader that can be used to scale the interpolated coordinates to what they should be.

Can you please send me a message (rocketdodger@yahoo.com) with an email contact so we can discuss this more formally? It would be nice if we could find a way to overcome these artifacts, then there would no longer be an advantage to PSM (except for the speed).

##### Share on other sites
Warping and Partitioning for Low Error Shadow Maps

http://gamma.cs.unc.edu/wnp/

##### Share on other sites
Quote:
 Original post by Schrompf// calculate texture coords and depth just like uniform shadow mappingfloat3 shadowTextureCoords = CalculateShadowTextureCoords();

This method sounds very interesting..But can you elaborate a bit more?

When you say "caculate texture coords" what exactly do you mean here? What texture coords need to be calculated in the depth pass?

i have a shadow mapping system implemented, and I have a hard time figuring out what the relevance of this code is. I dont calculate any texture coords in the depth pass.

##### Share on other sites
Quote:
 Original post by Matt AufderheideThis method sounds very interesting..But can you elaborate a bit more?When you say "caculate texture coords" what exactly do you mean here? What texture coords need to be calculated in the depth pass?i have a shadow mapping system implemented, and I have a hard time figuring out what the relevance of this code is. I dont calculate any texture coords in the depth pass.

You do, you just don't know it. What we actually mean are "shadowmap coordinates."

The relevance of the technique we are discussing is that it makes objects closer to the viewer larger in the shadowmap, which means their shadows will be much more refined. This doesn't matter for small scenes, but think of directional lights where the view frustrum has a far plane miles away -- if you want shadows that look decent you have to find a way weight the shadow detail based on distance.

##### Share on other sites
It's a good idea, but you might want to write up a demo demonstrating it. Right now, I'm a bit skeptical as to how robust it'd be since you're applying nonlinear transformations to your vertices, so the linear interpolations that the hardware does could get a bit weird or create undesirable results in some situations (esp. with low to moderate tesselation).

##### Share on other sites
Ok Schrompf I tried your equation and I like it better -- plus it runs faster, although I don't see how, since it involves two log() functions! I have a feeling its because of shader model 2.0, my version might be faster in 3.0 where dynamic branching operates as it should.

The artifacts due to linear interpolation between vertices are much less pronounced with your equation than with my version, so I am changing over. Hopefully in the future more people will get into this so we can find better and faster equations.

RE Cypher19 -- I agree, I don't like the fact that the transformations give strange abnormalities with low tesselation. Plus, I don't like how slow it is. I would much rather use LSPSM, but the problem is that LSPSM doesn't work when the viewer is looking down the light direction. I am waiting for someone to find a matrix transformation similar to LSPSM that does what we are doing with the texture coords, but so far I can't think of anything.

##### Share on other sites
First: thank you all for your interest. I don't have a isolated demonstration that shows the method but I have a WIP game engine that implements it. The only difference is that the data is much larger. Once I get home again this evening I'll try to trim down the package to the minimum and upload it. Download size will still be around 50 MB so pack a lunch. SM2.0 minimum.

Till evening I can only show pictures. They're taken from the (german) article I wrote some months ago: without distortion, with distortion, k == 50

Comparing against LSPSM: You basically exchange one set of problems with another. LSPSM is dependend on angle between view dir and light dir. The distortion method has problems with nonlinearity <-> linear interpolation. On the scene we're operating on, it works out fine. But it IS a problem for smaller scenes, where the slope change per scene meter is larger. You may try this out in the demo, i'll add some console vars to adjust view distance and the distortion factor.

Oh, and another advantage: It's possible to silence the shadow border flickering caused by readjusting the shadow map area every frame. The demo at present has no flickering when rotating the view and a occasional flickering when moving the view.

BTW: Vertex Shader performance is not an issue. Starting from SM2.0, a full precision log() does cost a single instruction slot and a single clock cycle AFAIK. The length() is approx. 3 cycles, the other math adds maybe another 5 cycles. As most of the games today are pixel shader limited anyways, this is no problem.

@Matt Aufderheide

JakeOfFury already answered this, but I want to make sure it's clear: CalculateShadowTextureCoords() does a simple transform into light space to calculate the shadow map texture coords for that vertex. Because it's the standard operation for uniform shadow mapping I moved it out of the code snippet and only left dummy function there. I hoped to show this way that the distortion method jumps in right after the standard shadow map texture coord calculation.

 on a second read: You have to apply the same distortion a second time on the vertex positions when rendering the depth pass. Without, the occluder position at the shadow map would not match the position where this shadow map part is applied at the scene.

After projection, pos.xy are in the range -1..+1 and pos.z is 0..1. You have to expand the distortion center and the formula accordingly. Namely, remove the " * 2.0f" at the formula and do a "* 2 - 1" on the distortion center. [/edit]

Bye, Thomas

##### Share on other sites
JakeOfFury, Schrompf, this is a quite an imaginative technique, the nicest one in my "shadow map techinque of the month" ranking :) Congrats.

Now, I tried it and I had quite bad results because my geometry is not tesselated enought (I have 20 meters long objects). I don't think I can inmediatly go for any of the obvious solutions:

1] statically subdivide my geometry (what I cannot affort, I have hundred of million of triangles)
2] add a distance based dynamic tesselation system (an aggresive LOD basically) 3] use Shader Model 4.0 and tesselate the stuff on the GPU as needed.

By the way, I cannot really tell if these is a good one, but I also tryed this other distortion function

sqrt( (t^2 h)/t^2) )

with the parameter h going from 0 (no distitoion at all) to infinity (total distition) and "good" values around 1/4. It translates to just 1 RSQ and 1 RCP in ShaderModel 2.0

DP3 nt.x, texDiff, texDiff;
RCP t.x, t.x;
MUL nt.x, nt.x, t.x;
RSQ nt.x, nt.x;

but the log()/log() formula one is also fast anyway, and "more correct", you know what I mean (by the way JakeOfFury, it only involves one log() because the complete divisor can be passed as a constant to the shader).

Again, this is a cool technique that can be applied in some specific cases (as yours), and interesting because I think its the first shadowmapping method that moves the problem to the vertex count side (as opposed to multiplass solutions) and the first one I know about that works with arbitrarilty big view frustum (not like PPSM, TSM and all the other frustum based methods). As bonus, you don't have to change your current software, just add few lines in the vertex shader; no complex frameworks at all !!

It worths a try, really.

1. 1
2. 2
Rutin
19
3. 3
4. 4
5. 5

• 9
• 9
• 9
• 14
• 12
• ### Forum Statistics

• Total Topics
633298
• Total Posts
3011259
• ### Who's Online (See full list)

There are no registered users currently online

×