Răsfoiți Sursa

Try to stabilize the CSM

Panagiotis Christopoulos Charitos 7 ani în urmă
părinte
comite
6e99c4b1da

+ 5 - 23
samples/sponza/assets/scene.lua

@@ -95,9 +95,9 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newReflectionProbeNode("reflprobe2", Vec4.new(-24.8339, -4.2659, -4.12719, 0), Vec4.new(24.8339, 4.2659, 4.12719, 0))
+node = scene:newReflectionProbeNode("reflprobe2", Vec4.new(-17.2975, -4.71667, -4.19979, 0), Vec4.new(17.2975, 4.71667, 4.19979, 0))
 trf = Transform.new()
-trf:setOrigin(Vec4.new(-1.00211, 19.8704, -7.63218, 0))
+trf:setOrigin(Vec4.new(-1.83947, 20.1518, -0.637399, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
 trf:setRotation(rot)
@@ -106,15 +106,6 @@ node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
 node = scene:newReflectionProbeNode("reflprobe3", Vec4.new(-24.8339, -4.2659, -4.12719, 0), Vec4.new(24.8339, 4.2659, 4.12719, 0))
 trf = Transform.new()
-trf:setOrigin(Vec4.new(-1.00211, 19.8704, -0.0445416, 0))
-rot = Mat3x4.new()
-rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
-trf:setRotation(rot)
-trf:setScale(1)
-node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
-
-node = scene:newReflectionProbeNode("reflprobe4", Vec4.new(-24.8339, -4.2659, -4.12719, 0), Vec4.new(24.8339, 4.2659, 4.12719, 0))
-trf = Transform.new()
 trf:setOrigin(Vec4.new(-1.00211, 11.4761, -0.0445416, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
@@ -122,7 +113,7 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newReflectionProbeNode("reflprobe5", Vec4.new(-24.8339, -3.89695, -4.12719, 0), Vec4.new(24.8339, 3.89695, 4.12719, 0))
+node = scene:newReflectionProbeNode("reflprobe4", Vec4.new(-24.8339, -3.89695, -4.12719, 0), Vec4.new(24.8339, 3.89695, 4.12719, 0))
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-1.00211, 3.65293, -0.0445416, 0))
 rot = Mat3x4.new()
@@ -131,7 +122,7 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newReflectionProbeNode("reflprobe6", Vec4.new(-24.8339, -3.89695, -4.12719, 0), Vec4.new(24.8339, 3.89695, 4.12719, 0))
+node = scene:newReflectionProbeNode("reflprobe5", Vec4.new(-24.8339, -3.89695, -4.12719, 0), Vec4.new(24.8339, 3.89695, 4.12719, 0))
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-1.00211, 3.65293, 6.48553, 0))
 rot = Mat3x4.new()
@@ -140,7 +131,7 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newReflectionProbeNode("reflprobe7", Vec4.new(-24.8339, -4.2659, -4.12719, 0), Vec4.new(24.8339, 4.2659, 4.12719, 0))
+node = scene:newReflectionProbeNode("reflprobe6", Vec4.new(-24.8339, -4.2659, -4.12719, 0), Vec4.new(24.8339, 4.2659, 4.12719, 0))
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-1.00211, 11.4761, 6.48553, 0))
 rot = Mat3x4.new()
@@ -149,15 +140,6 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newReflectionProbeNode("reflprobe8", Vec4.new(-24.8339, -4.2659, -4.12719, 0), Vec4.new(24.8339, 4.2659, 4.12719, 0))
-trf = Transform.new()
-trf:setOrigin(Vec4.new(-1.00211, 19.8704, 6.48553, 0))
-rot = Mat3x4.new()
-rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
-trf:setRotation(rot)
-trf:setScale(1)
-node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
-
 node = scene:newModelNode("sponza_277leaf-materialnone0", "assets/sponza_277leaf-material.ankimdl")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-10.8267, 3.02038, -4.74626, 0))

+ 1 - 1
shaders/LightShading.glslp

@@ -161,7 +161,7 @@ void main()
 			shadowFactor = 1.0;
 		}
 
-		Vec3 l = u_dirLight.m_dir;
+		Vec3 l = -u_dirLight.m_dir;
 
 		F32 lambert = max(gbuffer.m_subsurface, dot(l, gbuffer.m_normal));
 

+ 1 - 1
shaders/TraditionalDeferredShading.glslp

@@ -103,7 +103,7 @@ void main()
 	// Compute spec
 	Vec3 viewDir = normalize(u_unis.m_camPos - worldPos);
 #if LIGHT_TYPE == DIR_LIGHT_TYPE
