Selaa lähdekoodia

More work on shadow map rendering

BearishSun 8 vuotta sitten
vanhempi
sitoutus
4c6e4a10af

+ 7 - 1
Source/BansheeCore/Include/BsCLight.h

@@ -35,9 +35,15 @@ namespace bs
 	    /** @copydoc Light::getCastsShadow */
 		bool getCastsShadow() const { return mInternal->getCastsShadow(); }
 
-	    /** @copydoc Light::setCastsShadow  */
+	    /** @copydoc Light::setCastsShadow */
 		void setCastsShadow(bool castsShadow) { mInternal->setCastsShadow(castsShadow); }
 
+		/** @copydoc Light::setShadowBias */
+		void setShadowBias(float bias) { mInternal->setShadowBias(bias); }
+
+		/** @copydoc Light::setShadowBias() */
+		float getShadowBias() const { return mInternal->getShadowBias(); }
+
 	    /** @copydoc Light::getColor */
 		Color getColor() const { return mInternal->getColor(); }
 

+ 7 - 0
Source/BansheeCore/Include/BsCamera.h

@@ -583,6 +583,12 @@ namespace bs
 		/**	Returns the viewport used by the camera. */	
 		SPtr<Viewport> getViewport() const { return mViewport; }
 
+		/**	Sets an ID that can be used for uniquely identifying this object by the renderer. */
+		void setRendererId(UINT32 id) { mRendererId = id; }
+
+		/**	Retrieves an ID that can be used for uniquely identifying this object by the renderer. */
+		UINT32 getRendererId() const { return mRendererId; }
+
 	protected:
 		friend class bs::Camera;
 
@@ -600,6 +606,7 @@ namespace bs
 		/** @copydoc CoreObject::syncToCore */
 		void syncToCore(const CoreSyncData& data) override;
 
+		UINT32 mRendererId;
 		SPtr<Viewport> mViewport;
 	};
 	}

+ 13 - 0
Source/BansheeCore/Include/BsLight.h

@@ -74,6 +74,18 @@ namespace bs
 		/**	Sets whether this light will cast shadows when rendered. */
 		void setCastsShadow(bool castsShadow) { mCastsShadows = castsShadow; _markCoreDirty(); }
 
+		/** 
+		 * Shadow bias determines shadow accuracy. Low bias values mean that shadows start closer near their caster surface
+		 * but will result in more shadowing artifacts (shadow acne). Larger values reduce shadow acne but caster may appear
+		 * as floating on air as nearby part of the shadow is cut off (peter paning).
+		 * 
+		 * Default value is 0.5. Must be in range [0, 1].
+		 */
+		void setShadowBias(float bias) { mShadowBias = std::max(std::min(bias, 1.0f), 0.0f); _markCoreDirty(); }
+
+		/** @copydoc setShadowBias() */
+		float getShadowBias() const { return mShadowBias; }
+
 		/**	Returns the color emitted from the light. */
 		Color getColor() const { return mColor; }
 
@@ -203,6 +215,7 @@ namespace bs
 		Sphere mBounds; /**< Sphere that bounds the light area of influence. */
 		bool mAutoAttenuation; /**< Determines is attenuation radius is automatically determined. */
 		ObjectMobility mMobility; /**< Determines if there are any restrictions placed on light movement. */
+		float mShadowBias; /**< See setShadowBias() */
 	};
 
 	/** @} */

+ 1 - 0
Source/BansheeCore/Include/BsLightRTTI.h

@@ -28,6 +28,7 @@ namespace bs
 			BS_RTTI_MEMBER_PLAIN(mSpotFalloffAngle, 8)
 			BS_RTTI_MEMBER_PLAIN(mAutoAttenuation, 9)
 			BS_RTTI_MEMBER_PLAIN(mSourceRadius, 10)
+			BS_RTTI_MEMBER_PLAIN(mShadowBias, 11)
 		BS_END_RTTI_MEMBERS
 	public:
 		LightRTTI()

+ 2 - 2
Source/BansheeCore/Include/BsRenderable.h

@@ -277,10 +277,10 @@ namespace bs
 		/**	Gets world bounds of the mesh rendered by this object. */
 		Bounds getBounds() const;
 
-		/**	Sets an ID that can be used for uniquely identifying this handler by the renderer. */
+		/**	Sets an ID that can be used for uniquely identifying this object by the renderer. */
 		void setRendererId(UINT32 id) { mRendererId = id; }
 
-		/**	Retrieves an ID that can be used for uniquely identifying this handler by the renderer. */
+		/**	Retrieves an ID that can be used for uniquely identifying this object by the renderer. */
 		UINT32 getRendererId() const { return mRendererId; }
 
 		/** Returns the type of animation influencing this renderable, if any. */

+ 3 - 3
Source/BansheeCore/Include/BsRenderer.h

@@ -84,7 +84,7 @@ namespace bs
 		 *
 		 * @note	Core thread.
 		 */
-		virtual void notifyCameraAdded(const Camera* camera) { }
+		virtual void notifyCameraAdded(Camera* camera) { }
 
 		/**
 		 * Called whenever a camera's position or rotation is updated.
@@ -94,14 +94,14 @@ namespace bs
 		 *
 		 * @note	Core thread.
 		 */
-		virtual void notifyCameraUpdated(const Camera* camera, UINT32 updateFlag) { }
+		virtual void notifyCameraUpdated(Camera* camera, UINT32 updateFlag) { }
 
 		/**
 		 * Called whenever a camera is destroyed.
 		 *
 		 * @note	Core thread.
 		 */
