Merge 'remotes/trunk'
[0ad.git] / source / renderer / SilhouetteRenderer.cpp
blobacc26a1410ec8c329247747cb94af01eaf993af4
1 /* Copyright (C) 2014 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "SilhouetteRenderer.h"
22 #include "graphics/Camera.h"
23 #include "graphics/HFTracer.h"
24 #include "graphics/Model.h"
25 #include "graphics/Patch.h"
26 #include "graphics/ShaderManager.h"
27 #include "maths/MathUtil.h"
28 #include "ps/Profile.h"
29 #include "renderer/Renderer.h"
30 #include "renderer/Scene.h"
32 #include <cfloat>
34 // For debugging
35 static const bool g_DisablePreciseIntersections = false;
37 SilhouetteRenderer::SilhouetteRenderer()
39 m_DebugEnabled = false;
42 void SilhouetteRenderer::AddOccluder(CPatch* patch)
44 m_SubmittedPatchOccluders.push_back(patch);
47 void SilhouetteRenderer::AddOccluder(CModel* model)
49 m_SubmittedModelOccluders.push_back(model);
52 void SilhouetteRenderer::AddCaster(CModel* model)
54 m_SubmittedModelCasters.push_back(model);
58 * Silhouettes are the solid-coloured versions of units that are rendered when
59 * standing behind a building or terrain, so the player won't lose them.
61 * The rendering is done in CRenderer::RenderSilhouettes, by rendering the
62 * units (silhouette casters) and buildings/terrain (silhouette occluders)
63 * in an extra pass using depth and stencil buffers. It's very inefficient to
64 * render those objects when they're not actually going to contribute to a
65 * silhouette.
67 * This class is responsible for finding the subset of casters/occluders
68 * that might contribute to a silhouette and will need to be rendered.
70 * The algorithm is largely based on sweep-and-prune for detecting intersection
71 * along a single axis:
73 * First we compute the 2D screen-space bounding box of every occluder, and
74 * their minimum distance from the camera. We also compute the screen-space
75 * position of each caster (approximating them as points, which is not perfect
76 * but almost always good enough).
78 * We split each occluder's screen-space bounds into a left ('in') edge and
79 * right ('out') edge. We put those edges plus the caster points into a list,
80 * and sort by x coordinate.
82 * Then we walk through the list, maintaining an active set of occluders.
83 * An 'in' edge will add an occluder to the set, an 'out' edge will remove it.
84 * When we reach a caster point, the active set contains all the occluders that
85 * intersect it in x. We do a quick test of y and depth coordinates against
86 * each occluder in the set. If they pass that test, we do a more precise ray
87 * vs bounding box test (for model occluders) or ray vs patch (for terrain
88 * occluders) to see if we really need to render that caster and occluder.
90 * Performance relies on the active set being quite small. Given the game's
91 * typical occluder sizes and camera angles, this works out okay.
93 * We have to do precise ray/patch intersection tests for terrain, because
94 * if we just used the patch's bounding box, pretty much every unit would
95 * be seen as intersecting the patch it's standing on.
97 * We store screen-space coordinates as 14-bit integers (0..16383) because
98 * that lets us pack and sort the edge/point list efficiently.
101 static const int MAX_COORD = 16384;
103 struct Occluder
105 CRenderableObject* renderable;
106 bool isPatch;
107 u16 x0, y0, x1, y1;
108 float z;
109 bool rendered;
112 struct Caster
114 CModel* model;
115 u16 x, y;
116 float z;
117 bool rendered;
120 enum { EDGE_IN, EDGE_OUT, POINT };
122 // Entry is essentially:
123 // struct Entry {
124 // u16 id; // index into occluders array
125 // u16 type : 2;
126 // u16 x : 14;
127 // };
128 // where x is in the most significant bits, so that sorting as a uint32_t
129 // is the same as sorting by x. To avoid worrying about endianness and the
130 // compiler's ability to handle bitfields efficiently, we use uint32_t instead
131 // of the actual struct.
133 typedef uint32_t Entry;
135 static Entry EntryCreate(int type, u16 id, u16 x) { return (x << 18) | (type << 16) | id; }
136 static int EntryGetId(Entry e) { return e & 0xffff; }
137 static int EntryGetType(Entry e) { return (e >> 16) & 3; }
139 struct ActiveList
141 std::vector<u16> m_Ids;
143 void Add(u16 id)
145 m_Ids.push_back(id);
148 void Remove(u16 id)
150 ssize_t sz = m_Ids.size();
151 for (ssize_t i = sz-1; i >= 0; --i)
153 if (m_Ids[i] == id)
155 m_Ids[i] = m_Ids[sz-1];
156 m_Ids.pop_back();
157 return;
160 debug_warn(L"Failed to find id");
164 static void ComputeScreenBounds(Occluder& occluder, const CBoundingBoxAligned& bounds, CMatrix3D& proj)
166 int x0 = INT_MAX, y0 = INT_MAX, x1 = INT_MIN, y1 = INT_MIN;
167 float z0 = FLT_MAX;
168 for (size_t ix = 0; ix <= 1; ix++)
170 for (size_t iy = 0; iy <= 1; iy++)
172 for (size_t iz = 0; iz <= 1; iz++)
174 CVector4D vec(bounds[ix].X, bounds[iy].Y, bounds[iz].Z, 1.0f);
175 CVector4D svec = proj.Transform(vec);
176 x0 = std::min(x0, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W));
177 y0 = std::min(y0, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W));
178 x1 = std::max(x1, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W));
179 y1 = std::max(y1, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W));
180 z0 = std::min(z0, svec.Z / svec.W);
184 // TODO: there must be a quicker way to do this than to test every vertex,
185 // given the symmetry of the bounding box
187 occluder.x0 = clamp(x0, 0, MAX_COORD-1);
188 occluder.y0 = clamp(y0, 0, MAX_COORD-1);
189 occluder.x1 = clamp(x1, 0, MAX_COORD-1);
190 occluder.y1 = clamp(y1, 0, MAX_COORD-1);
191 occluder.z = z0;
194 static void ComputeScreenPos(Caster& caster, const CVector3D& pos, CMatrix3D& proj)
196 CVector4D vec(pos.X, pos.Y, pos.Z, 1.0f);
197 CVector4D svec = proj.Transform(vec);
198 int x = MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W);
199 int y = MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W);
200 float z = svec.Z / svec.W;
202 caster.x = clamp(x, 0, MAX_COORD-1);
203 caster.y = clamp(y, 0, MAX_COORD-1);
204 caster.z = z;
207 void SilhouetteRenderer::ComputeSubmissions(const CCamera& camera)
209 PROFILE3("compute silhouettes");
211 m_DebugBounds.clear();
212 m_DebugRects.clear();
213 m_DebugSpheres.clear();
215 m_VisiblePatchOccluders.clear();
216 m_VisibleModelOccluders.clear();
217 m_VisibleModelCasters.clear();
219 std::vector<Occluder> occluders;
220 std::vector<Caster> casters;
221 std::vector<Entry> entries;
223 occluders.reserve(m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size());
224 casters.reserve(m_SubmittedModelCasters.size());
225 entries.reserve((m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size()) * 2 + m_SubmittedModelCasters.size());
227 CMatrix3D proj = camera.GetViewProjection();
229 // Bump the positions of unit casters upwards a bit, so they're not always
230 // detected as intersecting the terrain they're standing on
231 CVector3D posOffset(0.0f, 0.1f, 0.0f);
233 #if 0
234 // For debugging ray-patch intersections - casts a ton of rays and draws
235 // a sphere where they intersect
236 extern int g_xres, g_yres;
237 for (int y = 0; y < g_yres; y += 8)
239 for (int x = 0; x < g_xres; x += 8)
241 SOverlaySphere sphere;
242 sphere.m_Color = CColor(1, 0, 0, 1);
243 sphere.m_Radius = 0.25f;
244 sphere.m_Center = camera.GetWorldCoordinates(x, y, false);
246 CVector3D origin, dir;
247 camera.BuildCameraRay(x, y, origin, dir);
249 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
251 CPatch* occluder = m_SubmittedPatchOccluders[i];
252 if (CHFTracer::PatchRayIntersect(occluder, origin, dir, &sphere.m_Center))
253 sphere.m_Color = CColor(0, 0, 1, 1);
255 m_DebugSpheres.push_back(sphere);
258 #endif
261 PROFILE("compute bounds");
263 for (size_t i = 0; i < m_SubmittedModelOccluders.size(); ++i)
265 CModel* occluder = m_SubmittedModelOccluders[i];
267 Occluder d;
268 d.renderable = occluder;
269 d.isPatch = false;
270 d.rendered = false;
271 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
273 // Skip zero-sized occluders, so we don't need to worry about EDGE_OUT
274 // getting sorted before EDGE_IN
275 if (d.x0 == d.x1 || d.y0 == d.y1)
276 continue;
278 size_t id = occluders.size();
279 occluders.push_back(d);
281 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
282 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
285 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
287 CPatch* occluder = m_SubmittedPatchOccluders[i];
289 Occluder d;
290 d.renderable = occluder;
291 d.isPatch = true;
292 d.rendered = false;
293 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
295 // Skip zero-sized occluders
296 if (d.x0 == d.x1 || d.y0 == d.y1)
297 continue;
299 size_t id = occluders.size();
300 occluders.push_back(d);
302 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
303 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
306 for (size_t i = 0; i < m_SubmittedModelCasters.size(); ++i)
308 CModel* model = m_SubmittedModelCasters[i];
309 CVector3D pos = model->GetTransform().GetTranslation() + posOffset;
311 Caster d;
312 d.model = model;
313 d.rendered = false;
314 ComputeScreenPos(d, pos, proj);
316 size_t id = casters.size();
317 casters.push_back(d);
319 entries.push_back(EntryCreate(POINT, id, d.x));
323 // Make sure the u16 id didn't overflow
324 ENSURE(occluders.size() < 65536 && casters.size() < 65536);
327 PROFILE("sorting");
328 std::sort(entries.begin(), entries.end());
332 PROFILE("sweeping");
334 ActiveList active;
335 CVector3D cameraPos = camera.GetOrientation().GetTranslation();
337 for (size_t i = 0; i < entries.size(); ++i)
339 Entry e = entries[i];
340 int type = EntryGetType(e);
341 u16 id = EntryGetId(e);
342 if (type == EDGE_IN)
343 active.Add(id);
344 else if (type == EDGE_OUT)
345 active.Remove(id);
346 else
348 Caster& caster = casters[id];
349 for (size_t j = 0; j < active.m_Ids.size(); ++j)
351 Occluder& occluder = occluders[active.m_Ids[j]];
353 if (caster.y < occluder.y0 || caster.y > occluder.y1)
354 continue;
356 if (caster.z < occluder.z)
357 continue;
359 // No point checking further if both are already being rendered
360 if (caster.rendered && occluder.rendered)
361 continue;
363 if (!g_DisablePreciseIntersections)
365 CVector3D pos = caster.model->GetTransform().GetTranslation() + posOffset;
366 if (occluder.isPatch)
368 CPatch* patch = static_cast<CPatch*>(occluder.renderable);
369 if (!CHFTracer::PatchRayIntersect(patch, pos, cameraPos - pos, NULL))
370 continue;
372 else
374 float tmin, tmax;
375 if (!occluder.renderable->GetWorldBounds().RayIntersect(pos, cameraPos - pos, tmin, tmax))
376 continue;
380 caster.rendered = true;
381 occluder.rendered = true;
387 if (m_DebugEnabled)
389 for (size_t i = 0; i < occluders.size(); ++i)
391 DebugRect r;
392 r.color = occluders[i].rendered ? CColor(1.0f, 1.0f, 0.0f, 1.0f) : CColor(0.2f, 0.2f, 0.0f, 1.0f);
393 r.x0 = occluders[i].x0;
394 r.y0 = occluders[i].y0;
395 r.x1 = occluders[i].x1;
396 r.y1 = occluders[i].y1;
397 m_DebugRects.push_back(r);
399 DebugBounds b;
400 b.color = r.color;
401 b.bounds = occluders[i].renderable->GetWorldBounds();
402 m_DebugBounds.push_back(b);
406 for (size_t i = 0; i < occluders.size(); ++i)
408 if (occluders[i].rendered)
410 if (occluders[i].isPatch)
411 m_VisiblePatchOccluders.push_back(static_cast<CPatch*>(occluders[i].renderable));
412 else
413 m_VisibleModelOccluders.push_back(static_cast<CModel*>(occluders[i].renderable));
417 for (size_t i = 0; i < casters.size(); ++i)
418 if (casters[i].rendered)
419 m_VisibleModelCasters.push_back(casters[i].model);
422 void SilhouetteRenderer::RenderSubmitOverlays(SceneCollector& collector)
424 for (size_t i = 0; i < m_DebugSpheres.size(); i++)
425 collector.Submit(&m_DebugSpheres[i]);
428 void SilhouetteRenderer::RenderSubmitOccluders(SceneCollector& collector)
430 for (size_t i = 0; i < m_VisiblePatchOccluders.size(); ++i)
431 collector.Submit(m_VisiblePatchOccluders[i]);
433 for (size_t i = 0; i < m_VisibleModelOccluders.size(); ++i)
434 collector.SubmitNonRecursive(m_VisibleModelOccluders[i]);
437 void SilhouetteRenderer::RenderSubmitCasters(SceneCollector& collector)
439 for (size_t i = 0; i < m_VisibleModelCasters.size(); ++i)
440 collector.SubmitNonRecursive(m_VisibleModelCasters[i]);
443 void SilhouetteRenderer::RenderDebugOverlays(const CCamera& camera)
445 CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
446 shaderTech->BeginPass();
447 CShaderProgramPtr shader = shaderTech->GetShader();
449 glDepthMask(0);
450 glDisable(GL_CULL_FACE);
452 shader->Uniform(str_transform, camera.GetViewProjection());
454 for (size_t i = 0; i < m_DebugBounds.size(); ++i)
456 shader->Uniform(str_color, m_DebugBounds[i].color);
457 m_DebugBounds[i].bounds.RenderOutline(shader);
460 CMatrix3D m;
461 m.SetIdentity();
462 m.Scale(1.0f, -1.f, 1.0f);
463 m.Translate(0.0f, (float)g_yres, -1000.0f);
465 CMatrix3D proj;
466 proj.SetOrtho(0.f, MAX_COORD, 0.f, MAX_COORD, -1.f, 1000.f);
467 m = proj * m;
469 shader->Uniform(str_transform, proj);
471 for (size_t i = 0; i < m_DebugRects.size(); ++i)
473 const DebugRect& r = m_DebugRects[i];
474 shader->Uniform(str_color, r.color);
475 u16 verts[] = {
476 r.x0, r.y0,
477 r.x1, r.y0,
478 r.x1, r.y1,
479 r.x0, r.y1,
480 r.x0, r.y0,
482 shader->VertexPointer(2, GL_SHORT, 0, verts);
483 glDrawArrays(GL_LINE_STRIP, 0, 5);
486 shaderTech->EndPass();
488 glEnable(GL_CULL_FACE);
489 glDepthMask(1);
492 void SilhouetteRenderer::EndFrame()
494 m_SubmittedPatchOccluders.clear();
495 m_SubmittedModelOccluders.clear();
496 m_SubmittedModelCasters.clear();