Kaynağa Gözat

Laid much of the foundation for shadow map depth rendering

BearishSun 8 yıl önce
ebeveyn
işleme
84eb9fc38b

+ 4 - 0
Data/Raw/Engine/DataList.json

@@ -261,6 +261,10 @@
         {
             "Path": "ShadowDepthNormal.bsl",
             "UUID": "c9b64475-375d-410e-8cd4-e1b1181318d4"
+        },
+        {
+            "Path": "ShadowDepthDirectional.bsl",
+            "UUID": "acd0f016-8084-4b32-806c-66a85b34ee5a"
         }
     ],
     "Skin": [

+ 5 - 12
Data/Raw/Engine/Includes/ShadowDepthBase.bslinc

@@ -1,5 +1,3 @@
-#include "$ENGINE$\GBufferOutput.bslinc"
-#include "$ENGINE$\PerCameraData.bslinc"
 #include "$ENGINE$\PerObjectData.bslinc"
 
 #include "$ENGINE$\SkinnedVertexInput.bslinc"
@@ -26,6 +24,7 @@ Technique : base("ShadowDepthBase") =
 			
 			cbuffer ShadowParams
 			{
+				float4x4 gMatViewProj;
 				float gDepthBias;
 				float gDepthRange;
 			};
@@ -44,8 +43,10 @@ Technique : base("ShadowDepthBase") =
 
 				// Output linear depth in range [0, 1]
 				// TODO - Handle case for backends using [-1, 1] depth range
-				float linearDepth = clipPos.z * gDepthRange + gDepthBias;
-				clipPos.z = linearDepth * clipPos.w;
+				#if LINEAR_DEPTH_RANGE
+					float linearDepth = clipPos.z * gDepthRange + gDepthBias;
+					clipPos.z = linearDepth * clipPos.w;
+				#endif
 			}		
 		
 			ShadowVStoFS main(VertexInput_PO input)
@@ -71,8 +72,6 @@ Technique : base("ShadowDepthBase") =
 };
 
 Technique 
- : inherits("GBufferOutput")
- : inherits("PerCameraData")
  : inherits("PerObjectData")
  : inherits("NormalVertexInput")
  : inherits("ShadowDepthBase")
@@ -81,8 +80,6 @@ Technique
 };
 
 Technique 
- : inherits("GBufferOutput")
- : inherits("PerCameraData")
  : inherits("PerObjectData")
  : inherits("SkinnedVertexInput")
  : inherits("ShadowDepthBase")
@@ -92,8 +89,6 @@ Technique
 };
 
 Technique 
- : inherits("GBufferOutput")
- : inherits("PerCameraData")
  : inherits("PerObjectData")
  : inherits("MorphVertexInput")
  : inherits("ShadowDepthBase")
@@ -103,8 +98,6 @@ Technique
 };
 
 Technique 
- : inherits("GBufferOutput")
- : inherits("PerCameraData")
  : inherits("PerObjectData")
  : inherits("SkinnedMorphVertexInput")
  : inherits("ShadowDepthBase")

+ 5 - 2
Data/Raw/Engine/Shaders/ShadowDepthCube.bsl

@@ -13,11 +13,14 @@ Technique : base("ShadowDepth") =
 				uint targetIdx : SV_RenderTargetArrayIndex;
 			};
 
-			cbuffer ShadowCubeParams
+			cbuffer ShadowCubeMatrices
 			{
 				float4x4 gFaceVPMatrices[6];
+			};
+			
+			cbuffer ShadowCubeMasks
+			{
 				uint gFaceMasks[6];
-				uint padding[2];
 			};
 			
 			[maxvertexcount(18)]

+ 4 - 0
Data/Raw/Engine/Shaders/ShadowDepthDirectional.bsl

@@ -0,0 +1,4 @@
+#include "$ENGINE$\ShadowDepthBase.bslinc"
+
+Technique : base("ShadowDepth") =
+{ };

+ 1 - 0
Data/Raw/Engine/Shaders/ShadowDepthNormal.bsl

@@ -1,3 +1,4 @@
+#define LINEAR_DEPTH_RANGE 1
 #include "$ENGINE$\ShadowDepthBase.bslinc"
 
 Technique : base("ShadowDepth") =

+ 1 - 1
Source/BansheeCore/Include/BsCoreThread.h

@@ -99,7 +99,7 @@ namespace bs
 		AsyncOp queueReturnCommand(std::function<void(AsyncOp&)> commandCallback, CoreThreadQueueFlags flags = CTQF_Default);
 
 		/**
-		 * Queues a new command that will be to the global command queue. 
+		 * Queues a new command that will be added to the global command queue. 
 		 * 	
 		 * @param[in]	commandCallback		Command to queue.
 		 * @param[in]	flags				Flags that further control command submission.

+ 1 - 1
Source/BansheeCore/Source/BsRenderable.cpp

@@ -577,7 +577,7 @@ namespace bs
 
 		if (mAnimType == RenderableAnimType::Morph || mAnimType == RenderableAnimType::SkinnedMorph)
 		{
-			if (mMorphShapeVersion < animInfo->morphShapeInfo.version)
+			if (mMorphShapeVersion != animInfo->morphShapeInfo.version)
 			{
 				SPtr<MeshData> meshData = animInfo->morphShapeInfo.meshData;
 

+ 8 - 0
Source/BansheeUtility/Include/BsTextureAtlasLayout.h

@@ -51,6 +51,12 @@ namespace bs
 		 */
 		bool addElement(UINT32 width, UINT32 height, UINT32& x, UINT32& y);
 
+		/** Removes all entries from the layout. */
+		void clear();
+
+		/** Checks have any elements been added to the layout. */
+		bool isEmpty() const { return mNodes.size() == 1; }
+
 		/** Returns the width of the atlas texture, in pixels. */
 		UINT32 getWidth() const { return mWidth; }
 
@@ -73,6 +79,8 @@ namespace bs
 		 */
 		bool addToNode(UINT32 nodeIdx, UINT32 width, UINT32 height, UINT32& x, UINT32& y, bool allowGrowth);
 
+		UINT32 mInitialWidth;
+		UINT32 mInitialHeight;
 		UINT32 mWidth;
 		UINT32 mHeight;
 		UINT32 mMaxWidth;

+ 13 - 3
Source/BansheeUtility/Source/BsTextureAtlasLayout.cpp

@@ -15,11 +15,12 @@ namespace bs
 	{ }
 
 	TextureAtlasLayout::TextureAtlasLayout()
-		: mWidth(0), mHeight(0), mMaxWidth(0), mMaxHeight(0), mPow2(false)
+		: mInitialWidth(0), mInitialHeight(0), mWidth(0), mHeight(0), mMaxWidth(0), mMaxHeight(0), mPow2(false)
 	{ }
 
 	TextureAtlasLayout::TextureAtlasLayout(UINT32 width, UINT32 height, UINT32 maxWidth, UINT32 maxHeight, bool pow2)
-		: mWidth(width), mHeight(height), mMaxWidth(maxWidth), mMaxHeight(maxHeight), mPow2(pow2)
+		: mInitialWidth(width), mInitialHeight(height), mWidth(width), mHeight(height), mMaxWidth(maxWidth)
+		, mMaxHeight(maxHeight), mPow2(pow2)
 	{
 		mNodes.push_back(TexAtlasNode(0, 0, maxWidth, maxHeight));
 	}
@@ -55,6 +56,15 @@ namespace bs
 		return true;
 	}
 
