Questions to Yann: cache for shadow maps

Started by
22 comments, last by Yann L 19 years, 9 months ago
I'm currently implementing a cache system for shadow maps, massive models and per-pixel lighting. I vaguely remember something you wrote about shadow maps in your engine some month (years?) ago, and after digging into the archives, i found this: Yann about shadow mapping. And indeed it's extremely similar to what i'm currently doing. Your last post was left unanswered, so if you don't mind, here are a few questions for you: 1. In your cathedral screenshot, which amount of video memory was allocated to the shadow maps cache ? To get a good quality i found i need _at least_ 50 Mb of shadow maps. In my engine i have a scale factor for shadow maps which i can adjust to lower the memory requirements, but then the quality of my shadow maps starts to decrease, and becomes noticeable. 50 Mb of shadow maps is quite a lot, and you can't compress them. 2. Again in your cathedral screen, how many lights are visible on screen ? You mention 39 lights for the whole cathedral, but they're not all visible at once. Also, how many lights do you have in each category (how many point lights, how many spot lights, and how many directionnal lights ?). Do you have some lights not casting shadows ? What are the typical resolutions for your shadow maps in that scene ? 3. How are you handling the soft edges ? I am antialiasing my spot/directionnal shadows from 4 to 8 times, but i get a speed hit that is quite high. It's even worse for omni lights since i must access a cube map many times. Basically, for a single scene, i am shader limited to 50 fps on a Radeon 9700 pro with a single light (filling the whole screen). I am guessing that's not the way you are antialiasing your shadows since you get 60 fps on a GF4. Are you rendering your shadows to a texture, blurring it, and modulating it in your lighting equation ? 4. Speaking of the lighting equation.. which per-pixel effects did you handle in this cathedral screenshot ? It only looks like dot3 bump mapping - i cannot see any specular/gloss contribution - but i might be wrong ? 5. I am going to implement LOD in the next months, but i am afraid to see some geometry popping in the shadows, as the shadow maps are cached, and not updated every frame.. did you have that problem ? 6. How do you quickly render your shadow maps to a texture ? I am using a Radeon 9700 and i must admit that even if rendering a 1024x1024 shadow map is quite fast, i hit a bottleneck to upload it to the graphics card. I'm not using the traditionnal render-to-texture approach, since given the amount of lights, it would require one PBuffer per light. Instead i'm using a constant amount of PBuffers, i generate my shadow map in the oldest one, and i copy its contents it to the graphics card by using glCopyTexSubImage2D. How were you doing it ? That's a few questions but i hope you do have the time to answer them :) Thanks, Y.
Advertisement
OK, let me find the jan-2003 source tree and docs. The shadowing system used in our engine changed a lot since then - I actually tried so many different approaches, I really don't remember from the top of my head which one was used at that time.
Thanks Yann, i'm looking forward to reading your answers :)

Btw, if you or one of our fellow visitors are curious, here is a screenshot of my current system in action (one light only):



(Note that it's the same engine i'm developping for the Minas Tirith model).

Y.
Quote:Original post by Ysaneya
(Note that it's the same engine i'm developping for the Minas Tirith model).


This is going to be very special :)
It's the same engine, not the same application :) This one above is a for an old-school 3D-maze like game (ala Dungeon Master, Eye of the Beholder, etc.. ).

Y.
How can you write so many great engines so quickly? Nice detail in the walls, is that using the same techniques as the Unreal Tournament 3 engine?

For my own curiosity, how is your lightspeed hovercraft game going?
very nice.

btw. If there's only one light, how does that wall straight in front of you get shaded? YOu would say that it would be totally black, or some equal coloour because of ambient lighting, but it looks brighter near the left wall.
Quote:
How can you write so many great engines so quickly?


I don't.. all my projects are using the same engine which i'm improving on time :)

Quote:
Nice detail in the walls, is that using the same techniques as the Unreal Tournament 3 engine?


More or less. UT3 also has static lightmaps and stencil shadows, which i don't, but that's planned for later. Other than that, i am guessing my lighting equation is similar to UT3, yeah. It's doing pretty much everything that is state-of-the-art nowadays. Namely, 1 to 4 times shadow maps antialiasing (spot, directional or omni lights with cube maps, 16 times AA maybe in the future), dot3 normal mapping, parallax/offset bump mapping, per-pixel lighting with attenuation, specular/gloss mapping with per-pixel exponent, light diffuse & specular color, and probably a few things i'm forgetting.

However due to lack of time i haven't implemented a non-ARB_fragment_program code path, so basically it only runs on Radeon 9700+ / Geforce FX+. Most features are simply disabled if you don't have these.

Quote:
For my own curiosity, how is your lightspeed hovercraft game going?


Not very well i'm afraid. My artist was busy at university and/or on other projects, so there hasn't been any real progress on the art side for a few months, and i do not feel like investing more time to a project that doesn't have an active artist. I'm improving the engine and working on other projects until he can resume his work. If he's still busy in september i'll be looking for somebody else, as the game is almost playable.. it would be a real shame to not finish it. Hell, it even has a fully functionnal MFC editor :)

