improve/simplify "Focus on shadow casters" algorithm

Functionally this shouldn't be too different, but we have some
improvements:
- better detection of "no shadows" cases
- more computations done in light-space, which should result in
  better light frustum.
- spit the code into several static functions
This commit is contained in:
Mathias Agopian
2023-09-14 17:16:14 -07:00
committed by Mathias Agopian
parent 9474798c75
commit da73f89a23
4 changed files with 436 additions and 339 deletions

View File

@@ -170,6 +170,8 @@ struct UTILS_PUBLIC Aabb {
value_type const* data() const { return vertices; }
value_type * data() { return vertices; }
size_t size() const { return 8; }
value_type const& operator[](size_t i) const noexcept { return vertices[i]; }
value_type& operator[](size_t i) noexcept { return vertices[i]; }
value_type vertices[8];
};

View File

@@ -115,145 +115,19 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept {
// Note: we keep the polygon offset even with VSM as it seems to help.
auto& lcm = engine.getLightManager();
FLightManager const& lcm = engine.getLightManager();
FLightManager::Instance const li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
FLightManager::ShadowParams params = lcm.getShadowParams(li);
#ifndef NDEBUG
// LISPSM debugging for directional light (works because we only have one)
const float dz = camera.zf - camera.zn;
float& dzn = engine.debug.shadowmap.dzn;
float& dzf = engine.debug.shadowmap.dzf;
if (dzn < 0) dzn = std::max(0.0f, params.options.shadowNearHint - camera.zn) / dz;
else params.options.shadowNearHint = dzn * dz - camera.zn;
if (dzf > 0) dzf =-std::max(0.0f, camera.zf - params.options.shadowFarHint) / dz;
else params.options.shadowFarHint = dzf * dz + camera.zf;
#endif
const auto direction = params.options.transform * lightData.elementAt<FScene::DIRECTION>(index);
/*
* Compute the light's model matrix
*/
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
// This will be adjusted later because of how we compute the depth metric for VSM.
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
const Aabb wsShadowCastersVolume = sceneInfo.wsShadowCastersVolume;
const Aabb wsShadowReceiversVolume = sceneInfo.wsShadowReceiversVolume;
if (wsShadowCastersVolume.isEmpty() || wsShadowReceiversVolume.isEmpty()) {
mHasVisibleShadows = false;
return {};
}
// view frustum vertices in world-space
float3 wsViewFrustumVertices[8];
const mat4f worldToClipMatrix = camera.cullingProjection * camera.view;
const Frustum wsFrustum(worldToClipMatrix);
computeFrustumCorners(wsViewFrustumVertices, inverse(worldToClipMatrix), sceneInfo.csNearFar);
// we use aligned_storage<> here to avoid the default initialization of std::array<>
std::aligned_storage<sizeof(FrustumBoxIntersection)>::type localStorage; // NOLINT(cppcoreguidelines-pro-type-member-init)
FrustumBoxIntersection& wsClippedShadowReceiverVolume{ reinterpret_cast<FrustumBoxIntersection&>(localStorage) };
// compute the intersection of the shadow receivers' volume with the view volume
// in world space. This returns a set of points on the convex-hull of the intersection.
size_t const vertexCount = intersectFrustumWithBox(wsClippedShadowReceiverVolume,
wsFrustum, wsViewFrustumVertices, wsShadowReceiversVolume);
if (UTILS_UNLIKELY(vertexCount < 4)) {
mHasVisibleShadows = false;
return {};
}
/*
* compute scene zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
* (near/far correspond to max/min because the light looks down the -z axis).
* - The Near plane is set to the shadow casters max z (i.e. closest to the light)
* - The Far plane is set to the closest of the farthest shadow casters and receivers
* i.e.: shadow casters behind the last receivers can't cast any shadows
*
* If "depth clamp" is supported, we can further tighten the near plane to the
* shadow receiver.
*
* Note: L has no influence here, since we're only interested in z values
* (L is a rotation around z)
*/
Aabb lsLightFrustumBounds;
if constexpr (!USE_DEPTH_CLAMP) {
// near plane from shadow caster volume
lsLightFrustumBounds.max.z = sceneInfo.lsNearFar[0];
}
for (size_t i = 0; i < vertexCount; ++i) {
// far: figure out the farthest shadow receivers
float3 const v = mat4f::project(MvAtOrigin, wsClippedShadowReceiverVolume[i]);
lsLightFrustumBounds.min.z = std::min(lsLightFrustumBounds.min.z, v.z);
if constexpr (USE_DEPTH_CLAMP) {
// tighten the shadow receiver volume further
lsLightFrustumBounds.max.z = std::max(lsLightFrustumBounds.max.z, v.z);
}
}
if (engine.debug.shadowmap.far_uses_shadowcasters) {
// far: closest of the farthest shadow casters and receivers
lsLightFrustumBounds.min.z = std::max(lsLightFrustumBounds.min.z, sceneInfo.lsNearFar[1]);
}
// Now that we know the znear (-lsLightFrustumBounds.max.z), adjust the light's position such
// that znear = 0, this is only needed for VSM, but doesn't hurt PCF.
const mat4f Mv = getDirectionalLightViewMatrix(direction, direction * -lsLightFrustumBounds.max.z);
// near / far planes are specified relative to the direction the eye is looking at
// i.e. the -z axis (see: ortho)
const float znear = 0.0f;
const float zfar = lsLightFrustumBounds.max.z - lsLightFrustumBounds.min.z;
// if znear >= zfar, it means we don't have any shadow caster in front of a shadow receiver
if (UTILS_UNLIKELY(znear >= zfar)) {
mHasVisibleShadows = false;
return {};
}
float4 viewVolumeBoundingSphere = {};
if (UTILS_UNLIKELY(params.options.stable)) {
// In stable mode, the light frustum size must be fixed, so we can choose either the
// whole view frustum, or the whole scene bounding volume. We simply pick whichever
// is smaller.
// in stable mode we simply take the shadow receivers volume
const float4 shadowReceiverVolumeBoundingSphere = computeBoundingSphere(
wsShadowReceiversVolume.getCorners().data(), 8);
// in stable mode we simply take the view volume bounding sphere, but we calculate it
// in view space, so that it's perfectly stable.
float3 vertices[8];
computeFrustumCorners(vertices, inverse(camera.cullingProjection), sceneInfo.csNearFar);
viewVolumeBoundingSphere = computeBoundingSphere(vertices, 8);
if (shadowReceiverVolumeBoundingSphere.w < viewVolumeBoundingSphere.w) {
// When using the shadowReceiver volume, we don't have to use its enclosing sphere
// because (we assume) the scene volume doesn't change. Seen from the light it only
// changes when the light moves or rotates, and it is acceptable in that case to have
// non "stable" shadows (the shadow will never be stable when the light moves).
//
// On the other hand, when using the view volume, we must use a sphere because otherwise
// its projection's bounds in light space change with the camera, leading to unstable
// shadows with camera movement.
viewVolumeBoundingSphere.w = 0;
std::copy_n(wsShadowReceiversVolume.getCorners().data(), 8,
wsClippedShadowReceiverVolume.data());
}
}
mHasVisibleShadows = true;
FLightManager::ShadowParams const params = lcm.getShadowParams(li);
// We can't use LISPSM in stable mode
const bool useLispsm = params.options.lispsm && !params.options.stable;
const auto direction = params.options.transform * lightData.elementAt<FScene::DIRECTION>(index);
auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo);
if (UTILS_UNLIKELY(!visibleShadows)) {
return {};
}
/*
* Compute the light's projection matrix
@@ -262,129 +136,48 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// The light's projection, ortho for directional lights, perspective otherwise
const mat4f Mp = directionalLightFrustum(znear, zfar);
const mat4f MpMv(Mp * Mv);
mat4f LMp = Mp;
mat4f LMpMv = Mp * Mv;
/*
* Compute warping (optional, improve quality)
*/
mat4f LMpMv = MpMv;
// Compute the LiSPSM warping
mat4f W, Wp;
mat4f L; // Rotation matrix in light space
mat4f W, Wp, L;
const bool useLispsm = params.options.lispsm && !params.options.stable;
if (useLispsm) {
// Orient the shadow map in the direction of the view vector by constructing a
// rotation matrix in light space around the z-axis, that aligns the y-axis with the camera's
// forward vector (V) -- this gives the wrap direction, vp, for LiSPSM.
const float3 wsCameraFwd = camera.getForwardVector();
const float3 lsCameraFwd = Mv.upperLeft() * wsCameraFwd;
// If the light and view vector are parallel, this rotation becomes
// meaningless. Just use identity.
// (LdotV == (Mv*V).z, because L = {0,0,1} in light-space)
if (UTILS_LIKELY(std::abs(lsCameraFwd.z) < 0.9997f)) { // this is |dot(L, V)|
const float3 vp{ normalize(lsCameraFwd.xy), 0 }; // wrap direction in light-space
L[0].xyz = cross(vp, float3{ 0, 0, 1 });
L[1].xyz = vp;
L[2].xyz = { 0, 0, 1 };
L = transpose(L);
}
LMpMv = L * MpMv;
W = applyLISPSM(Wp, camera, params, LMpMv,
wsClippedShadowReceiverVolume, vertexCount, direction);
const float3 lsCameraFwd = Mv.upperLeft() * camera.getForwardVector();
L = computeLightRotation(lsCameraFwd);
LMp = L * Mp;
LMpMv = LMp * Mv;
W = applyLISPSM(Wp, camera, params,
LMp, Mv, LMpMv, lsClippedShadowVolume, vertexCount, direction);
}
/*
* Compute focusing matrix (optional, greatly improves quality)
*/
// construct the warped light-space
const mat4f WLMpMv = W * LMpMv;
const mat4f WLMp = W * LMp;
// 2) Now we find the x-y bounds of our convex-hull (view volume & shadow receivers)
// in light space, so we can "focus" the shadow map to the interesting area.
// This is the most important step to increase the quality of the shadow map.
//
// In LiPSM mode, we're using the warped space here.
float2 s, o;
if (params.options.stable) {
if (viewVolumeBoundingSphere.w > 0) {
s = 1.0f / viewVolumeBoundingSphere.w;
o = mat4f::project(LMpMv * camera.model, viewVolumeBoundingSphere.xyz).xy;
} else {
Aabb const bounds = compute2DBounds(LMpMv,
wsClippedShadowReceiverVolume.data(), vertexCount);
if (UTILS_UNLIKELY((bounds.min.x >= bounds.max.x) || (bounds.min.y >= bounds.max.y))) {
// this could happen if the only thing visible is a perfectly horizontal or
// vertical thin line
mHasVisibleShadows = false;
return {};
}
assert_invariant(bounds.min.x < bounds.max.x);
assert_invariant(bounds.min.y < bounds.max.y);
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
// Quantize the scale in world-space units. This value can be very small because
// if it wasn't for floating-point imprecision, the scale would be a constant.
double2 const quantizer = 0.0625;
s = 1.0 / (ceil(1.0 / (s * quantizer)) * quantizer);
}
} else {
Aabb const bounds = compute2DBounds(WLMpMv,
wsClippedShadowReceiverVolume.data(), vertexCount);
lsLightFrustumBounds.min.xy = bounds.min.xy;
lsLightFrustumBounds.max.xy = bounds.max.xy;
// For directional lights, we further constraint the light frustum to the
// intersection of the shadow casters & shadow receivers in light-space.
// ** This relies on the 1-texel shadow map border **
if (engine.debug.shadowmap.focus_shadowcasters) {
intersectWithShadowCasters(&lsLightFrustumBounds, WLMpMv, wsShadowCastersVolume);
}
if (UTILS_UNLIKELY((lsLightFrustumBounds.min.x >= lsLightFrustumBounds.max.x) ||
(lsLightFrustumBounds.min.y >= lsLightFrustumBounds.max.y))) {
// this could happen if the only thing visible is a perfectly horizontal or
// vertical thin line
mHasVisibleShadows = false;
return {};
}
assert_invariant(lsLightFrustumBounds.min.x < lsLightFrustumBounds.max.x);
assert_invariant(lsLightFrustumBounds.min.y < lsLightFrustumBounds.max.y);
s = 2.0f / float2(lsLightFrustumBounds.max.xy - lsLightFrustumBounds.min.xy);
o = float2(lsLightFrustumBounds.max.xy + lsLightFrustumBounds.min.xy) * 0.5f;
// TODO: we could quantize `s` here to give some stability when lispsm is disabled,
// however, the quantization paramater should probably be user settable.
}
// adjust offset for scale
o = -s * o;
if (params.options.stable) {
snapLightFrustum(s, o, LMpMv,
sceneInfo.wsShadowCastersVolume.center(), shadowMapInfo.shadowDimension);
}
const mat4f F(mat4f::row_major_init {
s.x, 0.0f, 0.0f, o.x,
0.0f, s.y, 0.0f, o.y,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
const mat4f F = computeFocusMatrix(LMpMv, WLMp,
sceneInfo.wsShadowReceiversVolume,
lsClippedShadowVolume, vertexCount,
camera, sceneInfo.csNearFar,
shadowMapInfo.shadowDimension, params.options.stable);
/*
* Final shadow map transform
*/
// Final shadow transform
const mat4f S = F * WLMpMv;
// Final shadow transform (focused warped light-space)
const mat4f S = F * (W * LMpMv);
// Computes St the transform to use in the shader to access the shadow map texture
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
@@ -392,17 +185,18 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
const mat4f St = math::highPrecisionMultiply(Mt, S);
ShadowMap::ShaderParameters shaderParameters;
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
// note: in texelSizeWorldSpace() below, we can use Mb * Mt * F * W because
// L * Mp * Mv is a rigid transform for directional lights, and doesn't matter.
// if Wp[3][1] is 0, then LISPSM was cancelled.
if (useLispsm && Wp[3][1] != 0.0f) {
shaderParameters.texelSizeAtOneMeterWs = texelSizeWorldSpace(Wp, mat4f(Mt * F),
shadowMapInfo.shadowDimension);
shaderParameters.texelSizeAtOneMeterWs =
texelSizeWorldSpace(Wp, mat4f(Mt * F), shadowMapInfo.shadowDimension);
} else {
// We know we're using an ortho projection
shaderParameters.texelSizeAtOneMeterWs = texelSizeWorldSpace(St.upperLeft(),
shadowMapInfo.shadowDimension);
shaderParameters.texelSizeAtOneMeterWs =
texelSizeWorldSpace(St.upperLeft(), shadowMapInfo.shadowDimension);
}
if (!shadowMapInfo.vsm) {
shaderParameters.lightSpace = St;
@@ -410,8 +204,6 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
shaderParameters.lightSpace = computeVsmLightSpaceMatrix(St, Mv, znear, zfar);
}
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
// We apply the constant bias in world space (as opposed to light-space) to account
// for perspective and lispsm shadow maps. This also allows us to do this at zero-cost
// by baking it in the shadow-map itself.
@@ -427,11 +219,13 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// shader as 'viewFromWorldMatrix', and is used in the VSM case to compute the depth metric.
// (see depth_main.fs). Note that in the case of VSM, 'b' below is identity.
mCamera->setModelMatrix(FCamera::rigidTransformInverse(math::highPrecisionMultiplyd(Mv, b)));
mCamera->setCustomProjection(mat4(Mn * F * W * L * Mp), znear, zfar);
mCamera->setCustomProjection(mat4(Mn * F * WLMp), znear, zfar);
// for the debug camera, we need to undo the world origin
mDebugCamera->setCustomProjection(mat4(S * b * camera.worldOrigin), znear, zfar);
mHasVisibleShadows = true;
return shaderParameters;
}
@@ -514,18 +308,18 @@ ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
// find decent near/far
ShadowMap::updateSceneInfoSpot(Mv, scene, sceneInfo);
} else {
sceneInfo.lsNearFar = { -0.01f, -radius };
sceneInfo.lsCastersNearFar = { -0.01f, -radius };
}
// if the scene was empty, near > far
mHasVisibleShadows = -sceneInfo.lsNearFar[0] < -sceneInfo.lsNearFar[1];
mHasVisibleShadows = -sceneInfo.lsCastersNearFar[0] < -sceneInfo.lsCastersNearFar[1];
if (!mHasVisibleShadows) {
return {};
}
// FIXME: we need a configuration for minimum near plane (for now hardcoded to 1cm)
float const nearPlane = std::max(0.01f, -sceneInfo.lsNearFar[0]);
float const farPlane = std::min(radius, -sceneInfo.lsNearFar[1]);
float const nearPlane = std::max(0.01f, -sceneInfo.lsCastersNearFar[0]);
float const farPlane = std::min(radius, -sceneInfo.lsCastersNearFar[1]);
auto outerConeAngle = lcm.getSpotLightOuterCone(li);
return updatePunctual(Mv, outerConeAngle, nearPlane, farPlane, shadowMapInfo, params);
@@ -559,10 +353,134 @@ ShadowMap::ShaderParameters ShadowMap::updatePoint(FEngine& engine,
return updatePunctual(Mv, 45.0f * f::DEG_TO_RAD, 0.01f, radius, shadowMapInfo, params);
}
ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
FEngine& engine,
float3 const direction,
FLightManager::ShadowParams params,
filament::CameraInfo const& camera,
SceneInfo const& sceneInfo) noexcept {
// we use aligned_storage<> here to avoid the default initialization of std::array<>
using Storage = std::aligned_storage<sizeof(FrustumBoxIntersection)>::type;
Storage localStorage; // NOLINT(cppcoreguidelines-pro-type-member-init)
FrustumBoxIntersection& lsClippedShadowVolume =
reinterpret_cast<FrustumBoxIntersection&>(localStorage);
if (sceneInfo.lsReceiversNearFar[0] <= sceneInfo.lsReceiversNearFar[1]) {
// no visible receivers
return {};
}
if (sceneInfo.lsCastersNearFar[0] <= sceneInfo.lsCastersNearFar[1]) {
// no casters
return {};
}
// early exit if we don't have shadow receivers or casters
Aabb const& wsShadowCastersVolume = sceneInfo.wsShadowCastersVolume;
Aabb const& wsShadowReceiversVolume = sceneInfo.wsShadowReceiversVolume;
if (wsShadowCastersVolume.isEmpty() || wsShadowReceiversVolume.isEmpty()) {
return {};
}
#ifndef NDEBUG
// LISPSM debugging for directional light (works because we only have one)
const float dz = camera.zf - camera.zn;
float& dzn = engine.debug.shadowmap.dzn;
float& dzf = engine.debug.shadowmap.dzf;
if (dzn < 0) dzn = std::max(0.0f, params.options.shadowNearHint - camera.zn) / dz;
else params.options.shadowNearHint = dzn * dz - camera.zn;
if (dzf > 0) dzf =-std::max(0.0f, camera.zf - params.options.shadowFarHint) / dz;
else params.options.shadowFarHint = dzf * dz + camera.zf;
#endif
/*
* Compute the light's model matrix
*/
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
// This will be adjusted later because of how we compute the depth metric for VSM.
mat4f const MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
Aabb lsLightFrustumBounds = computeLightFrustumBounds(
MvAtOrigin, wsShadowReceiversVolume, wsShadowCastersVolume,
sceneInfo,
params.options.stable,
engine.debug.shadowmap.focus_shadowcasters,
engine.debug.shadowmap.far_uses_shadowcasters);
if (UTILS_UNLIKELY(!((lsLightFrustumBounds.min.x < lsLightFrustumBounds.max.x) &&
(lsLightFrustumBounds.min.y < lsLightFrustumBounds.max.y)))) {
return {};
}
// Compute the intersection of the view volume with the intersection of receivers and casters
// in light space. This returns a set of points on the convex-hull of the intersection.
size_t const vertexCount = intersectFrustumWithBox(lsClippedShadowVolume,
camera.cullingProjection * camera.view * FCamera::rigidTransformInverse(MvAtOrigin),
sceneInfo.csNearFar, lsLightFrustumBounds);
if (UTILS_UNLIKELY(vertexCount < 4)) {
return {};
}
/*
* Adjust the scene's zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
* (near/far correspond to max/min because the light looks down the -z axis).
* - The Near plane is set to the shadow casters max z (i.e. closest to the light)
* - The Far plane is set to the closest of the farthest shadow casters and receivers
* i.e.: shadow casters behind the last receivers can't cast any shadows
*
* If "depth clamp" is supported, we can further tighten the near plane to the
* shadow receiver.
*
* Note: L has no influence here, since we're only interested in z values
* (L is a rotation around z)
*/
for (size_t i = 0; i < vertexCount; ++i) {
float3 const v = lsClippedShadowVolume[i];
// far: figure out the farthest shadow receivers
lsLightFrustumBounds.min.z = min(lsLightFrustumBounds.min.z, v.z);
if constexpr (USE_DEPTH_CLAMP) {
lsLightFrustumBounds.max.z = max(lsLightFrustumBounds.max.z, v.z);
}
}
if (engine.debug.shadowmap.far_uses_shadowcasters) {
// far: closest of the farthest shadow casters and receivers
lsLightFrustumBounds.min.z =
std::max(lsLightFrustumBounds.min.z, sceneInfo.lsCastersNearFar[1]);
}
// Now that we know the znear (-lsLightFrustumBounds.max.z), adjust the light's position such
// that znear = 0, this is only needed for VSM, but doesn't hurt PCF.
const mat4f Mv = getDirectionalLightViewMatrix(direction, direction * -lsLightFrustumBounds.max.z);
// near / far planes are specified relative to the direction the eye is looking at
// i.e. the -z axis (see: ortho)
const float znear = 0.0f;
const float zfar = lsLightFrustumBounds.max.z - lsLightFrustumBounds.min.z;
// if znear >= zfar, it means we don't have any shadow caster in front of a shadow receiver
if (UTILS_UNLIKELY(znear >= zfar)) {
return {};
}
// update lsLightFrustum from MvAtOrigin to Mv
for (auto& v : lsClippedShadowVolume) {
v.z -= lsLightFrustumBounds.max.z;
}
return { Mv, znear, zfar, lsClippedShadowVolume, vertexCount, true };
}
mat4f ShadowMap::applyLISPSM(mat4f& Wp,
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
mat4f const& LMp,
mat4f const& Mv,
mat4f const& LMpMv,
FrustumBoxIntersection const& wsShadowReceiversVolume, size_t vertexCount,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
float3 const& dir) {
const float LoV = dot(camera.getForwardVector(), dir);
@@ -575,7 +493,11 @@ mat4f ShadowMap::applyLISPSM(mat4f& Wp,
const float dzf = std::max(0.0f, camera.zf - params.options.shadowFarHint);
// near/far plane's distance from the eye in view space of the shadow receiver volume.
float2 znf = -computeNearFar(camera.view, wsShadowReceiversVolume.data(), vertexCount);
float2 const znf = -computeNearFar(
camera.view * FCamera::rigidTransformInverse(Mv),
lsShadowVolume.data(), vertexCount);
const float zn = std::max(camera.zn, znf[0]); // near plane distance from the eye
const float zf = std::min(camera.zf, znf[1]); // far plane distance from the eye
@@ -584,7 +506,7 @@ mat4f ShadowMap::applyLISPSM(mat4f& Wp,
// i.e. the axis orthogonal to the light direction) and taking the min/max
// of the shadow receivers' volume.
// Note: znear/zfar encoded in Mp has no influence here (b/c we're interested only by the y-axis)
const float2 nf = computeNearFarOfWarpSpace(LMpMv, wsShadowReceiversVolume.data(), vertexCount);
const float2 nf = computeNearFarOfWarpSpace(LMp, lsShadowVolume.data(), vertexCount);
const float n = nf[0]; // near plane coordinate of Mp (light space)
const float f = nf[1]; // far plane coordinate of Mp (light space)
const float d = std::abs(f - n); // Wp's depth-range d (abs necessary because we're dealing with z-coordinates, not distances)
@@ -636,6 +558,112 @@ mat4f ShadowMap::applyLISPSM(mat4f& Wp,
return W;
}
math::mat4f ShadowMap::computeLightRotation(math::float3 const& lsDirection) noexcept {
mat4f L;
// Orient the shadow map in the direction of the view vector by constructing a
// rotation matrix in light space around the z-axis, that aligns the y-axis with the camera's
// forward vector (V) -- this gives the wrap direction, vp, for LiSPSM.
// If the light and view vector are parallel, this rotation becomes
// meaningless. Just use identity.
// (LdotV == (Mv*V).z, because L = {0,0,1} in light-space)
if (UTILS_LIKELY(std::abs(lsDirection.z) < 0.9997f)) { // this is |dot(L, V)|
const float3 vp{ normalize(lsDirection.xy), 0 }; // wrap direction in light-space
L[0].xyz = cross(vp, float3{ 0, 0, 1 });
L[1].xyz = vp;
L[2].xyz = { 0, 0, 1 };
L = transpose(L);
}
return L;
}
math::mat4f ShadowMap::computeFocusMatrix(
const mat4f& LMpMv, const mat4f& WLMp,
Aabb const& wsShadowReceiversVolume,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
filament::CameraInfo const& camera, float2 const& csNearFar,
uint16_t shadowDimension, bool stable) noexcept {
float2 s, o;
float4 wsViewVolumeBoundingSphere = {};
if (stable) {
// In stable mode, the light frustum size must be fixed, so we can choose either the
// whole view frustum, or the whole scene bounding volume. We simply pick whichever
// is smaller.
// in stable mode we simply take the shadow receivers volume
const float4 shadowReceiverVolumeBoundingSphere = computeBoundingSphere(
wsShadowReceiversVolume.getCorners().data(), 8);
// in stable mode we simply take the view volume bounding sphere, but we calculate it
// in view space, so that it's perfectly stable.
mat4f const viewFromClip = inverse(camera.cullingProjection);
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip, csNearFar);
wsViewVolumeBoundingSphere = computeBoundingSphere(wsFrustumVertices.vertices, 8);
if (shadowReceiverVolumeBoundingSphere.w < wsViewVolumeBoundingSphere.w) {
// When using the shadowReceiver volume, we don't have to use its enclosing sphere
// because (we assume) the scene volume doesn't change. Seen from the light it only
// changes when the light moves or rotates, and it is acceptable in that case to have
// non "stable" shadows (the shadow will never be stable when the light moves).
//
// On the other hand, when using the view volume, we must use a sphere because otherwise
// its projection's bounds in light space change with the camera, leading to unstable
// shadows with camera movement.
wsViewVolumeBoundingSphere.w = 0;
}
if (wsViewVolumeBoundingSphere.w > 0) {
s = 1.0f / wsViewVolumeBoundingSphere.w;
o = mat4f::project(LMpMv * camera.model, wsViewVolumeBoundingSphere.xyz).xy;
} else {
// TODO: another options is the sphere around the intersections of receiver & casters
// FIXME: this is not stable with the global rotation because wsShadowReceiversVolume
// is not stable with it.
Aabb const bounds = compute2DBounds(LMpMv,
wsShadowReceiversVolume.getCorners().data(),
wsShadowReceiversVolume.getCorners().size());
assert_invariant(bounds.min.x < bounds.max.x);
assert_invariant(bounds.min.y < bounds.max.y);
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
// Quantize the scale in world-space units. This value can be very small because
// if it wasn't for floating-point imprecision, the scale would be a constant.
double2 const quantizer = 0.0625;
s = 1.0 / (ceil(1.0 / (s * quantizer)) * quantizer);
}
} else {
Aabb const bounds = compute2DBounds(WLMp, lsShadowVolume.data(), vertexCount);
assert_invariant(bounds.min.x < bounds.max.x);
assert_invariant(bounds.min.y < bounds.max.y);
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
// TODO: we could quantize `s` here to give some stability when lispsm is disabled,
// however, the quantization paramater should probably be user settable.
}
// adjust offset for scale
o = -s * o;
if (stable) {
snapLightFrustum(s, o, LMpMv, wsShadowReceiversVolume.center(), shadowDimension);
}
const mat4f F(mat4f::row_major_init {
s.x, 0.0f, 0.0f, o.x,
0.0f, s.y, 0.0f, o.y,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
return F;
}
// Apply these remapping in double to maintain a high precision for the depth axis
ShadowMap::TextureCoordsMapping ShadowMap::getTextureCoordsMapping(ShadowMapInfo const& info,
@@ -747,7 +775,7 @@ float4 ShadowMap::computeBoundingSphere(float3 const* vertices, size_t count) no
for (size_t i = 0; i < count; i++) {
s.xyz += vertices[i];
}
s.xyz *= 1.0f / count;
s.xyz *= 1.0f / float(count);
for (size_t i = 0; i < count; i++) {
s.w = std::max(s.w, length2(vertices[i] - s.xyz));
}
@@ -758,71 +786,89 @@ float4 ShadowMap::computeBoundingSphere(float3 const* vertices, size_t count) no
Aabb ShadowMap::compute2DBounds(const mat4f& lightView,
float3 const* wsVertices, size_t count) noexcept {
Aabb bounds{};
// disable vectorization here because vertexCount is <= 64, not worth the increased code size.
#pragma clang loop vectorize(disable)
for (size_t i = 0; i < count; ++i) {
const float3 v = mat4f::project(lightView, wsVertices[i]);
utils::Slice<float3> const vertices{ wsVertices, count };
for (auto const& vertice : vertices) {
const float3 v = mat4f::project(lightView, vertice);
bounds.min.xy = min(bounds.min.xy, v.xy);
bounds.max.xy = max(bounds.max.xy, v.xy);
}
return bounds;
}
void ShadowMap::intersectWithShadowCasters(Aabb* UTILS_RESTRICT lightFrustum,
mat4f const& lightView, Aabb const& wsShadowCastersVolume) noexcept {
// construct the Focus transform (scale + offset)
const float2 s = 2.0f / float2(lightFrustum->max.xy - lightFrustum->min.xy);
const float2 o = -s * float2(lightFrustum->max.xy + lightFrustum->min.xy) * 0.5f;
const mat4f F(mat4f::row_major_init {
s.x, 0.0f, 0.0f, o.x,
0.0f, s.y, 0.0f, o.y,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
float3 wsLightFrustumCorners[8];
const mat4f projection = F * lightView;
const Frustum wsLightFrustum(projection);
computeFrustumCorners(wsLightFrustumCorners, inverse(projection));
// Intersect shadow-caster AABB with current light frustum in world-space:
//
// This (obviously) guarantees that the resulting volume is inside the light frustum;
// when using LiSPSM (or projection lights, i.e. when lightView is a projection), we must
// first intersect wsShadowCastersVolume with the light's frustum, otherwise we end-up
// transforming vertices that are "outside" the frustum, and that's forbidden.
FrustumBoxIntersection wsClippedShadowCasterVolumeVertices;
size_t const vertexCount = intersectFrustumWithBox(wsClippedShadowCasterVolumeVertices,
wsLightFrustum, wsLightFrustumCorners, wsShadowCastersVolume);
// compute shadow-caster bounds in light space
Aabb const box = compute2DBounds(lightView, wsClippedShadowCasterVolumeVertices.data(), vertexCount);
// intersect shadow-caster and current light frustum bounds
lightFrustum->min.xy = max(box.min.xy, lightFrustum->min.xy);
lightFrustum->max.xy = min(box.max.xy, lightFrustum->max.xy);
Aabb ShadowMap::compute2DBounds(const mat4f& lightView,
Aabb const& volume) noexcept {
Aabb bounds{};
auto const& corners = volume.getCorners();
for (auto const& corner : corners) {
const float3 v = mat4f::project(lightView, corner);
bounds.min = min(bounds.min, v);
bounds.max = max(bounds.max, v);
}
return bounds;
}
void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
const mat4f& UTILS_RESTRICT projectionViewInverse, float2 csNearFar) noexcept {
ShadowMap::Corners ShadowMap::computeFrustumCorners(
mat4f const& projectionInverse,
float2 csNearFar) noexcept {
Corners out;
// compute view frustum in world space (from its NDC)
// matrix to convert: ndc -> camera -> world
float near = csNearFar.x;
float far = csNearFar.y;
float3 const csViewFrustumCorners[8] = {
{ -1, -1, far },
{ 1, -1, far },
{ -1, 1, far },
{ 1, 1, far },
{ -1, -1, near },
{ 1, -1, near },
{ -1, 1, near },
{ 1, 1, near },
float const near = csNearFar.x;
float const far = csNearFar.y;
Corners const csViewFrustumCorners = {
.vertices = {
{ -1, -1, far },
{ 1, -1, far },
{ -1, 1, far },
{ 1, 1, far },
{ -1, -1, near },
{ 1, -1, near },
{ -1, 1, near },
{ 1, 1, near },
}
};
for (float3 const c : csViewFrustumCorners) {
*out++ = mat4f::project(projectionViewInverse, c);
for (size_t i = 0; i < csViewFrustumCorners.size(); i++) {
out[i] = mat4f::project(projectionInverse, csViewFrustumCorners[i]);
}
return out;
}
Aabb ShadowMap::computeLightFrustumBounds(mat4f const& lightView,
Aabb const& wsShadowReceiversVolume, Aabb const& wsShadowCastersVolume,
ShadowMap::SceneInfo const& sceneInfo, bool stable, bool focusShadowCasters,
bool farUsesShadowCasters) noexcept {
Aabb lsLightFrustumBounds{};
float const receiversFar = sceneInfo.lsReceiversNearFar[1];
float const castersNear = sceneInfo.lsCastersNearFar[0];
float const castersFar = sceneInfo.lsCastersNearFar[1];
Aabb const lsReceiversBounds = compute2DBounds(lightView, wsShadowReceiversVolume);
// far plane from receivers
lsLightFrustumBounds.min = { lsReceiversBounds.min.xy, receiversFar };
// near plane from casters
lsLightFrustumBounds.max = { lsReceiversBounds.max.xy, castersNear };
if (UTILS_LIKELY(!stable && focusShadowCasters)) {
// For directional lights, we further constraint the light frustum to the
// intersection of the shadow casters & shadow receivers in light-space.
// ** This relies on the 1-texel shadow map border **
Aabb const lsCastersBounds = compute2DBounds(lightView, wsShadowCastersVolume);
// intersection of in light space of casters and receiver
lsLightFrustumBounds.min.xy = max(lsLightFrustumBounds.min.xy, lsCastersBounds.min.xy);
lsLightFrustumBounds.max.xy = min(lsLightFrustumBounds.max.xy, lsCastersBounds.max.xy);
}
if (farUsesShadowCasters) {
// far: closest of the farthest shadow casters and receivers
lsLightFrustumBounds.min.z = max(lsLightFrustumBounds.min.z, castersFar);
}
return lsLightFrustumBounds;
}
void ShadowMap::snapLightFrustum(float2& s, float2& o,
@@ -863,10 +909,12 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
size_t ShadowMap::intersectFrustumWithBox(
FrustumBoxIntersection& UTILS_RESTRICT outVertices,
Frustum const& UTILS_RESTRICT wsFrustum,
float3 const* UTILS_RESTRICT wsFrustumCorners,
Aabb const& UTILS_RESTRICT wsBox)
mat4f const& UTILS_RESTRICT projection, math::float2 const& UTILS_RESTRICT csNearFar,
Aabb const& UTILS_RESTRICT box)
{
Frustum const frustum{ projection };
Corners const frustumVertices{ computeFrustumCorners(inverse(projection), csNearFar) };
constexpr const float EPSILON = 1.0f / 8192.0f; // ~0.012 mm
size_t vertexCount = 0;
@@ -883,16 +931,16 @@ size_t ShadowMap::intersectFrustumWithBox(
*/
// world-space scene volume
const Aabb::Corners wsSceneReceiversCorners = wsBox.getCorners();
const Aabb::Corners boxCorners = box.getCorners();
// a) Keep the frustum's vertices that are known to be inside the scene's box
UTILS_NOUNROLL
for (size_t i = 0; i < 8; i++) {
float3 const p = wsFrustumCorners[i];
float3 const p = frustumVertices[i];
outVertices[vertexCount] = p;
if ((p.x >= wsBox.min.x && p.x <= wsBox.max.x) &&
(p.y >= wsBox.min.y && p.y <= wsBox.max.y) &&
(p.z >= wsBox.min.z && p.z <= wsBox.max.z)) {
if ((p.x >= box.min.x && p.x <= box.max.x) &&
(p.y >= box.min.y && p.y <= box.max.y) &&
(p.z >= box.min.z && p.z <= box.max.z)) {
vertexCount++;
}
}
@@ -900,7 +948,7 @@ size_t ShadowMap::intersectFrustumWithBox(
// at this point if we have 8 vertices, we can skip the rest
if (vertexCount < 8) {
const size_t frustumVerticesInsideBoxCount = vertexCount;
float4 const* wsFrustumPlanes = wsFrustum.getNormalizedPlanes();
float4 const* frustumPlanes = frustum.getNormalizedPlanes();
// b) add the scene's vertices that are known to be inside the view frustum
//
@@ -908,14 +956,14 @@ size_t ShadowMap::intersectFrustumWithBox(
// the frustum. This actually often happens due to fitting light-space
// We fudge the distance to the plane by a small amount.
#pragma nounroll
for (float3 const p : wsSceneReceiversCorners) {
for (float3 const p : boxCorners) {
outVertices[vertexCount] = p;
float const l = dot(wsFrustumPlanes[0].xyz, p) + wsFrustumPlanes[0].w;
float const b = dot(wsFrustumPlanes[1].xyz, p) + wsFrustumPlanes[1].w;
float const r = dot(wsFrustumPlanes[2].xyz, p) + wsFrustumPlanes[2].w;
float const t = dot(wsFrustumPlanes[3].xyz, p) + wsFrustumPlanes[3].w;
float const f = dot(wsFrustumPlanes[4].xyz, p) + wsFrustumPlanes[4].w;
float const n = dot(wsFrustumPlanes[5].xyz, p) + wsFrustumPlanes[5].w;
float const l = dot(frustumPlanes[0].xyz, p) + frustumPlanes[0].w;
float const b = dot(frustumPlanes[1].xyz, p) + frustumPlanes[1].w;
float const r = dot(frustumPlanes[2].xyz, p) + frustumPlanes[2].w;
float const t = dot(frustumPlanes[3].xyz, p) + frustumPlanes[3].w;
float const f = dot(frustumPlanes[4].xyz, p) + frustumPlanes[4].w;
float const n = dot(frustumPlanes[5].xyz, p) + frustumPlanes[5].w;
if ((l <= EPSILON) && (b <= EPSILON) &&
(r <= EPSILON) && (t <= EPSILON) &&
(f <= EPSILON) && (n <= EPSILON)) {
@@ -968,11 +1016,11 @@ size_t ShadowMap::intersectFrustumWithBox(
if (boxVerticesInsideFrustumCount < 8) {
// c) intersect scene's volume edges with frustum planes
vertexCount = intersectFrustum(outVertices.data(), vertexCount,
wsSceneReceiversCorners.vertices, wsFrustumCorners);
boxCorners.vertices, frustumVertices.vertices);
// d) intersect frustum edges with the scene's volume planes
vertexCount = intersectFrustum(outVertices.data(), vertexCount,
wsFrustumCorners, wsSceneReceiversCorners.vertices);
frustumVertices.vertices, boxCorners.vertices);
} else {
// by construction vertexCount should be 8 here, but it can be more because
// step (b) above can classify a point as inside the frustum that isn't quite inside.
@@ -1203,14 +1251,22 @@ void ShadowMap::updateSceneInfoDirectional(mat4f const& Mv, FScene const& scene,
// the code below only works with affine transforms
assert_invariant(transpose(Mv)[3] == float4(0, 0, 0, 1));
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.lsCastersNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.lsReceiversNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster, Culler::result_type) {
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, r.max.z); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, r.min.z); // far
sceneInfo.lsCastersNearFar.x = max(sceneInfo.lsCastersNearFar.x, r.max.z);
sceneInfo.lsCastersNearFar.y = min(sceneInfo.lsCastersNearFar.y, r.min.z);
},
[&](Aabb receiver, Culler::result_type) {
[&](Aabb receiver, Culler::result_type vis) {
// account only for objects that are visible by the camera
auto mask = 1u << VISIBLE_RENDERABLE_BIT;
if ((vis & mask) == mask) {
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, receiver);
sceneInfo.lsReceiversNearFar.x = max(sceneInfo.lsReceiversNearFar.x, r.max.z);
sceneInfo.lsReceiversNearFar.y = min(sceneInfo.lsReceiversNearFar.y, r.min.z);
}
}
);
}
@@ -1221,13 +1277,15 @@ void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
// the code below only works with affine transforms
assert_invariant(transpose(Mv)[3] == float4(0, 0, 0, 1));
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.lsCastersNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
// account only for objects that are visible by both the camera and the light
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster, Culler::result_type mask) {
if (mask & VISIBLE_DYN_SHADOW_RENDERABLE) {
[&](Aabb caster, Culler::result_type vis) {
auto mask = VISIBLE_DYN_SHADOW_RENDERABLE;
if ((vis & mask) == mask) {
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, r.max.z); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, r.min.z); // far
sceneInfo.lsCastersNearFar.x = std::max(sceneInfo.lsCastersNearFar.x, r.max.z); // near
sceneInfo.lsCastersNearFar.y = std::min(sceneInfo.lsCastersNearFar.y, r.min.z); // far
}
},
[&](Aabb receiver, Culler::result_type) {

View File

@@ -26,6 +26,7 @@
#include <backend/DriverApiForward.h>
#include <filament/Box.h>
#include <filament/Viewport.h>
#include <math/mat4.h>
@@ -104,7 +105,8 @@ public:
// scratch data: light's near/far expressed in light-space, calculated from the scene's
// content assuming the light is at the origin.
math::float2 lsNearFar;
math::float2 lsCastersNearFar;
math::float2 lsReceiversNearFar;
// Viewing camera's near/far expressed in view-space, calculated from the
// scene's content.
@@ -207,6 +209,8 @@ private:
uint8_t v0, v1, v2, v3;
};
using Corners = Aabb::Corners;
// 8 corners, 12 segments w/ 2 intersection max -- all of this twice (8 + 12 * 2) * 2 (768 bytes)
using FrustumBoxIntersection = std::array<math::float3, 64>;
@@ -215,17 +219,50 @@ private:
const ShadowMapInfo& shadowMapInfo,
const FLightManager::ShadowParams& params) noexcept;
struct DirectionalShadowBounds {
math::mat4f Mv;
float zNear;
float zFar;
FrustumBoxIntersection lsClippedShadowVolume;
size_t vertexCount;
bool visibleShadows = false;
};
static DirectionalShadowBounds computeDirectionalShadowBounds(
FEngine& engine,
math::float3 direction,
FLightManager::ShadowParams params,
filament::CameraInfo const& camera,
SceneInfo const& sceneInfo) noexcept;
static math::mat4f applyLISPSM(math::mat4f& Wp,
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
const math::mat4f& LMp,
const math::mat4f& Mv,
const math::mat4f& LMpMv,
FrustumBoxIntersection const& wsShadowReceiverVolume, size_t vertexCount,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
const math::float3& dir);
static inline math::mat4f computeLightRotation(math::float3 const& lsDirection) noexcept;
static inline math::mat4f computeFocusMatrix(
const math::mat4f& LMpMv,
const math::mat4f& WLMp,
Aabb const& wsShadowReceiversVolume,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
filament::CameraInfo const& camera, math::float2 const& csNearFar,
uint16_t shadowDimension, bool stable) noexcept;
static inline void snapLightFrustum(math::float2& s, math::float2& o,
math::mat4f const& Mv, math::double3 wsSnapCoords, math::int2 resolution) noexcept;
static inline void computeFrustumCorners(math::float3* out,
const math::mat4f& projectionViewInverse, math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
static inline Aabb computeLightFrustumBounds(const math::mat4f& lightView,
Aabb const& wsShadowReceiversVolume, Aabb const& wsShadowCastersVolume,
SceneInfo const& sceneInfo,
bool stable, bool focusShadowCasters, bool farUsesShadowCasters) noexcept;
static Corners computeFrustumCorners(const math::mat4f& projectionInverse,
math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
static inline math::float2 computeNearFar(math::mat4f const& view,
math::float3 const* wsVertices, size_t count) noexcept;
@@ -240,9 +277,8 @@ private:
static inline Aabb compute2DBounds(const math::mat4f& lightView,
math::float3 const* wsVertices, size_t count) noexcept;
static inline void intersectWithShadowCasters(Aabb* lightFrustum,
math::mat4f const& lightView,
Aabb const& wsShadowCastersVolume) noexcept;
static inline Aabb compute2DBounds(const math::mat4f& lightView,
Aabb const& volume) noexcept;
static inline math::float2 computeNearFarOfWarpSpace(math::mat4f const& lightView,
math::float3 const* wsVertices, size_t count) noexcept;
@@ -261,9 +297,8 @@ private:
static size_t intersectFrustumWithBox(
FrustumBoxIntersection& outVertices,
Frustum const& wsFrustum,
math::float3 const* wsFrustumCorners,
Aabb const& wsBox);
math::mat4f const& projection, math::float2 const& csNearFar,
Aabb const& box);
static math::mat4f warpFrustum(float n, float f) noexcept;

View File

@@ -761,6 +761,8 @@ int main(int argc, char** argv) {
ImGui::Checkbox("Camera at origin", debug.getPropertyAddress<bool>("d.view.camera_at_origin"));
ImGui::Checkbox("Far Origin", &app.originIsFarAway);
ImGui::SliderFloat("Origin", &app.originDistance, 0, 10000000);
ImGui::Checkbox("Far uses shadow casters", debug.getPropertyAddress<bool>("d.shadowmap.far_uses_shadowcasters"));
ImGui::Checkbox("Focus shadow casters", debug.getPropertyAddress<bool>("d.shadowmap.focus_shadowcasters"));
auto dataSource = debug.getDataSource("d.view.frame_info");
if (dataSource.data) {
ImGuiExt::PlotLinesSeries("FrameInfo", 6,