Explorar o código

Merge branch 'master' of https://github.com/bearishsun/bansheeengine into shader-refactor

# Conflicts:
#	Data/Raw/Engine/Includes/ImageBasedLighting.bslinc
#	Data/Raw/Engine/Includes/LightingCommon.bslinc
#	Data/Raw/Engine/Includes/PerCameraData.bslinc
#	Data/Raw/Engine/Includes/ShadowDepthBase.bslinc
#	Data/Raw/Engine/Shaders/ReflectionCubeImportanceSample.bsl
#	Data/Raw/Engine/Shaders/ShadowDepthCube.bsl
#	Source/RenderBeast/Include/BsRenderBeast.h
#	Source/RenderBeast/Source/BsLightRendering.cpp
BearishSun %!s(int64=8) %!d(string=hai) anos
pai
achega
912c4e7adb
Modificáronse 51 ficheiros con 2838 adicións e 404 borrados
  1. 28 0
      Data/Raw/Engine/DataList.json
  2. 71 0
      Data/Raw/Engine/Includes/DeferredLightCommon.bslinc
  3. 13 14
      Data/Raw/Engine/Includes/ImageBasedLighting.bslinc
  4. 154 135
      Data/Raw/Engine/Includes/LightingCommon.bslinc
  5. 34 2
      Data/Raw/Engine/Includes/PerCameraData.bslinc
  6. 5 12
      Data/Raw/Engine/Includes/ShadowDepthBase.bslinc
  7. 40 0
      Data/Raw/Engine/Includes/ShadowProjectionCommon.bslinc
  8. 67 0
      Data/Raw/Engine/Shaders/DeferredDirectionalLight.bsl
  9. 121 0
      Data/Raw/Engine/Shaders/DeferredPointLight.bsl
  10. 1 1
      Data/Raw/Engine/Shaders/LightGridLLCreation.bsl
  11. 5 5
      Data/Raw/Engine/Shaders/ReflectionCubeImportanceSample.bsl
  12. 5 2
      Data/Raw/Engine/Shaders/ShadowDepthCube.bsl
  13. 4 0
      Data/Raw/Engine/Shaders/ShadowDepthDirectional.bsl
  14. 1 0
      Data/Raw/Engine/Shaders/ShadowDepthNormal.bsl
  15. 220 0
      Data/Raw/Engine/Shaders/ShadowProject.bsl
  16. 178 0
      Data/Raw/Engine/Shaders/ShadowProjectOmni.bsl
  17. 1 1
      Data/Raw/Engine/Shaders/TiledDeferredImageBasedLighting.bsl
  18. 1 1
      Data/Raw/Engine/Shaders/TiledDeferredLighting.bsl
  19. 1 1
      Data/Raw/Engine/Shaders/Transparent.bsl
  20. 0 1
      Documentation/Doxygen/footer.html
  21. 7 8
      Documentation/GitHub/roadmap.md
  22. 1 1
      Documentation/Manuals/Native/manuals.md
  23. 7 1
      Source/BansheeCore/Include/BsCLight.h
  24. 7 0
      Source/BansheeCore/Include/BsCamera.h
  25. 1 1
      Source/BansheeCore/Include/BsCoreThread.h
  26. 13 0
      Source/BansheeCore/Include/BsLight.h
  27. 1 0
      Source/BansheeCore/Include/BsLightRTTI.h
  28. 2 2
      Source/BansheeCore/Include/BsRenderable.h
  29. 3 3
      Source/BansheeCore/Include/BsRenderer.h
  30. 2 0
      Source/BansheeCore/Source/BsCamera.cpp
  31. 5 1
      Source/BansheeCore/Source/BsLight.cpp
  32. 1 1
      Source/BansheeCore/Source/BsRenderable.cpp
  33. 4 0
      Source/BansheeUtility/Include/BsStdHeaders.h
  34. 4 0
      Source/BansheeUtility/Include/BsString.h
  35. 10 1
      Source/BansheeUtility/Include/BsTextureAtlasLayout.h
  36. 2 2
      Source/BansheeUtility/Source/BsMatrix4.cpp
  37. 12 2
      Source/BansheeUtility/Source/BsTextureAtlasLayout.cpp
  38. 4 0
      Source/CMake/FindPackageOrBuild.cmake
  39. 5 2
      Source/RenderBeast/Include/BsGpuResourcePool.h
  40. 71 8
      Source/RenderBeast/Include/BsLightRendering.h
  41. 14 19
      Source/RenderBeast/Include/BsRenderBeast.h
  42. 6 0
      Source/RenderBeast/Include/BsRenderBeastOptions.h
  43. 33 12
      Source/RenderBeast/Include/BsRendererScene.h
  44. 5 4
      Source/RenderBeast/Include/BsRendererView.h
  45. 370 3
      Source/RenderBeast/Include/BsShadowRendering.h
  46. 11 4
      Source/RenderBeast/Source/BsGpuResourcePool.cpp
  47. 150 23
      Source/RenderBeast/Source/BsLightRendering.cpp
  48. 27 27
      Source/RenderBeast/Source/BsRenderBeast.cpp
  49. 129 103
      Source/RenderBeast/Source/BsRendererScene.cpp
  50. 1 0
      Source/RenderBeast/Source/BsRendererView.cpp
  51. 980 1
      Source/RenderBeast/Source/BsShadowRendering.cpp

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