-		virtual void notifyCameraRemoved(const Camera* camera) { }
+		virtual void notifyCameraRemoved(Camera* camera) { }
 
 		/**
 		 * Called whenever a new renderable is created.

+ 2 - 0
Source/BansheeCore/Source/BsCamera.cpp

@@ -833,11 +833,13 @@ namespace bs
 	}
 
 	Camera::Camera(SPtr<RenderTarget> target, float left, float top, float width, float height)
+		: mRendererId(0)
 	{
 		mViewport = Viewport::create(target, left, top, width, height);
 	}
 
 	Camera::Camera(const SPtr<Viewport>& viewport)
+		: mRendererId(0)
 	{
 		mViewport = viewport;
 	}

+ 5 - 1
Source/BansheeCore/Source/BsLight.cpp

@@ -12,7 +12,7 @@ namespace bs
 	LightBase::LightBase()
 		: mPosition(BsZero), mRotation(BsIdentity), mType(LightType::Radial), mCastsShadows(false), mColor(Color::White)
 		, mAttRadius(10.0f), mSourceRadius(0.0f), mIntensity(5.0f), mSpotAngle(45), mSpotFalloffAngle(35.0f)
-		, mIsActive(true), mAutoAttenuation(true), mMobility(ObjectMobility::Movable)
+		, mIsActive(true), mAutoAttenuation(true), mMobility(ObjectMobility::Movable), mShadowBias(0.5f)
 	{
 		updateAttenuationRange();
 	}
@@ -22,6 +22,7 @@ namespace bs
 		: mPosition(BsZero), mRotation(BsIdentity), mType(type), mCastsShadows(castsShadows), mColor(color)
 		, mAttRadius(attRadius), mSourceRadius(srcRadius), mIntensity(intensity), mSpotAngle(spotAngle)
 		, mSpotFalloffAngle(spotFalloffAngle), mIsActive(true), mAutoAttenuation(true), mMobility(ObjectMobility::Movable)
+		, mShadowBias(0.5f)
 	{
 		updateAttenuationRange();
 	}
@@ -241,6 +242,7 @@ namespace bs
 		size += rttiGetElemSize(getCoreDirtyFlags());
 		size += rttiGetElemSize(mBounds);
 		size += rttiGetElemSize(mMobility);
+		size += rttiGetElemSize(mShadowBias);
 
 		UINT8* buffer = allocator->alloc(size);
 
@@ -260,6 +262,7 @@ namespace bs
 		dataPtr = rttiWriteElem(getCoreDirtyFlags(), dataPtr);
 		dataPtr = rttiWriteElem(mBounds, dataPtr);
 		dataPtr = rttiWriteElem(mMobility, dataPtr);
+		dataPtr = rttiWriteElem(mShadowBias, dataPtr);
 
 		return CoreSyncData(buffer, size);
 	}
@@ -338,6 +341,7 @@ namespace bs
 		dataPtr = rttiReadElem(dirtyFlags, dataPtr);
 		dataPtr = rttiReadElem(mBounds, dataPtr);
 		dataPtr = rttiReadElem(mMobility, dataPtr);
+		dataPtr = rttiReadElem(mShadowBias, dataPtr);
 
 		updateBounds();
 

+ 4 - 0
Source/BansheeUtility/Include/BsStdHeaders.h

@@ -135,6 +135,10 @@ namespace bs
 	template <typename K, typename V, typename H = HashType<K>, typename C = std::equal_to<K>, typename A = StdAlloc<std::pair<const K, V>>> 
 	using UnorderedMultimap = std::unordered_multimap<K, V, H, C, A>;
 
+	/** Equivalent to Vector, except it avoids any dynamic allocations until the number of elements exceeds @p Count. */
+	template <typename T, int Count> 
+	using SmallVector = std::vector<T, StdAlloc<T>>; // TODO: Currently equivalent to Vector, need to implement the allocator
+
 	/** @} */
 
 	/** @addtogroup Memory

+ 4 - 0
Source/BansheeUtility/Include/BsString.h

@@ -33,6 +33,10 @@ namespace bs
 	/** Wide string stream used for primarily for constructing strings consisting of ASCII text. */
 	typedef BasicStringStream<char> StringStream;
 
+	/** Equivalent to String, except it avoids any dynamic allocations until the number of elements exceeds @p Count. */
+	template <int Count> 
+	using SmallString = std::basic_string <char, std::char_traits<char>, StdAlloc<char>>; // TODO: Currently equivalent to String, need to implement the allocator
+
 	/** @} */
 }
 

+ 1 - 1
Source/BansheeUtility/Source/BsDataStream.cpp

@@ -249,7 +249,7 @@ namespace bs
 	MemoryDataStream::MemoryDataStream(size_t size)
 		: DataStream(READ | WRITE), mData(nullptr), mFreeOnClose(true)
 	{
-		mData = mPos = (UINT8*)bs_alloc(size);
+		mData = mPos = (UINT8*)bs_alloc((UINT32)size);
 		mSize = size;
 		mEnd = mData + mSize;
 

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

@@ -37,7 +37,6 @@ namespace bs { namespace ct
 		void getParameters(LightData& output) const;
 		
 		Light* internal;
-		UINT32 shadowMapIndex;
 	};
 
 	/** Contains GPU buffers used by the renderer to manipulate lights. */

+ 3 - 3
Source/RenderBeast/Include/BsRenderBeast.h

@@ -83,13 +83,13 @@ namespace bs
 
 	private:
 		/** @copydoc Renderer::notifyCameraAdded */
-		void notifyCameraAdded(const Camera* camera) override;
+		void notifyCameraAdded(Camera* camera) override;
 
 		/** @copydoc Renderer::notifyCameraUpdated */
-		void notifyCameraUpdated(const Camera* camera, UINT32 updateFlag) override;
+		void notifyCameraUpdated(Camera* camera, UINT32 updateFlag) override;
 
 		/** @copydocRenderer::notifyCameraRemoved */
-		void notifyCameraRemoved(const Camera* camera) override;
+		void notifyCameraRemoved(Camera* camera) override;
 
 		/** @copydoc Renderer::notifyLightAdded */
 		void notifyLightAdded(Light* light) override;

+ 11 - 15
Source/RenderBeast/Include/BsRendererScene.h

@@ -26,7 +26,8 @@ namespace bs
 	{
 		// Cameras and render targets
 		Vector<RendererRenderTarget> renderTargets;
-		UnorderedMap<const Camera*, RendererView*> views;
+		Vector<RendererView*> views;
+		UnorderedMap<const Camera*, RendererView*> cameraToView;
 		
 		// Renderables
 		Vector<RendererObject*> renderables;
@@ -61,13 +62,13 @@ namespace bs
 		~RendererScene();
 
 		/** Registers a new camera in the scene. */
-		void registerCamera(const Camera* camera);
+		void registerCamera(Camera* camera);
 
 		/** Updates information about a previously registered camera. */
-		void updateCamera(const Camera* camera, UINT32 updateFlag);
+		void updateCamera(Camera* camera, UINT32 updateFlag);
 
 		/** Removes a camera from the scene. */
-		void unregisterCamera(const Camera* camera);
+		void unregisterCamera(Camera* camera);
 
 		/** Registers a new light in the scene. */
 		void registerLight(Light* light);
@@ -96,9 +97,6 @@ namespace bs
 		/** Removes a reflection probe from the scene. */
 		void unregisterReflectionProbe(ReflectionProbe* probe);
 
-		/** Updates the index which maps the light to a particular shadow map in ShadowRendering. */
-		void setLightShadowMapIdx(UINT32 lightIdx, LightType lightType, UINT32 shadowMapIndex);
-
 		/** Updates or replaces the filtered reflection texture of the probe at the specified index. */
 		void setReflectionProbeTexture(UINT32 probeIdx, const SPtr<Texture>& texture);
 
@@ -129,15 +127,14 @@ namespace bs
 		 */
 		void prepareRenderable(UINT32 idx, const FrameInfo& frameInfo);
 	private:
+		/** Creates a renderer view descriptor for the particular camera. */
+		RENDERER_VIEW_DESC createViewDesc(Camera* camera) const;
+
 		/** 
-		 * Updates (or adds) renderer specific data for the specified camera. Should be called whenever camera properties
-		 * change. 
-		 *
-		 * @param[in]	camera		Camera whose data to update.
-		 * @param[in]	forceRemove	If true, the camera data will be removed instead of updated.
-		 * @return					Renderer view object that represents the camera. Null if camera was removed.
+		 * Find the render target the camera belongs to and adds it to the relevant list. If the camera was previously
+		 * registered with some other render target it will be removed from it and added to the new target.
 		 */
-		RendererView* updateCameraData(const Camera* camera, bool forceRemove = false);
+		void updateCameraRenderTargets(Camera* camera);
 
 		SceneInfo mInfo;
 		UnorderedMap<SamplerOverrideKey, MaterialSamplerOverrides*> mSamplerOverrides;
@@ -146,6 +143,5 @@ namespace bs
 		SPtr<RenderBeastOptions> mOptions;
 	};
 
-
 	/** @} */
 }}

