Shadow mapping performance

Fb2b4b2e70419434ee2b6d27a44cb440
0
zelko 101 Dec 20, 2006 at 04:06

hi guys,

i dont post on forums, but i got quite a bit of help from here just by reading past mails… to cut it short, i implemented shadow mapping combined with optional projective textures like spotlight circle or other image for projector effect, also i combined light depth texture with fog rendered light view…

[just quick tip: you dont need depth texture to achieve shadow mapping, if you render image with linear glFog u get the same values(distance from light) in your image]

… anyway, here is demo featuring 4 dynamic lights+flashlight, they have nice fade-out in distance effect..

no pixel or vertex shader!

uses:
ARB multitextures
FBO for off-screen rendering
VBO(vertex arrays) to hold scene mesh, actually in this version it is compiled in display list for +10fps boost

its not optimised, it doesnt cool scene for any light
by the way scene contains 32x32 quads + 1 to 4 quads for sides where there is en elevation like steps or those other boxes(walls)

so scene is rendered 10 times each frame with flashlight on,
5 times from each lights view and 5 times to add/blend light to scene

shadow textures are 1024x1024, flashlight with short frustum can be as small as 128x128

PCF is OFF as it produces white anomalies when transiting to black in combination with fog texture, i dont really care if my blocky shadow is made out of round blocks instead of square ones, i want to keep resolution high so there are no blocks…

every frame:
//collect lights views
bind FBO
for all lights:
bind lightN.depth texture to FBO
bind lightN.rgb texture to FBO (rendered black/white with glFog to fade out distance later)
-render scene
bind FBO, 0

//blend lights to scene
for all lights:
set 3 multitexture stages
0-depth/compare_to_r
1-fog/blend
2-circle/blend
-render scene

end frame

here is a download link with source code:

http://one.xthost.info/zelko/opengl.html

newE04m.jpg

if your configuration cant push 60fps some bugs will happen with flash light depending if your vsync is on or off, flashlight in this demo is just a quick hack and is the same as other lights, for proper flashlight effects there needs to be added some illumination around from reflected light not just where beam is… that stuff will come later

on my 1GHz AMD + GeForce7300GT i get from 70-140FPS

however executables in download might be locked to 60fps, so you can change source code or if anyone is interested at all i can clean up source code or add option to better measure performance…..

the whole idea is to implement FLEXIBLE shadow class that can dynamically fit into any scene, also it should be easy to use, here is how it looks in my demo:

collect light views…

FRUSTUM::beginGetShadowPass();
for(counter=0;counter<lightsNum;counter++){

   light[counter].getShadow();

     drawStatic();
     drawDynamic(false);    
}

FRUSTUM::endGetShadowPass();

and blend light views with scene…

FRUSTUM::beginBlendShadowPass();    
for(int counter=0;counter<lightsNum;counter++){

  light[counter].blendShadow();

    drawStatic(); 
    drawDynamic(false);

}
FRUSTUM::endBlendShadowPass();

so, to finish my thought from the beginning, to return something back for all the wisdom i got from internet…. heres this little demo and if you like it and wanna implement some of the stuff, feel free to ask… zelco@xtra.co.nz

or if it can be done better, please advise

cheers

11 Replies

Please log in or register to post a reply.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Dec 20, 2006 at 04:55

How about posting this as a code spotlight or featured image? Also, maybe you could show a bit more of the actual drawing code with the GL calls that you use to set things up? I’m interested in this glFog idea, don’t you have precision problems though since the glFog values are linear instead of projectively scaled like a true depth map?

Fb2b4b2e70419434ee2b6d27a44cb440
0
zelko 101 Dec 20, 2006 at 06:57

the origin of idea to use RGB texture instead of depth texture was because it was faster to glCopyTexSubImage(.. RGB(A).. )than
glReadPixels(..DEPTH_COMPONENT..) or
glCopyTexSubImage(… DEPTH_COMPONENT…)(very slow if not supported, but fastest of all if supported even faster than RGB copy regadlles of 32/16bit)

but, with new depth texture and GL_COMPARE_R_TO_TEXTURE_ARB, and with Frame Buffer Objects we dont need above optimisation anymore which would, by the way, calculate and discard shadow pixels by having them in alpha channel and using GL_ALPHA_TEST

but there is still use for fog…
the goal is to produce distance fade-out and there is the trick is with rendering to FBOs.

