فهرست منبع

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"
         }
     ],
+    "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": [
         {
             "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",
             "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": [

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

@@ -42,7 +42,9 @@ mixin ImageBasedLighting
 		Texture2D gPreintegratedEnvBRDF;
 		SamplerState gPreintegratedEnvBRDFSamp;
 		
-		StructuredBuffer<ReflProbeData> gReflectionProbes;	
+		#ifndef STANDARD_DEFERRED
+		StructuredBuffer<ReflProbeData> gReflectionProbes;
+		#endif
 
 		#if USE_COMPUTE_INDICES
 			groupshared uint gReflectionProbeIndices[MAX_PROBES];
@@ -144,6 +146,43 @@ mixin ImageBasedLighting
 			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)
 		{
 			if(gUseReflectionMaps == 0)
@@ -160,31 +199,10 @@ mixin ImageBasedLighting
 						
 				uint probeIdx = gReflectionProbeIndices[probeOffset + i];
 				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)
@@ -198,13 +216,8 @@ mixin ImageBasedLighting
 			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
 			float3 N = surfaceData.worldNormal.xyz;
@@ -228,6 +241,7 @@ mixin ImageBasedLighting
 			
 			float2 envBRDF = gPreintegratedEnvBRDF.SampleLevel(gPreintegratedEnvBRDFSamp, float2(NoV, surfaceData.roughness), 0).rg;
 			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 "Material/BsShader.h"
 #include "Renderer/BsIBLUtility.h"
+#include "Math/BsAABox.h"
 
 namespace bs { namespace ct
 {
@@ -42,7 +43,27 @@ namespace bs { namespace ct
 			ShapeMeshes3D::solidSphere(localSphere, positionData, nullptr, nullptr, 0,
 				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);
 
-		/** 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
@@ -219,7 +222,8 @@ namespace bs { namespace ct
 
 	private:
 		SPtr<Mesh> mFullScreenQuadMesh;
-		SPtr<Mesh> mPointLightStencilMesh;
+		SPtr<Mesh> mUnitSphereStencilMesh;
+		SPtr<Mesh> mUnitBoxStencilMesh;
 		SPtr<Mesh> mSpotLightStencilMesh;
 		SPtr<Mesh> mSkyBoxMesh;
 	};

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

@@ -468,32 +468,32 @@ namespace bs
 		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)
 	{
+		mReflProbeData.clear();
+
 		const VisibilityInfo& visibility = viewGroup.getVisibilityInfo();
 
 		// Generate refl. probe data for the visible ones
@@ -33,8 +35,8 @@ namespace bs { namespace ct
 			if (!visibility.reflProbes[i])
 				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
@@ -44,9 +46,9 @@ namespace bs { namespace ct
 			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
 		UINT32 size = mNumProbes * sizeof(ReflProbeData);
@@ -72,9 +74,7 @@ namespace bs { namespace ct
 		}
 
 		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)
@@ -105,7 +105,7 @@ namespace bs { namespace ct
 	}
 
 	void ImageBasedLightingParams::populate(const SPtr<GpuParams>& params, GpuProgramType programType, bool optional, 
-		bool gridIndices)
+		bool gridIndices, bool probeArray)
 	{
 		// Sky
 		if (!optional || params->hasTexture(programType, "gSkyReflectionTex"))
@@ -115,10 +115,14 @@ namespace bs { namespace ct
 		if (!optional || params->hasTexture(programType, "gReflProbeCubemaps"))
 		{
 			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
 		if (params->hasTexture(programType, "gAmbientOcclusionTex"))
 		{
@@ -160,8 +164,8 @@ namespace bs { namespace ct
 		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;
 		UINT32 skyReflectionsAvailable = 0;
@@ -181,7 +185,7 @@ namespace bs { namespace ct
 
 		gReflProbeParamsParamDef.gSkyCubemapNumMips.set(buffer, numSkyMips);
 		gReflProbeParamsParamDef.gSkyCubemapAvailable.set(buffer, skyReflectionsAvailable);
-		gReflProbeParamsParamDef.gNumProbes.set(buffer, probeData.getNumProbes());
+		gReflProbeParamsParamDef.gNumProbes.set(buffer, numProbes);
 
 		UINT32 numReflProbeMips = 0;
 		if (reflectionCubemaps != nullptr)
@@ -218,7 +222,7 @@ namespace bs { namespace ct
 		mParamBuffer = gTiledImageBasedLightingParamDef.createBuffer();
 		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;
 		desc.minFilter = FO_POINT;
@@ -252,7 +256,7 @@ namespace bs { namespace ct
 		framebufferSize[1] = height;
 		gTiledImageBasedLightingParamDef.gFramebufferSize.set(mParamBuffer, framebufferSize);
 
-		mReflProbeParamBuffer.populate(sceneInfo.skybox, probeData, sceneInfo.reflProbeCubemapsTex, 
+		mReflProbeParamBuffer.populate(sceneInfo.skybox, probeData.getNumProbes(), sceneInfo.reflProbeCubemapsTex, 
 			viewProps.renderingReflections);
 
 		mParamBuffer->flushToGPU();

+ 11 - 8
Source/RenderBeast/BsImageBasedLighting.h

@@ -27,7 +27,7 @@ namespace bs { namespace ct
 		float transitionDistance;
 		Matrix4 invBoxTransform;
 		UINT32 cubemapIdx;
-		UINT32 type;
+		UINT32 type; // 0 - Sphere, 1 - Box
 		Vector2 padding;
 	};
 
@@ -49,12 +49,13 @@ namespace bs { namespace ct
 		/** Returns the number of reflection probes in the probe buffer. */
 		UINT32 getNumProbes() const { return mNumProbes; }
 
+		/** Returns information about a probe at the specified index. */
+		const ReflProbeData& getProbeData(UINT32 idx) const { return mReflProbeData[idx]; }
+
 	private:
+		Vector<ReflProbeData> mReflProbeData;
 		SPtr<GpuBuffer> mProbeBuffer;
 		UINT32 mNumProbes;
-
-		// Helper to avoid memory allocations
-		Vector<ReflProbeData> mReflProbeDataTemp;
 	};
 
 	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]	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]	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 ambientOcclusionTexParam;
@@ -122,9 +125,9 @@ namespace bs { namespace ct
 	{
 		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;
 	};

+ 1 - 1
Source/RenderBeast/BsObjectRendering.cpp

@@ -62,7 +62,7 @@ namespace bs { namespace ct
 		if (gpuParams->hasBuffer(GPT_FRAGMENT_PROGRAM, "gGridProbeOffsetsAndSize"))
 			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"))
 			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<RCNodeSceneColor>();
 		RenderCompositor::registerNodeType<RCNodeStandardDeferredLighting>();
+		RenderCompositor::registerNodeType<RCNodeStandardDeferredIBL>();
 		RenderCompositor::registerNodeType<RCNodeTiledDeferredLighting>();
 		RenderCompositor::registerNodeType<RCNodeTiledDeferredIBL>();
 		RenderCompositor::registerNodeType<RCNodeUnflattenLightAccum>();

+ 297 - 37
Source/RenderBeast/BsRenderCompositor.cpp

@@ -358,14 +358,20 @@ namespace bs { namespace ct
 		UINT32 height = viewProps.viewRect.height;
 		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
-		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]);
 		SPtr<PooledRenderTexture> sceneDepthTex = sceneDepthNode->depthTex;
 
-		if (viewProps.numSamples > 1)
+		if (tiledDeferredSupported && viewProps.numSamples > 1)
 		{
 			UINT32 bufferNumElements = width * height * viewProps.numSamples;
 			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)
 	{
+		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();
 		const RendererViewProperties& viewProps = inputs.view.getProperties();
 
@@ -513,12 +532,20 @@ namespace bs { namespace ct
 
 			renderTarget = RenderTexture::create(lightAccumulationRTDesc);
 		}
+
+		mOwnsTexture = true;
 	}
 
 	void RCNodeLightAccumulation::clear()
 	{
 		GpuResourcePool& resPool = GpuResourcePool::instance();
-		resPool.release(lightAccumulationTex);
+		if(mOwnsTexture)
+			resPool.release(lightAccumulationTex);
+		else
+		{
+			lightAccumulationTex = nullptr;
+			renderTarget = nullptr;
+		}
 
 		if (flattenedLightAccumBuffer)
 			resPool.release(flattenedLightAccumBuffer);
@@ -526,7 +553,15 @@ namespace bs { namespace ct
 
 	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)
@@ -581,14 +616,27 @@ namespace bs { namespace ct
 
 	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;
-			return;
 		}
 
 		GpuResourcePool& resPool = GpuResourcePool::instance();
@@ -598,8 +646,40 @@ namespace bs { namespace ct
 		UINT32 height = viewProps.viewRect.height;
 		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
 		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);
 		}
 
-		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();
-
-		RenderAPI& rapi = RenderAPI::instance();
 		for (UINT32 i = 0; i < (UINT32)LightType::Count; i++)
 		{
 			LightType lightType = (LightType)i;
@@ -661,7 +733,7 @@ namespace bs { namespace ct
 				const RendererLight& light = *lights[lightIdx];
 				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,
 					lightOcclusionTex->texture);
 			}
@@ -675,23 +747,192 @@ namespace bs { namespace ct
 
 	void RCNodeStandardDeferredLighting::clear()
 	{
-		output = nullptr;
+		// Do nothing
 	}
 
 	SmallVector<StringID, 4> RCNodeStandardDeferredLighting::getDependencies(const RendererView& view)
 	{
 		SmallVector<StringID, 4> deps;
 
-		deps.push_back(RCNodeTiledDeferredLighting::getNodeId());
 		deps.push_back(RCNodeGBuffer::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;
 	}
 
+	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)
 	{
 		RCNodeLightAccumulation* lightAccumNode = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[0]);
@@ -800,13 +1041,21 @@ namespace bs { namespace ct
 		deps.push_back(RCNodeGBuffer::getNodeId());
 		deps.push_back(RCNodeSceneDepth::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)
 			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;
 	}
@@ -949,7 +1198,7 @@ namespace bs { namespace ct
 
 		// Prepare refl. probe param buffer
 		ReflProbeParamBuffer reflProbeParamBuffer;
-		reflProbeParamBuffer.populate(sceneInfo.skybox, visibleReflProbeData, sceneInfo.reflProbeCubemapsTex, 
+		reflProbeParamBuffer.populate(sceneInfo.skybox, visibleReflProbeData.getNumProbes(), sceneInfo.reflProbeCubemapsTex, 
 			viewProps.renderingReflections);
 
 		SPtr<Texture> skyFilteredRadiance;
@@ -1072,7 +1321,7 @@ namespace bs { namespace ct
 			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;
 
 		RenderAPI& rapi = RenderAPI::instance();
@@ -1090,13 +1339,20 @@ namespace bs { namespace ct
 
 	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());
 
-		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;
 	}
@@ -2014,8 +2270,12 @@ namespace bs { namespace ct
 			deps.push_back(RCNodeHiZ::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;

+ 21 - 4
Source/RenderBeast/BsRenderCompositor.h

@@ -292,6 +292,8 @@ namespace ct
 
 		/** @copydoc RenderCompositorNode::clear */
 		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.
-	 * 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
 	{
 	public:
-		// Outputs
-		RCNodeLightAccumulation* output;
-
 		static StringID getNodeId() { return "StandardDeferredLighting"; }
 		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
 	protected:
@@ -336,6 +336,23 @@ namespace ct
 		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
 	 * 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);
 				mat->bind(shadowParams);
 
-				gRendererUtility().draw(gRendererUtility().getRadialLightStencil());
+				gRendererUtility().draw(gRendererUtility().getSphereStencil());
 			}
 		}
 		else // Directional & spot

+ 262 - 11
Source/RenderBeast/BsStandardDeferredLighting.cpp

@@ -5,17 +5,20 @@
 #include "BsRendererView.h"
 #include "Material/BsGpuParamsSet.h"
 #include "Mesh/BsMesh.h"
+#include "Renderer/BsSkybox.h"
+#include "BsRendererScene.h"
+#include "Renderer/BsReflectionProbe.h"
 
 namespace bs { namespace ct {
 	PerLightParamDef gPerLightParamDef;
 
-	DirectionalLightMat::DirectionalLightMat()
+	DeferredDirectionalLightMat::DeferredDirectionalLightMat()
 		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
 	{
 		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)
 	{
 		mGBufferParams.bind(gBufferInput);
@@ -26,7 +29,7 @@ namespace bs { namespace ct {
 		RendererMaterial::bind();
 	}
 
-	DirectionalLightMat* DirectionalLightMat::getVariation(bool msaa, bool singleSampleMSAA)
+	DeferredDirectionalLightMat* DeferredDirectionalLightMat::getVariation(bool msaa, bool singleSampleMSAA)
 	{
 		if (msaa)
 		{
@@ -39,13 +42,13 @@ namespace bs { namespace ct {
 		return get(getVariation<false, false>());
 	}
 
-	PointLightMat::PointLightMat()
+	DeferredPointLightMat::DeferredPointLightMat()
 		:mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
 	{
 		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)
 	{
 		mGBufferParams.bind(gBufferInput);
@@ -56,7 +59,7 @@ namespace bs { namespace ct {
 		RendererMaterial::bind();
 	}
 
-	PointLightMat* PointLightMat::getVariation(bool inside, bool msaa, bool singleSampleMSAA)
+	DeferredPointLightMat* DeferredPointLightMat::getVariation(bool inside, bool msaa, bool singleSampleMSAA)
 	{
 		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()
 	{
 		mPerLightBuffer = gPerLightParamDef.createBuffer();
@@ -101,7 +304,7 @@ namespace bs { namespace ct {
 
 		if (lightType == LightType::Directional)
 		{
-			DirectionalLightMat* material = DirectionalLightMat::getVariation(isMSAA, true);
+			DeferredDirectionalLightMat* material = DeferredDirectionalLightMat::getVariation(isMSAA, true);
 			material->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 
 			gRendererUtility().drawScreenQuad();
@@ -109,7 +312,7 @@ namespace bs { namespace ct {
 			// Draw pixels requiring per-sample evaluation
 			if(isMSAA)
 			{
-				DirectionalLightMat* msaaMaterial = DirectionalLightMat::getVariation(true, false);
+				DeferredDirectionalLightMat* msaaMaterial = DeferredDirectionalLightMat::getVariation(true, false);
 				msaaMaterial->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 
 				gRendererUtility().drawScreenQuad();
@@ -129,11 +332,11 @@ namespace bs { namespace ct {
 
 			SPtr<Mesh> stencilMesh;
 			if(lightType == LightType::Radial)
-				stencilMesh = RendererUtility::instance().getRadialLightStencil();
+				stencilMesh = RendererUtility::instance().getSphereStencil();
 			else // Spot
 				stencilMesh = RendererUtility::instance().getSpotLightStencil();
 
-			PointLightMat* material = PointLightMat::getVariation(isInside, isMSAA, true);
+			DeferredPointLightMat* material = DeferredPointLightMat::getVariation(isInside, isMSAA, true);
 			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
@@ -142,11 +345,59 @@ namespace bs { namespace ct {
 			// Draw pixels requiring per-sample evaluation
 			if(isMSAA)
 			{
-				PointLightMat* msaaMaterial = PointLightMat::getVariation(isInside, true, false);
+				DeferredPointLightMat* msaaMaterial = DeferredPointLightMat::getVariation(isInside, true, false);
 				msaaMaterial->bind(gBufferInput, lightOcclusion, perViewBuffer, mPerLightBuffer);
 
 				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 "Renderer/BsRendererMaterial.h"
 #include "BsLightRendering.h"
+#include "BsImageBasedLighting.h"
 
 namespace bs { namespace ct {
 	class RendererLight;
@@ -23,7 +24,7 @@ namespace bs { namespace ct {
 	extern PerLightParamDef gPerLightParamDef;
 
 	/** 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");
 
@@ -39,7 +40,7 @@ namespace bs { namespace ct {
 			return variation;
 		}
 	public:
-		DirectionalLightMat();
+		DeferredDirectionalLightMat();
 
 		/** Binds the material for rendering and sets up any parameters. */
 		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
@@ -53,14 +54,14 @@ namespace bs { namespace ct {
 		 *									evaluated. Otherwise all samples will be evaluated.
 		 * @return							Requested variation of the material.
 		 */
-		static DirectionalLightMat* getVariation(bool msaa, bool singleSampleMSAA = false);
+		static DeferredDirectionalLightMat* getVariation(bool msaa, bool singleSampleMSAA = false);
 	private:
 		GBufferParams mGBufferParams;
 		GpuParamTexture mLightOcclusionTexParam;
 	};
 
 	/** 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");
 
@@ -77,7 +78,7 @@ namespace bs { namespace ct {
 			return variation;
 		}
 	public:
-		PointLightMat();
+		DeferredPointLightMat();
 
 		/** Binds the material for rendering and sets up any parameters. */
 		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
@@ -92,12 +93,196 @@ namespace bs { namespace ct {
 		 *									evaluated. Otherwise all samples will be evaluated.
 		 * @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:
 		GBufferParams mGBufferParams;
 		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. */
 	class StandardDeferred : public Module<StandardDeferred>
 	{
@@ -108,6 +293,15 @@ namespace bs { namespace ct {
 		void renderLight(LightType type, const RendererLight& light, const RendererView& view, 
 			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:
 		SPtr<GpuParamBlockBuffer> mPerLightBuffer;
 	};