+	void TextureAtlasLayout::clear()
+	{
+		mNodes.clear();
+		mNodes.push_back(TexAtlasNode(0, 0, mWidth, mHeight));
+
+		mWidth = mInitialWidth;
+		mHeight = mInitialHeight;
+	}
+
 	bool TextureAtlasLayout::addToNode(UINT32 nodeIdx, UINT32 width, UINT32 height, UINT32& x, UINT32& y, bool allowGrowth)
 	{
 		TexAtlasNode* node = &mNodes[nodeIdx];
@@ -119,7 +129,7 @@ namespace bs
 	{
 		for (size_t i = 0; i < elements.size(); i++)
 		{
-			elements[i].output.idx = i; // Preserve original index before sorting
+			elements[i].output.idx = (UINT32)i; // Preserve original index before sorting
 			elements[i].output.page = -1;
 		}
 

+ 5 - 2
Source/RenderBeast/Include/BsGpuResourcePool.h

@@ -144,10 +144,11 @@ namespace bs { namespace ct
 		 * @param[in]	usage		Usage flags that control in which way is the texture going to be used.
 		 * @param[in]	samples		If higher than 1, texture containing multiple samples per pixel is created.
 		 * @param[in]	hwGamma		Should the written pixels be gamma corrected.
+		 * @param[in]	arraySize	Number of textures in a texture array. Specify 1 for no array.
 		 * @return					Descriptor that is accepted by RenderTexturePool.
 		 */
 		static POOLED_RENDER_TEXTURE_DESC create2D(PixelFormat format, UINT32 width, UINT32 height, 
-			INT32 usage = TU_STATIC, UINT32 samples = 0, bool hwGamma = false);
+			INT32 usage = TU_STATIC, UINT32 samples = 0, bool hwGamma = false, UINT32 arraySize = 1);
 
 		/**
 		 * Creates a descriptor for a three dimensional render texture.
@@ -169,10 +170,11 @@ namespace bs { namespace ct
 		 * @param[in]	width		Width of the render texture, in pixels.
 		 * @param[in]	height		Height of the render texture, in pixels.
 		 * @param[in]	usage		Usage flags that control in which way is the texture going to be used.
+		 * @param[in]	arraySize	Number of textures in a texture array. Specify 1 for no array.
 		 * @return					Descriptor that is accepted by RenderTexturePool.
 		 */
 		static POOLED_RENDER_TEXTURE_DESC createCube(PixelFormat format, UINT32 width, UINT32 height,
-			INT32 usage = TU_STATIC);
+			INT32 usage = TU_STATIC, UINT32 arraySize = 1);
 
 	private:
 		friend class GpuResourcePool;
@@ -185,6 +187,7 @@ namespace bs { namespace ct
 		TextureUsage flag;
 		TextureType type;
 		bool hwGamma;
+		UINT32 arraySize;
 	};
 
 	/** Structure used for describing a pooled storage buffer. */

+ 3 - 6
Source/RenderBeast/Include/BsLightRendering.h

@@ -35,12 +35,9 @@ namespace bs { namespace ct
 
 		/** Populates the structure with light parameters. */
 		void getParameters(LightData& output) const;
-
-		/** Gets the internal light representation. */
-		Light* getInternal() const { return mInternal; }
-
-	private:
-		Light* mInternal;
+		
+		Light* internal;
+		UINT32 shadowMapIndex;
 	};
 
 	/** Contains GPU buffers used by the renderer to manipulate lights. */

+ 11 - 16
Source/RenderBeast/Include/BsRenderBeast.h

@@ -32,11 +32,20 @@ namespace bs
 	static StringID RPS_GBufferDepth = "GBufferDepth";
 	static StringID RPS_BoneMatrices = "BoneMatrices";
 
+	/** Contains information global to an entire frame. */
+	struct FrameInfo
+	{
+		FrameInfo(float timeDelta, const RendererAnimationData& animData)
+			:timeDelta(timeDelta), animData(animData)
+		{ }
+
+		float timeDelta;
+		const RendererAnimationData& animData;
+	};
+
 	/**
 	 * Default renderer for Banshee. Performs frustum culling, sorting and renders all scene objects while applying
 	 * lighting, shadowing, special effects and post-processing.
-	 *
-	 * @note	Sim thread unless otherwise noted.
 	 */
 	class RenderBeast : public Renderer
 	{
@@ -47,17 +56,6 @@ namespace bs
 			UINT32 matVersion;
 		};
 
-		/** Contains information global to an entire frame. */
-		struct FrameInfo
-		{
-			FrameInfo(float timeDelta, const RendererAnimationData& animData)
-				:timeDelta(timeDelta), animData(animData)
-			{ }
-
-			float timeDelta;
-			const RendererAnimationData& animData;
-		};
-
 	public:
 		RenderBeast();
 		~RenderBeast() { }
@@ -201,9 +199,6 @@ namespace bs
 
 		// Scene data
 		SPtr<RendererScene> mScene;
-		Vector<bool> mRenderableVisibility; // Transient
-		Vector<bool> mRadialLightVisibility; // Transient
-		Vector<bool> mSpotLightVisibility; // Transient
 
 		//// Reflection probes
 		Vector<bool> mCubemapArrayUsedSlots;

+ 6 - 0
Source/RenderBeast/Include/BsRenderBeastOptions.h

@@ -39,6 +39,12 @@ namespace bs { namespace ct
 		 * changes. Sorting by material can reduce CPU usage but could increase overdraw.
 		 */
 		StateReduction stateReductionMode = StateReduction::Distance;
+
+		/**
+		 * Determines the maximum shadow map size, in pixels. The system might decide to use smaller resolution maps for
+		 * shadows far away, but will never increase the resolution past the provided value.
+		 */
+		UINT32 shadowMapSize = 2048;
 	};
 
 	/** @} */

+ 25 - 0
Source/RenderBeast/Include/BsRendererScene.h

@@ -7,6 +7,7 @@
 #include "BsSamplerOverrides.h"
 #include "BsLightRendering.h"
 #include "BsRendererView.h"
+#include "BsLight.h"
 
 namespace bs 
 { 
@@ -14,6 +15,8 @@ namespace bs
 
 	namespace ct
 	{
+		struct FrameInfo;
+
 	/** @addtogroup RenderBeast
 	 *  @{
 	 */
@@ -39,6 +42,15 @@ namespace bs
 		// Reflection probes
 		Vector<RendererReflectionProbe> reflProbes;
 		Vector<Sphere> reflProbeWorldBounds;
+
+		// Buffers for various transient data that gets rebuilt every frame
+		//// Rebuilt every frame
+		mutable Vector<bool> renderableReady;
+
+		//// Rebuilt for every set of views
+		mutable Vector<bool> renderableVisibility;
+		mutable Vector<bool> radialLightVisibility;
+		mutable Vector<bool> spotLightVisibility;
 	};
 
 	/** Contains information about the scene (e.g. renderables, lights, cameras) required by the renderer. */
@@ -84,6 +96,9 @@ 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);
 
@@ -103,6 +118,16 @@ namespace bs
 		 *						was detected or not.
 		 */
 		void refreshSamplerOverrides(bool force = false);
+
+		/**
+		 * Performs necessary steps to make a renderable ready for rendering. This must be called at least once every frame,
+		 * for every renderable that will be drawn. Multiple calls for the same renderable during a single frame will result
+		 * in a no-op.
+		 * 
+		 * @param[in]	idx			Index of the renderable to prepare.
+		 * @param[in]	frameInfo	Global information describing the current frame.
+		 */
+		void prepareRenderable(UINT32 idx, const FrameInfo& frameInfo);
 	private:
 		/** 
 		 * Updates (or adds) renderer specific data for the specified camera. Should be called whenever camera properties

+ 224 - 3
Source/RenderBeast/Include/BsShadowRendering.h

@@ -6,21 +6,224 @@
 #include "BsModule.h"
 #include "BsMatrix4.h"
 #include "BsConvexVolume.h"
+#include "BsParamBlocks.h"
+#include "BsRendererMaterial.h"
+#include "BsTextureAtlasLayout.h"
 
 namespace bs { namespace ct
 {
+	struct FrameInfo;
+	class RendererLight;
+	class RendererScene;
+
 	/** @addtogroup RenderBeast
 	 *  @{
 	 */
 
-	// TODO - Define normal and omni vertex shaders and their params
-	// TODO - Move renderable objects from RenderBeast into a separate object so I can pass them here?
-	//  - SceneInfo?
+	/** Number of frustum splits when rendering cascaded shadow maps. */
+	const UINT32 NUM_CASCADE_SPLITS = 4;
+
+	BS_PARAM_BLOCK_BEGIN(ShadowParamsDef)
+		BS_PARAM_BLOCK_ENTRY(Matrix4, gMatViewProj)
+		BS_PARAM_BLOCK_ENTRY(float, gDepthBias)
+		BS_PARAM_BLOCK_ENTRY(float, gDepthRange)
+	BS_PARAM_BLOCK_END
+
+	extern ShadowParamsDef gShadowParamsDef;
+	
+	/** Material used for rendering a single face of a shadow map. */
+	class ShadowDepthNormalMat : public RendererMaterial<ShadowDepthNormalMat>
+	{
+		RMAT_DEF("ShadowDepthNormal.bsl");
+
+	public:
+		ShadowDepthNormalMat();
+
+		/** Binds the material to the pipeline, ready to be used on subsequent draw calls. */
+		void bind(const SPtr<GpuParamBlockBuffer>& shadowParams);
+
+		/** Sets a new buffer that determines per-object properties. */
+		void setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams);
+	};
+
+	/** Material used for rendering a single face of a shadow map, for a directional light. */
+	class ShadowDepthDirectionalMat : public RendererMaterial<ShadowDepthDirectionalMat>
+	{
+		RMAT_DEF("ShadowDepthDirectional.bsl");
+
+	public:
+		ShadowDepthDirectionalMat();
+
+		/** Binds the material to the pipeline, ready to be used on subsequent draw calls. */
+		void bind(const SPtr<GpuParamBlockBuffer>& shadowParams);
+
+		/** Sets a new buffer that determines per-object properties. */
+		void setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams);
+	};
+
+	BS_PARAM_BLOCK_BEGIN(ShadowCubeMatricesDef)
+		BS_PARAM_BLOCK_ENTRY_ARRAY(Matrix4, gFaceVPMatrices, 6)
+	BS_PARAM_BLOCK_END
+
+	extern ShadowCubeMatricesDef gShadowCubeMatricesDef;
+
+	BS_PARAM_BLOCK_BEGIN(ShadowCubeMasksDef)
+		BS_PARAM_BLOCK_ENTRY_ARRAY(int, gFaceMasks, 6)
+	BS_PARAM_BLOCK_END
+
+	extern ShadowCubeMasksDef gShadowCubeMasksDef;
+
+	/** Material used for rendering an omni directional cube shadow map. */
+	class ShadowDepthCubeMat : public RendererMaterial<ShadowDepthCubeMat>
+	{
+		RMAT_DEF("ShadowDepthCube.bsl");
+
+	public:
+		ShadowDepthCubeMat();
+
+		/** Binds the material to the pipeline, ready to be used on subsequent draw calls. */
+		void bind(const SPtr<GpuParamBlockBuffer>& shadowParams, const SPtr<GpuParamBlockBuffer>& shadowCubeParams);
+
+		/** Sets a new buffer that determines per-object properties. */
+		void setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams, 
+			const SPtr<GpuParamBlockBuffer>& shadowCubeMasks);
+	};
+
+	/** Information about a single shadow map in a shadow map atlas. */
+	struct ShadowMapInfo
+	{
+		/** 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;
+	};
+
+	/** 
+	 * Contains a texture that serves as an atlas for one or multiple shadow maps. Provides methods for inserting new maps
+	 * in the atlas. 
+	 */
+	class ShadowMapAtlas
+	{
+	public:
+		ShadowMapAtlas(UINT32 size);
+		~ShadowMapAtlas();
+
+		/** 
+		 * Registers a new map in the shadow map atlas. Returns true if the map fits in the atlas, or false otherwise.
+		 * Resets the last used counter to zero.
+		 */
+		bool addMap(UINT32 size, Rect2I& area, UINT32 border = 4);
+
+		/** Clears all shadow maps from the atlas. Increments the last used counter.*/
+		void clear();
+
+		/** Checks have any maps been added to the atlas. */
+		bool isEmpty() const;
+
+		/** 
+		 * Returns the value of the last used counter. See addMap() and clear() for information on how the counter is
+		 * incremented/decremented.
+		 */
+		UINT32 getLastUsedCounter() const { return mLastUsedCounter; }
+
+		/** Returns the bindable atlas texture. */
+		SPtr<Texture> getTexture() const;
+
+		/** Returns the render target that allows you to render into the atlas. */
+		SPtr<RenderTexture> getTarget() const;
+
+	private:
+		SPtr<PooledRenderTexture> mAtlas;
+
+		TextureAtlasLayout mLayout;
+		UINT32 mLastUsedCounter;
+	};
+
+	/** Contains common code for different shadow map types. */
+	class ShadowMapBase
+	{
+	public:
+		ShadowMapBase(UINT32 size);
+		virtual ~ShadowMapBase() {}
+
+		/** Returns the bindable shadow map texture. */
+		SPtr<Texture> getTexture() const;
+
+		/** Returns the size of a single face of the shadow map texture, in pixels. */
+		UINT32 getSize() const { return mSize; }
+
+		/** Makes the shadow map available for re-use and increments the counter returned by getLastUsedCounter(). */
+		void clear() { mIsUsed = false; mLastUsedCounter++; }
+
+		/** Marks the shadow map as used and resets the last used counter to zero. */
+		void markAsUsed() { mIsUsed = true; mLastUsedCounter = 0; }
+
+		/** Returns true if the object is storing a valid shadow map. */
+		bool isUsed() const { return mIsUsed; }
+
+		/** 
+		 * Returns the value of the last used counter. See incrementUseCounter() and markAsUsed() for information on how is
+		 * the counter incremented/decremented.
+		 */
+		UINT32 getLastUsedCounter() const { return mLastUsedCounter; }
+
+	protected:
+		SPtr<PooledRenderTexture> mShadowMap;
+		UINT32 mSize;
+
+		bool mIsUsed;
+		UINT32 mLastUsedCounter;
+	};
+
+	/** Contains a cubemap for storing an omnidirectional cubemap. */
+	class ShadowCubemap : public ShadowMapBase
+	{
+	public:
+		ShadowCubemap(UINT32 size);
+		~ShadowCubemap();
+
+		/** Returns a render target encompassing all six faces of the shadow cubemap. */
+		SPtr<RenderTexture> getTarget() const;
+	};
+
+	/** Contains a texture required for rendering cascaded shadow maps. */
+	class ShadowCascadedMap : public ShadowMapBase
+	{
+	public:
+		ShadowCascadedMap(UINT32 size);
+		~ShadowCascadedMap();
+
+		/** Returns a render target that allows rendering into a specific cascade of the cascaded shadow map. */
+		SPtr<RenderTexture> getTarget(UINT32 cascadeIdx) const;
+	private:
+
+		SPtr<RenderTexture> mTargets[NUM_CASCADE_SPLITS];
+	};
 
 	/** Provides functionality for rendering shadow maps. */
 	class ShadowRendering : public Module<ShadowRendering>
 	{
 	public:
+		ShadowRendering(UINT32 shadowMapSize);
+
+		/** For each visibile shadow casting light, renders a shadow map from its point of view. */
+		void renderShadowMaps(RendererScene& scene, const FrameInfo& frameInfo);
+
+		/** Changes the default shadow map size. Will cause all shadow maps to be rebuilt. */
+		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);
+
+		/** Renders shadow maps for the provided spot light. */
+		void renderSpotShadowMaps(const RendererLight& light, RendererScene& scene, const FrameInfo& frameInfo);
+
+		/** Renders shadow maps for the provided radial light. */
+		void renderRadialShadowMaps(const RendererLight& light, RendererScene& scene, const FrameInfo& frameInfo);
+
 		/**
 		 * Generates a frustum for a single cascade of a cascaded shadow map. Also outputs spherical bounds of the
 		 * split view frustum.
@@ -46,6 +249,24 @@ namespace bs { namespace ct
 		 * @return						Distance to the split position along the view direction.
 		 */
 		static float getCSMSplitDistance(const RendererView& view, UINT32 index, UINT32 numCascades);
+
+		/** Size of a single shadow map atlas, in pixels. */
+		static const UINT32 MAX_ATLAS_SIZE;
+
+		/** Determines how long will an unused shadow map atlas stay allocated, in frames. */
+		static const UINT32 MAX_UNUSED_FRAMES;
+
+		ShadowDepthNormalMat mDepthNormalMat;
+		ShadowDepthCubeMat mDepthCubeMat;
+		ShadowDepthDirectionalMat mDepthDirectionalMat;
+
+		UINT32 mShadowMapSize;
+
+		Vector<ShadowMapAtlas> mDynamicShadowMaps;
+		Vector<ShadowCascadedMap> mCascadedShadowMaps;
+		Vector<ShadowCubemap> mShadowCubemaps;
+
+		Vector<bool> mRenderableVisibility; // Transient
 	};
 
 	/* @} */

