Multi-pass Multitexturing and Shadow Maps

Started by
4 comments, last by spek 14 years, 10 months ago
#1: When you run out of texture units you just make another pass with the remaining texture units in slots 0 up. In order to get the fragment that resulted from the previous pass you have to change the blending modes. But shadow-mapping requires blending modes to be set a certain way. Will this not interfere with your shadow-maps? #2: The fragment from the first pass may be altered before the second pass begins, for example by having transparent objects on top of it (can be fixed by drawing transparent objects in a completely separate set of passes, which needs to be done with shadow-mapping anyway) or via fog. What is the solution, particularly with fog, which has no solution? L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Advertisement
You could reserve a place for the shadowMap and just make sure you never override it so it can be applied in pass 2,3...n again:
<PASS1>   color.rgb = (tex1 + tex2 + tex3) * tex4(shadowMap)Enable Additive Blending, apply 3 other textures for unit1,2,3.<PASS2>   color.rgb = (tex1 + tex2 + tex3) * tex4(shadowMap)..and so on

If the textures need to multiplied with each other it gets a little bit more difficult. But you can still apply the shadowMap afterwards though:
<PASS1>   color.rgb = (tex1 * tex2 * tex3);Enable Multiply Blending, apply 3 other textures for unit1,2,3.<PASS2>   color.rgb = (tex1 * tex2 * tex3);..and so onFinally multiply the whole thing with the shadowMap, also with Multiply Blending enabled:   color.rgb = tex4(shadowMap)

It's an extra pass, but you can use more textures for each pass now, and the individual shaders are simpler/faster.

I would take a look into Deferred Rendering for this kind of stuff. That will also bring your second issue. Transparency is a headache. But do transparent objects always need many lights/texture? Glass, fences, grass, foliage... As transparent stuff is already pretty expensive on itself, it might be wise to force everything into 1 pass when it comes to transparent stuff. First render all opaque stuff with as many passes and tricks you like, then add the transparent surfaces on top with somewhat simpler shaders.

Greetings,
Rick

By the way I am not using shaders.
Using my existing setup as an example:

I have 4 slots [0..3]. Shadow map goes into slot 3 always.

#1: The render-queue overrides prevent the objects from being able to put texture into a slot filled with a shadow-map. Thus the objects need to know how many slots are available to them and they can break their render data into multiple passes if they have not enough.
#2: When all of the objects have submitted their render data to the render queue, the max texture unit used is tracked and if (for example) only slots 0 and 1 are used by objects, slot 2 can be filled with another shadow map if there are any.
#3: But when the objects need more texture units than are available, they have to submit 2 (or more) sets of render data.


Here are the problems I plan to counter and the questions I have from your post:
#1:
Quote:If the textures need to multiplied with each other it gets a little bit more difficult. But you can still apply the shadowMap afterwards though:

This requires multiple render targets?
I really want to avoid deferred shading if possible; I prefer anti-aliasing.

#2: When the render queue sees a set of render states that have been split among several passes, it can disable fog for all but the last pass. That should handle fog correctly yes?

#3: I currently make the render-queue system handle multiple passes by finishing the current pass entirely and then going back to handle any remaining render states (for as many iterations as needed).
These techniques seem easier to implement if I were to get to an item in the render queue and handle all of its passes at once before moving on.
Both methods would require only one alternative render target, but finishing the whole pass may improve speed since the object may later get overwritten by another (though unlikely since my opaque objects are sorted near-to-far) which would avoid calculations during the following passes.

Are there any other reasons for going one way or the other that I have not considered?

#4: As for my original question, what I meant was this:
Testing the alpha on the shadow-map texture requires these alpha settings:
CFnd::SetAlphaTest( true );CFnd::SetAlphaFunc( LSG_AC_GEQUAL, static_cast<LSGREAL>(0.9) );

Making a second pass (with or without deferred shading) requires these alpha settings:
CFnd::SetAlphaTest( true );CFnd::SetBlendFunc( LSG_BM_ZERO, LSG_BM_SRC_COLOR );

Won’t the blend function mess up any texture units other than the first, including the shadow-map texture?


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

No Multiple Render Targets or Deferred rendering required. Just do what you are already doing, except that you make all 4 slots available and put the shadowMap in a final pass.
1. disable blending2. render object X with texture 1,2,3,43. activate multiply-blending4. render object X again with texture 5,6,7,8...until all textures are done...5. render object X again only with the shadowMap applied, and eventually fog

It's the same as multiplying many textures into 1 big shader. You can insert the fog in one of the passes as well (I would use the shadow pass).


As for the sorting, you don't have to sort opaque objecst at all. I would just render them completely first. You can sort on textures though. Don't forget that switching the texture units alot is also an impact on your performance. If you want to sort on texture(set), you must first render all objects with pass1, then all objects with pass2, and so on.