-	Vec3 l = u_unis.m_lightDir;
+	Vec3 l = -u_unis.m_lightDir;
 #else
 	Vec3 frag2Light = u_unis.m_position - worldPos;
 	Vec3 l = normalize(frag2Light);

+ 13 - 0
src/anki/collision/Aabb.h

@@ -125,6 +125,19 @@ public:
 	/// Calculate from a set of points
 	void setFromPointCloud(const void* buff, U count, PtrSize stride, PtrSize buffSize);
 
+	// Intersect a ray against an AABB. The ray is inside the AABB. The function returns the distance 'a' where the
+	// intersection point is rayOrigin + rayDir * a
+	// https://community.arm.com/graphics/b/blog/posts/reflections-based-on-local-cubemaps-in-unity
+	F32 intersectRayInside(const Vec3& rayOrigin, const Vec3& rayDir) const
+	{
+		const Vec3 reciprocal = rayDir.reciprocal();
+		const Vec3 intersectMaxPointPlanes = (m_max.xyz() - rayOrigin) * reciprocal;
+		const Vec3 intersectMinPointPlanes = (m_min.xyz() - rayOrigin) * reciprocal;
+		const Vec3 largestParams = intersectMaxPointPlanes.max(intersectMinPointPlanes);
+		const F32 distToIntersect = min(min(largestParams.x(), largestParams.y()), largestParams.z());
+		return distToIntersect;
+	}
+
 private:
 	Vec4 m_min;
 	Vec4 m_max;

+ 11 - 0
src/anki/math/Vec.h

@@ -2239,6 +2239,17 @@ public:
 		return out;
 	}
 
