Loading...
Searching...
No Matches
Tutorial 26: OcclusionQuery

This Tutorial shows how to speed up rendering by use of the OcclusionQuery feature. The usual rendering tries to avoid rendering of scene nodes by culling those nodes which are outside the visible area, the view frustum. However, this technique does not cope with occluded objects which are still in the line of sight, but occluded by some larger object between the object and the eye (camera). Occlusion queries check exactly that. The queries basically measure the number of pixels that a previous render left on the screen. Since those pixels cannot be recognized at the end of a rendering anymore, the pixel count is measured directly when rendering. Thus, one needs to render the occluder (the object in front) first. This object needs to write to the z-buffer in order to become a real occluder. Then the node is rendered and in case a z-pass happens, i.e. the pixel is written to the framebuffer, the pixel is counted in the query. The result of a query is the number of pixels which got through. One can, based on this number, judge if the scene node is visible enough to be rendered, or if the node should be removed in the next round. Also note that the number of pixels is a safe over approximation in general. The pixels might be overdrawn later on, and the GPU tries to avoid inaccuracies which could lead to false negatives in the queries.

As you might have recognized already, we had to render the node to get the numbers. So where's the benefit, you might say. There are several ways where occlusion queries can help. It is often a good idea to just render the bbox of the node instead of the actual mesh. This is really fast and is a safe over approximation. If you need a more exact render with the actual geometry, it's a good idea to render with just basic solid material. Avoid complex shaders and state changes through textures. There's no need while just doing the occlusion query. At least if the render is not used for the actual scene. This is the third way to optimize occlusion queries. Just check the queries every 5th or 10th frame, or even less frequent. This depends on the movement speed of the objects and camera.

#ifdef _MSC_VER
// We'll also define this to stop MSVC complaining about sprintf().
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(lib, "Irrlicht.lib")
#endif
#include <irrlicht.h>
#include "driverChoice.h"
using namespace irr;
driverChoice.h
irrlicht.h
Main header file of the irrlicht, the only file needed to include.
irr
Everything in the Irrlicht Engine can be found in this namespace.
Definition aabbox3d.h:13

We need keyboard input events to switch some parameters

class MyEventReceiver : public IEventReceiver
{
public:
// This is the one method that we have to implement
virtual bool OnEvent(const SEvent& event)
{
// Remember whether each key is down or up
if (event.EventType == irr::EET_KEY_INPUT_EVENT)
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
return false;
}
// This is used to check whether a key is being held down
virtual bool IsKeyDown(EKEY_CODE keyCode) const
{
return KeyIsDown[keyCode];
}
MyEventReceiver()
{
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
KeyIsDown[i] = false;
}
private:
// We use this array to store the current state of each key
bool KeyIsDown[KEY_KEY_CODES_COUNT];
};
irr::IEventReceiver
Interface of an object which can receive events.
Definition IEventReceiver.h:434
irr::EKEY_CODE
EKEY_CODE
Definition Keycodes.h:12
irr::KEY_KEY_CODES_COUNT
@ KEY_KEY_CODES_COUNT
Definition Keycodes.h:168
irr::EET_KEY_INPUT_EVENT
@ EET_KEY_INPUT_EVENT
A key input event.
Definition IEventReceiver.h:35
irr::SEvent
SEvents hold information about an event. See irr::IEventReceiver for details on event handling.
Definition IEventReceiver.h:263
irr::SEvent::EventType
EEVENT_TYPE EventType
Definition IEventReceiver.h:414

We create an irr::IrrlichtDevice and the scene nodes. One occluder, one occluded. The latter is a complex sphere, which has many triangles.