It's nice to have your alpha objects sorted on depth. Apply transparency and render them on top of the opaque result (which is 100% finished by that time). If you plan to use many transparent objects (Grass quads for example), its still wise to sort on material here as well. Or make a few groups (grass, glass, objects, foliage, static mesh...) and sort inside these groups on depth. It might deliver artifacts, but you gain speed. On the other hand if you don't use objects with the same texture lots of times, sorting on material is probably a waste.


Alpha problems... Not sure what the functions do. Does it completely hide pixels if their alpha value is lower than 0.9 (black/white)? Or do fade in/out from 100% to 0% transparency? In the first case it's pretty simple. Blend function don't mess up textures, they just tell how to draw the upcoming pixels onto the previous('background') ones. Just do the same trick again as first. Transparent pixels won't alter the background, because their alpha value is 0. However, that only works if the alpha values are the same on all textures. For example, if you render a metal fence with 1 texture, the background pixels between the wires won't get touched as your fence texture should have alpha 0 pixels there. But if you use a shadowMap in the second pass, it should have exactly the same alpha values, otherwise it also darkens the background pixels, and the fog will multiply itself again with background pixels that already had fog.

Like I said, transparency is tricky stuff. First try if you can render them just in 1 pass, always (thus maximum 3 textures). If that is really not possible, make sure to pass 1 of the textures that has the alpha info together with the shadowMap so you can mask the shadowMap... Although that only works with shaders. Fixed pipeline multitexturing won't do that for you I guess. I don't know if shaders are an option in your case, otherwise I would really try to use them. They make life a whole lot easier when it comes to this kind of stuff :)




BTW, have you considered atlas textures? Instead of switching between textures alot, you put a large set of textures into 1 big texture and adjust the texture coordinates to pick it out. It doesn't work for tiles surfaces though. In that case you need 3D textures or texture array's, but probably you can't use them without shaders... (maybe 3D textures though)...

The atlas texture doesn't reduce texture slots in your case, but it can speed up the whole thing since you only need 1 (or a few) textures = far less switching. With shaders you can even tell to pick all textures from the same slot so you only need 1 slot for a whole bunch of textures.

Rick
Thank you for all of your feedback.


I sort on depth with opaque objects to improve early-out on the Z buffer. Drawing the near objects first fills the screen with objects that block other objects and this saves me about 10 FPS (out of 200 at the time). I sort on a per-object basis with a fast depth calculator so it is really worth the gain. Alpha object are sorted at a finer (per-sub-object) grain.



As for texture-coordinate sorting, I sort by no texture, then 1 texture, then 2, etc. I may in the future advance this to actually take the current texture status and put matches up at the front of the queue, so the last texture + slots drawn on the previous frame would be the first drawn on the next frame, which would normally swap the object order every other frame. Unfortunately I will probably not get around to it because drawing the alpha last almost always leaves it in a state where the last texture used in the previous pass is not used on opaque objects at all.



I don’t know about this new plan.
I think I need the shadow texture in on every pass actually, after having thought about it a lot.
I originally thought that would screw up the result, but actually consider this:
If I draw passes 0-3 without a separate render target (which I think I can not use in FFP OpenGL anyway) the result will be written to the frame buffer already before the shadow occlusion test. This is fine on the ambient pass, which draws dim lighting and does no shadow-occlusion tests. But on the next render phase I draw everything again using alpha compare (taken from the shadow texture) to avoid blitting fragments in shadowed areas. Without the shadowed occlusion test, shadowed areas would be re-rendered with everything done on pass 1 (instead of remaining dark).


It seems intensive, but I think I need to do the shadow test on every pass.
The first pass will occlude the fragments, leaving the frame buffer dark and unmodified there. Next pass will pick up that dark pixel and continue working on it (which is when I originally assumed the final result would be wrong) but actually that fragment will just be occluded by the shadow-map again, and again the frame buffer remains unscathed. Only at the edges of the shadows will there be slight problem areas, but it seems a small price to pay for the gained flexibility.


Feedback is still welcome. I would especially like to have verification for this idea, as I may easily have overlooked something. I am juggling other tasks on this engine so my thoughts on how to do this particular part are always interrupted and incomplete.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

What kind of shadowMapping are you using? Rendering from a light point of view and applying a depth map on the objects/world?

If you include the shadow in each pass, be carefull not too multiply shadow with shadow multiple times. The more passes you get, the darker the result will be. Unless its a pure black/white shadow, in that case it doesn't matter if its used 1 or more times. By the way, the approach above doesn't use multiple render targets, just the default framebuffer. Each pass multiplies itself with the previous result(background pixels).

Greetz

This topic is closed to new replies.

Advertisement