why fade out?
here is what www.cbloom.com say:

” E. DISTANCE FADE-OUT

It’s useful to make your shadows fade out in the distance. It makes them
look better, because it does a sort of fake simulation of the fact that
in the distance other light than the shadow caster are contributing
illumination. It also allows you to put a far clip plane on your shadow
frustum, which limits the number of things you project onto. It also
reduces the anomalies due to projecting through walls and such. You can
implement distance fade out in a few ways. You could do it with a per-
vertex computation in a vertex shader. You could also do it using the
trilinear mip-mapping hardware of the GPU (simply by giving your entire
shadow map a white mipmap in the second level); this technique is pretty
cool except for the memory waste of making a big all-white texture.
You could also do it using the clipper…”

its good read:

http://www.cbloom.com/3d/techdocs/shadowmap_advanced.txt

so as he later say,
you can do that with projecting 1D 1x2 b/w linear interpolated or, say, 1x256 grayscale texture, in the same time you get rid of back-projection aka reverse-projection problem, BUT there is better way that opens room for quite a few new tricks, like shadows of transparent colored bodies…

first, i want to point out the fact that if you render your depth texture then convert it to GL_INTENSITY or GL_LUMINANCE and show it on the screen it will be the SAME grayscale image fading to white as if you render your texture using glFog, GL_LINEAR with fogstart at your near plane and fogend at far, or actually move them, shift fog near and far to achieve effects like with glPolygonOffset … of course with fog u have to render all objects black on white with white fog or you want to render some objects in color if they transparent

so now when we know they are the same, pixels containing information about distance from light, or you can think about them as color or as both for some interesting effects…
or, in short - if its faster on your hardware to get RGB values than depth, use fog…..

back to fade-out to distance….
if we now use harware supported depth texture and render to FBO, why do we need same thing in RGB texture as well, why dont use that depth texture as fog texture if they same?

unfortunately you cant bind same texture as depth and later in the same ARBmultitexturing combo as alpha, luminance or intensity

glActiveTextureARB t0
bind (shadow, depth_component)

glActiveTextureARB t1
bind (shadow, intensity)

not working..

but fortunately we dont loose any fps if we had attached rgb(a) texture as well so we get our shadow in rgb anyway, and now we have all new 32bits to play with for free in same render pass!

back to the point, again…

this is imprtant stuff if you new to Frame Buffer Objects (like me)

-u dont need depth buffer render context as long as you have attached depthtexture your polygons will get z-sorted, and funny thing is its FASTER!, the other funny thing is that if you only have attached 1 texture to FBO and its your depth texture, and you render with glReadBuffer(gl_none), glDrawBuffer(gl_none) it WILL NOT be faster than if you attach RGB(A) texture on top, so in other words if u render to FBOs:

-dont use depth_texture rendercontext, instead attach some depth texture and your depth_test will work

-if you want to render only to depth texture, you might as well attach a color texture too for no extra cost when rendering, binding is fast with FBOs

when i say “depth buffer render context”, i mean this stuff:

glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);

you can skip that and instead have this, turns out to be faster:
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, img, 0);

then attach some rgb(a) like this:
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0);

so now we have 2 textures, depth and fog, they both the same, grayscale fading to white.. or black it doesnt matter, just use GL_ONE_MINUS.. to invert grayscale images, one is in depth texture and other is rgb (or 8/16/32bit INTENSITY, ALPHA or LUMINANCE… )

[quick tip, its faster to work with RGB(A) textures than with any of 1 channel like alpha, intesity or luminance even if you render to 8bit luminance and 32 RGBA the later will be faster, correct me if im wrong]

so for each light only in one pass we combine 3(4) or more arb multitexture stages:
1. depth texture is projected with compare_to_r
2. RGB fog texture is blended over to fade out with same projective transform as 1
3. spotlight circle texture is blended with same projective transform as 1
4.(1.) blend whatever else, it could be mesh texture or other projective

3 and 2 can be combined and further exploit the fact that we now have shadows in color buffer as well, what that means is that we can beside projecting light/shadow on scene we can project images when creating light view and than have projected ‘projections’ … im not really sure what else can be achieved except to combine spotcircle and to support colored shadows for transparent colored surfaces, which is by itself enough, any other ideas?

cheers

Fb2b4b2e70419434ee2b6d27a44cb440
0
zelko 101 Dec 20, 2006 at 11:22