@@ -131,6 +131,14 @@
         {
             "Path": "ShadowDepthBase.bslinc",
             "UUID": "ff6f2a9d-6766-4f80-984b-c159a5fd3c5e"
+        },
+        {
+            "Path": "DeferredLightCommon.bslinc",
+            "UUID": "8ffd1df9-3de4-442d-adca-95e8766fa81a"
+        },
+        {
+            "Path": "ShadowProjectionCommon.bslinc",
+            "UUID": "36da8807-b201-482b-b320-96f5300d1751"
         }
     ],
     "Shaders": [
@@ -257,6 +265,26 @@
         {
             "Path": "ShadowDepthNormal.bsl",
             "UUID": "c9b64475-375d-410e-8cd4-e1b1181318d4"
+        },
+        {
+            "Path": "ShadowDepthDirectional.bsl",
+            "UUID": "acd0f016-8084-4b32-806c-66a85b34ee5a"
+        },
+        {
+            "Path": "DeferredDirectionalLight.bsl",
+            "UUID": "17d573f8-1142-4257-9e32-90038d3786f3"
+        },
+        {
+            "Path": "DeferredPointLight.bsl",
+            "UUID": "428d9fde-9838-4367-9857-57bd80c8f599"
+        },
+        {
+            "Path": "ShadowProject.bsl",
+            "UUID": "93667cf9-c699-4d92-ba20-6e2e912f76fa"
+        },
+        {
+            "Path": "ShadowProjectOmni.bsl",
+            "UUID": "50447773-98e9-410c-8f64-eacbb9622c52"
         }
     ],
     "Skin": [

+ 71 - 0
Data/Raw/Engine/Includes/DeferredLightCommon.bslinc

@@ -0,0 +1,71 @@
+#include "$ENGINE$\GBufferInput.bslinc"
+#include "$ENGINE$\PerCameraData.bslinc"
+#include "$ENGINE$\LightingCommon.bslinc"
+
+mixin DeferredLightCommon
+{
+	mixin GBufferInput;
+	mixin PerCameraData;
+	mixin LightingCommon;
+
+	depth
+	{
+		write = false;
+		
+		#ifdef INSIDE_GEOMETRY
+		read = false;
+		#else
+		read = true;
+		#endif
+	};
+	
+	raster
+	{
+		#ifdef INSIDE_GEOMETRY
+		cull = cw;
+		#else
+		cull = ccw;
+		#endif
+	};
+
+	code
+	{
+		cbuffer PerLight
+		{
+			// x, y, z - World position of the light
+			// w - Radius of the area light source, zero if not an area light
+			float4 gLightPositionAndSrcRadius;
+			float4 gLightColorAndLuminance;
+			// x - outerAngle in radians, y - cos(outerAngle), z - 1.0f/(cos(innerAngle) - cos(outerAngle)), w - inverse light attenuation radius
+			float4 gLightSpotAnglesAndSqrdInvAttRadius;
+			float4 gLightDirectionAndAttRadius;
+			// xyz - light position shifted in order to adjust for area spot lights
+			// w - light type -> Directional = 0, Radial = >0, Spot = >0.5
+			float4 gShiftedLightPositionAndType;
+			
+			// x - Num sides (zero for radial lights)
+			// y - Num slices (zero for radial lights)
+			// z - Sphere radius for radial lights
+			// w - Cone radius for spot lights
+			float4 gLightGeometry; 
+			float4x4 gMatConeTransform;
+		}		
+
+		LightData getLightData()
+		{
+			LightData output;
+			
+			output.position = gLightPositionAndSrcRadius.xyz;
+			output.attRadius = gLightDirectionAndAttRadius.w;
+			output.direction = gLightDirectionAndAttRadius.xyz;
+			output.luminance = gLightColorAndLuminance.w;
+			output.spotAngles = gLightSpotAnglesAndSqrdInvAttRadius.xyz;
+			output.attRadiusSqrdInv = gLightSpotAnglesAndSqrdInvAttRadius.w;
+			output.color = gLightColorAndLuminance.rgb;
+			output.srcRadius = gLightPositionAndSrcRadius.w;
+			output.shiftedLightPosition = gShiftedLightPositionAndType.rgb;
+
+			return output;
+		}
+	};
+};

+ 13 - 14
Data/Raw/Engine/Includes/ImageBasedLighting.bslinc

@@ -1,6 +1,6 @@
 #include "$ENGINE$\ReflectionCubemapCommon.bslinc"
 
-mixin ImageBasedLighting 
+mixin ImageBasedLighting
 {
 	mixin ReflectionCubemapCommon;
 
@@ -22,28 +22,27 @@ mixin ImageBasedLighting
 			float2 padding;
 		};
 	
-		[internal] TextureCube gSkyReflectionTex;
-		[internal] SamplerState gSkyReflectionSamp;
+		TextureCube gSkyReflectionTex;
+		SamplerState gSkyReflectionSamp;
 		
-		[internal] TextureCube gSkyIrradianceTex;
-		[internal] SamplerState gSkyIrradianceSamp;
+		TextureCube gSkyIrradianceTex;
+		SamplerState gSkyIrradianceSamp;
 		
-		[internal] TextureCubeArray gReflProbeCubemaps;
-		[internal] SamplerState gReflProbeSamp;
+		TextureCubeArray gReflProbeCubemaps;
+		SamplerState gReflProbeSamp;
 		
-		[internal] Texture2D gPreintegratedEnvBRDF;
-		[internal] SamplerState gPreintegratedEnvBRDFSamp;
+		Texture2D gPreintegratedEnvBRDF;
+		SamplerState gPreintegratedEnvBRDFSamp;
 		
-		[internal] StructuredBuffer<ReflProbeData> gReflectionProbes;	
+		StructuredBuffer<ReflProbeData> gReflectionProbes;	
 
-		#ifdef USE_COMPUTE_INDICES
+		#if USE_COMPUTE_INDICES
 			groupshared uint gReflectionProbeIndices[MAX_PROBES];
 		#endif
-		#ifdef USE_LIGHT_GRID_INDICES
-			[internal] Buffer<uint> gReflectionProbeIndices;
+		#if USE_LIGHT_GRID_INDICES
+			Buffer<uint> gReflectionProbeIndices;
 		#endif
 		
-		[internal]
 		cbuffer ReflProbeParams
 		{
 			uint gReflCubemapNumMips;

+ 154 - 135
Data/Raw/Engine/Includes/LightingCommon.bslinc

@@ -193,15 +193,161 @@ mixin LightingCommon
 			return diffuse + specular * specLobeEnergy;
 		}	
 		
-		[internal] StructuredBuffer<LightData> gLights;
+		float3 getLuminanceDirectional(LightData lightData, float3 worldPos, float3 V, float3 R, SurfaceData surfaceData)
+		{
+			float3 N = surfaceData.worldNormal.xyz;
+			float3 L = -lightData.direction;
+			float NoL = saturate(dot(N, L));
+			float specEnergy = 1.0f;
+			
+			// Distant disk area light. Calculate its contribution analytically by
+			// finding the most important (least error) point on the area light and
+			// use it as a form of importance sampling.
+			if(lightData.srcRadius > 0)
+			{
+				float diskRadius = sin(lightData.srcRadius);
+				float distanceToDisk = cos(lightData.srcRadius);
+				
+				// Closest point to disk (approximation for distant disks)
+				float DoR = dot(L, R);
+				float3 S = normalize(R - DoR * L);
+				L = DoR < distanceToDisk ? normalize(distanceToDisk * L + S * diskRadius) : R;
+			}
+			
+			float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
+			float illuminance = lightData.luminance * NoL;
+			return lightData.color * illuminance * surfaceShading;
+		}
+		
+		float3 getLuminanceRadial(LightData lightData, float3 worldPos, float3 V, float3 R, float roughness2, SurfaceData surfaceData)
+		{
+			float3 N = surfaceData.worldNormal.xyz;
+			float3 toLight = lightData.position - worldPos;
+			float distToLightSqrd = dot(toLight, toLight);
+			float invDistToLight = rsqrt(distToLightSqrd);
+			
+			float3 L = toLight * invDistToLight;
+			float NoL = dot(N, L);
+			
+			float specEnergy = 1.0f;
+			float illuminance = 0.0f;
+
+			// Sphere area light. Calculate its contribution analytically by
+			// finding the most important (least error) point on the area light and
+			// use it as a form of importance sampling.
+			if(lightData.srcRadius > 0)
+			{
+				// Calculate illuminance depending on source size, distance and angle
+				illuminance = illuminanceSphereAreaLight(NoL, distToLightSqrd, lightData);	
+
+				// Energy conservation:
+				//    We are widening the specular distribution by the sphere's subtended angle, 
+				//    so we need to handle the increase in energy. It is not enough just to account
+				//    for the sphere solid angle, since the energy difference is highly dependent on
+				//    specular distribution. By accounting for this energy difference we ensure glossy
+				//    reflections have sharp edges, instead of being too blurry.
+				//    See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for reference
+				float sphereAngle = saturate(lightData.srcRadius * invDistToLight);
+				
+				specEnergy = roughness2 / saturate(roughness2 + 0.5f * sphereAngle);
+				specEnergy *= specEnergy;							
+			
+				// Find closest point on sphere to ray
+				float3 closestPointOnRay = dot(toLight, R) * R;
+				float3 centerToRay = closestPointOnRay - toLight;
+				float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
+				float3 closestPointOnSphere = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
+				
+				toLight = closestPointOnSphere;
+				L = normalize(toLight);
+			}
+			else
+			{
+				NoL = saturate(NoL);
+				illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
+			}
+			
+			float attenuation = getRadialAttenuation(distToLightSqrd, lightData);
+			float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
+				
+			return lightData.color * illuminance * attenuation * surfaceShading;
+		}
+		
+		float3 getLuminanceSpot(LightData lightData, float3 worldPos, float3 V, float3 R, float roughness2, SurfaceData surfaceData)
+		{
+			float3 N = surfaceData.worldNormal.xyz;
+			float3 toLight = lightData.position - worldPos;
+			float distToLightSqrd = dot(toLight, toLight);
+			float invDistToLight = rsqrt(distToLightSqrd);
+			
+			float3 L = toLight * invDistToLight;
+			float NoL = dot(N, L);
+			
+			float specEnergy = 1.0f;
+			float illuminance = 0.0f;
+			float spotAttenuation = 1.0f;
+			
+			// Disc area light. Calculate its contribution analytically by
+			// finding the most important (least error) point on the area light and
+			// use it as a form of importance sampling.
+			if(lightData.srcRadius > 0)
+			{
+				// Calculate illuminance depending on source size, distance and angle
+				illuminance = illuminanceDiscAreaLight(NoL, distToLightSqrd, L, lightData);	
+			
+				// Energy conservation: Similar case as with radial lights
+				float rightDiscAngle = saturate(lightData.srcRadius * invDistToLight);
+				
+				// Account for disc orientation somewhat
+				float discAngle = rightDiscAngle * saturate(dot(lightData.direction, -L));
+				
+				specEnergy = roughness2 / saturate(roughness2 + 0.5f * discAngle);
+				specEnergy *= specEnergy;							
+			
+				// Find closest point on disc to ray
+				float3 discNormal = -lightData.direction;
+				float distAlongLightDir = max(dot(R, discNormal), 1e-6f);
+				float t = dot(toLight, discNormal) / distAlongLightDir;
+				float3 closestPointOnPlane = R * t; // Relative to shaded world point
+				
+				float3 centerToRay = closestPointOnPlane - toLight;
+				float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
+				float3 closestPointOnDisc = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
+
+				toLight = closestPointOnDisc;
+				L = normalize(toLight);
+				
+				// Expand spot attenuation by disc radius (not physically based)
+				float3 toSpotEdge = normalize(lightData.shiftedLightPosition - worldPos);
+				spotAttenuation = getSpotAttenuation(toSpotEdge, lightData);
+				
+				// TODO - Spot attenuation fades out the specular highlight in a noticeable way
+			}
+			else
+			{
+				NoL = saturate(NoL);
+				illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
+				
+				spotAttenuation = getSpotAttenuation(L, lightData);
+			}
+			
+			float radialAttenuation = getRadialAttenuation(distToLightSqrd, lightData);
+			float attenuation = spotAttenuation * radialAttenuation;
+			float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
+				
+			return lightData.color * illuminance * attenuation * surfaceShading;
+		}
 		
-		#ifdef USE_COMPUTE_INDICES
+		#if USE_COMPUTE_INDICES
 			groupshared uint gLightIndices[MAX_LIGHTS];
 		#endif
-		#ifdef USE_LIGHT_GRID_INDICES
-			[internal] Buffer<uint> gLightIndices;
+		#if USE_LIGHT_GRID_INDICES
+			Buffer<uint> gLightIndices;
 		#endif
 		
+		#if USE_COMPUTE_INDICES || USE_LIGHT_GRID_INDICES
+		StructuredBuffer<LightData> gLights;
+		
 		float4 getDirectLighting(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, uint4 lightOffsets)
 		{
 			float3 N = surfaceData.worldNormal.xyz;
@@ -216,28 +362,7 @@ mixin LightingCommon
 				for(uint i = 0; i < lightOffsets.x; ++i)
 				{
 					LightData lightData = gLights[i];
-				
-					float3 L = -lightData.direction;
-					float NoL = saturate(dot(N, L));
-					float specEnergy = 1.0f;
-					
-					// Distant disk area light. Calculate its contribution analytically by
-					// finding the most important (least error) point on the area light and
-					// use it as a form of importance sampling.
-					if(lightData.srcRadius > 0)
-					{
-						float diskRadius = sin(lightData.srcRadius);
-						float distanceToDisk = cos(lightData.srcRadius);
-						
-						// Closest point to disk (approximation for distant disks)
-						float DoR = dot(L, R);
-						float3 S = normalize(R - DoR * L);
-						L = DoR < distanceToDisk ? normalize(distanceToDisk * L + S * diskRadius) : R;
-					}
-					
-					float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
-					float illuminance = lightData.luminance * NoL;
-					outLuminance += lightData.color * illuminance * surfaceShading;
+					outLuminance += getLuminanceDirectional(lightData, worldPos, V, R, surfaceData);
 				}
 				
 				// Handle radial lights
@@ -246,55 +371,7 @@ mixin LightingCommon
 					uint lightIdx = gLightIndices[i];
 					LightData lightData = gLights[lightIdx];
 					
-					float3 toLight = lightData.position - worldPos;
-					float distToLightSqrd = dot(toLight, toLight);
-					float invDistToLight = rsqrt(distToLightSqrd);
-					
-					float3 L = toLight * invDistToLight;
-					float NoL = dot(N, L);
-					
-					float specEnergy = 1.0f;
-					float illuminance = 0.0f;
-
-					// Sphere area light. Calculate its contribution analytically by
-					// finding the most important (least error) point on the area light and
-					// use it as a form of importance sampling.
-					if(lightData.srcRadius > 0)
-					{
-						// Calculate illuminance depending on source size, distance and angle
-						illuminance = illuminanceSphereAreaLight(NoL, distToLightSqrd, lightData);	
-
-						// Energy conservation:
-						//    We are widening the specular distribution by the sphere's subtended angle, 
-						//    so we need to handle the increase in energy. It is not enough just to account
-						//    for the sphere solid angle, since the energy difference is highly dependent on
-						//    specular distribution. By accounting for this energy difference we ensure glossy
-						//    reflections have sharp edges, instead of being too blurry.
-						//    See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for reference
-						float sphereAngle = saturate(lightData.srcRadius * invDistToLight);
-						
-						specEnergy = roughness2 / saturate(roughness2 + 0.5f * sphereAngle);
-						specEnergy *= specEnergy;							
-					
-						// Find closest point on sphere to ray
-						float3 closestPointOnRay = dot(toLight, R) * R;
-						float3 centerToRay = closestPointOnRay - toLight;
-						float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
-						float3 closestPointOnSphere = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
-						
-						toLight = closestPointOnSphere;
-						L = normalize(toLight);
-					}
-					else
-					{
-						NoL = saturate(NoL);
-						illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
-					}
-					
-					float attenuation = getRadialAttenuation(distToLightSqrd, lightData);
-					float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
-						
-					outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
+					outLuminance += getLuminanceRadial(lightData, worldPos, V, R, roughness2, surfaceData);
 				}
 
 				// Handle spot lights
@@ -303,66 +380,7 @@ mixin LightingCommon
 					uint lightIdx = gLightIndices[i];
 					LightData lightData = gLights[lightIdx];
 					
-					float3 toLight = lightData.position - worldPos;
-					float distToLightSqrd = dot(toLight, toLight);
-					float invDistToLight = rsqrt(distToLightSqrd);
-					
-					float3 L = toLight * invDistToLight;
-					float NoL = dot(N, L);
-					
-					float specEnergy = 1.0f;
-					float illuminance = 0.0f;
-					float spotAttenuation = 1.0f;
-					
-					// Disc area light. Calculate its contribution analytically by
-					// finding the most important (least error) point on the area light and
-					// use it as a form of importance sampling.
-					if(lightData.srcRadius > 0)
-					{
-						// Calculate illuminance depending on source size, distance and angle
-						illuminance = illuminanceDiscAreaLight(NoL, distToLightSqrd, L, lightData);	
-					
-						// Energy conservation: Similar case as with radial lights
-						float rightDiscAngle = saturate(lightData.srcRadius * invDistToLight);
-						
-						// Account for disc orientation somewhat
-						float discAngle = rightDiscAngle * saturate(dot(lightData.direction, -L));
-						
-						specEnergy = roughness2 / saturate(roughness2 + 0.5f * discAngle);
-						specEnergy *= specEnergy;							
-					
-						// Find closest point on disc to ray
-						float3 discNormal = -lightData.direction;
-						float distAlongLightDir = max(dot(R, discNormal), 1e-6f);
-						float t = dot(toLight, discNormal) / distAlongLightDir;
-						float3 closestPointOnPlane = R * t; // Relative to shaded world point
-						
-						float3 centerToRay = closestPointOnPlane - toLight;
-						float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
-						float3 closestPointOnDisc = toLight + centerToRay * saturate(lightData.srcRadius * invDistToRay);
-
-						toLight = closestPointOnDisc;
-						L = normalize(toLight);
-						
-						// Expand spot attenuation by disc radius (not physically based)
-						float3 toSpotEdge = normalize(lightData.shiftedLightPosition - worldPos);
-						spotAttenuation = getSpotAttenuation(toSpotEdge, lightData);
-						
-						// TODO - Spot attenuation fades out the specular highlight in a noticeable way
-					}
-					else
-					{
-						NoL = saturate(NoL);
-						illuminance = illuminancePointLight(distToLightSqrd, NoL, lightData);
-						
-						spotAttenuation = getSpotAttenuation(L, lightData);
-					}
-					
-					float radialAttenuation = getRadialAttenuation(distToLightSqrd, lightData);
-					float attenuation = spotAttenuation * radialAttenuation;
-					float3 surfaceShading = getSurfaceShading(V, L, specEnergy, surfaceData);
-						
-					outLuminance += lightData.color * illuminance * attenuation * surfaceShading;
+					outLuminance += getLuminanceSpot(lightData, worldPos, V, R, roughness2, surfaceData);
 				}
 				
 				// Ambient term for in-editor visualization, not used in actual lighting
@@ -372,5 +390,6 @@ mixin LightingCommon
 			
 			return float4(outLuminance, alpha);
 		}
+		#endif
 	};
 };

+ 34 - 2
Data/Raw/Engine/Includes/PerCameraData.bslinc