+	/// Get a safe 1 / (*this)
+	TV reciprocal() const
+	{
+		TV out;
+		for(U i = 0; i < N; ++i)
+		{
+			out[i] = T(1) / m_arr[i];
+		}
+		return out;
+	}
+
 	/// Serialize the structure.
 	void serialize(void* data, PtrSize& size) const
 	{

+ 2 - 2
src/anki/renderer/Indirect.cpp

@@ -194,7 +194,7 @@ Error Indirect::initShadowMapping(const ConfigSet& cfg)
 
 	// RT descr
 	m_shadowMapping.m_rtDescr =
-		m_r->create2DRenderTargetDescription(resolution * 6, resolution, Format::D16_UNORM, "GI SM");
+		m_r->create2DRenderTargetDescription(resolution * 6, resolution, Format::D32_SFLOAT, "GI SM");
 	m_shadowMapping.m_rtDescr.bake();
 
 	// FB descr
@@ -886,7 +886,7 @@ Bool Indirect::findBestCacheEntry(U64 probeUuid, U32& cacheEntryIdxAllocated, Bo
 
 void Indirect::runShadowMapping(CommandBufferPtr& cmdb)
 {
-	cmdb->setPolygonOffset(7.0f, 5.0f);
+	cmdb->setPolygonOffset(1.0f, 1.0f);
 
 	for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 	{

+ 6 - 10
src/anki/scene/Octree.h

@@ -106,18 +106,14 @@ public:
 		debugDrawRecursive(*m_rootLeaf, drawer);
 	}
 
-	/// Get the min bound of the scene as calculated by the objects that were placed inside the Octree.
-	const Vec3& getActualSceneMinBound() const
+	/// Get the bounds of the scene as calculated by the objects that were placed inside the Octree.
+	void getActualSceneBounds(Vec3& min, Vec3& max) const
 	{
+		LockGuard<Mutex> lock(m_globalMtx);
 		ANKI_ASSERT(m_actualSceneAabbMin.x() < MAX_F32);
-		return m_actualSceneAabbMin;
-	}
-
-	/// Get the max bound of the scene as calculated by the objects that were placed inside the Octree.
-	const Vec3& getActualSceneMaxBound() const
-	{
 		ANKI_ASSERT(m_actualSceneAabbMax.x() > MIN_F32);
-		return m_actualSceneAabbMax;
+		min = m_actualSceneAabbMin;
+		max = m_actualSceneAabbMax;
 	}
 
 private:
@@ -206,7 +202,7 @@ private:
 	U32 m_maxDepth = 0;
 	Vec3 m_sceneAabbMin = Vec3(0.0f);
 	Vec3 m_sceneAabbMax = Vec3(0.0f);
-	Mutex m_globalMtx;
+	mutable Mutex m_globalMtx;
 
 	ObjectAllocatorSameType<Leaf, 256> m_leafAlloc;
 	ObjectAllocatorSameType<LeafNode, 128> m_leafNodeAlloc;

+ 65 - 100
src/anki/scene/components/LightComponent.cpp

@@ -8,6 +8,8 @@
 #include <anki/scene/SceneGraph.h>
 #include <anki/scene/Octree.h>
 #include <anki/collision/Frustum.h>
+#include <anki/collision/Sphere.h>
+#include <anki/collision/Plane.h>
 #include <shaders/glsl_cpp_common/ClusteredShading.h>
 
 namespace anki
@@ -32,7 +34,8 @@ LightComponent::LightComponent(LightComponentType type, U64 uuid)
 		m_spot.m_textureMat = Mat4::getIdentity();
 		break;
 	case LightComponentType::DIRECTIONAL:
-		m_dir.m_sceneAabbMaxZLightSpace = MIN_F32;
+		m_dir.m_sceneMax = Vec3(MIN_F32);
+		m_dir.m_sceneMin = Vec3(MAX_F32);
 		break;
 	default:
 		ANKI_ASSERT(0);
@@ -66,30 +69,7 @@ Error LightComponent::update(SceneNode& node, Second prevTime, Second crntTime,
 	// Update the scene bounds always
 	if(m_type == LightComponentType::DIRECTIONAL)
 	{
-		// Get some values fro the octree
-		const Vec3& sceneWSpaceMin = node.getSceneGraph().getOctree().getActualSceneMinBound();
-		const Vec3& sceneWSpaceMax = node.getSceneGraph().getOctree().getActualSceneMaxBound();
-
-		// Gather the 8 scene points
-		Array<Vec4, 8> sceneEdgesWSpace;
-		sceneEdgesWSpace[0] = Vec4(sceneWSpaceMin.x(), sceneWSpaceMin.y(), sceneWSpaceMin.z(), 1.0f);
-		sceneEdgesWSpace[1] = Vec4(sceneWSpaceMin.x(), sceneWSpaceMin.y(), sceneWSpaceMax.z(), 1.0f);
-		sceneEdgesWSpace[2] = Vec4(sceneWSpaceMin.x(), sceneWSpaceMax.y(), sceneWSpaceMin.z(), 1.0f);
-		sceneEdgesWSpace[3] = Vec4(sceneWSpaceMin.x(), sceneWSpaceMax.y(), sceneWSpaceMax.z(), 1.0f);
-		sceneEdgesWSpace[4] = Vec4(sceneWSpaceMax.x(), sceneWSpaceMin.y(), sceneWSpaceMin.z(), 1.0f);
-		sceneEdgesWSpace[5] = Vec4(sceneWSpaceMax.x(), sceneWSpaceMin.y(), sceneWSpaceMax.z(), 1.0f);
-		sceneEdgesWSpace[6] = Vec4(sceneWSpaceMax.x(), sceneWSpaceMax.y(), sceneWSpaceMin.z(), 1.0f);
-		sceneEdgesWSpace[7] = Vec4(sceneWSpaceMax.x(), sceneWSpaceMax.y(), sceneWSpaceMax.z(), 1.0f);
-
-		// Transform the 8 scene points to light space
-		m_dir.m_sceneAabbMaxZLightSpace = MIN_F32;
-		const Mat4 viewMat = Mat4(m_trf).getInverse();
-		for(const Vec4& point : sceneEdgesWSpace)
-		{
-			const Vec4 lightSpaceEdge = viewMat * point;
-
-			m_dir.m_sceneAabbMaxZLightSpace = max(m_dir.m_sceneAabbMaxZLightSpace, lightSpaceEdge.z());
-		}
+		node.getSceneGraph().getOctree().getActualSceneBounds(m_dir.m_sceneMin, m_dir.m_sceneMax);
 	}
 
 	return Error::NONE;
@@ -109,7 +89,7 @@ void LightComponent::setupDirectionalLightQueueElement(const Frustum& frustum,
 	el.m_drawCallback = derectionalLightDebugDrawCallback;
 	el.m_uuid = m_uuid;
 	el.m_diffuseColor = m_diffColor.xyz();
-	el.m_direction = m_trf.getRotation().getZAxis().xyz();
+	el.m_direction = -m_trf.getRotation().getZAxis().xyz();
 	el.m_shadowCascadeCount = shadowCascadeCount;
 
 	// Compute the texture matrices
@@ -119,7 +99,6 @@ void LightComponent::setupDirectionalLightQueueElement(const Frustum& frustum,
 	}
 
 	const Mat4 lightTrf(m_trf);
-	const Mat4 lightViewMat(lightTrf.getInverse());
 	if(frustum.getType() == FrustumType::PERSPECTIVE)
 	{
 		// Get some stuff
@@ -128,103 +107,89 @@ void LightComponent::setupDirectionalLightQueueElement(const Frustum& frustum,
 		const F32 fovY = pfrustum.getFovY();
 		const F32 far = (overrideFrustumFar > 0.0f) ? overrideFrustumFar : pfrustum.getFar();
 
-		// Gather the edges
-		Array<Vec4, (MAX_SHADOW_CASCADES + 1) * 4> edgesLocalSpaceStorage;
-		WeakArray<Vec4> edgesLocalSpace(&edgesLocalSpaceStorage[0], (shadowCascadeCount + 1) * 4);
-		edgesLocalSpace[0] = edgesLocalSpace[1] = edgesLocalSpace[2] = edgesLocalSpace[3] =
-			Vec4(0.0f, 0.0f, 0.0f, 1.0f); // Eye
+		// Compute a sphere per cascade
+		Array<Sphere, MAX_SHADOW_CASCADES> boundingSpheres;
 		for(U i = 0; i < shadowCascadeCount; ++i)
 		{
 			const F32 cascadeFarNearDist = far / F32(shadowCascadeCount);
-			const F32 cascadeFar = F32(i + 1) * cascadeFarNearDist;
-
-			const F32 x = cascadeFar * tan(fovY / 2.0f) * fovX / fovY;
-			const F32 y = tan(fovY / 2.0f) * cascadeFar;
-			const F32 z = -cascadeFar;
 
-			U idx = (i + 1) * 4;
-			edgesLocalSpace[idx++] = Vec4(x, y, z, 1.0f); // top right
-			edgesLocalSpace[idx++] = Vec4(-x, y, z, 1.0f); // top left
-			edgesLocalSpace[idx++] = Vec4(-x, -y, z, 1.0f); // bot left
-			edgesLocalSpace[idx++] = Vec4(x, -y, z, 1.0f); // bot right
-		}
-
-		// Transform those edges to light space
-		Array<Vec4, edgesLocalSpaceStorage.getSize()> edgesLightSpaceStorage;
-		WeakArray<Vec4> edgesLightSpace(&edgesLightSpaceStorage[0], edgesLocalSpace.getSize());
-		const Mat4 lspaceMtx = lightViewMat * Mat4(frustum.getTransform());
-		for(U i = 0; i < edgesLightSpace.getSize(); ++i)
-		{
-			edgesLightSpace[i] = lspaceMtx * edgesLocalSpace[i];
+			// Compute the center of the sphere
+			//           ^ z
+			//           |
+			// ----------|---------- A(a, -f)
+			//  \        |        /
+			//   \       |       /
+			//    \    C(0,z)   /
+			//     \     |     /
+			//      \    |    /
+			//       \---|---/ B(b, -n)
+			//        \  |  /
+			//         \ | /
+			//           v
+			// --------------------------> x
+			//           |
+			// The square distance of A-C is equal to B-C. Solve the equation to find the z.
+			const F32 f = F32(i + 1) * cascadeFarNearDist; // Cascade far
+			const F32 n = max(frustum.getNear(), F32(i) * cascadeFarNearDist); // Cascade near
+			const F32 a = f * tan(fovY / 2.0f) * fovX / fovY;
+			const F32 b = n * tan(fovY / 2.0f) * fovX / fovY;
+			const F32 z = (b * b + n * n - a * a - f * f) / (2.0f * (f - n));
+			ANKI_ASSERT(isZero((Vec2(a, -f) - Vec2(0, z)).getLength() - (Vec2(b, -n) - Vec2(0, z)).getLength()));
+
+			Vec3 C(0.0f, 0.0f, z); // Sphere center
+
+			// Compute the radius of the sphere
+			const Vec3 A(a, tan(fovY / 2.0f) * f, -f);
+			const F32 r = (A - C).getLength();
+
+			// Set the sphere
+			boundingSpheres[i].setRadius(r);
+			boundingSpheres[i].setCenter(frustum.getTransform().transform(C));
 		}
 
-		// Compute the min max per cascade
-		Array2d<Vec3, MAX_SHADOW_CASCADES, 2> minMaxes;
+		// Compute the matrices
 		for(U i = 0; i < shadowCascadeCount; ++i)
 		{
-			Vec3 aabbMin(MAX_F32);
-			Vec3 aabbMax(MIN_F32);
-			for(U j = i * 4; j < i * 4 + 8; ++j)
+			const Sphere& sphere = boundingSpheres[i];
+			const Vec3 sphereCenter = sphere.getCenter().xyz();
+			const F32 sphereRadius = sphere.getRadius();
+			const Vec3& lightDir = el.m_direction;
+			const Vec3 sceneMin = m_dir.m_sceneMin - Vec3(sphereRadius); // Push the bounds a bit
+			const Vec3 sceneMax = m_dir.m_sceneMax + Vec3(sphereRadius);
+
+			// Compute the intersections with the scene bounds
+			Vec3 eye;
+			if(sphereCenter > sceneMin && sphereCenter < sceneMax)
 			{
-				aabbMin = aabbMin.min(edgesLightSpace[j].xyz());
-				aabbMax = aabbMax.max(edgesLightSpace[j].xyz());
+				// Inside the scene bounds
+				const Aabb sceneBox(sceneMin, sceneMax);
+				const F32 t = sceneBox.intersectRayInside(sphereCenter, -lightDir);
+				eye = sphereCenter + t * (-lightDir);
 			}
-
-			// Adjust the max to take into account the scene bounds
-			aabbMax.z() = max(aabbMax.z(), m_dir.m_sceneAabbMaxZLightSpace);
-
-			// Align it to avoid flickering
-			const PtrSize alignmentMeters = 2;
-			for(U j = 0; j < 3; ++j)
+			else
 			{
-				aabbMin[j] = getAlignedRoundDown(alignmentMeters, I32(aabbMin[j]));
-				aabbMax[j] = getAlignedRoundUp(alignmentMeters, I32(aabbMax[j]));
+				eye = sphereCenter + sphereRadius * (-lightDir);
 			}
 
-			ANKI_ASSERT(aabbMax > aabbMin);
-			minMaxes[i][0] = aabbMin;
-			minMaxes[i][1] = aabbMax;
-		}
-
-		// Compute the view and projection matrices per cascade
-		for(U i = 0; i < shadowCascadeCount; ++i)
-		{
-			const Vec3& min = minMaxes[i][0];
-			const Vec3& max = minMaxes[i][1];
-
-			const Vec2 halfDistances = (max.xy() - min.xy()) / 2.0f;
-			ANKI_ASSERT(halfDistances > Vec2(0.0f));
-
-			const Mat4 cascadeProjMat = Mat4::calculateOrthographicProjectionMatrix(halfDistances.x(),
-				-halfDistances.x(),
-				halfDistances.y(),
-				-halfDistances.y(),
-				LIGHT_FRUSTUM_NEAR_PLANE,
-				max.z() - min.z() - LIGHT_FRUSTUM_NEAR_PLANE);
-
-			Vec4 eye;
-			eye.x() = (max.x() + min.x()) / 2.0f;
-			eye.y() = (max.y() + min.y()) / 2.0f;
-			eye.z() = max.z();
-			eye.w() = 1.0f;
-			eye = lightTrf * eye;
+			// Projection
+			const F32 far = (eye - sphereCenter).getLength() + sphereRadius;
+			const Mat4 cascadeProjMat = Mat4::calculateOrthographicProjectionMatrix(
+				sphereRadius, -sphereRadius, sphereRadius, -sphereRadius, LIGHT_FRUSTUM_NEAR_PLANE, far);
 
+			// View
 			Transform cascadeTransform = m_trf;
 			cascadeTransform.setOrigin(eye.xyz0());
 			const Mat4 cascadeViewMat = Mat4(cascadeTransform.getInverse());
 
+			// Light matrix
 			static const Mat4 biasMat4(
 				0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
 			el.m_textureMatrices[i] = biasMat4 * cascadeProjMat * cascadeViewMat;
 
 			// Fill the frustum
 			OrthographicFrustum& cascadeFrustum = cascadeFrustums[i];
-			cascadeFrustum.setAll(-halfDistances.x(),
-				halfDistances.x(),
-				LIGHT_FRUSTUM_NEAR_PLANE,
-				max.z() - min.z() - LIGHT_FRUSTUM_NEAR_PLANE,
-				halfDistances.y(),
-				-halfDistances.y());
+			cascadeFrustum.setAll(
+				-sphereRadius, sphereRadius, LIGHT_FRUSTUM_NEAR_PLANE, far, sphereRadius, -sphereRadius);
 			cascadeFrustum.transform(cascadeTransform);
 		}
 	}

+ 2 - 1
src/anki/scene/components/LightComponent.h

@@ -181,7 +181,8 @@ private:
 
 	struct Dir
 	{
-		F32 m_sceneAabbMaxZLightSpace;
+		Vec3 m_sceneMin;
+		Vec3 m_sceneMax;
 	};
 
 	union