Преглед изворни кода

WIP: Adding separate rendering path for when tiled deferred is not available

BearishSun пре 8 година
родитељ
комит
f16667cece
28 измењених фајлова са 1454 додато и 147 уклоњено
  1. BIN
      Data/Engine/Includes/ImageBasedLighting.bslinc.asset
  2. 77 0
      Data/Engine/ShaderDependencies.json
  3. BIN
      Data/Engine/Shaders/DeferredIBLFinalize.bsl.asset
  4. BIN
      Data/Engine/Shaders/DeferredIBLProbe.bsl.asset
  5. BIN
      Data/Engine/Shaders/DeferredIBLSetup.bsl.asset
  6. BIN
      Data/Engine/Shaders/DeferredIBLSky.bsl.asset
  7. BIN
      Data/Engine/Shaders/LightGridLLCreation.bsl.asset
  8. BIN
      Data/Engine/Shaders/TiledDeferredImageBasedLighting.bsl.asset
  9. BIN
      Data/Engine/Shaders/TiledDeferredLighting.bsl.asset
  10. BIN
      Data/Engine/Shaders/Transparent.bsl.asset
  11. 16 0
      Data/Raw/Engine/DataList.json
  12. 47 33
      Data/Raw/Engine/Includes/ImageBasedLighting.bslinc
  13. 103 0
      Data/Raw/Engine/Shaders/DeferredIBLFinalize.bsl
  14. 155 0
      Data/Raw/Engine/Shaders/DeferredIBLProbe.bsl
  15. 97 0
      Data/Raw/Engine/Shaders/DeferredIBLSetup.bsl
  16. 90 0
      Data/Raw/Engine/Shaders/DeferredIBLSky.bsl
  17. 22 1
      Source/BansheeEngine/Renderer/BsRendererUtility.cpp
  18. 7 3
      Source/BansheeEngine/Renderer/BsRendererUtility.h
  19. 27 27
      Source/BansheeUtility/Math/BsAABox.cpp
  20. 19 15
      Source/RenderBeast/BsImageBasedLighting.cpp
  21. 11 8
      Source/RenderBeast/BsImageBasedLighting.h
  22. 1 1
      Source/RenderBeast/BsObjectRendering.cpp
  23. 1 0
      Source/RenderBeast/BsRenderBeast.cpp
  24. 297 37
      Source/RenderBeast/BsRenderCompositor.cpp
  25. 21 4
      Source/RenderBeast/BsRenderCompositor.h
  26. 1 1
      Source/RenderBeast/BsShadowRendering.cpp
  27. 262 11
      Source/RenderBeast/BsStandardDeferredLighting.cpp
  28. 200 6
      Source/RenderBeast/BsStandardDeferredLighting.h

BIN
Data/Engine/Includes/ImageBasedLighting.bslinc.asset


+ 77 - 0
Data/Engine/ShaderDependencies.json

@@ -45,6 +45,83 @@
             "Path": "PerCameraData.bslinc"
             "Path": "PerCameraData.bslinc"
         }
         }
     ],
     ],
+    "DeferredIBLFinalize.bsl": [
+        {
+            "Path": "ImageBasedLighting.bslinc"
+        },
+        {
+            "Path": "ReflectionCubemapCommon.bslinc"
+        },
+        {
+            "Path": "GBufferInput.bslinc"
+        },
+        {
+            "Path": "PerCameraData.bslinc"
+        },
+        {
+            "Path": "PPBase.bslinc"
+        },
+        {
+            "Path": "SurfaceData.bslinc"
+        }
+    ],
+    "DeferredIBLProbe.bsl": [
+        {
+            "Path": "ImageBasedLighting.bslinc"
+        },
+        {
+            "Path": "ReflectionCubemapCommon.bslinc"
+        },
+        {
+            "Path": "GBufferInput.bslinc"
+        },
+        {
+            "Path": "PerCameraData.bslinc"
+        },
+        {
+            "Path": "SurfaceData.bslinc"
+        }
+    ],
+    "DeferredIBLSetup.bsl": [
+        {
+            "Path": "ImageBasedLighting.bslinc"
+        },
+        {
+            "Path": "ReflectionCubemapCommon.bslinc"
+        },
+        {
+            "Path": "GBufferInput.bslinc"
+        },
+        {
+            "Path": "PerCameraData.bslinc"
+        },
+        {
+            "Path": "PPBase.bslinc"
+        },
+        {
+            "Path": "SurfaceData.bslinc"
+        }
+    ],
+    "DeferredIBLSky.bsl": [
+        {
+            "Path": "ImageBasedLighting.bslinc"
+        },
+        {
+            "Path": "ReflectionCubemapCommon.bslinc"
+        },
+        {
+            "Path": "GBufferInput.bslinc"
+        },
+        {
+            "Path": "PerCameraData.bslinc"
+        },
+        {
+            "Path": "PPBase.bslinc"
+        },
+        {
+            "Path": "SurfaceData.bslinc"
+        }
+    ],
     "DeferredPointLight.bsl": [
     "DeferredPointLight.bsl": [
         {
         {
             "Path": "LightingCommon.bslinc"
             "Path": "LightingCommon.bslinc"

BIN
Data/Engine/Shaders/DeferredIBLFinalize.bsl.asset


BIN
Data/Engine/Shaders/DeferredIBLProbe.bsl.asset


BIN
Data/Engine/Shaders/DeferredIBLSetup.bsl.asset


BIN
Data/Engine/Shaders/DeferredIBLSky.bsl.asset


BIN
Data/Engine/Shaders/LightGridLLCreation.bsl.asset


BIN
Data/Engine/Shaders/TiledDeferredImageBasedLighting.bsl.asset


BIN
Data/Engine/Shaders/TiledDeferredLighting.bsl.asset


BIN
Data/Engine/Shaders/Transparent.bsl.asset


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

@@ -397,6 +397,22 @@
         {
         {
             "Path": "PPEyeAdaptationBasicSetup.bsl",
             "Path": "PPEyeAdaptationBasicSetup.bsl",
             "UUID": "86cda288-4fd5-4d0f-a5a2-4fd56d3b92ea"
             "UUID": "86cda288-4fd5-4d0f-a5a2-4fd56d3b92ea"
+        },
+        {
+            "Path": "DeferredIBLFinalize.bsl",
+            "UUID": "5973a208-4956-8ea4-85af-495668744158"
+        },
+        {
+            "Path": "DeferredIBLProbe.bsl",
+            "UUID": "76dd3c87-4cea-7066-9182-4ceade228448"
+        },
+        {
+            "Path": "DeferredIBLSetup.bsl",
+            "UUID": "115c9291-4260-606d-1098-426042ea073a"
+        },
+        {
+            "Path": "DeferredIBLSky.bsl",
+            "UUID": "e3c4e19e-4d65-dd20-bca1-4d65a0b9980b"
         }
         }
     ],
     ],
     "Skin": [
     "Skin": [

+ 47 - 33
Data/Raw/Engine/Includes/ImageBasedLighting.bslinc

@@ -42,7 +42,9 @@ mixin ImageBasedLighting
 		Texture2D gPreintegratedEnvBRDF;
 		Texture2D gPreintegratedEnvBRDF;
 		SamplerState gPreintegratedEnvBRDFSamp;
 		SamplerState gPreintegratedEnvBRDFSamp;
 		
 		
-		StructuredBuffer<ReflProbeData> gReflectionProbes;	
+		#ifndef STANDARD_DEFERRED
+		StructuredBuffer<ReflProbeData> gReflectionProbes;
+		#endif
 
 
 		#if USE_COMPUTE_INDICES
 		#if USE_COMPUTE_INDICES
 			groupshared uint gReflectionProbeIndices[MAX_PROBES];
 			groupshared uint gReflectionProbeIndices[MAX_PROBES];
@@ -144,6 +146,43 @@ mixin ImageBasedLighting
 			return lookupDir;
 			return lookupDir;
 		}
 		}
 		
 		
+		// rgb - probe color, a - probe contribution
+		float4 evaluateProbe(float3 worldPos, float3 dir, float mipLevel, ReflProbeData probeData)
+		{
+			float3 probeToPos = worldPos - probeData.position;
+			float distToProbe = length(probeToPos);
+			float normalizedDist = saturate(distToProbe / probeData.radius);
+						
+			if(distToProbe <= probeData.radius)
+			{
+				float3 correctedDir;
+				float contribution = 0;
+				if(probeData.type == 0) // Sphere
+				{
+					correctedDir = getLookupForSphereProxy(worldPos, dir, probeData.position, probeData.radius);
+					contribution = getSphereReflectionContribution(normalizedDist);
+				}
+				else if(probeData.type == 1) // Box
+				{
+					correctedDir = getLookupForBoxProxy(worldPos, dir, probeData.position, probeData.boxExtents, probeData.invBoxTransform, probeData.transitionDistance, contribution);
+				}
+				
+				float4 probeSample = gReflProbeCubemaps.SampleLevel(gReflProbeSamp, float4(correctedDir, probeData.cubemapIdx), mipLevel);
+				probeSample *= contribution;
+				
+				return float4(probeSample.rgb, (1.0f - contribution));
+			}
+			
+			return float4(0, 0, 0, 0);
+		}
+		
+		float getSpecularOcclusion(float NoV, float r, float ao)
+		{
+			float r2 = r * r;
+			return saturate(pow(NoV + ao, r2) - 1.0f + ao);
+		}			
+		
+		#ifndef STANDARD_DEFERRED
 		float3 gatherReflectionRadiance(float3 worldPos, float3 dir, float roughness, float alpha, float3 specularColor, uint probeOffset, uint numProbes)
 		float3 gatherReflectionRadiance(float3 worldPos, float3 dir, float roughness, float alpha, float3 specularColor, uint probeOffset, uint numProbes)
 		{
 		{
 			if(gUseReflectionMaps == 0)
 			if(gUseReflectionMaps == 0)
@@ -160,31 +199,10 @@ mixin ImageBasedLighting
 						
 						
 				uint probeIdx = gReflectionProbeIndices[probeOffset + i];
 				uint probeIdx = gReflectionProbeIndices[probeOffset + i];
 				ReflProbeData probeData = gReflectionProbes[probeIdx];
 				ReflProbeData probeData = gReflectionProbes[probeIdx];
+				float4 probeValue = evaluateProbe(worldPos, dir, mipLevel, probeData);
 				
 				
-				float3 probeToPos = worldPos - probeData.position;
-				float distToProbe = length(probeToPos);
-				float normalizedDist = saturate(distToProbe / probeData.radius);
-							
-				if(distToProbe <= probeData.radius)
-				{
-					float3 correctedDir;
-					float contribution = 0;
-					if(probeData.type == 0) // Sphere
-					{
-						correctedDir = getLookupForSphereProxy(worldPos, dir, probeData.position, probeData.radius);
-						contribution = getSphereReflectionContribution(normalizedDist);
-					}
-					else if(probeData.type == 1) // Box
-					{
-						correctedDir = getLookupForBoxProxy(worldPos, dir, probeData.position, probeData.boxExtents, probeData.invBoxTransform, probeData.transitionDistance, contribution);
-					}
-					
-					float4 probeSample = gReflProbeCubemaps.SampleLevel(gReflProbeSamp, float4(correctedDir, probeData.cubemapIdx), mipLevel);
-					probeSample *= contribution;
-					
-					output += probeSample.rgb * alpha; 
-					alpha *= (1.0f - contribution);
-				}
+				output += probeValue.rgb * alpha; 
+				alpha *= probeValue.w;
 			}
 			}
 				
 				
 			if(gSkyCubemapAvailable > 0)
 			if(gSkyCubemapAvailable > 0)
@@ -198,13 +216,8 @@ mixin ImageBasedLighting
 			return output;
 			return output;
 		}
 		}
 		
 		
-		float getSpecularOcclusion(float NoV, float r, float ao)
-		{
-			float r2 = r * r;
-			return saturate(pow(NoV + ao, r2) - 1.0f + ao);
-		}		
-		
-		float3 getImageBasedSpecular(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, float ao, float4 ssr, uint probeOffset, uint numProbes)
+		float3 getImageBasedSpecular(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, float ao, float4 ssr, 
+			uint probeOffset, uint numProbes)
 		{
 		{
 			// See C++ code for generation of gPreintegratedEnvBRDF to see why this code works as is
 			// See C++ code for generation of gPreintegratedEnvBRDF to see why this code works as is
 			float3 N = surfaceData.worldNormal.xyz;
 			float3 N = surfaceData.worldNormal.xyz;
@@ -228,6 +241,7 @@ mixin ImageBasedLighting
 			
 			
 			float2 envBRDF = gPreintegratedEnvBRDF.SampleLevel(gPreintegratedEnvBRDFSamp, float2(NoV, surfaceData.roughness), 0).rg;
 			float2 envBRDF = gPreintegratedEnvBRDF.SampleLevel(gPreintegratedEnvBRDFSamp, float2(NoV, surfaceData.roughness), 0).rg;
 			return radiance * (specularColor * envBRDF.x + envBRDF.y);
 			return radiance * (specularColor * envBRDF.x + envBRDF.y);
-		}		
+		}
+		#endif		
 	};
 	};
 };
 };

