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"
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
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;
105 CRenderableObject
* renderable
;
120 enum { EDGE_IN
, EDGE_OUT
, POINT
};
122 // Entry is essentially:
124 // u16 id; // index into occluders array
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; }
141 std::vector
<u16
> m_Ids
;
150 ssize_t sz
= m_Ids
.size();
151 for (ssize_t i
= sz
-1; i
>= 0; --i
)
155 m_Ids
[i
] = m_Ids
[sz
-1];
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
;
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);
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);
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
);
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
);
261 PROFILE("compute bounds");
263 for (size_t i
= 0; i
< m_SubmittedModelOccluders
.size(); ++i
)
265 CModel
* occluder
= m_SubmittedModelOccluders
[i
];
268 d
.renderable
= occluder
;
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
)
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
];
290 d
.renderable
= occluder
;
293 ComputeScreenBounds(d
, occluder
->GetWorldBounds(), proj
);
295 // Skip zero-sized occluders
296 if (d
.x0
== d
.x1
|| d
.y0
== d
.y1
)
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
;
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);
328 std::sort(entries
.begin(), entries
.end());
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
);
344 else if (type
== EDGE_OUT
)
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
)
356 if (caster
.z
< occluder
.z
)
359 // No point checking further if both are already being rendered
360 if (caster
.rendered
&& occluder
.rendered
)
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
))
375 if (!occluder
.renderable
->GetWorldBounds().RayIntersect(pos
, cameraPos
- pos
, tmin
, tmax
))
380 caster
.rendered
= true;
381 occluder
.rendered
= true;
389 for (size_t i
= 0; i
< occluders
.size(); ++i
)
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
);
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
));
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();
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
);
462 m
.Scale(1.0f
, -1.f
, 1.0f
);
463 m
.Translate(0.0f
, (float)g_yres
, -1000.0f
);
466 proj
.SetOrtho(0.f
, MAX_COORD
, 0.f
, MAX_COORD
, -1.f
, 1000.f
);
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
);
482 shader
->VertexPointer(2, GL_SHORT
, 0, verts
);
483 glDrawArrays(GL_LINE_STRIP
, 0, 5);
486 shaderTech
->EndPass();
488 glEnable(GL_CULL_FACE
);
492 void SilhouetteRenderer::EndFrame()
494 m_SubmittedPatchOccluders
.clear();
495 m_SubmittedModelOccluders
.clear();
496 m_SubmittedModelCasters
.clear();