int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
MyEventReceiver receiver;
IrrlichtDevice* device = createDevice(driverType,
core::dimension2d<u32>(640, 480), 16, false, false, false, &receiver);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
smgr->getGUIEnvironment()->addStaticText(L"Press Space to hide occluder.", core::recti(10,10, 200,50));
irr::IrrlichtDevice
The Irrlicht device. You can create it with createDevice() or createDeviceEx().
Definition IrrlichtDevice.h:44
irr::IrrlichtDevice::getSceneManager
virtual scene::ISceneManager * getSceneManager()=0
Provides access to the scene manager.
irr::IrrlichtDevice::getVideoDriver
virtual video::IVideoDriver * getVideoDriver()=0
Provides access to the video driver for drawing 3d and 2d geometry.
irr::core::dimension2d
Specifies a 2 dimensional size.
Definition dimension2d.h:21
irr::gui::IGUIEnvironment::addStaticText
virtual IGUIStaticText * addStaticText(const wchar_t *text, const core::rect< s32 > &rectangle, bool border=false, bool wordWrap=true, IGUIElement *parent=0, s32 id=-1, bool fillBackground=false)=0
Adds a static text.
irr::scene::ISceneManager
The Scene Manager manages scene nodes, mesh recources, cameras and all the other stuff.
Definition ISceneManager.h:151
irr::scene::ISceneManager::getGUIEnvironment
virtual gui::IGUIEnvironment * getGUIEnvironment()=0
Get the active GUIEnvironment.
irr::video::IVideoDriver
Interface to driver which is able to perform 2d and 3d graphics functions.
Definition IVideoDriver.h:257
irr::core::recti
rect< s32 > recti
Rectangle with int values.
Definition rect.h:273
irr::video::E_DRIVER_TYPE
E_DRIVER_TYPE
An enum for all types of drivers the Irrlicht Engine supports.
Definition EDriverTypes.h:15
irr::video::EDT_COUNT
@ EDT_COUNT
No driver, just for counting the elements.
Definition EDriverTypes.h:56
irr::createDevice
IRRLICHT_API IrrlichtDevice *IRRCALLCONV createDevice(video::E_DRIVER_TYPE deviceType=video::EDT_SOFTWARE, const core::dimension2d< u32 > &windowSize=(core::dimension2d< u32 >(640, 480)), u32 bits=16, bool fullscreen=false, bool stencilbuffer=false, bool vsync=false, IEventReceiver *receiver=0)
Creates an Irrlicht device. The Irrlicht device is the root object for using the engine.

Create the node to be occluded. We create a sphere node with high poly count.

scene::ISceneNode * node = smgr->addSphereSceneNode(10, 64);
if (node)
{
node->setPosition(core::vector3df(0,0,60));
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
}
irr::scene::ISceneManager::addSphereSceneNode
virtual IMeshSceneNode * addSphereSceneNode(f32 radius=5.0f, s32 polyCount=16, ISceneNode *parent=0, s32 id=-1, const core::vector3df &position=core::vector3df(0, 0, 0), const core::vector3df &rotation=core::vector3df(0, 0, 0), const core::vector3df &scale=core::vector3df(1.0f, 1.0f, 1.0f))=0
Adds a sphere scene node of the given radius and detail.
irr::scene::ISceneNode
Scene node interface.
Definition ISceneNode.h:41
irr::scene::ISceneNode::setMaterialTexture
void setMaterialTexture(u32 textureLayer, video::ITexture *texture)
Sets the texture of the specified layer in all materials of this scene node to the new texture.
Definition ISceneNode.h:436
irr::scene::ISceneNode::setPosition
virtual void setPosition(const core::vector3df &newpos)
Sets the position of the node relative to its parent.
Definition ISceneNode.h:507
irr::scene::ISceneNode::setMaterialFlag
void setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue)
Sets all material flags at once to a new value.
Definition ISceneNode.h:425
irr::video::IVideoDriver::getTexture
virtual ITexture * getTexture(const io::path &filename)=0
Get access to a named texture.
irr::core::vector3df
vector3d< f32 > vector3df
Typedef for a f32 3d vector.
Definition vector3d.h:445
irr::video::EMF_LIGHTING
@ EMF_LIGHTING
Will this material be lighted? Default: true.
Definition EMaterialFlags.h:26