i forgot to mention the most important thing of all..

why would you be interested in this?

-it solves problems connected with shadow mapping and opens door for extra effects all in one render pass, the same one as before you’d normally make for each light with normal shadow mapping anyway

issues:
-back projection
-resolution
-polygon selfshadowing

first of all… the price to pay is to have lights fade-out in distance, it might not fit everyones taste, but is exactly what i want to make effects in my game as in demo, kind of like what Splinter Cell tried, but for real

so, if you ok for your scene to be in some kind of invisible fog (if in real fog, no problems there) and that light gets fogged in distance than there you have it. But yet another (better) way to think about it as bloom said, fade-out would be the drop of intensity or one lights influence to luminance of some object with distance(we talk colors here as well, not just brightnes), which is really what you want, but there is a problem if that is the only light (like flashlight) in complete dark, in that case fade out is a bit unnatural, but i think its cull and fits well to my vision of graphics visuals for my game…..
so, by using this technique you remove back projection and resolution issues, simply you put your fog end where your ligth resolution drops and effectively not cast any shadows (or light) thats far enough to be crappy, so no need for PPS, PSM, TSM, FSM and what have you… also you dont worry about lights when making a level design, you put them wherever and move them as you wish, they simply CAN NOT cast bad shadow, also there is a way that nicely fits to this technique to include 2 static lights with huge resolution for the cost of 1 normal dynamic light and even cheaper! because we can have them keep background info(prerendered) and just update dynamic objects, so you can have two suns with res of 4096x4096 and have them update only dynamic objects, and even better blend them both in one pass with scene in 4 layer arb multitexture

that leaves polygon selfshadowing…

i like Metal Gear Solid for ps1(not other 2 that much), you may notice similarity with my demo… hahahahaaa…
i know… my camera handling is much better.. but the thing about MGS1 i like the most is that when i thought the game was over(couple of times) liquid would come out saying: “It’s not over yet!”

anyway…
the great thing about having combined our “fog” RGB texture for free when rendering shadow view as normal is… that there is one more thing left that can be used in that one, light view pass!!

so far…
we bind depth texture to FBO, so to later project correctly in regards to depth(if you just project textures without r compare you project on all polygons, this actually works well if you only have ground/floor to project to, like most of the games, you can actually have walls as well as long as you not supposed to see outside the walls), thats what bloom is talking about when he say fade-out solves projecting thru walls in Munch’s Oddyssey, since we using “real” shadow mapping with z-buffer we dont worry about that..
then,
we bind RGB texture (or LUMINANCE or INTENSITY, but rgb faster) to FBO as well, later we project it too, blended with previous, so with one render we get shadows that can fade in distance and be different colors, while we at it we may use some projective texturing now as well, not only when projecting light to final scene, so we can draw it with projected spotlight circle and save texture stage in camera render or we can project other texture in z direction, just like when projecting normal shadow, and achieve effect of real circular fade-out around the light, so that the wide end (far end) of light “cone” frustrum looks round(oval far clipping plane), the same effect can be achieved by rendering (with blending optionaly) 3d shape of a culling frustum before(or after) drawScene call… render it opaque before other geomery for some culling speed-up, just as you normally want to render scene from front to back when using depth test

where was i?

so, we have all that and there is still one set of values to be used in that one pass, its ALPHA bits, or if thats not enough you can combine these 32 RGBA bits as you wish so you can have your fade out fog say in intenity of 8(4) and rest in alpha or some of those gl combinations like GL_LUMINANCE8_ALPHA16

the point here is that you can tag your objects or triangles to alpha chanel, and later use that info “alpha-id” to not self-shadow with GL_ALPHA_TEST, this works great but is less robust as it requires having all your scene organised to allow for alphaID indexing, of course if you building an engine not just a demo youd have your objects organised in triangles or as objects at least, having that its really easy to implement, all you do is increase alpha for next object or triangle you draw when creating shadow view and than when rendering from camera and projecting that ‘fog’ texture now with alpha channel use glAlphaFunc with NOT_EQUAL to currently drawn object alphaID eg. glAlphaFunc(GL_NOTEQUAL, alpha[objectID]), (or use GL_LESS/GREATER for some interesting cell-shading like effect), this demo, however uses just polygonOffset, on normaly drawn BACK_FACE CULLED polygons

the end