Quote:
very nice.

btw. If there's only one light, how does that wall straight in front of you get shaded? YOu would say that it would be totally black, or some equal coloour because of ambient lighting, but it looks brighter near the left wall.


Thanks. You are right on the shading of the wall, it was caused by a bug that i had already fixed, which was incorrectly applying a light specular effect in shadowed areas. Didn't thought somebody would notice it :)

Y.
OK, I couldn't find source snapshots for january 2003 (it's on the company server, and I just realized I don't even have a local copy of the '03 source tree. Hmmm). I'll try to answer the questions from memory, and add information about how it was been done in later engine revisions (a lot of things were still broken and sub optimal at the time of that thread you linked to).

Quote:Original post by Ysaneya
1. In your cathedral screenshot, which amount of video memory was allocated to the shadow maps cache ? To get a good quality i found i need _at least_ 50 Mb of shadow maps. In my engine i have a scale factor for shadow maps which i can adjust to lower the memory requirements, but then the quality of my shadow maps starts to decrease, and becomes noticeable. 50 Mb of shadow maps is quite a lot, and you can't compress them.

The cache for the shadow maps is around 32 MB in the current engine, but it's a user setting and can be modified at will.

Generally, the caching is handled using a priority scheme. Each light has an importance factor associated with it, a visual priority. The higher the priority, the more important the shadow is deemed by the system, and the more resolution it gets. Smaller priority maps get gradually less resolution. Direct sunlight always has maximum priority, and is guaranteed to get a 2048 map at least. In nightscenes, the moon takes the role of the sun, but with reduced map resolution. Maps assigned to sun or moon are never cached, as they are viewdependent and updated every frame.

All other lightsources are then assigned maps from the pool, using a modified LRU scheme (modified to take the priorities into account). If a lightsource was moved, or geometry changed within its visual range, its associated shadowmap is sent to an update manager. The manager tries to balance shadowmap regeneration over several frames, in order to avoid updating hundreds of maps in a single frame. Again, the visual importance factor helps a lot: lights with lower priority can be updated less often, and their update can be defered to a later frame. This can lead to an effect that the shadow of some low priority light lags behind the object (especially if the object moves very fast), but is generally unnoticeable if the system is well balanced.

The priority is assigned depending on several different visual metrics: distance from the viewer and occlusion based temporal coherence are the two most important ones. You can add several others, but this depends on your engine and the type of realism you're looking for. Also, visual metrics is the perfect spot to include a user adjustable shadow quality setting.

Quote:Original post by Ysaneya
2. Again in your cathedral screen, how many lights are visible on screen ? You mention 39 lights for the whole cathedral, but they're not all visible at once. Also, how many lights do you have in each category (how many point lights, how many spot lights, and how many directionnal lights ?). Do you have some lights not casting shadows ? What are the typical resolutions for your shadow maps in that scene ?

I can't give you any detailed information about this particular scene, because we don't use it anymore. From memory, all lightsources in the cathedral were point lights, except for the sun, which is directional (and using PSM back then, now replaced by TSM). No spots in that scene, afair. Many lights didn't cast shadows, those were what we called 'virtual lights' back then. They were used to give the scene the radiosity ambience, but still keep the lighting dynamic. We used a lot of those virtual light sources, but their number was mostly important in preprocessing. We later replaced that system by dynamically controlled GI lighting, because we needed more flexibility and realtime HDR support.

Quote:Original post by Ysaneya
3. How are you handling the soft edges ? I am antialiasing my spot/directionnal shadows from 4 to 8 times, but i get a speed hit that is quite high. It's even worse for omni lights since i must access a cube map many times. Basically, for a single scene, i am shader limited to 50 fps on a Radeon 9700 pro with a single light (filling the whole screen). I am guessing that's not the way you are antialiasing your shadows since you get 60 fps on a GF4. Are you rendering your shadows to a texture, blurring it, and modulating it in your lighting equation ?

The GeForce card series have dedicated hardware for bilinear filtered shadowmaps, a kind of poor man's PCF natively built into the chipset. All shadowmaps in the cathedral shot used that feature, which was available from GF3 upwards. ATI never supported that, because of some intellectual property issues they had with nvidia.

Quote:Original post by Ysaneya
4. Speaking of the lighting equation.. which per-pixel effects did you handle in this cathedral screenshot ? It only looks like dot3 bump mapping - i cannot see any specular/gloss contribution - but i might be wrong ?

No, you're perfectly right. There were not enough regcom stages left on the GF4 to do nice looking speculars. I didn't manage to both normalize the half angle vector per pixel, and to exponentiate H dot N to an acceptable power. So instead of having sucky speculars, I just switched them off completely. I don't remember if the speculars were actually switched off or simply invisible on that particular shot, but I think it was the former. The problem came from the way the engine handled multiple shadowmaps back then, there were no texture units left for cubemap normalization of H (keep in mind, the GF4 only had 4 units). The thing was possible later on, using the new material and shader system, by dynamically distributing the work load over multiple passes. Thanks god for ARB_fragment_program, which solved all that ;)

