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