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 |
|
---|
34 | extern int g_xres, g_yres;
|
---|
35 |
|
---|
36 | // For debugging
|
---|
37 | static const bool g_DisablePreciseIntersections = false;
|
---|
38 |
|
---|
39 | SilhouetteRenderer::SilhouetteRenderer()
|
---|
40 | {
|
---|
41 | m_DebugEnabled = false;
|
---|
42 | }
|
---|
43 |
|
---|
44 | void SilhouetteRenderer::AddOccluder(CPatch* patch)
|
---|
45 | {
|
---|
46 | m_SubmittedPatchOccluders.push_back(patch);
|
---|
47 | }
|
---|
48 |
|
---|
49 | void SilhouetteRenderer::AddOccluder(CModel* model)
|
---|
50 | {
|
---|
51 | m_SubmittedModelOccluders.push_back(model);
|
---|
52 | }
|
---|
53 |
|
---|
54 | void 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 |
|
---|
103 | static const u16 g_MaxCoord = 1 << 14;
|
---|
104 | static const u16 g_HalfMaxCoord = g_MaxCoord / 2;
|
---|
105 |
|
---|
106 | struct Occluder
|
---|
107 | {
|
---|
108 | CRenderableObject* renderable;
|
---|
109 | bool isPatch;
|
---|
110 | u16 x0, y0, x1, y1;
|
---|
111 | float z;
|
---|
112 | bool rendered;
|
---|
113 | };
|
---|
114 |
|
---|
115 | struct Caster
|
---|
116 | {
|
---|
117 | CModel* model;
|
---|
118 | u16 x, y;
|
---|
119 | float z;
|
---|
120 | bool rendered;
|
---|
121 | };
|
---|
122 |
|
---|
123 | enum { 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 |
|
---|
136 | typedef uint32_t Entry;
|
---|
137 |
|
---|
138 | static Entry EntryCreate(int type, u16 id, u16 x) { return (x << 18) | (type << 16) | id; }
|
---|
139 | static int EntryGetId(Entry e) { return e & 0xffff; }
|
---|
140 | static int EntryGetType(Entry e) { return (e >> 16) & 3; }
|
---|
141 |
|
---|
142 | struct 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 |
|
---|
167 | static 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 |
|
---|
199 | static 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 |
|
---|
209 | void 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 |
|
---|
423 | void SilhouetteRenderer::RenderSubmitOverlays(SceneCollector& collector)
|
---|
424 | {
|
---|
425 | for (size_t i = 0; i < m_DebugSpheres.size(); i++)
|
---|
426 | collector.Submit(&m_DebugSpheres[i]);
|
---|
427 | }
|
---|
428 |
|
---|
429 | void 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 |
|
---|
438 | void SilhouetteRenderer::RenderSubmitCasters(SceneCollector& collector)
|
---|
439 | {
|
---|
440 | for (size_t i = 0; i < m_VisibleModelCasters.size(); ++i)
|
---|
441 | collector.SubmitNonRecursive(m_VisibleModelCasters[i]);
|
---|
442 | }
|
---|
443 |
|
---|
444 | void 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 |
|
---|
493 | void SilhouetteRenderer::EndFrame()
|
---|
494 | {
|
---|
495 | m_SubmittedPatchOccluders.clear();
|
---|
496 | m_SubmittedModelOccluders.clear();
|
---|
497 | m_SubmittedModelCasters.clear();
|
---|
498 | }
|
---|