+ 103 - 0
Data/Raw/Engine/Shaders/DeferredIBLFinalize.bsl

@@ -0,0 +1,103 @@
+#if MSAA
+#define MSAA_COUNT 2
+#else
+#define MSAA_COUNT 1
+#endif
+
+#include "$ENGINE$\GBufferInput.bslinc"
+#include "$ENGINE$\PPBase.bslinc"
+#include "$ENGINE$\PerCameraData.bslinc"
+#define STANDARD_DEFERRED
+#include "$ENGINE$\ImageBasedLighting.bslinc"
+
+technique DeferredIBLFinalize
+{
+	mixin PPBase;
+	mixin GBufferInput;
+	mixin PerCameraData;
+	mixin ImageBasedLighting;
+
+	blend
+	{
+		target
+		{
+			enabled = true;
+			color = { one, one, add };		
+		};
+	};
+	
+	variations
+	{
+		MSAA = { true, false };
+		MSAA_RESOLVE_0TH = { true, false };
+	};		
+	
+	#if MSAA
+	stencil
+	{
+		enabled = true;
+		readmask = 0x80;
+		
+		#if INSIDE_GEOMETRY
+		back = { keep, keep, keep, eq };
+		#else
+		front = { keep, keep, keep, eq };
+		#endif
+		
+		#if MSAA_RESOLVE_0TH
+		reference = 0;
+		#else
+		reference = 0x80;
+		#endif
+	};
+	#endif
+	
+	code
+	{
+		#if MSAA_COUNT > 1
+			Texture2DMS<float4>	gIBLRadianceTex;
+		#else
+			Texture2D gIBLRadianceTex;
+		#endif
+	
+		float4 fsmain(VStoFS input, float4 pixelPos : SV_Position
+			#if MSAA_COUNT > 1 && !MSAA_RESOLVE_0TH
+			, uint sampleIdx : SV_SampleIndex
+			#endif
+			) : SV_Target0
+		{		
+			#if MSAA_COUNT > 1
+				#if MSAA_RESOLVE_0TH
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, 0);
+					float3 radiance = gIBLRadianceTex.Load((uint2)pixelPos.xy, 0).rgb;
+				#else
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, sampleIdx);
+					float3 radiance = gIBLRadianceTex.Load((uint2)pixelPos.xy, sampleIdx).rgb;
+				#endif
+			#else
+				SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy);
+				float3 radiance = gIBLRadianceTex.Load(int3((int2)pixelPos.xy, 0)).rgb;
+			#endif	
+
+			if(surfaceData.worldNormal.w > 0.0f)
+			{
+				// See C++ code for generation of gPreintegratedEnvBRDF to see why this code works as is
+				float3 worldPosition = NDCToWorld(input.screenPos, surfaceData.depth);
+				
+				float3 V = normalize(gViewOrigin - worldPosition);
+				float3 N = surfaceData.worldNormal.xyz;
+				float NoV = saturate(dot(N, V));
+				
+				// Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
+				// For more customizability allow the user to provide separate albedo/specular colors for both types.
+				float3 specularColor = lerp(float3(0.04f, 0.04f, 0.04f), surfaceData.albedo.rgb, surfaceData.metalness);
+								
+				float2 envBRDF = gPreintegratedEnvBRDF.SampleLevel(gPreintegratedEnvBRDFSamp, float2(NoV, surfaceData.roughness), 0).rg;
+				
+				return float4(radiance * specularColor * envBRDF.x + envBRDF.y, 0.0f);
+			}
+			else
+				return float4(0.0f, 0.0f, 0.0f, 0.0f);
+		}
+	};
+};

+ 155 - 0
Data/Raw/Engine/Shaders/DeferredIBLProbe.bsl

@@ -0,0 +1,155 @@
+#if MSAA
+#define MSAA_COUNT 2
+#else
+#define MSAA_COUNT 1
+#endif
+
+#include "$ENGINE$\GBufferInput.bslinc"
+#include "$ENGINE$\PerCameraData.bslinc"
+#define STANDARD_DEFERRED
+#include "$ENGINE$\ImageBasedLighting.bslinc"
+
+technique DeferredIBLProbe
+{
+	mixin GBufferInput;
+	mixin PerCameraData;
+	mixin ImageBasedLighting;
+
+	depth
+	{
+		write = false;
+		
+		#ifdef INSIDE_GEOMETRY
+		read = false;
+		#else
+		read = true;
+		#endif
+	};
+	
+	blend
+	{
+		target
+		{
+			enabled = true;
+			color = { dstA, one, add };
+			alpha = { dstA, zero, add };			
+		};
+	};
+	
+	raster
+	{
+		#ifdef INSIDE_GEOMETRY
+		cull = cw;
+		#else
+		cull = ccw;
+		#endif
+	};
+
+	variations
+	{
+		MSAA = { true, false };
+		INSIDE_GEOMETRY = { true, false };
+		MSAA_RESOLVE_0TH = { true, false };
+	};		
+	
+	#if MSAA
+	stencil
+	{
+		enabled = true;
+		readmask = 0x80;
+		
+		#if INSIDE_GEOMETRY
+		back = { keep, keep, keep, eq };
+		#else
+		front = { keep, keep, keep, eq };
+		#endif
+		
+		#if MSAA_RESOLVE_0TH
+		reference = 0;
+		#else
+		reference = 0x80;
+		#endif
+	};
+	#endif
+	
+	code
+	{
+		struct VStoFS
+		{
+			float4 position : SV_POSITION;
+			float4 screenPos : TEXCOORD0;
+		};
+
+		struct VertexInput
+		{
+			float3 position : POSITION;
+			uint vertexIdx : SV_VERTEXID;
+		};
+		
+		cbuffer PerProbe
+		{
+			float3 gPosition;
+			float3 gExtents;
+			float gTransitionDistance;
+			float4x4 gInvBoxTransform;
+			uint gCubemapIdx;
+			uint gType; // 0 - Sphere, 1 - Box
+		}			
+		
+		VStoFS vsmain(VertexInput input)
+		{
+			VStoFS output;
+			
+			// Position & scale geometry
+			float3 worldPosition = input.position * gExtents + gPosition;
+			
+			output.screenPos = mul(gMatViewProj, float4(worldPosition, 1));
+			output.position = output.screenPos;
+			
+			return output;
+		}			
+
+		float4 fsmain(VStoFS input, float4 pixelPos : SV_Position
+			#if MSAA_COUNT > 1 && !MSAA_RESOLVE_0TH
+			, uint sampleIdx : SV_SampleIndex
+			#endif
+			) : SV_Target0
+		{
+			#if MSAA_COUNT > 1
+				#if MSAA_RESOLVE_0TH
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, 0);
+				#else
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, sampleIdx);
+				#endif
+			#else
+				SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy);
+			#endif			
+		
+			if(surfaceData.worldNormal.w > 0.0f)
+			{
+				ReflProbeData probeData;
+				probeData.position = gPosition;
+				probeData.radius = gExtents.x;
+				probeData.boxExtents = gExtents;
+				probeData.transitionDistance = gTransitionDistance;
+				probeData.invBoxTransform = gInvBoxTransform;
+				probeData.cubemapIdx = gCubemapIdx;
+				probeData.type = gType;
+				probeData.padding = float2(0, 0);
+				
+				float2 ndcPos = input.screenPos.xy / input.screenPos.w;
+				float3 worldPosition = NDCToWorld(ndcPos, surfaceData.depth);			
+
+				float3 V = normalize(gViewOrigin - worldPosition);
+				float3 N = surfaceData.worldNormal.xyz;
+				float3 R = 2 * dot(V, N) * N - V;				
+			
+				float mipLevel = mapRoughnessToMipLevel(surfaceData.roughness, gReflCubemapNumMips);			
+				
+				return evaluateProbe(worldPosition, R, mipLevel, probeData);
+			}
+			else
+				return float4(0.0f, 0.0f, 0.0f, 0.0f);
+		}
+	};
+};

+ 97 - 0
Data/Raw/Engine/Shaders/DeferredIBLSetup.bsl

@@ -0,0 +1,97 @@
+#if MSAA
+#define MSAA_COUNT 2
+#else
+#define MSAA_COUNT 1
+#endif
+
+#include "$ENGINE$\GBufferInput.bslinc"
+#include "$ENGINE$\PPBase.bslinc"
+#include "$ENGINE$\PerCameraData.bslinc"
+#define STANDARD_DEFERRED
+#include "$ENGINE$\ImageBasedLighting.bslinc"
+
+technique DeferredIBLSetup
+{
+	mixin PPBase;
+	mixin GBufferInput;
+	mixin PerCameraData;
+	mixin ImageBasedLighting;
+	
+	variations
+	{
+		MSAA = { true, false };
+		MSAA_RESOLVE_0TH = { true, false };
+	};		
+	
+	#if MSAA
+	stencil
+	{
+		enabled = true;
+		readmask = 0x80;
+		
+		#if INSIDE_GEOMETRY
+		back = { keep, keep, keep, eq };
+		#else
+		front = { keep, keep, keep, eq };
+		#endif
+		
+		#if MSAA_RESOLVE_0TH
+		reference = 0;
+		#else
+		reference = 0x80;
+		#endif
+	};
+	#endif
+	
+	code
+	{
+		float4 fsmain(VStoFS input, float4 pixelPos : SV_Position
+			#if MSAA_COUNT > 1 && !MSAA_RESOLVE_0TH
+			, uint sampleIdx : SV_SampleIndex
+			#endif
+			) : SV_Target0
+		{		
+			#if MSAA_COUNT > 1
+				#if MSAA_RESOLVE_0TH
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, 0);
+				#else
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, sampleIdx);
+				#endif
+			#else
+				SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy);
+			#endif	
+
+			if(surfaceData.worldNormal.w > 0.0f)
+			{
+				float3 worldPosition = NDCToWorld(input.screenPos, surfaceData.depth);
+
+				float ao = gAmbientOcclusionTex.SampleLevel(gAmbientOcclusionSamp, input.uv0, 0.0f).r;
+				float4 ssr = gSSRTex.SampleLevel(gSSRSamp, input.uv0, 0.0f);
+				
+				float3 V = normalize(gViewOrigin - worldPosition);
+				float3 N = surfaceData.worldNormal.xyz;
+				float NoV = saturate(dot(N, V));
+				
+				float3 radiance = ssr.rgb;
+				float alpha = 1.0f - ssr.a; // Determines how much to blend in reflection probes & skybox
+				
+				// Generate an approximate spec. occlusion value from AO. This doesn't need to be applied to SSR since it accounts
+				// for occlusion by tracing rays.
+				float specOcclusion = getSpecularOcclusion(NoV, surfaceData.roughness * surfaceData.roughness, ao);
+				alpha *= specOcclusion;
+				
+				if(gUseReflectionMaps == 0)
+				{
+					// Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
+					// For more customizability allow the user to provide separate albedo/specular colors for both types.
+					float3 specularColor = lerp(float3(0.04f, 0.04f, 0.04f), surfaceData.albedo.rgb, surfaceData.metalness);								
+					radiance += specularColor;
+				}
+
+				return float4(radiance, alpha);
+			}
+			else
+				return float4(0.0f, 0.0f, 0.0f, 0.0f);
+		}
+	};
+};

+ 90 - 0
Data/Raw/Engine/Shaders/DeferredIBLSky.bsl

@@ -0,0 +1,90 @@
+#if MSAA
+#define MSAA_COUNT 2
+#else
+#define MSAA_COUNT 1
+#endif
+
+#include "$ENGINE$\GBufferInput.bslinc"
+#include "$ENGINE$\PPBase.bslinc"
+#include "$ENGINE$\PerCameraData.bslinc"
+#define STANDARD_DEFERRED
+#include "$ENGINE$\ImageBasedLighting.bslinc"
+
+technique DeferredIBLFinalize
+{
+	mixin PPBase;
+	mixin GBufferInput;
+	mixin PerCameraData;
+	mixin ImageBasedLighting;
+
+	blend
+	{
+		target
+		{
+			enabled = true;
+			color = { dstA, one, add };		
+		};
+	};
+	
+	variations
+	{
+		MSAA = { true, false };
+		MSAA_RESOLVE_0TH = { true, false };
+	};		
+	
+	#if MSAA
+	stencil
+	{
+		enabled = true;
+		readmask = 0x80;
+		
+		#if INSIDE_GEOMETRY
+		back = { keep, keep, keep, eq };
+		#else
+		front = { keep, keep, keep, eq };
+		#endif
+		
+		#if MSAA_RESOLVE_0TH
+		reference = 0;
+		#else
+		reference = 0x80;
+		#endif
+	};
+	#endif
+	
+	code
+	{
+		float4 fsmain(VStoFS input, float4 pixelPos : SV_Position
+			#if MSAA_COUNT > 1 && !MSAA_RESOLVE_0TH
+			, uint sampleIdx : SV_SampleIndex
+			#endif
+			) : SV_Target0
+		{		
+			#if MSAA_COUNT > 1
+				#if MSAA_RESOLVE_0TH
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, 0);
+				#else
+					SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy, sampleIdx);
+				#endif
+			#else
+				SurfaceData surfaceData = getGBufferData((uint2)pixelPos.xy);
+			#endif	
+
+			if(surfaceData.worldNormal.w > 0.0f && gSkyCubemapAvailable > 0 && gUseReflectionMaps != 0)
+			{
+				float3 worldPosition = NDCToWorld(input.screenPos, surfaceData.depth);
+				
+				float3 V = normalize(gViewOrigin - worldPosition);
+				float3 N = surfaceData.worldNormal.xyz;
+				float3 R = 2 * dot(V, N) * N - V;				
+			
+				float skyMipLevel = mapRoughnessToMipLevel(surfaceData.roughness, gSkyCubemapNumMips);
+				float4 skySample = gSkyReflectionTex.SampleLevel(gSkyReflectionSamp, R, skyMipLevel) * gSkyBrightness;
+				
+				return float4(skySample.rgb, 1.0f); 
+			}
+			else
+				return float4(0.0f, 0.0f, 0.0f, 0.0f);
+		}
+	};
+};