@@ -1,6 +1,6 @@
 mixin PerCameraData
 {
-	code 
+	code
 	{
 		cbuffer PerCamera
 		{
@@ -29,7 +29,7 @@ mixin PerCameraData
 			int4 	 gViewportRectangle;
 			
 			// xy - (Viewport size in pixels / 2) / Target size in pixels
-			// zw - (Viewport offset in pixels + (Viewport size in pixels / 2) + Optional pixel center offset) / Target size in pixels	
+			// zw - (Viewport offset in pixels + (Viewport size in pixels / 2) + Optional pixel center offset) / Target size in pixels
 			float4 	 gClipToUVScaleOffset;	
 			float	gAmbientFactor;
 		}
@@ -45,5 +45,37 @@ mixin PerCameraData
 		{
 			return -gNDCZToWorldZ.y + (gNDCZToWorldZ.x / viewZ);
 		}
+		
+		/** Converts position in NDC to UV coordinates mapped to the screen rectangle. */ 
+		float2 NDCToUV(float2 ndcPos)
+		{
+			return ndcPos.xy * gClipToUVScaleOffset.xy + gClipToUVScaleOffset.zw;
+		}
+		
+		/** Converts position in UV coordinates mapped to the screen, to screen coordinates in pixels. */
+		uint2 UVToScreen(float2 uv)
+		{
+			return (uint2)(uv * (float2)gViewportRectangle.zw - ((float2)gViewportRectangle.xy + 0.5f));
+		}
+		
+		/** Converts position in NDC to screen coordinates in pixels. */
+		uint2 NDCToScreen(float2 ndcPos)
+		{
+			float2 uv = NDCToUV(ndcPos);
+			return UVToScreen(uv);
+		}
+		
+		/** Converts position in NDC to world space. */
+		float3 NDCToWorld(float2 ndcPos, float depth)
+		{
+			// x, y are now in clip space, z, w are in view space
+			// We multiply them by a special inverse view-projection matrix, that had the projection entries that effect
+			// z, w eliminated (since they are already in view space)
+			// Note: Multiply by depth should be avoided if using ortographic projection
+			float4 mixedSpacePos = float4(ndcPos.xy * -depth, depth, 1);
+			float4 worldPosition4D = mul(gMatScreenToWorld, mixedSpacePos);
+			
+			return worldPosition4D.xyz / worldPosition4D.w;
+		}
 	};
 };

+ 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"
@@ -24,6 +22,7 @@ mixin ShadowDepthBase
 		
 		cbuffer ShadowParams
 		{
+			float4x4 gMatViewProj;
 			float gDepthBias;
 			float gDepthRange;
 		};