Now we create another node, the occluder. It's a simple plane.

scene::ISceneNode* plane = smgr->addMeshSceneNode(smgr->addHillPlaneMesh(
"plane", core::dimension2df(10,10), core::dimension2du(2,2)), 0, -1,
core::vector3df(0,0,20), core::vector3df(270,0,0));
if (plane)
{
plane->setMaterialTexture(0, driver->getTexture("../../media/t351sml.jpg"));
plane->setMaterialFlag(video::EMF_LIGHTING, false);
plane->setMaterialFlag(video::EMF_BACK_FACE_CULLING, true);
}
irr::scene::ISceneManager::addMeshSceneNode
virtual IMeshSceneNode * addMeshSceneNode(IMesh *mesh, ISceneNode *parent=0, s32 id=-1, const core::vector3df &position=core::vector3df(0, 0, 0), const core::vector3df &rotation=core::vector3df(0, 0, 0), const core::vector3df &scale=core::vector3df(1.0f, 1.0f, 1.0f), bool alsoAddIfMeshPointerZero=false)=0
Adds a scene node for rendering a static mesh.
irr::scene::ISceneManager::addHillPlaneMesh
virtual IAnimatedMesh * addHillPlaneMesh(const io::path &name, const core::dimension2d< f32 > &tileSize, const core::dimension2d< u32 > &tileCount, video::SMaterial *material=0, f32 hillHeight=0.0f, const core::dimension2d< f32 > &countHills=core::dimension2d< f32 >(0.0f, 0.0f), const core::dimension2d< f32 > &textureRepeatCount=core::dimension2d< f32 >(1.0f, 1.0f))=0
Adds a Hill Plane mesh to the mesh pool.
irr::core::dimension2du
dimension2d< u32 > dimension2du
Typedef for an unsigned integer dimension.
Definition dimension2d.h:212
irr::core::dimension2df
dimension2d< f32 > dimension2df
Typedef for an f32 dimension.
Definition dimension2d.h:210
irr::video::EMF_BACK_FACE_CULLING
@ EMF_BACK_FACE_CULLING
Is backface culling enabled? Default: true.
Definition EMaterialFlags.h:36

Here we create the occlusion query. Because we don't have a plain mesh scene node (ESNT_MESH or ESNT_ANIMATED_MESH), we pass the base geometry as well. Instead, we could also pass a simpler mesh or the bounding box. But we will use a time based method, where the occlusion query renders to the frame buffer and in case of success (occlusion), the mesh is not drawn for several frames.

driver->addOcclusionQuery(node, ((scene::IMeshSceneNode*)node)->getMesh());
irr::scene::IMeshSceneNode
A scene node displaying a static mesh.
Definition IMeshSceneNode.h:21
irr::video::IVideoDriver::addOcclusionQuery
virtual void addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh *mesh=0)=0
Create occlusion query.

We have done everything, just a camera and draw it. We also write the current frames per second and the name of the driver to the caption of the window to examine the render speedup. We also store the time for measuring the time since the last occlusion query ran and store whether the node should be visible in the next frames.

