Visualization LibraryA lightweight C++ OpenGL middleware for 2D/3D graphics |
[Home] [Tutorials] [All Classes] [Grouped Classes] |
GL_ARB_occlusion_query.
For more information about the OpenGL extension GL_ARB_occlusion_query see also http://oss.sgi.com/projects/ogl-sample/registry/ARB/occlusion_query.txt
| Sample Foreset Model (1681 trees) - Benchmark on NVIDIA GeForce 8600M GT, Intel Core 2 Duo 2.0GHz | |||
|---|---|---|---|
|
|
|
|
Large occluder in red:
| View of the forest from above:
| View from under, the ground is a very good occluder:
| View of the forest from inside:
|
When rendering highly detailed and densely populated scenes most of the rendered geometry is actually hidden by the other objects and geometry which are closer to the camera. This means that a consistent part the GPU time and computational power is spent to render objects that will not be visible in the final rendering. Consider for example a dense forest with thousands of trees. The trees that are close to the camera will have a high chance to be visible but the ones that are far away from the camera will probably not be visible due to the fact that the close ones occlude a significant part of the view. The term "occlusion culling" refers to a set of techniques that try to exploit this fact. By detecting in advance which objects will be occluded and thus invisible, we can avoid rendering objects that will not significantly contribute to the scene, potentially obtaining a dramatic rendering boost as seen from the figures above.
GL_ARB_occlusion_query allows a program to request if a given object will contribute or not to the final rendering providing a huge potential in terms of rendering performances increase as we have seen. Unfortunately this technique has also some drawbacks due to the way the GPU and CPU collaborate, the most important being the fact that once you requested this occlusion visibility check, the OpenGL driver cannot reply before having executed all the OpenGL commands that precede such request. For this reason in order to maximize the CPU/GPU concurrency (and with it the rendering performances) Visualization Library waits one rendering frame before checking the result of the occlusion queries. This means that at any given frame the result of such query actually refers to the position that the camera and the objects had relative to each other in the previous frame. As a result of this occasional flickers might occur when an invisible object becomes visible especially when the camera or such object is moving fast compared to the rendering refresh rate. An example of such problem is shown below.
In the image above the camera is quickly moving to the left uncovering a part of the trees that in the preceding frame were covered by the red occluder. Such trees weren't visible in the previous frame and are thus not rendered in the current frame producing a temporary "hole" in the forest. Note that such "hole" lasts for a single rendering frame but potentially new holes might occur while the camera continues its movement.
Artifacts like this might be more or less noticeable based on the kind of scene rendered and based on the refresh rate of the rendering. For example, it is very easy to notice this effect at 10 fps, but is much more difficult if not impossible to notice them at let say 160 fps.
Note that requesting the result of an occlusion query in the same frame in which it is issued is not an option as this would not only kill the GPU/CPU concurrency but would also cause a vicious cpu-stall/gpu-starvation circle that would result in unacceptable performances even for simple scenes.
The red dot on the left represents the camera, the gray object is a large occluder, the boxes are other objects occluded by the large gray one. As we can see if we simply render the objects in front to back order based on their centers we end up rendering first the red boxes (thus wasting time since they will not be visible!), then the large gray occluder which overdraws the red boxes when rendered on the screen, and then the green boxes (which are actually not rendered thanks to the occlusion culling). With this approach we rendered six boxes (the red ones) which are actually not visible. To optimize this case we would render the gray occluder for first, specifying for it a render rank lower than the boxes' render rank. Remember that the user specified ordering (Actor render rank and render block and Effect render rank) always wins against any eventual automatic sorting performed by a vl::RenderQueueSorter.
Also keep in mind that the concept of "large occluder" is heavily subjective as a skyscraper seen from 1 Km away is not a good occluder while a matchbox seen from very close can become a very good one!
[From App_OcclusionCulling.hpp]
class App_OcclusionCulling: public BaseDemo { public: void initEvent() { BaseDemo::initEvent(); // ####################################################################### // # These 2 lines are the only code needed to enable occlusion culling! # // ####################################################################### vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->renderer()->setOcclusionCullingEnabled(true); vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->setRenderQueueSorter(new vl::RenderQueueSorterOcclusion); /* the rest of the code simply generates a forest of thousands of trees */ /* setup a simple effect to use for all our objects */ vl::ref<vl::Effect> fx = new vl::Effect; fx->shader()->enable(vl::EN_DEPTH_TEST); fx->shader()->enable(vl::EN_LIGHTING); fx->shader()->gocMaterial()->setColorMaterialEnabled(true); fx->shader()->gocLight(0)->setLinearAttenuation(0.0025f); /* the ground under the trees */ float side = 400; vl::ref<vl::Geometry> ground = vlut::makeGrid(vl::vec3(0,0,0), side*2.0f,side*2.0f, (int)side, (int)side); ground->computeNormals(); ground->setColorArray(vlut::green); vl::Actor* ground_act = sceneManager()->tree()->addActor(ground.get(), fx.get(), NULL); /* assign a render rank of -1 (default is 0) to be sure to render the ground before all the trees */ ground_act->setRenderRank(-1); /* the red wall in front of the camera */ vl::ref<vl::Geometry> wall = vlut::makeBox(vl::vec3(0,25,500), 50,50,1); wall->computeNormals(); wall->setColorArray(vlut::red); sceneManager()->tree()->addActor(wall.get(), fx.get(), NULL); /* the trees */ float trunk_h = 20; float trunk_w = 4; /* the tree's branches */ vl::ref<vl::Geometry> branches = vlut::makeIcosphere(vl::vec3(0,trunk_h/2.0f,0), 7, 1, false); branches->computeNormals(); branches->setColorArray( vlut::green ); /* the tree's trunk */ vl::ref<vl::Geometry> trunk = vlut::makeCylinder(vl::vec3(0,0,0),trunk_w,trunk_h, 35, 35); trunk->computeNormals(); trunk->setColorArray( vlut::gold ); /* fill our forest with trees! */ int trunk_count = 20; for(int i=-trunk_count; i<=trunk_count; ++i) for(int j=-trunk_count; j<=trunk_count; ++j) { float x = (float) i * side / trunk_count; float z = (float) j * side / trunk_count; vl::ref<vl::Transform> tr = new vl::Transform( vl::mat4::translation(x,trunk_h/2.0f+0.1f,z) ); tr->computeWorldMatrix(); sceneManager()->tree()->addActor(trunk.get(), fx.get(), tr.get()); sceneManager()->tree()->addActor(branches.get(), fx.get(), tr.get()); } } /* spacebar = toggles occlusion culling */ void keyPressEvent(unsigned short ch, vl::EKey key) { BaseDemo::keyPressEvent(ch, key); if (key == vl::Key_Space) { bool on = vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->renderer()->occlusionCullingEnabled(); vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->renderer()->setOcclusionCullingEnabled(!on); vl::Log::print( vl::Say("Occlusion = %s\n") << (vl::VisualizationLibrary::rendering()->as<vl::Rendering>()->renderer()->occlusionCullingEnabled() ? "On" : "Off") ); } } }; // Have fun!