@@ -39,8 +38,10 @@ mixin 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 vsmain(VertexInput_PO input)
@@ -66,8 +67,6 @@ mixin ShadowDepthBase
 
 technique ShadowDepth
 {
-	mixin GBufferOutput;
-	mixin PerCameraData;
 	mixin PerObjectData;
 	mixin NormalVertexInput;
 	mixin ShadowDepthBase;
@@ -76,8 +75,6 @@ technique ShadowDepth
 
 technique ShadowDepthSkinned
 {
-	mixin GBufferOutput;
-	mixin PerCameraData;
 	mixin PerObjectData;
 	mixin SkinnedVertexInput;
 	mixin ShadowDepthBase;
@@ -88,8 +85,6 @@ technique ShadowDepthSkinned
 
 technique ShadowDepthMorph
 {
-	mixin GBufferOutput;
-	mixin PerCameraData;
 	mixin PerObjectData;
 	mixin MorphVertexInput;
 	mixin ShadowDepthBase;
@@ -100,8 +95,6 @@ technique ShadowDepthMorph
 
 technique ShadowDepthSkinnedMorph
 {
-	mixin GBufferOutput;
-	mixin PerCameraData;
 	mixin PerObjectData;
 	mixin SkinnedMorphVertexInput;
 	mixin ShadowDepthBase;

+ 40 - 0
Data/Raw/Engine/Includes/ShadowProjectionCommon.bslinc

@@ -0,0 +1,40 @@
+#include "$ENGINE$/PerCameraData.bslinc"
+
+mixin ShadowProjectionCommon
+{
+	mixin PerCameraData;
+
+	code
+	{
+		struct VStoFS
+		{
+			float4 position : SV_POSITION;
+		};
+
+		struct VertexInput
+		{
+			float3 position : POSITION;
+		};
+		
+		#if NEEDS_TRANSFORM
+		cbuffer VertParams
+		{
+			float4 gPositionAndScale;
+		};
+		#endif
+		
+		VStoFS vsmain(VertexInput input)
+		{
+			VStoFS output;
+			
+			#if NEEDS_TRANSFORM
+				float3 worldPos = input.position.xyz * gPositionAndScale.w + gPositionAndScale.xyz;
+				output.position = mul(gMatViewProj, float4(worldPos, 1));
+			#else
+				output.position = float4(input.position, 1);
+			#endif
+		
+			return output;
+		}			
+	};
+};

+ 67 - 0
Data/Raw/Engine/Shaders/DeferredDirectionalLight.bsl

@@ -0,0 +1,67 @@
+#include "$ENGINE$\DeferredLightCommon.bslinc"
+
+technique DeferredDirectionalLight
+{
+	mixin DeferredLightCommon;
+
+	depth
+	{
+		read = false;
+	};
+	
+	code 
+	{
+		struct VStoFS
+		{
+			float4 position : SV_POSITION;
+			float2 uv0 : TEXCOORD0;
+			float3 screenDir : TEXCOORD1;
+		};
+
+		struct VertexInput
+		{
+			float2 screenPos : POSITION;
+			float2 uv0 : TEXCOORD0;
+		};
+		
+		VStoFS vsmain(VertexInput input)
+		{
+			VStoFS output;
+		
+			output.position = float4(input.screenPos, 0, 1);
+			output.uv0 = input.uv0;
+			output.screenDir = mul(gMatInvProj, float4(input.screenPos, 1, 0)).xyz - gViewOrigin.xyz;
+		
+			return output;
+		}
+
+		float4 fsmain(VStoFS input, uint sampleIdx : SV_SampleIndex) : SV_Target0
+		{
+			uint2 pixelPos = (uint2)(input.uv0 * (float2)gViewportRectangle.zw - ((float2)gViewportRectangle.xy + 0.5f));
+			
+			#if MSAA_COUNT > 1
+			SurfaceData surfaceData = getGBufferData(pixelPos, sampleIdx);
+			#else
+			SurfaceData surfaceData = getGBufferData(pixelPos);
+			#endif			
+		
+			if(surfaceData.worldNormal.w > 0.0f)
+			{
+				float3 cameraDir = normalize(input.screenDir);
+				float3 worldPosition = input.screenDir * surfaceData.depth + gViewOrigin;
+				
+				float3 V = normalize(gViewOrigin - worldPosition);
+				float3 N = surfaceData.worldNormal.xyz;
+				float3 R = 2 * dot(V, N) * N - V;
+
+				float roughness2 = max(surfaceData.roughness, 0.08f);
+				roughness2 *= roughness2;
+				
+				LightData lightData = getLightData();
+				return float4(getLuminanceDirectional(lightData, worldPosition, V, R, surfaceData), 1.0f);
+			}
+			else
+				return float4(0.0f, 0.0f, 0.0f, 0.0f);
+		}
+	};
+};

+ 121 - 0
Data/Raw/Engine/Shaders/DeferredPointLight.bsl

@@ -0,0 +1,121 @@
+#include "$ENGINE$\DeferredLightCommon.bslinc"
+
+technique DeferredPointLight
+{
+	mixin DeferredLightCommon;
+
+	code
+	{
+		struct VStoFS
+		{
+			float4 position : SV_POSITION;
+			float4 screenPos : TEXCOORD0;
+		};
+
+		struct VertexInput
+		{
+			float3 position : POSITION;
+			uint vertexIdx : SV_VERTEXID;
+		};
+		
+		VStoFS vsmain(VertexInput input)
+		{
+			VStoFS output;
+			
+			float3 worldPosition;
+			uint numSides = gLightGeometry.x;
+			if(numSides > 0) // Generate spot light geometry
+			{
+				uint numSlices = gLightGeometry.y;
+				float radius = gLightGeometry.w;
+				float angle = gLightSpotAnglesAndSqrdInvAttRadius.x;
+			
+				// Extra scale to ensure edges lie on the circle, not inside it
+				// TODO - These can be precomputed
+				float extraRadiusScale = 1.0f / cos(PI / (float)numSides);
+				float angleTan = tan(angle);
+				float height = radius / angleTan;
+	
+				uint sphereStartIdx = numSides * numSlices;
+				// Cone vertices
+				if (input.vertexIdx < sphereStartIdx)
+				{
+					uint sliceIdx = input.vertexIdx / numSides;
+					uint sideIdx = input.vertexIdx % numSides;
+
+					float curAngle = sideIdx * 2 * PI / (float)numSides;
+					float sliceOffset = height * sliceIdx / (float)(numSlices - 1);
+					float sliceRadius = sliceOffset * angleTan * extraRadiusScale;
+
+					float4 localPos = float4(sliceRadius * cos(curAngle), 
+						sliceRadius * sin(curAngle), -sliceOffset, 1.0f);
+					worldPosition = (mul(gMatConeTransform, localPos)).xyz;
+				}
+				else // Sphere cap vertices
+				{
+					uint sphereVertexIdx = input.vertexIdx - sphereStartIdx;
+					uint sliceIdx = sphereVertexIdx / numSides;
+					uint sideIdx = sphereVertexIdx % numSides;
+
+					float curAngle = sideIdx * 2 * PI / (float)numSides;
+					float sliceOffset = radius * sliceIdx / (float)(numSlices - 1);
+					float sliceRadius = sqrt(max(0.0f, radius * radius - sliceOffset * sliceOffset)) * extraRadiusScale;
+
+					float4 localPos = float4(sliceRadius * cos(curAngle), 
+						sliceRadius * sin(curAngle), -height - sliceOffset, 1.0f);
+					worldPosition = (mul(gMatConeTransform, localPos)).xyz;
+				}
+			}
+			else // Scale and position pre-generated sphere geometry
+			{
+				worldPosition = input.position * gLightGeometry.z + gLightPositionAndSrcRadius.xyz;
+			}
+			
+			output.screenPos = mul(gMatViewProj, float4(worldPosition, 1));
+			output.position = output.screenPos;
+			
+			return output;
+		}			
+
+		float4 fsmain(VStoFS input, uint sampleIdx : SV_SampleIndex) : SV_Target0
+		{
+			float2 ndcPos = input.screenPos.xy / input.screenPos.w;
+			uint2 pixelPos = NDCToScreen(ndcPos);
+			
+			#if MSAA_COUNT > 1
+			SurfaceData surfaceData = getGBufferData(pixelPos, sampleIdx);
+			#else
+			SurfaceData surfaceData = getGBufferData(pixelPos);
+			#endif
+
+			if(surfaceData.worldNormal.w > 0.0f)
+			{
+				// x, y are now in clip space, z, w are in view space
+				// We multiply them by a special inverse view-projection matrix, that had the projection entries that effect
+				// z, w eliminated (since they are already in view space)
+				// Note: Multiply by depth should be avoided if using ortographic projection
+				float4 mixedSpacePos = float4(ndcPos.xy * -surfaceData.depth, surfaceData.depth, 1);
+				float4 worldPosition4D = mul(gMatScreenToWorld, mixedSpacePos);
+				float3 worldPosition = worldPosition4D.xyz / worldPosition4D.w;
+
+				float3 V = normalize(gViewOrigin - worldPosition);
+				float3 N = surfaceData.worldNormal.xyz;
+				float3 R = 2 * dot(V, N) * N - V;
+				float3 specR = getSpecularDominantDir(N, R, surfaceData.roughness);
+				
+				float roughness2 = max(surfaceData.roughness, 0.08f);
+				roughness2 *= roughness2;
+				
+				LightData lightData = getLightData();
+				
+				bool isSpot = gShiftedLightPositionAndType.w > 0.5f;
+				if(isSpot)
+					return float4(getLuminanceSpot(lightData, worldPosition, V, R, roughness2, surfaceData), 1.0f);
+				else // Radial
+					return float4(getLuminanceRadial(lightData, worldPosition, V, R, roughness2, surfaceData), 1.0f);
+			}
+			else
+				return float4(0.0f, 0.0f, 0.0f, 0.0f);
+		}
+	};
+};

+ 1 - 1
Data/Raw/Engine/Shaders/LightGridLLCreation.bsl

@@ -1,5 +1,5 @@
 #include "$ENGINE$\PerCameraData.bslinc"
-#define USE_LIGHT_GRID_INDICES
+#define USE_LIGHT_GRID_INDICES 1
 #include "$ENGINE$\LightingCommon.bslinc"
 #include "$ENGINE$\ImageBasedLighting.bslinc"
 #include "$ENGINE$\LightGridCommon.bslinc"

+ 5 - 5
Data/Raw/Engine/Shaders/ReflectionCubeImportanceSample.bsl

@@ -10,23 +10,24 @@ technique ReflectionCubeImportanceSample
 	{
 		#define PI 3.1415926
 	
-		// From Hacker's Delight
-		float reverseBits(uint bits)  
+		float radicalInverse(uint bits)  
 		{
+			// Reverse bits. Algorithm from Hacker's Delight.
 			bits = (bits << 16u) | (bits >> 16u);
 			bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
 			bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
 			bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
 			bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
 			
-			return float(bits) * 2.3283064365386963e-10; // 0x100000000
+			// Normalizes unsigned int in range [0, 4294967295] to [0, 1]
+			return float(bits) * 2.3283064365386963e-10;
 		}
 		
 		float2 hammersleySequence(uint i, uint count)
 		{
 			float2 output;
 			output.x = i / (float)count;
-			output.y = reverseBits(i);
+			output.y = radicalInverse(i);
 			
 			return output;
 		}
@@ -59,7 +60,6 @@ technique ReflectionCubeImportanceSample
 			return roughness4 * cosTheta * sinTheta / (d*d*PI);
 		}
 		
-		[internal]
 		cbuffer Input
 		{
 			int gCubeFace;

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

@@ -11,11 +11,14 @@ mixin 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"
+
+mixin ShadowDepth
+{ };

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

@@ -1,3 +1,4 @@
+#define LINEAR_DEPTH_RANGE 1
 #include "$ENGINE$\ShadowDepthBase.bslinc"
 
 mixin ShadowDepth

+ 220 - 0
Data/Raw/Engine/Shaders/ShadowProject.bsl

@@ -0,0 +1,220 @@
+#include "$ENGINE$/GBufferInput.bslinc"
+#include "$ENGINE$/ShadowProjectionCommon.bslinc"
+
+technique ShadowProject
+{
+	mixin GBufferInput;
+	mixin ShadowProjectionCommon;
+
+	code
+	{
+		Texture2D gShadowTex;
+		SamplerState gShadowSampler;
+	
+		cbuffer Params
+		{
+			// Transform a point in mixed space (xy - clip space, z - view space) to a point
+			// in shadow space
+			float4x4 gMixedToShadowSpace;
+			float2 gShadowMapSize;
+			float2 gShadowMapSizeInv;
+			float gSoftTransitionScale;
+			float gFadePercent;
+			
+			float gFadePlaneDepth;
+			float gInvFadePlaneRange;
+		};
+		
+		// Converts a set of shadow depths into occlusion values, where 1 means scene object is occluded and 0
+		// not occluded. Values between 1 and 0 are used for soft transitions on receivers that are near casters.
+		float4 getOcclusion(float4 shadowDepth, float sceneDepth)
+		{
+			// Offset the shadow a bit to reduce shadow acne and use scale for soft transitions.
+			// Visualization (Mathematica): Plot[1.0 - Clip[(500 - x)*0.5 + 1, {0, 1}], {x, 480, 520}]
+			return 1.0f - saturate((shadowDepth - sceneDepth) * gSoftTransitionScale + 1);
+		}
+		
+		// Takes UV coordinates as input and returns a location to sample from, and a fraction
+		// that can be used for bilinear interpolation between the samples. Returned sample
+		// center is always located on a border between texels, in UV space.
+		float2 getFilteringInfo(float2 uv, out float2 fraction)
+		{
+			// UV to texel position
+			float2 texelXY = uv * gShadowMapSize;
+
+			// -0.5f offset because UV (0, 0) maps to (-0.5, -0.5) texel position
+			texelXY -= 0.5f;
+
+			// Get fraction to use for interpolation
+			fraction = frac(texelXY);
+
+			// Get center location to gather from (in UV coordinates)
+			return (floor(texelXY) + 0.5f)  * gShadowMapSizeInv;
+		}
+		
+		float PCF1x1(float2 uv, float sceneDepth)
+		{
+			float depthSample = gShadowTex.Sample(gShadowSampler, uv).r;
+			return getOcclusion(depthSample.rrrr, sceneDepth).r;
+		}
+	
+		float PCF2x2(float2 uv, float sceneDepth)
+		{
+			float2 fraction;
+			float2 sampleCenter = getFilteringInfo(uv, fraction);
+						
+			// Gather four samples. Samples are returned in counter-clockwise order, starting with lower left
+			float4 depthSamples = gShadowTex.GatherRed(gShadowSampler, sampleCenter);
+			
+			// Convert samples to occlusion
+			float4 occlusion = getOcclusion(depthSamples, sceneDepth);
+			
+			// Perform bilinear interpolation
+			float2 lerpHorz = lerp(occlusion.wx, occlusion.zy, fraction.xx);
+			return lerp(lerpHorz.x, lerpHorz.y, fraction.y);				
+		}
+		
+		float PCF4x4(float2 uv, float sceneDepth)
+		{
+			float2 fraction;
+			float2 sampleCenter = getFilteringInfo(uv, fraction);
+							
+			// Gather 16 samples in four 2x2 gathers. Samples are returned in counter-clockwise order, starting with lower left.
+			// Gathers are performed in clockwise order, starting with top left block.
+			float4 topLeftSamples = gShadowTex.GatherRed(gShadowSampler, sampleCenter, int2(-1, -1));
+			float4 topRightSamples = gShadowTex.GatherRed(gShadowSampler, sampleCenter, int2(1, -1));
+			float4 botLeftSamples = gShadowTex.GatherRed(gShadowSampler, sampleCenter, int2(-1, 1));
+			float4 botRightSamples = gShadowTex.GatherRed(gShadowSampler, sampleCenter, int2(1, 1));
+			
+			// Convert samples to occlusion
+			float4 topLeftOcclusion = getOcclusion(topLeftSamples, sceneDepth);
+			float4 topRightOcclusion = getOcclusion(topRightSamples, sceneDepth);
+			float4 botLeftOcclusion = getOcclusion(botLeftSamples, sceneDepth);
+			float4 botRightOcclusion = getOcclusion(botRightSamples, sceneDepth);
+			
+			// Get the average occusion value. Fraction only needs to be applied to edge samples.
+			//// Acculate occlusion per row
+			float4 rowOcclusion;
+			
+			//// Add column 1, top to bottom
+			rowOcclusion.x = topLeftOcclusion.w * (1.0f - fraction.x);
+			rowOcclusion.y = topLeftOcclusion.x * (1.0f - fraction.x);
+			rowOcclusion.z = botLeftOcclusion.w * (1.0f - fraction.x);
+			rowOcclusion.w = botLeftOcclusion.x * (1.0f - fraction.x);
+			
+			//// Add column 2 & 3, top to bottom
+			rowOcclusion.x += topLeftOcclusion.z + topRightOcclusion.w;
+			rowOcclusion.y += topLeftOcclusion.y + topRightOcclusion.x;
+			rowOcclusion.z += botLeftOcclusion.z + botRightOcclusion.w;
+			rowOcclusion.w += botLeftOcclusion.y + botRightOcclusion.x;
+			
+			//// Add column 4, top to bottom
+			rowOcclusion.x += topRightOcclusion.z * fraction.x;
+			rowOcclusion.y += topRightOcclusion.y * fraction.x;
+			rowOcclusion.z += botRightOcclusion.z * fraction.x;
+			rowOcclusion.w += botRightOcclusion.w * fraction.x;
+			
+			//// Accumulate occlusion per columns
+			float4 occlusionAccumulator = dot(rowOcclusion, float4(1.0f - fraction.y, 1.0f, 1.0f, fraction.y));
+			
+			// Calc average occlusion using a 3x3 area and return
+			return occlusionAccumulator * (1.0f / 9.0f);				
+		}
+		
+		// Accumulates samples for all columns in a row, for 6x2 samples. Samples are provided in three 2x2
+		// blocks. Samples in a block are in counter-clockwise order, starting with lower left. Returns two
+		// rows with their accumulated values, starting with top row.
+		float2 accumulateRows6x2(float fraction, float4 left, float4 mid, float4 right)
+		{
+			float2 row;
+			
+			// Column 1, top to bottom
+			row.x = left.w * (1.0f - fraction);
+			row.y = left.x * (1.0f - fraction);
+			
+			// Columns 2, 3, 4, 5, top to bottom
+			row.x += left.z + mid.w + mid.z + right.w;
+			row.y += left.y + mid.x + mid.y + right.x;
+			
+			// Column 6, top to bottom
+			row.x += right.z * fraction;
+			row.y += right.y * fraction;
+			
+			return row;
+		}
+		
+		float PCF6x6(float2 uv, float sceneDepth)
+		{
+			float2 fraction;
+			float2 sampleCenter = getFilteringInfo(uv, fraction);
+							
+			// Gather 36 samples in nine 2x2 gathers. Gathers are performed in clockwise order, starting with top left block.
+			// Every three gathers (one row), the values are accumulated to their corresponding row.
+			// Samples for individual gather operations are returned in counter-clockwise order, starting with lower left.
+			float2 rows[3];
+			[unroll]
+			for(int i = 0; i < 3; i++)
+			{
+				int y = -2 + i * 2;
+			
+				float4 left = getOcclusion(gShadowTex.GatherRed(gShadowSampler, sampleCenter, int2(-2, y)), sceneDepth);
+				float4 middle = getOcclusion(gShadowTex.GatherRed(gShadowSampler, sampleCenter, int2(0, y)), sceneDepth);
+				float4 right = getOcclusion(gShadowTex.GatherRed(gShadowSampler, sampleCenter, int2(2, y)), sceneDepth);
+				
+				rows[i] = accumulateRows6x2(fraction.x, left, middle, right);
+			}
+			
+			// Accumulate all rows
+			float occlusionAccumulator;
+			occlusionAccumulator = rows[0].x * (1.0f - fraction.y);
+			occlusionAccumulator += rows[0].y + rows[1].x + rows[1].y + rows[2].x;
+			occlusionAccumulator += rows[2].y * fraction.y;
+							
+			// Calc average occlusion using 5x5 area and return
+			return occlusionAccumulator * (1.0f / 25.0f);				
+		}
+
+		float4 fsmain(VStoFS input, uint sampleIdx : SV_SampleIndex) : SV_Target0
+		{
+			// Get depth & calculate world position
+			#if MSAA_COUNT > 1
+			uint2 screenPos = NDCToScreen(input.position.xy);
+			float deviceZ = gDepthBufferTex.Load(screenPos, sampleIdx).r;
+			#else
+			float2 screenUV = NDCToUV(input.position.xy);				
+			float deviceZ = gDepthBufferTex.Sample(gDepthBufferSamp, screenUV).r;
+			#endif
+			
+			float depth = convertFromDeviceZ(deviceZ);
+			float4 mixedSpacePos = float4(input.position.xy * -depth, depth, 1);
+			
+			float4 shadowPosition = mul(gMixedToShadowSpace, mixedSpacePos); 
+			shadowPosition.xy /= shadowPosition.w;
+			
+			// Clamp depth range because pixels in the shadow map that haven't been rendered to will have a value of 1,
+			// and we want those to remain unshadowed.
+			float lightSpaceDepth = min(shadowPosition.z, 0.999999f);
+			
+			float occlusion = 0.0f;
+			#if SHADOW_QUALITY <= 1
+				occlusion = PCF1x1(shadowPosition.xy, lightSpaceDepth);
+			#elif SHADOW_QUALITY == 2
+				occlusion = PCF2x2(shadowPosition.xy, lightSpaceDepth);
+			#elif SHADOW_QUALITY == 3
+				occlusion = PCF4x4(shadowPosition.xy, lightSpaceDepth);
+			#else
+				occlusion = PCF6x6(shadowPosition.xy, lightSpaceDepth);
+			#endif
+			
+			float alpha = 1.0f;
+			#if FADE_PLANE
+				alpha = 1.0f - saturate((depth - gFadePlaneDepth) * gInvFadePlaneRange);
+			#endif
+
+			occlusion *= gFadePercent;
+			
+			// Encode to get better precision in the blacks, similar to gamma correction but cheaper to execute
+			return float4(sqrt(occlusion), 0.0f, 0.0f, alpha);
+		}
+	};
+};

+ 178 - 0
Data/Raw/Engine/Shaders/ShadowProjectOmni.bsl

@@ -0,0 +1,178 @@
+#include "$ENGINE$/GBufferInput.bslinc"
+#include "$ENGINE$/ShadowProjectionCommon.bslinc"
+
+technique ShadowProjectOmni
+{
+	mixin GBufferInput;
+	mixin ShadowProjectionCommon;
+
+	code
+	{
+		// Random samples on a disc of radius 2.5. Random values generated using low discrepancy
+		// Hammersley sequence and then mapped to the disc.
+		static const float2 discSamples4[4] =
+		{
+			float2(0, 0),
+			float2(-1.25, 6.69872e-08),
+			float2(4.73671e-08, 1.76777),
+			float2(-1.74038e-07, -2.16506)
+		};
+		
+		static const float2 discSamples12[12] =
+		{
+			float2(0, 0),
+			float2(-0.721688, 3.86751e-08),
+			float2(2.73474e-08, 1.02062),
+			float2(-1.00481e-07, -1.25),
+			float2(1.02062, 1.02062),
+			float2(-1.14109, -1.14109),
+			float2(-1.25, 1.25),
+			float2(1.35015, -1.35015),
+			float2(1.88586, 0.781149),
+			float2(-2.00026, -0.828534),
+			float2(-0.873351, 2.10846),
+			float2(0.915979, -2.21137)
+		};
+
+		static const float2 discSamples32[32] =
+		{
+			float2(0, 0),
+			float2(-0.441942, 2.36836e-08),
+			float2(1.67468e-08, 0.625),
+			float2(-6.15317e-08, -0.765466),
+			float2(0.625, 0.625),
+			float2(-0.698771, -0.698771),
+			float2(-0.765465, 0.765466),
+			float2(0.826797, -0.826797),
+			float2(1.15485, 0.478354),
+			float2(-1.2249, -0.507371),
+			float2(-0.534816, 1.29116),
+			float2(0.56092, -1.35418),
+			float2(0.585862, 1.4144),
+			float2(-0.609785, -1.47215),
+			float2(-1.52772, 0.632803),
+			float2(1.58134, -0.655014),
+			float2(1.7338, 0.344874),
+			float2(-1.78716, -0.355488),
+			float2(-0.365794, 1.83897),
+			float2(0.375818, -1.88936),
+			float2(1.09804, 1.64334),
+			float2(-1.12516, -1.68392),
+			float2(-1.72355, 1.15164),
+			float2(1.76228, -1.17752),
+			float2(1.80018, 1.20284),
+			float2(-1.83731, -1.22765),
+			float2(-1.25196, 1.87369),
+			float2(1.27581, -1.90938),
+			float2(0.456226, 2.2936),
+			float2(-0.464301, -2.3342),
+			float2(-2.3741, 0.472239),
+			float2(2.41335, -0.480045)
+		};
+		
+		TextureCube gShadowCubeTex;
+		SamplerComparisonState gShadowCubeSampler;
+		
+		cbuffer Params
+		{
+			float4x4 gFaceVPMatrices[6];
+			float4 gLightPosAndRadius;
+			float gInvResolution;
+			float gFadePercent;
+			float gDepthBias;
+		};			
+		
+		// Returns occlusion where 1 = fully shadowed, 0 = not shadowed
+		float cubemapPCF(float3 worldPos, float3 lightPos, float lightRadius)
+		{
+			float3 toLight = lightPos - worldPos;
+			float distToLight = length(toLight);
+			
+			// No occlusion if outside radius
+			if(distToLight > lightRadius)
+				return 0.0f;
+				
+			float3 lightDir = toLight / distToLight;
+			
+			float3 up = abs(lightDir.z) < 0.999f ? float3(0, 0, 1) : float3(1, 0, 0);
+			float3 side = normalize(cross(up, lightDir));
+			up = cross(lightDir, side);
+			
+			up *= gInvResolution;
+			side *= gInvResolution;
+			
+			// Determine cube face to sample from
+			float3 absToLight = abs(toLight);
+			float maxComponent = max(absToLight.x, max(absToLight.y, absToLight.z));
+			
+			int faceIdx = 0;
+			if(maxComponent == absToLight.x)
+				faceIdx = toLight.x > 0.0f ? 0 : 1;
+			else if(maxComponent == absToLight.z)
+				faceIdx = toLight.z > 0.0f ? 4 : 5;
+			else
+				faceIdx = toLight.y > 0.0f ? 2 : 3;
+			
+			// Get position of the receiver in shadow space
+			float4 shadowPos = mul(gFaceVPMatrices[faceIdx], worldPos);
+			
+			float receiverDepth = shadowPos.z / shadowPos.w;
+			float shadowBias = gDepthBias / shadowPos.w;
+			
+			float occlusion = 0.0f;
+			#if SHADOW_QUALITY <= 1
+				//occlusion = gShadowCubeTex.SampleCmpLevelZero(gShadowCubeSampler, lightDir, receiverDepth - shadowBias);
+			#elif SHADOW_QUALITY == 2
+				[unroll]
+				for(int i = 0; i < 4; ++i)
+				{
+					float sampleDir = lightDir + side * discSamples4[i].x + up * discSamples4[i].y;
+					//occlusion += gShadowCubeTex.SampleCmpLevelZero(gShadowCubeSampler, sampleDir, receiverDepth - shadowBias);
+				}
+				
+				occlusion /= 4;
+			#elif SHADOW_QUALITY == 3
+				[unroll]
+				for(int i = 0; i < 12; ++i)
+				{
+					float sampleDir = lightDir + side * discSamples12[i].x + up * discSamples12[i].y;
+					//occlusion += gShadowCubeTex.SampleCmpLevelZero(gShadowCubeSampler, sampleDir, receiverDepth - shadowBias);
+				}
+				
+				occlusion /= 12;
+			#else
+				[unroll]
+				for(int i = 0; i < 32; ++i)
+				{
+					float sampleDir = lightDir + side * discSamples32[i].x + up * discSamples32[i].y;
+					//occlusion += gShadowCubeTex.SampleCmpLevelZero(gShadowCubeSampler, sampleDir, receiverDepth - shadowBias);
+				}
+				
+				occlusion /= 32;
+			#endif
+			
+			return occlusion;
+		}
+		
+		float4 fsmain(VStoFS input, uint sampleIdx : SV_SampleIndex) : SV_Target0
+		{
+			// Get depth & calculate world position
+			#if MSAA_COUNT > 1
+			uint2 screenPos = NDCToScreen(input.position.xy);
+			float deviceZ = gDepthBufferTex.Load(screenPos, sampleIdx).r;
+			#else
+			float2 screenUV = NDCToUV(input.position.xy);				
+			float deviceZ = gDepthBufferTex.Sample(gDepthBufferSamp, screenUV).r;
+			#endif
+			
+			float depth = convertFromDeviceZ(deviceZ);
+			float3 worldPos = NDCToWorld(input.position.xy, depth);
+		
+			float occlusion = cubemapPCF(worldPos, gLightPosAndRadius.xyz, gLightPosAndRadius.w);
+			occlusion *= gFadePercent;
+			
+			// Encode to get better precision in the blacks, similar to gamma correction but cheaper to execute
+			return sqrt(occlusion);
+		}
+	};
+};

+ 1 - 1
Data/Raw/Engine/Shaders/TiledDeferredImageBasedLighting.bsl

@@ -1,7 +1,7 @@
 #include "$ENGINE$\GBufferInput.bslinc"
 #include "$ENGINE$\PerCameraData.bslinc"
 #include "$ENGINE$\ReflectionCubemapCommon.bslinc"
-#define USE_COMPUTE_INDICES
+#define USE_COMPUTE_INDICES 1
 #include "$ENGINE$\LightingCommon.bslinc"
 #include "$ENGINE$\ImageBasedLighting.bslinc"
 

+ 1 - 1
Data/Raw/Engine/Shaders/TiledDeferredLighting.bsl

@@ -1,6 +1,6 @@
 #include "$ENGINE$\GBufferInput.bslinc"
 #include "$ENGINE$\PerCameraData.bslinc"
-#define USE_COMPUTE_INDICES
+#define USE_COMPUTE_INDICES 1
 #include "$ENGINE$\LightingCommon.bslinc"
 #include "$ENGINE$\ReflectionCubemapCommon.bslinc"
 #include "$ENGINE$\ImageBasedLighting.bslinc"

+ 1 - 1
Data/Raw/Engine/Shaders/Transparent.bsl

@@ -1,7 +1,7 @@
 #include "$ENGINE$\BasePass.bslinc"
 #include "$ENGINE$\LightGridCommon.bslinc"
 #include "$ENGINE$\ReflectionCubemapCommon.bslinc"
-#define USE_LIGHT_GRID_INDICES
+#define USE_LIGHT_GRID_INDICES 1
 #include "$ENGINE$\LightingCommon.bslinc"
 #include "$ENGINE$\ImageBasedLighting.bslinc"
 #include "$ENGINE$\Surface.bslinc"

+ 0 - 1
Documentation/Doxygen/footer.html

@@ -16,7 +16,6 @@
                     <h4 class="ui inverted header">Info</h4>
                     <div class="ui inverted link list">
                         <a href="http://www.banshee3d.com/contribute" class="item">Contribute</a>
-                        <a href="http://www.banshee3d.com/development-state" class="item">Development state</a>
                         <a href="https://github.com/BearishSun/BansheeEngine/blob/master/Documentation/GitHub/roadmap.md" target="_blank" class="item">Roadmap</a>
                         <a href="https://github.com/BearishSun/BansheeEngine/blob/master/Documentation/GitHub/license.md" target="_blank" class="item">License</a>
                     </div>

+ 7 - 8
Documentation/GitHub/roadmap.md

@@ -1,19 +1,19 @@
 # Roadmap
 
-We are currently focusing on finish up a final set of features before releasing 1.0 stable. These features are:
- - Physically based renderer
- - Linux/Mac ports
-
-Planned for Q2 2017 release
-
+Remaining v1.0 features (in order):
+ - Physically based renderer (Planned for Q3 2017)
+ - Linux/Mac ports (Planned for Q4 2017)
+ - v1.0 BETA release (Late 2017/Early 2018)
+ 
 ---------------------------------------------------
 
-Following 1.0 release the focus will be on following features (in no specific order): 
+Post v1.0 features (in no specific order): 
  - Sprites & 2D rendering
  - 2D physics (Box2D integration)
  - 2D animation (Sprite based at least, likely bones as well)
  - Mobile render API (likely Vulkan, possibly Metal for iOS)
  - Android/iOS/WP ports
+ - Occlussion culling
  - High level networking (replication, RPCs)
  - Effects/Particle editor
  - Terrain system
@@ -21,7 +21,6 @@ Following 1.0 release the focus will be on following features (in no specific or
  
 And more to come after that:
  - Dynamic global illumination (+ other high fidelity graphical improvements)
- - Occlussion culling
  - AI (pathfinding, navmesh)
  - VR support
  - Cinematics editor

+ 1 - 1
Documentation/Manuals/Native/manuals.md

@@ -115,7 +115,7 @@ A set of manuals covering advanced functionality intented for those wanting to e
 - [Code style](@ref codeStyle)
 - [Quick reference](@ref quickref)
  
-## General guides
+## Old guides (need updating)
 Name                                      | Description
 ------------------------------------------|-------------
 [BSLFX](@ref bsl)    	  		  		  | Provides a reference for the Banshee Shading Language syntax.

+ 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;
 	};
 	}

+ 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.

+ 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

@@ -59,7 +59,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.
@@ -69,14 +69,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();
 

+ 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;
 

+ 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
+
 	/** @} */
 }
 

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

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

+ 2 - 2
Source/BansheeUtility/Source/BsMatrix4.cpp

@@ -331,8 +331,8 @@ namespace bs
 	}
 
 	Matrix4 Matrix4::projectionPerspective(const Degree& horzFOV, float aspect, float near, float far)
-    {
-	    // Note: Duplicate code in Camera, bring it all here eventually
+	{
+		// Note: Duplicate code in Camera, bring it all here eventually
 		static constexpr float INFINITE_FAR_PLANE_ADJUST = 0.00001f;
 
 		Radian thetaX(horzFOV * 0.5f);

+ 12 - 2
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];

+ 4 - 0
Source/CMake/FindPackageOrBuild.cmake

@@ -43,6 +43,10 @@ function(find_package_or_build DEPENDENCY_NAME DEPENDENCY_INCLUDE_PATH BUILD_OPT
 								--init 
 								-- External/${DEPENDENCY_NAME}
 							WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+		else()
+			execute_process(COMMAND git submodule update
+								-- External/${DEPENDENCY_NAME}
+							WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
 		endif()
 
 		# Build

+ 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. */

+ 71 - 8
Source/RenderBeast/Include/BsLightRendering.h

@@ -22,11 +22,23 @@ namespace bs { namespace ct
 		Vector3 spotAngles;
 		float attRadiusSqrdInv;
 		Vector3 color;
-        float srcRadius;
+		float srcRadius;
 		Vector3 shiftedLightPosition;
 		float padding;
 	};
 
+	BS_PARAM_BLOCK_BEGIN(PerLightParamDef)
+		BS_PARAM_BLOCK_ENTRY(Vector4, gLightPositionAndSrcRadius)
+		BS_PARAM_BLOCK_ENTRY(Vector4, gLightColorAndLuminance)
+		BS_PARAM_BLOCK_ENTRY(Vector4, gLightSpotAnglesAndSqrdInvAttRadius)
+		BS_PARAM_BLOCK_ENTRY(Vector4, gLightDirectionAndAttRadius)
+		BS_PARAM_BLOCK_ENTRY(Vector4, gShiftedLightPositionAndType)
+		BS_PARAM_BLOCK_ENTRY(Vector4, gLightGeometry)
+		BS_PARAM_BLOCK_ENTRY(Matrix4, gMatConeTransform)
+	BS_PARAM_BLOCK_END
+
+	extern PerLightParamDef gPerLightParamDef;
+
 	/**	Renderer information specific to a single light. */
 	class RendererLight
 	{
@@ -36,11 +48,65 @@ 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; }
+		/** 
+		 * Populates the provided parameter block buffer with information about the light. Provided buffer's structure
+		 * must match PerLightParamDef.
+		 */
+		void getParameters(SPtr<GpuParamBlockBuffer>& buffer) const;
+		
+		Light* internal;
+	};
+
+	/** Allows you to easily bind GBuffer textures to some material. */
+	class GBufferParams
+	{
+	public:
+		GBufferParams(const SPtr<Material>& material, const SPtr<GpuParamsSet>& paramsSet);
+
+		/** Binds the GBuffer textures to the pipeline. */
+		void bind(const SPtr<RenderTargets>& renderTargets);
 
 	private:
-		Light* mInternal;
+		GpuParamTexture mGBufferA;
+		GpuParamTexture mGBufferB;
+		GpuParamTexture mGBufferC;
+		GpuParamTexture mGBufferDepth;
+	};
+
+	/** Shader that renders directional light sources during deferred rendering light pass. */
+	template<bool MSAA>
+	class DirectionalLightMat : public RendererMaterial<DirectionalLightMat<MSAA>>
+	{
+		RMAT_DEF("DeferredDirectionalLight.bsl");
+
+	public:
+		DirectionalLightMat();
+
+		/** Binds the material for rendering and sets up any global parameters. */
+		void bind(const SPtr<RenderTargets>& gbuffer, const SPtr<GpuParamBlockBuffer>& perCamera);
+
+		/** Updates the per-light buffers used by the material. */
+		void setPerLightParams(const SPtr<GpuParamBlockBuffer>& perLight);
+	private:
+		GBufferParams mGBufferParams;
+	};
+
+	/** Shader that renders point (radial & spot) light sources during deferred rendering light pass. */
+	template<bool MSAA, bool InsideGeometry>
+	class PointLightMat : public RendererMaterial<PointLightMat<MSAA, InsideGeometry>>
+	{
+		RMAT_DEF("DeferredPointLight.bsl");
+
+	public:
+		PointLightMat();
+
+		/** Binds the material for rendering and sets up any global parameters. */
+		void bind(const SPtr<RenderTargets>& gbuffer, const SPtr<GpuParamBlockBuffer>& perCamera);
+
+		/** Updates the per-light buffers used by the material. */
+		void setPerLightParams(const SPtr<GpuParamBlockBuffer>& perLight);
+	private:
+		GBufferParams mGBufferParams;
 	};
 
 	/** Contains GPU buffers used by the renderer to manipulate lights. */
@@ -96,10 +162,7 @@ namespace bs { namespace ct
 		SPtr<Material> mMaterial;
 		SPtr<GpuParamsSet> mParamsSet;
 
-		GpuParamTexture mGBufferA;
-		GpuParamTexture mGBufferB;
-		GpuParamTexture mGBufferC;
-		GpuParamTexture mGBufferDepth;
+		GBufferParams mGBufferParams;
 
 		Vector3I mLightOffsets;
 		GpuParamBuffer mLightBufferParam;

+ 14 - 19
Source/RenderBeast/Include/BsRenderBeast.h

@@ -25,11 +25,20 @@ namespace bs
 	 *  @{
 	 */
 
+	/** 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
 	{
@@ -40,17 +49,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() { }
@@ -78,13 +76,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;
@@ -194,9 +192,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;
 	};
 
 	/** @} */

+ 33 - 12
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
 	 *  @{
 	 */
@@ -23,7 +26,8 @@ namespace bs
 	{
 		// Cameras and render targets
 		Vector<RendererRenderTarget> renderTargets;
-		UnorderedMap<const Camera*, RendererView*> views;
+		Vector<RendererView*> views;
+		UnorderedMap<const Camera*, UINT32> cameraToView;
 		
 		// Renderables
 		Vector<RendererObject*> renderables;
@@ -39,6 +43,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. */
@@ -49,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);
@@ -103,16 +116,25 @@ 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:
+		/** 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;
@@ -121,6 +143,5 @@ namespace bs
 		SPtr<RenderBeastOptions> mOptions;
 	};
 
-
 	/** @} */
 }}

+ 5 - 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. */
@@ -122,6 +122,7 @@ namespace bs { namespace ct
 		Matrix4 viewProjTransform;
 
 		SPtr<RenderTarget> target;
+		Rect2I viewRect;
 		Rect2 nrmViewRect;
 		UINT32 numSamples;
 
@@ -152,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. */
@@ -179,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().
@@ -274,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;

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

@@ -6,21 +6,343 @@
 #include "BsModule.h"
 #include "BsMatrix4.h"
 #include "BsConvexVolume.h"
+#include "BsParamBlocks.h"
+#include "BsRendererMaterial.h"
+#include "BsTextureAtlasLayout.h"
+#include "BsLight.h"
+#include "BsLightRendering.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);
+	};
+
+	BS_PARAM_BLOCK_BEGIN(ShadowProjectParamsDef)
+		BS_PARAM_BLOCK_ENTRY(Matrix4, gMixedToShadowSpace)
+		BS_PARAM_BLOCK_ENTRY(Vector2, gShadowMapSize)
+		BS_PARAM_BLOCK_ENTRY(Vector2, gShadowMapSizeInv)
+		BS_PARAM_BLOCK_ENTRY(float, gSoftTransitionScale)
+		BS_PARAM_BLOCK_ENTRY(float, gFadePercent)
+		BS_PARAM_BLOCK_ENTRY(float, gFadePlaneDepth)
+		BS_PARAM_BLOCK_ENTRY(float, gInvFadePlaneRange)
+		BS_PARAM_BLOCK_END
+
+	extern ShadowProjectParamsDef gShadowProjectParamsDef;
+
+	/** Material used for projecting depth into a shadow accumulation buffer for non-omnidirectional shadow maps. */
+	template<int ShadowQuality, bool Directional, bool MSAA>
+	class ShadowProjectMat : public RendererMaterial<ShadowProjectMat<ShadowQuality, Directional, MSAA>>
+	{
+		RMAT_DEF("ShadowProject.bsl");
+
+	public:
+		ShadowProjectMat();
+
+		/** Binds the material to the pipeline, ready to be used on subsequent draw calls. */
+		void bind(const SPtr<Texture>& shadowMap, const SPtr<GpuParamBlockBuffer>& shadowParams, 
+			const SPtr<GpuParamBlockBuffer>& perCamera);
+
+	private:
+		SPtr<SamplerState> mSamplerState;
+
+		GBufferParams mGBufferParams;
+
+		GpuParamTexture mShadowMapParam;
+		GpuParamSampState mShadowSamplerParam;
+	};
+
+	BS_PARAM_BLOCK_BEGIN(ShadowProjectOmniParamsDef)
+		BS_PARAM_BLOCK_ENTRY_ARRAY(Matrix4, gFaceVPMatrices, 6)
+		BS_PARAM_BLOCK_ENTRY(Vector4, gLightPosAndRadius)
+		BS_PARAM_BLOCK_ENTRY(float, gInvResolution)
+		BS_PARAM_BLOCK_ENTRY(float, gFadePercent)
+		BS_PARAM_BLOCK_ENTRY(float, gDepthBias)
+		BS_PARAM_BLOCK_END
+
+	extern ShadowProjectOmniParamsDef gShadowProjectOmniParamsDef;
+
+	/** Material used for projecting depth into a shadow accumulation buffer for omnidirectional shadow maps. */
+	template<int ShadowQuality, bool MSAA>
+	class ShadowProjectOmniMat : public RendererMaterial<ShadowProjectOmniMat<ShadowQuality, MSAA>>
+	{
+		RMAT_DEF("ShadowProjectOmni.bsl");
+
+	public:
+		ShadowProjectOmniMat();
+
+		/** Binds the material to the pipeline, ready to be used on subsequent draw calls. */
+		void bind(const SPtr<Texture>& shadowMap, const SPtr<GpuParamBlockBuffer>& shadowParams,
+			const SPtr<GpuParamBlockBuffer>& perCamera);
+
+	private:
+		SPtr<SamplerState> mSamplerState;
+
+		GBufferParams mGBufferParams;
+
+		GpuParamTexture mShadowMapParam;
+		GpuParamSampState mShadowSamplerParam;
+	};
+
+	/** 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);
+
+		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;
+	};
+
+	/** 
+	 * 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;
+
+		/** 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. */
 	class ShadowRendering : public Module<ShadowRendering>
 	{
+		/** Contains information required for generating a shadow map for a specific light. */
+		struct ShadowMapOptions
+		{
+			UINT32 lightIdx;
+			UINT32 mapSize;
+			SmallVector<float, 4> fadePercents;
+		};
+
+		/** Contains references to all shadows cast by a specific light. */
+		struct LightShadows
+		{
+			UINT32 startIdx;
+			UINT32 numShadows;
+		};
 	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);
+
+		/** 
+		 * Renders shadow occlusion values for the specified light, into the currently bound render target. 
+		 * The system uses shadow maps rendered by renderShadowMaps().
+		 */
+		void renderShadowOcclusion(const RendererScene& scene, const RendererLight& light, UINT32 viewIdx);
+
+		/** 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(UINT32 viewIdx, UINT32 lightIdx, RendererScene& scene, const FrameInfo& frameInfo);
+
+		/** Renders shadow maps for the provided spot light. */
+		void renderSpotShadowMap(const RendererLight& light, const ShadowMapOptions& options, RendererScene& scene,
+			const FrameInfo& frameInfo);
+
+		/** Renders shadow maps for the provided radial light. */
+		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]	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, 
+			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
 		 * split view frustum.
@@ -46,6 +368,51 @@ namespace bs { namespace ct
 		 * @return						Distance to the split position along the view direction.
 		 */
 		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;
+
+		/** Determines how long will an unused shadow map atlas stay allocated, in frames. */
+		static const UINT32 MAX_UNUSED_FRAMES;
+
+		/** Determines the minimal resolution of a shadow map. */
+		static const UINT32 MIN_SHADOW_MAP_SIZE;
+
+		/** Determines the resolution at which shadow maps begin fading out. */
+		static const UINT32 SHADOW_MAP_FADE_SIZE;
+
+		/** Size of the border of a shadow map in a shadow map atlas, in pixels. */
+		static const UINT32 SHADOW_MAP_BORDER;
+
+		ShadowDepthNormalMat mDepthNormalMat;
+		ShadowDepthCubeMat mDepthCubeMat;
+		ShadowDepthDirectionalMat mDepthDirectionalMat;
+
+		UINT32 mShadowMapSize;
+
+		Vector<ShadowMapAtlas> mDynamicShadowMaps;
+		Vector<ShadowCascadedMap> mCascadedShadowMaps;
+		Vector<ShadowCubemap> mShadowCubemaps;
+
+		Vector<ShadowInfo> mShadowInfos;
+
+		Vector<LightShadows> mSpotLightShadows;
+		Vector<LightShadows> mRadialLightShadows;
+		Vector<UINT32> mDirectionalLightShadows;
+
+		Vector<bool> mRenderableVisibility; // Transient
+		Vector<ShadowMapOptions> mSpotLightShadowOptions; // Transient
+		Vector<ShadowMapOptions> mRadialLightShadowOptions; // 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;
 	}

+ 150 - 23
Source/RenderBeast/Source/BsLightRendering.cpp

@@ -16,22 +16,23 @@ namespace bs { namespace ct
 	static const UINT32 BUFFER_INCREMENT = 16 * sizeof(LightData);
 
 	TiledLightingParamDef gTiledLightingParamDef;
+	PerLightParamDef gPerLightParamDef;
 
 	RendererLight::RendererLight(Light* light)
-		:mInternal(light)
+		:internal(light)
 	{ }
 
 	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,16 +40,150 @@ 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;
 	}
 
+	void RendererLight::getParameters(SPtr<GpuParamBlockBuffer>& buffer) const
+	{
+		LightData lightData;
+		getParameters(lightData);
+
+		float type = 0.0f;
+		switch (internal->getType())
+		{
+		case LightType::Directional:
+			type = 0;
+			break;
+		case LightType::Radial:
+			type = 0.3f;
+			break;
+		case LightType::Spot:
+			type = 0.8f;
+			break;
+		}
+
+		gPerLightParamDef.gLightPositionAndSrcRadius.set(buffer, Vector4(lightData.position, lightData.srcRadius));
+		gPerLightParamDef.gLightColorAndLuminance.set(buffer, Vector4(lightData.color, lightData.luminance));
+		gPerLightParamDef.gLightSpotAnglesAndSqrdInvAttRadius.set(buffer, Vector4(lightData.spotAngles, lightData.attRadiusSqrdInv));
+		gPerLightParamDef.gLightDirectionAndAttRadius.set(buffer, Vector4(lightData.direction, lightData.attRadius));
+		gPerLightParamDef.gShiftedLightPositionAndType.set(buffer, Vector4(lightData.shiftedLightPosition, type));
+
+		Vector4 lightGeometry;
+		lightGeometry.x = internal->getType() == LightType::Spot ? (float)Light::LIGHT_CONE_NUM_SIDES : 0;
+		lightGeometry.y = (float)Light::LIGHT_CONE_NUM_SLICES;
+		lightGeometry.z = internal->getBounds().getRadius();
+
+		float coneRadius = Math::sin(lightData.spotAngles.x) * internal->getAttenuationRadius();
+		lightGeometry.w = coneRadius;
+
+		gPerLightParamDef.gLightGeometry.set(buffer, lightGeometry);
+
+		Matrix4 transform = Matrix4::TRS(internal->getPosition(), internal->getRotation(), Vector3::ONE);
+		gPerLightParamDef.gMatConeTransform.set(buffer, transform);
+	}
+
+	GBufferParams::GBufferParams(const SPtr<Material>& material, const SPtr<GpuParamsSet>& paramsSet)
+	{
+		SPtr<GpuParams> params = paramsSet->getGpuParams();
+
+		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gGBufferATex", mGBufferA);
+		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gGBufferBTex", mGBufferB);
+		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gGBufferCTex", mGBufferC);
+		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gDepthBufferTex", mGBufferDepth);
+	}
+
+	void GBufferParams::bind(const SPtr<RenderTargets>& renderTargets)
+	{
+		mGBufferA.set(renderTargets->getGBufferA());
+		mGBufferB.set(renderTargets->getGBufferB());
+		mGBufferC.set(renderTargets->getGBufferC());
+		mGBufferDepth.set(renderTargets->getSceneDepth());
+	}
+
+	template<bool MSAA>
+	DirectionalLightMat<MSAA>::DirectionalLightMat()
+		:mGBufferParams(mMaterial, mParamsSet)
+	{
+
+	}
+
+	template<bool MSAA>
+	void DirectionalLightMat<MSAA>::_initDefines(ShaderDefines& defines)
+	{
+		if (MSAA)
+			defines.set("MSAA_COUNT", 2); // Actual count doesn't matter, as long as it's greater than one
+		else
+			defines.set("MSAA_COUNT", 1);
+	}
+
+	template<bool MSAA>
+	void DirectionalLightMat<MSAA>::bind(const SPtr<RenderTargets>& gbuffer, const SPtr<GpuParamBlockBuffer>& perCamera)
+	{
+		RendererUtility::instance().setPass(mMaterial, 0);
+
+		mGBufferParams.bind(gbuffer);
+		mParamsSet->setParamBlockBuffer("PerCamera", perCamera, true);
+	}
+
+	template<bool MSAA>
+	void DirectionalLightMat<MSAA>::setPerLightParams(const SPtr<GpuParamBlockBuffer>& perLight)
+	{
+		mParamsSet->setParamBlockBuffer("PerLight", perLight, true);
+		
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
+	template class DirectionalLightMat<true>;
+	template class DirectionalLightMat<false>;
+
+	template<bool MSAA, bool InsideGeometry>
+	PointLightMat<MSAA, InsideGeometry>::PointLightMat()
+		:mGBufferParams(mMaterial, mParamsSet)
+	{
+	}
+
+	template<bool MSAA, bool InsideGeometry>
+	void PointLightMat<MSAA, InsideGeometry>::_initDefines(ShaderDefines& defines)
+	{
+		if (MSAA)
+			defines.set("MSAA_COUNT", 2); // Actual count doesn't matter, as long as it's greater than one
+		else
+			defines.set("MSAA_COUNT", 1);
+
+		if (InsideGeometry)
+			defines.set("INSIDE_GEOMETRY", 1);
+	}
+
+	template<bool MSAA, bool InsideGeometry>
+	void PointLightMat<MSAA, InsideGeometry>::bind(const SPtr<RenderTargets>& gbuffer, 
+		const SPtr<GpuParamBlockBuffer>& perCamera)
+	{
+		RendererUtility::instance().setPass(mMaterial, 0);
+
+		mGBufferParams.bind(gbuffer);
+		mParamsSet->setParamBlockBuffer("PerCamera", perCamera, true);
+	}
+
+	template<bool MSAA, bool InsideGeometry>
+	void PointLightMat<MSAA, InsideGeometry>::setPerLightParams(const SPtr<GpuParamBlockBuffer>& perLight)
+	{
+		mParamsSet->setParamBlockBuffer("PerLight", perLight, true);
+		
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
+	template class PointLightMat<false, false>;
+	template class PointLightMat<false, true>;
+	template class PointLightMat<true, false>;
+	template class PointLightMat<true, true>;
+
 	GPULightData::GPULightData()
 		:mNumLights{}
 	{ }
@@ -97,15 +232,11 @@ namespace bs { namespace ct
 
 	TiledDeferredLighting::TiledDeferredLighting(const SPtr<Material>& material, const SPtr<GpuParamsSet>& paramsSet,
 													UINT32 sampleCount)
-		:mSampleCount(sampleCount), mMaterial(material), mParamsSet(paramsSet), mLightOffsets()
+		: mSampleCount(sampleCount), mMaterial(material), mParamsSet(paramsSet), mGBufferParams(material, paramsSet)
+		, mLightOffsets()
 	{
 		SPtr<GpuParams> params = mParamsSet->getGpuParams();
 
-		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gGBufferATex", mGBufferA);
-		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gGBufferBTex", mGBufferB);
-		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gGBufferCTex", mGBufferC);
-		params->getTextureParam(GPT_COMPUTE_PROGRAM, "gDepthBufferTex", mGBufferDepth);
-
 		params->getBufferParam(GPT_COMPUTE_PROGRAM, "gLights", mLightBufferParam);
 
 		if (params->hasLoadStoreTexture(GPT_COMPUTE_PROGRAM, "gOutput"))
@@ -141,11 +272,7 @@ namespace bs { namespace ct
 		}
 		mParamBuffer->flushToGPU();
 
-		mGBufferA.set(renderTargets->getGBufferA());
-		mGBufferB.set(renderTargets->getGBufferB());
-		mGBufferC.set(renderTargets->getGBufferC());
-		mGBufferDepth.set(renderTargets->getSceneDepth());
-
+		mGBufferParams.bind(renderTargets);
 		mParamsSet->setParamBlockBuffer("PerCamera", perCamera, true);
 
 		if (mSampleCount > 1)

+ 27 - 27
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()
@@ -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);
 	}
@@ -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);
 
@@ -335,12 +342,13 @@ 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]);
+				UINT32 viewIdx = sceneInfo.cameraToView.at(cameras[i]);
+				RendererView* viewInfo = sceneInfo.views[viewIdx];
 				views.push_back(viewInfo);
 			}
 		}
@@ -365,11 +373,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 +389,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 +406,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 +458,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++)