smgr->addCameraSceneNode();
int lastFPS = -1;
u32 timeNow = device->getTimer()->getTime();
bool nodeVisible=true;
while(device->run())
{
plane->setVisible(!receiver.IsKeyDown(irr::KEY_SPACE));
driver->beginScene(true, true, video::SColor(255,113,113,133));
irr::ITimer::getTime
virtual u32 getTime() const =0
Returns current virtual time in milliseconds.
irr::IrrlichtDevice::run
virtual bool run()=0
Runs the device.
irr::IrrlichtDevice::getTimer
virtual ITimer * getTimer()=0
Provides access to the engine's timer.
irr::scene::ISceneManager::addCameraSceneNode
virtual ICameraSceneNode * addCameraSceneNode(ISceneNode *parent=0, const core::vector3df &position=core::vector3df(0, 0, 0), const core::vector3df &lookat=core::vector3df(0, 0, 100), s32 id=-1, bool makeActive=true)=0
Adds a camera scene node to the scene graph and sets it as active camera.
irr::scene::ISceneNode::setVisible
virtual void setVisible(bool isVisible)
Sets if the node should be visible or not.
Definition ISceneNode.h:252
irr::video::IVideoDriver::beginScene
virtual bool beginScene(bool backBuffer=true, bool zBuffer=true, SColor color=SColor(255, 0, 0, 0), const SExposedVideoData &videoData=SExposedVideoData(), core::rect< s32 > *sourceRect=0)=0
Applications must call this method before performing any rendering.
irr::video::SColor
Class representing a 32 bit ARGB color.
Definition SColor.h:202
irr::u32
unsigned int u32
32 bit unsigned variable.
Definition irrTypes.h:58
irr::KEY_SPACE
@ KEY_SPACE
Definition Keycodes.h:40

First, we draw the scene, possibly without the occluded element. This is necessary because we need the occluder to be drawn first. You can also use several scene managers to collect a number of possible occluders in a separately rendered scene.

node->setVisible(nodeVisible);
smgr->drawAll();
smgr->getGUIEnvironment()->drawAll();
irr::gui::IGUIEnvironment::drawAll
virtual void drawAll()=0
Draws all gui elements by traversing the GUI environment starting at the root node.
irr::scene::ISceneManager::drawAll
virtual void drawAll()=0
Draws all the scene nodes.

Once in a while, here every 100 ms, we check the visibility. We run the queries, update the pixel value, and query the result. Since we already rendered the node we render the query invisible. The update is made blocking, as we need the result immediately. If you don't need the result immediately, e.g. because you have other things to render, you can call the update non-blocking. This gives the GPU more time to pass back the results without flushing the render pipeline. If the update was called non-blocking, the result from getOcclusionQueryResult is either the previous value, or 0xffffffff if no value has been generated at all, yet. The result is taken immediately as visibility flag for the node.

if (device->getTimer()->getTime()-timeNow>100)
{
driver->runAllOcclusionQueries(false);
driver->updateAllOcclusionQueries();
nodeVisible=driver->getOcclusionQueryResult(node)>0;
timeNow=device->getTimer()->getTime();
}
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw tmp(L"OcclusionQuery Example [");
tmp += driver->getName();
tmp += L"] fps: ";
tmp += fps;
device->setWindowCaption(tmp.c_str());
lastFPS = fps;
}
}
irr::IrrlichtDevice::setWindowCaption
virtual void setWindowCaption(const wchar_t *text)=0
Sets the caption of the window.
irr::video::IVideoDriver::getOcclusionQueryResult
virtual u32 getOcclusionQueryResult(scene::ISceneNode *node) const =0
Return query result.
irr::video::IVideoDriver::getFPS
virtual s32 getFPS() const =0
Returns current frames per second value.
irr::video::IVideoDriver::getName
virtual const wchar_t * getName() const =0
Gets name of this video driver.
irr::video::IVideoDriver::endScene
virtual bool endScene()=0
Presents the rendered image to the screen.
irr::video::IVideoDriver::updateAllOcclusionQueries
virtual void updateAllOcclusionQueries(bool block=true)=0
Update all occlusion queries. Retrieves results from GPU.
irr::video::IVideoDriver::runAllOcclusionQueries
virtual void runAllOcclusionQueries(bool visible=false)=0
Run all occlusion queries. Draws all meshes stored in queries.
irr::core::stringw
string< wchar_t > stringw
Typedef for wide character strings.
Definition irrString.h:1361

In the end, delete the Irrlicht device.

device->drop();
return 0;
}
irr::IReferenceCounted::drop
bool drop() const
Drops the object. Decrements the reference counter by one.
Definition IReferenceCounted.h:116

That's it. Compile and play around with the program.

- Irrlicht Engine Documentation © 2003-2012 by Nikolaus Gebhardt. Generated on for Irrlicht 3D Engine by Doxygen 1.15.0