1
/** Example 026 OcclusionQuery
3
This Tutorial shows how to speed up rendering by use of the
4
OcclusionQuery feature. The usual rendering tries to avoid rendering of
5
scene nodes by culling those nodes which are outside the visible area, the
6
view frustum. However, this technique does not cope with occluded objects
7
which are still in the line of sight, but occluded by some larger object
8
between the object and the eye (camera). Occlusion queries check exactly that.
9
The queries basically measure the number of pixels that a previous render
11
Since those pixels cannot be recognized at the end of a rendering anymore,
12
the pixel count is measured directly when rendering. Thus, one needs to render
13
the occluder (the object in front) first. This object needs to write to the
14
z-buffer in order to become a real occluder. Then the node is rendered and in
15
case a z-pass happens, i.e. the pixel is written to the framebuffer, the pixel
16
is counted in the query.
17
The result of a query is the number of pixels which got through. One can, based
18
on this number, judge if the scene node is visible enough to be rendered, or if
19
the node should be removed in the next round. Also note that the number of
20
pixels is a safe over approximation in general. The pixels might be overdrawn
21
later on, and the GPU tries to avoid inaccuracies which could lead to false
22
negatives in the queries.
24
As you might have recognized already, we had to render the node to get the
25
numbers. So where's the benefit, you might say. There are several ways where
26
occlusion queries can help. It is often a good idea to just render the bbox
27
of the node instead of the actual mesh. This is really fast and is a safe over
28
approximation. If you need a more exact render with the actual geometry, it's
29
a good idea to render with just basic solid material. Avoid complex shaders
30
and state changes through textures. There's no need while just doing the
31
occlusion query. At least if the render is not used for the actual scene. This
32
is the third way to optimize occlusion queries. Just check the queries every
33
5th or 10th frane, or even less frequent. This depends on the movement speed
34
of the objects and camera.
38
// We'll also define this to stop MSVC complaining about sprintf().
39
#define _CRT_SECURE_NO_WARNINGS
40
#pragma comment(lib, "Irrlicht.lib")
44
#include "driverChoice.h"
49
We need keyboard input events to switch some parameters
51
class MyEventReceiver : public IEventReceiver
54
// This is the one method that we have to implement
55
virtual bool OnEvent(const SEvent& event)
57
// Remember whether each key is down or up
58
if (event.EventType == irr::EET_KEY_INPUT_EVENT)
59
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
64
// This is used to check whether a key is being held down
65
virtual bool IsKeyDown(EKEY_CODE keyCode) const
67
return KeyIsDown[keyCode];
72
for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
77
// We use this array to store the current state of each key
78
bool KeyIsDown[KEY_KEY_CODES_COUNT];
83
We create an irr::IrrlichtDevice and the scene nodes. One occluder, one
84
occluded. The latter is a complex sphere, which has many triangles.
88
// ask user for driver
89
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
90
if (driverType==video::EDT_COUNT)
94
MyEventReceiver receiver;
96
IrrlichtDevice* device = createDevice(driverType,
97
core::dimension2d<u32>(640, 480), 16, false, false, false, &receiver);
100
return 1; // could not create selected driver.
102
video::IVideoDriver* driver = device->getVideoDriver();
103
scene::ISceneManager* smgr = device->getSceneManager();
105
smgr->getGUIEnvironment()->addStaticText(L"Press Space to hide occluder.", core::recti(10,10, 200,50));
108
Create the node to be occluded. We create a sphere node with high poly count.
110
scene::ISceneNode * node = smgr->addSphereSceneNode(10, 64);
113
node->setPosition(core::vector3df(0,0,60));
114
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
115
node->setMaterialFlag(video::EMF_LIGHTING, false);
119
Now we create another node, the occluder. It's a simple plane.
121
scene::ISceneNode* plane = smgr->addMeshSceneNode(smgr->addHillPlaneMesh(
122
"plane", core::dimension2df(10,10), core::dimension2du(2,2)), 0, -1,
123
core::vector3df(0,0,20), core::vector3df(270,0,0));
127
plane->setMaterialTexture(0, driver->getTexture("../../media/t351sml.jpg"));
128
plane->setMaterialFlag(video::EMF_LIGHTING, false);
129
plane->setMaterialFlag(video::EMF_BACK_FACE_CULLING, true);
133
Here we create the occlusion query. Because we don't have a plain mesh scene node
134
(ESNT_MESH or ESNT_ANIMATED_MESH), we pass the base geometry as well. Instead,
135
we could also pass a simpler mesh or the bounding box. But we will use a time
136
based method, where the occlusion query renders to the frame buffer and in case
137
of success (occlusion), the mesh is not drawn for several frames.
139
driver->addOcclusionQuery(node, ((scene::IMeshSceneNode*)node)->getMesh());
142
We have done everything, just a camera and draw it. We also write the
143
current frames per second and the name of the driver to the caption of the
144
window to examine the render speedup.
145
We also store the time for measuring the time since the last occlusion query ran
146
and store whether the node should be visible in the next frames.
148
smgr->addCameraSceneNode();
150
u32 timeNow = device->getTimer()->getTime();
151
bool nodeVisible=true;
155
plane->setVisible(!receiver.IsKeyDown(irr::KEY_SPACE));
157
driver->beginScene(true, true, video::SColor(255,113,113,133));
159
First, we draw the scene, possibly without the occluded element. This is necessary
160
because we need the occluder to be drawn first. You can also use several scene
161
managers to collect a number of possible occluders in a separately rendered
164
node->setVisible(nodeVisible);
166
smgr->getGUIEnvironment()->drawAll();
169
Once in a while, here every 100 ms, we check the visibility. We run the queries,
170
update the pixel value, and query the result. Since we already rendered the node
171
we render the query invisible. The update is made blocking, as we need the result
172
immediately. If you don't need the result immediately, e.g. because oyu have other
173
things to render, you can call the update non-blocking. This gives the GPU more
174
time to pass back the results without flushing the render pipeline.
175
If the update was called non-blocking, the result from getOcclusionQueryResult is
176
either the previous value, or 0xffffffff if no value has been generated at all, yet.
177
The result is taken immediately as visibility flag for the node.
179
if (device->getTimer()->getTime()-timeNow>100)
181
driver->runAllOcclusionQueries(false);
182
driver->updateAllOcclusionQueries();
183
nodeVisible=driver->getOcclusionQueryResult(node)>0;
184
timeNow=device->getTimer()->getTime();
189
int fps = driver->getFPS();
193
core::stringw tmp(L"OcclusionQuery Example [");
194
tmp += driver->getName();
198
device->setWindowCaption(tmp.c_str());
204
In the end, delete the Irrlicht device.
212
That's it. Compile and play around with the program.