+ 11 - 4
Source/RenderBeast/Source/BsGpuResourcePool.cpp

@@ -69,6 +69,9 @@ namespace bs { namespace ct
 		texDesc.hwGamma = desc.hwGamma;
 		texDesc.numSamples = desc.numSamples;
 
+		if (desc.type != TEX_TYPE_3D)
+			texDesc.numArraySlices = desc.arraySize;
+
 		newTextureData->texture = TextureManager::instance().createTexture(texDesc);
 		
 		if ((desc.flag & (TU_RENDERTARGET | TU_DEPTHSTENCIL)) != 0)
@@ -79,7 +82,7 @@ namespace bs { namespace ct
 			{
 				rtDesc.colorSurfaces[0].texture = newTextureData->texture;
 				rtDesc.colorSurfaces[0].face = 0;
-				rtDesc.colorSurfaces[0].numFaces = 1;
+				rtDesc.colorSurfaces[0].numFaces = newTextureData->texture->getProperties().getNumFaces();
 				rtDesc.colorSurfaces[0].mipLevel = 0;
 			}
 
@@ -87,7 +90,7 @@ namespace bs { namespace ct
 			{
 				rtDesc.depthStencilSurface.texture = newTextureData->texture;
 				rtDesc.depthStencilSurface.face = 0;
-				rtDesc.depthStencilSurface.numFaces = 1;
+				rtDesc.depthStencilSurface.numFaces = newTextureData->texture->getProperties().getNumFaces();
 				rtDesc.depthStencilSurface.mipLevel = 0;
 			}
 
@@ -160,6 +163,7 @@ namespace bs { namespace ct
 					&& texProps.getDepth() == desc.depth)
 				|| (desc.type == TEX_TYPE_CUBE_MAP)
 				)
+			&& texProps.getNumArraySlices() == desc.arraySize
 			;
 
 		return match;
@@ -202,7 +206,7 @@ namespace bs { namespace ct
 	}
 
 	POOLED_RENDER_TEXTURE_DESC POOLED_RENDER_TEXTURE_DESC::create2D(PixelFormat format, UINT32 width, UINT32 height,
-		INT32 usage, UINT32 samples, bool hwGamma)
+		INT32 usage, UINT32 samples, bool hwGamma, UINT32 arraySize)
 	{
 		POOLED_RENDER_TEXTURE_DESC desc;
 		desc.width = width;
@@ -213,6 +217,7 @@ namespace bs { namespace ct
 		desc.flag = (TextureUsage)usage;
 		desc.hwGamma = hwGamma;
 		desc.type = TEX_TYPE_2D;
+		desc.arraySize = arraySize;
 
 		return desc;
 	}
@@ -229,12 +234,13 @@ namespace bs { namespace ct
 		desc.flag = (TextureUsage)usage;
 		desc.hwGamma = false;
 		desc.type = TEX_TYPE_3D;
+		desc.arraySize = 1;
 
 		return desc;
 	}
 
 	POOLED_RENDER_TEXTURE_DESC POOLED_RENDER_TEXTURE_DESC::createCube(PixelFormat format, UINT32 width, UINT32 height,
-		INT32 usage)
+		INT32 usage, UINT32 arraySize)
 	{
 		POOLED_RENDER_TEXTURE_DESC desc;
 		desc.width = width;
@@ -245,6 +251,7 @@ namespace bs { namespace ct
 		desc.flag = (TextureUsage)usage;
 		desc.hwGamma = false;
 		desc.type = TEX_TYPE_CUBE_MAP;
+		desc.arraySize = arraySize;
 
 		return desc;
 	}

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

@@ -18,20 +18,20 @@ namespace bs { namespace ct
 	TiledLightingParamDef gTiledLightingParamDef;
 
 	RendererLight::RendererLight(Light* light)
-		:mInternal(light)
+		:internal(light), shadowMapIndex(-1)
 	{ }
 
 	void RendererLight::getParameters(LightData& output) const
 	{
-		Radian spotAngle = Math::clamp(mInternal->getSpotAngle() * 0.5f, Degree(0), Degree(89));
-		Radian spotFalloffAngle = Math::clamp(mInternal->getSpotFalloffAngle() * 0.5f, Degree(0), (Degree)spotAngle);
-		Color color = mInternal->getColor();
-
-		output.position = mInternal->getPosition();
-		output.attRadius = mInternal->getBounds().getRadius();
-		output.srcRadius = mInternal->getSourceRadius();
-		output.direction = mInternal->getRotation().zAxis();
-		output.luminance = mInternal->getLuminance();
+		Radian spotAngle = Math::clamp(internal->getSpotAngle() * 0.5f, Degree(0), Degree(89));
+		Radian spotFalloffAngle = Math::clamp(internal->getSpotFalloffAngle() * 0.5f, Degree(0), (Degree)spotAngle);
+		Color color = internal->getColor();
+
+		output.position = internal->getPosition();
+		output.attRadius = internal->getBounds().getRadius();
+		output.srcRadius = internal->getSourceRadius();
+		output.direction = internal->getRotation().zAxis();
+		output.luminance = internal->getLuminance();
 		output.spotAngles.x = spotAngle.valueRadians();
 		output.spotAngles.y = Math::cos(output.spotAngles.x);
 		output.spotAngles.z = 1.0f / std::max(Math::cos(spotFalloffAngle) - output.spotAngles.y, 0.001f);
@@ -39,11 +39,11 @@ namespace bs { namespace ct
 		output.color = Vector3(color.r, color.g, color.b);
 
 		// If directional lights, convert angular radius in degrees to radians
-		if (mInternal->getType() == LightType::Directional)
+		if (internal->getType() == LightType::Directional)
 			output.srcRadius *= Math::DEG2RAD;
 
 		// Create position for fake attenuation for area spot lights (with disc center)
-		if (mInternal->getType() == LightType::Spot)
+		if (internal->getType() == LightType::Spot)
 			output.shiftedLightPosition = output.position - output.direction * (output.srcRadius / Math::tan(spotAngle * 0.5f));
 		else
 			output.shiftedLightPosition = output.position;

+ 21 - 22
Source/RenderBeast/Source/BsRenderBeast.cpp

@@ -91,7 +91,7 @@ namespace bs { namespace ct
 
 		GpuResourcePool::startUp();
 		PostProcessing::startUp();
-		ShadowRendering::startUp();
+		ShadowRendering::startUp(mCoreOptions->shadowMapSize);
 	}
 
 	void RenderBeast::destroyCore()
@@ -288,6 +288,7 @@ namespace bs { namespace ct
 		*mCoreOptions = options;
 
 		mScene->setOptions(mCoreOptions);
+		ShadowRendering::instance().setShadowMapSize(mCoreOptions->shadowMapSize);
 	}
 
 	void RenderBeast::renderAll() 
@@ -325,8 +326,14 @@ namespace bs { namespace ct
 		AnimationManager::instance().waitUntilComplete();
 		const RendererAnimationData& animData = AnimationManager::instance().getRendererData();
 		
+		sceneInfo.renderableReady.resize(sceneInfo.renderables.size(), false);
+		sceneInfo.renderableReady.assign(sceneInfo.renderables.size(), false);
+		
 		FrameInfo frameInfo(delta, animData);
 
+		// Render shadow maps
+		//ShadowRendering::instance().renderShadowMaps(*mScene, frameInfo);
+
 		// Update reflection probes
 		updateLightProbes(frameInfo);
 
@@ -365,11 +372,11 @@ namespace bs { namespace ct
 		const SceneInfo& sceneInfo = mScene->getSceneInfo();
 
 		// Generate render queues per camera
-		mRenderableVisibility.resize(sceneInfo.renderables.size(), false);
-		mRenderableVisibility.assign(mRenderableVisibility.size(), false);
+		sceneInfo.renderableVisibility.resize(sceneInfo.renderables.size(), false);
+		sceneInfo.renderableVisibility.assign(sceneInfo.renderableVisibility.size(), false);
 
 		for(UINT32 i = 0; i < numViews; i++)
-			views[i]->determineVisible(sceneInfo.renderables, sceneInfo.renderableCullInfos, &mRenderableVisibility);
+			views[i]->determineVisible(sceneInfo.renderables, sceneInfo.renderableCullInfos, &sceneInfo.renderableVisibility);
 
 		// Generate a list of lights and their GPU buffers
 		UINT32 numDirLights = (UINT32)sceneInfo.directionalLights.size();
@@ -381,14 +388,14 @@ namespace bs { namespace ct
 
 		UINT32 numRadialLights = (UINT32)sceneInfo.radialLights.size();
 		UINT32 numVisibleRadialLights = 0;
-		mRadialLightVisibility.resize(numRadialLights, false);
-		mRadialLightVisibility.assign(numRadialLights, false);
+		sceneInfo.radialLightVisibility.resize(numRadialLights, false);
+		sceneInfo.radialLightVisibility.assign(numRadialLights, false);
 		for (UINT32 i = 0; i < numViews; i++)
-			views[i]->calculateVisibility(sceneInfo.radialLightWorldBounds, mRadialLightVisibility);
+			views[i]->calculateVisibility(sceneInfo.radialLightWorldBounds, sceneInfo.radialLightVisibility);
 
 		for(UINT32 i = 0; i < numRadialLights; i++)
 		{
-			if (!mRadialLightVisibility[i])
+			if (!sceneInfo.radialLightVisibility[i])
 				continue;
 
 			mLightDataTemp.push_back(LightData());
@@ -398,14 +405,14 @@ namespace bs { namespace ct
 
 		UINT32 numSpotLights = (UINT32)sceneInfo.spotLights.size();
 		UINT32 numVisibleSpotLights = 0;
-		mSpotLightVisibility.resize(numSpotLights, false);
-		mSpotLightVisibility.assign(numSpotLights, false);
+		sceneInfo.spotLightVisibility.resize(numSpotLights, false);
+		sceneInfo.spotLightVisibility.assign(numSpotLights, false);
 		for (UINT32 i = 0; i < numViews; i++)
-			views[i]->calculateVisibility(sceneInfo.spotLightWorldBounds, mSpotLightVisibility);
+			views[i]->calculateVisibility(sceneInfo.spotLightWorldBounds, sceneInfo.spotLightVisibility);
 
 		for (UINT32 i = 0; i < numSpotLights; i++)
 		{
-			if (!mSpotLightVisibility[i])
+			if (!sceneInfo.spotLightVisibility[i])
 				continue;
 
 			mLightDataTemp.push_back(LightData());
@@ -450,18 +457,10 @@ namespace bs { namespace ct
 		UINT32 numRenderables = (UINT32)sceneInfo.renderables.size();
 		for (UINT32 i = 0; i < numRenderables; i++)
 		{
-			if (!mRenderableVisibility[i])
+			if (!sceneInfo.renderableVisibility[i])
 				continue;
 
-			// Note: Before uploading bone matrices perhaps check if they has actually been changed since last frame
-			sceneInfo.renderables[i]->renderable->updateAnimationBuffers(frameInfo.animData);
-
-			// Note: Could this step be moved in notifyRenderableUpdated, so it only triggers when material actually gets
-			// changed? Although it shouldn't matter much because if the internal versions keeping track of dirty params.
-			for (auto& element : sceneInfo.renderables[i]->elements)
-				element.material->updateParamsSet(element.params);
-
-			sceneInfo.renderables[i]->perObjectParamBuffer->flushToGPU();
+			mScene->prepareRenderable(i, frameInfo);
 		}
 
 		for (UINT32 i = 0; i < numViews; i++)

+ 37 - 3
Source/RenderBeast/Source/BsRendererScene.cpp

@@ -10,6 +10,7 @@
 #include "BsPass.h"
 #include "BsGpuParamsSet.h"
 #include "BsRenderBeastOptions.h"
+#include "BsRenderBeast.h"
 
 namespace bs {	namespace ct
 {
@@ -116,7 +117,7 @@ namespace bs {	namespace ct
 		UINT32 lightId = light->getRendererId();
 		if (light->getType() == LightType::Directional)
 		{
-			Light* lastLight = mInfo.directionalLights.back().getInternal();
+			Light* lastLight = mInfo.directionalLights.back().internal;
 			UINT32 lastLightId = lastLight->getRendererId();
 
 			if (lightId != lastLightId)
@@ -133,7 +134,7 @@ namespace bs {	namespace ct
 		{
 			if (light->getType() == LightType::Radial)
 			{
-				Light* lastLight = mInfo.radialLights.back().getInternal();
+				Light* lastLight = mInfo.radialLights.back().internal;
 				UINT32 lastLightId = lastLight->getRendererId();
 
 				if (lightId != lastLightId)
@@ -151,7 +152,7 @@ namespace bs {	namespace ct
 			}
 			else // Spot
 			{
-				Light* lastLight = mInfo.spotLights.back().getInternal();
+				Light* lastLight = mInfo.spotLights.back().internal;
 				UINT32 lastLightId = lastLight->getRendererId();
 
 				if (lightId != lastLightId)
@@ -396,6 +397,22 @@ 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];
@@ -665,4 +682,21 @@ namespace bs {	namespace ct
 		for (auto& entry : mSamplerOverrides)
 			entry.second->isDirty = false;
 	}
+
+	void RendererScene::prepareRenderable(UINT32 idx, const FrameInfo& frameInfo)
+	{
+		if (mInfo.renderableReady[idx])
+			return;
+		
+		// Note: Before uploading bone matrices perhaps check if they has actually been changed since last frame
+		mInfo.renderables[idx]->renderable->updateAnimationBuffers(frameInfo.animData);
+		
+		// Note: Could this step be moved in notifyRenderableUpdated, so it only triggers when material actually gets
+		// changed? Although it shouldn't matter much because if the internal versions keeping track of dirty params.
+		for (auto& element : mInfo.renderables[idx]->elements)
+			element.material->updateParamsSet(element.params);
+		
+		mInfo.renderables[idx]->perObjectParamBuffer->flushToGPU();
+		mInfo.renderableReady[idx] = true;
+	}
 }}

+ 633 - 1
Source/RenderBeast/Source/BsShadowRendering.cpp

@@ -2,9 +2,641 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsShadowRendering.h"
 #include "BsRendererView.h"
+#include "BsRendererScene.h"
+#include "BsLight.h"
+#include "BsRendererUtility.h"
+#include "BsGpuParamsSet.h"
+#include "BsMesh.h"
 
 namespace bs { namespace ct
 {
+	ShadowParamsDef gShadowParamsDef;
+
+	ShadowDepthNormalMat::ShadowDepthNormalMat()
+	{ }
+
+	void ShadowDepthNormalMat::_initDefines(ShaderDefines& defines)
+	{
+		// No defines
+	}
+
+	void ShadowDepthNormalMat::bind(const SPtr<GpuParamBlockBuffer>& shadowParams)
+	{
+		mParamsSet->setParamBlockBuffer("ShadowParams", shadowParams);
+
+		gRendererUtility().setPass(mMaterial);
+	}
+	
+	void ShadowDepthNormalMat::setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams)
+	{
+		mParamsSet->setParamBlockBuffer("PerObject", perObjectParams);
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
+	ShadowDepthDirectionalMat::ShadowDepthDirectionalMat()
+	{ }
+
+	void ShadowDepthDirectionalMat::_initDefines(ShaderDefines& defines)
+	{
+		// No defines
+	}
+
+	void ShadowDepthDirectionalMat::bind(const SPtr<GpuParamBlockBuffer>& shadowParams)
+	{
+		mParamsSet->setParamBlockBuffer("ShadowParams", shadowParams);
+
+		gRendererUtility().setPass(mMaterial);
+	}
+	
+	void ShadowDepthDirectionalMat::setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams)
+	{
+		mParamsSet->setParamBlockBuffer("PerObject", perObjectParams);
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
+	ShadowCubeMatricesDef gShadowCubeMatricesDef;
+	ShadowCubeMasksDef gShadowCubeMasksDef;
+
+	ShadowDepthCubeMat::ShadowDepthCubeMat()
+	{ }
+
+	void ShadowDepthCubeMat::_initDefines(ShaderDefines& defines)
+	{
+		// No defines
+	}
+
+	void ShadowDepthCubeMat::bind(const SPtr<GpuParamBlockBuffer>& shadowParams, 
+		const SPtr<GpuParamBlockBuffer>& shadowCubeMatrices)
+	{
+		mParamsSet->setParamBlockBuffer("ShadowParams", shadowParams);
+		mParamsSet->setParamBlockBuffer("ShadowCubeMatrices", shadowCubeMatrices);
+
+		gRendererUtility().setPass(mMaterial);
+	}
+
+	void ShadowDepthCubeMat::setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams,
+		const SPtr<GpuParamBlockBuffer>& shadowCubeMasks)
+	{
+		mParamsSet->setParamBlockBuffer("PerObject", perObjectParams);
+		mParamsSet->setParamBlockBuffer("ShadowCubeMasks", shadowCubeMasks);
+
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
+	void ShadowMapInfo::updateNormArea(UINT32 atlasSize)
+	{
+		normArea.x = area.x / (float)atlasSize;
+		normArea.y = area.y / (float)atlasSize;
+		normArea.width = area.width / (float)atlasSize;
+		normArea.height = area.height / (float)atlasSize;
+	}
+
+	ShadowMapAtlas::ShadowMapAtlas(UINT32 size)
+		:mLastUsedCounter(0)
+	{
+		mAtlas = GpuResourcePool::instance().get(
+			POOLED_RENDER_TEXTURE_DESC::create2D(PF_D24S8, size, size, TU_DEPTHSTENCIL));
+	}
+
+	ShadowMapAtlas::~ShadowMapAtlas()
+	{
+		GpuResourcePool::instance().release(mAtlas);
+	}
+
+	bool ShadowMapAtlas::addMap(UINT32 size, Rect2I& area, UINT32 border)
+	{
+		UINT32 sizeWithBorder = size + border * 2;
+
+		UINT32 x, y;
+		if (!mLayout.addElement(sizeWithBorder, sizeWithBorder, x, y))
+			return false;
+
+		area.width = area.height = size;
+		area.x = x + border;
+		area.y = y + border;
+
+		mLastUsedCounter = 0;
+		return true;
+	}
+
+	void ShadowMapAtlas::clear()
+	{
+		mLayout.clear();
+		mLastUsedCounter++;
+	}
+
+	bool ShadowMapAtlas::isEmpty() const
+	{
+		return mLayout.isEmpty();
+	}
+
+	SPtr<Texture> ShadowMapAtlas::getTexture() const
+	{
+		return mAtlas->texture;
+	}
+
+	SPtr<RenderTexture> ShadowMapAtlas::getTarget() const
+	{
+		return mAtlas->renderTexture;
+	}
+
+	ShadowMapBase::ShadowMapBase(UINT32 size)
+		: mSize(size), mIsUsed(false), mLastUsedCounter (0)
+	{ }
+
+	SPtr<Texture> ShadowMapBase::getTexture() const
+	{
+		return mShadowMap->texture;
+	}
+
+	ShadowCubemap::ShadowCubemap(UINT32 size)
+		:ShadowMapBase(size)
+	{
+		mShadowMap = GpuResourcePool::instance().get(
+			POOLED_RENDER_TEXTURE_DESC::createCube(PF_D24S8, size, size, TU_DEPTHSTENCIL));
+
+		RENDER_TEXTURE_DESC rtDesc;
+		rtDesc.depthStencilSurface.texture = mShadowMap->texture;
+		rtDesc.depthStencilSurface.numFaces = 6;
+	}
+
+	ShadowCubemap::~ShadowCubemap()
+	{
+		GpuResourcePool::instance().release(mShadowMap);
+	}
+
+	SPtr<RenderTexture> ShadowCubemap::getTarget() const
+	{
+		return mShadowMap->renderTexture;
+	}
+
+	ShadowCascadedMap::ShadowCascadedMap(UINT32 size)
+		:ShadowMapBase(size)
+	{
+		mShadowMap = GpuResourcePool::instance().get(
+			POOLED_RENDER_TEXTURE_DESC::create2D(PF_D24S8, size, size, TU_DEPTHSTENCIL, 0, false, NUM_CASCADE_SPLITS));
+
+		RENDER_TEXTURE_DESC rtDesc;
+		rtDesc.depthStencilSurface.texture = mShadowMap->texture;
+		rtDesc.depthStencilSurface.numFaces = 1;
+
+		for (int i = 0; i < NUM_CASCADE_SPLITS; ++i)
+		{
+			rtDesc.depthStencilSurface.face = i;
+			mTargets[i] = RenderTexture::create(rtDesc);
+		}
+	}
+
+	ShadowCascadedMap::~ShadowCascadedMap()
+	{
+		GpuResourcePool::instance().release(mShadowMap);
+	}
+
+	SPtr<RenderTexture> ShadowCascadedMap::getTarget(UINT32 cascadeIdx) const
+	{
+		return mTargets[cascadeIdx];
+	}
+
+	const UINT32 ShadowRendering::MAX_ATLAS_SIZE = 8192;
+	const UINT32 ShadowRendering::MAX_UNUSED_FRAMES = 60;
+
+	ShadowRendering::ShadowRendering(UINT32 shadowMapSize)
+		: mShadowMapSize(shadowMapSize)
+	{ }
+
+	void ShadowRendering::setShadowMapSize(UINT32 size)
+	{
+		if (mShadowMapSize == size)
+			return;
+
+		mCascadedShadowMaps.clear();
+		mDynamicShadowMaps.clear();
+		mShadowCubemaps.clear();
+	}
+
+	void ShadowRendering::renderShadowMaps(RendererScene& scene, const FrameInfo& frameInfo)
+	{
+		// Note: Currently all shadows are dynamic and are rebuilt every frame. I should later added support for static
+		// shadow maps which can be used for immovable lights. Such a light can then maintain a set of shadow maps,
+		// one of which is static and only effects the static geometry, while the rest are per-object shadow maps used
+		// for dynamic objects. Then only a small subset of geometry needs to be redrawn, instead of everything.
+
+		// Note: Add support for per-object shadows and a way to force a renderable to use per-object shadows. This can be
+		// used for adding high quality shadows on specific objects (e.g. important characters during cinematics).
+
+		const SceneInfo& sceneInfo = scene.getSceneInfo();
+		
+		// Clear all dynamic light atlases
+		for (auto& entry : mCascadedShadowMaps)
+			entry.clear();
+
+		for (auto& entry : mDynamicShadowMaps)
+			entry.clear();
+
+		for (auto& entry : mShadowCubemaps)
+			entry.clear();
+
+		// Render shadow maps
+		for (UINT32 i = 0; i < (UINT32)sceneInfo.directionalLights.size(); ++i)
+		{
+			scene.setLightShadowMapIdx(i, LightType::Directional, -1);
+
+			for(auto& entry : sceneInfo.views)
+				renderCascadedShadowMaps(*entry.second, sceneInfo.directionalLights[i], scene, frameInfo);
+		}
+
+		for (UINT32 i = 0; i < (UINT32)sceneInfo.spotLights.size(); ++i)
+		{
+			if (!sceneInfo.spotLightVisibility[i])
+				continue;
+
+			scene.setLightShadowMapIdx(i, LightType::Spot, -1);
+			renderSpotShadowMaps(sceneInfo.spotLights[i], scene, frameInfo);
+		}
+
+		for (UINT32 i = 0; i < (UINT32)sceneInfo.radialLights.size(); ++i)
+		{
+			if (!sceneInfo.radialLightVisibility[i])
+				continue;
+
+			scene.setLightShadowMapIdx(i, LightType::Radial, -1);
+			renderRadialShadowMaps(sceneInfo.radialLights[i], scene, frameInfo);
+		}
+		
+		// Deallocate unused atlas textures
+		for(auto iter = mDynamicShadowMaps.begin(); iter != mDynamicShadowMaps.end(); ++iter)
+		{
+			if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+			{
+				mDynamicShadowMaps.erase(iter, mDynamicShadowMaps.end());
+				break;
+			}
+		}
+
+		for(auto iter = mCascadedShadowMaps.begin(); iter != mCascadedShadowMaps.end(); ++iter)
+		{
+			if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+			{
+				mCascadedShadowMaps.erase(iter, mCascadedShadowMaps.end());
+				break;
+			}
+		}
+		
+		for(auto iter = mShadowCubemaps.begin(); iter != mShadowCubemaps.end(); ++iter)
+		{
+			if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+			{
+				mShadowCubemaps.erase(iter, mShadowCubemaps.end());
+				break;
+			}
+		}
+	}
+
+	void ShadowRendering::renderCascadedShadowMaps(const RendererView& view, const RendererLight& rendererLight,
+		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.
+
+		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
+		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;
+				shadowMap.markAsUsed();
+
+				break;
+			}
+		}
+
+		if (mapIdx == -1)
+		{
+			mapIdx = (UINT32)mCascadedShadowMaps.size();
+			mCascadedShadowMaps.push_back(ShadowCascadedMap(mapSize));
+
+			ShadowCascadedMap& shadowMap = mCascadedShadowMaps.back();
+			shadowMap.markAsUsed();
+		}
+
+		ShadowCascadedMap& shadowMap = mCascadedShadowMaps[mapIdx];
+
+		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);
+
+			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;
+
+			float nearDist = 0.05f;
+			float farDist = light->getAttenuationRadius();
+			float depthRange = farDist - nearDist;
+			float depthBias = 0.0f; // TODO - determine optimal depth bias
+
+			gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, depthBias);
+			gShadowParamsDef.gDepthRange.set(shadowParamsBuffer, depthRange);
+			gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, viewProj);
+
+			rapi.setRenderTarget(shadowMap.getTarget(i));
+			rapi.clearRenderTarget(FBT_DEPTH);
+
+			mDepthDirectionalMat.bind(shadowParamsBuffer);
+
+			for (UINT32 j = 0; j < sceneInfo.renderables.size(); j++)
+			{
+				if (!cascadeCullVolume.intersects(sceneInfo.renderableCullInfos[j].bounds.getSphere()))
+					continue;
+
+				scene.prepareRenderable(j, frameInfo);
+
+				RendererObject* renderable = sceneInfo.renderables[j];
+				mDepthDirectionalMat.setPerObjectBuffer(renderable->perObjectParamBuffer);
+
+				for (auto& element : renderable->elements)
+				{
+					if (element.morphVertexDeclaration == nullptr)
+						gRendererUtility().draw(element.mesh, element.subMesh);
+					else
+						gRendererUtility().drawMorph(element.mesh, element.subMesh, element.morphShapeBuffer,
+							element.morphVertexDeclaration);
+				}
+			}
+		}
+	}
+
+	void ShadowRendering::renderSpotShadowMaps(const RendererLight& rendererLight, RendererScene& scene,
+		const FrameInfo& frameInfo)
+	{
+		Light* light = rendererLight.internal;
+
+		if (!light->getCastsShadow())
+			return;
+
+		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))
+			{
+				mapInfo.atlasIdx = i;
+
+				foundSpace = true;
+				break;
+			}
+		}
+
+		if (!foundSpace)
+		{
+			mapInfo.atlasIdx = (UINT32)mDynamicShadowMaps.size();
+			mDynamicShadowMaps.push_back(ShadowMapAtlas(MAX_ATLAS_SIZE));
+
+			ShadowMapAtlas& atlas = mDynamicShadowMaps.back();
+			atlas.addMap(mapSize, mapInfo.area);
+		}
+
+		mapInfo.updateNormArea(MAX_ATLAS_SIZE);
+		ShadowMapAtlas& atlas = mDynamicShadowMaps[mapInfo.atlasIdx];
+
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(atlas.getTarget());
+		rapi.setViewport(mapInfo.normArea);
+		rapi.clearViewport(FBT_DEPTH);
+
+		float nearDist = 0.05f;
+		float farDist = light->getAttenuationRadius();
+		float depthRange = farDist - nearDist;
+		float depthBias = 0.0f; // TODO - determine optimal depth bias
+
+		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;
+
+		gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, depthBias);
+		gShadowParamsDef.gDepthRange.set(shadowParamsBuffer, depthRange);
+		gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, viewProj);
+
+		mDepthNormalMat.bind(shadowParamsBuffer);
+
+		ConvexVolume localFrustum = ConvexVolume(proj);
+
+		const Vector<Plane>& frustumPlanes = localFrustum.getPlanes();
+		Matrix4 worldMatrix = view.transpose();
+
+		Vector<Plane> worldPlanes(frustumPlanes.size());
+		UINT32 j = 0;
+		for (auto& plane : frustumPlanes)
+		{
+			worldPlanes[j] = worldMatrix.multiplyAffine(plane);
+			j++;
+		}
+
+		ConvexVolume worldFrustum(worldPlanes);
+		for (UINT32 i = 0; i < sceneInfo.renderables.size(); i++)
+		{
+			if (!worldFrustum.intersects(sceneInfo.renderableCullInfos[i].bounds.getSphere()))
+				continue;
+
+			scene.prepareRenderable(i, frameInfo);
+
+			RendererObject* renderable = sceneInfo.renderables[i];
+			mDepthNormalMat.setPerObjectBuffer(renderable->perObjectParamBuffer);
+
+			for (auto& element : renderable->elements)
+			{
+				if (element.morphVertexDeclaration == nullptr)
+					gRendererUtility().draw(element.mesh, element.subMesh);
+				else
+					gRendererUtility().drawMorph(element.mesh, element.subMesh, element.morphShapeBuffer,
+						element.morphVertexDeclaration);
+			}
+		}
+
+		// Restore viewport
+		rapi.setViewport(Rect2(0.0f, 0.0f, 1.0f, 1.0f));
+	}
+
+	void ShadowRendering::renderRadialShadowMaps(const RendererLight& rendererLight, 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();
+
+		// 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++)
+		{
+			ShadowCubemap& cubemap = mShadowCubemaps[i];
+
+			if (!cubemap.isUsed() && cubemap.getSize() == mapSize)
+			{
+				mapIdx = i;
+				cubemap.markAsUsed();
+
+				break;
+			}
+		}
+
+		if (mapIdx == -1)
+		{
+			mapIdx = (UINT32)mShadowCubemaps.size();
+			mShadowCubemaps.push_back(ShadowCubemap(mapSize));
+
+			ShadowCubemap& cubemap = mShadowCubemaps.back();
+			cubemap.markAsUsed();
+		}
+
+		ShadowCubemap& cubemap = mShadowCubemaps[mapIdx];
+
+		float nearDist = 0.05f;
+		float farDist = light->getAttenuationRadius();
+		float depthRange = farDist - nearDist;
+		float depthBias = 0.0f; // TODO - determine optimal depth bias
+
+		Matrix4 proj = Matrix4::projectionPerspective(Degree(90.0f), 1.0f, 0.05f, light->getAttenuationRadius());
+		RenderAPI::instance().convertProjectionMatrix(proj, proj);
+
+		ConvexVolume localFrustum(proj);
+
+		gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, depthBias);
+		gShadowParamsDef.gDepthRange.set(shadowParamsBuffer, depthRange);
+		gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, Matrix4::IDENTITY);
+
+		Matrix4 viewOffsetMat = Matrix4::translation(-light->getPosition());
+
+		ConvexVolume frustums[6];
+		Vector<Plane> boundingPlanes;
+		for (UINT32 i = 0; i < 6; i++)
+		{
+			// Calculate view matrix
+			Vector3 forward;
+			Vector3 up = Vector3::UNIT_Y;
+
+			switch (i)
+			{
+			case CF_PositiveX:
+				forward = Vector3::UNIT_X;
+				break;
+			case CF_NegativeX:
+				forward = -Vector3::UNIT_X;
+				break;
+			case CF_PositiveY:
+				forward = Vector3::UNIT_Y;
+				up = -Vector3::UNIT_Z;
+				break;
+			case CF_NegativeY:
+				forward = Vector3::UNIT_X;
+				up = Vector3::UNIT_Z;
+				break;
+			case CF_PositiveZ:
+				forward = Vector3::UNIT_Z;
+				break;
+			case CF_NegativeZ:
+				forward = -Vector3::UNIT_Z;
+				break;
+			}
+
+			Vector3 right = Vector3::cross(up, forward);
+			Matrix3 viewRotationMat = Matrix3(right, up, forward);
+
+			Matrix4 view = Matrix4(viewRotationMat) * viewOffsetMat;
+			gShadowCubeMatricesDef.gFaceVPMatrices.set(shadowCubeMatricesBuffer, proj * view, i);
+
+			// Calculate world frustum for culling
+			const Vector<Plane>& frustumPlanes = localFrustum.getPlanes();
+			Matrix4 worldMatrix = view.transpose();
+
+			Vector<Plane> worldPlanes(frustumPlanes.size());
+			UINT32 j = 0;
+			for (auto& plane : frustumPlanes)
+			{
+				worldPlanes[j] = worldMatrix.multiplyAffine(plane);
+				j++;
+			}
+
+			frustums[i] = ConvexVolume(worldPlanes);
+
+			// Register far plane of all frustums
+			boundingPlanes.push_back(worldPlanes.back());
+		}
+
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(cubemap.getTarget());
+		rapi.clearRenderTarget(FBT_DEPTH);
+
+		mDepthCubeMat.bind(shadowParamsBuffer, shadowCubeMatricesBuffer);
+
+		// First cull against a global volume
+		ConvexVolume boundingVolume(boundingPlanes);
+		for (UINT32 i = 0; i < sceneInfo.renderables.size(); i++)
+		{
+			const Sphere& bounds = sceneInfo.renderableCullInfos[i].bounds.getSphere();
+			if (!boundingVolume.intersects(bounds))
+				continue;
+
+			scene.prepareRenderable(i, frameInfo);
+
+			for(UINT32 j = 0; j < 6; j++)
+			{
+				int mask = frustums->intersects(bounds) ? 1 : 0;
+				gShadowCubeMasksDef.gFaceMasks.set(shadowCubeMasksBuffer, mask, j);
+			}
+
+			RendererObject* renderable = sceneInfo.renderables[i];
+			mDepthCubeMat.setPerObjectBuffer(renderable->perObjectParamBuffer, shadowCubeMasksBuffer);
+
+			for (auto& element : renderable->elements)
+			{
+				if (element.morphVertexDeclaration == nullptr)
+					gRendererUtility().draw(element.mesh, element.subMesh);
+				else
+					gRendererUtility().drawMorph(element.mesh, element.subMesh, element.morphShapeBuffer,
+						element.morphVertexDeclaration);
+			}
+		}
+	}
+
 	ConvexVolume ShadowRendering::getCSMSplitFrustum(const RendererView& view, const Vector3& lightDir, UINT32 cascade, 
 		UINT32 numCascades, Sphere& outBounds)
 	{
@@ -180,4 +812,4 @@ namespace bs { namespace ct
 
 		return near + (far - near) * scale;
 	}
-}}
+}}