Bläddra i källkod

Added code for calculating optimal shadow map size and fade percentage

BearishSun 8 år sedan
förälder
incheckning
bb877b8a13

+ 2 - 1
Source/BansheeUtility/Include/BsTextureAtlasLayout.h

@@ -41,7 +41,8 @@ namespace bs
 		TextureAtlasLayout(UINT32 width, UINT32 height, UINT32 maxWidth, UINT32 maxHeight, bool pow2 = false);
 
 		/**
-		 * Attempts to add a new element in the layout.
+		 * Attempts to add a new element in the layout. Elements should be added to the atlas from largest to smallest,
+		 * otherwise a non-optimal layout is likely to be generated.
 		 * 
 		 * @param[in]	width	Width of the new element, in pixels.
 		 * @param[in]	height	Height of the new element, in pixels.

+ 1 - 0
Source/RenderBeast/Include/BsRendererView.h

@@ -122,6 +122,7 @@ namespace bs { namespace ct
 		Matrix4 viewProjTransform;
 
 		SPtr<RenderTarget> target;
+		Rect2I viewRect;
 		Rect2 nrmViewRect;
 		UINT32 numSamples;
 

+ 38 - 2
Source/RenderBeast/Include/BsShadowRendering.h

@@ -9,6 +9,7 @@
 #include "BsParamBlocks.h"
 #include "BsRendererMaterial.h"
 #include "BsTextureAtlasLayout.h"
+#include "BsLight.h"
 
 namespace bs { namespace ct
 {
@@ -205,6 +206,13 @@ namespace bs { namespace ct
 	/** Provides functionality for rendering shadow maps. */
 	class ShadowRendering : public Module<ShadowRendering>
 	{
+		/** Contains information required for generating a shadow map for a specific light. */
+		struct ShadowMapOptions
+		{
+			UINT32 lightIdx;
+			UINT32 mapSize;
+			float fadePercent;
+		};
 	public:
 		ShadowRendering(UINT32 shadowMapSize);
 
@@ -219,10 +227,24 @@ namespace bs { namespace ct
 			const FrameInfo& frameInfo);
 
 		/** Renders shadow maps for the provided spot light. */
-		void renderSpotShadowMaps(const RendererLight& light, RendererScene& scene, const FrameInfo& frameInfo);
+		void renderSpotShadowMaps(const RendererLight& light, UINT32 mapSize, RendererScene& scene,
+			const FrameInfo& frameInfo);
 
 		/** Renders shadow maps for the provided radial light. */
-		void renderRadialShadowMaps(const RendererLight& light, RendererScene& scene, const FrameInfo& frameInfo);
+		void renderRadialShadowMaps(const RendererLight& light, UINT32 mapSize, RendererScene& scene,
+			const FrameInfo& frameInfo);
+
+		/** 
+		 * Calculates optimal shadow map size, taking into account all views in the scene. Also calculates a fade value
+		 * that can be used for fading out small shadow maps.
+		 * 
+		 * @param[in]	light		Light for which to calculate the shadow map properties. Cannot be a directional light.
+		 * @param[in]	scene		Scene information containing all the views the light can be seen through.
+		 * @param[out]	size		Optimal size of the shadow map, in pixels.
+		 * @param[out]	fadePercent	Value in range [0, 1] determining how much should the shadow map be faded out.
+		 */
+		void calcShadowMapProperties(const RendererLight& light, RendererScene& scene, UINT32& size, 
+			float& fadePercent) const;
 
 		/**
 		 * Generates a frustum for a single cascade of a cascaded shadow map. Also outputs spherical bounds of the
@@ -256,6 +278,15 @@ namespace bs { namespace ct
 		/** Determines how long will an unused shadow map atlas stay allocated, in frames. */
 		static const UINT32 MAX_UNUSED_FRAMES;
 
+		/** Determines the minimal resolution of a shadow map. */
+		static const UINT32 MIN_SHADOW_MAP_SIZE;
+
+		/** Determines the resolution at which shadow maps begin fading out. */
+		static const UINT32 SHADOW_MAP_FADE_SIZE;
+
+		/** Size of the border of a shadow map in a shadow map atlas, in pixels. */
+		static const UINT32 SHADOW_MAP_BORDER;
+
 		ShadowDepthNormalMat mDepthNormalMat;
 		ShadowDepthCubeMat mDepthCubeMat;
 		ShadowDepthDirectionalMat mDepthDirectionalMat;
@@ -266,7 +297,12 @@ namespace bs { namespace ct
 		Vector<ShadowCascadedMap> mCascadedShadowMaps;
 		Vector<ShadowCubemap> mShadowCubemaps;
 
+		Vector<ShadowMapInfo> mSpotLightShadowInfos;
+		Vector<ShadowMapInfo> mRadialLightShadowInfos;
+
 		Vector<bool> mRenderableVisibility; // Transient
+		Vector<ShadowMapOptions> mSpotLightShadowOptions; // Transient
+		Vector<ShadowMapOptions> mRadialLightShadowOptions; // Transient
 	};
 
 	/* @} */

+ 1 - 0
Source/RenderBeast/Source/BsRendererView.cpp

@@ -60,6 +60,7 @@ namespace bs { namespace ct
 		viewProjTransform = src.projTransform * src.viewTransform;
 
 		target = src.target.target;
+		viewRect = src.target.viewRect;
 		nrmViewRect = src.target.nrmViewRect;
 		numSamples = src.target.numSamples;
 

+ 113 - 35
Source/RenderBeast/Source/BsShadowRendering.cpp

@@ -7,6 +7,8 @@
 #include "BsRendererUtility.h"
 #include "BsGpuParamsSet.h"
 #include "BsMesh.h"
+#include "BsCamera.h"
+#include "BsBitwise.h"
 
 namespace bs { namespace ct
 {
@@ -199,6 +201,9 @@ namespace bs { namespace ct
 
 	const UINT32 ShadowRendering::MAX_ATLAS_SIZE = 8192;
 	const UINT32 ShadowRendering::MAX_UNUSED_FRAMES = 60;
+	const UINT32 ShadowRendering::MIN_SHADOW_MAP_SIZE = 32;
+	const UINT32 ShadowRendering::SHADOW_MAP_FADE_SIZE = 64;
+	const UINT32 ShadowRendering::SHADOW_MAP_BORDER = 4;
 
 	ShadowRendering::ShadowRendering(UINT32 shadowMapSize)
 		: mShadowMapSize(shadowMapSize)
@@ -227,6 +232,12 @@ namespace bs { namespace ct
 		const SceneInfo& sceneInfo = scene.getSceneInfo();
 		
 		// Clear all dynamic light atlases
+		mSpotLightShadowInfos.clear();
+		mRadialLightShadowInfos.clear();
+
+		mSpotLightShadowOptions.clear();
+		mRadialLightShadowOptions.clear();
+
 		for (auto& entry : mCascadedShadowMaps)
 			entry.clear();
 
@@ -236,6 +247,48 @@ namespace bs { namespace ct
 		for (auto& entry : mShadowCubemaps)
 			entry.clear();
 
+		// Determine shadow map sizes and sort them
+		for (UINT32 i = 0; i < (UINT32)sceneInfo.spotLights.size(); ++i)
+		{
+			scene.setLightShadowMapIdx(i, LightType::Spot, -1);
+
+			// Note: I'm using visibility across all views, while I could be using visibility for every view individually,
+			// if I kept that information somewhere
+			if (!sceneInfo.spotLightVisibility[i])
+				continue;
+
+			const RendererLight& light = sceneInfo.spotLights[i];
+
+			mSpotLightShadowOptions.push_back(ShadowMapOptions());
+			ShadowMapOptions& options = mSpotLightShadowOptions.back();
+			options.lightIdx = i;
+
+			calcShadowMapProperties(light, scene, options.mapSize, options.fadePercent);
+		}
+
+		for (UINT32 i = 0; i < (UINT32)sceneInfo.radialLights.size(); ++i)
+		{
+			scene.setLightShadowMapIdx(i, LightType::Radial, -1);
+
+			// Note: I'm using visibility across all views, while I could be using visibility for every view individually,
+			// if I kept that information somewhere
+			if (!sceneInfo.radialLightVisibility[i])
+				continue;
+
+			const RendererLight& light = sceneInfo.radialLights[i];
+
+			mRadialLightShadowOptions.push_back(ShadowMapOptions());
+			ShadowMapOptions& options = mRadialLightShadowOptions.back();
+			options.lightIdx = i;
+
+			calcShadowMapProperties(light, scene, options.mapSize, options.fadePercent);
+		}
+
+		// Sort spot lights by size so they fit neatly in the texture atlas
+		std::sort(mSpotLightShadowOptions.begin(), mSpotLightShadowOptions.end(),
+			[](const ShadowMapOptions& a, const ShadowMapOptions& b) { return a.mapSize > b.mapSize; } );
+
+
 		// Render shadow maps
 		for (UINT32 i = 0; i < (UINT32)sceneInfo.directionalLights.size(); ++i)
 		{
@@ -245,50 +298,43 @@ namespace bs { namespace ct
 				renderCascadedShadowMaps(*entry.second, sceneInfo.directionalLights[i], scene, frameInfo);
 		}
 
-		for (UINT32 i = 0; i < (UINT32)sceneInfo.spotLights.size(); ++i)
+		for(auto& entry : mSpotLightShadowOptions)
 		{
-			if (!sceneInfo.spotLightVisibility[i])
-				continue;
-
-			scene.setLightShadowMapIdx(i, LightType::Spot, -1);
-			renderSpotShadowMaps(sceneInfo.spotLights[i], scene, frameInfo);
+			UINT32 lightIdx = entry.lightIdx;
+			renderSpotShadowMaps(sceneInfo.spotLights[lightIdx], entry.mapSize, scene, frameInfo);
 		}
 
-		for (UINT32 i = 0; i < (UINT32)sceneInfo.radialLights.size(); ++i)
+		for (auto& entry : mRadialLightShadowOptions)
 		{
-			if (!sceneInfo.radialLightVisibility[i])
-				continue;
-
-			scene.setLightShadowMapIdx(i, LightType::Radial, -1);
-			renderRadialShadowMaps(sceneInfo.radialLights[i], scene, frameInfo);
+			UINT32 lightIdx = entry.lightIdx;
+			renderRadialShadowMaps(sceneInfo.radialLights[lightIdx], entry.mapSize, scene, frameInfo);
 		}
 		
-		// Deallocate unused atlas textures
+		// Deallocate unused textures
 		for(auto iter = mDynamicShadowMaps.begin(); iter != mDynamicShadowMaps.end(); ++iter)
 		{
 			if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
 			{
+				// These are always populated in order, so we can assume all following atlases are also empty
 				mDynamicShadowMaps.erase(iter, mDynamicShadowMaps.end());
 				break;
 			}
 		}
 
-		for(auto iter = mCascadedShadowMaps.begin(); iter != mCascadedShadowMaps.end(); ++iter)
+		for(auto iter = mCascadedShadowMaps.begin(); iter != mCascadedShadowMaps.end();)
 		{
-			if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
-			{
-				mCascadedShadowMaps.erase(iter, mCascadedShadowMaps.end());
-				break;
-			}
+			if (iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+				iter = mCascadedShadowMaps.erase(iter);
+			else
+				++iter;
 		}
 		
-		for(auto iter = mShadowCubemaps.begin(); iter != mShadowCubemaps.end(); ++iter)
+		for(auto iter = mShadowCubemaps.begin(); iter != mShadowCubemaps.end();)
 		{
-			if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
-			{
-				mShadowCubemaps.erase(iter, mShadowCubemaps.end());
-				break;
-			}
+			if (iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+				iter = mShadowCubemaps.erase(iter);
+			else
+				++iter;
 		}
 	}
 
@@ -387,7 +433,7 @@ namespace bs { namespace ct
 		}
 	}
 
-	void ShadowRendering::renderSpotShadowMaps(const RendererLight& rendererLight, RendererScene& scene,
+	void ShadowRendering::renderSpotShadowMaps(const RendererLight& rendererLight, UINT32 mapSize, RendererScene& scene,
 		const FrameInfo& frameInfo)
 	{
 		Light* light = rendererLight.internal;
@@ -398,16 +444,13 @@ namespace bs { namespace ct
 		const SceneInfo& sceneInfo = scene.getSceneInfo();
 		SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
 
-		// TODO - Calculate shadow map size depending on size on the screen
-		UINT32 mapSize = std::min(mShadowMapSize, MAX_ATLAS_SIZE);
 		ShadowMapInfo mapInfo;
-
 		bool foundSpace = false;
 		for (UINT32 i = 0; i < (UINT32)mDynamicShadowMaps.size(); i++)
 		{
 			ShadowMapAtlas& atlas = mDynamicShadowMaps[i];
 
-			if (atlas.addMap(mapSize, mapInfo.area))
+			if (atlas.addMap(mapSize, mapInfo.area, SHADOW_MAP_BORDER))
 			{
 				mapInfo.atlasIdx = i;
 
@@ -422,7 +465,7 @@ namespace bs { namespace ct
 			mDynamicShadowMaps.push_back(ShadowMapAtlas(MAX_ATLAS_SIZE));
 
 			ShadowMapAtlas& atlas = mDynamicShadowMaps.back();
-			atlas.addMap(mapSize, mapInfo.area);
+			atlas.addMap(mapSize, mapInfo.area, SHADOW_MAP_BORDER);
 		}
 
 		mapInfo.updateNormArea(MAX_ATLAS_SIZE);
@@ -488,7 +531,7 @@ namespace bs { namespace ct
 		rapi.setViewport(Rect2(0.0f, 0.0f, 1.0f, 1.0f));
 	}
 
-	void ShadowRendering::renderRadialShadowMaps(const RendererLight& rendererLight, RendererScene& scene,
+	void ShadowRendering::renderRadialShadowMaps(const RendererLight& rendererLight, UINT32 mapSize, RendererScene& scene, 
 		const FrameInfo& frameInfo)
 	{
 		Light* light = rendererLight.internal;
@@ -501,9 +544,6 @@ namespace bs { namespace ct
 		SPtr<GpuParamBlockBuffer> shadowCubeMatricesBuffer = gShadowCubeMatricesDef.createBuffer();
 		SPtr<GpuParamBlockBuffer> shadowCubeMasksBuffer = gShadowCubeMasksDef.createBuffer();
 
-		// TODO - Calculate shadow map size depending on size on the screen
-		UINT32 mapSize = std::min(mShadowMapSize, MAX_ATLAS_SIZE);
-
 		UINT32 mapIdx = -1;
 		for (UINT32 i = 0; i < (UINT32)mShadowCubemaps.size(); i++)
 		{
@@ -637,6 +677,44 @@ namespace bs { namespace ct
 		}
 	}
 
+	void ShadowRendering::calcShadowMapProperties(const RendererLight& light, RendererScene& scene, UINT32& size, 
+		float& fadePercent) const
+	{
+		const SceneInfo& sceneInfo = scene.getSceneInfo();
+
+		// Find a view in which the light has the largest radius
+		float maxRadiusPercent;
+		for (auto& entry : sceneInfo.views)
+		{
+			const RendererViewProperties& viewProps = entry.second->getProperties();
+
+			float viewScaleX = viewProps.projTransform[0][0] * 0.5f;
+			float viewScaleY = viewProps.projTransform[1][1] * 0.5f;
+			float viewScale = std::max(viewScaleX, viewScaleY);
+
+			const Matrix4& viewVP = viewProps.viewProjTransform;
+
+			Vector4 lightClipPos = viewVP.multiply(Vector4(light.internal->getPosition(), 1.0f));
+			float radiusNDC = light.internal->getBounds().getRadius() / std::max(lightClipPos.w, 1.0f);
+
+			// Radius of light bounds in percent of the view surface
+			float radiusPercent = radiusNDC * viewScale;
+			maxRadiusPercent = std::max(maxRadiusPercent, radiusPercent);
+		}
+
+		// If light fully (or nearly fully) covers the screen, use full shadow map resolution, otherwise
+		// scale it down to smaller power of two, while clamping to minimal allowed resolution
+		float optimalMapSize = mShadowMapSize * maxRadiusPercent;
+		UINT32 effectiveMapSize = Bitwise::nextPow2((UINT32)optimalMapSize);
+		effectiveMapSize = Math::clamp(effectiveMapSize, MIN_SHADOW_MAP_SIZE, mShadowMapSize);
+
+		// Leave room for border
+		size = std::max(effectiveMapSize - 2 * SHADOW_MAP_BORDER, 1u);
+
+		// Determine if the shadow should fade out
+		fadePercent = Math::lerp01(optimalMapSize, (float)MIN_SHADOW_MAP_SIZE, (float)SHADOW_MAP_FADE_SIZE);
+	}
+
 	ConvexVolume ShadowRendering::getCSMSplitFrustum(const RendererView& view, const Vector3& lightDir, UINT32 cascade, 
 		UINT32 numCascades, Sphere& outBounds)
 	{