]> git.tdb.fi Git - libs/gl.git/blob - source/occludedscene.cpp
Implement a scene class that performs occlusion culling on renderables
[libs/gl.git] / source / occludedscene.cpp
1 #include <algorithm>
2 #include <msp/gl/extensions/arb_occlusion_query.h>
3 #include "camera.h"
4 #include "occludedscene.h"
5 #include "renderer.h"
6 #include "sphere.h"
7
8 using namespace std;
9
10 namespace {
11
12 const char vshader[] =
13         "void main()\n"
14         "{\n"
15         "       gl_Position = gl_ProjectionMatrix*gl_ModelViewMatrix*gl_Vertex;\n"
16         "}";
17
18 const char fshader[] =
19         "void main()\n"
20         "{\n"
21         "       gl_FragColor = vec4(1.0);\n"
22         "}";
23
24 }
25
26 namespace Msp {
27 namespace GL {
28
29 OccludedScene::OccludedScene():
30         bounding_mesh((VERTEX3, NORMAL3)),
31         bounding_shader(vshader, fshader),
32         occluder_min_size(0.25f),
33         cache_dirty(false)
34 {
35         static Require req(ARB_occlusion_query);
36
37         /* Use a slightly larger radius to ensure that all parts of the renderable
38         fit inside the icosahedron */
39         IcoSphereBuilder(1.26f, 1).build(bounding_mesh);
40         bounding_mesh.set_winding(&WindingTest::counterclockwise());
41 }
42
43 OccludedScene::~OccludedScene()
44 {
45         vector<unsigned> queries;
46         queries.reserve(occluded_cache.size());
47         for(OccludedArray::iterator i=occluded_cache.begin(); i!=occluded_cache.end(); ++i)
48                 queries.push_back(i->query);
49         glDeleteQueries(queries.size(), &queries[0]);
50 }
51
52 void OccludedScene::add(const Renderable &r)
53 {
54         renderables.insert(&r);
55         cache_dirty = true;
56 }
57
58 void OccludedScene::remove(const Renderable &r)
59 {
60         renderables.erase(&r);
61         cache_dirty = true;
62 }
63
64 void OccludedScene::render(Renderer &renderer, const Tag &tag) const
65 {
66         if(renderables.empty())
67                 return;
68
69         const Camera *camera = renderer.get_camera();
70         if(!camera)
71         {
72                 for(RenderableSet::const_iterator i=renderables.begin(); i!=renderables.end(); ++i)
73                         renderer.render(**i, tag);
74                 return;
75         }
76
77         if(cache_dirty)
78         {
79                 if(occluded_cache.size()<renderables.size())
80                 {
81                         unsigned old_size = occluded_cache.size();
82                         occluded_cache.resize(renderables.size());
83                         vector<unsigned> new_queries(occluded_cache.size()-old_size);
84                         glGenQueries(new_queries.size(), &new_queries[0]);
85                         for(unsigned i=0; i<new_queries.size(); ++i)
86                                 occluded_cache[old_size+i].query = new_queries[i];
87                 }
88
89                 OccludedArray::iterator j = occluded_cache.begin();
90                 for(RenderableSet::iterator i=renderables.begin(); i!=renderables.end(); ++i, ++j)
91                         j->renderable = *i;
92                 for(; j!=occluded_cache.end(); ++j)
93                 {
94                         j->renderable = 0;
95                         j->in_frustum = false;
96                 }
97
98                 cache_dirty = false;
99         }
100
101         const Vector3 &camera_pos = camera->get_position();
102         const Vector3 &look_dir = camera->get_look_direction();
103         float near_clip = camera->get_near_clip();
104         float frustum_h = tan(camera->get_field_of_view()/2.0f)*2.0f;
105
106         // Perform frustum culling and render any major occluders
107         bool use_frustum = setup_frustum(renderer);
108         for(OccludedArray::iterator i=occluded_cache.begin(); (i!=occluded_cache.end() && i->renderable); ++i)
109         {
110                 i->in_frustum = (!use_frustum || !frustum_cull(*i->renderable));
111                 if(!i->in_frustum)
112                         continue;
113
114                 const Matrix *matrix = i->renderable->get_matrix();
115                 i->bounding_sphere = i->renderable->get_bounding_sphere();
116                 if(matrix && i->bounding_sphere)
117                 {
118                         float depth = dot(*matrix*i->bounding_sphere->get_center()-camera_pos, look_dir);
119                         float size = i->bounding_sphere->get_radius()*2/max(depth, near_clip);
120                         i->occluder = (size>frustum_h*occluder_min_size);
121                 }
122                 else
123                         // If the size can't be calculated, treat it as occluder
124                         i->occluder = true;
125
126                 if(i->occluder)
127                         renderer.render(*i->renderable, tag);
128         }
129
130         // Move all objects within the frustum to the beginning of the array
131         for(OccludedArray::iterator i=occluded_cache.begin(), j=i+renderables.size()-1; i!=j; )
132         {
133                 if(i->in_frustum)
134                         ++i;
135                 else if(j->in_frustum)
136                         swap(*i, *j);
137                 else
138                         --j;
139         }
140
141         {
142                 Renderer::Push push(renderer);
143                 renderer.set_shader_program(&bounding_shader);
144
145                 glColorMask(false, false, false, false);
146                 glDepthMask(false);
147
148                 // Fire off the occlusion queries
149                 for(OccludedArray::const_iterator i=occluded_cache.begin(); (i!=occluded_cache.end() && i->in_frustum); ++i)
150                         if(!i->occluder)
151                         {
152                                 glBeginQuery(GL_SAMPLES_PASSED, i->query);
153                                 Renderer::Push push2(renderer);
154                                 renderer.transform(Matrix(*i->renderable->get_matrix())
155                                         .translate(i->bounding_sphere->get_center())
156                                         .scale(i->bounding_sphere->get_radius()));
157                                 bounding_mesh.draw(renderer);
158                                 glEndQuery(GL_SAMPLES_PASSED);
159                         }
160
161                 glColorMask(true, true, true, true);
162                 glDepthMask(true);
163         }
164
165         // Render anything that has a chance of being visible
166         for(OccludedArray::const_iterator i=occluded_cache.begin(); (i!=occluded_cache.end() && i->in_frustum); ++i)
167                 if(!i->occluder)
168                 {
169                         int samples_passed;
170                         glGetQueryObjectiv(i->query, GL_QUERY_RESULT, &samples_passed);
171                         if(samples_passed>0)
172                                 renderer.render(*i->renderable, tag);
173                 }
174 }
175
176
177 OccludedScene::OccludedRenderable::OccludedRenderable():
178         renderable(0),
179         bounding_sphere(0),
180         in_frustum(false),
181         occluder(false),
182         query(0)
183 { }
184
185 } // namespace GL
186 } // namespace Msp