+ 129 - 103
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
 {
@@ -25,37 +26,53 @@ 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();
+
+		UINT32 viewIdx = (UINT32)mInfo.views.size();
+		mInfo.views.push_back(view);
+
+		mInfo.cameraToView[camera] = viewIdx;
+		camera->setRendererId(viewIdx);
+
+		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(),
@@ -63,12 +80,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
+		RendererView* view = mInfo.views[mInfo.views.size() - 1];
+		bs_delete(view);
+
+		mInfo.views.erase(mInfo.views.end() - 1);
+
+		auto iterFind = mInfo.cameraToView.find(camera);
+		if(iterFind != mInfo.cameraToView.end())
+			mInfo.cameraToView.erase(iterFind);
+
+		updateCameraRenderTargets(camera);
 	}
 
 	void RendererScene::registerLight(Light* light)
@@ -116,7 +155,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 +172,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 +190,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)
@@ -417,105 +456,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<RenderTarget> renderTarget = camera->getViewport()->getTarget();
-
-		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;
+		SPtr<Viewport> viewport = camera->getViewport();
+		RENDERER_VIEW_DESC viewDesc;
 
-			viewDesc.target.clearFlags = 0;
-			if (viewport->getRequiresColorClear())
-				viewDesc.target.clearFlags |= FBT_COLOR;
+		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
@@ -573,13 +584,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)
@@ -665,4 +674,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;
+	}
 }}

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

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

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