+ 22 - 1
Source/BansheeEngine/Renderer/BsRendererUtility.cpp

@@ -12,6 +12,7 @@
 #include "Renderer/BsLight.h"
 #include "Renderer/BsLight.h"
 #include "Material/BsShader.h"
 #include "Material/BsShader.h"
 #include "Renderer/BsIBLUtility.h"
 #include "Renderer/BsIBLUtility.h"
+#include "Math/BsAABox.h"
 
 
 namespace bs { namespace ct
 namespace bs { namespace ct
 {
 {
@@ -42,7 +43,27 @@ namespace bs { namespace ct
 			ShapeMeshes3D::solidSphere(localSphere, positionData, nullptr, nullptr, 0,
 			ShapeMeshes3D::solidSphere(localSphere, positionData, nullptr, nullptr, 0,
 				vertexDesc->getVertexStride(), indexData, 0, 3);
 				vertexDesc->getVertexStride(), indexData, 0, 3);
 
 
-			mPointLightStencilMesh = Mesh::create(meshData);
+			mUnitSphereStencilMesh = Mesh::create(meshData);
+		}
+
+		{
+			SPtr<VertexDataDesc> vertexDesc = bs_shared_ptr_new<VertexDataDesc>();
+			vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
+
+			UINT32 numVertices = 0;
+			UINT32 numIndices = 0;
+
+			ShapeMeshes3D::getNumElementsAABox(numVertices, numIndices);
+			SPtr<MeshData> meshData = bs_shared_ptr_new<MeshData>(numVertices, numIndices, vertexDesc);
+
+			UINT32* indexData = meshData->getIndices32();
+			UINT8* positionData = meshData->getElementData(VES_POSITION);
+
+			AABox localBox(-Vector3::ONE, Vector3::ONE);
+			ShapeMeshes3D::solidAABox(localBox, positionData, nullptr, nullptr, 0,
+				vertexDesc->getVertexStride(), indexData, 0);
+
+			mUnitBoxStencilMesh = Mesh::create(meshData);
 		}
 		}
 
 
 		{
 		{

+ 7 - 3
Source/BansheeEngine/Renderer/BsRendererUtility.h

@@ -205,8 +205,11 @@ namespace bs { namespace ct
 		 */
 		 */
 		void clear(UINT32 value);
 		void clear(UINT32 value);
 
 
-		/** Returns a stencil mesh used for a radial light (a unit sphere). */
-		SPtr<Mesh> getRadialLightStencil() const { return mPointLightStencilMesh; }
+		/** Returns a unit sphere stencil mesh. */
+		SPtr<Mesh> getSphereStencil() const { return mUnitSphereStencilMesh; }
+
+		/** Returns a unit axis aligned box stencil mesh. */
+		SPtr<Mesh> getBoxStencil() const { return mUnitBoxStencilMesh; }
 
 
 		/** 
 		/** 
 		 * Returns a stencil mesh used for a spot light. Actual vertex positions need to be computed in shader as this
 		 * Returns a stencil mesh used for a spot light. Actual vertex positions need to be computed in shader as this
@@ -219,7 +222,8 @@ namespace bs { namespace ct
 
 
 	private:
 	private:
 		SPtr<Mesh> mFullScreenQuadMesh;
 		SPtr<Mesh> mFullScreenQuadMesh;
-		SPtr<Mesh> mPointLightStencilMesh;
+		SPtr<Mesh> mUnitSphereStencilMesh;
+		SPtr<Mesh> mUnitBoxStencilMesh;
 		SPtr<Mesh> mSpotLightStencilMesh;
 		SPtr<Mesh> mSpotLightStencilMesh;
 		SPtr<Mesh> mSkyBoxMesh;
 		SPtr<Mesh> mSkyBoxMesh;
 	};
 	};

+ 27 - 27
Source/BansheeUtility/Math/BsAABox.cpp

@@ -468,32 +468,32 @@ namespace bs
 		return diff.x * diff.y * diff.z;
 		return diff.x * diff.y * diff.z;
 	}
 	}
 
 
-    bool AABox::contains(const Vector3& v) const
-    {
-        return mMinimum.x <= v.x && v.x <= mMaximum.x &&
-                mMinimum.y <= v.y && v.y <= mMaximum.y &&
-                mMinimum.z <= v.z && v.z <= mMaximum.z;
-    }
-
-    bool AABox::contains(const AABox& other) const
-    {
-        return this->mMinimum.x <= other.mMinimum.x &&
-                this->mMinimum.y <= other.mMinimum.y &&
-                this->mMinimum.z <= other.mMinimum.z &&
-                other.mMaximum.x <= this->mMaximum.x &&
-                other.mMaximum.y <= this->mMaximum.y &&
-                other.mMaximum.z <= this->mMaximum.z;
-    }
-
-    bool AABox::operator== (const AABox& rhs) const
-    {
-        return this->mMinimum == rhs.mMinimum &&
-                this->mMaximum == rhs.mMaximum;
-    }
-
-    bool AABox::operator!= (const AABox& rhs) const
-    {
-        return !(*this == rhs);
-    }
+	bool AABox::contains(const Vector3& v) const
+	{
+		return mMinimum.x <= v.x && v.x <= mMaximum.x &&
+				mMinimum.y <= v.y && v.y <= mMaximum.y &&
+				mMinimum.z <= v.z && v.z <= mMaximum.z;
+	}
+
+	bool AABox::contains(const AABox& other) const
+	{
+		return this->mMinimum.x <= other.mMinimum.x &&
+				this->mMinimum.y <= other.mMinimum.y &&
+				this->mMinimum.z <= other.mMinimum.z &&
+				other.mMaximum.x <= this->mMaximum.x &&
+				other.mMaximum.y <= this->mMaximum.y &&
+				other.mMaximum.z <= this->mMaximum.z;
+	}
+
+	bool AABox::operator== (const AABox& rhs) const
+	{
+		return this->mMinimum == rhs.mMinimum &&
+				this->mMaximum == rhs.mMaximum;
+	}
+
+	bool AABox::operator!= (const AABox& rhs) const
+	{
+		return !(*this == rhs);
+	}
 }
 }
 
 

+ 19 - 15
Source/RenderBeast/BsImageBasedLighting.cpp

@@ -24,6 +24,8 @@ namespace bs { namespace ct
 
 
 	void VisibleReflProbeData::update(const SceneInfo& sceneInfo, const RendererViewGroup& viewGroup)
 	void VisibleReflProbeData::update(const SceneInfo& sceneInfo, const RendererViewGroup& viewGroup)
 	{
 	{
+		mReflProbeData.clear();
+
 		const VisibilityInfo& visibility = viewGroup.getVisibilityInfo();
 		const VisibilityInfo& visibility = viewGroup.getVisibilityInfo();
 
 
 		// Generate refl. probe data for the visible ones
 		// Generate refl. probe data for the visible ones
@@ -33,8 +35,8 @@ namespace bs { namespace ct
 			if (!visibility.reflProbes[i])
 			if (!visibility.reflProbes[i])
 				continue;
 				continue;
 
 
-			mReflProbeDataTemp.push_back(ReflProbeData());
-			sceneInfo.reflProbes[i].getParameters(mReflProbeDataTemp.back());
+			mReflProbeData.push_back(ReflProbeData());
+			sceneInfo.reflProbes[i].getParameters(mReflProbeData.back());
 		}
 		}
 
 
 		// Sort probes so bigger ones get accessed first, this way we overlay smaller ones on top of biggers ones when
 		// Sort probes so bigger ones get accessed first, this way we overlay smaller ones on top of biggers ones when
@@ -44,9 +46,9 @@ namespace bs { namespace ct
 			return rhs.radius < lhs.radius;
 			return rhs.radius < lhs.radius;
 		};
 		};
 
 
-		std::sort(mReflProbeDataTemp.begin(), mReflProbeDataTemp.end(), sorter);
+		std::sort(mReflProbeData.begin(), mReflProbeData.end(), sorter);
 
 
-		mNumProbes = (UINT32)mReflProbeDataTemp.size();
+		mNumProbes = (UINT32)mReflProbeData.size();
 
 
 		// Move refl. probe data into a GPU buffer
 		// Move refl. probe data into a GPU buffer
 		UINT32 size = mNumProbes * sizeof(ReflProbeData);
 		UINT32 size = mNumProbes * sizeof(ReflProbeData);
@@ -72,9 +74,7 @@ namespace bs { namespace ct
 		}
 		}
 
 
 		if (size > 0)
 		if (size > 0)
-			mProbeBuffer->writeData(0, size, mReflProbeDataTemp.data(), BWT_DISCARD);
-
-		mReflProbeDataTemp.clear();
+			mProbeBuffer->writeData(0, size, mReflProbeData.data(), BWT_DISCARD);
 	}
 	}
 
 
 	RendererReflectionProbe::RendererReflectionProbe(ReflectionProbe* probe)
 	RendererReflectionProbe::RendererReflectionProbe(ReflectionProbe* probe)
@@ -105,7 +105,7 @@ namespace bs { namespace ct
 	}
 	}
 
 
 	void ImageBasedLightingParams::populate(const SPtr<GpuParams>& params, GpuProgramType programType, bool optional, 
 	void ImageBasedLightingParams::populate(const SPtr<GpuParams>& params, GpuProgramType programType, bool optional, 
-		bool gridIndices)
+		bool gridIndices, bool probeArray)
 	{
 	{
 		// Sky
 		// Sky
 		if (!optional || params->hasTexture(programType, "gSkyReflectionTex"))
 		if (!optional || params->hasTexture(programType, "gSkyReflectionTex"))
@@ -115,10 +115,14 @@ namespace bs { namespace ct
 		if (!optional || params->hasTexture(programType, "gReflProbeCubemaps"))
 		if (!optional || params->hasTexture(programType, "gReflProbeCubemaps"))
 		{
 		{
 			params->getTextureParam(programType, "gReflProbeCubemaps", reflectionProbeCubemapsTexParam);
 			params->getTextureParam(programType, "gReflProbeCubemaps", reflectionProbeCubemapsTexParam);
-			params->getBufferParam(programType, "gReflectionProbes", reflectionProbesParam);
-			params->getTextureParam(programType, "gPreintegratedEnvBRDF", preintegratedEnvBRDFParam);
+
+			if(probeArray)
+				params->getBufferParam(programType, "gReflectionProbes", reflectionProbesParam);
 		}
 		}
 
 
+		if (!optional || params->hasTexture(programType, "gPreintegratedEnvBRDF"))
+			params->getTextureParam(programType, "gPreintegratedEnvBRDF", preintegratedEnvBRDFParam);
+
 		// AO
 		// AO
 		if (params->hasTexture(programType, "gAmbientOcclusionTex"))
 		if (params->hasTexture(programType, "gAmbientOcclusionTex"))
 		{
 		{
@@ -160,8 +164,8 @@ namespace bs { namespace ct
 		buffer = gReflProbeParamsParamDef.createBuffer();
 		buffer = gReflProbeParamsParamDef.createBuffer();
 	}
 	}
 
 
-	void ReflProbeParamBuffer::populate(const Skybox* sky, const VisibleReflProbeData& probeData, 
-		const SPtr<Texture>& reflectionCubemaps, bool capturingReflections)
+	void ReflProbeParamBuffer::populate(const Skybox* sky, UINT32 numProbes, const SPtr<Texture>& reflectionCubemaps, 
+		bool capturingReflections)
 	{
 	{
 		float brightness = 1.0f;
 		float brightness = 1.0f;
 		UINT32 skyReflectionsAvailable = 0;
 		UINT32 skyReflectionsAvailable = 0;
@@ -181,7 +185,7 @@ namespace bs { namespace ct
 
 
 		gReflProbeParamsParamDef.gSkyCubemapNumMips.set(buffer, numSkyMips);
 		gReflProbeParamsParamDef.gSkyCubemapNumMips.set(buffer, numSkyMips);
 		gReflProbeParamsParamDef.gSkyCubemapAvailable.set(buffer, skyReflectionsAvailable);
 		gReflProbeParamsParamDef.gSkyCubemapAvailable.set(buffer, skyReflectionsAvailable);
-		gReflProbeParamsParamDef.gNumProbes.set(buffer, probeData.getNumProbes());
+		gReflProbeParamsParamDef.gNumProbes.set(buffer, numProbes);
 
 
 		UINT32 numReflProbeMips = 0;
 		UINT32 numReflProbeMips = 0;
 		if (reflectionCubemaps != nullptr)
 		if (reflectionCubemaps != nullptr)
@@ -218,7 +222,7 @@ namespace bs { namespace ct
 		mParamBuffer = gTiledImageBasedLightingParamDef.createBuffer();
 		mParamBuffer = gTiledImageBasedLightingParamDef.createBuffer();
 		mParams->setParamBlockBuffer("Params", mParamBuffer);
 		mParams->setParamBlockBuffer("Params", mParamBuffer);
 
 
-		mImageBasedParams.populate(mParams, GPT_COMPUTE_PROGRAM, false, false);
+		mImageBasedParams.populate(mParams, GPT_COMPUTE_PROGRAM, false, false, true);
 
 
 		SAMPLER_STATE_DESC desc;
 		SAMPLER_STATE_DESC desc;
 		desc.minFilter = FO_POINT;
 		desc.minFilter = FO_POINT;
@@ -252,7 +256,7 @@ namespace bs { namespace ct
 		framebufferSize[1] = height;
 		framebufferSize[1] = height;
 		gTiledImageBasedLightingParamDef.gFramebufferSize.set(mParamBuffer, framebufferSize);
 		gTiledImageBasedLightingParamDef.gFramebufferSize.set(mParamBuffer, framebufferSize);
 
 
-		mReflProbeParamBuffer.populate(sceneInfo.skybox, probeData, sceneInfo.reflProbeCubemapsTex, 
+		mReflProbeParamBuffer.populate(sceneInfo.skybox, probeData.getNumProbes(), sceneInfo.reflProbeCubemapsTex, 
 			viewProps.renderingReflections);
 			viewProps.renderingReflections);
 
 
 		mParamBuffer->flushToGPU();
 		mParamBuffer->flushToGPU();

+ 11 - 8
Source/RenderBeast/BsImageBasedLighting.h

@@ -27,7 +27,7 @@ namespace bs { namespace ct
 		float transitionDistance;
 		float transitionDistance;
 		Matrix4 invBoxTransform;
 		Matrix4 invBoxTransform;
 		UINT32 cubemapIdx;
 		UINT32 cubemapIdx;
-		UINT32 type;
+		UINT32 type; // 0 - Sphere, 1 - Box
 		Vector2 padding;
 		Vector2 padding;
 	};
 	};
 
 
@@ -49,12 +49,13 @@ namespace bs { namespace ct
 		/** Returns the number of reflection probes in the probe buffer. */
 		/** Returns the number of reflection probes in the probe buffer. */
 		UINT32 getNumProbes() const { return mNumProbes; }
 		UINT32 getNumProbes() const { return mNumProbes; }
 
 
+		/** Returns information about a probe at the specified index. */
+		const ReflProbeData& getProbeData(UINT32 idx) const { return mReflProbeData[idx]; }
+
 	private:
 	private:
+		Vector<ReflProbeData> mReflProbeData;
 		SPtr<GpuBuffer> mProbeBuffer;
 		SPtr<GpuBuffer> mProbeBuffer;
 		UINT32 mNumProbes;
 		UINT32 mNumProbes;
-
-		// Helper to avoid memory allocations
-		Vector<ReflProbeData> mReflProbeDataTemp;
 	};
 	};
 
 
 	BS_PARAM_BLOCK_BEGIN(ReflProbeParamsParamDef)
 	BS_PARAM_BLOCK_BEGIN(ReflProbeParamsParamDef)
@@ -99,8 +100,10 @@ namespace bs { namespace ct
 		 * @param[in]	programType	Type of the GPU program to look up the parameters for.
 		 * @param[in]	programType	Type of the GPU program to look up the parameters for.
 		 * @param[in]	optional	If true no warnings will be thrown if some or all of the parameters will be found.
 		 * @param[in]	optional	If true no warnings will be thrown if some or all of the parameters will be found.
 		 * @param[in]	gridIndices	Set to true if grid indices (used by light grid) parameter is required.
 		 * @param[in]	gridIndices	Set to true if grid indices (used by light grid) parameter is required.
+		 * @param[in]	probeArray	True if the refl. probe data is to be provided in a structured buffer.
 		 */
 		 */
-		void populate(const SPtr<GpuParams>& params, GpuProgramType programType, bool optional, bool gridIndices);
+		void populate(const SPtr<GpuParams>& params, GpuProgramType programType, bool optional, bool gridIndices, 
+			bool probeArray);
 
 
 		GpuParamTexture skyReflectionsTexParam;
 		GpuParamTexture skyReflectionsTexParam;
 		GpuParamTexture ambientOcclusionTexParam;
 		GpuParamTexture ambientOcclusionTexParam;
@@ -122,9 +125,9 @@ namespace bs { namespace ct
 	{
 	{
 		ReflProbeParamBuffer();
 		ReflProbeParamBuffer();
 
 
-		/** Updates the parameter buffer contents with require refl. probe data. */
-		void populate(const Skybox* sky, const VisibleReflProbeData& probeData, 
-			const SPtr<Texture>& reflectionCubemaps, bool capturingReflections);
+		/** Updates the parameter buffer contents with required refl. probe data. */
+		void populate(const Skybox* sky, UINT32 numProbes, const SPtr<Texture>& reflectionCubemaps, 
+			bool capturingReflections);
 
 
 		SPtr<GpuParamBlockBuffer> buffer;
 		SPtr<GpuParamBlockBuffer> buffer;
 	};
 	};

+ 1 - 1
Source/RenderBeast/BsObjectRendering.cpp

@@ -62,7 +62,7 @@ namespace bs { namespace ct
 		if (gpuParams->hasBuffer(GPT_FRAGMENT_PROGRAM, "gGridProbeOffsetsAndSize"))
 		if (gpuParams->hasBuffer(GPT_FRAGMENT_PROGRAM, "gGridProbeOffsetsAndSize"))
 			gpuParams->getBufferParam(GPT_FRAGMENT_PROGRAM, "gGridProbeOffsetsAndSize", element.gridProbeOffsetsAndSizeParam);
 			gpuParams->getBufferParam(GPT_FRAGMENT_PROGRAM, "gGridProbeOffsetsAndSize", element.gridProbeOffsetsAndSizeParam);
 
 
-		element.imageBasedParams.populate(gpuParams, GPT_FRAGMENT_PROGRAM, true, true);
+		element.imageBasedParams.populate(gpuParams, GPT_FRAGMENT_PROGRAM, true, true, true);
 
 
 		if (gpuParams->hasBuffer(GPT_VERTEX_PROGRAM, "boneMatrices"))
 		if (gpuParams->hasBuffer(GPT_VERTEX_PROGRAM, "boneMatrices"))
 			gpuParams->setBuffer(GPT_VERTEX_PROGRAM, "boneMatrices", element.boneMatrixBuffer);
 			gpuParams->setBuffer(GPT_VERTEX_PROGRAM, "boneMatrices", element.boneMatrixBuffer);

+ 1 - 0
Source/RenderBeast/BsRenderBeast.cpp

@@ -93,6 +93,7 @@ namespace bs { namespace ct
 		RenderCompositor::registerNodeType<RCNodeLightAccumulation>();
 		RenderCompositor::registerNodeType<RCNodeLightAccumulation>();
 		RenderCompositor::registerNodeType<RCNodeSceneColor>();
 		RenderCompositor::registerNodeType<RCNodeSceneColor>();
 		RenderCompositor::registerNodeType<RCNodeStandardDeferredLighting>();
 		RenderCompositor::registerNodeType<RCNodeStandardDeferredLighting>();
+		RenderCompositor::registerNodeType<RCNodeStandardDeferredIBL>();
 		RenderCompositor::registerNodeType<RCNodeTiledDeferredLighting>();
 		RenderCompositor::registerNodeType<RCNodeTiledDeferredLighting>();
 		RenderCompositor::registerNodeType<RCNodeTiledDeferredIBL>();
 		RenderCompositor::registerNodeType<RCNodeTiledDeferredIBL>();
 		RenderCompositor::registerNodeType<RCNodeUnflattenLightAccum>();
 		RenderCompositor::registerNodeType<RCNodeUnflattenLightAccum>();

+ 297 - 37
Source/RenderBeast/BsRenderCompositor.cpp

@@ -358,14 +358,20 @@ namespace bs { namespace ct
 		UINT32 height = viewProps.viewRect.height;
 		UINT32 height = viewProps.viewRect.height;
 		UINT32 numSamples = viewProps.numSamples;
 		UINT32 numSamples = viewProps.numSamples;
 
 
+		UINT32 usageFlags = TU_RENDERTARGET;
+
+		bool tiledDeferredSupported = inputs.featureSet != RenderBeastFeatureSet::DesktopMacOS;
+		if(tiledDeferredSupported)
+			usageFlags |= TU_LOADSTORE;
+
 		// Note: Consider customizable HDR format via options? e.g. smaller PF_FLOAT_R11G11B10 or larger 32-bit format
 		// Note: Consider customizable HDR format via options? e.g. smaller PF_FLOAT_R11G11B10 or larger 32-bit format
-		sceneColorTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height, TU_RENDERTARGET | 
-			TU_LOADSTORE, numSamples, false));
+		sceneColorTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height, usageFlags, 
+			numSamples, false));
 
 
 		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
 		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
 		SPtr<PooledRenderTexture> sceneDepthTex = sceneDepthNode->depthTex;
 		SPtr<PooledRenderTexture> sceneDepthTex = sceneDepthNode->depthTex;
 
 
-		if (viewProps.numSamples > 1)
+		if (tiledDeferredSupported && viewProps.numSamples > 1)
 		{
 		{
 			UINT32 bufferNumElements = width * height * viewProps.numSamples;
 			UINT32 bufferNumElements = width * height * viewProps.numSamples;
 			flattenedSceneColorBuffer = resPool.get(POOLED_STORAGE_BUFFER_DESC::createStandard(BF_16X4F, bufferNumElements));
 			flattenedSceneColorBuffer = resPool.get(POOLED_STORAGE_BUFFER_DESC::createStandard(BF_16X4F, bufferNumElements));
@@ -458,6 +464,19 @@ namespace bs { namespace ct
 
 
 	void RCNodeLightAccumulation::render(const RenderCompositorNodeInputs& inputs)
 	void RCNodeLightAccumulation::render(const RenderCompositorNodeInputs& inputs)
 	{
 	{
+		bool supportsTiledDeferred = gRenderBeast()->getFeatureSet() != RenderBeastFeatureSet::DesktopMacOS;
+		if(!supportsTiledDeferred)
+		{
+			// If tiled deferred is not supported, we don't need a separate texture for light accumulation, instead we
+			// use scene color directly
+			RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
+			lightAccumulationTex = sceneColorNode->sceneColorTex;
+			renderTarget = sceneColorNode->renderTarget;
+
+			mOwnsTexture = false;
+			return;
+		}
+
 		GpuResourcePool& resPool = GpuResourcePool::instance();
 		GpuResourcePool& resPool = GpuResourcePool::instance();
 		const RendererViewProperties& viewProps = inputs.view.getProperties();
 		const RendererViewProperties& viewProps = inputs.view.getProperties();
 
 
@@ -513,12 +532,20 @@ namespace bs { namespace ct
 
 
 			renderTarget = RenderTexture::create(lightAccumulationRTDesc);
 			renderTarget = RenderTexture::create(lightAccumulationRTDesc);
 		}
 		}
+
+		mOwnsTexture = true;
 	}
 	}
 
 
 	void RCNodeLightAccumulation::clear()
 	void RCNodeLightAccumulation::clear()
 	{
 	{
 		GpuResourcePool& resPool = GpuResourcePool::instance();
 		GpuResourcePool& resPool = GpuResourcePool::instance();
-		resPool.release(lightAccumulationTex);
+		if(mOwnsTexture)
+			resPool.release(lightAccumulationTex);
+		else
+		{
+			lightAccumulationTex = nullptr;
+			renderTarget = nullptr;
+		}
 
 
 		if (flattenedLightAccumBuffer)
 		if (flattenedLightAccumBuffer)
 			resPool.release(flattenedLightAccumBuffer);
 			resPool.release(flattenedLightAccumBuffer);
@@ -526,7 +553,15 @@ namespace bs { namespace ct
 
 
 	SmallVector<StringID, 4> RCNodeLightAccumulation::getDependencies(const RendererView& view)
 	SmallVector<StringID, 4> RCNodeLightAccumulation::getDependencies(const RendererView& view)
 	{
 	{
-		return { RCNodeSceneDepth::getNodeId() };
+		SmallVector<StringID, 4> deps;
+
+		bool supportsTiledDeferred = gRenderBeast()->getFeatureSet() != RenderBeastFeatureSet::DesktopMacOS;
+		if(!supportsTiledDeferred)
+			deps.push_back(RCNodeSceneColor::getNodeId());
+		else
+			deps.push_back(RCNodeSceneDepth::getNodeId());
+
+		return deps;
 	}
 	}
 
 
 	void RCNodeTiledDeferredLighting::render(const RenderCompositorNodeInputs& inputs)
 	void RCNodeTiledDeferredLighting::render(const RenderCompositorNodeInputs& inputs)
@@ -581,14 +616,27 @@ namespace bs { namespace ct
 
 
 	void RCNodeStandardDeferredLighting::render(const RenderCompositorNodeInputs& inputs)
 	void RCNodeStandardDeferredLighting::render(const RenderCompositorNodeInputs& inputs)
 	{
 	{
-		RCNodeTiledDeferredLighting* tileDeferredNode = static_cast<RCNodeTiledDeferredLighting*>(inputs.inputNodes[0]);
-		output = tileDeferredNode->output;
+		SPtr<RenderTexture> outputRT;
 
 
-		// If shadows are disabled we handle all lights through tiled deferred
-		if (!inputs.view.getRenderSettings().enableShadows)
+		bool tiledDeferredSupported = inputs.featureSet == RenderBeastFeatureSet::Desktop;
+		if(tiledDeferredSupported)
 		{
 		{
+			RCNodeTiledDeferredLighting* tileDeferredNode = static_cast<RCNodeTiledDeferredLighting*>(inputs.inputNodes[2]);
+			outputRT = tileDeferredNode->output->renderTarget;
+
+			// If shadows are disabled we handle all lights through tiled deferred, except when tiled deferred isn't available
+			if (!inputs.view.getRenderSettings().enableShadows)
+			{
+				mLightOcclusionRT = nullptr;
+				return;
+			}
+		}
+		else
+		{
+			RCNodeLightAccumulation* lightAccumNode = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[2]);
+			outputRT = lightAccumNode->renderTarget;
+
 			mLightOcclusionRT = nullptr;
 			mLightOcclusionRT = nullptr;
-			return;
 		}
 		}
 
 
 		GpuResourcePool& resPool = GpuResourcePool::instance();
 		GpuResourcePool& resPool = GpuResourcePool::instance();
@@ -598,8 +646,40 @@ namespace bs { namespace ct
 		UINT32 height = viewProps.viewRect.height;
 		UINT32 height = viewProps.viewRect.height;
 		UINT32 numSamples = viewProps.numSamples;
 		UINT32 numSamples = viewProps.numSamples;
 
 
-		RCNodeGBuffer* gbufferNode = static_cast<RCNodeGBuffer*>(inputs.inputNodes[1]);
-		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[2]);
+		RCNodeGBuffer* gbufferNode = static_cast<RCNodeGBuffer*>(inputs.inputNodes[0]);
+		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[1]);
+
+		GBufferTextures gbuffer;
+		gbuffer.albedo = gbufferNode->albedoTex->texture;
+		gbuffer.normals = gbufferNode->normalTex->texture;
+		gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
+		gbuffer.depth = sceneDepthNode->depthTex->texture;
+
+		const VisibleLightData& lightData = inputs.viewGroup.getVisibleLightData();
+
+		RenderAPI& rapi = RenderAPI::instance();
+
+		// Render unshadowed lights
+		if(!tiledDeferredSupported)
+		{
+			rapi.setRenderTarget(outputRT, FBT_DEPTH | FBT_STENCIL, RT_COLOR0 | RT_DEPTH_STENCIL);
+
+			for (UINT32 i = 0; i < (UINT32)LightType::Count; i++)
+			{
+				LightType lightType = (LightType)i;
+
+				auto& lights = lightData.getLights(lightType);
+				UINT32 count = lightData.getNumUnshadowedLights(lightType);
+
+				for (UINT32 j = 0; j < count; j++)
+				{
+					UINT32 lightIdx = j;
+					const RendererLight& light = *lights[lightIdx];
+
+					StandardDeferred::instance().renderLight(lightType, light, inputs.view, gbuffer, Texture::BLACK);
+				}
+			}
+		}
 
 
 		// Allocate light occlusion
 		// Allocate light occlusion
 		SPtr<PooledRenderTexture> lightOcclusionTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, width,
 		SPtr<PooledRenderTexture> lightOcclusionTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, width,
@@ -630,16 +710,8 @@ namespace bs { namespace ct
 			mLightOcclusionRT = RenderTexture::create(lightOcclusionRTDesc);
 			mLightOcclusionRT = RenderTexture::create(lightOcclusionRTDesc);
 		}
 		}
 
 
-		GBufferTextures gbuffer;
-		gbuffer.albedo = gbufferNode->albedoTex->texture;
-		gbuffer.normals = gbufferNode->normalTex->texture;
-		gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
-		gbuffer.depth = sceneDepthNode->depthTex->texture;
-
-		const VisibleLightData& lightData = inputs.viewGroup.getVisibleLightData();
+		// Render shadowed lights
 		const ShadowRendering& shadowRenderer = inputs.viewGroup.getShadowRenderer();
 		const ShadowRendering& shadowRenderer = inputs.viewGroup.getShadowRenderer();
-
-		RenderAPI& rapi = RenderAPI::instance();
 		for (UINT32 i = 0; i < (UINT32)LightType::Count; i++)
 		for (UINT32 i = 0; i < (UINT32)LightType::Count; i++)
 		{
 		{
 			LightType lightType = (LightType)i;
 			LightType lightType = (LightType)i;
@@ -661,7 +733,7 @@ namespace bs { namespace ct
 				const RendererLight& light = *lights[lightIdx];
 				const RendererLight& light = *lights[lightIdx];
 				shadowRenderer.renderShadowOcclusion(inputs.view, inputs.options.shadowFilteringQuality, light, gbuffer);
 				shadowRenderer.renderShadowOcclusion(inputs.view, inputs.options.shadowFilteringQuality, light, gbuffer);
 
 
-				rapi.setRenderTarget(output->renderTarget, FBT_DEPTH | FBT_STENCIL, RT_COLOR0 | RT_DEPTH_STENCIL);
+				rapi.setRenderTarget(outputRT, FBT_DEPTH | FBT_STENCIL, RT_COLOR0 | RT_DEPTH_STENCIL);
 				StandardDeferred::instance().renderLight(lightType, light, inputs.view, gbuffer,
 				StandardDeferred::instance().renderLight(lightType, light, inputs.view, gbuffer,
 					lightOcclusionTex->texture);
 					lightOcclusionTex->texture);
 			}
 			}
@@ -675,23 +747,192 @@ namespace bs { namespace ct
 
 
 	void RCNodeStandardDeferredLighting::clear()
 	void RCNodeStandardDeferredLighting::clear()
 	{
 	{
-		output = nullptr;
+		// Do nothing
 	}
 	}
 
 
 	SmallVector<StringID, 4> RCNodeStandardDeferredLighting::getDependencies(const RendererView& view)
 	SmallVector<StringID, 4> RCNodeStandardDeferredLighting::getDependencies(const RendererView& view)
 	{
 	{
 		SmallVector<StringID, 4> deps;
 		SmallVector<StringID, 4> deps;
 
 
-		deps.push_back(RCNodeTiledDeferredLighting::getNodeId());
 		deps.push_back(RCNodeGBuffer::getNodeId());
 		deps.push_back(RCNodeGBuffer::getNodeId());
 		deps.push_back(RCNodeSceneDepth::getNodeId());
 		deps.push_back(RCNodeSceneDepth::getNodeId());
 
 
-		if (view.getProperties().numSamples > 1)
-			deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+		if(gRenderBeast()->getFeatureSet() == RenderBeastFeatureSet::DesktopMacOS)
+		{
+			deps.push_back(RCNodeLightAccumulation::getNodeId());
+		}
+		else
+		{
+			deps.push_back(RCNodeTiledDeferredLighting::getNodeId());
+
+			if (view.getProperties().numSamples > 1)
+				deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+		}
 
 
 		return deps;
 		return deps;
 	}
 	}
 
 
+	void RCNodeStandardDeferredIBL::render(const RenderCompositorNodeInputs& inputs)
+	{
+		RCNodeLightAccumulation* lightAccumNode = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[2]);
+		SPtr<RenderTexture>	outputRT = lightAccumNode->renderTarget;
+
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+		const RendererViewProperties& viewProps = inputs.view.getProperties();
+
+		UINT32 width = viewProps.viewRect.width;
+		UINT32 height = viewProps.viewRect.height;
+		UINT32 numSamples = viewProps.numSamples;
+
+		RCNodeGBuffer* gbufferNode = static_cast<RCNodeGBuffer*>(inputs.inputNodes[0]);
+		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[1]);
+
+		GBufferTextures gbuffer;
+		gbuffer.albedo = gbufferNode->albedoTex->texture;
+		gbuffer.normals = gbufferNode->normalTex->texture;
+		gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
+		gbuffer.depth = sceneDepthNode->depthTex->texture;
+
+		RenderAPI& rapi = RenderAPI::instance();
+
+		const RenderSettings& rs = inputs.view.getRenderSettings();
+		bool isMSAA = viewProps.numSamples > 1;
+
+		SPtr<PooledRenderTexture> iblRadianceTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width,
+			height, TU_RENDERTARGET, numSamples, false));
+
+		RENDER_TEXTURE_DESC rtDesc;
+		rtDesc.colorSurfaces[0].texture = iblRadianceTex->texture;
+		rtDesc.depthStencilSurface.texture = sceneDepthNode->depthTex->texture;
+
+		SPtr<GpuParamBlockBuffer> perViewBuffer = inputs.view.getPerViewBuffer();
+
+		SPtr<RenderTexture> iblRadianceRT = RenderTexture::create(rtDesc);
+		rapi.setRenderTarget(iblRadianceRT, FBT_DEPTH | FBT_STENCIL, RT_DEPTH_STENCIL);
+
+		const VisibleReflProbeData& probeData = inputs.viewGroup.getVisibleReflProbeData();
+
+		ReflProbeParamBuffer reflProbeParams;
+		reflProbeParams.populate(inputs.scene.skybox, probeData.getNumProbes(), inputs.scene.reflProbeCubemapsTex,
+			viewProps.renderingReflections);
+
+		// Prepare the texture for refl. probe and skybox rendering
+		{
+			SPtr<Texture> ssr;
+			if (rs.screenSpaceReflections.enabled)
+			{
+				RCNodeSSR* ssrNode = static_cast<RCNodeSSR*>(inputs.inputNodes[3]);
+				ssr = ssrNode->output->texture;
+			}
+			else
+				ssr = Texture::BLACK;
+
+			UINT32 nodeIdx = 4;
+			SPtr<Texture> ssao;
+			if (rs.ambientOcclusion.enabled)
+			{
+				RCNodeSSAO* ssaoNode = static_cast<RCNodeSSAO*>(inputs.inputNodes[nodeIdx++]);
+				ssao = ssaoNode->output->texture;
+			}
+			else
+				ssao = Texture::WHITE;
+
+			DeferredIBLSetupMat* mat = DeferredIBLSetupMat::getVariation(isMSAA, true);
+			mat->bind(gbuffer, perViewBuffer, ssr, ssao, reflProbeParams.buffer);
+
+			gRendererUtility().drawScreenQuad();
+
+			// Draw pixels requiring per-sample evaluation
+			if (isMSAA)
+			{
+				DeferredIBLSetupMat* msaaMat = DeferredIBLSetupMat::getVariation(true, false);
+				msaaMat->bind(gbuffer, perViewBuffer, ssr, ssao, reflProbeParams.buffer);
+
+				gRendererUtility().drawScreenQuad();
+			}
+		}
+
+		if (viewProps.renderingReflections)
+		{
+			// Render refl. probes
+			UINT32 numProbes = probeData.getNumProbes();
+			for (UINT32 i = 0; i < numProbes; i++)
+			{
+				const ReflProbeData& probe = probeData.getProbeData(i);
+
+				StandardDeferred::instance().renderReflProbe(probe, inputs.view, gbuffer, inputs.scene,
+					reflProbeParams.buffer);
+			}
+
+			// Render sky
+			SPtr<Texture> skyFilteredRadiance;
+			if (inputs.scene.skybox)
+				skyFilteredRadiance = inputs.scene.skybox->getFilteredRadiance();
+
+			if (skyFilteredRadiance)
+			{
+				DeferredIBLSkyMat* skymat = DeferredIBLSkyMat::getVariation(isMSAA, true);
+				skymat->bind(gbuffer, perViewBuffer, inputs.scene.skybox, reflProbeParams.buffer);
+
+				gRendererUtility().drawScreenQuad();
+
+				// Draw pixels requiring per-sample evaluation
+				if (isMSAA)
+				{
+					DeferredIBLSkyMat* msaaMat = DeferredIBLSkyMat::getVariation(true, false);
+					msaaMat->bind(gbuffer, perViewBuffer, inputs.scene.skybox, reflProbeParams.buffer);
+
+					gRendererUtility().drawScreenQuad();
+				}
+			}
+		}
+
+		// Finalize rendered reflections and output them to main render target
+		{
+			rapi.setRenderTarget(outputRT, FBT_DEPTH | FBT_STENCIL, RT_COLOR0 | RT_DEPTH_STENCIL);
+
+			DeferredIBLFinalizeMat* mat = DeferredIBLFinalizeMat::getVariation(isMSAA, true);
+			mat->bind(gbuffer, perViewBuffer, iblRadianceTex->texture, RendererTextures::preintegratedEnvGF,
+				reflProbeParams.buffer);
+
+			gRendererUtility().drawScreenQuad();
+
+			// Draw pixels requiring per-sample evaluation
+			if (isMSAA)
+			{
+				DeferredIBLFinalizeMat* msaaMat = DeferredIBLFinalizeMat::getVariation(true, false);
+				msaaMat->bind(gbuffer, perViewBuffer, iblRadianceTex->texture, RendererTextures::preintegratedEnvGF,
+					reflProbeParams.buffer);
+
+				gRendererUtility().drawScreenQuad();
+			}
+		}
+
+		// Makes sure light accumulation can be read by following passes
+		rapi.setRenderTarget(nullptr);
+	}
+
+	void RCNodeStandardDeferredIBL::clear()
+	{
+		// Do nothing
+	}
+
+	SmallVector<StringID, 4> RCNodeStandardDeferredIBL::getDependencies(const RendererView& view)
+	{
+		SmallVector<StringID, 4> deps;
+
+		deps.push_back(RCNodeGBuffer::getNodeId());
+		deps.push_back(RCNodeSceneDepth::getNodeId());
+		deps.push_back(RCNodeLightAccumulation::getNodeId());
+		deps.push_back(RCNodeSSR::getNodeId());
+
+		if (view.getRenderSettings().ambientOcclusion.enabled)
+			deps.push_back(RCNodeSSAO::getNodeId());
+
+		deps.push_back(RCNodeStandardDeferredLighting::getNodeId());
+
+		return deps;
+	}
 	void RCNodeUnflattenLightAccum::render(const RenderCompositorNodeInputs& inputs)
 	void RCNodeUnflattenLightAccum::render(const RenderCompositorNodeInputs& inputs)
 	{
 	{
 		RCNodeLightAccumulation* lightAccumNode = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[0]);
 		RCNodeLightAccumulation* lightAccumNode = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[0]);
@@ -800,13 +1041,21 @@ namespace bs { namespace ct
 		deps.push_back(RCNodeGBuffer::getNodeId());
 		deps.push_back(RCNodeGBuffer::getNodeId());
 		deps.push_back(RCNodeSceneDepth::getNodeId());
 		deps.push_back(RCNodeSceneDepth::getNodeId());
 		deps.push_back(RCNodeLightAccumulation::getNodeId());
 		deps.push_back(RCNodeLightAccumulation::getNodeId());
-		deps.push_back(RCNodeStandardDeferredLighting::getNodeId());
+
+		bool supportsTiledDeferred = gRenderBeast()->getFeatureSet() != RenderBeastFeatureSet::DesktopMacOS;
+		if(supportsTiledDeferred)
+			deps.push_back(RCNodeStandardDeferredLighting::getNodeId());
+		else
+			deps.push_back(RCNodeStandardDeferredIBL::getNodeId());
 
 
 		if(view.getRenderSettings().ambientOcclusion.enabled)
 		if(view.getRenderSettings().ambientOcclusion.enabled)
 			deps.push_back(RCNodeSSAO::getNodeId());
 			deps.push_back(RCNodeSSAO::getNodeId());
 
 
-		if (view.getProperties().numSamples > 1)
-			deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+		if(supportsTiledDeferred)
+		{
+			if (view.getProperties().numSamples > 1)
+				deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+		}
 
 
 		return deps;
 		return deps;
 	}
 	}
@@ -949,7 +1198,7 @@ namespace bs { namespace ct
 
 
 		// Prepare refl. probe param buffer
 		// Prepare refl. probe param buffer
 		ReflProbeParamBuffer reflProbeParamBuffer;
 		ReflProbeParamBuffer reflProbeParamBuffer;
-		reflProbeParamBuffer.populate(sceneInfo.skybox, visibleReflProbeData, sceneInfo.reflProbeCubemapsTex, 
+		reflProbeParamBuffer.populate(sceneInfo.skybox, visibleReflProbeData.getNumProbes(), sceneInfo.reflProbeCubemapsTex, 
 			viewProps.renderingReflections);
 			viewProps.renderingReflections);
 
 
 		SPtr<Texture> skyFilteredRadiance;
 		SPtr<Texture> skyFilteredRadiance;
@@ -1072,7 +1321,7 @@ namespace bs { namespace ct
 			material->bind(inputs.view.getPerViewBuffer(), nullptr, clearColor);
 			material->bind(inputs.view.getPerViewBuffer(), nullptr, clearColor);
 		}
 		}
 
 
-		RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[1]);
+		RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
 		int readOnlyFlags = FBT_DEPTH | FBT_STENCIL;
 		int readOnlyFlags = FBT_DEPTH | FBT_STENCIL;
 
 
 		RenderAPI& rapi = RenderAPI::instance();
 		RenderAPI& rapi = RenderAPI::instance();
@@ -1090,13 +1339,20 @@ namespace bs { namespace ct
 
 
 	SmallVector<StringID, 4> RCNodeSkybox::getDependencies(const RendererView& view)
 	SmallVector<StringID, 4> RCNodeSkybox::getDependencies(const RendererView& view)
 	{
 	{
-		SmallVector<StringID, 4> deps;
+		bool supportsTiledDeferred = gRenderBeast()->getFeatureSet() != RenderBeastFeatureSet::DesktopMacOS;
 
 
-		deps.push_back(RCNodeTiledDeferredIBL::getNodeId());
+		SmallVector<StringID, 4> deps;
 		deps.push_back(RCNodeSceneColor::getNodeId());
 		deps.push_back(RCNodeSceneColor::getNodeId());
 
 
-		if (view.getProperties().numSamples > 1)
-			deps.push_back(RCNodeUnflattenSceneColor::getNodeId());
+		if(supportsTiledDeferred)
+		{
+			deps.push_back(RCNodeTiledDeferredIBL::getNodeId());
+
+			if (view.getProperties().numSamples > 1)
+				deps.push_back(RCNodeUnflattenSceneColor::getNodeId());
+		}
+		else
+			deps.push_back(RCNodeIndirectLighting::getNodeId());
 
 
 		return deps;
 		return deps;
 	}
 	}
@@ -2014,8 +2270,12 @@ namespace bs { namespace ct
 			deps.push_back(RCNodeHiZ::getNodeId());
 			deps.push_back(RCNodeHiZ::getNodeId());
 			deps.push_back(RCNodeResolvedSceneDepth::getNodeId());
 			deps.push_back(RCNodeResolvedSceneDepth::getNodeId());
 
 
-			if (view.getProperties().numSamples > 1)
-				deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+			bool supportsTiledDeferred = gRenderBeast()->getFeatureSet() != RenderBeastFeatureSet::DesktopMacOS;
+			if(supportsTiledDeferred)
+			{
+				if (view.getProperties().numSamples > 1)
+					deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+			}
 		}
 		}
 
 
 		return deps;
 		return deps;

+ 21 - 4
Source/RenderBeast/BsRenderCompositor.h

@@ -292,6 +292,8 @@ namespace ct
 
 
 		/** @copydoc RenderCompositorNode::clear */
 		/** @copydoc RenderCompositorNode::clear */
 		void clear() override;
 		void clear() override;
+
+		bool mOwnsTexture = false;
 	};
 	};
 
 
 	/** 
 	/** 
@@ -316,14 +318,12 @@ namespace ct
 
 
 	/**
 	/**
 	 * Performs standard deferred lighting, outputting any lighting information in the light accumulation buffer.
 	 * Performs standard deferred lighting, outputting any lighting information in the light accumulation buffer.
-	 * Only renders shadowed lights.
+	 * Normally only used for shadowed light rendering, unless tiled deferred is unavailable, in which case it renders
+	 * all lights.
 	 */
 	 */
 	class RCNodeStandardDeferredLighting : public RenderCompositorNode
 	class RCNodeStandardDeferredLighting : public RenderCompositorNode
 	{
 	{
 	public:
 	public:
-		// Outputs
-		RCNodeLightAccumulation* output;
-
 		static StringID getNodeId() { return "StandardDeferredLighting"; }
 		static StringID getNodeId() { return "StandardDeferredLighting"; }
 		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
 		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
 	protected:
 	protected:
@@ -336,6 +336,23 @@ namespace ct
 		SPtr<RenderTexture> mLightOcclusionRT;
 		SPtr<RenderTexture> mLightOcclusionRT;
 	};
 	};
 
 
+	/**
+	 * Render reflection probes and sky reflections using standard deferred lighting. Only used if tiled deferred IBL
+	 * is not supported.
+	 */
+	class RCNodeStandardDeferredIBL : public RenderCompositorNode
+	{
+	public:
+		static StringID getNodeId() { return "StandardDeferredIBL"; }
+		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
+	protected:
+		/** @copydoc RenderCompositorNode::render */
+		void render(const RenderCompositorNodeInputs& inputs) override;
+
+		/** @copydoc RenderCompositorNode::clear */
+		void clear() override;
+	};
+
 	/**
 	/**
 	 * In case light accumulation was rendered into a buffer instead of a texture (if MSAA is used), this node will
 	 * In case light accumulation was rendered into a buffer instead of a texture (if MSAA is used), this node will
 	 * unflatten the buffer and write its contents into the light accumulation texture.
 	 * unflatten the buffer and write its contents into the light accumulation texture.

+ 1 - 1
Source/RenderBeast/BsShadowRendering.cpp

@@ -723,7 +723,7 @@ namespace bs { namespace ct
 					viewProps.numSamples > 1);
 					viewProps.numSamples > 1);
 				mat->bind(shadowParams);
 				mat->bind(shadowParams);
 
 
-				gRendererUtility().draw(gRendererUtility().getRadialLightStencil());
+				gRendererUtility().draw(gRendererUtility().getSphereStencil());
 			}
 			}
 		}
 		}
 		else // Directional & spot
 		else // Directional & spot

+ 262 - 11
Source/RenderBeast/BsStandardDeferredLighting.cpp

@@ -5,17 +5,20 @@
 #include "BsRendererView.h"
 #include "BsRendererView.h"
 #include "Material/BsGpuParamsSet.h"
 #include "Material/BsGpuParamsSet.h"
 #include "Mesh/BsMesh.h"
 #include "Mesh/BsMesh.h"
+#include "Renderer/BsSkybox.h"
+#include "BsRendererScene.h"
+#include "Renderer/BsReflectionProbe.h"
 
 
 namespace bs { namespace ct {
 namespace bs { namespace ct {
 	PerLightParamDef gPerLightParamDef;
 	PerLightParamDef gPerLightParamDef;
 
 
-	DirectionalLightMat::DirectionalLightMat()
+	DeferredDirectionalLightMat::DeferredDirectionalLightMat()
 		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
 		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
 	{
 	{
 		mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gLightOcclusionTex", mLightOcclusionTexParam);
 		mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gLightOcclusionTex", mLightOcclusionTexParam);
 	}
 	}
 
 
-	void DirectionalLightMat::bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
+	void DeferredDirectionalLightMat::bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 		const SPtr<GpuParamBlockBuffer>& perCamera, const SPtr<GpuParamBlockBuffer>& perLight)
 		const SPtr<GpuParamBlockBuffer>& perCamera, const SPtr<GpuParamBlockBuffer>& perLight)
 	{
 	{
 		mGBufferParams.bind(gBufferInput);
 		mGBufferParams.bind(gBufferInput);
@@ -26,7 +29,7 @@ namespace bs { namespace ct {
 		RendererMaterial::bind();
 		RendererMaterial::bind();
 	}
 	}
 
 
-	DirectionalLightMat* DirectionalLightMat::getVariation(bool msaa, bool singleSampleMSAA)
+	DeferredDirectionalLightMat* DeferredDirectionalLightMat::getVariation(bool msaa, bool singleSampleMSAA)
 	{
 	{
 		if (msaa)
 		if (msaa)
 		{
 		{
@@ -39,13 +42,13 @@ namespace bs { namespace ct {
 		return get(getVariation<false, false>());
 		return get(getVariation<false, false>());
 	}
 	}
 
 
-	PointLightMat::PointLightMat()
+	DeferredPointLightMat::DeferredPointLightMat()
 		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
 		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
 	{
 	{
 		mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gLightOcclusionTex", mLightOcclusionTexParam);
 		mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gLightOcclusionTex", mLightOcclusionTexParam);
 	}
 	}
 
 
-	void PointLightMat::bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
+	void DeferredPointLightMat::bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 		const SPtr<GpuParamBlockBuffer>& perCamera, const SPtr<GpuParamBlockBuffer>& perLight)
 		const SPtr<GpuParamBlockBuffer>& perCamera, const SPtr<GpuParamBlockBuffer>& perLight)
 	{
 	{
 		mGBufferParams.bind(gBufferInput);
 		mGBufferParams.bind(gBufferInput);
@@ -56,7 +59,7 @@ namespace bs { namespace ct {
 		RendererMaterial::bind();
 		RendererMaterial::bind();
 	}
 	}
 
 
-	PointLightMat* PointLightMat::getVariation(bool inside, bool msaa, bool singleSampleMSAA)
+	DeferredPointLightMat* DeferredPointLightMat::getVariation(bool inside, bool msaa, bool singleSampleMSAA)
 	{
 	{
 		if(msaa)
 		if(msaa)
 		{
 		{
@@ -84,6 +87,206 @@ namespace bs { namespace ct {
 		}
 		}
 	}
 	}
 
 
+	PerProbeParamDef gPerProbeParamDef;
+
+	DeferredIBLSetupMat::DeferredIBLSetupMat()
+		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
+	{
+		mIBLParams.populate(mParams, GPT_FRAGMENT_PROGRAM, true, false, false);
+
+		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;
+
+		SPtr<SamplerState> samplerState = SamplerState::create(desc);
+		mIBLParams.ssrSampParam.set(samplerState);
+		mIBLParams.ambientOcclusionSampParam.set(samplerState);
+	}
+
+	void DeferredIBLSetupMat::bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera, 
+		const SPtr<Texture>& ssr, const SPtr<Texture>& ao, const SPtr<GpuParamBlockBuffer>& reflProbeParams)
+	{
+		mGBufferParams.bind(gBufferInput);
+
+		mParams->setParamBlockBuffer("PerCamera", perCamera);
+		mParams->setParamBlockBuffer("ReflProbeParams", reflProbeParams);
+
+		mIBLParams.ambientOcclusionTexParam.set(ao);
+		mIBLParams.ssrTexParam.set(ssr);
+
+		RendererMaterial::bind();
+	}
+
+	DeferredIBLSetupMat* DeferredIBLSetupMat::getVariation(bool msaa, bool singleSampleMSAA)
+	{
+		if(msaa)
+		{
+			if (singleSampleMSAA)
+				return get(getVariation<true, true>());
+
+			return get(getVariation<true, false>());
+		}
+		else
+		{
+			return get(getVariation<false, false>());
+		}
+	}
+
+	DeferredIBLProbeMat::DeferredIBLProbeMat()
+		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
+	{
+		mIBLParams.populate(mParams, GPT_FRAGMENT_PROGRAM, true, false, false);
+
+		mParamBuffer = gPerProbeParamDef.createBuffer();
+		mParams->setParamBlockBuffer("PerProbe", mParamBuffer);
+	}
+
+	void DeferredIBLProbeMat::bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera, 
+		const SceneInfo& sceneInfo, const ReflProbeData& probeData, const SPtr<GpuParamBlockBuffer>& reflProbeParams)
+	{
+		mGBufferParams.bind(gBufferInput);
+
+		mParams->setParamBlockBuffer("PerCamera", perCamera);
+		mParams->setParamBlockBuffer("ReflProbeParams", reflProbeParams);
+
+		gPerProbeParamDef.gPosition.set(mParamBuffer, probeData.position);
+
+		if(probeData.type == 1)
+			gPerProbeParamDef.gExtents.set(mParamBuffer, probeData.boxExtents);
+		else
+		{
+			Vector3 extents(probeData.radius, probeData.radius, probeData.radius);
+			gPerProbeParamDef.gExtents.set(mParamBuffer, extents);
+		}
+
+		gPerProbeParamDef.gTransitionDistance.set(mParamBuffer, probeData.transitionDistance);
+		gPerProbeParamDef.gInvBoxTransform.set(mParamBuffer, probeData.invBoxTransform);
+		gPerProbeParamDef.gCubemapIdx.set(mParamBuffer, probeData.cubemapIdx);
+		gPerProbeParamDef.gType.set(mParamBuffer, probeData.type);
+
+		mIBLParams.reflectionProbeCubemapsTexParam.set(sceneInfo.reflProbeCubemapsTex);
+
+		RendererMaterial::bind();
+	}
+
+	DeferredIBLProbeMat* DeferredIBLProbeMat::getVariation(bool inside, bool msaa, bool singleSampleMSAA)
+	{
+		if(msaa)
+		{
+			if (inside)
+			{
+				if (singleSampleMSAA)
+					return get(getVariation<true, true, true>());
+
+				return get(getVariation<true, true, false>());
+			}
+			else
+			{
+				if (singleSampleMSAA)
+					return get(getVariation<false, true, true>());
+
+				return get(getVariation<false, true, false>());
+			}
+		}
+		else
+		{
+			if (inside)
+				return get(getVariation<true, false, false>());
+			else
+				return get(getVariation<false, false, false>());
+		}
+	}
+
+	DeferredIBLSkyMat::DeferredIBLSkyMat()
+		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
+	{
+		mIBLParams.populate(mParams, GPT_FRAGMENT_PROGRAM, true, false, false);
+
+		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;
+
+		SPtr<SamplerState> samplerState = SamplerState::create(desc);
+		mIBLParams.ssrSampParam.set(samplerState);
+		mIBLParams.ambientOcclusionSampParam.set(samplerState);
+	}
+
+	void DeferredIBLSkyMat::bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera, 
+		const Skybox* skybox, const SPtr<GpuParamBlockBuffer>& reflProbeParams)
+	{
+		mGBufferParams.bind(gBufferInput);
+
+		mParams->setParamBlockBuffer("PerCamera", perCamera);
+		mParams->setParamBlockBuffer("ReflProbeParams", reflProbeParams);
+
+		if(skybox != nullptr)
+			mIBLParams.skyReflectionsTexParam.set(skybox->getFilteredRadiance());
+
+		RendererMaterial::bind();
+	}
+
+	DeferredIBLSkyMat* DeferredIBLSkyMat::getVariation(bool msaa, bool singleSampleMSAA)
+	{
+		if(msaa)
+		{
+			if (singleSampleMSAA)
+				return get(getVariation<true, true>());
+
+			return get(getVariation<true, false>());
+		}
+		else
+		{
+			return get(getVariation<false, false>());
+		}
+	}
+
+	DeferredIBLFinalizeMat::DeferredIBLFinalizeMat()
+		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
+	{
+		mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gIBLRadianceTex", mIBLRadiance);
+
+		mIBLParams.populate(mParams, GPT_FRAGMENT_PROGRAM, true, false, false);
+	}
+
+	void DeferredIBLFinalizeMat::bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera, 
+		const SPtr<Texture>& iblRadiance, const SPtr<Texture>& preintegratedBrdf, 
+		const SPtr<GpuParamBlockBuffer>& reflProbeParams)
+	{
+		mGBufferParams.bind(gBufferInput);
+
+		mParams->setParamBlockBuffer("PerCamera", perCamera);
+		mParams->setParamBlockBuffer("ReflProbeParams", reflProbeParams);
+
+		mIBLParams.preintegratedEnvBRDFParam.set(preintegratedBrdf);
+
+		mIBLRadiance.set(iblRadiance);
+
+		RendererMaterial::bind();
+	}
+
+	DeferredIBLFinalizeMat* DeferredIBLFinalizeMat::getVariation(bool msaa, bool singleSampleMSAA)
+	{
+		if(msaa)
+		{
+			if (singleSampleMSAA)
+				return get(getVariation<true, true>());
+
+			return get(getVariation<true, false>());
+		}
+		else
+		{
+			return get(getVariation<false, false>());
+		}
+	}
+
 	StandardDeferred::StandardDeferred()
 	StandardDeferred::StandardDeferred()
 	{
 	{
 		mPerLightBuffer = gPerLightParamDef.createBuffer();
 		mPerLightBuffer = gPerLightParamDef.createBuffer();
@@ -101,7 +304,7 @@ namespace bs { namespace ct {
 
 
 		if (lightType == LightType::Directional)
 		if (lightType == LightType::Directional)
 		{
 		{
-			DirectionalLightMat* material = DirectionalLightMat::getVariation(isMSAA, true);
+			DeferredDirectionalLightMat* material = DeferredDirectionalLightMat::getVariation(isMSAA, true);
 			material->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 			material->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 
 
 			gRendererUtility().drawScreenQuad();
 			gRendererUtility().drawScreenQuad();
@@ -109,7 +312,7 @@ namespace bs { namespace ct {
 			// Draw pixels requiring per-sample evaluation
 			// Draw pixels requiring per-sample evaluation
 			if(isMSAA)
 			if(isMSAA)
 			{
 			{
-				DirectionalLightMat* msaaMaterial = DirectionalLightMat::getVariation(true, false);
+				DeferredDirectionalLightMat* msaaMaterial = DeferredDirectionalLightMat::getVariation(true, false);
 				msaaMaterial->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 				msaaMaterial->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 
 
 				gRendererUtility().drawScreenQuad();
 				gRendererUtility().drawScreenQuad();
@@ -129,11 +332,11 @@ namespace bs { namespace ct {
 
 
 			SPtr<Mesh> stencilMesh;
 			SPtr<Mesh> stencilMesh;
 			if(lightType == LightType::Radial)
 			if(lightType == LightType::Radial)
-				stencilMesh = RendererUtility::instance().getRadialLightStencil();
+				stencilMesh = RendererUtility::instance().getSphereStencil();
 			else // Spot
 			else // Spot
 				stencilMesh = RendererUtility::instance().getSpotLightStencil();
 				stencilMesh = RendererUtility::instance().getSpotLightStencil();
 
 
-			PointLightMat* material = PointLightMat::getVariation(isInside, isMSAA, true);
+			DeferredPointLightMat* material = DeferredPointLightMat::getVariation(isInside, isMSAA, true);
 			material->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 			material->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 
 
 			// Note: If MSAA is enabled this will be rendered multisampled (on polygon edges), see if this can be avoided
 			// Note: If MSAA is enabled this will be rendered multisampled (on polygon edges), see if this can be avoided
@@ -142,11 +345,59 @@ namespace bs { namespace ct {
 			// Draw pixels requiring per-sample evaluation
 			// Draw pixels requiring per-sample evaluation
 			if(isMSAA)
 			if(isMSAA)
 			{
 			{
-				PointLightMat* msaaMaterial = PointLightMat::getVariation(isInside, true, false);
+				DeferredPointLightMat* msaaMaterial = DeferredPointLightMat::getVariation(isInside, true, false);
 				msaaMaterial->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 				msaaMaterial->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 
 
 				gRendererUtility().draw(stencilMesh);
 				gRendererUtility().draw(stencilMesh);
 			}
 			}
 		}
 		}
 	}
 	}
+	void StandardDeferred::renderReflProbe(const ReflProbeData& probeData, const RendererView& view,
+		const GBufferTextures& gBufferInput, const SceneInfo& sceneInfo, const SPtr<GpuParamBlockBuffer>& reflProbeParams)
+	{
+		const auto& viewProps = view.getProperties();
+		bool isMSAA = viewProps.numSamples > 1;
+
+		SPtr<GpuParamBlockBuffer> perViewBuffer = view.getPerViewBuffer();
+
+		// When checking if viewer is inside the volume extend the bounds slighty to cover the case when the viewer is
+		// outside, but the near plane is intersecting the bounds. We need to be conservative since the material for
+		// rendering outside will not properly render the inside of the volume.
+		float radiusBuffer = viewProps.nearPlane * 3.0f;
+
+		SPtr<Mesh> stencilMesh;
+		bool isInside;
+		if(probeData.type == 0) // Sphere
+		{
+			// Check if viewer is inside the light volume
+			float distSqrd = (probeData.position - viewProps.viewOrigin).squaredLength();
+			float boundRadius = probeData.radius + radiusBuffer;
+			
+			isInside = distSqrd < (boundRadius * boundRadius);
+			stencilMesh = RendererUtility::instance().getSphereStencil();
+		} 
+		else // Box
+		{
+			Vector3 extents = probeData.boxExtents + radiusBuffer;
+			AABox box(probeData.position - extents, probeData.position + extents);
+
+			isInside = box.contains(viewProps.viewOrigin);
+			stencilMesh = RendererUtility::instance().getBoxStencil();
+		}
+
+		DeferredIBLProbeMat* material = DeferredIBLProbeMat::getVariation(isInside, isMSAA, true);
+		material->bind(gBufferInput, perViewBuffer, sceneInfo, probeData, reflProbeParams);
+
+		// Note: If MSAA is enabled this will be rendered multisampled (on polygon edges), see if this can be avoided
+		gRendererUtility().draw(stencilMesh);
+
+		// Draw pixels requiring per-sample evaluation
+		if (isMSAA)
+		{
+			DeferredIBLProbeMat* msaaMaterial = DeferredIBLProbeMat::getVariation(isInside, true, false);
+			msaaMaterial->bind(gBufferInput, perViewBuffer, sceneInfo, probeData, reflProbeParams);
+
+			gRendererUtility().draw(stencilMesh);
+		}
+	}
 }}
 }}

+ 200 - 6
Source/RenderBeast/BsStandardDeferredLighting.h

@@ -6,6 +6,7 @@
 #include "Utility/BsModule.h"
 #include "Utility/BsModule.h"
 #include "Renderer/BsRendererMaterial.h"
 #include "Renderer/BsRendererMaterial.h"
 #include "BsLightRendering.h"
 #include "BsLightRendering.h"
+#include "BsImageBasedLighting.h"
 
 
 namespace bs { namespace ct {
 namespace bs { namespace ct {
 	class RendererLight;
 	class RendererLight;
@@ -23,7 +24,7 @@ namespace bs { namespace ct {
 	extern PerLightParamDef gPerLightParamDef;
 	extern PerLightParamDef gPerLightParamDef;
 
 
 	/** Shader that renders directional light sources during deferred rendering light pass. */
 	/** Shader that renders directional light sources during deferred rendering light pass. */
-	class DirectionalLightMat : public RendererMaterial<DirectionalLightMat>
+	class DeferredDirectionalLightMat : public RendererMaterial<DeferredDirectionalLightMat>
 	{
 	{
 		RMAT_DEF("DeferredDirectionalLight.bsl");
 		RMAT_DEF("DeferredDirectionalLight.bsl");
 
 
@@ -39,7 +40,7 @@ namespace bs { namespace ct {
 			return variation;
 			return variation;
 		}
 		}
 	public:
 	public:
-		DirectionalLightMat();
+		DeferredDirectionalLightMat();
 
 
 		/** Binds the material for rendering and sets up any parameters. */
 		/** Binds the material for rendering and sets up any parameters. */
 		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
@@ -53,14 +54,14 @@ namespace bs { namespace ct {
 		 *									evaluated. Otherwise all samples will be evaluated.
 		 *									evaluated. Otherwise all samples will be evaluated.
 		 * @return							Requested variation of the material.
 		 * @return							Requested variation of the material.
 		 */
 		 */
-		static DirectionalLightMat* getVariation(bool msaa, bool singleSampleMSAA = false);
+		static DeferredDirectionalLightMat* getVariation(bool msaa, bool singleSampleMSAA = false);
 	private:
 	private:
 		GBufferParams mGBufferParams;
 		GBufferParams mGBufferParams;
 		GpuParamTexture mLightOcclusionTexParam;
 		GpuParamTexture mLightOcclusionTexParam;
 	};
 	};
 
 
 	/** Shader that renders point (radial & spot) light sources during deferred rendering light pass. */
 	/** Shader that renders point (radial & spot) light sources during deferred rendering light pass. */
-	class PointLightMat : public RendererMaterial<PointLightMat>
+	class DeferredPointLightMat : public RendererMaterial<DeferredPointLightMat>
 	{
 	{
 		RMAT_DEF("DeferredPointLight.bsl");
 		RMAT_DEF("DeferredPointLight.bsl");
 
 
@@ -77,7 +78,7 @@ namespace bs { namespace ct {
 			return variation;
 			return variation;
 		}
 		}
 	public:
 	public:
-		PointLightMat();
+		DeferredPointLightMat();
 
 
 		/** Binds the material for rendering and sets up any parameters. */
 		/** Binds the material for rendering and sets up any parameters. */
 		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
@@ -92,12 +93,196 @@ namespace bs { namespace ct {
 		 *									evaluated. Otherwise all samples will be evaluated.
 		 *									evaluated. Otherwise all samples will be evaluated.
 		 * @return							Requested variation of the material.
 		 * @return							Requested variation of the material.
 		 */
 		 */
-		static PointLightMat* getVariation(bool inside, bool msaa, bool singleSampleMSAA = false);
+		static DeferredPointLightMat* getVariation(bool inside, bool msaa, bool singleSampleMSAA = false);
 	private:
 	private:
 		GBufferParams mGBufferParams;
 		GBufferParams mGBufferParams;
 		GpuParamTexture mLightOcclusionTexParam;
 		GpuParamTexture mLightOcclusionTexParam;
 	};
 	};
 
 
+	BS_PARAM_BLOCK_BEGIN(PerProbeParamDef)
+		BS_PARAM_BLOCK_ENTRY(Vector3, gPosition)
+		BS_PARAM_BLOCK_ENTRY(Vector3, gExtents)
+		BS_PARAM_BLOCK_ENTRY(float, gTransitionDistance)
+		BS_PARAM_BLOCK_ENTRY(Matrix4, gInvBoxTransform)
+		BS_PARAM_BLOCK_ENTRY(INT32, gCubemapIdx)
+		BS_PARAM_BLOCK_ENTRY(INT32, gType)
+	BS_PARAM_BLOCK_END
+
+	extern PerProbeParamDef gPerProbeParamDef;
+
+	/** 
+	 * Shader that prepares the surface for image based lighting. 
+	 * 
+	 * This is an alternative to TiledDeferredImageBasedLighting for cases when compute shaders are not usable or suitable. 
+	 * Needs to be followed by execution of all other DeferredIBL* materials.
+	 */
+	class DeferredIBLSetupMat : public RendererMaterial<DeferredIBLSetupMat>
+	{
+		RMAT_DEF("DeferredIBLSetup.bsl");
+
+		/** Helper method used for initializing variations of this material. */
+		template<bool msaa, bool singleSampleMSAA>
+		static const ShaderVariation& getVariation()
+		{
+			static ShaderVariation variation = ShaderVariation({
+				ShaderVariation::Param("MSAA", msaa),
+				ShaderVariation::Param("MSAA_RESOLVE_0TH", singleSampleMSAA)
+			});
+
+			return variation;
+		}
+	public:
+		DeferredIBLSetupMat();
+
+		/** Binds the material for rendering and sets up any parameters. */
+		void bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera,
+			const SPtr<Texture>& ssr, const SPtr<Texture>& ao, const SPtr<GpuParamBlockBuffer>& reflProbeParams);
+
+		/** 
+		 * Returns the material variation matching the provided parameters. 
+		 * 
+		 * @param[in]	msaa				True if the shader will operate on a multisampled surface.
+		 * @param[in]	singleSampleMSAA	Only relevant of @p msaa is true. When enabled only the first sample will be
+		 *									evaluated. Otherwise all samples will be evaluated.
+		 * @return							Requested variation of the material.
+		 */
+		static DeferredIBLSetupMat* getVariation(bool msaa, bool singleSampleMSAA = false);
+	private:
+		GBufferParams mGBufferParams;
+		ImageBasedLightingParams mIBLParams;
+	};
+
+	/** 
+	 * Shader that renders an individual reflection probe for image based lighting. 
+	 * 
+	 * This is an alternative to TiledDeferredImageBasedLighting for cases when compute shaders are not usable or suitable. 
+	 * Must be preceeded by DeferredIBLSetupMat and followed by DeferredIBLSkyMat and DeferredIBLFinalizeMat. 
+	 */
+	class DeferredIBLProbeMat : public RendererMaterial<DeferredIBLProbeMat>
+	{
+		RMAT_DEF("DeferredIBLProbe.bsl");
+
+		/** Helper method used for initializing variations of this material. */
+		template<bool inside, bool msaa, bool singleSampleMSAA>
+		static const ShaderVariation& getVariation()
+		{
+			static ShaderVariation variation = ShaderVariation({
+				ShaderVariation::Param("MSAA", msaa),
+				ShaderVariation::Param("INSIDE_GEOMETRY", inside),
+				ShaderVariation::Param("MSAA_RESOLVE_0TH", singleSampleMSAA)
+			});
+
+			return variation;
+		}
+	public:
+		DeferredIBLProbeMat();
+
+		/** Binds the material for rendering and sets up any parameters. */
+		void bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera,
+			const SceneInfo& sceneInfo, const ReflProbeData& probeData, const SPtr<GpuParamBlockBuffer>& reflProbeParams);
+
+		/** 
+		 * Returns the material variation matching the provided parameters. 
+		 * 
+		 * @param[in]	inside				Set to true if viewer is inside the probe's stencil geometry.
+		 * @param[in]	msaa				True if the shader will operate on a multisampled surface.
+		 * @param[in]	singleSampleMSAA	Only relevant of @p msaa is true. When enabled only the first sample will be
+		 *									evaluated. Otherwise all samples will be evaluated.
+		 * @return							Requested variation of the material.
+		 */
+		static DeferredIBLProbeMat* getVariation(bool inside, bool msaa, bool singleSampleMSAA = false);
+	private:
+		SPtr<GpuParamBlockBuffer> mParamBuffer;
+		GBufferParams mGBufferParams;
+		ImageBasedLightingParams mIBLParams;
+	};
+
+	/** 
+	 * Shader that renders the sky reflections. The results are additively blended with the currently bound render target.
+	 * 
+	 * This is an alternative to TiledDeferredImageBasedLighting for cases when compute shaders are not usable or suitable.
+	 * Must be preceeded by DeferredIBLSetupMat and followed by DeferredIBLFinalizeMat. 
+	 */
+	class DeferredIBLSkyMat : public RendererMaterial<DeferredIBLSkyMat>
+	{
+		RMAT_DEF("DeferredIBLSky.bsl");
+
+		/** Helper method used for initializing variations of this material. */
+		template<bool msaa, bool singleSampleMSAA>
+		static const ShaderVariation& getVariation()
+		{
+			static ShaderVariation variation = ShaderVariation({
+				ShaderVariation::Param("MSAA", msaa),
+				ShaderVariation::Param("MSAA_RESOLVE_0TH", singleSampleMSAA)
+			});
+
+			return variation;
+		}
+	public:
+		DeferredIBLSkyMat();
+
+		/** Binds the material for rendering and sets up any parameters. */
+		void bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera, 
+			const Skybox* skybox, const SPtr<GpuParamBlockBuffer>& reflProbeParams);
+
+		/** 
+		 * Returns the material variation matching the provided parameters. 
+		 * 
+		 * @param[in]	msaa				True if the shader will operate on a multisampled surface.
+		 * @param[in]	singleSampleMSAA	Only relevant of @p msaa is true. When enabled only the first sample will be
+		 *									evaluated. Otherwise all samples will be evaluated.
+		 * @return							Requested variation of the material.
+		 */
+		static DeferredIBLSkyMat* getVariation(bool msaa, bool singleSampleMSAA = false);
+	private:
+		GBufferParams mGBufferParams;
+		ImageBasedLightingParams mIBLParams;
+	};
+
+	/** 
+	 * Material that finalizes the rendering of reflections. As input it takes the texture output by previous DeferredIBL*
+	 * materials, and the resulting output is blended additively with the current render target. 
+	 * 
+	 * This is an alternative to TiledDeferredImageBasedLighting for cases when compute shaders are not usable or suitable.
+	 */
+	class DeferredIBLFinalizeMat : public RendererMaterial<DeferredIBLFinalizeMat>
+	{
+		RMAT_DEF("DeferredIBLFinalize.bsl");
+
+		/** Helper method used for initializing variations of this material. */
+		template<bool msaa, bool singleSampleMSAA>
+		static const ShaderVariation& getVariation()
+		{
+			static ShaderVariation variation = ShaderVariation({
+				ShaderVariation::Param("MSAA", msaa),
+				ShaderVariation::Param("MSAA_RESOLVE_0TH", singleSampleMSAA)
+			});
+
+			return variation;
+		}
+	public:
+		DeferredIBLFinalizeMat();
+
+		/** Binds the material for rendering and sets up any parameters. */
+		void bind(const GBufferTextures& gBufferInput, const SPtr<GpuParamBlockBuffer>& perCamera, 
+			const SPtr<Texture>& iblRadiance, const SPtr<Texture>& preintegratedBrdf, 
+			const SPtr<GpuParamBlockBuffer>& reflProbeParams);
+
+		/** 
+		 * Returns the material variation matching the provided parameters. 
+		 * 
+		 * @param[in]	msaa				True if the shader will operate on a multisampled surface.
+		 * @param[in]	singleSampleMSAA	Only relevant of @p msaa is true. When enabled only the first sample will be
+		 *									evaluated. Otherwise all samples will be evaluated.
+		 * @return							Requested variation of the material.
+		 */
+		static DeferredIBLFinalizeMat* getVariation(bool msaa, bool singleSampleMSAA = false);
+	private:
+		GBufferParams mGBufferParams;
+		ImageBasedLightingParams mIBLParams;
+		GpuParamTexture mIBLRadiance;
+	};
+
 	/** Provides functionality for standard (non-tiled) deferred rendering. */
 	/** Provides functionality for standard (non-tiled) deferred rendering. */
 	class StandardDeferred : public Module<StandardDeferred>
 	class StandardDeferred : public Module<StandardDeferred>
 	{
 	{
@@ -108,6 +293,15 @@ namespace bs { namespace ct {
 		void renderLight(LightType type, const RendererLight& light, const RendererView& view, 
 		void renderLight(LightType type, const RendererLight& light, const RendererView& view, 
 			const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion);
 			const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion);
 
 
+		/** 
+		 * Evaluates filtered radiance from a single reflection probe and blends it into the current render target. 
+		 * Alpha value of the render target is used for determining the contribution and will be updated with new
+		 * contibution after blending.
+		 */
+		void renderReflProbe(const ReflProbeData& probeData, const RendererView& view, 
+			const GBufferTextures& gBufferInput, const SceneInfo& sceneInfo, 
+			const SPtr<GpuParamBlockBuffer>& reflProbeParams);
+
 	private:
 	private:
 		SPtr<GpuParamBlockBuffer> mPerLightBuffer;
 		SPtr<GpuParamBlockBuffer> mPerLightBuffer;
 	};
 	};