source: ps/trunk/source/renderer/SilhouetteRenderer.cpp@ 25266

Last change on this file since 25266 was 25266, checked in by wraitii, 3 years ago

Use type_identity to simplify Clamp usage.

Differential Revision: https://code.wildfiregames.com/D3859

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1/* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
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.
8 *
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.
13 *
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/>.
16 */
17
18#include "precompiled.h"
19
20#include "SilhouetteRenderer.h"
21
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"
31
32#include <cfloat>
33
34extern int g_xres, g_yres;
35
36// For debugging
37static const bool g_DisablePreciseIntersections = false;
38
39SilhouetteRenderer::SilhouetteRenderer()
40{
41 m_DebugEnabled = false;
42}
43
44void SilhouetteRenderer::AddOccluder(CPatch* patch)
45{
46 m_SubmittedPatchOccluders.push_back(patch);
47}
48
49void SilhouetteRenderer::AddOccluder(CModel* model)
50{
51 m_SubmittedModelOccluders.push_back(model);
52}
53
54void SilhouetteRenderer::AddCaster(CModel* model)
55{
56 m_SubmittedModelCasters.push_back(model);
57}
58
59/*
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.
62 *
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.
68 *
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.
71 *
72 * The algorithm is largely based on sweep-and-prune for detecting intersection
73 * along a single axis:
74 *
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).
79 *
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.
83 *
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.
91 *
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.
94 *
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.
98 *
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.
101 */
102
103static const u16 g_MaxCoord = 1 << 14;
104static const u16 g_HalfMaxCoord = g_MaxCoord / 2;
105
106struct Occluder
107{
108 CRenderableObject* renderable;
109 bool isPatch;
110 u16 x0, y0, x1, y1;
111 float z;
112 bool rendered;
113};
114
115struct Caster
116{
117 CModel* model;
118 u16 x, y;
119 float z;
120 bool rendered;
121};
122
123enum { EDGE_IN, EDGE_OUT, POINT };
124
125// Entry is essentially:
126// struct Entry {
127// u16 id; // index into occluders array
128// u16 type : 2;
129// u16 x : 14;
130// };
131// where x is in the most significant bits, so that sorting as a uint32_t
132// is the same as sorting by x. To avoid worrying about endianness and the
133// compiler's ability to handle bitfields efficiently, we use uint32_t instead
134// of the actual struct.
135
136typedef uint32_t Entry;
137
138static Entry EntryCreate(int type, u16 id, u16 x) { return (x << 18) | (type << 16) | id; }
139static int EntryGetId(Entry e) { return e & 0xffff; }
140static int EntryGetType(Entry e) { return (e >> 16) & 3; }
141
142struct ActiveList
143{
144 std::vector<u16> m_Ids;
145
146 void Add(u16 id)
147 {
148 m_Ids.push_back(id);
149 }
150
151 void Remove(u16 id)
152 {
153 ssize_t sz = m_Ids.size();
154 for (ssize_t i = sz-1; i >= 0; --i)
155 {
156 if (m_Ids[i] == id)
157 {
158 m_Ids[i] = m_Ids[sz-1];
159 m_Ids.pop_back();
160 return;
161 }
162 }
163 debug_warn(L"Failed to find id");
164 }
165};
166
167static void ComputeScreenBounds(Occluder& occluder, const CBoundingBoxAligned& bounds, CMatrix3D& proj)
168{
169 u16 x0 = std::numeric_limits<u16>::max();
170 u16 y0 = std::numeric_limits<u16>::max();
171 u16 x1 = std::numeric_limits<u16>::min();
172 u16 y1 = std::numeric_limits<u16>::min();
173 float z0 = std::numeric_limits<float>::max();
174 for (size_t ix = 0; ix <= 1; ++ix)
175 {
176 for (size_t iy = 0; iy <= 1; ++iy)
177 {
178 for (size_t iz = 0; iz <= 1; ++iz)
179 {
180 CVector4D svec = proj.Transform(CVector4D(bounds[ix].X, bounds[iy].Y, bounds[iz].Z, 1.0f));
181 x0 = std::min(x0, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.X / svec.W)));
182 y0 = std::min(y0, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.Y / svec.W)));
183 x1 = std::max(x1, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.X / svec.W)));
184 y1 = std::max(y1, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.Y / svec.W)));
185 z0 = std::min(z0, svec.Z / svec.W);
186 }
187 }
188 }
189 // TODO: there must be a quicker way to do this than to test every vertex,
190 // given the symmetry of the bounding box
191
192 occluder.x0 = Clamp(x0, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
193 occluder.y0 = Clamp(y0, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
194 occluder.x1 = Clamp(x1, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
195 occluder.y1 = Clamp(y1, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
196 occluder.z = z0;
197}
198
199static void ComputeScreenPos(Caster& caster, const CVector3D& pos, CMatrix3D& proj)
200{
201 CVector4D svec = proj.Transform(CVector4D(pos.X, pos.Y, pos.Z, 1.0f));
202 u16 x = g_HalfMaxCoord + static_cast<int>(g_HalfMaxCoord * svec.X / svec.W);
203 u16 y = g_HalfMaxCoord + static_cast<int>(g_HalfMaxCoord * svec.Y / svec.W);
204 caster.x = Clamp(x, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
205 caster.y = Clamp(y, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
206 caster.z = svec.Z / svec.W;
207}
208
209void SilhouetteRenderer::ComputeSubmissions(const CCamera& camera)
210{
211 PROFILE3("compute silhouettes");
212
213 m_DebugBounds.clear();
214 m_DebugRects.clear();
215 m_DebugSpheres.clear();
216
217 m_VisiblePatchOccluders.clear();
218 m_VisibleModelOccluders.clear();
219 m_VisibleModelCasters.clear();
220
221 std::vector<Occluder> occluders;
222 std::vector<Caster> casters;
223 std::vector<Entry> entries;
224
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());
228
229 CMatrix3D proj = camera.GetViewProjection();
230
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);
234
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)
239 {
240 for (int x = 0; x < g_xres; x += 8)
241 {
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);
246
247 CVector3D origin, dir;
248 camera.BuildCameraRay(x, y, origin, dir);
249
250 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
251 {
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);
255 }
256 m_DebugSpheres.push_back(sphere);
257 }
258 }
259#endif
260
261 {
262 PROFILE("compute bounds");
263
264 for (size_t i = 0; i < m_SubmittedModelOccluders.size(); ++i)
265 {
266 CModel* occluder = m_SubmittedModelOccluders[i];
267
268 Occluder d;
269 d.renderable = occluder;
270 d.isPatch = false;
271 d.rendered = false;
272 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
273
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;
278
279 u16 id = static_cast<u16>(occluders.size());
280 occluders.push_back(d);
281
282 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
283 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
284 }
285
286 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
287 {
288 CPatch* occluder = m_SubmittedPatchOccluders[i];
289
290 Occluder d;
291 d.renderable = occluder;
292 d.isPatch = true;
293 d.rendered = false;
294 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
295
296 // Skip zero-sized occluders
297 if (d.x0 == d.x1 || d.y0 == d.y1)
298 continue;
299
300 u16 id = static_cast<u16>(occluders.size());
301 occluders.push_back(d);
302
303 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
304 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
305 }
306
307 for (size_t i = 0; i < m_SubmittedModelCasters.size(); ++i)
308 {
309 CModel* model = m_SubmittedModelCasters[i];
310 CVector3D pos = model->GetTransform().GetTranslation() + posOffset;
311
312 Caster d;
313 d.model = model;
314 d.rendered = false;
315 ComputeScreenPos(d, pos, proj);
316
317 u16 id = static_cast<u16>(casters.size());
318 casters.push_back(d);
319
320 entries.push_back(EntryCreate(POINT, id, d.x));
321 }
322 }
323
324 // Make sure the u16 id didn't overflow
325 ENSURE(occluders.size() < 65536 && casters.size() < 65536);
326
327 {
328 PROFILE("sorting");
329 std::sort(entries.begin(), entries.end());
330 }
331
332 {
333 PROFILE("sweeping");
334
335 ActiveList active;
336 CVector3D cameraPos = camera.GetOrientation().GetTranslation();
337
338 for (size_t i = 0; i < entries.size(); ++i)
339 {
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
348 {
349 Caster& caster = casters[id];
350 for (size_t j = 0; j < active.m_Ids.size(); ++j)
351 {
352 Occluder& occluder = occluders[active.m_Ids[j]];
353
354 if (caster.y < occluder.y0 || caster.y > occluder.y1)
355 continue;
356
357 if (caster.z < occluder.z)
358 continue;
359
360 // No point checking further if both are already being rendered
361 if (caster.rendered && occluder.rendered)
362 continue;
363
364 if (!g_DisablePreciseIntersections)
365 {
366 CVector3D pos = caster.model->GetTransform().GetTranslation() + posOffset;
367 if (occluder.isPatch)
368 {
369 CPatch* patch = static_cast<CPatch*>(occluder.renderable);
370 if (!CHFTracer::PatchRayIntersect(patch, pos, cameraPos - pos, NULL))
371 continue;
372 }
373 else
374 {
375 float tmin, tmax;
376 if (!occluder.renderable->GetWorldBounds().RayIntersect(pos, cameraPos - pos, tmin, tmax))
377 continue;
378 }
379 }
380
381 caster.rendered = true;
382 occluder.rendered = true;
383 }
384 }
385 }
386 }
387
388 if (m_DebugEnabled)
389 {
390 for (size_t i = 0; i < occluders.size(); ++i)
391 {
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);
399
400 DebugBounds b;
401 b.color = r.color;
402 b.bounds = occluders[i].renderable->GetWorldBounds();
403 m_DebugBounds.push_back(b);
404 }
405 }
406
407 for (size_t i = 0; i < occluders.size(); ++i)
408 {
409 if (occluders[i].rendered)
410 {
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));
415 }
416 }
417
418 for (size_t i = 0; i < casters.size(); ++i)
419 if (casters[i].rendered)
420 m_VisibleModelCasters.push_back(casters[i].model);
421}
422
423void SilhouetteRenderer::RenderSubmitOverlays(SceneCollector& collector)
424{
425 for (size_t i = 0; i < m_DebugSpheres.size(); i++)
426 collector.Submit(&m_DebugSpheres[i]);
427}
428
429void SilhouetteRenderer::RenderSubmitOccluders(SceneCollector& collector)
430{
431 for (size_t i = 0; i < m_VisiblePatchOccluders.size(); ++i)
432 collector.Submit(m_VisiblePatchOccluders[i]);
433
434 for (size_t i = 0; i < m_VisibleModelOccluders.size(); ++i)
435 collector.SubmitNonRecursive(m_VisibleModelOccluders[i]);
436}
437
438void SilhouetteRenderer::RenderSubmitCasters(SceneCollector& collector)
439{
440 for (size_t i = 0; i < m_VisibleModelCasters.size(); ++i)
441 collector.SubmitNonRecursive(m_VisibleModelCasters[i]);
442}
443
444void SilhouetteRenderer::RenderDebugOverlays(const CCamera& camera)
445{
446 CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
447 shaderTech->BeginPass();
448 CShaderProgramPtr shader = shaderTech->GetShader();
449
450 glDepthMask(0);
451 glDisable(GL_CULL_FACE);
452
453 shader->Uniform(str_transform, camera.GetViewProjection());
454
455 for (size_t i = 0; i < m_DebugBounds.size(); ++i)
456 {
457 shader->Uniform(str_color, m_DebugBounds[i].color);
458 m_DebugBounds[i].bounds.RenderOutline(shader);
459 }
460
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);
465
466 CMatrix3D proj;
467 proj.SetOrtho(0.f, g_MaxCoord, 0.f, g_MaxCoord, -1.f, 1000.f);
468 m = proj * m;
469
470 shader->Uniform(str_transform, proj);
471
472 for (size_t i = 0; i < m_DebugRects.size(); ++i)
473 {
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,
482 };
483 shader->VertexPointer(2, GL_SHORT, 0, verts);
484 glDrawArrays(GL_LINE_STRIP, 0, 5);
485 }
486
487 shaderTech->EndPass();
488
489 glEnable(GL_CULL_FACE);
490 glDepthMask(1);
491}
492
493void SilhouetteRenderer::EndFrame()
494{
495 m_SubmittedPatchOccluders.clear();
496 m_SubmittedModelOccluders.clear();
497 m_SubmittedModelCasters.clear();
498}
Note: See TracBrowser for help on using the repository browser.