@@ -2,9 +2,953 @@
 //**************** 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"
+#include "BsCamera.h"
+#include "BsBitwise.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);
+	}
+
+	ShadowProjectParamsDef gShadowProjectParamsDef;
+
+	template<int ShadowQuality, bool Directional, bool MSAA>
+	ShadowProjectMat<ShadowQuality, Directional, MSAA>::ShadowProjectMat()
+		: mGBufferParams(mMaterial, mParamsSet)
+	{
+		SPtr<GpuParams> params = mParamsSet->getGpuParams();
+
+		params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gShadowTex", mShadowMapParam);
+		params->getSamplerStateParam(GPT_FRAGMENT_PROGRAM, "gShadowSampler", mShadowSamplerParam);
+
+		SAMPLER_STATE_DESC desc;
+		desc.minFilter = FO_POINT;
+		desc.magFilter = FO_POINT;
+		desc.mipFilter = FO_POINT;
+		desc.addressMode.u = TAM_CLAMP;
+		desc.addressMode.v = TAM_CLAMP;
+		desc.addressMode.w = TAM_CLAMP;
+
+		mSamplerState = SamplerState::create(desc);
+	}
+
+	template<int ShadowQuality, bool Directional, bool MSAA>
+	void ShadowProjectMat<ShadowQuality, Directional, MSAA>::_initDefines(ShaderDefines& defines)
+	{
+		switch(ShadowQuality)
+		{
+		default:
+		case 1:
+			defines.set("SHADOW_QUALITY", 1);
+			break;
+		case 2:
+			defines.set("SHADOW_QUALITY", 2);
+			break;
+		case 3:
+			defines.set("SHADOW_QUALITY", 3);
+			break;
+		case 4:
+			defines.set("SHADOW_QUALITY", 4);
+			break;
+		}
+
+		defines.set("FADE_PLANE", Directional ? 1 : 0);
+		defines.set("NEEDS_TRANSFORM", Directional ? 0 : 1);
+
+		defines.set("MSAA_COUNT", MSAA ? 2 : 1); // Actual count doesn't matter, as long as its >1 if enabled
+	}
+
+	template<int ShadowQuality, bool Directional, bool MSAA>
+	void ShadowProjectMat<ShadowQuality, Directional, MSAA>::bind(const SPtr<Texture>& shadowMap, 
+		const SPtr<GpuParamBlockBuffer>& shadowParams, const SPtr<GpuParamBlockBuffer>& perCameraParams)
+	{
+		mShadowMapParam.set(shadowMap);
+		mShadowSamplerParam.set(mSamplerState);
+
+		mParamsSet->setParamBlockBuffer("Params", shadowParams);
+		mParamsSet->setParamBlockBuffer("PerCamera", perCameraParams);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
+#define TEMPL_INSTANTIATE(QUALITY)								\
+	template class ShadowProjectMat<QUALITY, true, true>;		\
+	template class ShadowProjectMat<QUALITY, true, false>;		\
+	template class ShadowProjectMat<QUALITY, false, true>;		\
+	template class ShadowProjectMat<QUALITY, false, false>;
+	
+TEMPL_INSTANTIATE(0)
+TEMPL_INSTANTIATE(1)
+TEMPL_INSTANTIATE(2)
+TEMPL_INSTANTIATE(3)
+
+#undef TEMPL_INSTANTIATE
+
+	ShadowProjectOmniParamsDef gShadowProjectOmniParamsDef;
+
+	template<int ShadowQuality, bool MSAA>
+	ShadowProjectOmniMat<ShadowQuality, MSAA>::ShadowProjectOmniMat()
+		: mGBufferParams(mMaterial, mParamsSet)
+	{
+		SPtr<GpuParams> params = mParamsSet->getGpuParams();
+
+		params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gShadowCubeTex", mShadowMapParam);
+		params->getSamplerStateParam(GPT_FRAGMENT_PROGRAM, "gShadowCubeSampler", mShadowSamplerParam);
+
+		SAMPLER_STATE_DESC desc;
+		desc.minFilter = FO_LINEAR;
+		desc.magFilter = FO_LINEAR;
+		desc.mipFilter = FO_POINT;
+		desc.addressMode.u = TAM_CLAMP;
+		desc.addressMode.v = TAM_CLAMP;
+		desc.addressMode.w = TAM_CLAMP;
+		desc.comparisonFunc = CMPF_GREATER_EQUAL;
+
+		mSamplerState = SamplerState::create(desc);
+	}
+
+	template<int ShadowQuality, bool MSAA>
+	void ShadowProjectOmniMat<ShadowQuality, MSAA>::_initDefines(ShaderDefines& defines)
+	{
+		switch(ShadowQuality)
+		{
+		default:
+		case 1:
+			defines.set("SHADOW_QUALITY", 1);
+			break;
+		case 2:
+			defines.set("SHADOW_QUALITY", 2);
+			break;
+		case 3:
+			defines.set("SHADOW_QUALITY", 3);
+			break;
+		case 4:
+			defines.set("SHADOW_QUALITY", 4);
+			break;
+		}
+
+		defines.set("NEEDS_TRANSFORM", 1);
+		defines.set("MSAA_COUNT", MSAA ? 2 : 1); // Actual count doesn't matter, as long as its >1 if enabled
+	}
+
+	template<int ShadowQuality, bool MSAA>
+	void ShadowProjectOmniMat<ShadowQuality, MSAA>::bind(const SPtr<Texture>& shadowMap, 
+		const SPtr<GpuParamBlockBuffer>& shadowParams, const SPtr<GpuParamBlockBuffer>& perCameraParams)
+	{
+		mShadowMapParam.set(shadowMap);
+		mShadowSamplerParam.set(mSamplerState);
+
+		mParamsSet->setParamBlockBuffer("Params", shadowParams);
+		mParamsSet->setParamBlockBuffer("PerCamera", perCameraParams);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
+#define TEMPL_INSTANTIATE(QUALITY)								\
+	template class ShadowProjectOmniMat<QUALITY, true>;			\
+	template class ShadowProjectOmniMat<QUALITY, false>;		\
+	
+TEMPL_INSTANTIATE(0)
+TEMPL_INSTANTIATE(1)
+TEMPL_INSTANTIATE(2)
+TEMPL_INSTANTIATE(3)
+
+#undef TEMPL_INSTANTIATE
+
+	void ShadowInfo::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;
+	const UINT32 ShadowRendering::MIN_SHADOW_MAP_SIZE = 32;
+	const UINT32 ShadowRendering::SHADOW_MAP_FADE_SIZE = 64;
+	const UINT32 ShadowRendering::SHADOW_MAP_BORDER = 4;
+
+	ShadowRendering::ShadowRendering(UINT32 shadowMapSize)
+		: mShadowMapSize(shadowMapSize)
+	{ }
+
+	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 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();
+
+		for (auto& entry : mDynamicShadowMaps)
+			entry.clear();
+
+		for (auto& entry : mShadowCubemaps)
+			entry.clear();
+
+		// Determine shadow map sizes and sort them
+		UINT32 shadowInfoCount = 0;
+		for (UINT32 i = 0; i < (UINT32)sceneInfo.spotLights.size(); ++i)
+		{
+			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 (!light.internal->getCastsShadow() || !sceneInfo.spotLightVisibility[i])
+				continue;
+
+			ShadowMapOptions options;
+			options.lightIdx = i;
+
+			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)
+		{
+			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 (!light.internal->getCastsShadow() || !sceneInfo.radialLightVisibility[i])
+				continue;
+
+			ShadowMapOptions options;
+			options.lightIdx = i;
+
+			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)
+		{
+			const RendererLight& light = sceneInfo.directionalLights[i];
+
+			if (!light.internal->getCastsShadow())
+				return;
+
+			for (UINT32 j = 0; j < (UINT32)sceneInfo.views.size(); ++j)
+				renderCascadedShadowMaps(j, i, scene, frameInfo);
+		}
+
+		for(auto& entry : mSpotLightShadowOptions)
+		{
+			UINT32 lightIdx = entry.lightIdx;
+			renderSpotShadowMap(sceneInfo.spotLights[lightIdx], entry, scene, frameInfo);
+		}
+
+		for (auto& entry : mRadialLightShadowOptions)
+		{
+			UINT32 lightIdx = entry.lightIdx;
+			renderRadialShadowMap(sceneInfo.radialLights[lightIdx], entry, scene, frameInfo);
+		}
+		
+		// Deallocate unused textures
+		for(auto iter = mDynamicShadowMaps.begin(); iter != mDynamicShadowMaps.end(); ++iter)
+		{
+			if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+			{
+				// These are always populated in order, so we can assume all following atlases are also empty
+				mDynamicShadowMaps.erase(iter, mDynamicShadowMaps.end());
+				break;
+			}
+		}
+
+		for(auto iter = mCascadedShadowMaps.begin(); iter != mCascadedShadowMaps.end();)
+		{
+			if (iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+				iter = mCascadedShadowMaps.erase(iter);
+			else
+				++iter;
+		}
+		
+		for(auto iter = mShadowCubemaps.begin(); iter != mShadowCubemaps.end();)
+		{
+			if (iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
+				iter = mShadowCubemaps.erase(iter);
+			else
+				++iter;
+		}
+	}
+
+	void ShadowRendering::renderShadowOcclusion(const RendererScene& scene, const RendererLight& rendererLight, 
+		UINT32 viewIdx)
+	{
+		const Light* light = rendererLight.internal;
+		UINT32 lightIdx = light->getRendererId();
+
+		RenderAPI& rapi = RenderAPI::instance();
+		// TODO - Calculate and set a scissor rectangle for the light
+
+		switch (light->getType())
+		{
+		case LightType::Directional: 
+			// TODO
+			break;
+		case LightType::Radial:
+			{
+				const LightShadows& shadows = mRadialLightShadows[lightIdx];
+				for (UINT32 i = 0; i < shadows.numShadows; ++i)
+				{
+					UINT32 shadowIdx = shadows.startIdx + i;
+					const ShadowInfo& shadowInfo = mShadowInfos[shadowIdx];
+
+					if (shadowInfo.fadePerView[viewIdx] < 0.005f)
+						continue;
+
+					// TODO - Bind shader and necessary parameters
+
+				}
+			}
+			break;
+		case LightType::Spot: 
+			// TODO
+			break;
+		default: 
+			break;
+		}
+	}
+
+	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;
+
+		RenderAPI& rapi = RenderAPI::instance();
+
+		Vector3 lightDir = light->getRotation().zAxis();
+		SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
+
+		ShadowInfo shadowInfo;
+		shadowInfo.lightIdx = lightIdx;
+		shadowInfo.textureIdx = -1;
+
+		UINT32 mapSize = std::min(mShadowMapSize, MAX_ATLAS_SIZE);
+
+		for (UINT32 i = 0; i < (UINT32)mCascadedShadowMaps.size(); i++)
+		{
+			ShadowCascadedMap& shadowMap = mCascadedShadowMaps[i];
+
+			if (!shadowMap.isUsed() && shadowMap.getSize() == mapSize)
+			{
+				shadowInfo.textureIdx = i;
+				shadowMap.markAsUsed();
+
+				break;
+			}
+		}
+
+		if (shadowInfo.textureIdx == -1)
+		{
+			shadowInfo.textureIdx = (UINT32)mCascadedShadowMaps.size();
+			mCascadedShadowMaps.push_back(ShadowCascadedMap(mapSize));
+
+			ShadowCascadedMap& shadowMap = mCascadedShadowMaps.back();
+			shadowMap.markAsUsed();
+		}
+
+		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);
+
+			float orthoSize = frustumBounds.getRadius();
+			Matrix4 proj = Matrix4::projectionOrthographic(-orthoSize, orthoSize, -orthoSize, orthoSize, 0.0f, 1000.0f);
+			RenderAPI::instance().convertProjectionMatrix(proj, proj);
+
+			shadowInfo.shadowVPTransform = proj * viewMat;
+
+			float nearDist = 0.05f;
+			float farDist = light->getAttenuationRadius();
+			float depthRange = farDist - nearDist;
+			float depthBias = getDepthBias(*light, depthRange, mapSize);
+
+			gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, depthBias);
+			gShadowParamsDef.gDepthRange.set(shadowParamsBuffer, depthRange);
+			gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, shadowInfo.shadowVPTransform);
+
+			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);
+				}
+			}
+
+			shadowMap.setShadowInfo(i, shadowInfo);
+		}
+
+		mDirectionalLightShadows[lightIdx] = shadowInfo.textureIdx;
+	}
+
+	void ShadowRendering::renderSpotShadowMap(const RendererLight& rendererLight, const ShadowMapOptions& options,
+		RendererScene& scene, const FrameInfo& frameInfo)
+	{
+		Light* light = rendererLight.internal;
+
+		const SceneInfo& sceneInfo = scene.getSceneInfo();
+		SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
+
+		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(options.mapSize, mapInfo.area, SHADOW_MAP_BORDER))
+			{
+				mapInfo.textureIdx = i;
+
+				foundSpace = true;
+				break;
+			}
+		}
+
+		if (!foundSpace)
+		{
+			mapInfo.textureIdx = (UINT32)mDynamicShadowMaps.size();
+			mDynamicShadowMaps.push_back(ShadowMapAtlas(MAX_ATLAS_SIZE));
+
+			ShadowMapAtlas& atlas = mDynamicShadowMaps.back();
+			atlas.addMap(options.mapSize, mapInfo.area, SHADOW_MAP_BORDER);
+		}
+
+		mapInfo.updateNormArea(MAX_ATLAS_SIZE);
+		ShadowMapAtlas& atlas = mDynamicShadowMaps[mapInfo.textureIdx];
+
+		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 = 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);
+
+		mapInfo.shadowVPTransform = proj * view;
+
+		gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, depthBias);
+		gShadowParamsDef.gDepthRange.set(shadowParamsBuffer, depthRange);
+		gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, mapInfo.shadowVPTransform);
+
+		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));
+
+		LightShadows& lightShadows = mSpotLightShadows[options.lightIdx];
+
+		mShadowInfos[lightShadows.startIdx + lightShadows.numShadows] = mapInfo;
+		lightShadows.numShadows++;
+	}
+
+	void ShadowRendering::renderRadialShadowMap(const RendererLight& rendererLight, 
+		const ShadowMapOptions& options, RendererScene& scene, const FrameInfo& frameInfo)
+	{
+		Light* light = rendererLight.internal;
+
+		const SceneInfo& sceneInfo = scene.getSceneInfo();
+		SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
+		SPtr<GpuParamBlockBuffer> shadowCubeMatricesBuffer = gShadowCubeMatricesDef.createBuffer();
+		SPtr<GpuParamBlockBuffer> shadowCubeMasksBuffer = gShadowCubeMasksDef.createBuffer();
+
+		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() == options.mapSize)
+			{
+				mapInfo.textureIdx = i;
+				cubemap.markAsUsed();
+
+				break;
+			}
+		}
+
+		if (mapInfo.textureIdx == -1)
+		{
+			mapInfo.textureIdx = (UINT32)mShadowCubemaps.size();
+			mShadowCubemaps.push_back(ShadowCubemap(options.mapSize));
+
+			ShadowCubemap& cubemap = mShadowCubemaps.back();
+			cubemap.markAsUsed();
+		}
+
+		ShadowCubemap& cubemap = mShadowCubemaps[mapInfo.textureIdx];
+
+		float nearDist = 0.05f;
+		float farDist = light->getAttenuationRadius();
+		float depthRange = farDist - nearDist;
+		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);
+
+		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;
+			mapInfo.shadowVPTransforms[i] = proj * view;
+
+			gShadowCubeMatricesDef.gFaceVPMatrices.set(shadowCubeMatricesBuffer, mapInfo.shadowVPTransforms[i], 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);
+			}
+		}
+
+		LightShadows& lightShadows = mRadialLightShadows[options.lightIdx];
+
+		mShadowInfos[lightShadows.startIdx + lightShadows.numShadows] = mapInfo;
+		lightShadows.numShadows++;
+	}
+
+	void ShadowRendering::calcShadowMapProperties(const RendererLight& light, RendererScene& scene, UINT32& size, 
+		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 = 0.0f;
+		maxFadePercent = 0.0f;
+		for (int i = 0; i < (int)sceneInfo.views.size(); ++i)
+		{
+			const RendererViewProperties& viewProps = sceneInfo.views[i]->getProperties();
+
+			float viewScaleX = viewProps.projTransform[0][0] * 0.5f;
+			float viewScaleY = viewProps.projTransform[1][1] * 0.5f;
+			float viewScale = std::max(viewScaleX, viewScaleY);
+
+			const Matrix4& viewVP = viewProps.viewProjTransform;
+
+			Vector4 lightClipPos = viewVP.multiply(Vector4(light.internal->getPosition(), 1.0f));
+			float radiusNDC = light.internal->getBounds().getRadius() / std::max(lightClipPos.w, 1.0f);
+
+			// Radius of light bounds in percent of the view surface
+			float radiusPercent = radiusNDC * viewScale;
+			maxRadiusPercent = std::max(maxRadiusPercent, radiusPercent);
+
+			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 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);
+	}
+
 	ConvexVolume ShadowRendering::getCSMSplitFrustum(const RendererView& view, const Vector3& lightDir, UINT32 cascade, 
 		UINT32 numCascades, Sphere& outBounds)
 	{
@@ -180,4 +1124,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;
+	}
+}}