Cogs.Core
LightSystem.cpp
1#ifdef _WIN32
2// TODO: Remove when we update past GLM 0.9.9.7
3#pragma warning(push)
4#pragma warning(disable: 4127) // conditional expression is constant
5#include <glm/ext/matrix_clip_space.hpp>
6#pragma warning(pop)
7#endif
8
9#include "LightSystem.h"
10
11#include "Rendering/ICapabilities.h"
12
13#include "Context.h"
14#include "Services/Time.h"
15
16#include "Scene/GetBounds.h"
17
18#include "Systems/Core/TransformSystem.h"
19#include "Systems/Core/CameraSystem.h"
20
21#include "Renderer/CullingManager.h"
22#include "Renderer/IRenderer.h"
23
24#include "Resources/TextureManager.h"
25#include "Resources/MaterialManager.h"
26
27#include "Utilities/Parsing.h"
28#include "Utilities/Math.h"
29
30#include <glm/glm.hpp>
31#include <glm/gtx/compatibility.hpp>
32#include <glm/gtc/color_space.hpp>
33
34namespace
35{
36 using namespace Cogs::Core;
37
38 struct CamState
39 {
40 const CameraData * cameraData = nullptr;
41 glm::mat4 rawInverseProjection;
42 glm::mat4 rawInverseViewProjection;
43 };
44
45 static const glm::vec3 corners[] =
46 {
47 { -1, -1, -1 },
48 { 1, -1, -1 },
49 { 1, 1, -1 },
50 { -1, 1, -1 },
51 { -1, -1, 1 },
52 { 1, -1, 1 },
53 { 1, 1, 1 },
54 { -1, 1, 1 },
55 };
56
57 static const uint32_t edgeIndicesData[][2] = {
58 {0, 1},
59 {0, 2},
60 {0, 4},
61 {1, 3},
62 {1, 5},
63 {2, 3},
64 {2, 6},
65 {3, 7},
66 {4, 5},
67 {4, 6},
68 {5, 7},
69 {6, 7}
70 };
71
72 static void clipLineSegments(std::vector<glm::vec3>& E, const glm::vec4 plane)
73 {
74 size_t o = 0;
75 for (size_t i = 0; i < E.size(); i += 2) {
76 const auto a = E[i + 0];
77 const auto b = E[i + 1];
78 const float d0 = glm::dot(glm::vec4(a, 1.f), plane);
79 const float d1 = glm::dot(glm::vec4(b, 1.f), plane);
80
81 bool b0 = 0.f <= d0;
82 bool b1 = 0.f <= d1;
83
84 auto d = b - a;
85 auto num = glm::dot(d, glm::vec3(plane));
86 if (glm::abs(num) < 0.01f) { // close to parallel, discard and let other edges find intersection.
87 b0 = b1 = false;
88 }
89 if (b0) E[o++] = a;
90 if (b1) E[o++] = b;
91 if (b0 == b1) continue;
92
93 // <a + t(b-a), m> = 0 <=> <a,m> + t<b-a,m> = 0 <=> t = -<a,m> / <b-a,m>
94
95 auto t = -glm::dot(glm::vec4(a, 1.f), plane) / num;
96
97 E[o++] = a + t * d;
98 }
99 E.resize(o);
100 assert((E.size() & 1) == 0);
101
102 }
103
104 static glm::mat4 rotateVecToY(const glm::vec2& d)
105 {
106 auto l2 = glm::dot(d, d);
107 float c = 1.f;
108 float s = 0.f;
109 if (0.001f <= l2) {
110 const auto r = 1.f / glm::sqrt(l2);
111 c = r * d.y; // Cosine of rotation
112 s = r * d.x; // Sine of rotation
113 }
114 return glm::mat4( c, s, 0, 0, // Rotation about Z matrix.
115 -s, c, 0, 0,
116 0, 0, 1, 0,
117 0, 0, 0, 1);
118 }
119
120 static glm::mat4 createLightViewMatrix(const glm::vec4 & /*cascadeLine*/, const glm::quat& rotation)
121 {
122 const glm::mat4 lightView = glm::mat4_cast(glm::conjugate(rotation));
123
124 auto lz = glm::abs(euclidean(lightView * glm::vec4(0, 0, 1, 1)));
125
126 glm::vec3 align_axis = glm::vec3(0, 1, 0);
127 if (lz.y > lz.x && lz.y > lz.z) {
128 // light mainly from y-dir, align light y with z.
129 align_axis = glm::vec3(0, 0, 1);
130 }
131
132 // Try to rotate such that the light view Y-axis aligns with the cascade-line,
133 // so the cascade frustum enclose the cascade as tight as possible.
134 auto cascadeAxisLW = glm::vec3(glm::vec2(glm::mat3(lightView) * glm::vec3(align_axis)), 0.f);
135 return rotateVecToY(cascadeAxisLW) * lightView;
136 }
137
138 static glm::mat4 createRotaryAlignmentMatrix(const glm::mat4& M)
139 {
140 auto ro = euclidean(M * glm::vec4(0, 0, 0, 1));
141 auto rx = euclidean(M * glm::vec4(1, 0, 0, 1)) - ro;
142 auto ry = euclidean(M * glm::vec4(0, 1, 0, 1)) - ro;
143 auto rz = euclidean(M * glm::vec4(0, 0, 1, 1)) - ro;
144
145 auto xz = glm::abs(rx.z);
146 auto yz = glm::abs(ry.z);
147 auto zz = glm::abs(rz.z);
148
149 glm::vec2 d;
150 if (xz < yz && xz < zz) {
151 d = glm::vec2(rx);
152 }
153 else if (yz < zz) {
154 d = glm::vec2(ry);
155 }
156 else {
157 d = glm::vec2(rz);
158 }
159 return rotateVecToY(d);
160 }
161
162 static void createFrustumFitMatrix(Context * context,
163 glm::mat4& rawCascadeProjectionMatrix,
164 glm::mat4& rawCascadeCullMatrix,
165 LightCascadeFrustaPoints* frustaPoints,
166 const CameraData* refCamData,
167 const std::vector<CamState>& camStates,
168 const glm::mat4& lightView,
169 const glm::vec4& cascadeLine,
170 const float n, const float f,
171 float frustumSlack,
172 uint32_t resolution,
173 glm::uvec2 &blueNoiseOffset)
174 {
175
176 auto toCull = createRotaryAlignmentMatrix(lightView * refCamData->inverseViewMatrix);
177
178 glm::vec3 min_lv(std::numeric_limits<float>::max());
179 glm::vec3 min_cv = min_lv;
180 glm::vec3 max_lv(-std::numeric_limits<float>::max());
181 glm::vec3 max_cv = max_lv;
182
183 for (const auto & camState : camStates) {
184 const auto M = lightView * camState.rawInverseViewProjection;
185 if (camState.cameraData == refCamData) {
186 // cascadeLine is per definition aligned with the reference camera,
187 // so we can avoid the more elaborate and numerically challenging
188 // approach of arbitrary clipping.
189
190 // First, we find the z and w clip-space values of points on the near
191 // and far-planes.
192 const auto row2 = glm::transpose(refCamData->rawProjectionMatrix)[2];
193 const auto row3 = glm::transpose(refCamData->rawProjectionMatrix)[3];
194 const auto zn = glm::dot(row2, glm::vec4(0, 0, -n, 1));
195 const auto wn = glm::dot(row3, glm::vec4(0, 0, -n, 1));
196 const auto zf = glm::dot(row2, glm::vec4(0, 0, -f, 1));
197 const auto wf = glm::dot(row3, glm::vec4(0, 0, -f, 1));
198
199 // Then we transform the corners of the truncated frustum into light
200 // space, and use this to form a bounding box.
201 for (unsigned i = 0; i < 4; i++) {
202 const auto pn = euclidean(M * glm::vec4(wn*glm::vec2(corners[i]), zn, wn));
203 const auto pf = euclidean(M * glm::vec4(wf*glm::vec2(corners[i]), zf, wf));
204 if (frustaPoints) {
205 frustaPoints->points.push_back(pn);
206 frustaPoints->points.push_back(pf);
207 }
208 min_lv = glm::min(min_lv, glm::min(pn, pf));
209 max_lv = glm::max(max_lv, glm::max(pn, pf));
210
211 min_cv = glm::min(min_cv, glm::min(glm::vec3(toCull * glm::vec4(pn, 1)), // toCull is just a rotation, so it is safe to skip 1/w.
212 glm::vec3(toCull * glm::vec4(pf, 1))));
213 max_cv = glm::max(max_cv, glm::max(glm::vec3(toCull * glm::vec4(pn, 1)),
214 glm::vec3(toCull * glm::vec4(pf, 1))));
215 }
216 }
217 else {
218 // Cascade-line is not aligned, so we use a more elaborate approach:
219 // First, we transform the (un-truncated) view frustum to light space.
220 glm::vec3 p[8];
221 for (unsigned i = 0; i < 8; i++) {
222 p[i] = euclidean(M * glm::vec4(corners[i], 1.f));
223 }
224
225 // And we form all edges of the frustum. We will clip these edges against
226 // planes orthogonal to the cascade line at near and far values. In the
227 // end, the clipped edges will span the convex hull of the clipped frustum.
228 // We use the end-points of the clipped edges to form a bounding box.
229 std::vector<glm::vec3> E;
230 for (const auto & e : edgeIndicesData) {
231 E.push_back(p[e[0]]);
232 E.push_back(p[e[1]]);
233 }
234 // Assuming ligthView is just rotations (i.e., no need for inverse-transpose).
235 clipLineSegments(E, lightView * glm::vec4(glm::vec3(cascadeLine), cascadeLine.w - n));
236 clipLineSegments(E, -lightView * glm::vec4(glm::vec3(cascadeLine), cascadeLine.w - f));
237 for (const auto & pp : E) {
238 if (frustaPoints) {
239 frustaPoints->points.push_back(pp);
240 }
241 min_lv = glm::min(min_lv, pp);
242 max_lv = glm::max(max_lv, pp);
243
244 min_cv = glm::min(min_cv, glm::vec3(toCull * glm::vec4(pp, 1)));
245 max_cv = glm::max(max_cv, glm::vec3(toCull * glm::vec4(pp, 1)));
246 }
247 }
248
249 if (frustaPoints) {
250 frustaPoints->offsets.push_back(unsigned(frustaPoints->points.size()));
251 }
252 }
253
254 // Quadratic viewport that exactly fits the frustum. This viewport will be
255 // slightly expanded with a margin later.
256 const float size = std::max((max_lv.x - min_lv.x),
257 (max_lv.y - min_lv.y));
258 glm::vec2 center_lv = 0.5f * glm::vec2(min_lv + max_lv);
259 const float zNear = -max_lv.z - std::max(0.1f, frustumSlack) * (max_lv.z - min_lv.z);
260 const float zFar = -min_lv.z + frustumSlack * (max_lv.z - min_lv.z);
261
262 // Check if viewport fits in the frustum used in the previous frame, and is
263 // not way smaller. If it is an OK fit, we recylce the frustum to minimize
264 // jittering of the shadows when the view changes slightly.
265 bool needNewFrustum = frustumSlack == 0.f;
266 const float factor = 0.5f * (1.f + frustumSlack);
267 const float greaterThanValue = glm::max(0.f, 1.f - 2.f * frustumSlack);
268 for (unsigned i = 0; i < 8u; i++) {
269 glm::vec4 p = rawCascadeProjectionMatrix * glm::vec4(center_lv.x + 0.5 * (((i >> 0) & 1) ? -size : size),
270 center_lv.y + 0.5 * (((i >> 1) & 1) ? -size : size),
271 ((i >> 2) & 1) ? min_lv.z : max_lv.z,
272 1.f);
273
274 bool xOk = ((-p.w <= p.x) && (p.x <= -greaterThanValue * p.w)) || ((greaterThanValue * p.w <= p.x) && (p.x <= p.w));
275 bool yOk = ((-p.w <= p.y) && (p.y <= -greaterThanValue * p.w)) || ((greaterThanValue * p.y <= p.y) && (p.y <= p.w));
276 bool zOk = ((-p.w <= p.z) && (p.z <= -0.5f * p.w)) || ((0.5f * p.z <= p.z) && (p.z <= p.w));
277 needNewFrustum = needNewFrustum || !(xOk && yOk && zOk);
278 }
279
280 if (needNewFrustum) {
281 // Last frame's frustum didn't match, so we create a new one.
282
283 {
284 // Adjust for origin offset.
285 const float steppyness = 5.f;
286 const float texelSize = std::exp2(std::ceil(log2(size / resolution) * steppyness) / steppyness);
287 const glm::dvec3 origin = context->transformSystem->getOrigin();
288 const glm::dvec3 offset = glm::dmat3(lightView) * origin;
289 const double snappX = std::floor((center_lv.x + offset.x) / texelSize);
290 const double snappY = std::floor((center_lv.y + offset.y) / texelSize);
291 double snapX_ = std::fmod(snappX, 64.0);
292 if (snapX_ < 0.0) snapX_ += 64;
293 double snapY_ = std::fmod(snappY, 64.0);
294 if (snapY_ < 0.0) snapY_ += 64;
295
296 blueNoiseOffset = glm::uvec2(static_cast<uint32_t>(snapX_), static_cast<uint32_t>(snapY_));
297 blueNoiseOffset = glm::uvec2(blueNoiseOffset.x, (64u - blueNoiseOffset.y) % 64u);
298 }
299
300 // Grow viewport slightly
301 const glm::vec2 viewportMin = center_lv - glm::vec2(factor * size);
302 const glm::vec2 viewportMax = center_lv + glm::vec2(factor * size);
303 if (frustaPoints) {
304 frustaPoints->viewportMin = viewportMin;
305 frustaPoints->viewportMax = viewportMax;
306 }
307 rawCascadeProjectionMatrix = glm::ortho(viewportMin.x, viewportMax.x,
308 viewportMin.y, viewportMax.y,
309 zNear, zFar);
310 }
311 rawCascadeCullMatrix = glm::ortho(min_cv.x, max_cv.x,
312 min_cv.y, max_cv.y,
313 zNear, zFar) * toCull;
314 }
315
316 static void getCameras(Context* context, const CameraData* & refCamData, std::vector<CamState>& camStates, const LightComponent& light)
317 {
318 if (auto e = light.lodReference.lock(); e) {
319 if (auto * c = e->getComponent<CameraComponent>(); c && ((c->lightingMask & light.lightingLayer) != LightingLayers::None)) {
320 refCamData = &context->cameraSystem->getData(c);
321 }
322 }
323 if (!refCamData) {
324 refCamData = &context->cameraSystem->getMainCameraData();
325 }
326
327 for (auto & we : light.cameras) {
328 if (auto e = we.lock(); e) {
329 if (const auto * c = e->getComponent<CameraComponent>(); c && ((c->lightingMask & light.lightingLayer) != LightingLayers::None)) {
330 camStates.emplace_back();
331 camStates.back().cameraData = &context->cameraSystem->getData(c);
332 }
333 }
334 }
335 if (camStates.empty() && refCamData) {
336 camStates.emplace_back();
337 camStates.back().cameraData = refCamData;
338 }
339 for (auto & camState : camStates) {
340 camState.rawInverseViewProjection = glm::inverse(camState.cameraData->rawViewProjection);
341 }
342 }
343
344 static void calculateCascadeCount(LightData & lightData, float zNear, float zFar, float FOV)
345 {
346 if(!lightData.dynamicCascadeCount) return;
347 float a = FOV*0.5f;
348 float tana = tanf(a);
349 float ns = tana*zNear;
350 float fs = tana*zFar;
351 float r = fs/ns-1.0f;
352 lightData.numViewports = (uint16_t)ceil(r);
353 lightData.numViewports = std::max(lightData.numViewports, (uint16_t)1);
354 lightData.numViewports = std::min(lightData.numViewports, (uint16_t)lightData.maxViewports);
355 }
356
357 static void calculateSplits(Context* context, LightData & lightData, float zNear, float zFar)
358 {
359 const auto expFactor = context->variables->get("shadows.cascades.expFactor")->getFloat();
360 const auto overlapFactor = 0.5f * context->variables->get("shadows.cascades.overlapFactor")->getFloat();
361
362
363 const float cascadeWidth = 1.f / static_cast<float>(lightData.numViewports);
364 if(lightData.numViewports == 1){
365 lightData.nearDepths[0] = zNear;
366 lightData.farDepths[0] = zFar;
367 }
368 else{
369 for (int i = 0; i < lightData.numViewports; ++i) {
370 const float iF = std::min(1.f, cascadeWidth * (static_cast<float>(i) + 1.f + overlapFactor));
371 const float iN = std::max(0.f, cascadeWidth * (static_cast<float>(i) - overlapFactor));
372
373 const float zExpNear = zNear * glm::pow(zFar / zNear, iN);
374 const float zLinearNear = zNear + iN * (zFar - zNear);
375 lightData.nearDepths[i] = glm::max(glm::lerp(zLinearNear, zExpNear, expFactor), zNear);
376 assert(std::isfinite(lightData.nearDepths[i]));
377
378 const float zExp = zNear * glm::pow(zFar / zNear, iF);
379 const float zLinear = zNear + iF * (zFar - zNear);
380
381 lightData.farDepths[i] = glm::lerp(zLinear, zExp, expFactor);
382 assert(std::isfinite(lightData.farDepths[i]));
383 }
384 }
385 }
386
387 static float calculateSplits(Context * context, LightData & lightData, const glm::mat4& rotation, const CameraData* refCamData, const std::vector<CamState>& camStates, const float maxShadowDistance)
388 {
389 // Determine axis along which we will calculate cascades.
390 const glm::mat4 & M = refCamData->inverseViewMatrix;
391 const glm::vec3 o = glm::vec3(M * glm::vec4(0, 0, 0, 1));
392 const glm::vec3 a = glm::normalize(glm::vec3(M * glm::vec4(0, 0, -1, 0)));
393 const float d = -glm::dot(o, a);
394 assert(std::isfinite(d));
395 lightData.cascadeLine = glm::vec4(a, d);
396
397 // Determine range along cascade axis
398 auto zNear = refCamData->nearDistance;
399 auto zFar = refCamData->farDistance;
400 for (auto & camState : camStates) {
401 for (auto c : corners) {
402 auto q = camState.rawInverseViewProjection * glm::vec4(c, 1.f);
403 if (std::numeric_limits<float>::epsilon() < q.w) {
404 auto t = glm::dot(a, (1.f / q.w)*glm::vec3(q)) + d;
405 assert(std::isfinite(t));
406
407 zNear = glm::min(zNear, t);
408 zFar = glm::max(zFar, t);
409 }
410 }
411 }
412
413 if(!lightData.tightShadowBounds){
414 calculateCascadeCount(lightData, zNear, zFar, refCamData->fieldOfView);
415 calculateSplits(context, lightData, refCamData->nearDistance, std::min(zFar, refCamData->nearDistance + maxShadowDistance));
416 return std::max(zNear, refCamData->nearDistance - maxShadowDistance);
417 }
418 else{
419 const Cogs::Geometry::BoundingBox bbox = context->bounds->getShadowBounds(context);
420 const glm::vec3 bbox_corners[] = {
421 glm::vec3(bbox.min.x, bbox.min.y, bbox.min.z),
422 glm::vec3(bbox.max.x, bbox.min.y, bbox.min.z),
423 glm::vec3(bbox.max.x, bbox.max.y, bbox.min.z),
424 glm::vec3(bbox.min.x, bbox.max.y, bbox.min.z),
425 glm::vec3(bbox.min.x, bbox.min.y, bbox.max.z),
426 glm::vec3(bbox.max.x, bbox.min.y, bbox.max.z),
427 glm::vec3(bbox.max.x, bbox.max.y, bbox.max.z),
428 glm::vec3(bbox.min.x, bbox.max.y, bbox.max.z),
429 };
430 const glm::vec2 viewport_corners[] = { {-1, -1}, {1, -1}, {1, 1}, {-1, 1} };
431
432 // zmin, zmax: bounding box z-extent (cascade line extent)
433 float zmin = std::numeric_limits<float>::max();
434 float zmax = 0.0f;
435
436 //glm::vec3 lightDir = glm::vec3(lightData.lightDirection);
437 glm::vec3 lightDir = glm::mat3(rotation) * glm::vec3(0, 0, -1);
438
439 // Construct a shadow frustrum slice (A plane containing the light vector):
440 glm::vec3 planeTangent = glm::cross(a, lightDir);
441 glm::vec3 n = glm::normalize(glm::cross(lightDir, planeTangent)); // Plane Normal
442
443 for (auto &camState : camStates) {
444 const CameraData &cameraData = *camState.cameraData;
445 const glm::vec3 ndcLightDir = euclidean(cameraData.rawProjectionMatrix*glm::vec4(glm::mat3(cameraData.viewMatrix)*lightDir, 1.0f));
446 const float eps = 0.0001f;
447 const bool along = ndcLightDir.z <= 0.0f;
448
449 // Find z-range for bounding volume
450 float azmin = std::numeric_limits<float>::max();
451 float azmax = -std::numeric_limits<float>::max();
452 for (const glm::vec3 &p0 : bbox_corners){
453 float t = glm::dot(a, p0) + d;
454 azmin = std::min(azmin, t);
455 azmax = std::max(azmax, t);
456 }
457
458 // Check that shadows cannot travel infinitely far into the view frustrum.
459 // Is camPos+lightDir inside the view frustrum? Then don't cull the with this camera.
460 if(std::abs(ndcLightDir.x)-eps <= 1.0f && std::abs(ndcLightDir.y)-eps <= 1.0f){// && ndcLightDir.z <= 0.0f){
461 zmin = zNear;
462 zmax = zFar;
463 if(along) zmin = std::max(zmin, azmin);
464 else zmax = std::min(zmax, azmax);
465 break;
466 }
467
468 // Generate a slice for all points on the bbox
469 for (const glm::vec3 &p0 : bbox_corners){
470 // Intersect with frustrum
471 for (const glm::vec2 &corn : viewport_corners){
472 // Frustrum corner line:
473 glm::vec3 l0 = glm::vec3(cameraData.inverseViewMatrix * glm::vec4(0, 0, 0, 1));
474 glm::vec3 l = glm::normalize(euclidean(camState.rawInverseViewProjection * glm::vec4(corn, 1.0f, 1.f))-l0);
475 // Intersect plane with frustrum lines:
476 float LdotN = glm::dot(l, n);
477 float ld = glm::dot(p0-l0, n)/LdotN;
478 glm::vec3 p = l0+ld*l;
479 float t = glm::dot(a, p) + d;
480
481 if(along) t = std::max(t, azmin);
482 else t = std::min(t, azmax);
483
484 t = std::max(0.0f, t);
485 zmin = std::min(zmin, t);
486 zmax = std::max(zmax, t);
487 }
488 }
489 }
490
491 zNear = glm::max(zNear, zmin);
492 zFar = glm::min(zFar, zmax);
493
494 zFar = glm::max(zNear, zFar);
495
496 zNear = std::max(zNear, refCamData->nearDistance);
497 zFar = std::min(zFar, refCamData->nearDistance + maxShadowDistance);
498 calculateCascadeCount(lightData, zNear, zFar, refCamData->fieldOfView);
499 calculateSplits(context, lightData, zNear, zFar);
500 return zNear;
501 }
502 }
503} // namespace ...
504
506{
508 context->variables->set("shadows.cascades.expFactor", 0.9f);
509 context->variables->set("shadows.cascades.overlapFactor", 0.2f);
510 context->variables->set("renderer.maxShadowDistance", 30000.0f);
511}
512
514{
515}
516
518{
519 const bool originOnTop = context->device->getCapabilities()->getDeviceCapabilities().OriginOnTop;
520
521 if (!cascadeArray) {
522 cascadeArray = context->textureManager->create();
523 cascadeArray->setName("Light.ShadowCascades");
524 }
525
526 const bool useTextureCubeArrays = context->device->getCapabilities()->getDeviceCapabilities().TextureCubeArrays;
527 if (!cubeArray) {
528 cubeArray = context->textureManager->create();
529 cubeArray->setName("Light.ShadowCubeArray");
530 }
531
532 auto transformSystem = context->transformSystem;
533 auto variables = context->variables.get();
534
535 softShadows = parseEnum<SoftShadows>(variables->get("shadows.softShadows", "Default"));
536 auto shadowUpdate = parseEnum<ShadowUpdate>(variables->get("shadows.update", "Default"));
537 const auto pointShadowResolution = static_cast<unsigned>(std::max(1, variables->get("shadows.pointShadowResolution", 256)));
538
539 unsigned cascadeShadowResolution = (unsigned)std::max(0, variables->get("shadows.cascadeShadowResolution", 1024));
540
541 // How much larger the light frustum will be to avoid jittery shadows when camera moves
542 const float frustumSlack = glm::clamp(variables->get("shadows.frustumSlack", 0.1f), 0.f, 1.f);
543
544 const auto pointShadowFormat = parseTextureFormat(variables->get("shadows.pointShadowFormat", "R32_TYPELESS"));
545 const auto cascadeShadowFormat = parseTextureFormat(variables->get("shadows.cascadeShadowFormat", "R32_TYPELESS"));
546 const bool shadowsEnabled = variables->get("renderer.shadowsEnabled", false);
547
548 const bool lightSystemRun = variables->getOrAdd("lightSystem.run", true);
549 if (!lightSystemRun) {
550 return;
551 }
552
553 bool anyChanged = false;
554 for (const auto & light : pool) {
555 anyChanged |= light.hasChanged();
556 }
557
558 lightsChanged |= anyChanged;
559
560 uint32_t cascadeInstances = 0;
561 uint32_t layerCount = 0;
562
563 uint32_t cubeInstances = 0;
564 for (const auto & light : pool) {
565 const auto transformComponent = light.getComponent<TransformComponent>();
566
567 auto & lightData = getData(&light);
568 lightData.enabled = light.enabled;
569 lightData.castShadows = light.enabled && light.castShadows && shadowsEnabled;
570 lightData.tightShadowBounds = light.tightShadowBounds;
571 lightData.dynamicCascadeCount = light.dynamicCascadeCount;
572 lightData.lightColor = glm::vec4(glm::convertSRGBToLinear(glm::vec3(light.lightColor)),
573 light.lightColor.a);
574
575 if (!light.enabled) continue;
576
577 lightData.shadowIntensityOffset = light.shadowIntensityOffset;
578
579 if (light.lightType == LightType::Directional) {
580
581 lightData.lightDirection = transformSystem->getLocalToWorld(transformComponent) * glm::vec4(0, 0, -1, 0);
582 lightData.lightDirection = glm::normalize(lightData.lightDirection);
583
584 lightData.lightPosition = glm::vec4(0, 0, 0, 0);
585
586 if (lightData.castShadows) {
587
588 uint32_t framesSinceDirty = context->time->getFrame() - context->engine->getLastDirtyFrame();
589 if (framesSinceDirty <= lightData.maxViewports) {
590 context->engine->triggerUpdate();
591 }
592
593 auto passOptions = &lightData.passOptions;
594 passOptions->setFlag(RenderPassOptions::Flags::NoDepthClip);
595 passOptions->depthBias = light.shadowBias;
596 passOptions->depthSlopedBias = light.shadowSlopedBias;
597 passOptions->depthBiasClamp = light.shadowBiasClamp;
598
599 lightData.textureSize = cascadeShadowResolution;
600 lightData.shadowUpdate = shadowUpdate;
601
602 lightData.maxViewports = 4;
603 lightData.numViewports = lightData.maxViewports;
604 lightData.shadowTexture = cascadeArray;
605 lightData.arrayOffset = layerCount;
606
607 layerCount += lightData.maxViewports;
608 ++cascadeInstances;
609
610 const CameraData * refCamData = nullptr;
611 std::vector<CamState> cameras;
612 getCameras(context, refCamData, cameras, light);
613 auto nearest = calculateSplits(context, lightData, transformSystem->getLocalToWorld(transformComponent), refCamData, cameras, context->variables->get("renderer.maxShadowDistance", 30000.0f));
614
615 for (size_t i = 0; i < lightData.numViewports; ++i) {
616 auto frame = context->time->getFrame();
617 if (lightData.shadowUpdate == ShadowUpdate::Partial) {
618 lightData.frameMod[i] = (uint16_t)lightData.numViewports;
619 lightData.frameOffset[i] = (uint16_t)i;
620 }
621 else if (lightData.shadowUpdate == ShadowUpdate::Static) {
622 lightData.frameMod[i] = 0;
623 lightData.frameOffset[i] = 0;
624 }
625 else if (lightData.shadowUpdate == ShadowUpdate::StaticPartial) {
626 lightData.frameMod[i] = (uint16_t)lightData.numViewports;
627 lightData.frameOffset[i] = (uint16_t)i;
628 }
629 else if (lightData.shadowUpdate == ShadowUpdate::None) {
630 lightData.frameMod[i] = 1;
631 lightData.frameOffset[i] = static_cast<uint16_t>(-1);
632 }
633 else {
634 lightData.frameMod[i] = 0;
635 lightData.frameOffset[i] = 0;
636 }
637 if (lightData.frameMod[i] != 0) {
638 if ((frame % lightData.frameMod[i]) != lightData.frameOffset[i]) {
639 continue;
640 }
641 }
642
643 // Note nearDepth[i] is for cascade i-1, while farDepth[i] is for cascade i.
644 auto n = 0 < i ? lightData.nearDepths[i - 1] : nearest;
645 auto f = lightData.farDepths[i];
646
647 auto frustaPoints = lightData.frustaPointsCapture ? &lightData.frustaPoints[i] : nullptr;
648 if (frustaPoints) {
649 frustaPoints->points.clear();
650 frustaPoints->offsets.clear();
651 }
652
653 glm::mat4 lightView = createLightViewMatrix(lightData.cascadeLine, transformComponent->rotation);
654 glm::uvec2 blueNoiseOffset;
655 glm::mat4 rawCascadeCullMatrix;
656 createFrustumFitMatrix(context, lightData.lightRawProjection[i], rawCascadeCullMatrix, frustaPoints, refCamData, cameras,
657 lightView, lightData.cascadeLine, n, f, frustumSlack, lightData.textureSize, blueNoiseOffset);
658
659 auto cascadeProjectionMatrix = context->renderer->getProjectionMatrix(lightData.lightRawProjection[i]);
660
661 // Set of cascade render view
662 // --------------------------
663 //
664 // Handling of y-flip for GL backends is done when updating engine buffers
665 //
666 CameraData& lightCameraData = lightData.lightCameraData[i];
667
668 lightCameraData.layerMask = RenderLayers::Default;
669
670 lightCameraData.viewMatrix = lightView;
671 lightCameraData.projectionMatrix = cascadeProjectionMatrix;
672
673 lightCameraData.viewProjection = cascadeProjectionMatrix * lightView;
674 lightCameraData.inverseViewMatrix = glm::inverse(lightView);
675 lightCameraData.inverseViewProjectionMatrix = glm::inverse(lightCameraData.viewProjection);
676 lightCameraData.inverseProjectionMatrix = glm::inverse(cascadeProjectionMatrix);
677
678 lightCameraData.rawProjectionMatrix = lightData.lightRawProjection[i];
679 lightCameraData.rawViewProjection = lightData.lightRawProjection[i] * lightView;
680 lightCameraData.rawViewCullMatrix = rawCascadeCullMatrix * lightView;
681
682 lightCameraData.passOptions = passOptions;
683
684 lightCameraData.viewportOrigin = { 0, 0 };
685
686 lightCameraData.viewportSize = { lightData.textureSize , lightData.textureSize };
687 lightCameraData.blueNoiseOffset = blueNoiseOffset;
688
689 // Enable shadow pancaking for cascades.
690 lightCameraData.depthClamp = 0;
691
692 lightCameraData.frustum = Geometry::calculateFrustum<Geometry::Frustum, glm::mat4>(lightCameraData.viewProjection);
693 }
694 }
695 }
696 else if (light.lightType == LightType::Point) {
697
698 if (!useTextureCubeArrays && lightData.castShadows) {
699
700 if (cubeInstances) { // we only support one cube
701 lightData.castShadows = false;
702 }
703 }
704
705 glm::vec3 lightPosition = transformSystem->getLocalToWorld(transformComponent) * glm::vec4(0, 0, 0, 1);
706 lightData.lightDirection = glm::vec4(0, 0, -1, 1);
707 lightData.lightPosition = glm::vec4(lightPosition, 1);
708
709 if (lightData.castShadows) {
710
711 auto passOptions = &lightData.passOptions;
712 passOptions->unsetFlag(RenderPassOptions::Flags::NoDepthClip);
713 passOptions->depthBias = light.shadowBias;
714 passOptions->depthSlopedBias = light.shadowSlopedBias;
715 passOptions->depthBiasClamp = light.shadowBiasClamp;
716
717 lightData.shadowUpdate = shadowUpdate;
718 lightData.shadowTexture = cubeArray;
719 lightData.maxViewports = 6;
720 lightData.numViewports = lightData.maxViewports;
721 lightData.arrayOffset = cubeInstances * lightData.numViewports;
722 ++cubeInstances;
723
724 if (light.shadowNearPlane != 0) {
725 lightData.nearDepths[0] = light.shadowNearPlane;
726 } else {
727 lightData.nearDepths[0] = glm::max(0.1f, light.range / 1000.0f);
728 }
729
730 lightData.farDepths[0] = light.range;
731
732 for (size_t i = 0; i < lightData.numViewports; ++i) {
733 auto frame = context->time->getFrame();
734 if (lightData.shadowUpdate == ShadowUpdate::Partial) {
735 lightData.frameMod[i] = (uint16_t)lightData.numViewports;
736 lightData.frameOffset[i] = (uint16_t)i;
737 }
738 else if (lightData.shadowUpdate == ShadowUpdate::Static) {
739 lightData.frameMod[i] = 0;
740 lightData.frameOffset[i] = 0;
741 }
742 else if (lightData.shadowUpdate == ShadowUpdate::StaticPartial) {
743 lightData.frameMod[i] = (uint16_t)lightData.numViewports;
744 lightData.frameOffset[i] = (uint16_t)i;
745 }
746 else if (lightData.shadowUpdate == ShadowUpdate::None) {
747 lightData.frameMod[i] = 1;
748 lightData.frameOffset[i] = static_cast<uint16_t>(-1);
749 }
750 else {
751 lightData.frameMod[i] = 0;
752 lightData.frameOffset[i] = 0;
753 }
754 if (lightData.frameMod[i] != 0) {
755 if ((frame % lightData.frameMod[i]) != lightData.frameOffset[i]) {
756 continue;
757 }
758 }
759 CameraData& lightCameraData = lightData.lightCameraData[i];
760
761
762 // Calculate point light projection
763 // --------------------------------
764 //
765 // If origin is on botton (GL backends), we flip the Y axis out of the
766 // projection so that we match engine conventions. Note that this flips
767 // orientation so we must flip the winding order used for culling.
768 //
769 glm::mat4 lightProjection = glm::perspective(glm::pi<float>() / 2.0f,
770 1.0f,
771 lightData.nearDepths[0],
772 lightData.farDepths[0]);
773 if (!originOnTop) {
774 lightProjection = glm::mat4(1.f, 0.f, 0.f, 0.f,
775 0.f, -1.f, 0.f, 0.f,
776 0.f, 0.f, 1.f, 0.f,
777 0.f, 0.f, 0.f, 1.f) * lightProjection;
778 lightCameraData.flipWindingOrder = true;
779 }
780 lightProjection = context->renderer->getProjectionMatrix(lightProjection);
781
782 glm::vec3 directions[6] = {
783 glm::vec3(1, 0, 0),
784 glm::vec3(-1, 0, 0),
785 glm::vec3(0, 0, 1),
786 glm::vec3(0, 0, -1),
787 glm::vec3(0, 1, 0),
788 glm::vec3(0, -1, 0),
789 };
790
791 glm::vec3 ups[6] = {
792 glm::vec3(0, 0, 1),
793 glm::vec3(0, 0, 1),
794 glm::vec3(0, -1, 0),
795 glm::vec3(0, 1, 0),
796 glm::vec3(0, 0, 1),
797 glm::vec3(0, 0, 1),
798 };
799
800 const glm::mat4 lightView = glm::lookAt(lightPosition, lightPosition + directions[i], ups[i]);
801
802 lightCameraData.layerMask = RenderLayers::Default;
803 lightCameraData.viewMatrix = lightView;
804 lightCameraData.projectionMatrix = lightProjection;
805 lightCameraData.inverseViewMatrix = glm::inverse(lightView);
806 lightCameraData.viewProjection = lightProjection * lightView;
807 lightCameraData.inverseViewProjectionMatrix = glm::inverse(lightCameraData.viewProjection);
808 lightCameraData.inverseProjectionMatrix = glm::inverse(lightProjection);
809 lightCameraData.rawProjectionMatrix = lightProjection;
810 lightCameraData.rawViewProjection = lightProjection * lightCameraData.viewMatrix;
811 lightCameraData.rawViewCullMatrix = lightCameraData.rawViewProjection;
812 lightCameraData.passOptions = passOptions;
813 lightCameraData.viewportOrigin = { 0, 0 };
814 lightCameraData.viewportSize = { pointShadowResolution, pointShadowResolution };
815 lightCameraData.depthClamp = -std::numeric_limits<float>::max();
816
817 lightCameraData.frustum = Geometry::calculateFrustum<Geometry::Frustum, glm::mat4>(lightCameraData.viewProjection);
818 }
819 }
820 }
821 }
822
823 if (!cascadeInstances) cascadeInstances = 1;
824
825 if (cascadeInstances && (layerCount != currentLayerCount ||
826 cascadeShadowResolution != currentTextureSize ||
827 cascadeShadowFormat != currentShadowFormat))
828 {
829 assert(cascadeArray);
830
831 cascadeArray->description.target = ResourceDimensions::Texture2DArray;
832 cascadeArray->description.layers = layerCount;
833 cascadeArray->description.width = cascadeShadowResolution;
834 cascadeArray->description.height = cascadeShadowResolution;
835 cascadeArray->description.format = cascadeShadowFormat;
836 cascadeArray->description.flags = TextureFlags::DepthBuffer | TextureFlags::Texture;
837 if (layerCount) {
838 cascadeArray->setChanged();
839 }
840 currentLayerCount = layerCount;
841 currentTextureSize = cascadeShadowResolution;
842 currentShadowFormat = cascadeShadowFormat;
843 }
844
845 if (cubeInstances && (cubeInstances != currentCubeCount ||
846 pointShadowResolution != currentPointShadowResolution ||
847 pointShadowFormat != currentPointShadowFormat))
848 {
849 assert(cubeArray);
850 cubeArray->description.target = useTextureCubeArrays ? ResourceDimensions::TextureCubeArray : ResourceDimensions::TextureCube;
851 cubeArray->description.layers = useTextureCubeArrays ? cubeInstances : 1;
852 cubeArray->description.faces = 6;
853 cubeArray->description.width = pointShadowResolution;
854 cubeArray->description.height = pointShadowResolution;
855 cubeArray->description.format = pointShadowFormat;
857 if (cubeInstances) {
858 cubeArray->setChanged();
859 }
860 currentCubeCount = cubeInstances;
861 currentPointShadowResolution = pointShadowResolution;
862 currentPointShadowFormat = pointShadowFormat;
863 }
864
865 for (const auto & light : pool) {
866 auto & lightData = getData(&light);
867 if (!light.enabled) continue;
868 if (!lightData.castShadows) continue;
869
870 if (light.lightType == LightType::Directional) {
871 for (size_t i = 0; i < lightData.numViewports; ++i) {
872 auto & lightCameraData = lightData.lightCameraData[i];
873 context->cullingManager->dispatchCulling(&lightCameraData);
874 }
875 }
876 else if (light.lightType == LightType::Point) {
877 for (size_t i = 0; i < 6; ++i) {
878 auto & lightCameraData = lightData.lightCameraData[i];
879 context->cullingManager->dispatchCulling(&lightCameraData);
880 }
881 }
882 }
883
884}
constexpr void unsetFlag(const uint32_t flag)
Unset the given flag. Does not remove the status of other than the given flags.
Definition: Component.h:371
ComponentType * getComponent() const
Definition: Component.h:159
constexpr void setFlag(const uint32_t flag)
Set the given flags. Does not override the currently set flags.
Definition: Component.h:368
Context * context
Pointer to the Context instance the system lives in.
virtual void initialize(Context *context)
Initialize the system.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class CullingManager > cullingManager
CullingManager instance.
Definition: Context.h:195
class IRenderer * renderer
Renderer.
Definition: Context.h:228
std::unique_ptr< class Bounds > bounds
Bounds service instance.
Definition: Context.h:216
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
std::unique_ptr< class Time > time
Time service instance.
Definition: Context.h:198
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
virtual glm::mat4 getProjectionMatrix(const glm::mat4 projectionMatrix)=0
Get an adjusted projection matrix used to render.
Defines a single light source and its behavior.
LightType lightType
The type of light.
float shadowBias
Constant term shadow map rasterization bias.
bool castShadows
If this light should cast shadows.
bool enabled
If the light is enabled.
float shadowBiasClamp
Shadow map bias rasterization clamp.
float shadowNearPlane
Shadow near plane. If set to 0 near plane is 1/1000th of the radius.
std::vector< WeakEntityPtr > cameras
Cameras from which the shadow maps will be used, defaults to main camera.
WeakEntityPtr lodReference
Camera entity of which its z-axis define the shadowmap cascade split axis, defaults to main camera.
float shadowSlopedBias
Linear term of shadow map rasterization bias.
float shadowIntensityOffset
Shadow intensity offset.
bool tightShadowBounds
If the shadows should have tight bounds.
glm::vec4 lightColor
Color contribution of the light.
bool dynamicCascadeCount
Dynamically determine how many cascades should be drawn.
LightingLayers lightingLayer
The lighting layer the light belongs to.
float range
Falloff range.
void initialize(Context *context) override
Initialize the system.
void preRender(Context *context)
Cull light system.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
glm::quat rotation
Rotation given as a quaternion.
glm::dvec3 getOrigin() const
Gets the Origin offset of the scene.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ Point
Point light source.
@ Directional
Directional light.
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
glm::mat4 rawProjectionMatrix
Projection matrix that has not been adjusted by the renderer, and is thus appropriate for direct scre...
Definition: CameraSystem.h:129
Defines calculated light data.
Definition: LightSystem.h:34
@ DepthBuffer
The texture can be used as a depth target and have depth buffer values written into.
Definition: Flags.h:122
@ Texture
Texture usage, see Default.
Definition: Flags.h:118
@ CubeMap
The texture can be used as a cube map.
Definition: Flags.h:126