Quote:Original post by Ysaneya
5. I am going to implement LOD in the next months, but i am afraid to see some geometry popping in the shadows, as the shadow maps are cached, and not updated every frame.. did you have that problem ?

Yep. That will happen, unless you update each map every frame (or as soon as anything within the light frustum changes). But by carefully choosing the priority metrics, it can be minimized. Also, there are a few tricks we learned by trial and error:

* for example, if you need space in the cache, and want to downgrade an existing cached map to a lower resolution, you can simply resample the existing shadowmap without rerendering it (by using a nearest point filter). If you do that in gradual steps, users are unlikely to notice the temporal blur. For example, you have a 1024 map cached, but much of the light got occluded. So your priority metrics tell you that a 128*128 map is enough. Now, don't downsample in a single step (1024->128), but do it gradually: 1024->512->256->128, distributed over several frames.

* Another neat trick is gradual resolution increase. Imagine you have a high priority shadow suddendly coming into view, and requesting a huge shadow map. But you don't have the space in the cache right now, you first need to downgrade a few other maps, and rerender the new light. You don't want to do all that in a single frame. What you can do, is using a low resolution map even for the high priority light. The shadows will be blurry. The next frame, you render the light view at full resolution, but don't directly replace the map by the hires version. Instead, you quickly increase resolution over the next frames, until it finally matches the target quality. This works especially well with very bright light sources. It looks like the eye trying to adapt to the bright light, being a little out of focus at first, and getting sharp after a short while.

* A very mean, but highly effective trick: if possible, change the shadow maps when they are out of view. For example: the player moves towards a light source, the maps are still a bit too lowres. He turns around - the shadows are now out of view, the perfect opportunity to sneak in the new maps without the player noticing - turns back to the previous view, and magically the previously blurry shadows are now perfectly sharp. Noone is ever going to notice the change, unless he is specifically told to watch for the effect.

* When using point lights: from a certain distance on, you can safely switch from a cubemap to a dual paraboloid map. The latter will take considerably less memory, and require less work in case of an update. You can directly convert a hires cubemap to a lowres dual paraboloid map without rerendering the light view.

Quote:Original post by Ysaneya
6. How do you quickly render your shadow maps to a texture ? I am using a Radeon 9700 and i must admit that even if rendering a 1024x1024 shadow map is quite fast, i hit a bottleneck to upload it to the graphics card. I'm not using the traditionnal render-to-texture approach, since given the amount of lights, it would require one PBuffer per light. Instead i'm using a constant amount of PBuffers, i generate my shadow map in the oldest one, and i copy its contents it to the graphics card by using glCopyTexSubImage2D. How were you doing it ?

I use RTT pbuffers only for the large map entries in the cache. I then use a single shared pbuffer for all other, lower resolution entries. Updating the hires textures is a simple render-to-texture operation. For each lowres light to update, I first render the light view into the shared pbuffer, and copy it to the target texture. While the copy operation does cost a little more than a direct RTT, everything stays on the card, nothing runs over the AGP bus. The shared pbuffer will also heavily reduce the number of context switches required.

Also, don't forget that you can tile several smaller 2D maps onto a single larger shadowmap. You just need to be careful with the clamping, and it won't work on cubemaps. It works very well for spot lights however, or for point lights using dual paraboloid maps.
Thanks Yann, these answers have been helpful.

Quote:
Maps assigned to sun or moon are never cached, as they are viewdependent and updated every frame.


Ok, so because you are using PSM/TSM, you need to update the sun's shadow map every frame.. that makes sense. But i have no idea how you were able to do it so quickly on a GF4 at 60 fps with scenes like 100 to 300k triangles in view. If you download the PSM demo on NVidia's site (which also has a fairly complex scene), the framerate is more like in the 20-30 fps, on a Geforce fx. And you're rendering to a 2048x2048. Is there a trick to get such a speed ?

Quote:
From memory, all lightsources in the cathedral were point lights, except for the sun


Were some all of them per-pixel, or some per-vertex ?

Quote:
but their number was mostly important in preprocessing.


What were you preprocessing for your lights ?

Quote:
The GeForce card series have dedicated hardware for bilinear filtered shadowmaps


I see, the most simple explanation was the good one. Did you left the shadows sharp on ATI cards ?

Quote:
for example, if you need space in the cache, and want to downgrade an existing cached map to a lower resolution, you can simply resample the existing shadowmap without rerendering it


That sounds interesting, but it's not clear to me how it worked. If your shadow maps contain a depth information, how can you downsample this ? The most immediate idea is to render-to-texture (say 512x512) in ortho mode a single quad using the original 1024x1024 texture. But how do you perform the depth-copy operation ?

Quote:
if possible, change the shadow maps when they are out of view.


That's a neat idea, and i think i've done it the wrong way in my system so far. When my shadow maps are out of the view i simply free them from the cache to make room for other lights. It obviously becomes a nightmare when you are turning the head quickly. I need a bit of time to think about and improve the priorities thing.

Y.

This topic is closed to new replies.

Advertisement