Merge 'remotes/trunk'
[0ad.git] / source / renderer / SilhouetteRenderer.cpp
blob916693ffd91e0f8e4dd774fcd7a99ca0ebfa21df
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 extern int g_xres, g_yres;
36 // For debugging
37 static const bool g_DisablePreciseIntersections = false;
39 SilhouetteRenderer::SilhouetteRenderer()
41 m_DebugEnabled = false;
44 void SilhouetteRenderer::AddOccluder(CPatch* patch)
46 m_SubmittedPatchOccluders.push_back(patch);
49 void SilhouetteRenderer::AddOccluder(CModel* model)
51 m_SubmittedModelOccluders.push_back(model);
54 void SilhouetteRenderer::AddCaster(CModel* model)
56 m_SubmittedModelCasters.push_back(model);
60 * Silhouettes are the solid-colored versions of units that are rendered when
61 * standing behind a building or terrain, so the player won't lose them.
63 * The rendering is done in CRenderer::RenderSilhouettes, by rendering the
64 * units (silhouette casters) and buildings/terrain (silhouette occluders)
65 * in an extra pass using depth and stencil buffers. It's very inefficient to
66 * render those objects when they're not actually going to contribute to a
67 * silhouette.
69 * This class is responsible for finding the subset of casters/occluders
70 * that might contribute to a silhouette and will need to be rendered.
72 * The algorithm is largely based on sweep-and-prune for detecting intersection
73 * along a single axis:
75 * First we compute the 2D screen-space bounding box of every occluder, and
76 * their minimum distance from the camera. We also compute the screen-space
77 * position of each caster (approximating them as points, which is not perfect
78 * but almost always good enough).
80 * We split each occluder's screen-space bounds into a left ('in') edge and
81 * right ('out') edge. We put those edges plus the caster points into a list,
82 * and sort by x coordinate.
84 * Then we walk through the list, maintaining an active set of occluders.
85 * An 'in' edge will add an occluder to the set, an 'out' edge will remove it.
86 * When we reach a caster point, the active set contains all the occluders that
87 * intersect it in x. We do a quick test of y and depth coordinates against
88 * each occluder in the set. If they pass that test, we do a more precise ray
89 * vs bounding box test (for model occluders) or ray vs patch (for terrain
90 * occluders) to see if we really need to render that caster and occluder.
92 * Performance relies on the active set being quite small. Given the game's
93 * typical occluder sizes and camera angles, this works out okay.
95 * We have to do precise ray/patch intersection tests for terrain, because
96 * if we just used the patch's bounding box, pretty much every unit would
97 * be seen as intersecting the patch it's standing on.
99 * We store screen-space coordinates as 14-bit integers (0..16383) because
100 * that lets us pack and sort the edge/point list efficiently.
103 static const int MAX_COORD = 16384;
105 struct Occluder
107 CRenderableObject* renderable;
108 bool isPatch;
109 u16 x0, y0, x1, y1;
110 float z;
111 bool rendered;
114 struct Caster
116 CModel* model;
117 u16 x, y;
118 float z;
119 bool rendered;
122 enum { EDGE_IN, EDGE_OUT, POINT };
124 // Entry is essentially:
125 // struct Entry {
126 // u16 id; // index into occluders array
127 // u16 type : 2;
128 // u16 x : 14;
129 // };
130 // where x is in the most significant bits, so that sorting as a uint32_t
131 // is the same as sorting by x. To avoid worrying about endianness and the
132 // compiler's ability to handle bitfields efficiently, we use uint32_t instead
133 // of the actual struct.
135 typedef uint32_t Entry;
137 static Entry EntryCreate(int type, u16 id, u16 x) { return (x << 18) | (type << 16) | id; }
138 static int EntryGetId(Entry e) { return e & 0xffff; }
139 static int EntryGetType(Entry e) { return (e >> 16) & 3; }
141 struct ActiveList
143 std::vector<u16> m_Ids;
145 void Add(u16 id)
147 m_Ids.push_back(id);
150 void Remove(u16 id)
152 ssize_t sz = m_Ids.size();
153 for (ssize_t i = sz-1; i >= 0; --i)
155 if (m_Ids[i] == id)
157 m_Ids[i] = m_Ids[sz-1];
158 m_Ids.pop_back();
159 return;
162 debug_warn(L"Failed to find id");
166 static void ComputeScreenBounds(Occluder& occluder, const CBoundingBoxAligned& bounds, CMatrix3D& proj)
168 int x0 = INT_MAX, y0 = INT_MAX, x1 = INT_MIN, y1 = INT_MIN;
169 float z0 = FLT_MAX;
170 for (size_t ix = 0; ix <= 1; ix++)
172 for (size_t iy = 0; iy <= 1; iy++)
174 for (size_t iz = 0; iz <= 1; iz++)
176 CVector4D vec(bounds[ix].X, bounds[iy].Y, bounds[iz].Z, 1.0f);
177 CVector4D svec = proj.Transform(vec);
178 x0 = std::min(x0, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W));
179 y0 = std::min(y0, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W));
180 x1 = std::max(x1, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W));
181 y1 = std::max(y1, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W));
182 z0 = std::min(z0, svec.Z / svec.W);
186 // TODO: there must be a quicker way to do this than to test every vertex,
187 // given the symmetry of the bounding box
189 occluder.x0 = clamp(x0, 0, MAX_COORD-1);
190 occluder.y0 = clamp(y0, 0, MAX_COORD-1);
191 occluder.x1 = clamp(x1, 0, MAX_COORD-1);
192 occluder.y1 = clamp(y1, 0, MAX_COORD-1);
193 occluder.z = z0;
196 static void ComputeScreenPos(Caster& caster, const CVector3D& pos, CMatrix3D& proj)
198 CVector4D vec(pos.X, pos.Y, pos.Z, 1.0f);
199 CVector4D svec = proj.Transform(vec);
200 int x = MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W);
201 int y = MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W);
202 float z = svec.Z / svec.W;
204 caster.x = clamp(x, 0, MAX_COORD-1);
205 caster.y = clamp(y, 0, MAX_COORD-1);
206 caster.z = z;
209 void SilhouetteRenderer::ComputeSubmissions(const CCamera& camera)
211 PROFILE3("compute silhouettes");
213 m_DebugBounds.clear();
214 m_DebugRects.clear();
215 m_DebugSpheres.clear();
217 m_VisiblePatchOccluders.clear();
218 m_VisibleModelOccluders.clear();
219 m_VisibleModelCasters.clear();
221 std::vector<Occluder> occluders;
222 std::vector<Caster> casters;
223 std::vector<Entry> entries;
225 occluders.reserve(m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size());
226 casters.reserve(m_SubmittedModelCasters.size());
227 entries.reserve((m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size()) * 2 + m_SubmittedModelCasters.size());
229 CMatrix3D proj = camera.GetViewProjection();
231 // Bump the positions of unit casters upwards a bit, so they're not always
232 // detected as intersecting the terrain they're standing on
233 CVector3D posOffset(0.0f, 0.1f, 0.0f);
235 #if 0
236 // For debugging ray-patch intersections - casts a ton of rays and draws
237 // a sphere where they intersect
238 for (int y = 0; y < g_yres; y += 8)
240 for (int x = 0; x < g_xres; x += 8)
242 SOverlaySphere sphere;
243 sphere.m_Color = CColor(1, 0, 0, 1);
244 sphere.m_Radius = 0.25f;
245 sphere.m_Center = camera.GetWorldCoordinates(x, y, false);
247 CVector3D origin, dir;
248 camera.BuildCameraRay(x, y, origin, dir);
250 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
252 CPatch* occluder = m_SubmittedPatchOccluders[i];
253 if (CHFTracer::PatchRayIntersect(occluder, origin, dir, &sphere.m_Center))
254 sphere.m_Color = CColor(0, 0, 1, 1);
256 m_DebugSpheres.push_back(sphere);
259 #endif
262 PROFILE("compute bounds");
264 for (size_t i = 0; i < m_SubmittedModelOccluders.size(); ++i)
266 CModel* occluder = m_SubmittedModelOccluders[i];
268 Occluder d;
269 d.renderable = occluder;
270 d.isPatch = false;
271 d.rendered = false;
272 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
274 // Skip zero-sized occluders, so we don't need to worry about EDGE_OUT
275 // getting sorted before EDGE_IN
276 if (d.x0 == d.x1 || d.y0 == d.y1)
277 continue;
279 size_t id = occluders.size();
280 occluders.push_back(d);
282 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
283 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
286 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
288 CPatch* occluder = m_SubmittedPatchOccluders[i];
290 Occluder d;
291 d.renderable = occluder;
292 d.isPatch = true;
293 d.rendered = false;
294 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
296 // Skip zero-sized occluders
297 if (d.x0 == d.x1 || d.y0 == d.y1)
298 continue;
300 size_t id = occluders.size();
301 occluders.push_back(d);
303 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
304 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
307 for (size_t i = 0; i < m_SubmittedModelCasters.size(); ++i)
309 CModel* model = m_SubmittedModelCasters[i];
310 CVector3D pos = model->GetTransform().GetTranslation() + posOffset;
312 Caster d;
313 d.model = model;
314 d.rendered = false;
315 ComputeScreenPos(d, pos, proj);
317 size_t id = casters.size();
318 casters.push_back(d);
320 entries.push_back(EntryCreate(POINT, id, d.x));
324 // Make sure the u16 id didn't overflow
325 ENSURE(occluders.size() < 65536 && casters.size() < 65536);
328 PROFILE("sorting");
329 std::sort(entries.begin(), entries.end());
333 PROFILE("sweeping");
335 ActiveList active;
336 CVector3D cameraPos = camera.GetOrientation().GetTranslation();
338 for (size_t i = 0; i < entries.size(); ++i)
340 Entry e = entries[i];
341 int type = EntryGetType(e);
342 u16 id = EntryGetId(e);
343 if (type == EDGE_IN)
344 active.Add(id);
345 else if (type == EDGE_OUT)
346 active.Remove(id);
347 else
349 Caster& caster = casters[id];
350 for (size_t j = 0; j < active.m_Ids.size(); ++j)
352 Occluder& occluder = occluders[active.m_Ids[j]];
354 if (caster.y < occluder.y0 || caster.y > occluder.y1)
355 continue;
357 if (caster.z < occluder.z)
358 continue;
360 // No point checking further if both are already being rendered
361 if (caster.rendered && occluder.rendered)
362 continue;
364 if (!g_DisablePreciseIntersections)
366 CVector3D pos = caster.model->GetTransform().GetTranslation() + posOffset;
367 if (occluder.isPatch)
369 CPatch* patch = static_cast<CPatch*>(occluder.renderable);
370 if (!CHFTracer::PatchRayIntersect(patch, pos, cameraPos - pos, NULL))
371 continue;
373 else
375 float tmin, tmax;
376 if (!occluder.renderable->GetWorldBounds().RayIntersect(pos, cameraPos - pos, tmin, tmax))
377 continue;
381 caster.rendered = true;
382 occluder.rendered = true;
388 if (m_DebugEnabled)
390 for (size_t i = 0; i < occluders.size(); ++i)
392 DebugRect r;
393 r.color = occluders[i].rendered ? CColor(1.0f, 1.0f, 0.0f, 1.0f) : CColor(0.2f, 0.2f, 0.0f, 1.0f);
394 r.x0 = occluders[i].x0;
395 r.y0 = occluders[i].y0;
396 r.x1 = occluders[i].x1;
397 r.y1 = occluders[i].y1;
398 m_DebugRects.push_back(r);
400 DebugBounds b;
401 b.color = r.color;
402 b.bounds = occluders[i].renderable->GetWorldBounds();
403 m_DebugBounds.push_back(b);
407 for (size_t i = 0; i < occluders.size(); ++i)
409 if (occluders[i].rendered)
411 if (occluders[i].isPatch)
412 m_VisiblePatchOccluders.push_back(static_cast<CPatch*>(occluders[i].renderable));
413 else
414 m_VisibleModelOccluders.push_back(static_cast<CModel*>(occluders[i].renderable));
418 for (size_t i = 0; i < casters.size(); ++i)
419 if (casters[i].rendered)
420 m_VisibleModelCasters.push_back(casters[i].model);
423 void SilhouetteRenderer::RenderSubmitOverlays(SceneCollector& collector)
425 for (size_t i = 0; i < m_DebugSpheres.size(); i++)
426 collector.Submit(&m_DebugSpheres[i]);
429 void SilhouetteRenderer::RenderSubmitOccluders(SceneCollector& collector)
431 for (size_t i = 0; i < m_VisiblePatchOccluders.size(); ++i)
432 collector.Submit(m_VisiblePatchOccluders[i]);
434 for (size_t i = 0; i < m_VisibleModelOccluders.size(); ++i)
435 collector.SubmitNonRecursive(m_VisibleModelOccluders[i]);
438 void SilhouetteRenderer::RenderSubmitCasters(SceneCollector& collector)
440 for (size_t i = 0; i < m_VisibleModelCasters.size(); ++i)
441 collector.SubmitNonRecursive(m_VisibleModelCasters[i]);
444 void SilhouetteRenderer::RenderDebugOverlays(const CCamera& camera)
446 CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
447 shaderTech->BeginPass();
448 CShaderProgramPtr shader = shaderTech->GetShader();
450 glDepthMask(0);
451 glDisable(GL_CULL_FACE);
453 shader->Uniform(str_transform, camera.GetViewProjection());
455 for (size_t i = 0; i < m_DebugBounds.size(); ++i)
457 shader->Uniform(str_color, m_DebugBounds[i].color);
458 m_DebugBounds[i].bounds.RenderOutline(shader);
461 CMatrix3D m;
462 m.SetIdentity();
463 m.Scale(1.0f, -1.f, 1.0f);
464 m.Translate(0.0f, (float)g_yres, -1000.0f);
466 CMatrix3D proj;
467 proj.SetOrtho(0.f, MAX_COORD, 0.f, MAX_COORD, -1.f, 1000.f);
468 m = proj * m;
470 shader->Uniform(str_transform, proj);
472 for (size_t i = 0; i < m_DebugRects.size(); ++i)
474 const DebugRect& r = m_DebugRects[i];
475 shader->Uniform(str_color, r.color);
476 u16 verts[] = {
477 r.x0, r.y0,
478 r.x1, r.y0,
479 r.x1, r.y1,
480 r.x0, r.y1,
481 r.x0, r.y0,
483 shader->VertexPointer(2, GL_SHORT, 0, verts);
484 glDrawArrays(GL_LINE_STRIP, 0, 5);
487 shaderTech->EndPass();
489 glEnable(GL_CULL_FACE);
490 glDepthMask(1);
493 void SilhouetteRenderer::EndFrame()
495 m_SubmittedPatchOccluders.clear();
496 m_SubmittedModelOccluders.clear();
497 m_SubmittedModelCasters.clear();