+ 4 - 4
Source/RenderBeast/Include/BsRendererView.h

@@ -110,7 +110,7 @@ namespace bs { namespace ct
 		RENDERER_VIEW_TARGET_DESC target;
 
 		StateReduction stateReduction;
-		const Camera* sceneCamera;
+		Camera* sceneCamera;
 	};
 
 	/** Set of properties used describing a specific view that the renderer can render. */
@@ -153,7 +153,7 @@ namespace bs { namespace ct
 	struct RendererRenderTarget
 	{
 		SPtr<RenderTarget> target;
-		Vector<const Camera*> cameras;
+		Vector<Camera*> cameras;
 	};
 
 	/** Contains information about a single view into the scene, used by the renderer. */
@@ -180,7 +180,7 @@ namespace bs { namespace ct
 		const RendererViewProperties& getProperties() const { return mProperties; }
 
 		/** Returns the scene camera this object is based of. This can be null for manually constructed renderer cameras. */
-		const Camera* getSceneCamera() const { return mCamera; }
+		Camera* getSceneCamera() const { return mCamera; }
 
 		/** 
 		 * Prepares render targets for rendering. When done call endRendering().
@@ -275,7 +275,7 @@ namespace bs { namespace ct
 
 		RendererViewProperties mProperties;
 		RENDERER_VIEW_TARGET_DESC mTargetDesc;
-		const Camera* mCamera;
+		Camera* mCamera;
 
 		SPtr<RenderQueue> mOpaqueQueue;
 		SPtr<RenderQueue> mTransparentQueue;

+ 55 - 18
Source/RenderBeast/Include/BsShadowRendering.h

@@ -90,15 +90,25 @@ namespace bs { namespace ct
 			const SPtr<GpuParamBlockBuffer>& shadowCubeMasks);
 	};
 
-	/** Information about a single shadow map in a shadow map atlas. */
-	struct ShadowMapInfo
+	/** Information about a shadow cast from a single light. */
+	struct ShadowInfo
 	{
 		/** Updates normalized area coordinates based on the non-normalized ones and the provided atlas size. */
 		void updateNormArea(UINT32 atlasSize);
 
-		Rect2I area; // Area in pixels
-		Rect2 normArea; // Normalized area in [0, 1] range
-		UINT32 atlasIdx;
+		UINT32 lightIdx; /**< Index of the light casting this shadow. */
+		Rect2I area; /**< Area of the shadow map in pixels, relative to its source texture. */
+		Rect2 normArea; /**< Normalized shadow map area in [0, 1] range. */
+		UINT32 textureIdx; /**< Index of the texture the shadow map is stored in. */
+
+		/** View-projection matrix from the shadow casters point of view. */
+		Matrix4 shadowVPTransform; 
+
+		/** View-projection matrix for each cubemap face, used for omni-directional shadows. */
+		Matrix4 shadowVPTransforms[6]; 
+
+		/** Determines the fade amount of the shadow, for each view in the scene. */
+		SmallVector<float, 4> fadePerView;
 	};
 
 	/** 
@@ -198,9 +208,15 @@ namespace bs { namespace ct
 
 		/** Returns a render target that allows rendering into a specific cascade of the cascaded shadow map. */
 		SPtr<RenderTexture> getTarget(UINT32 cascadeIdx) const;
-	private:
 
+		/** Provides information about a shadow for the specified cascade. */
+		void setShadowInfo(UINT32 cascadeIdx, const ShadowInfo& info) { mShadowInfos[cascadeIdx] = info; }
+
+		/** @copydoc setShadowInfo */
+		const ShadowInfo& getShadowInfo(UINT32 cascadeIdx) const { return mShadowInfos[cascadeIdx]; }
+	private:
 		SPtr<RenderTexture> mTargets[NUM_CASCADE_SPLITS];
+		ShadowInfo mShadowInfos[NUM_CASCADE_SPLITS];
 	};
 
 	/** Provides functionality for rendering shadow maps. */
@@ -211,7 +227,14 @@ namespace bs { namespace ct
 		{
 			UINT32 lightIdx;
 			UINT32 mapSize;
-			float fadePercent;
+			SmallVector<float, 4> fadePercents;
+		};
+
+		/** Contains references to all shadows cast by a specific light. */
+		struct LightShadows
+		{
+			UINT32 startIdx;
+			UINT32 numShadows;
 		};
 	public:
 		ShadowRendering(UINT32 shadowMapSize);
@@ -223,28 +246,29 @@ namespace bs { namespace ct
 		void setShadowMapSize(UINT32 size);
 	private:
 		/** Renders cascaded shadow maps for the provided directional light viewed from the provided view. */
-		void renderCascadedShadowMaps(const RendererView& view, const RendererLight& light, RendererScene& scene,
-			const FrameInfo& frameInfo);
+		void renderCascadedShadowMaps(UINT32 viewIdx, UINT32 lightIdx, RendererScene& scene, const FrameInfo& frameInfo);
 
 		/** Renders shadow maps for the provided spot light. */
-		void renderSpotShadowMaps(const RendererLight& light, UINT32 mapSize, RendererScene& scene,
+		void renderSpotShadowMap(const RendererLight& light, const ShadowMapOptions& options, RendererScene& scene,
 			const FrameInfo& frameInfo);
 
 		/** Renders shadow maps for the provided radial light. */
-		void renderRadialShadowMaps(const RendererLight& light, UINT32 mapSize, RendererScene& scene,
+		void renderRadialShadowMap(const RendererLight& light, const ShadowMapOptions& options, 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.
+		 * @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]	fadePercents	Value in range [0, 1] determining how much should the shadow map be faded out. Each
+		 *								entry corresponds to a single view.
+		 * @param[out]	maxFadePercent	Maximum value in the @p fadePercents array.
 		 */
 		void calcShadowMapProperties(const RendererLight& light, RendererScene& scene, UINT32& size, 
-			float& fadePercent) const;
+			SmallVector<float, 4>& fadePercents, float& maxFadePercent) const;
 
 		/**
 		 * Generates a frustum for a single cascade of a cascaded shadow map. Also outputs spherical bounds of the
@@ -272,6 +296,16 @@ namespace bs { namespace ct
 		 */
 		static float getCSMSplitDistance(const RendererView& view, UINT32 index, UINT32 numCascades);
 
+		/**
+		 * Calculates a bias that can be applied when rendering shadow maps, in order to reduce shadow artifacts.
+		 * 
+		 * @param[in]	light		Light to calculate the depth bias for.
+		 * @param[in]	depthRange	Range of depths (distance between near and far planes) covered by the shadow.
+		 * @param[in]	mapSize		Size of the shadow map, in pixels.
+		 * @return					Depth bias that can be passed to shadow depth rendering shader. 
+		 */
+		static float getDepthBias(const Light& light, float depthRange, UINT32 mapSize);
+
 		/** Size of a single shadow map atlas, in pixels. */
 		static const UINT32 MAX_ATLAS_SIZE;
 
@@ -297,8 +331,11 @@ namespace bs { namespace ct
 		Vector<ShadowCascadedMap> mCascadedShadowMaps;
 		Vector<ShadowCubemap> mShadowCubemaps;
 
-		Vector<ShadowMapInfo> mSpotLightShadowInfos;
-		Vector<ShadowMapInfo> mRadialLightShadowInfos;
+		Vector<ShadowInfo> mShadowInfos;
+
+		Vector<LightShadows> mSpotLightShadows;
+		Vector<LightShadows> mRadialLightShadows;
+		Vector<UINT32> mDirectionalLightShadows;
 
 		Vector<bool> mRenderableVisibility; // Transient
 		Vector<ShadowMapOptions> mSpotLightShadowOptions; // Transient

+ 1 - 1
Source/RenderBeast/Source/BsLightRendering.cpp

@@ -18,7 +18,7 @@ namespace bs { namespace ct
 	TiledLightingParamDef gTiledLightingParamDef;
 
 	RendererLight::RendererLight(Light* light)
-		:internal(light), shadowMapIndex(-1)
+		:internal(light)
 	{ }
 
 	void RendererLight::getParameters(LightData& output) const

+ 5 - 5
Source/RenderBeast/Source/BsRenderBeast.cpp

@@ -160,17 +160,17 @@ namespace bs { namespace ct
 		mScene->unregisterLight(light);
 	}
 
-	void RenderBeast::notifyCameraAdded(const Camera* camera)
+	void RenderBeast::notifyCameraAdded(Camera* camera)
 	{
 		mScene->registerCamera(camera);
 	}
 
-	void RenderBeast::notifyCameraUpdated(const Camera* camera, UINT32 updateFlag)
+	void RenderBeast::notifyCameraUpdated(Camera* camera, UINT32 updateFlag)
 	{
 		mScene->updateCamera(camera, updateFlag);
 	}
 
-	void RenderBeast::notifyCameraRemoved(const Camera* camera)
+	void RenderBeast::notifyCameraRemoved(Camera* camera)
 	{
 		mScene->unregisterCamera(camera);
 	}
@@ -342,12 +342,12 @@ namespace bs { namespace ct
 		for (auto& rtInfo : sceneInfo.renderTargets)
 		{
 			SPtr<RenderTarget> target = rtInfo.target;
-			const Vector<const Camera*>& cameras = rtInfo.cameras;
+			const Vector<Camera*>& cameras = rtInfo.cameras;
 
 			UINT32 numCameras = (UINT32)cameras.size();
 			for (UINT32 i = 0; i < numCameras; i++)
 			{
-				RendererView* viewInfo = sceneInfo.views.at(cameras[i]);
+				RendererView* viewInfo = sceneInfo.cameraToView.at(cameras[i]);
 				views.push_back(viewInfo);
 			}
 		}

+ 109 - 116
Source/RenderBeast/Source/BsRendererScene.cpp

@@ -26,37 +26,54 @@ namespace bs {	namespace ct
 			bs_delete(entry);
 
 		for (auto& entry : mInfo.views)
-			bs_delete(entry.second);
+			bs_delete(entry);
 
 		assert(mSamplerOverrides.empty());
 
 		bs_delete(mDefaultMaterial);
 	}
 
-	void RendererScene::registerCamera(const Camera* camera)
+	void RendererScene::registerCamera(Camera* camera)
 	{
-		RendererView* view = updateCameraData(camera);
+		RENDERER_VIEW_DESC viewDesc = createViewDesc(camera);
+
+		RendererView* view = bs_new<RendererView>(viewDesc);
+		view->setPostProcessSettings(camera->getPostProcessSettings());
 		view->updatePerViewBuffer();
+
+		mInfo.cameraToView[camera] = view;
+
+		UINT32 cameraId = (UINT32)mInfo.views.size();
+		mInfo.views.push_back(view);
+
+		camera->setRendererId(cameraId);
+
+		updateCameraRenderTargets(camera);
 	}
 
-	void RendererScene::updateCamera(const Camera* camera, UINT32 updateFlag)
+	void RendererScene::updateCamera(Camera* camera, UINT32 updateFlag)
 	{
-		RendererView* rendererCam;
+		UINT32 cameraId = camera->getRendererId();
+		RendererView* view = mInfo.views[cameraId];
+
 		if((updateFlag & (UINT32)CameraDirtyFlag::Everything) != 0)
 		{
-			rendererCam = updateCameraData(camera);
+			RENDERER_VIEW_DESC viewDesc = createViewDesc(camera);
+
+			view->setView(viewDesc);
+			view->setPostProcessSettings(camera->getPostProcessSettings());
+
+			updateCameraRenderTargets(camera);
 		}
 		else if((updateFlag & (UINT32)CameraDirtyFlag::PostProcess) != 0)
 		{
-			rendererCam = mInfo.views[camera];
-
-			rendererCam->setPostProcessSettings(camera->getPostProcessSettings());
+			view->setPostProcessSettings(camera->getPostProcessSettings());
 		}
 		else // Transform
 		{
-			rendererCam = mInfo.views[camera];
+			view = mInfo.views[cameraId];
 
-			rendererCam->setTransform(
+			view->setTransform(
 				camera->getPosition(),
 				camera->getForward(),
 				camera->getViewMatrix(),
@@ -64,12 +81,34 @@ namespace bs {	namespace ct
 				camera->getWorldFrustum());
 		}
 
-		rendererCam->updatePerViewBuffer();
+		view->updatePerViewBuffer();
 	}
 
-	void RendererScene::unregisterCamera(const Camera* camera)
+	void RendererScene::unregisterCamera(Camera* camera)
 	{
-		updateCameraData(camera, true);
+		UINT32 cameraId = camera->getRendererId();
+
+		Camera* lastCamera = mInfo.views.back()->getSceneCamera();
+		UINT32 lastCameraId = lastCamera->getRendererId();
+		
+		if (cameraId != lastCameraId)
+		{
+			// Swap current last element with the one we want to erase
+			std::swap(mInfo.views[cameraId], mInfo.views[lastCameraId]);
+			lastCamera->setRendererId(cameraId);
+		}
+		
+		// Last element is the one we want to erase
+		mInfo.views.erase(mInfo.views.end() - 1);
+
+		auto iterFind = mInfo.cameraToView.find(camera);
+		if(iterFind != mInfo.cameraToView.end())
+		{
+			bs_delete(iterFind->second);
+			mInfo.cameraToView.erase(iterFind);
+		}
+
+		updateCameraRenderTargets(camera);
 	}
 
 	void RendererScene::registerLight(Light* light)
@@ -397,22 +436,6 @@ namespace bs {	namespace ct
 		LightProbeCache::instance().unloadCachedTexture(probe->getUUID());
 	}
 
-	void RendererScene::setLightShadowMapIdx(UINT32 lightIdx, LightType lightType, UINT32 shadowMapIndex)
-	{
-		switch (lightType)
-		{
-		case LightType::Directional:
-			mInfo.directionalLights[lightIdx].shadowMapIndex = shadowMapIndex;
-			break;
-		case LightType::Radial: 
-			mInfo.radialLights[lightIdx].shadowMapIndex = shadowMapIndex;
-			break;
-		case LightType::Spot: 
-			mInfo.spotLights[lightIdx].shadowMapIndex = shadowMapIndex;
-			break;
-		}
-	}
-
 	void RendererScene::setReflectionProbeTexture(UINT32 probeIdx, const SPtr<Texture>& texture)
 	{
 		RendererReflectionProbe* probe = &mInfo.reflProbes[probeIdx];
@@ -434,105 +457,77 @@ namespace bs {	namespace ct
 		mOptions = options;
 
 		for (auto& entry : mInfo.views)
-		{
-			RendererView* rendererCam = entry.second;
-			rendererCam->setStateReductionMode(mOptions->stateReductionMode);
-		}
+			entry->setStateReductionMode(mOptions->stateReductionMode);
 	}
 
-	RendererView* RendererScene::updateCameraData(const Camera* camera, bool forceRemove)
+	RENDERER_VIEW_DESC RendererScene::createViewDesc(Camera* camera) const
 	{
-		RendererView* output;
+		SPtr<Viewport> viewport = camera->getViewport();
+		RENDERER_VIEW_DESC viewDesc;
 
-		SPtr<RenderTarget> renderTarget = camera->getViewport()->getTarget();
+		viewDesc.target.clearFlags = 0;
+		if (viewport->getRequiresColorClear())
+			viewDesc.target.clearFlags |= FBT_COLOR;
 
-		auto iterFind = mInfo.views.find(camera);
-		if(forceRemove)
-		{
-			if(iterFind != mInfo.views.end())
-			{
-				bs_delete(iterFind->second);
-				mInfo.views.erase(iterFind);
-			}
-
-			renderTarget = nullptr;
-			output = nullptr;
-		}
-		else
-		{
-			SPtr<Viewport> viewport = camera->getViewport();
-			RENDERER_VIEW_DESC viewDesc;
-
-			viewDesc.target.clearFlags = 0;
-			if (viewport->getRequiresColorClear())
-				viewDesc.target.clearFlags |= FBT_COLOR;
+		if (viewport->getRequiresDepthClear())
+			viewDesc.target.clearFlags |= FBT_DEPTH;
 
-			if (viewport->getRequiresDepthClear())
-				viewDesc.target.clearFlags |= FBT_DEPTH;
+		if (viewport->getRequiresStencilClear())
+			viewDesc.target.clearFlags |= FBT_STENCIL;
 
-			if (viewport->getRequiresStencilClear())
-				viewDesc.target.clearFlags |= FBT_STENCIL;
+		viewDesc.target.clearColor = viewport->getClearColor();
+		viewDesc.target.clearDepthValue = viewport->getClearDepthValue();
+		viewDesc.target.clearStencilValue = viewport->getClearStencilValue();
 
-			viewDesc.target.clearColor = viewport->getClearColor();
-			viewDesc.target.clearDepthValue = viewport->getClearDepthValue();
-			viewDesc.target.clearStencilValue = viewport->getClearStencilValue();
+		viewDesc.target.target = viewport->getTarget();
+		viewDesc.target.nrmViewRect = viewport->getNormArea();
+		viewDesc.target.viewRect = Rect2I(
+			viewport->getX(),
+			viewport->getY(),
+			(UINT32)viewport->getWidth(),
+			(UINT32)viewport->getHeight());
 
-			viewDesc.target.target = viewport->getTarget();
-			viewDesc.target.nrmViewRect = viewport->getNormArea();
-			viewDesc.target.viewRect = Rect2I(
-				viewport->getX(),
-				viewport->getY(),
-				(UINT32)viewport->getWidth(),
-				(UINT32)viewport->getHeight());
-
-			if (viewDesc.target.target != nullptr)
-			{
-				viewDesc.target.targetWidth = viewDesc.target.target->getProperties().getWidth();
-				viewDesc.target.targetHeight = viewDesc.target.target->getProperties().getHeight();
-			}
-			else
-			{
-				viewDesc.target.targetWidth = 0;
-				viewDesc.target.targetHeight = 0;
-			}
+		if (viewDesc.target.target != nullptr)
+		{
+			viewDesc.target.targetWidth = viewDesc.target.target->getProperties().getWidth();
+			viewDesc.target.targetHeight = viewDesc.target.target->getProperties().getHeight();
+		}
+		else
+		{
+			viewDesc.target.targetWidth = 0;
+			viewDesc.target.targetHeight = 0;
+		}
 
-			viewDesc.target.numSamples = camera->getMSAACount();
+		viewDesc.target.numSamples = camera->getMSAACount();
 
-			viewDesc.isOverlay = camera->getFlags().isSet(CameraFlag::Overlay);
-			viewDesc.isHDR = camera->getFlags().isSet(CameraFlag::HDR);
-			viewDesc.noLighting = camera->getFlags().isSet(CameraFlag::NoLighting);
-			viewDesc.triggerCallbacks = true;
-			viewDesc.runPostProcessing = true;
-			viewDesc.renderingReflections = false;
+		viewDesc.isOverlay = camera->getFlags().isSet(CameraFlag::Overlay);
+		viewDesc.isHDR = camera->getFlags().isSet(CameraFlag::HDR);
+		viewDesc.noLighting = camera->getFlags().isSet(CameraFlag::NoLighting);
+		viewDesc.triggerCallbacks = true;
+		viewDesc.runPostProcessing = true;
+		viewDesc.renderingReflections = false;
 
-			viewDesc.cullFrustum = camera->getWorldFrustum();
-			viewDesc.visibleLayers = camera->getLayers();
-			viewDesc.nearPlane = camera->getNearClipDistance();
-			viewDesc.farPlane = camera->getFarClipDistance();
-			viewDesc.flipView = false;
+		viewDesc.cullFrustum = camera->getWorldFrustum();
+		viewDesc.visibleLayers = camera->getLayers();
+		viewDesc.nearPlane = camera->getNearClipDistance();
+		viewDesc.farPlane = camera->getFarClipDistance();
+		viewDesc.flipView = false;
 
-			viewDesc.viewOrigin = camera->getPosition();
-			viewDesc.viewDirection = camera->getForward();
-			viewDesc.projTransform = camera->getProjectionMatrixRS();
-			viewDesc.viewTransform = camera->getViewMatrix();
-			viewDesc.projType = camera->getProjectionType();
+		viewDesc.viewOrigin = camera->getPosition();
+		viewDesc.viewDirection = camera->getForward();
+		viewDesc.projTransform = camera->getProjectionMatrixRS();
+		viewDesc.viewTransform = camera->getViewMatrix();
+		viewDesc.projType = camera->getProjectionType();
 
-			viewDesc.stateReduction = mOptions->stateReductionMode;
-			viewDesc.sceneCamera = camera;
+		viewDesc.stateReduction = mOptions->stateReductionMode;
+		viewDesc.sceneCamera = camera;
 
-			if (iterFind != mInfo.views.end())
-			{
-				output = iterFind->second;
-				output->setView(viewDesc);
-			}
-			else
-			{
-				output = bs_new<RendererView>(viewDesc);
-				mInfo.views[camera] = output;
-			}
+		return viewDesc;
+	}
 
-			output->setPostProcessSettings(camera->getPostProcessSettings());
-		}
+	void RendererScene::updateCameraRenderTargets(Camera* camera)
+	{
+		SPtr<RenderTarget> renderTarget = camera->getViewport()->getTarget();
 
 		// Remove from render target list
 		int rtChanged = 0; // 0 - No RT, 1 - RT found, 2 - RT changed
@@ -590,13 +585,11 @@ namespace bs {	namespace ct
 
 			for (auto& camerasPerTarget : mInfo.renderTargets)
 			{
-				Vector<const Camera*>& cameras = camerasPerTarget.cameras;
+				Vector<Camera*>& cameras = camerasPerTarget.cameras;
 
 				std::sort(begin(cameras), end(cameras), cameraComparer);
 			}
 		}
-
-		return output;
 	}
 
 	void RendererScene::refreshSamplerOverrides(bool force)

+ 160 - 76
Source/RenderBeast/Source/BsShadowRendering.cpp

@@ -85,7 +85,7 @@ namespace bs { namespace ct
 		gRendererUtility().setPassParams(mParamsSet);
 	}
 
-	void ShadowMapInfo::updateNormArea(UINT32 atlasSize)
+	void ShadowInfo::updateNormArea(UINT32 atlasSize)
 	{
 		normArea.x = area.x / (float)atlasSize;
 		normArea.y = area.y / (float)atlasSize;
@@ -231,13 +231,17 @@ namespace bs { namespace ct
 
 		const SceneInfo& sceneInfo = scene.getSceneInfo();
 		
-		// Clear all dynamic light atlases
-		mSpotLightShadowInfos.clear();
-		mRadialLightShadowInfos.clear();
+		// Clear all transient data from last frame
+		mShadowInfos.clear();
+
+		mSpotLightShadows.resize(sceneInfo.spotLights.size());
+		mRadialLightShadows.resize(sceneInfo.radialLights.size());
+		mDirectionalLightShadows.resize(sceneInfo.directionalLights.size());
 
 		mSpotLightShadowOptions.clear();
 		mRadialLightShadowOptions.clear();
 
+		// Clear all dynamic light atlases
 		for (auto& entry : mCascadedShadowMaps)
 			entry.clear();
 
@@ -248,66 +252,88 @@ namespace bs { namespace ct
 			entry.clear();
 
 		// Determine shadow map sizes and sort them
+		UINT32 shadowInfoCount = 0;
 		for (UINT32 i = 0; i < (UINT32)sceneInfo.spotLights.size(); ++i)
 		{
-			scene.setLightShadowMapIdx(i, LightType::Spot, -1);
+			const RendererLight& light = sceneInfo.spotLights[i];
 
 			// 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])
+			if (!light.internal->getCastsShadow() || !sceneInfo.spotLightVisibility[i])
 				continue;
 
-			const RendererLight& light = sceneInfo.spotLights[i];
-
-			mSpotLightShadowOptions.push_back(ShadowMapOptions());
-			ShadowMapOptions& options = mSpotLightShadowOptions.back();
+			ShadowMapOptions options;
 			options.lightIdx = i;
 
-			calcShadowMapProperties(light, scene, options.mapSize, options.fadePercent);
+			float maxFadePercent;
+			calcShadowMapProperties(light, scene, options.mapSize, options.fadePercents, maxFadePercent);
+
+			// Don't render shadow maps that will end up nearly completely faded out
+			if (maxFadePercent < 0.005f)
+				continue;
+
+			mSpotLightShadowOptions.push_back(options);
+			mSpotLightShadows[i].startIdx = shadowInfoCount;
+			mSpotLightShadows[i].numShadows = 0;
+
+			shadowInfoCount++; // For now, always a single fully dynamic shadow for a single light, but that may change
 		}
 
 		for (UINT32 i = 0; i < (UINT32)sceneInfo.radialLights.size(); ++i)
 		{
-			scene.setLightShadowMapIdx(i, LightType::Radial, -1);
+			const RendererLight& light = sceneInfo.radialLights[i];
 
 			// 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])
+			if (!light.internal->getCastsShadow() || !sceneInfo.radialLightVisibility[i])
 				continue;
 
-			const RendererLight& light = sceneInfo.radialLights[i];
-
-			mRadialLightShadowOptions.push_back(ShadowMapOptions());
-			ShadowMapOptions& options = mRadialLightShadowOptions.back();
+			ShadowMapOptions options;
 			options.lightIdx = i;
 
-			calcShadowMapProperties(light, scene, options.mapSize, options.fadePercent);
+			float maxFadePercent;
+			calcShadowMapProperties(light, scene, options.mapSize, options.fadePercents, maxFadePercent);
+
+			// Don't render shadow maps that will end up nearly completely faded out
+			if (maxFadePercent < 0.005f)
+				continue;
+
+			mRadialLightShadowOptions.push_back(options);
+			mRadialLightShadows[i].startIdx = shadowInfoCount;
+			mRadialLightShadows[i].numShadows = 0;
+
+			shadowInfoCount++; // For now, always a single fully dynamic shadow for a single light, but that may change
 		}
 
 		// 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; } );
 
+		// Reserve space for shadow infos
+		mShadowInfos.resize(shadowInfoCount);
 
 		// Render shadow maps
 		for (UINT32 i = 0; i < (UINT32)sceneInfo.directionalLights.size(); ++i)
 		{
-			scene.setLightShadowMapIdx(i, LightType::Directional, -1);
+			const RendererLight& light = sceneInfo.directionalLights[i];
+
+			if (!light.internal->getCastsShadow())
+				return;
 
-			for(auto& entry : sceneInfo.views)
-				renderCascadedShadowMaps(*entry.second, sceneInfo.directionalLights[i], scene, frameInfo);
+			for (UINT32 j = 0; j < (UINT32)sceneInfo.views.size(); ++j)
+				renderCascadedShadowMaps(j, i, scene, frameInfo);
 		}
 
 		for(auto& entry : mSpotLightShadowOptions)
 		{
 			UINT32 lightIdx = entry.lightIdx;
-			renderSpotShadowMaps(sceneInfo.spotLights[lightIdx], entry.mapSize, scene, frameInfo);
+			renderSpotShadowMap(sceneInfo.spotLights[lightIdx], entry, scene, frameInfo);
 		}
 
 		for (auto& entry : mRadialLightShadowOptions)
 		{
 			UINT32 lightIdx = entry.lightIdx;
-			renderRadialShadowMaps(sceneInfo.radialLights[lightIdx], entry.mapSize, scene, frameInfo);
+			renderRadialShadowMap(sceneInfo.radialLights[lightIdx], entry, scene, frameInfo);
 		}
 		
 		// Deallocate unused textures
@@ -338,73 +364,74 @@ namespace bs { namespace ct
 		}
 	}
 
-	void ShadowRendering::renderCascadedShadowMaps(const RendererView& view, const RendererLight& rendererLight,
-		RendererScene& scene, const FrameInfo& frameInfo)
+	void ShadowRendering::renderCascadedShadowMaps(UINT32 viewIdx, UINT32 lightIdx, RendererScene& scene, 
+		const FrameInfo& frameInfo)
 	{
 		// Note: Currently I'm using spherical bounds for the cascaded frustum which might result in non-optimal usage
 		// of the shadow map. A different approach would be to generate a bounding box and then both adjust the aspect
 		// ratio (and therefore dimensions) of the shadow map, as well as rotate the camera so the visible area best fits
 		// in the map. It remains to be seen if this is viable.
+		const SceneInfo& sceneInfo = scene.getSceneInfo();
 
+		const RendererView* view = sceneInfo.views[viewIdx];
+		const RendererLight& rendererLight = sceneInfo.directionalLights[lightIdx];
 		Light* light = rendererLight.internal;
 
-		if (!light->getCastsShadow())
-			return;
-
-		const SceneInfo& sceneInfo = scene.getSceneInfo();
 		RenderAPI& rapi = RenderAPI::instance();
 
 		Vector3 lightDir = light->getRotation().zAxis();
 		SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
 
-		// Assuming all cascaded shadow maps are the same size
+		ShadowInfo shadowInfo;
+		shadowInfo.lightIdx = lightIdx;
+		shadowInfo.textureIdx = -1;
+
 		UINT32 mapSize = std::min(mShadowMapSize, MAX_ATLAS_SIZE);
 
-		UINT32 mapIdx = -1;
 		for (UINT32 i = 0; i < (UINT32)mCascadedShadowMaps.size(); i++)
 		{
 			ShadowCascadedMap& shadowMap = mCascadedShadowMaps[i];
 
 			if (!shadowMap.isUsed() && shadowMap.getSize() == mapSize)
 			{
-				mapIdx = i;
+				shadowInfo.textureIdx = i;
 				shadowMap.markAsUsed();
 
 				break;
 			}
 		}
 
-		if (mapIdx == -1)
+		if (shadowInfo.textureIdx == -1)
 		{
-			mapIdx = (UINT32)mCascadedShadowMaps.size();
+			shadowInfo.textureIdx = (UINT32)mCascadedShadowMaps.size();
 			mCascadedShadowMaps.push_back(ShadowCascadedMap(mapSize));
 
 			ShadowCascadedMap& shadowMap = mCascadedShadowMaps.back();
 			shadowMap.markAsUsed();
 		}
 
-		ShadowCascadedMap& shadowMap = mCascadedShadowMaps[mapIdx];
+		ShadowCascadedMap& shadowMap = mCascadedShadowMaps[shadowInfo.textureIdx];
 
 		Matrix4 viewMat = Matrix4::view(light->getPosition(), light->getRotation());
 		for (int i = 0; i < NUM_CASCADE_SPLITS; ++i)
 		{
 			Sphere frustumBounds;
-			ConvexVolume cascadeCullVolume = getCSMSplitFrustum(view, lightDir, i, NUM_CASCADE_SPLITS, frustumBounds);
+			ConvexVolume cascadeCullVolume = getCSMSplitFrustum(*view, lightDir, i, NUM_CASCADE_SPLITS, frustumBounds);
 
 			float orthoSize = frustumBounds.getRadius();
 			Matrix4 proj = Matrix4::projectionOrthographic(-orthoSize, orthoSize, -orthoSize, orthoSize, 0.0f, 1000.0f);
 			RenderAPI::instance().convertProjectionMatrix(proj, proj);
 
-			Matrix4 viewProj = proj * viewMat;
+			shadowInfo.shadowVPTransform = proj * viewMat;
 
 			float nearDist = 0.05f;
 			float farDist = light->getAttenuationRadius();
 			float depthRange = farDist - nearDist;
-			float depthBias = 0.0f; // TODO - determine optimal depth bias
+			float depthBias = getDepthBias(*light, depthRange, mapSize);
 
 			gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, depthBias);
 			gShadowParamsDef.gDepthRange.set(shadowParamsBuffer, depthRange);
-			gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, viewProj);
+			gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, shadowInfo.shadowVPTransform);
 
 			rapi.setRenderTarget(shadowMap.getTarget(i));
 			rapi.clearRenderTarget(FBT_DEPTH);
@@ -430,29 +457,33 @@ namespace bs { namespace ct
 							element.morphVertexDeclaration);
 				}
 			}
+
+			shadowMap.setShadowInfo(i, shadowInfo);
 		}
+
+		mDirectionalLightShadows[lightIdx] = shadowInfo.textureIdx;
 	}
 
-	void ShadowRendering::renderSpotShadowMaps(const RendererLight& rendererLight, UINT32 mapSize, RendererScene& scene,
-		const FrameInfo& frameInfo)
+	void ShadowRendering::renderSpotShadowMap(const RendererLight& rendererLight, const ShadowMapOptions& options,
+		RendererScene& scene, const FrameInfo& frameInfo)
 	{
 		Light* light = rendererLight.internal;
 
-		if (!light->getCastsShadow())
-			return;
-
 		const SceneInfo& sceneInfo = scene.getSceneInfo();
 		SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
 
-		ShadowMapInfo mapInfo;
+		ShadowInfo mapInfo;
+		mapInfo.fadePerView = options.fadePercents;
+		mapInfo.lightIdx = options.lightIdx;
+
 		bool foundSpace = false;
 		for (UINT32 i = 0; i < (UINT32)mDynamicShadowMaps.size(); i++)
 		{
 			ShadowMapAtlas& atlas = mDynamicShadowMaps[i];
 
-			if (atlas.addMap(mapSize, mapInfo.area, SHADOW_MAP_BORDER))
+			if (atlas.addMap(options.mapSize, mapInfo.area, SHADOW_MAP_BORDER))
 			{
-				mapInfo.atlasIdx = i;
+				mapInfo.textureIdx = i;
 
 				foundSpace = true;
 				break;
@@ -461,15 +492,15 @@ namespace bs { namespace ct
 
 		if (!foundSpace)
 		{
-			mapInfo.atlasIdx = (UINT32)mDynamicShadowMaps.size();
+			mapInfo.textureIdx = (UINT32)mDynamicShadowMaps.size();
 			mDynamicShadowMaps.push_back(ShadowMapAtlas(MAX_ATLAS_SIZE));
 
 			ShadowMapAtlas& atlas = mDynamicShadowMaps.back();
-			atlas.addMap(mapSize, mapInfo.area, SHADOW_MAP_BORDER);
+			atlas.addMap(options.mapSize, mapInfo.area, SHADOW_MAP_BORDER);
 		}
 
 		mapInfo.updateNormArea(MAX_ATLAS_SIZE);
-		ShadowMapAtlas& atlas = mDynamicShadowMaps[mapInfo.atlasIdx];
+		ShadowMapAtlas& atlas = mDynamicShadowMaps[mapInfo.textureIdx];
 
 		RenderAPI& rapi = RenderAPI::instance();
 		rapi.setRenderTarget(atlas.getTarget());
@@ -479,17 +510,17 @@ namespace bs { namespace ct
 		float nearDist = 0.05f;
 		float farDist = light->getAttenuationRadius();
 		float depthRange = farDist - nearDist;
-		float depthBias = 0.0f; // TODO - determine optimal depth bias
+		float depthBias = getDepthBias(*light, depthRange, options.mapSize);
 
 		Matrix4 view = Matrix4::view(light->getPosition(), light->getRotation());
 		Matrix4 proj = Matrix4::projectionPerspective(light->getSpotAngle(), 1.0f, 0.05f, light->getAttenuationRadius());
 		RenderAPI::instance().convertProjectionMatrix(proj, proj);
 
-		Matrix4 viewProj = proj * view;
+		mapInfo.shadowVPTransform = proj * view;
 
 		gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, depthBias);
 		gShadowParamsDef.gDepthRange.set(shadowParamsBuffer, depthRange);
-		gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, viewProj);
+		gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, mapInfo.shadowVPTransform);
 
 		mDepthNormalMat.bind(shadowParamsBuffer);
 
@@ -529,50 +560,56 @@ namespace bs { namespace ct
 
 		// Restore viewport
 		rapi.setViewport(Rect2(0.0f, 0.0f, 1.0f, 1.0f));
+
+		LightShadows& lightShadows = mSpotLightShadows[options.lightIdx];
+
+		mShadowInfos[lightShadows.startIdx + lightShadows.numShadows] = mapInfo;
+		lightShadows.numShadows++;
 	}
 
-	void ShadowRendering::renderRadialShadowMaps(const RendererLight& rendererLight, UINT32 mapSize, RendererScene& scene, 
-		const FrameInfo& frameInfo)
+	void ShadowRendering::renderRadialShadowMap(const RendererLight& rendererLight, 
+		const ShadowMapOptions& options, RendererScene& scene, const FrameInfo& frameInfo)
 	{
 		Light* light = rendererLight.internal;
 
-		if (!light->getCastsShadow())
-			return;
-
 		const SceneInfo& sceneInfo = scene.getSceneInfo();
 		SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
 		SPtr<GpuParamBlockBuffer> shadowCubeMatricesBuffer = gShadowCubeMatricesDef.createBuffer();
 		SPtr<GpuParamBlockBuffer> shadowCubeMasksBuffer = gShadowCubeMasksDef.createBuffer();
 
-		UINT32 mapIdx = -1;
+		ShadowInfo mapInfo;
+		mapInfo.lightIdx = options.lightIdx;
+		mapInfo.textureIdx = -1;
+		mapInfo.fadePerView = options.fadePercents;
+
 		for (UINT32 i = 0; i < (UINT32)mShadowCubemaps.size(); i++)
 		{
 			ShadowCubemap& cubemap = mShadowCubemaps[i];
 
-			if (!cubemap.isUsed() && cubemap.getSize() == mapSize)
+			if (!cubemap.isUsed() && cubemap.getSize() == options.mapSize)
 			{
-				mapIdx = i;
+				mapInfo.textureIdx = i;
 				cubemap.markAsUsed();
 
 				break;
 			}
 		}
 
-		if (mapIdx == -1)
+		if (mapInfo.textureIdx == -1)
 		{
-			mapIdx = (UINT32)mShadowCubemaps.size();
-			mShadowCubemaps.push_back(ShadowCubemap(mapSize));
+			mapInfo.textureIdx = (UINT32)mShadowCubemaps.size();
+			mShadowCubemaps.push_back(ShadowCubemap(options.mapSize));
 
 			ShadowCubemap& cubemap = mShadowCubemaps.back();
 			cubemap.markAsUsed();
 		}
 
-		ShadowCubemap& cubemap = mShadowCubemaps[mapIdx];
+		ShadowCubemap& cubemap = mShadowCubemaps[mapInfo.textureIdx];
 
 		float nearDist = 0.05f;
 		float farDist = light->getAttenuationRadius();
 		float depthRange = farDist - nearDist;
-		float depthBias = 0.0f; // TODO - determine optimal depth bias
+		float depthBias = getDepthBias(*light, depthRange, options.mapSize);
 
 		Matrix4 proj = Matrix4::projectionPerspective(Degree(90.0f), 1.0f, 0.05f, light->getAttenuationRadius());
 		RenderAPI::instance().convertProjectionMatrix(proj, proj);
@@ -621,7 +658,9 @@ namespace bs { namespace ct
 			Matrix3 viewRotationMat = Matrix3(right, up, forward);
 
 			Matrix4 view = Matrix4(viewRotationMat) * viewOffsetMat;
-			gShadowCubeMatricesDef.gFaceVPMatrices.set(shadowCubeMatricesBuffer, proj * view, i);
+			mapInfo.shadowVPTransforms[i] = proj * view;
+
+			gShadowCubeMatricesDef.gFaceVPMatrices.set(shadowCubeMatricesBuffer, mapInfo.shadowVPTransforms[i], i);
 
 			// Calculate world frustum for culling
 			const Vector<Plane>& frustumPlanes = localFrustum.getPlanes();
@@ -675,18 +714,24 @@ namespace bs { namespace ct
 						element.morphVertexDeclaration);
 			}
 		}
+
+		LightShadows& lightShadows = mRadialLightShadows[options.lightIdx];
+
+		mShadowInfos[lightShadows.startIdx + lightShadows.numShadows] = mapInfo;
+		lightShadows.numShadows++;
 	}
 
 	void ShadowRendering::calcShadowMapProperties(const RendererLight& light, RendererScene& scene, UINT32& size, 
-		float& fadePercent) const
+		SmallVector<float, 4>& fadePercents, float& maxFadePercent) const
 	{
 		const SceneInfo& sceneInfo = scene.getSceneInfo();
 
 		// Find a view in which the light has the largest radius
-		float maxRadiusPercent;
-		for (auto& entry : sceneInfo.views)
+		float maxRadiusPercent = 0.0f;
+		maxFadePercent = 0.0f;
+		for (int i = 0; i < (int)sceneInfo.views.size(); ++i)
 		{
-			const RendererViewProperties& viewProps = entry.second->getProperties();
+			const RendererViewProperties& viewProps = sceneInfo.views[i]->getProperties();
 
 			float viewScaleX = viewProps.projTransform[0][0] * 0.5f;
 			float viewScaleY = viewProps.projTransform[1][1] * 0.5f;
@@ -700,19 +745,23 @@ namespace bs { namespace ct
 			// Radius of light bounds in percent of the view surface
 			float radiusPercent = radiusNDC * viewScale;
 			maxRadiusPercent = std::max(maxRadiusPercent, radiusPercent);
+
+			float optimalMapSize = mShadowMapSize * radiusPercent;
+			
+			// Determine if the shadow should fade out
+			float fadePercent = Math::lerp01(optimalMapSize, (float)MIN_SHADOW_MAP_SIZE, (float)SHADOW_MAP_FADE_SIZE);
+			fadePercents.push_back(fadePercent);
+			maxFadePercent = std::max(maxFadePercent, fadePercent);
 		}
 
 		// 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);
+		float maxOptimalMapSize = mShadowMapSize * maxRadiusPercent;
+		UINT32 effectiveMapSize = Bitwise::nextPow2((UINT32)maxOptimalMapSize);
 		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, 
@@ -890,4 +939,39 @@ namespace bs { namespace ct
 
 		return near + (far - near) * scale;
 	}
+
+	float ShadowRendering::getDepthBias(const Light& light, float depthRange, UINT32 mapSize)
+	{
+		const static float RADIAL_LIGHT_BIAS = 0.05f;
+		const static float SPOT_DEPTH_BIAS = 1.0f;
+		const static float DIR_DEPTH_BIAS = 5.0f;
+		const static float DEFAULT_RESOLUTION = 512.0f;
+		
+		// Increase bias if map size smaller than some resolution
+		float resolutionScale;
+		
+		if (light.getType() == LightType::Directional)
+			resolutionScale = light.getBounds().getRadius() / (float)mapSize;
+		else
+			resolutionScale = DEFAULT_RESOLUTION / (float)mapSize;
+
+		// Decrease bias with larger depth range
+		float rangeScale = 1.0f / depthRange;
+		
+		float defaultBias = 1.0f;
+		switch(light.getType())
+		{
+		case LightType::Directional: 
+			defaultBias = DIR_DEPTH_BIAS;
+			break;
+		case LightType::Radial: 
+			defaultBias = RADIAL_LIGHT_BIAS;
+			break;
+		case LightType::Spot: 
+			defaultBias = SPOT_DEPTH_BIAS;
+			break;
+		}
+		
+		return defaultBias * light.getShadowBias() *resolutionScale * rangeScale;
+	}
 }}