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

Last change on this file since 25269 was 25269, checked in by Vladislav Belov, 3 years ago

Removes low-level GL calls from graphics and geometrics primitives and adds DebugRenderer.

Tested By: Freagarach

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

  • Property svn:eol-style set to native
File size: 14.3 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/DebugRenderer.h"
30#include "renderer/Renderer.h"
31#include "renderer/Scene.h"
32
33#include <cfloat>
34
35extern int g_xres, g_yres;
36
37// For debugging
38static const bool g_DisablePreciseIntersections = false;
39
40SilhouetteRenderer::SilhouetteRenderer()
41{
42 m_DebugEnabled = false;
43}
44
45void SilhouetteRenderer::AddOccluder(CPatch* patch)
46{
47 m_SubmittedPatchOccluders.push_back(patch);
48}
49
50void SilhouetteRenderer::AddOccluder(CModel* model)
51{
52 m_SubmittedModelOccluders.push_back(model);
53}
54
55void SilhouetteRenderer::AddCaster(CModel* model)
56{
57 m_SubmittedModelCasters.push_back(model);
58}
59
60/*
61 * Silhouettes are the solid-colored versions of units that are rendered when
62 * standing behind a building or terrain, so the player won't lose them.
63 *
64 * The rendering is done in CRenderer::RenderSilhouettes, by rendering the
65 * units (silhouette casters) and buildings/terrain (silhouette occluders)
66 * in an extra pass using depth and stencil buffers. It's very inefficient to
67 * render those objects when they're not actually going to contribute to a
68 * silhouette.
69 *
70 * This class is responsible for finding the subset of casters/occluders
71 * that might contribute to a silhouette and will need to be rendered.
72 *
73 * The algorithm is largely based on sweep-and-prune for detecting intersection
74 * along a single axis:
75 *
76 * First we compute the 2D screen-space bounding box of every occluder, and
77 * their minimum distance from the camera. We also compute the screen-space
78 * position of each caster (approximating them as points, which is not perfect
79 * but almost always good enough).
80 *
81 * We split each occluder's screen-space bounds into a left ('in') edge and
82 * right ('out') edge. We put those edges plus the caster points into a list,
83 * and sort by x coordinate.
84 *
85 * Then we walk through the list, maintaining an active set of occluders.
86 * An 'in' edge will add an occluder to the set, an 'out' edge will remove it.
87 * When we reach a caster point, the active set contains all the occluders that
88 * intersect it in x. We do a quick test of y and depth coordinates against
89 * each occluder in the set. If they pass that test, we do a more precise ray
90 * vs bounding box test (for model occluders) or ray vs patch (for terrain
91 * occluders) to see if we really need to render that caster and occluder.
92 *
93 * Performance relies on the active set being quite small. Given the game's
94 * typical occluder sizes and camera angles, this works out okay.
95 *
96 * We have to do precise ray/patch intersection tests for terrain, because
97 * if we just used the patch's bounding box, pretty much every unit would
98 * be seen as intersecting the patch it's standing on.
99 *
100 * We store screen-space coordinates as 14-bit integers (0..16383) because
101 * that lets us pack and sort the edge/point list efficiently.
102 */
103
104static const u16 g_MaxCoord = 1 << 14;
105static const u16 g_HalfMaxCoord = g_MaxCoord / 2;
106
107struct Occluder
108{
109 CRenderableObject* renderable;
110 bool isPatch;
111 u16 x0, y0, x1, y1;
112 float z;
113 bool rendered;
114};
115
116struct Caster
117{
118 CModel* model;
119 u16 x, y;
120 float z;
121 bool rendered;
122};
123
124enum { EDGE_IN, EDGE_OUT, POINT };
125
126// Entry is essentially:
127// struct Entry {
128// u16 id; // index into occluders array
129// u16 type : 2;
130// u16 x : 14;
131// };
132// where x is in the most significant bits, so that sorting as a uint32_t
133// is the same as sorting by x. To avoid worrying about endianness and the
134// compiler's ability to handle bitfields efficiently, we use uint32_t instead
135// of the actual struct.
136
137typedef uint32_t Entry;
138
139static Entry EntryCreate(int type, u16 id, u16 x) { return (x << 18) | (type << 16) | id; }
140static int EntryGetId(Entry e) { return e & 0xffff; }
141static int EntryGetType(Entry e) { return (e >> 16) & 3; }
142
143struct ActiveList
144{
145 std::vector<u16> m_Ids;
146
147 void Add(u16 id)
148 {
149 m_Ids.push_back(id);
150 }
151
152 void Remove(u16 id)
153 {
154 ssize_t sz = m_Ids.size();
155 for (ssize_t i = sz-1; i >= 0; --i)
156 {
157 if (m_Ids[i] == id)
158 {
159 m_Ids[i] = m_Ids[sz-1];
160 m_Ids.pop_back();
161 return;
162 }
163 }
164 debug_warn(L"Failed to find id");
165 }
166};
167
168static void ComputeScreenBounds(Occluder& occluder, const CBoundingBoxAligned& bounds, CMatrix3D& proj)
169{
170 u16 x0 = std::numeric_limits<u16>::max();
171 u16 y0 = std::numeric_limits<u16>::max();
172 u16 x1 = std::numeric_limits<u16>::min();
173 u16 y1 = std::numeric_limits<u16>::min();
174 float z0 = std::numeric_limits<float>::max();
175 for (size_t ix = 0; ix <= 1; ++ix)
176 {
177 for (size_t iy = 0; iy <= 1; ++iy)
178 {
179 for (size_t iz = 0; iz <= 1; ++iz)
180 {
181 CVector4D svec = proj.Transform(CVector4D(bounds[ix].X, bounds[iy].Y, bounds[iz].Z, 1.0f));
182 x0 = std::min(x0, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.X / svec.W)));
183 y0 = std::min(y0, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.Y / svec.W)));
184 x1 = std::max(x1, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.X / svec.W)));
185 y1 = std::max(y1, static_cast<u16>(g_HalfMaxCoord + static_cast<u16>(g_HalfMaxCoord * svec.Y / svec.W)));
186 z0 = std::min(z0, svec.Z / svec.W);
187 }
188 }
189 }
190 // TODO: there must be a quicker way to do this than to test every vertex,
191 // given the symmetry of the bounding box
192
193 occluder.x0 = Clamp(x0, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
194 occluder.y0 = Clamp(y0, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
195 occluder.x1 = Clamp(x1, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
196 occluder.y1 = Clamp(y1, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
197 occluder.z = z0;
198}
199
200static void ComputeScreenPos(Caster& caster, const CVector3D& pos, CMatrix3D& proj)
201{
202 CVector4D svec = proj.Transform(CVector4D(pos.X, pos.Y, pos.Z, 1.0f));
203 u16 x = g_HalfMaxCoord + static_cast<int>(g_HalfMaxCoord * svec.X / svec.W);
204 u16 y = g_HalfMaxCoord + static_cast<int>(g_HalfMaxCoord * svec.Y / svec.W);
205 caster.x = Clamp(x, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
206 caster.y = Clamp(y, std::numeric_limits<u16>::min(), g_MaxCoord - 1);
207 caster.z = svec.Z / svec.W;
208}
209
210void SilhouetteRenderer::ComputeSubmissions(const CCamera& camera)
211{
212 PROFILE3("compute silhouettes");
213
214 m_DebugBounds.clear();
215 m_DebugRects.clear();
216 m_DebugSpheres.clear();
217
218 m_VisiblePatchOccluders.clear();
219 m_VisibleModelOccluders.clear();
220 m_VisibleModelCasters.clear();
221
222 std::vector<Occluder> occluders;
223 std::vector<Caster> casters;
224 std::vector<Entry> entries;
225
226 occluders.reserve(m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size());
227 casters.reserve(m_SubmittedModelCasters.size());
228 entries.reserve((m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size()) * 2 + m_SubmittedModelCasters.size());
229
230 CMatrix3D proj = camera.GetViewProjection();
231
232 // Bump the positions of unit casters upwards a bit, so they're not always
233 // detected as intersecting the terrain they're standing on
234 CVector3D posOffset(0.0f, 0.1f, 0.0f);
235
236#if 0
237 // For debugging ray-patch intersections - casts a ton of rays and draws
238 // a sphere where they intersect
239 for (int y = 0; y < g_yres; y += 8)
240 {
241 for (int x = 0; x < g_xres; x += 8)
242 {
243 SOverlaySphere sphere;
244 sphere.m_Color = CColor(1, 0, 0, 1);
245 sphere.m_Radius = 0.25f;
246 sphere.m_Center = camera.GetWorldCoordinates(x, y, false);
247
248 CVector3D origin, dir;
249 camera.BuildCameraRay(x, y, origin, dir);
250
251 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
252 {
253 CPatch* occluder = m_SubmittedPatchOccluders[i];
254 if (CHFTracer::PatchRayIntersect(occluder, origin, dir, &sphere.m_Center))
255 sphere.m_Color = CColor(0, 0, 1, 1);
256 }
257 m_DebugSpheres.push_back(sphere);
258 }
259 }
260#endif
261
262 {
263 PROFILE("compute bounds");
264
265 for (size_t i = 0; i < m_SubmittedModelOccluders.size(); ++i)
266 {
267 CModel* occluder = m_SubmittedModelOccluders[i];
268
269 Occluder d;
270 d.renderable = occluder;
271 d.isPatch = false;
272 d.rendered = false;
273 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
274
275 // Skip zero-sized occluders, so we don't need to worry about EDGE_OUT
276 // getting sorted before EDGE_IN
277 if (d.x0 == d.x1 || d.y0 == d.y1)
278 continue;
279
280 u16 id = static_cast<u16>(occluders.size());
281 occluders.push_back(d);
282
283 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
284 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
285 }
286
287 for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
288 {
289 CPatch* occluder = m_SubmittedPatchOccluders[i];
290
291 Occluder d;
292 d.renderable = occluder;
293 d.isPatch = true;
294 d.rendered = false;
295 ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
296
297 // Skip zero-sized occluders
298 if (d.x0 == d.x1 || d.y0 == d.y1)
299 continue;
300
301 u16 id = static_cast<u16>(occluders.size());
302 occluders.push_back(d);
303
304 entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
305 entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
306 }
307
308 for (size_t i = 0; i < m_SubmittedModelCasters.size(); ++i)
309 {
310 CModel* model = m_SubmittedModelCasters[i];
311 CVector3D pos = model->GetTransform().GetTranslation() + posOffset;
312
313 Caster d;
314 d.model = model;
315 d.rendered = false;
316 ComputeScreenPos(d, pos, proj);
317
318 u16 id = static_cast<u16>(casters.size());
319 casters.push_back(d);
320
321 entries.push_back(EntryCreate(POINT, id, d.x));
322 }
323 }
324
325 // Make sure the u16 id didn't overflow
326 ENSURE(occluders.size() < 65536 && casters.size() < 65536);
327
328 {
329 PROFILE("sorting");
330 std::sort(entries.begin(), entries.end());
331 }
332
333 {
334 PROFILE("sweeping");
335
336 ActiveList active;
337 CVector3D cameraPos = camera.GetOrientation().GetTranslation();
338
339 for (size_t i = 0; i < entries.size(); ++i)
340 {
341 Entry e = entries[i];
342 int type = EntryGetType(e);
343 u16 id = EntryGetId(e);
344 if (type == EDGE_IN)
345 active.Add(id);
346 else if (type == EDGE_OUT)
347 active.Remove(id);
348 else
349 {
350 Caster& caster = casters[id];
351 for (size_t j = 0; j < active.m_Ids.size(); ++j)
352 {
353 Occluder& occluder = occluders[active.m_Ids[j]];
354
355 if (caster.y < occluder.y0 || caster.y > occluder.y1)
356 continue;
357
358 if (caster.z < occluder.z)
359 continue;
360
361 // No point checking further if both are already being rendered
362 if (caster.rendered && occluder.rendered)
363 continue;
364
365 if (!g_DisablePreciseIntersections)
366 {
367 CVector3D pos = caster.model->GetTransform().GetTranslation() + posOffset;
368 if (occluder.isPatch)
369 {
370 CPatch* patch = static_cast<CPatch*>(occluder.renderable);
371 if (!CHFTracer::PatchRayIntersect(patch, pos, cameraPos - pos, NULL))
372 continue;
373 }
374 else
375 {
376 float tmin, tmax;
377 if (!occluder.renderable->GetWorldBounds().RayIntersect(pos, cameraPos - pos, tmin, tmax))
378 continue;
379 }
380 }
381
382 caster.rendered = true;
383 occluder.rendered = true;
384 }
385 }
386 }
387 }
388
389 if (m_DebugEnabled)
390 {
391 for (size_t i = 0; i < occluders.size(); ++i)
392 {
393 DebugRect r;
394 r.color = occluders[i].rendered ? CColor(1.0f, 1.0f, 0.0f, 1.0f) : CColor(0.2f, 0.2f, 0.0f, 1.0f);
395 r.x0 = occluders[i].x0;
396 r.y0 = occluders[i].y0;
397 r.x1 = occluders[i].x1;
398 r.y1 = occluders[i].y1;
399 m_DebugRects.push_back(r);
400
401 DebugBounds b;
402 b.color = r.color;
403 b.bounds = occluders[i].renderable->GetWorldBounds();
404 m_DebugBounds.push_back(b);
405 }
406 }
407
408 for (size_t i = 0; i < occluders.size(); ++i)
409 {
410 if (occluders[i].rendered)
411 {
412 if (occluders[i].isPatch)
413 m_VisiblePatchOccluders.push_back(static_cast<CPatch*>(occluders[i].renderable));
414 else
415 m_VisibleModelOccluders.push_back(static_cast<CModel*>(occluders[i].renderable));
416 }
417 }
418
419 for (size_t i = 0; i < casters.size(); ++i)
420 if (casters[i].rendered)
421 m_VisibleModelCasters.push_back(casters[i].model);
422}
423
424void SilhouetteRenderer::RenderSubmitOverlays(SceneCollector& collector)
425{
426 for (size_t i = 0; i < m_DebugSpheres.size(); i++)
427 collector.Submit(&m_DebugSpheres[i]);
428}
429
430void SilhouetteRenderer::RenderSubmitOccluders(SceneCollector& collector)
431{
432 for (size_t i = 0; i < m_VisiblePatchOccluders.size(); ++i)
433 collector.Submit(m_VisiblePatchOccluders[i]);
434
435 for (size_t i = 0; i < m_VisibleModelOccluders.size(); ++i)
436 collector.SubmitNonRecursive(m_VisibleModelOccluders[i]);
437}
438
439void SilhouetteRenderer::RenderSubmitCasters(SceneCollector& collector)
440{
441 for (size_t i = 0; i < m_VisibleModelCasters.size(); ++i)
442 collector.SubmitNonRecursive(m_VisibleModelCasters[i]);
443}
444
445void SilhouetteRenderer::RenderDebugOverlays(const CCamera& camera)
446{
447 CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
448 shaderTech->BeginPass();
449 CShaderProgramPtr shader = shaderTech->GetShader();
450
451 glDepthMask(0);
452 glDisable(GL_CULL_FACE);
453
454 shader->Uniform(str_transform, camera.GetViewProjection());
455
456 for (size_t i = 0; i < m_DebugBounds.size(); ++i)
457 {
458 shader->Uniform(str_color, m_DebugBounds[i].color);
459 g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m_DebugBounds[i].bounds, shader);
460 }
461
462 CMatrix3D m;
463 m.SetIdentity();
464 m.Scale(1.0f, -1.f, 1.0f);
465 m.Translate(0.0f, (float)g_yres, -1000.0f);
466
467 CMatrix3D proj;
468 proj.SetOrtho(0.f, g_MaxCoord, 0.f, g_MaxCoord, -1.f, 1000.f);
469 m = proj * m;
470
471 shader->Uniform(str_transform, proj);
472
473 for (size_t i = 0; i < m_DebugRects.size(); ++i)
474 {
475 const DebugRect& r = m_DebugRects[i];
476 shader->Uniform(str_color, r.color);
477 u16 verts[] = {
478 r.x0, r.y0,
479 r.x1, r.y0,
480 r.x1, r.y1,
481 r.x0, r.y1,
482 r.x0, r.y0,
483 };
484 shader->VertexPointer(2, GL_SHORT, 0, verts);
485 glDrawArrays(GL_LINE_STRIP, 0, 5);
486 }
487
488 shaderTech->EndPass();
489
490 glEnable(GL_CULL_FACE);
491 glDepthMask(1);
492}
493
494void SilhouetteRenderer::EndFrame()
495{
496 m_SubmittedPatchOccluders.clear();
497 m_SubmittedModelOccluders.clear();
498 m_SubmittedModelCasters.clear();
499}
Note: See TracBrowser for help on using the repository browser.