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"
34 extern int g_xres
, g_yres
;
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
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;
107 CRenderableObject
* renderable
;
122 enum { EDGE_IN
, EDGE_OUT
, POINT
};
124 // Entry is essentially:
126 // u16 id; // index into occluders array
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; }
143 std::vector
<u16
> m_Ids
;
152 ssize_t sz
= m_Ids
.size();
153 for (ssize_t i
= sz
-1; i
>= 0; --i
)
157 m_Ids
[i
] = m_Ids
[sz
-1];
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
;
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);
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);
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
);
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
);
262 PROFILE("compute bounds");
264 for (size_t i
= 0; i
< m_SubmittedModelOccluders
.size(); ++i
)
266 CModel
* occluder
= m_SubmittedModelOccluders
[i
];
269 d
.renderable
= occluder
;
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
)
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
];
291 d
.renderable
= occluder
;
294 ComputeScreenBounds(d
, occluder
->GetWorldBounds(), proj
);
296 // Skip zero-sized occluders
297 if (d
.x0
== d
.x1
|| d
.y0
== d
.y1
)
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
;
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);
329 std::sort(entries
.begin(), entries
.end());
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
);
345 else if (type
== EDGE_OUT
)
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
)
357 if (caster
.z
< occluder
.z
)
360 // No point checking further if both are already being rendered
361 if (caster
.rendered
&& occluder
.rendered
)
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
))
376 if (!occluder
.renderable
->GetWorldBounds().RayIntersect(pos
, cameraPos
- pos
, tmin
, tmax
))
381 caster
.rendered
= true;
382 occluder
.rendered
= true;
390 for (size_t i
= 0; i
< occluders
.size(); ++i
)
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
);
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
));
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();
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
);
463 m
.Scale(1.0f
, -1.f
, 1.0f
);
464 m
.Translate(0.0f
, (float)g_yres
, -1000.0f
);
467 proj
.SetOrtho(0.f
, MAX_COORD
, 0.f
, MAX_COORD
, -1.f
, 1000.f
);
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
);
483 shader
->VertexPointer(2, GL_SHORT
, 0, verts
);
484 glDrawArrays(GL_LINE_STRIP
, 0, 5);
487 shaderTech
->EndPass();
489 glEnable(GL_CULL_FACE
);
493 void SilhouetteRenderer::EndFrame()
495 m_SubmittedPatchOccluders
.clear();
496 m_SubmittedModelOccluders
.clear();
497 m_SubmittedModelCasters
.clear();