actually im not talking anything new here, ive read of most of the bits im talking about somewhere on the net, but im surprised no one got all the effects combined in one, no extra cost, no extra rendering… no extra code whatsoever, just one more glBind and one more glActiveTextureARB…

thanks FBO, i was waiting for you since i’ve tried to implement accumulation buffer on my Voodoo2 quite some time ago

there is also motion-blur and funky-scanlines effect that i dont mention

does this work on ATI?

cheers

6aa952514ff4e5439df1e9e6d337b864
0
roel 101 Dec 20, 2006 at 18:20

Pretty cool! I have to admit that i didn’t read everything but i like the fog-for-depth trick.

46407cc1bdfbd2db4f6e8876d74f990a
0
Kenneth_Gorking 101 Dec 20, 2006 at 19:26

As for the “does this work on ATI?” I can report that I get a very consistent framerate of 0(zero). That would probably be because of your use of pixel buffer objects, forcing the driver to do it in software. Might I ask why you’re not using vertex buffer objects, since you say that you are only using the PBOs for scene geometry? VBOs are also supported by far more GPUs…

Fb2b4b2e70419434ee2b6d27a44cb440
0
zelko 101 Dec 21, 2006 at 00:45

sheesh! you are right im using VBO Vertex Buffer Objects -Vertex Arrayas not PBOs.. hahhaa, ill see if i cen make changes and correct that, thanks!

i have to admit im bit lost in all new extensions and stuff,
last time i wrote OpenGL code there wasnt any of these:

extern PFNGLBINDRENDERBUFFEREXTPROC glBindRenderbufferEXT = NULL;
extern PFNGLDELETERENDERBUFFERSEXTPROC glDeleteRenderbuffersEXT = NULL;
extern PFNGLGENRENDERBUFFERSEXTPROC glGenRenderbuffersEXT = NULL;
extern PFNGLRENDERBUFFERSTORAGEEXTPROC glRenderbufferStorageEXT = NULL;

what the hell!?
it reminded me of direct3d and why i choose OpenGL in a first place, eventualy i figured out about GLee.h, wheeeew, thanks Glee! but, even then, extensions have gone thru process of promotions over to ARB, so its really hard to catch good source code, i myself cant always understand OpenGL spec and some little piece of practical code of 3-4 lines can make it clear what 3 pages of specs couldnt, so i believe i have catch most of the quirks by now in regard to most up-to date extension you’d wanna use today to implement shadow mapping, and id like to see how my experience compares to others (yours)?

VBOs are just like normal vertex array, syntax pretty much the same, it just that these arrays got to be kept in fast memory if possible,

this is all of the code in my demo regardin VBOs

Normal = new GLfloat[CNT];
Vertex = new GLfloat[CNT];

//some other code to read map
//some other code to load values into *Vertex and *Normal

//*** init VBO, vertex,normals arrays
glGenBuffersARB(1, &vboId);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, CNT*2*sizeof(GLfloat), 0, GL_DYNAMIC_DRAW_ARB);
glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, CNT*sizeof(GLfloat), Vertex);
glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, CNT*sizeof(GLfloat), CNT*sizeof(GLfloat), Normal);

glNormalPointer(GL_FLOAT, 0, (void*)(CNT*sizeof(GLfloat)));
glVertexPointer(3, GL_FLOAT, 0, 0); //call this last
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
//***end init

//***draw
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);

glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);

glColor3f(1,1,1);
glDrawArrays(GL_QUADS, 0, quadCount*4);

glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays
glDisableClientState(GL_NORMAL_ARRAY);

glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

and also, in this last demo i compile the scene in disply list, rather than glDrawArrays for small performce boost on my system..

so, if im not using VBOs nor PBOs, but diplay list why doesnt it work on ATI?

that leaves only:
ARB Multitexturing
ARB shadow (depth texture, compare_to_r thingy)

demo also uses only GL_CLAMP not CLAP_TO_EDGE or _TO_BORDER
everything else is opengl 1.1

46407cc1bdfbd2db4f6e8876d74f990a
0
Kenneth_Gorking 101 Dec 21, 2006 at 15:07

I think I might now whats wrong… A while ago when I was working with framebuffer objects, I was having some trouble setting them up properly and the driver kept returning ‘GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT’ no matter what I did. I went over the specs, other peoples code, everything I could get my hands on, but nothing worked! Then I noticed that in the ATI frameworks OpenGL renderer code where they create the texture to use with the framebuffer object, they had two glTexParameterf that I didn’t have in my code.

