Revert "fix a couple shadow stability bugs"

This reverts commit 1b0db0fca2.
This commit is contained in:
Benjamin Doherty
2023-11-08 12:33:24 -08:00
parent 13571868de
commit 763950ec18
16 changed files with 222 additions and 281 deletions

View File

@@ -15,7 +15,6 @@ Instead, if you are authoring a PR for the main branch, add your release note to
- engine: New tone mapper: `AgXTonemapper`.
- matinfo: Add support for viewing ESSL 1.0 shaders
- engine: Add `Renderer::getClearOptions()` [b/243846268]
- engine: Fix stable shadows (again) when an IBL rotation is used
## v1.45.0

View File

@@ -60,7 +60,7 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction,
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.clipFromWorldMatrix[0] = clipFromWorld; // projection * view
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform));
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
s.clipTransform = camera.clipTransform;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);

View File

@@ -75,7 +75,7 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
s.clipFromViewMatrix = clipFromView; // projection
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform));
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
s.clipTransform = camera.clipTransform;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
@@ -151,7 +151,7 @@ void PerViewUniforms::prepareFog(FEngine& engine, const CameraInfo& cameraInfo,
// why we store the cofactor matrix.
mat4f const viewFromWorld = cameraInfo.view;
mat4 const worldFromUserWorld = cameraInfo.worldTransform;
mat4 const worldFromUserWorld = cameraInfo.worldOrigin;
mat4 const worldFromFog = worldFromUserWorld * userWorldFromFog;
mat4 const viewFromFog = viewFromWorld * worldFromFog;

View File

@@ -77,13 +77,20 @@ void ShadowMap::initialize(size_t lightIndex, ShadowType shadowType,
mFace = face;
}
math::mat4f ShadowMap::getDirectionalLightViewMatrix(math::float3 direction, math::float3 up,
math::float3 position) noexcept {
// 1. we use the x-axis as the "up" reference so that the math is stable when the light
// is pointing down, which is a common case for lights.
// 2. we do the math in double to avoid some precision issues when the light is almost
// straight (i.e. parallel to the x-axis)
mat4f const Mm = mat4f{ mat4::lookTo(direction, position, up) };
mat4f ShadowMap::getDirectionalLightViewMatrix(float3 direction, float3 position) noexcept {
auto z_axis = direction;
auto norm_up = float3{ 0, 1, 0 };
if (UTILS_UNLIKELY(std::abs(dot(z_axis, norm_up)) > 0.999f)) {
// Fix up vector if we're degenerate (looking straight up, basically)
norm_up = { norm_up.z, norm_up.x, norm_up.y };
}
auto x_axis = normalize(cross(z_axis, norm_up));
auto y_axis = cross(x_axis, z_axis);
const mat4f Mm{
float4{ x_axis, 0 },
float4{ y_axis, 0 },
float4{ -z_axis, 0 },
float4{ position, 1 }};
return FCamera::rigidTransformInverse(Mm);
}
@@ -98,7 +105,7 @@ math::mat4f ShadowMap::getPointLightViewMatrix(backend::TextureCubemapFace face,
case TextureCubemapFace::POSITIVE_Z: direction = { 0, 0, 1 }; break;
case TextureCubemapFace::NEGATIVE_Z: direction = { 0, 0, -1 }; break;
}
const mat4f Mv = getDirectionalLightViewMatrix(direction, { 0, 1, 0 }, position);
const mat4f Mv = getDirectionalLightViewMatrix(direction, position);
return Mv;
}
@@ -113,7 +120,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
FLightManager::ShadowParams const params = lcm.getShadowParams(li);
// We can't use LISPSM in stable mode
const auto direction = lightData.elementAt<FScene::SHADOW_DIRECTION>(index);
const auto direction = params.options.transform * lightData.elementAt<FScene::DIRECTION>(index);
auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo);
@@ -159,22 +166,11 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// This is the most important step to increase the quality of the shadow map.
//
// In LiPSM mode, we're using the warped space here.
float4 f = computeFocusParams(LMpMv, WLMp,
const mat4f F = computeFocusMatrix(LMpMv, WLMp,
sceneInfo.wsShadowReceiversVolume,
lsClippedShadowVolume, vertexCount,
camera, sceneInfo.csNearFar,
params.options.shadowFar, params.options.stable);
if (params.options.stable) {
const auto lsRef = lightData.elementAt<FScene::SHADOW_REF>(index);
snapLightFrustum(f.xy, f.zw, lsRef, shadowMapInfo.shadowDimension);
}
const mat4f F(mat4f::row_major_init {
f.x, 0.0f, 0.0f, f.z,
0.0f, f.y, 0.0f, f.w,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
shadowMapInfo.shadowDimension, params.options.stable);
/*
* Final shadow map transform
@@ -226,7 +222,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
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.worldTransform), znear, zfar);
mDebugCamera->setCustomProjection(mat4(S * b * camera.worldOrigin), znear, zfar);
mHasVisibleShadows = true;
@@ -301,7 +297,7 @@ ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
auto radius = lightData.elementAt<FScene::POSITION_RADIUS>(index).w;
auto li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
const FLightManager::ShadowParams& params = lcm.getShadowParams(li);
const mat4f Mv = getDirectionalLightViewMatrix(direction, { 0, 1, 0 }, position);
const mat4f Mv = getDirectionalLightViewMatrix(direction, position);
// We only keep this for reference. updateSceneInfoSpot() is quite expensive on large scenes
// currently, and only needed to find a near/far. Instead, we just use a small near and the
@@ -405,8 +401,7 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
// 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,
normalize(camera.worldTransform[0].xyz));
mat4f const MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
Aabb lsLightFrustumBounds = computeLightFrustumBounds(
@@ -460,6 +455,10 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
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;
@@ -474,11 +473,6 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
v.z -= lsLightFrustumBounds.max.z;
}
// 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, normalize(camera.worldTransform[0].xyz),
direction * -lsLightFrustumBounds.max.z);
return { Mv, znear, zfar, lsClippedShadowVolume, vertexCount, true };
}
@@ -583,36 +577,65 @@ math::mat4f ShadowMap::computeLightRotation(math::float3 const& lsDirection) noe
return L;
}
math::float4 ShadowMap::computeFocusParams(
mat4f const& LMpMv,
mat4f const& WLMp,
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,
float shadowFar, bool stable) noexcept {
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 choose the
// whole view frustum.
// We simply take the view volume bounding sphere, but we calculate it
// 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);
auto getViewVolumeBoundingSphere = [&]() {
if (shadowFar > 0) {
float4 const wsViewVolumeBoundingSphere = { camera.getPosition(), shadowFar };
return wsViewVolumeBoundingSphere;
} else {
mat4f const viewFromClip = inverse(camera.cullingProjection);
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip, csNearFar);
float4 const wsViewVolumeBoundingSphere =
computeBoundingSphere(wsFrustumVertices.vertices, 8);
return wsViewVolumeBoundingSphere;
}
};
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.
float4 const wsViewVolumeBoundingSphere = getViewVolumeBoundingSphere();
s = 1.0f / wsViewVolumeBoundingSphere.w;
o = mat4f::project(LMpMv * camera.model, wsViewVolumeBoundingSphere.xyz).xy;
o = -s * o;
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);
@@ -620,11 +643,29 @@ math::float4 ShadowMap::computeFocusParams(
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
o = -s * o;
// TODO: we could quantize `s` here to give some stability when lispsm is disabled,
// however, the quantization paramater should probably be user settable.
}
return { s, o };
// 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,
backend::Viewport const& viewport) noexcept {
@@ -638,8 +679,6 @@ ShadowMap::TextureCoordsMapping ShadowMap::getTextureCoordsMapping(ShadowMapInfo
0.0f, 0.0f, 0.0f, 1.0f
}};
constexpr mat4f MtInverse = inverse(Mt);
// apply the viewport transform
const float2 o = float2{ viewport.left, viewport.bottom } / float(info.atlasDimension);
const float2 s = float2{ viewport.width, viewport.height } / float(info.atlasDimension);
@@ -661,7 +700,7 @@ ShadowMap::TextureCoordsMapping ShadowMap::getTextureCoordsMapping(ShadowMapInfo
}} : mat4f{};
// Compute shadow-map texture access and viewport transform
return { Mf * (Mv * Mt), MtInverse * (Mv * Mt) };
return { Mf * (Mv * Mt), inverse(Mt) * (Mv * Mt) };
}
mat4f ShadowMap::computeVsmLightSpaceMatrix(const mat4f& lightSpacePcf,
@@ -802,8 +841,8 @@ ShadowMap::Corners ShadowMap::computeFrustumCorners(
Aabb ShadowMap::computeLightFrustumBounds(mat4f const& lightView,
Aabb const& wsShadowReceiversVolume, Aabb const& wsShadowCastersVolume,
ShadowMap::SceneInfo const& sceneInfo,
bool stable, bool focusShadowCasters, bool farUsesShadowCasters) noexcept {
ShadowMap::SceneInfo const& sceneInfo, bool stable, bool focusShadowCasters,
bool farUsesShadowCasters) noexcept {
Aabb lsLightFrustumBounds{};
float const receiversFar = sceneInfo.lsReceiversNearFar[1];
@@ -834,13 +873,13 @@ Aabb ShadowMap::computeLightFrustumBounds(mat4f const& lightView,
}
void ShadowMap::snapLightFrustum(float2& s, float2& o,
double2 lsRef, int2 resolution) noexcept {
mat4f const& Mv, double3 wsSnapCoords, int2 resolution) noexcept {
auto proj2 = [](mat4 m, double2 v) -> double2 {
double2 p;
p.x = dot(double2{ m[0].x, m[1].x }, v) + m[3].x;
p.y = dot(double2{ m[0].y, m[1].y }, v) + m[3].y;
return p;
auto proj = [](mat4 m, double3 v) -> double3 {
// for directional light p.w == 1, exactly
auto p = m * v;
assert_invariant(p.w == 1.0);
return p.xyz;
};
auto fract = [](auto v) {
@@ -857,13 +896,13 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
});
// The (resolution * 0.5) comes from Mv having a NDC in the range -1,1 (so a range of 2).
// Another (resolution * 0.5) is there to snap on even texels, which helps with debugging
// focused light-space
mat4 const FMv{ F * Mv };
// This offsets the texture coordinates, so it has a fixed offset w.r.t the world
// F * Mv * ref
double2 const lsFocusedOrigin = proj2(F, lsRef);
double2 const d = fract(lsFocusedOrigin * (resolution * 0.25)) / (resolution * 0.25);
double2 const lsOrigin = proj(FMv, wsSnapCoords).xy;
double2 const d = (fract(lsOrigin * resolution * 0.5) * 2.0) / resolution;
// adjust offset
o -= d;

View File

@@ -122,8 +122,8 @@ public:
uint8_t visibleLayers;
};
static math::mat4f getDirectionalLightViewMatrix(math::float3 direction, math::float3 up,
math::float3 position = {}) noexcept;
static math::mat4f getDirectionalLightViewMatrix(
math::float3 direction, math::float3 position = {}) noexcept;
static math::mat4f getPointLightViewMatrix(backend::TextureCubemapFace face,
math::float3 position) noexcept;
@@ -246,15 +246,16 @@ private:
static inline math::mat4f computeLightRotation(math::float3 const& lsDirection) noexcept;
static inline math::float4 computeFocusParams(
math::mat4f const& LMpMv,
math::mat4f const& WLMp,
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,
float shadowFar, bool stable) noexcept;
uint16_t shadowDimension, bool stable) noexcept;
static inline void snapLightFrustum(math::float2& s, math::float2& o,
math::double2 lsRef, math::int2 resolution) noexcept;
math::mat4f const& Mv, math::double3 wsSnapCoords, math::int2 resolution) noexcept;
static inline Aabb computeLightFrustumBounds(const math::mat4f& lightView,
Aabb const& wsShadowReceiversVolume, Aabb const& wsShadowCastersVolume,

View File

@@ -527,8 +527,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
// 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,
normalize(cameraInfo.worldTransform[0].xyz));
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
@@ -696,7 +695,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap,
const auto outerConeAngle = lcm.getSpotLightOuterCone(li);
// compute shadow map frustum for culling
const mat4f Mv = ShadowMap::getDirectionalLightViewMatrix(direction, { 0, 1, 0 }, position);
const mat4f Mv = ShadowMap::getDirectionalLightViewMatrix(direction, position);
const mat4f Mp = mat4f::perspective(outerConeAngle * f::RAD_TO_DEG * 2.0f, 1.0f, 0.01f, radius);
const mat4f MpMv = math::highPrecisionMultiply(Mp, Mv);
const Frustum frustum(MpMv);

View File

@@ -266,8 +266,8 @@ CameraInfo::CameraInfo(FCamera const& camera) noexcept {
d = std::max(zn, camera.getFocusDistance());
}
CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept {
const mat4 modelMatrix{ inWorldTransform * camera.getModelMatrix() };
CameraInfo::CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamera) noexcept {
const mat4 modelMatrix{ worldOriginCamera * camera.getModelMatrix() };
for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) {
eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) };
eyeFromView[i] = mat4f{ camera.getEyeFromViewMatrix(i) };
@@ -275,7 +275,7 @@ CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform
cullingProjection = mat4f{ camera.getCullingProjectionMatrix() };
model = mat4f{ modelMatrix };
view = mat4f{ inverse(modelMatrix) };
worldTransform = inWorldTransform;
worldOrigin = worldOriginCamera;
zn = (float)camera.getNear();
zf = (float)camera.getCullingFar();
ev100 = Exposure::ev100(camera);

View File

@@ -223,7 +223,7 @@ private:
struct CameraInfo {
CameraInfo() noexcept {}
explicit CameraInfo(FCamera const& camera) noexcept;
CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept;
CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamera) noexcept;
union {
// projection matrix for drawing (infinite zfar)
@@ -239,7 +239,7 @@ struct CameraInfo {
math::mat4f model; // camera model matrix
math::mat4f view; // camera view matrix (inverse(model))
math::mat4f eyeFromView[CONFIG_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic)
math::mat4 worldTransform; // world transform (already applied
math::mat4 worldOrigin; // world origin transform (already applied
// to model and view)
math::float4 clipTransform{1, 1, 0, 0}; // clip-space transform, only for VERTEX_DOMAIN_DEVICE
float zn{}; // distance (positive) to the near plane
@@ -250,7 +250,7 @@ struct CameraInfo {
float d{}; // focus distance [m]
math::float3 const& getPosition() const noexcept { return model[3].xyz; }
math::float3 getForwardVector() const noexcept { return normalize(-model[2].xyz); }
math::mat4 getUserViewMatrix() const noexcept { return view * worldTransform; }
math::mat4 getUserViewMatrix() const noexcept { return view * worldOrigin; }
};
FILAMENT_DOWNCAST(Camera)

View File

@@ -33,8 +33,6 @@
#include <utils/Range.h>
#include <utils/Systrace.h>
#include <math/quat.h>
#include <algorithm>
using namespace filament::backend;
@@ -54,7 +52,7 @@ FScene::~FScene() noexcept = default;
void FScene::prepare(utils::JobSystem& js,
LinearAllocatorArena& allocator,
mat4 const& worldTransform,
const mat4& worldOriginTransform,
bool shadowReceiversAreCasters) noexcept {
// TODO: can we skip this in most cases? Since we rely on indices staying the same,
// we could only skip, if nothing changed in the RCM.
@@ -170,7 +168,7 @@ void FScene::prepare(utils::JobSystem& js,
* Fill the SoA with the JobSystem
*/
auto renderableWork = [first = renderableInstances.data(), &rcm, &tcm, &worldTransform,
auto renderableWork = [first = renderableInstances.data(), &rcm, &tcm, &worldOriginTransform,
&sceneData, shadowReceiversAreCasters](auto* p, auto c) {
SYSTRACE_NAME("renderableWork");
@@ -178,12 +176,12 @@ void FScene::prepare(utils::JobSystem& js,
auto [ri, ti] = p[i];
// this is where we go from double to float for our transforms
const mat4f shaderWorldTransform{
worldTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(shaderWorldTransform.upperLeft()) < 0;
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(worldTransform.upperLeft()) < 0;
// compute the world AABB so we can perform culling
const Box worldAABB = rigidTransform(rcm.getAABB(ri), shaderWorldTransform);
const Box worldAABB = rigidTransform(rcm.getAABB(ri), worldTransform);
auto visibility = rcm.getVisibility(ri);
visibility.reversedWindingOrder = reversedWindingOrder;
@@ -201,7 +199,7 @@ void FScene::prepare(utils::JobSystem& js,
assert_invariant(index < sceneData.size());
sceneData.elementAt<RENDERABLE_INSTANCE>(index) = ri;
sceneData.elementAt<WORLD_TRANSFORM>(index) = shaderWorldTransform;
sceneData.elementAt<WORLD_TRANSFORM>(index) = worldTransform;
sceneData.elementAt<VISIBILITY_STATE>(index) = visibility;
sceneData.elementAt<SKINNING_BUFFER>(index) = rcm.getSkinningBufferInfo(ri);
sceneData.elementAt<MORPHING_BUFFER>(index) = rcm.getMorphingBufferInfo(ri);
@@ -218,20 +216,19 @@ void FScene::prepare(utils::JobSystem& js,
}
};
auto lightWork = [first = lightInstances.data(), &lcm, &tcm, &worldTransform,
auto lightWork = [first = lightInstances.data(), &lcm, &tcm, &worldOriginTransform,
&lightData](auto* p, auto c) {
SYSTRACE_NAME("lightWork");
for (size_t i = 0; i < c; i++) {
auto [li, ti] = p[i];
// this is where we go from double to float for our transforms
mat4f const shaderWorldTransform{
worldTransform * tcm.getWorldTransformAccurate(ti) };
float4 const position = shaderWorldTransform * float4{ lcm.getLocalPosition(li), 1 };
const mat4f worldTransform{ worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const float4 position = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
float3 d = 0;
if (!lcm.isPointLight(li) || lcm.isIESLight(li)) {
d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(shaderWorldTransform.upperLeft()) * d);
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
}
size_t const index = DIRECTIONAL_LIGHTS_COUNT + std::distance(first, p) + i;
assert_invariant(index < lightData.size());
@@ -264,43 +261,14 @@ void FScene::prepare(utils::JobSystem& js,
*/
if (auto [li, ti] = directionalLightInstances ; li) {
// in the code below, we only transform directions, so the translation of the
// world transform is irrelevant, and we don't need to use getWorldTransformAccurate()
FLightManager::ShadowParams const params = lcm.getShadowParams(li);
float3 const localDirection = lcm.getLocalDirection(li);
float3 const shadowLocalDirection = params.options.transform * localDirection;
mat3 const worldDirectionTransform = tcm.getWorldTransformAccurate(ti).upperLeft();
mat3 const shaderWorldTransform = worldTransform.upperLeft() * worldDirectionTransform;
// using mat3::getTransformForNormals handles non-uniform scaling
// note: in the common case of the rigid-body transform, getTransformForNormals() returns
// identity.
mat3 const worlTransformNormals = mat3::getTransformForNormals(shaderWorldTransform);
double3 const d = worlTransformNormals * localDirection;
double3 const s = worlTransformNormals * shadowLocalDirection;
// We compute the reference point for snapping shadowmaps without applying the
// rotation of `worldOriginTransform` on both sides, so that we don't have any instability
// due to the limited precision of the "light space" matrix (even at double precision).
// getMv() Returns the world-to-lightspace transformation. See ShadowMap.cpp.
auto getMv = [](double3 direction) -> mat3 {
// We use the x-axis as the "up" reference so that the math is stable when the light
// is pointing down, which is a common case for lights. See ShadowMap.cpp.
return transpose(mat3::lookTo(direction, double3{ 1, 0, 0 }));
};
double3 const worldDirection =
mat3::getTransformForNormals(worldDirectionTransform) * shadowLocalDirection;
double3 const worldOrigin = transpose(worldTransform.upperLeft()) * worldTransform[3].xyz;
mat3 const Mv = getMv(worldDirection);
double2 const lsReferencePoint = (Mv * worldOrigin).xy;
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
// using mat3f::getTransformForNormals handles non-uniform scaling
float3 d = lcm.getLocalDirection(li);
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
constexpr float inf = std::numeric_limits<float>::infinity();
lightData.elementAt<POSITION_RADIUS>(0) = float4{ 0, 0, 0, inf };
lightData.elementAt<DIRECTION>(0) = normalize(d);
lightData.elementAt<SHADOW_DIRECTION>(0) = normalize(s);
lightData.elementAt<SHADOW_REF>(0) = lsReferencePoint;
lightData.elementAt<DIRECTION>(0) = d;
lightData.elementAt<LIGHT_INSTANCE>(0) = li;
} else {
lightData.elementAt<LIGHT_INSTANCE>(0) = 0;

View File

@@ -71,7 +71,7 @@ public:
void terminate(FEngine& engine);
void prepare(utils::JobSystem& js, LinearAllocatorArena& allocator,
math::mat4 const& worldTransform, bool shadowReceiversAreCasters) noexcept;
math::mat4 const& worldOriginTransform, bool shadowReceiversAreCasters) noexcept;
void prepareVisibleRenderables(utils::Range<uint32_t> visibleRenderables) noexcept;
@@ -162,8 +162,6 @@ public:
enum {
POSITION_RADIUS,
DIRECTION,
SHADOW_DIRECTION,
SHADOW_REF,
LIGHT_INSTANCE,
VISIBILITY,
SCREEN_SPACE_Z_RANGE,
@@ -173,8 +171,6 @@ public:
using LightSoa = utils::StructureOfArrays<
math::float4,
math::float3,
math::float3,
math::double2,
FLightManager::Instance,
Culler::result_type,
math::float2,

View File

@@ -397,8 +397,8 @@ CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
* The "world origin" is also used to keep the origin close to the camera position to
* improve fp precision in the shader for large scenes.
*/
double3 translation;
mat3 rotation;
mat4 translation;
mat4 rotation;
/*
* Calculate all camera parameters needed to render this View for this frame.
@@ -409,18 +409,16 @@ CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
// view-space, which improves floating point precision in the shader by staying around
// zero, where fp precision is highest. This also ensures that when the camera is placed
// very far from the origin, objects are still rendered and lit properly.
translation = -camera->getPosition();
translation = mat4::translation( -camera->getPosition() );
}
FIndirectLight const* const ibl = scene->getIndirectLight();
if (ibl) {
// the IBL transformation must be a rigid transform
rotation = mat3{ transpose(scene->getIndirectLight()->getRotation()) };
// it is important to orthogonalize the matrix when converting it to doubles, because
// as float, it only has about a 1e-8 precision on the size of the basis vectors
rotation = orthogonalize(rotation);
rotation = mat4{ transpose(scene->getIndirectLight()->getRotation()) };
}
return { *camera, mat4{ rotation } * mat4::translation(translation) };
return { *camera, rotation * translation };
}
void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
@@ -448,7 +446,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
// intent of the code, which is that we should only depend on CameraInfo here.
// This is an extremely uncommon case.
const mat4 projection = mCullingCamera->getCullingProjectionMatrix();
const mat4 view = inverse(cameraInfo.worldTransform * mCullingCamera->getModelMatrix());
const mat4 view = inverse(cameraInfo.worldOrigin * mCullingCamera->getModelMatrix());
return Frustum{ mat4f{ projection * view }};
}
};
@@ -461,9 +459,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
* Gather all information needed to render this scene. Apply the world origin to all
* objects in the scene.
*/
scene->prepare(js, arena.getAllocator(),
cameraInfo.worldTransform,
hasVSM());
scene->prepare(js, arena.getAllocator(), cameraInfo.worldOrigin, hasVSM());
/*
* Light culling: runs in parallel with Renderable culling (below)

View File

@@ -702,8 +702,8 @@ TEST(FilamentTest, FroxelData) {
LightManager::Instance instance = engine->getLightManager().getInstance(e);
FScene::LightSoa lights;
lights.push_back({}, {}, {}, {}, {}, {}, {}, {}); // first one is always skipped
lights.push_back(float4{ 0, 0, -5, 1 }, {}, {}, {}, instance, 1, {}, {});
lights.push_back({}, {}, {}, {}, {}, {}); // first one is always skipped
lights.push_back(float4{ 0, 0, -5, 1 }, {}, instance, 1, {}, {});
{
froxelData.froxelizeLights(*engine, {}, lights);

View File

@@ -289,14 +289,6 @@ public:
return matrix::cof(m);
}
/*
* Returns a matrix representing the pose of a virtual camera looking towards -Z in its
* local Y-up coordinate system. "up" defines where the Y axis of the camera's local coordinate
* system is.
*/
template<typename A, typename B>
static TMat33 lookTo(const TVec3<A>& direction, const TVec3<B>& up) noexcept;
/**
* Packs the tangent frame represented by the specified matrix into a quaternion.
* Reflection is preserved by encoding it as the sign of the w component in the
@@ -414,29 +406,6 @@ constexpr TMat33<T>::TMat33(const TQuaternion<U>& q) noexcept : m_value{} {
m_value[2] = col_type(xz + yw, yz - xw, 1 - xx - yy); // NOLINT
}
template<typename T>
constexpr T dot_tolerance() noexcept;
template<>
constexpr float dot_tolerance<float>() noexcept { return 0.999f; }
template<>
constexpr double dot_tolerance<double>() noexcept { return 0.9999; }
template<typename T>
template<typename A, typename B>
TMat33<T> TMat33<T>::lookTo(const TVec3<A>& direction, const TVec3<B>& up) noexcept {
auto const z_axis = direction;
auto norm_up = up;
if (std::abs(dot(z_axis, norm_up)) > dot_tolerance< arithmetic_result_t<A, B> >()) {
// Fix up vector if we're degenerate (looking straight up, basically)
norm_up = { norm_up.z, norm_up.x, norm_up.y };
}
auto const x_axis = normalize(cross(z_axis, norm_up));
auto const y_axis = cross(x_axis, z_axis);
return { x_axis, y_axis, -z_axis };
}
//------------------------------------------------------------------------------
template<typename T>
constexpr TQuaternion<T> TMat33<T>::packTangentFrame(const TMat33<T>& m, size_t storageSize) noexcept {

View File

@@ -285,9 +285,6 @@ public:
template<typename A, typename B, typename C>
static TMat44 lookAt(const TVec3<A>& eye, const TVec3<B>& center, const TVec3<C>& up) noexcept;
template<typename A, typename B, typename C>
static TMat44 lookTo(const TVec3<A>& direction, const TVec3<B>& position, const TVec3<C>& up) noexcept;
template<typename A>
static constexpr TVec3<A> project(const TMat44& projectionMatrix, TVec3<A> vertice) noexcept{
TVec4<A> r = projectionMatrix * TVec4<A>{ vertice, 1 };
@@ -520,19 +517,19 @@ template<typename T>
template<typename A, typename B, typename C>
TMat44<T> TMat44<T>::lookAt(const TVec3<A>& eye, const TVec3<B>& center,
const TVec3<C>& up) noexcept {
return lookTo(normalize(center - eye), eye, normalize(up));
}
template<typename T>
template<typename A, typename B, typename C>
TMat44<T> TMat44<T>::lookTo(const TVec3<A>& direction, const TVec3<B>& position,
const TVec3<C>& up) noexcept {
auto r = TMat33<T>::lookTo(direction, up);
return TMat44<T>{
TVec4<T>{ r[0], 0 },
TVec4<T>{ r[1], 0 },
TVec4<T>{ r[2], 0 },
TVec4<T>{ position, 1 } };
TVec3<T> z_axis(normalize(center - eye));
TVec3<T> norm_up(normalize(up));
if (std::abs(dot(z_axis, norm_up)) > T(0.999)) {
// Fix up vector if we're degenerate (looking straight up, basically)
norm_up = { norm_up.z, norm_up.x, norm_up.y };
}
TVec3<T> x_axis(normalize(cross(z_axis, norm_up)));
TVec3<T> y_axis(cross(x_axis, z_axis));
return TMat44<T>(
TVec4<T>(x_axis, 0),
TVec4<T>(y_axis, 0),
TVec4<T>(-z_axis, 0),
TVec4<T>(eye, 1));
}
// ----------------------------------------------------------------------------------------

View File

@@ -32,71 +32,6 @@ class MatTest : public testing::Test {
protected:
};
//------------------------------------------------------------------------------
// A macro to help with vector comparisons within floating point range.
#define EXPECT_VEC_EQ(VEC1, VEC2) \
do { \
const decltype(VEC1) v1 = VEC1; \
const decltype(VEC2) v2 = VEC2; \
if (std::is_same<TypeParam,float>::value) { \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_FLOAT_EQ(v1[i], v2[i]); \
} \
} else if (std::is_same<TypeParam,double>::value) { \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_DOUBLE_EQ(v1[i], v2[i]); \
} \
} else { \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_EQ(v1[i], v2[i]); \
} \
} \
} while(0)
//------------------------------------------------------------------------------
// A macro to help with vector comparisons within a range.
#define EXPECT_VEC_NEAR(VEC1, VEC2, eps) \
do { \
const decltype(VEC1) v1 = VEC1; \
const decltype(VEC2) v2 = VEC2; \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_NEAR(v1[i], v2[i], eps); \
} \
} while(0)
//------------------------------------------------------------------------------
// A macro to help with type comparisons within floating point range.
#define ASSERT_TYPE_EQ(T1, T2) \
do { \
const decltype(T1) t1 = T1; \
const decltype(T2) t2 = T2; \
if (std::is_same<TypeParam,float>::value) { \
ASSERT_FLOAT_EQ(t1, t2); \
} else if (std::is_same<TypeParam,double>::value) { \
ASSERT_DOUBLE_EQ(t1, t2); \
} else { \
ASSERT_EQ(t1, t2); \
} \
} while(0)
TEST_F(MatTest, LargeFloatRotationsWithOrthogonalization) {
double3 const t = { 2304097.1410110965, -4688442.9915525438, -3639452.5611694567 };
mat4 const T = mat4::translation(t);
for (float d = 0; d < 90; d = d + 1.0) {
mat3f const R = mat3f::rotation(d * f::DEG_TO_RAD, float3{ 0, 1, 0 });
mat3 RR = orthogonalize(mat3{ R });
ASSERT_NEAR(dot(RR[0], RR[0]), 1.0, 1e-12);
ASSERT_NEAR(dot(RR[1], RR[1]), 1.0, 1e-12);
ASSERT_NEAR(dot(RR[2], RR[2]), 1.0, 1e-12);
mat4 M = mat4{ RR } * T;
double3 const t2 = transpose(M.upperLeft()) * M[3].xyz;
EXPECT_VEC_NEAR(t, t2, 0.0001); // 0.1mm
}
}
TEST_F(MatTest, ConstexprMat2) {
constexpr float a = F_PI;
constexpr mat2f M;
@@ -617,6 +552,53 @@ TYPED_TEST(MatTestT, Inverse2) {
TEST_MATRIX_INVERSE(m4, 20.0 * std::numeric_limits<TypeParam>::epsilon());
}
//------------------------------------------------------------------------------
// A macro to help with vector comparisons within floating point range.
#define EXPECT_VEC_EQ(VEC1, VEC2) \
do { \
const decltype(VEC1) v1 = VEC1; \
const decltype(VEC2) v2 = VEC2; \
if (std::is_same<TypeParam,float>::value) { \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_FLOAT_EQ(v1[i], v2[i]); \
} \
} else if (std::is_same<TypeParam,double>::value) { \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_DOUBLE_EQ(v1[i], v2[i]); \
} \
} else { \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_EQ(v1[i], v2[i]); \
} \
} \
} while(0)
//------------------------------------------------------------------------------
// A macro to help with vector comparisons within a range.
#define EXPECT_VEC_NEAR(VEC1, VEC2, eps) \
do { \
const decltype(VEC1) v1 = VEC1; \
const decltype(VEC2) v2 = VEC2; \
for (int i = 0; i < v1.size(); ++i) { \
EXPECT_NEAR(v1[i], v2[i], eps); \
} \
} while(0)
//------------------------------------------------------------------------------
// A macro to help with type comparisons within floating point range.
#define ASSERT_TYPE_EQ(T1, T2) \
do { \
const decltype(T1) t1 = T1; \
const decltype(T2) t2 = T2; \
if (std::is_same<TypeParam,float>::value) { \
ASSERT_FLOAT_EQ(t1, t2); \
} else if (std::is_same<TypeParam,double>::value) { \
ASSERT_DOUBLE_EQ(t1, t2); \
} else { \
ASSERT_EQ(t1, t2); \
} \
} while(0)
TYPED_TEST(MatTestT, NormalsNegativeScale) {
typedef filament::math::details::TMat33<TypeParam> M33T;

View File

@@ -101,7 +101,7 @@ struct App {
bool actualSize = false;
bool originIsFarAway = false;
float originDistance = 1.0f;
float originDistance = 6378137; // Earth's radius in [m]
struct Scene {
Entity groundPlane;
@@ -762,7 +762,7 @@ 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, 1);
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",
@@ -981,12 +981,7 @@ int main(int argc, char** argv) {
tcm.setParent(tcm.getInstance(camera.getEntity()), root);
tcm.setParent(tcm.getInstance(app.asset->getRoot()), root);
tcm.setParent(tcm.getInstance(view->getFogEntity()), root);
// these values represent a point somewhere on Earth's surface
float const d = app.originIsFarAway ? app.originDistance : 0.0f;
// tcm.setTransform(root, mat4::translation(double3{ 67.0, -6366759.0, -21552.0 } * d));
tcm.setTransform(root, mat4::translation(
double3{ 2304097.1410110965, -4688442.9915525438, -3639452.5611694567 } * d));
tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? app.originDistance : 0.0f }));
// Check if color grading has changed.
ColorGradingSettings const& options = app.viewer->getSettings().view.colorGrading;