Skip to main content
GameDev.net gamedev.net
🔒 Locked

Batching VS Occlusion Culling

Started by kovacsp Mar 20, 2006 at 5:36 AM 12 replies 4.2k views
Original Post
kovacsp
kovacsp
Hi, when I have hundreds of objects, having ~10 materials each, rendering will be slow because of the large number of batches. The first thing that comes to my mind is using atlases, the second is occlusion culling. First, I tried creating atlases, then merging the meshes of objects, but then, I still had many-many DrawPrim calls, because not all materials can be merged by an atlas, and I have to render my scene twice if using shadow volumes for one light (and even more times, it I have more lights) Then, I've found that maybe I could use occlusion culling to reduce the number of objects to render, but then I realized that it will not decrease the number of DP calls, as I will have to call one for each object anyway (even if it's just a bbox). So I need more agressive batching (merging different objects, by duplicating and baking in transformations). But this makes occlusion culling even more impossible, as all I can do is to start a query for a bunch of parts of totally independent objects, which doesn't really seem to make sense. So where is a good balance between the two? Does it worth doing occlusion culling in this situation at all? How do you usually solve this dilemma? Thanks for your kind help, kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
remigius
remigius
I had this problem too a while back (in this thread) and it turns out that combining occlusion culling and batching typically doesn't work out. As Sirob said in that thread, it's probably better to use a quadtree/octree for view frustum culling. I haven't gotten around to properly testing this yet, but does seem the best solution.

Hope this helps :)
Nik02
Nik02
If you're planning for the future, then Direct3D 10 will help you a lot with this problem. It enables predicated rendering, whereby you don't have to download occlusion data back to CPU in order to use it, as it is now. This saves you from unnecessary waiting and state changes.

Batching tends to be more important than occlusion culling, when using relatively simple pixel shaders. This is because the commands to draw something usually take more time than the drawing itself in this case.

It is always beneficial to do some simple space partitioning before issuing rendering commands, if it is obvious that some objects won't be at the screen most of the time. However, do this per-object, not per-polygon at any case.
Niko Suni
kovacsp
kovacsp
Hi guys,

and thanks for your replies. In this case, I think I should forget occlusion culling for now (however I have lots of occluded objects in my scene). As I don't have anything similar to an octree yet, the best I can do is to get the best out of batching as possible.

Then comes another question:
if I wanna fill a huge vertex/index buffer, and use it (or portions of it) for rendering the scene in the next frame, then what's the best choice? I was thinking about a vertex buffer in systemmem, and one in default mem, using the latter for rendering, while filling the former. Then, at the end of the frame, I could load the systemmem buffer to the default mem buffer. Thus, no stalling takes place (hopefully).
Or should I just create a dynamic vertex and index buffer, always lock it with discard, and let it go?

kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
Nik02
Nik02
Indices take up much less bandwidth than vertices, therefore it is wiser to make them dynamic - instead of the actual vertex data - if you at all can, and group all possible geometry to as few hardware vertex buffers as possible.

With Shader Model 3, you can send objects' instance-specific data in a separate geometry stream, so you can render multiple geometrically similar objects with one draw command. If possible, use this to your advantage.
Niko Suni
remigius
remigius
Quote:
if I wanna fill a huge vertex/index buffer, and use it (or portions of it) for rendering the scene in the next frame, then what's the best choice? I was thinking about a vertex buffer in systemmem, and one in default mem, using the latter for rendering, while filling the former. Then, at the end of the frame, I could load the systemmem buffer to the default mem buffer


I've been thinking about this today and I have a gut feeling that re-using a single dynamic vertex/index buffer pair should be much more efficient than the approach you're describing. It seems to me that by filling the sysmem buffer you're probably pushing your rendering towards CPU bound limits and in addition to this it consumes unnecessary bandwidth to the GPU.

If you want to take the swap approach, my guess is that you're probably better off by creating two buffer pairs (or only the two index buffers if you can, like Nik02 pointed out) in the managed memory pool with the Dynamic attribute and swapping these out each frame. However, I'm not entirely sure about all of this this, so any authorative word would be much appreciated [smile]
Wixner
Wixner
I'd like to borrow this thread a little bit, but i suppose others interested in culling and batching would benefit from my hijacking.

What about occlusion (and frustum culling too, actually) and occluded (or frustum culled) objects that casts shadow?
kovacsp
kovacsp
Thanks for your help!
Quote:
Original post by Nik02
Indices take up much less bandwidth than vertices, therefore it is wiser to make them dynamic - instead of the actual vertex data - if you at all can, and group all possible geometry to as few hardware vertex buffers as possible.

I was thinking about this, but still don't know.. could you please show a real-world situation where I can achieve my goal by just changing the index buffer? When I have a new object, I have to have new vertices. When it moves, I have to modify my vertices, since I've pre-baked the transformation. So whwn should I modify my indices only?

kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
sirob
sirob
LOD is a prime example of when changing indices works great. If you want to reduce an object's LOD, you can "skip" vertices by simply changing the indices.

This is also a great example of a possible optimization for your case. Since you have lots of meshes with several materials on each, you could possibly create a lower-poly, and more importantly lower-material-count mesh for use when the mesh is far from the camera. If done right, the user would never even tell.

Hope this helps.
Sirob Yes.» - status: Work-O-Rama.
kovacsp
kovacsp
Hi, and thanks for your reply!
Quote:
Original post by remigius
I've been thinking about this today and I have a gut feeling that re-using a single dynamic vertex/index buffer pair should be much more efficient than the approach you're describing. It seems to me that by filling the sysmem buffer you're probably pushing your rendering towards CPU bound limits and in addition to this it consumes unnecessary bandwidth to the GPU.

If I do so.. which one do you think better?

1. I have an object to render. I render it, but also copy its vertices to my batch buffer. Then, in the next frame, I don't render it since it's already in the buffer, so it will be rendered batched.

2. I have an object to render. I don't render it, but copy it to the buffer, then I render the buffer contents in the same frame.

With both approaches, the objects already in the buffer will remain intact, so there will be no work to do with them. If some of them are deleted, they will be omitted from the DP call (two DP instead of one, in case of one deleted obj, I don't think it matters)

With both cases: am I right that locking only parts of the dynamic buffers makes sense?

kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
kovacsp
kovacsp
Quote:
Original post by sirob
LOD is a prime example of when changing indices works great. If you want to reduce an object's LOD, you can "skip" vertices by simply changing the indices.
wow, thanks! [smile] now it's clear!

kp

------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
kovacsp
kovacsp
Quote:
Original post by Wixner
I'd like to borrow this thread a little bit, but i suppose others interested in culling and batching would benefit from my hijacking.
You're welcome [smile]

Quote:
Original post by Wixner
What about occlusion (and frustum culling too, actually) and occluded (or frustum culled) objects that casts shadow?

In case of shadow volumes, I think you should do the culling not for the objects, but the shadow volumes themselves. If you do so, you'll not lose any important shadow volumes.

In case of shadow maps, you'd probably do culling from the light point of view (since you render your depth map from there), and in this case, you'll now lose anything again.


I hope I'm right :)
kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
remigius
remigius
Quote:
If I do so.. which one do you think better?


I think I'd go for the second approach. In both cases you'll be adding the object to your batch buffer anyway, so you might as well use the updated buffer right away and not render the object seperately.

Quote:
With both cases: am I right that locking only parts of the dynamic buffers makes sense?


AFAIK, yes :) You can lock static buffers too, but perfomancewise you should always use dynamic buffers if you're going to do a lot of writing to the buffers, which should be the case for your batch buffer.

Quote:
When I have a new object, I have to have new vertices. When it moves, I have to modify my vertices, since I've pre-baked the transformation. So whwn should I modify my indices only?


This also might be a point where you can improve. I don't know if you have a specific reason to pre-bake the transfomation into the vertices, but since you're doing this you essentially have a seperate mesh for each object. If you need to render the same mesh multiple times, it's probably a good idea to use the base mesh as the stream source and apply the transformations through the world matrix.

This saves memory and might speed up performance since you don't have to call SetStreamSource as often when you're rendering the meshes seperately, without batching. It may well be that if you have only a few unique base meshes (< ~25 unique models) to render and you use this approach, you won't even need batching.

If you can seperate the transformations (and other instance specific data) from the vertex data, you also might want to look into hardware instancing as Nik02 suggested. This essentially is a more advanced form of batching which can take advantage of SM2 and SM3 hardware to render large batches of meshes without doing too much CPU bound work.

Quote:
What about occlusion (and frustum culling too, actually) and occluded (or frustum culled) objects that casts shadow?


If you're using shadow volumes, you'll still need to render the shadow volumes objects that may cast shadows onto the scene, as kovacsp already pointed out. His proposed approach of performing culling on the shadow volumes themselves is probably the safest way, but if you're extruding the volumes on the GPU you're a bit stuck.

For most of these cases though I found it works out well enough if you cull the base shadowcasting objects against a frustum of about 2 times the size of your view frustum. It's a bit messy and it may not be the most efficient way, but if it works, it works.

Well, it sounds to me kovacsp is also right about the shadow map, so I'll just leave it at this :)
kovacsp
kovacsp
Quote:
Original post by remigius
Quote:
If I do so.. which one do you think better?

I think I'd go for the second approach. In both cases you'll be adding the object to your batch buffer anyway, so you might as well use the updated buffer right away and not render the object seperately.
Yes' but in this case, I have to wait till it will be uploaded to the card before rendering. If I use it in the next frame only, it can be uploaded in some spare time.. this is why I'm uncertain - but maybe this is not that important.

kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.