glGenTextures(1, &rti.id);
glBindTexture(rti.target, rti.id);

glTexParameterf(rti.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(rti.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

I didn’t pay much attention to them, because I was convinced they were not important. 3 days later I still hadn’t gotten it to work, so in my desperation I just copied the two calls into my code, and then it worked! :blink:

I don’t why it works, I suspect it might be a bug since there are no mentioning of this anywhere in the specs or on ATIs site. But try it out, it might just work for you too :)

Fb2b4b2e70419434ee2b6d27a44cb440
0
zelko 101 Dec 22, 2006 at 00:29

hi,

i dont think that should matter to FBO, they anyway set by default to GL_NEAREST and you can have different seetings for different textures all attached to one FBO

anyway, i went to buy geforce fx5200, 128mb (my mbo is only 4x AGP)

that uncovered few things and i believe the problem was because of non power of 2 texture for scanlines that was set to 480x480

with that set to 512x512, my new second-hand fx5200 moved from 0 fps to 25-40fps

here is the fix with two EXEs, one has fps lock to enable flashlight hack to work(it will slow down if your card cant push 60fps)

the other have free fps so you can see how it performs on your system(dont forget to turn vsync off in your desktop settings)

http://www.mediamax.com/zedemo/z3D_0_5_3_ATI_fix.zip

interesting thing to notice is that it has only around 5fps drop with double of polygons per scene with fx5200, while with 7300GT you can push much more, i guess the slowdown happens on fx5200 mainly coz its 128mb in contrast to 7300 that is 256, so when display list or vertex arrays and textures all wanna be in fast memory something has to fall out at the point of full usage, eh?

that is 64x64x2=8192 (if flat, but quite a few more in this scene) triangles per scene
with 5 off-screen rendering to 1024x1024
and another 5 off-screen rendering camera view to (3 layer arbmultitex) 512x512(blur texture)

per frame, thats more than 1.6mil triangles per second with fx5200
and more tha 5.7mil triangles per sec with 7300GT

2 questions:

  1. whats the average polycount in todays games per scene?
  2. how can you make stuff to be drawn in say black but so you dont have to change original color eg.

drawCube(){
glColor3f(1, 0, 0.5f);
auxSolidCube(20);
}

then, i want to call drawCube(); but i want to reder it in black without changing that glColor call,
(we need that for fog texture so our shadows get black(if not transparent), this is only to make code more flexible so it can fit easily in any code)

here is how i do it:
//make it draw black regadless of later change of color
glEnable(GL_LIGHTING);
glDisable(GL_LIGHT0);
glDisable(GL_COLOR_MATERIAL);

drawCube();

enable lighting, but disable light, huh?!
that works, and i believe it can also be done with some color scaling, but is there a simpler method that is supposed to be used in such cases?

thanks

46407cc1bdfbd2db4f6e8876d74f990a
0
Kenneth_Gorking 101 Dec 22, 2006 at 12:37

@zelko

i dont think that should matter to FBO, they anyway set by default to GL_NEAREST and you can have different seetings for different textures all attached to one FBO

Well like I said, I didn’t think it mattered either, but it did ;)
@zelko

with that set to 512x512, my new second-hand fx5200 moved from 0 fps to 25-40fps

Works on mine too now :)
But it looks like it is using 8-bit colors…

Fb2b4b2e70419434ee2b6d27a44cb440
0
zelko 101 Dec 22, 2006 at 23:00

thanks for the info, and you are right, its 16bit color this ati_fix version

interestingly, on 7300GT all combinations 16/32 color and 16/32 depth produce same fps, and fps drops only proportionaly to render texture size(shadow and motion blur texture)

while on fx5200 depth can be 16 or 32 with no impact to fps, but 16bit color performs 10-20fps more than 32bit

can enyone shad any light on
- why would it be faste to render to 32bit RGBA than 8bit LUMINANCE?
- with FBO, why RenderbufferStorage(..GL_DEPTH_COMPONENT..) slows down and is completely unnecessary as such, easily replacad by attaching depth texture which is faster and texture can be used later in contrast to former

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Dec 23, 2006 at 13:11

I cant a framerate of around 200 on my Fx 5950, but it doesnt actually display anything appart from some frustum