Explorar el Código

Light probe indirect lighting mostly looked up (but untested)

BearishSun hace 8 años
padre
commit
180a3d46f1
Se han modificado 31 ficheros con 813 adiciones y 209 borrados
  1. 4 0
      Data/Raw/Engine/DataList.json
  2. 1 9
      Data/Raw/Engine/Includes/ImageBasedLighting.bslinc
  3. 72 2
      Data/Raw/Engine/Includes/SHCommon.bslinc
  4. 137 0
      Data/Raw/Engine/Shaders/IrradianceEvaluate.bsl
  5. 0 2
      Data/Raw/Engine/Shaders/TiledDeferredImageBasedLighting.bsl
  6. 0 2
      Data/Raw/Engine/Shaders/Transparent.bsl
  7. 3 0
      Source/BansheeCore/Include/BsRenderSettings.h
  8. 1 0
      Source/BansheeCore/Include/BsRenderSettingsRTTI.h
  9. 2 1
      Source/BansheeCore/Include/BsRenderer.h
  10. 1 6
      Source/BansheeCore/Source/BsLightProbeVolume.cpp
  11. 4 1
      Source/BansheeCore/Source/BsRenderSettings.cpp
  12. 1 4
      Source/RenderBeast/Include/BsImageBasedLighting.h
  13. 84 11
      Source/RenderBeast/Include/BsLightProbes.h
  14. 3 3
      Source/RenderBeast/Include/BsLightRendering.h
  15. 2 2
      Source/RenderBeast/Include/BsPostProcessing.h
  16. 1 1
      Source/RenderBeast/Include/BsRenderBeast.h
  17. 17 1
      Source/RenderBeast/Include/BsRenderCompositor.h
  18. 19 0
      Source/RenderBeast/Include/BsRendererScene.h
  19. 3 0
      Source/RenderBeast/Include/BsRendererTextures.h
  20. 3 3
      Source/RenderBeast/Include/BsShadowRendering.h
  21. 3 3
      Source/RenderBeast/Include/BsStandardDeferredLighting.h
  22. 0 5
      Source/RenderBeast/Source/BsImageBasedLighting.cpp
  23. 259 131
      Source/RenderBeast/Source/BsLightProbes.cpp
  24. 2 2
      Source/RenderBeast/Source/BsLightRendering.cpp
  25. 2 2
      Source/RenderBeast/Source/BsPostProcessing.cpp
  26. 10 4
      Source/RenderBeast/Source/BsRenderBeast.cpp
  27. 89 9
      Source/RenderBeast/Source/BsRenderCompositor.cpp
  28. 20 0
      Source/RenderBeast/Source/BsRendererScene.cpp
  29. 66 1
      Source/RenderBeast/Source/BsRendererTextures.cpp
  30. 1 1
      Source/RenderBeast/Source/BsShadowRendering.cpp
  31. 3 3
      Source/RenderBeast/Source/BsStandardDeferredLighting.cpp

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

@@ -349,6 +349,10 @@
         {
             "Path": "TetrahedraRender.bsl",
             "UUID": "5912b0ab-b2be-4c0e-a039-91fcc2d3e536"
+        },
+        {
+            "Path": "IrradianceEvaluate.bsl",
+            "UUID": "6f6ed59d-b94b-428e-91bc-82c94ed48892"
         }
     ],
     "Skin": [

+ 1 - 9
Data/Raw/Engine/Includes/ImageBasedLighting.bslinc

@@ -25,9 +25,6 @@ mixin ImageBasedLighting
 		TextureCube gSkyReflectionTex;
 		SamplerState gSkyReflectionSamp;
 		
-		TextureCube gSkyIrradianceTex;
-		SamplerState gSkyIrradianceSamp;
-		
 		TextureCubeArray gReflProbeCubemaps;
 		SamplerState gReflProbeSamp;
 		
@@ -52,12 +49,7 @@ mixin ImageBasedLighting
 			uint gSkyCubemapNumMips;
 			float gSkyBrightness;
 		}	
-		
-		float3 getSkyIndirectDiffuse(float3 dir)
-		{
-			return gSkyIrradianceTex.SampleLevel(gSkyIrradianceSamp, dir, 0).rgb * gSkyBrightness;
-		}
-		
+
 		float getSphereReflectionContribution(float normalizedDistance)
 		{			
 			// If closer than 60% to the probe radius, then full contribution is used.

+ 72 - 2
Data/Raw/Engine/Includes/SHCommon.bslinc

@@ -45,12 +45,26 @@ mixin SHCommon
 			v.v6 = 0;
 		}
 		
+		void SHZero(inout SHVector5RGB v)
+		{
+			SHZero(v.R);
+			SHZero(v.G);
+			SHZero(v.B);
+		}				
+		
 		void SHZero(inout SHVector3 v)
 		{
 			v.v0 = 0;
 			v.v1 = 0;
 			v.v2 = 0;
-		}	
+		}
+		
+		void SHZero(inout SHVector3RGB v)
+		{
+			SHZero(v.R);
+			SHZero(v.G);
+			SHZero(v.B);
+		}		
 
 		void SHMultiplyAdd(inout SHVector5 lhs, SHVector5 rhs, float c)
 		{
@@ -70,6 +84,20 @@ mixin SHCommon
 			lhs.v2 += rhs.v2 * c;
 		}		
 		
+		void SHMultiplyAdd(inout SHVector5RGB lhs, SHVector5RGB rhs, float c)
+		{
+			SHMultiplyAdd(lhs.R, rhs.R, c);
+			SHMultiplyAdd(lhs.G, rhs.G, c);
+			SHMultiplyAdd(lhs.B, rhs.B, c);
+		}
+		
+		void SHMultiplyAdd(inout SHVector3RGB lhs, SHVector3RGB rhs, float c)
+		{
+			SHMultiplyAdd(lhs.R, rhs.R, c);
+			SHMultiplyAdd(lhs.G, rhs.G, c);
+			SHMultiplyAdd(lhs.B, rhs.B, c);
+		}			
+		
 		void SHAdd(inout SHVector5 lhs, SHVector5 rhs)
 		{
 			lhs.v0 += rhs.v0;
@@ -86,6 +114,20 @@ mixin SHCommon
 			lhs.v0 += rhs.v0;
 			lhs.v1 += rhs.v1;
 			lhs.v2 += rhs.v2;
+		}
+		
+		void SHAdd(inout SHVector5RGB lhs, SHVector5RGB rhs)
+		{
+			SHAdd(lhs.R, rhs.R);
+			SHAdd(lhs.G, rhs.G);
+			SHAdd(lhs.B, rhs.B);
+		}
+		
+		void SHAdd(inout SHVector3RGB lhs, SHVector3RGB rhs)
+		{
+			SHAdd(lhs.R, rhs.R);
+			SHAdd(lhs.G, rhs.G);
+			SHAdd(lhs.B, rhs.B);
 		}		
 		
 		void SHMultiply(inout SHVector5 lhs, SHVector5 rhs)
@@ -104,7 +146,21 @@ mixin SHCommon
 			lhs.v0 *= rhs.v0;
 			lhs.v1 *= rhs.v1;
 			lhs.v2 *= rhs.v2;
-		}		
+		}
+		
+		void SHMultiply(inout SHVector5RGB lhs, SHVector5RGB rhs)
+		{
+			SHMultiply(lhs.R, rhs.R);
+			SHMultiply(lhs.G, rhs.G);
+			SHMultiply(lhs.B, rhs.B);
+		}	
+		
+		void SHMultiply(inout SHVector3RGB lhs, SHVector3RGB rhs)
+		{
+			SHMultiply(lhs.R, rhs.R);
+			SHMultiply(lhs.G, rhs.G);
+			SHMultiply(lhs.B, rhs.B);
+		}	
 		
 		void SHMultiply(inout SHVector5 lhs, float rhs)
 		{
@@ -122,6 +178,20 @@ mixin SHCommon
 			lhs.v0 *= rhs;
 			lhs.v1 *= rhs;
 			lhs.v2 *= rhs;
+		}
+
+		void SHMultiply(inout SHVector5RGB lhs, float rhs)
+		{
+			SHMultiply(lhs.R, rhs);
+			SHMultiply(lhs.G, rhs);
+			SHMultiply(lhs.B, rhs);
+		}	
+		
+		void SHMultiply(inout SHVector3RGB lhs, float rhs)
+		{
+			SHMultiply(lhs.R, rhs);
+			SHMultiply(lhs.G, rhs);
+			SHMultiply(lhs.B, rhs);
 		}		
 		
 		SHVector5 SHBasis5(float3 dir)

+ 137 - 0
Data/Raw/Engine/Shaders/IrradianceEvaluate.bsl

@@ -0,0 +1,137 @@
+#include "$ENGINE$\PPBase.bslinc"
+#include "$ENGINE$\SHCommon.bslinc"
+#include "$ENGINE$\GBufferInput.bslinc"
+#include "$ENGINE$\PerCameraData.bslinc"
+
+technique IrradianceEvaluate
+{
+	mixin PPBase;
+	mixin SHCommon;
+	mixin GBufferInput;
+	mixin PerCameraData;
+
+	blend
+	{
+		target	
+		{
+			enabled = true;
+			color = { one, one, add };
+		};
+	};	
+	
+	code
+	{
+		#if MSAA_COUNT > 1
+			Texture2DMS<uint> gInputTex;
+		#else
+			Texture2D<uint> gInputTex;
+		#endif
+		
+		struct ProbeVolume
+		{
+			uint4 indices;
+			float3x4 transform;
+		};
+		
+		StructuredBuffer<SHVector3RGB> gSHCoeffs;
+		StructuredBuffer<ProbeVolume> gProbeVolumes;
+		
+		TextureCube gSkyIrradianceTex;
+		SamplerState gSkyIrradianceSamp;
+
+		cbuffer Params
+		{
+			float gSkyBrightness;
+		}				
+		
+		float3 getSkyIndirectDiffuse(float3 dir)
+		{
+			return gSkyIrradianceTex.SampleLevel(gSkyIrradianceSamp, dir, 0).rgb * gSkyBrightness;
+		}
+		
+		float evaluateLambert(SHVector3 coeffs)
+		{
+			// Multiply irradiance SH coefficients by cosine lobe (Lambert diffuse) and evaluate resulting SH
+			// See: http://cseweb.ucsd.edu/~ravir/papers/invlamb/josa.pdf for derivation of the
+			// cosine lobe factors
+			float output = 0.0f;
+			
+			// Band 0 (factor 1.0)
+			output += coeffs.v0[0];
+			
+			// Band 1 (factor 2/3)
+			float f = (2.0f/3.0f);
+			float4 f4 = float4(f, f, f, f);
+			
+			output += dot(coeffs.v0.gba, f4.rgb);
+			
+			// Band 2 (factor 1/4)
+			f = (1.0f/4.0f);
+			f4 = float4(f, f, f, f);
+			
+			output += dot(coeffs.v1, f4);
+			output += coeffs.v2 * f;
+						
+			return output;
+		}		
+		
+		float3 fsmain(VStoFS input
+			#if MSAA_COUNT > 1
+			, uint sampleIdx : SV_SampleIndex
+			#endif
+			) : SV_Target0
+		{
+			uint2 pixelPos = trunc(input.uv0);
+		
+			SurfaceData surfaceData;
+			#if MSAA_COUNT > 1
+				surfaceData = getGBufferData(pixelPos, sampleIdx);
+			#else
+				surfaceData = getGBufferData(pixelPos);
+			#endif		
+			
+			float3 radiance;
+			#if SKY_ONLY
+				radiance = gSkyIrradianceTex.SampleLevel(gSkyIrradianceSamp, surfaceData.worldNormal, 0).rgb * gSkyBrightness;
+			#else
+				uint volumeIdx;
+				#if MSAA_COUNT > 1
+					volumeIdx = gInputTex.Load(uint3(pixelPos, 0), sampleIdx).x;
+				#else
+					volumeIdx = gInputTex.Load(uint3(pixelPos, 0)).x;
+				#endif
+				
+				ProbeVolume volume = gProbeVolumes[volumeIdx];
+				
+				float3 P = NDCToWorld(input.screenPos, surfaceData.depth);
+				float3 factors = mul(volume.transform, float4(P, 1.0f));			
+				float4 coords = float4(factors, 1.0f - factors.x - factors.y - factors.z);
+				
+				// Ignore extra points we added to make the volume cover everything
+				coords = volume.indices != -1 ? coords : float4(0, 0, 0, 0);
+				
+				// Renormalize after potential change
+				float sum = coords.x + coords.y + coords.z + coords.w;
+				coords /= sum;
+				
+				SHVector3RGB shCoeffs = gSHCoeffs[volume.indices[0]];
+				
+				SHMultiply(shCoeffs, coords.x);
+				SHMultiplyAdd(shCoeffs, gSHCoeffs[volume.indices[1]], coords.y);
+				SHMultiplyAdd(shCoeffs, gSHCoeffs[volume.indices[2]], coords.z);
+				SHMultiplyAdd(shCoeffs, gSHCoeffs[volume.indices[3]], coords.w);
+				
+				SHVector3 shBasis = SHBasis3(surfaceData.worldNormal);
+				SHMultiply(shCoeffs.R, shBasis);
+				SHMultiply(shCoeffs.G, shBasis);
+				SHMultiply(shCoeffs.B, shBasis);
+				
+				radiance.r = evaluateLambert(shCoeffs.R);
+				radiance.g = evaluateLambert(shCoeffs.G);
+				radiance.b = evaluateLambert(shCoeffs.B);
+			#endif // SKY_ONLY
+			
+			return radiance * surfaceData.albedo.rgb;
+		}	
+	};
+};

+ 0 - 2
Data/Raw/Engine/Shaders/TiledDeferredImageBasedLighting.bsl

@@ -164,11 +164,9 @@ technique TiledDeferredImageBasedLighting
 			existingColor = gInColor.Load(int3(pixelPos.xy, 0));
 			#endif				
 			
-			float3 indirectDiffuse = getSkyIndirectDiffuse(N) * surfaceData.albedo.rgb;
 			float3 imageBasedSpecular = getImageBasedSpecular(worldPosition, V, specR, surfaceData, probeOffset, numProbes);
 
 			float4 totalLighting = existingColor;
-			totalLighting.rgb += indirectDiffuse;
 			totalLighting.rgb += imageBasedSpecular;
 			
 			return totalLighting;				

+ 0 - 2
Data/Raw/Engine/Shaders/Transparent.bsl

@@ -82,12 +82,10 @@ mixin Surface
 			float3 specR = getSpecularDominantDir(N, R, surfaceData.roughness);
 			
 			float4 directLighting = getDirectLighting(input.worldPosition, V, specR, surfaceData, lightOffsets);
-			float3 indirectDiffuse = getSkyIndirectDiffuse(surfaceData.worldNormal) * surfaceData.albedo;
 			float3 imageBasedSpecular = getImageBasedSpecular(input.worldPosition, V, specR, surfaceData, 
 				reflProbeOffsetAndSize.x, reflProbeOffsetAndSize.y);
 
 			float3 totalLighting = directLighting.rgb;
-			totalLighting.rgb += indirectDiffuse;
 			totalLighting.rgb += imageBasedSpecular;
 
 			return float4(totalLighting, gOpacity);

+ 3 - 0
Source/BansheeCore/Include/BsRenderSettings.h

@@ -447,6 +447,9 @@ namespace bs
 		/** Determines if shadows cast by lights should be rendered. Only relevant if lighting is turned on. */
 		bool enableShadows;
 
+		/** Determines if indirect lighting (e.g. from light probes or the sky) is rendered. */
+		bool enableIndirectLighting;
+
 		/** 
 		 * Signals the renderer to only render overlays (like GUI), and not scene objects. Such rendering doesn't require
 		 * depth buffer or multi-sampled render targets and will not render any scene objects. This can improve performance

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

@@ -269,6 +269,7 @@ namespace bs
 			BS_RTTI_MEMBER_PLAIN(enableLighting, 13)
 			BS_RTTI_MEMBER_PLAIN(enableShadows, 14)
 			BS_RTTI_MEMBER_PLAIN(overlayOnly, 15)
+			BS_RTTI_MEMBER_PLAIN(enableIndirectLighting, 16)
 		BS_END_RTTI_MEMBERS
 			
 	public:

+ 2 - 1
Source/BansheeCore/Include/BsRenderer.h

@@ -15,6 +15,7 @@ namespace bs
 	namespace ct
 	{
 	class RendererTask;
+	class LightProbeVolume;
 
 	/** @addtogroup Renderer-Internal
 	 *  @{
@@ -159,7 +160,7 @@ namespace bs
 		 *
 		 * @note	Core thread.
 		 */
-		virtual void notifyLightProbeVolumeUpdated(LightProbeVolume* volume, bool coefficientsUpdated) { }
+		virtual void notifyLightProbeVolumeUpdated(LightProbeVolume* volume) { }
 
 		/**
 		 * Called whenever a light probe volume is destroyed.

+ 1 - 6
Source/BansheeCore/Source/BsLightProbeVolume.cpp

@@ -341,7 +341,7 @@ namespace bs
 				break;
 		}
 
-		gRenderer()->notifyLightProbeVolumeUpdated(this, true);
+		gRenderer()->notifyLightProbeVolumeUpdated(this);
 
 		return mFirstDirtyProbe == (UINT32)mProbeInfos.size();
 	}
@@ -469,11 +469,6 @@ namespace bs
 			else
 				gRenderer()->notifyLightProbeVolumeRemoved(this);
 		}
-		else
-		{
-			if(mIsActive)
-				gRenderer()->notifyLightProbeVolumeUpdated(this, false);
-		}
 	}
 
 	void LightProbeVolume::getProbeCoefficients(Vector<LightProbeCoefficientInfo>& output) const

+ 4 - 1
Source/BansheeCore/Source/BsRenderSettings.cpp

@@ -110,7 +110,7 @@ namespace bs
 
 	RenderSettings::RenderSettings()
 		: enableAutoExposure(true), enableTonemapping(true), enableFXAA(false), exposureScale(0.0f), gamma(2.2f)
-		, enableHDR(true), enableLighting(true), enableShadows(true), overlayOnly(false)
+		, enableHDR(true), enableLighting(true), enableShadows(true), enableIndirectLighting(true), overlayOnly(false)
 	{ }
 
 	RTTITypeBase* RenderSettings::getRTTIStatic()
@@ -134,6 +134,7 @@ namespace bs
 		bufferSize += rttiGetElemSize(enableHDR);
 		bufferSize += rttiGetElemSize(enableLighting);
 		bufferSize += rttiGetElemSize(enableShadows);
+		bufferSize += rttiGetElemSize(enableIndirectLighting);
 		bufferSize += rttiGetElemSize(overlayOnly);
 
 		bufferSize += rttiGetElemSize(autoExposure.histogramLog2Min);
@@ -204,6 +205,7 @@ namespace bs
 		writeDst = rttiWriteElem(enableHDR, writeDst);
 		writeDst = rttiWriteElem(enableLighting, writeDst);
 		writeDst = rttiWriteElem(enableShadows, writeDst);
+		writeDst = rttiWriteElem(enableIndirectLighting, writeDst);
 		writeDst = rttiWriteElem(overlayOnly, writeDst);
 
 		writeDst = rttiWriteElem(autoExposure.histogramLog2Min, writeDst);
@@ -266,6 +268,7 @@ namespace bs
 		readSource = rttiReadElem(enableHDR, readSource);
 		readSource = rttiReadElem(enableLighting, readSource);
 		readSource = rttiReadElem(enableShadows, readSource);
+		readSource = rttiReadElem(enableIndirectLighting, readSource);
 		readSource = rttiReadElem(overlayOnly, readSource);
 
 		readSource = rttiReadElem(autoExposure.histogramLog2Min, readSource);

+ 1 - 4
Source/RenderBeast/Include/BsImageBasedLighting.h

@@ -62,7 +62,6 @@ namespace bs { namespace ct
 		BS_PARAM_BLOCK_ENTRY(INT32, gSkyCubemapAvailable)
 		BS_PARAM_BLOCK_ENTRY(INT32, gUseReflectionMaps)
 		BS_PARAM_BLOCK_ENTRY(INT32, gSkyCubemapNumMips)
-		BS_PARAM_BLOCK_ENTRY(float, gSkyBrightness)
 	BS_PARAM_BLOCK_END
 
 	extern ReflProbeParamsParamDef gReflProbeParamsParamDef;
@@ -102,8 +101,6 @@ namespace bs { namespace ct
 		void populate(const SPtr<GpuParamsSet>& paramsSet, GpuProgramType programType, bool optional, bool gridIndices);
 
 		GpuParamTexture skyReflectionsTexParam;
-		GpuParamTexture skyIrradianceTexParam;
-
 		GpuParamTexture reflectionProbeCubemapsTexParam;
 
 		GpuParamTexture preintegratedEnvBRDFParam;
@@ -134,7 +131,7 @@ namespace bs { namespace ct
 		/** Container for parameters to be passed to the execute() method. */
 		struct Inputs
 		{
-			GBufferInput gbuffer;
+			GBufferTextures gbuffer;
 			SPtr<Texture> lightAccumulation;
 			SPtr<Texture> sceneColorTex;
 			SPtr<GpuBuffer> sceneColorBuffer;

+ 84 - 11
Source/RenderBeast/Include/BsLightProbes.h

@@ -9,9 +9,11 @@
 #include "BsRendererMaterial.h"
 #include "BsGpuResourcePool.h"
 #include "BsParamBlocks.h"
+#include "BsLightRendering.h"
 
 namespace bs { namespace ct
 {
+	struct GBufferTextures;
 	struct FrameInfo;
 	class LightProbeVolume;
 
@@ -66,6 +68,61 @@ namespace bs { namespace ct
 		static ShaderVariation VAR_MSAA;
 	};
 
+	BS_PARAM_BLOCK_BEGIN(IrradianceEvaluateParamDef)
+		BS_PARAM_BLOCK_ENTRY(float, gSkyBrightness)
+	BS_PARAM_BLOCK_END
+
+	extern IrradianceEvaluateParamDef gIrradianceEvaluateParamDef;
+
+	/** Evaluates radiance from the light probe volume, or the sky if light probes are not available. */
+	class IrradianceEvaluateMat : public RendererMaterial<IrradianceEvaluateMat>
+	{
+		RMAT_DEF("IrradianceEvaluate.bsl");
+
+	public:
+		IrradianceEvaluateMat();
+
+		/**
+		 * Executes the material using the provided parameters.
+		 * 
+		 * @param[in]	view				View that is currently being rendered.
+		 * @param[in]	gbuffer				Previously rendered GBuffer textures.
+		 * @param[in]	lightProbeIndices	Indices calculated by TetrahedraRenderMat.
+		 * @param[in]	shCoeffs			Buffer containing spherical harmonic coefficients for every light probe.
+		 * @param[in]	volumes				Buffer containing information about tetrahedra inside the light volume, 
+		 *									including indices of the light probes they reference.
+		 * @param[in]	skybox				Skybox, if available. If sky is not available, but sky rendering is enabled, 
+		 *									the system will instead use a default irradiance texture.
+		 * @param[in]	output				Output texture to write the radiance to. The evaluated value will be added to 
+		 *									existing radiance in the texture, using blending.
+		 */
+		void execute(const RendererView& view, const GBufferTextures& gbuffer, const SPtr<Texture>& lightProbeIndices,
+			const SPtr<GpuBuffer>& shCoeffs, const SPtr<GpuBuffer>& volumes, const Skybox* skybox, 
+			const SPtr<RenderTexture>& output);
+
+		/** 
+		 * Returns the material variation matching the provided parameters. 
+		 *
+		 * @param[in]	msaaCount	Number of MSAA samples used by the view rendering the material.
+		 * @param[in]	skyOnly		When true, only the sky irradiance will be evaluated. Otherwise light probe irradiance
+		 *							will be evaluated.
+		 */
+		static IrradianceEvaluateMat* getVariation(UINT32 msaaCount, bool skyOnly);
+	private:
+		GBufferParams mGBufferParams;
+		SPtr<GpuParamBlockBuffer> mParamBuffer;
+		GpuParamTexture mParamInputTex;
+		GpuParamTexture mParamSkyIrradianceTex;
+		GpuParamBuffer mParamSHCoeffsBuffer;
+		GpuParamBuffer mParamVolumeBuffer;
+		bool mSkyOnly;
+
+		static ShaderVariation VAR_MSAA_Probes;
+		static ShaderVariation VAR_NoMSAA_Probes;
+		static ShaderVariation VAR_MSAA_Sky;
+		static ShaderVariation VAR_NoMSAA_Sky;
+	};
+
 	/** Handles any pre-processing for light (irradiance) probe lighting. */
 	class LightProbes
 	{
@@ -73,7 +130,7 @@ namespace bs { namespace ct
 		struct VolumeInfo
 		{
 			/** Volume containing the information about the probes. */
-			SPtr<LightProbeVolume> volume;
+			LightProbeVolume* volume;
 			/** Remains true as long as there are dirty probes in the volume. */
 			bool isDirty;
 		};
@@ -92,17 +149,40 @@ namespace bs { namespace ct
 		LightProbes();
 
 		/** Notifies sthe manager that the provided light probe volume has been added. */
-		void notifyAdded(const SPtr<LightProbeVolume>& volume);
+		void notifyAdded(LightProbeVolume* volume);
 
 		/** Notifies the manager that the provided light probe volume has some dirty light probes. */
-		void notifyDirty(const SPtr<LightProbeVolume>& volume);
+		void notifyDirty(LightProbeVolume* volume);
 
 		/** Notifies the manager that all the probes in the provided volume have been removed. */
-		void notifyRemoved(const SPtr<LightProbeVolume>& volume);
+		void notifyRemoved(LightProbeVolume* volume);
 
 		/** Updates light probe tetrahedron data after probes changed (added/removed/moved). */
 		void updateProbes();
 
+		/** Returns true if there are any registered light probes. */
+		bool hasAnyProbes() const;
+
+		/** 
+		 * Returns a GPU buffer containing SH coefficients for all active light probes. updateProbes() must be called
+		 * at least once before the buffer is populated. If the probes changed since the last call, call updateProbes()
+		 * to refresh the buffer.
+		 */
+		SPtr<GpuBuffer> getSHCoefficientsBuffer() const { return mProbeCoefficientsGPU; }
+
+		/** 
+		 * Returns a GPU buffer containing information about light probe volumes (tetrahedra). updateProbes() must be called
+		 * at least once before the buffer is populated. If the probes changed since the last call, call updateProbes()
+		 * to refresh the buffer.
+		 */
+		SPtr<GpuBuffer> getTetrahedonInfosBuffer() const { return mTetrahedronInfosGPU; }
+
+		/**
+		 * Returns a mesh that contains triangles of all the light volume tetrahedra, including their corresponding
+		 * tetrahedron index. By rendering this mesh you can find which tetrahedron influences which pixel.
+		 */
+		SPtr<Mesh> getTetrahedraVolumeMesh() const { return mVolumeMesh; }
+
 	private:
 		/**
 		 * Perform tetrahedrization of the provided point list, and outputs a list of tetrahedrons and outer faces of the
@@ -145,13 +225,6 @@ namespace bs { namespace ct
 		Vector<UINT32> mTempTetrahedronBufferIndices;
 	};
 
-	/** Storage of tetrahedron AA box, for use on the GPU. */
-	struct TetrahedronBoundsGPU
-	{
-		Vector4 center;
-		Vector4 extents;
-	};
-
 	/** Information about a single tetrahedron, for use on the GPU. */
 	struct TetrahedronDataGPU
 	{

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

@@ -57,7 +57,7 @@ namespace bs { namespace ct
 	};
 
 	/** Container for all GBuffer textures. */
-	struct GBufferInput
+	struct GBufferTextures
 	{
 		SPtr<Texture> albedo;
 		SPtr<Texture> normals;
@@ -72,7 +72,7 @@ namespace bs { namespace ct
 		GBufferParams(const SPtr<Material>& material, const SPtr<GpuParamsSet>& paramsSet);
 
 		/** Binds the GBuffer textures to the pipeline. */
-		void bind(const GBufferInput& gbuffer);
+		void bind(const GBufferTextures& gbuffer);
 	private:
 		SPtr<Material> mMaterial;
 		SPtr<GpuParamsSet> mParamsSet;
@@ -151,7 +151,7 @@ namespace bs { namespace ct
 		TiledDeferredLightingMat();
 
 		/** Binds the material for rendering, sets up parameters and executes it. */
-		void execute(const RendererView& view, const VisibleLightData& lightData, const GBufferInput& gbuffer,
+		void execute(const RendererView& view, const VisibleLightData& lightData, const GBufferTextures& gbuffer,
 			const SPtr<Texture>& lightAccumTex, const SPtr<GpuBuffer>& lightAccumBuffer);
 
 		/** Returns the material variation matching the provided parameters. */

+ 2 - 2
Source/RenderBeast/Include/BsPostProcessing.h

@@ -634,7 +634,7 @@ namespace bs { namespace ct
 		 * @param[in]	gbuffer			GBuffer textures.
 		 * @param[in]	settings		Parameters used for controling the SSR effect.
 		 */
-		void execute(const RendererView& view, GBufferInput gbuffer, const ScreenSpaceReflectionsSettings& settings);
+		void execute(const RendererView& view, GBufferTextures gbuffer, const ScreenSpaceReflectionsSettings& settings);
 	private:
 		SPtr<GpuParamBlockBuffer> mParamBuffer;
 		GBufferParams mGBufferParams;
@@ -669,7 +669,7 @@ namespace bs { namespace ct
 		 * @param[in]	settings		Parameters used for controling the SSR effect.
 		 * @param[in]	destination		Render target to which to write the results to.
 		 */
-		void execute(const RendererView& view, GBufferInput gbuffer, const SPtr<Texture>& sceneColor, 
+		void execute(const RendererView& view, GBufferTextures gbuffer, const SPtr<Texture>& sceneColor, 
 			const SPtr<Texture>& hiZ, const ScreenSpaceReflectionsSettings& settings, 
 			const SPtr<RenderTarget>& destination);
 

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

@@ -113,7 +113,7 @@ namespace bs
 		void notifyLightProbeVolumeAdded(LightProbeVolume* volume) override;
 
 		/** @copydoc Renderer::notifyLightProbeVolumeUpdated */
-		void notifyLightProbeVolumeUpdated(LightProbeVolume* volume, bool coefficientsUpdated) override;
+		void notifyLightProbeVolumeUpdated(LightProbeVolume* volume) override;
 
 		/** @copydoc Renderer::notifyLightProbeVolumeRemoved */
 		void notifyLightProbeVolumeRemoved(LightProbeVolume* volume) override;

+ 17 - 1
Source/RenderBeast/Include/BsRenderCompositor.h

@@ -335,8 +335,24 @@ namespace ct
 		void clear() override;
 	};
 
+	/** Evaluates indirect lighting from the light probe volume, if available, or the sky irradiance otherwise. */
+	class RCNodeIndirectLighting : public RenderCompositorNode
+	{
+	public:
+		// Outputs to the unflattened RCNodeLightAccumulation
+
+		static StringID getNodeId() { return "IndirectLighting"; }
+		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
+	protected:
+		/** @copydoc RenderCompositorNode::render */
+		void render(const RenderCompositorNodeInputs& inputs) override;
+
+		/** @copydoc RenderCompositorNode::clear */
+		void clear() override;
+	};
+
 	/**
-	 * Perform tiled deferred image based lighting, combines it with direct lighting present in the light accumulation
+	 * Performs tiled deferred image based lighting, combines it with direct lighting present in the light accumulation
 	 * buffer and outputs the results to the scene color texture or buffer.
 	 */
 	class RCNodeTiledDeferredIBL : public RenderCompositorNode

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

@@ -8,6 +8,7 @@
 #include "BsLightRendering.h"
 #include "BsRendererView.h"
 #include "BsLight.h"
+#include "BsLightProbes.h"
 
 namespace bs 
 { 
@@ -49,6 +50,9 @@ namespace bs
 		Vector<bool> reflProbeCubemapArrayUsedSlots;
 		SPtr<Texture> reflProbeCubemapsTex;
 
+		// Light probes (indirect lighting)
+		LightProbes lightProbes;
+
 		// Sky
 		Skybox* skybox = nullptr;
 
@@ -103,6 +107,21 @@ namespace bs
 		/** Updates the index at which the reflection probe's texture is stored at, in the global array. */
 		void setReflectionProbeArrayIndex(UINT32 probeIdx, UINT32 arrayIdx, bool markAsClean);
 
+		/** Registers a new light probe volume in the scene. */
+		void registerLightProbeVolume(LightProbeVolume* volume);
+
+		/** Updates information about a previously registered light probe volume. */
+		void updateLightProbeVolume(LightProbeVolume* volume);
+
+		/** Removes a light probe volume from the scene. */
+		void unregisterLightProbeVolume(LightProbeVolume* volume);
+
+		/** 
+		 * Rebuilds any light probe related information. Should be called once immediately before rendering. If no change
+		 * is detected since the last call, the call does nothing.
+		 */
+		void updateLightProbes();
+
 		/** Registers a new sky texture in the scene. */
 		void registerSkybox(Skybox* skybox);
 

+ 3 - 0
Source/RenderBeast/Include/BsRendererTextures.h

@@ -32,6 +32,9 @@ namespace bs { namespace ct
 
 		/** Tileable 4x4 texture to be used for randomization in SSAO rendering. */
 		static SPtr<Texture> ssaoRandomization4x4;
+
+		/** Cubemap containing indirect lighting, when no other is available. */
+		static SPtr<Texture> defaultIndirect;
 	};
 
 	/** @} */

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

@@ -133,7 +133,7 @@ namespace bs { namespace ct
 	{
 		ShadowProjectParams(const Light& light, const SPtr<Texture>& shadowMap, UINT32 shadowMapFace,
 			const SPtr<GpuParamBlockBuffer>& shadowParams, const SPtr<GpuParamBlockBuffer>& perCameraParams,
-			GBufferInput gbuffer)
+			GBufferTextures gbuffer)
 			: light(light), shadowMap(shadowMap), shadowMapFace(shadowMapFace), shadowParams(shadowParams)
 			, perCamera(perCameraParams), gbuffer(gbuffer)
 		{ }
@@ -154,7 +154,7 @@ namespace bs { namespace ct
 		const SPtr<GpuParamBlockBuffer>& perCamera;
 
 		/** Contains the GBuffer textures. */
-		GBufferInput gbuffer;
+		GBufferTextures gbuffer;
 	};
 
 	BS_PARAM_BLOCK_BEGIN(ShadowProjectParamsDef)
@@ -434,7 +434,7 @@ namespace bs { namespace ct
 		 * The system uses shadow maps rendered by renderShadowMaps().
 		 */
 		void renderShadowOcclusion(const SceneInfo& sceneInfo, UINT32 shadowQuality, const RendererLight& light,
-			UINT32 viewIdx, GBufferInput gbuffer) const;
+			UINT32 viewIdx, GBufferTextures gbuffer) const;
 
 		/** Changes the default shadow map size. Will cause all shadow maps to be rebuilt. */
 		void setShadowMapSize(UINT32 size);

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

@@ -31,7 +31,7 @@ namespace bs { namespace ct {
 		DirectionalLightMat();
 
 		/** Binds the material for rendering and sets up any global parameters. */
-		void bind(const GBufferInput& gBufferInput, const SPtr<Texture>& lightOcclusion, 
+		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 			const SPtr<GpuParamBlockBuffer>& perCamera);
 
 		/** Updates the per-light buffers used by the material. */
@@ -56,7 +56,7 @@ namespace bs { namespace ct {
 		PointLightMat();
 
 		/** Binds the material for rendering and sets up any global parameters. */
-		void bind(const GBufferInput& gBufferInput, const SPtr<Texture>& lightOcclusion, 
+		void bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 			const SPtr<GpuParamBlockBuffer>& perCamera);
 
 		/** Updates the per-light buffers used by the material. */
@@ -82,7 +82,7 @@ namespace bs { namespace ct {
 
 		/** Calculates lighting for the specified light, using the standard deferred renderer. */
 		void renderLight(LightType type, const RendererLight& light, const RendererView& view, 
-			const GBufferInput& gBufferInput, const SPtr<Texture>& lightOcclusion);
+			const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion);
 
 	private:
 		SPtr<GpuParamBlockBuffer> mPerLightBuffer;

+ 0 - 5
Source/RenderBeast/Source/BsImageBasedLighting.cpp

@@ -110,10 +110,7 @@ namespace bs { namespace ct
 
 		// Sky
 		if (!optional || params->hasTexture(programType, "gSkyReflectionTex"))
-		{
 			params->getTextureParam(programType, "gSkyReflectionTex", skyReflectionsTexParam);
-			params->getTextureParam(programType, "gSkyIrradianceTex", skyIrradianceTexParam);
-		}
 
 		// Reflections
 		if (!optional || params->hasTexture(programType, "gReflProbeCubemaps"))
@@ -158,7 +155,6 @@ namespace bs { namespace ct
 
 		gReflProbeParamsParamDef.gSkyCubemapNumMips.set(buffer, numSkyMips);
 		gReflProbeParamsParamDef.gSkyCubemapAvailable.set(buffer, skyReflectionsAvailable);
-		gReflProbeParamsParamDef.gSkyBrightness.set(buffer, brightness);
 		gReflProbeParamsParamDef.gNumProbes.set(buffer, probeData.getNumProbes());
 
 		UINT32 numReflProbeMips = 0;
@@ -262,7 +258,6 @@ namespace bs { namespace ct
 		mImageBasedParams.reflectionProbesParam.set(probeData.getProbeBuffer());
 		mImageBasedParams.reflectionProbeCubemapsTexParam.set(sceneInfo.reflProbeCubemapsTex);
 		mImageBasedParams.skyReflectionsTexParam.set(skyFilteredRadiance);
-		mImageBasedParams.skyIrradianceTexParam.set(skyIrradiance);
 
 		mParamsSet->setParamBlockBuffer("PerCamera", view.getPerViewBuffer(), true);
 

+ 259 - 131
Source/RenderBeast/Source/BsLightProbes.cpp

@@ -9,6 +9,8 @@
 #include "BsVertexDataDesc.h"
 #include "BsGpuParamsSet.h"
 #include "BsRendererUtility.h"
+#include "BsSkybox.h"
+#include "BsRendererTextures.h"
 
 namespace bs { namespace ct 
 {
@@ -101,13 +103,127 @@ namespace bs { namespace ct
 		return get(VAR_NoMSAA);
 	}
 
-	LightProbes::LightProbes()
-		:mTetrahedronVolumeDirty(false), mMaxCoefficients(0), mMaxTetrahedra(0)
+	IrradianceEvaluateParamDef gIrradianceEvaluateParamDef;
+
+	ShaderVariation IrradianceEvaluateMat::VAR_MSAA_Probes = ShaderVariation({
+		ShaderVariation::Param("MSAA_COUNT", 2),
+		ShaderVariation::Param("SKY_ONLY", false)
+	});
+
+	ShaderVariation IrradianceEvaluateMat::VAR_NoMSAA_Probes = ShaderVariation({
+		ShaderVariation::Param("MSAA_COUNT", 1),
+		ShaderVariation::Param("SKY_ONLY", false)
+	});
+
+	ShaderVariation IrradianceEvaluateMat::VAR_MSAA_Sky = ShaderVariation({
+		ShaderVariation::Param("MSAA_COUNT", 2),
+		ShaderVariation::Param("SKY_ONLY", true)
+	});
+
+	ShaderVariation IrradianceEvaluateMat::VAR_NoMSAA_Sky = ShaderVariation({
+		ShaderVariation::Param("MSAA_COUNT", 1),
+		ShaderVariation::Param("SKY_ONLY", true)
+	});
+
+	IrradianceEvaluateMat::IrradianceEvaluateMat()
+		:mGBufferParams(mMaterial, mParamsSet)
 	{
-		resizeCoefficientBuffer(512);
+		mSkyOnly = mVariation.getBool("SKY_ONLY");
+
+		SPtr<GpuParams> params = mParamsSet->getGpuParams();
+
+		if(mSkyOnly)
+			params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gSkyIrradianceTex", mParamSkyIrradianceTex);
+		else
+		{
+			params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex", mParamInputTex);
+			params->getBufferParam(GPT_FRAGMENT_PROGRAM, "gSHCoeffs", mParamSHCoeffsBuffer);
+			params->getBufferParam(GPT_FRAGMENT_PROGRAM, "gProbeVolumes", mParamVolumeBuffer);
+		}
+
+		mParamBuffer = gIrradianceEvaluateParamDef.createBuffer();
+		mParamsSet->setParamBlockBuffer("Params", mParamBuffer, true);
+	}
+
+	void IrradianceEvaluateMat::_initVariations(ShaderVariations& variations)
+	{
+		variations.add(VAR_MSAA_Probes);
+		variations.add(VAR_MSAA_Sky);
+		variations.add(VAR_NoMSAA_Probes);
+		variations.add(VAR_NoMSAA_Sky);
 	}
 
-	void LightProbes::notifyAdded(const SPtr<LightProbeVolume>& volume)
+	void IrradianceEvaluateMat::execute(const RendererView& view, const GBufferTextures& gbuffer, 
+		const SPtr<Texture>& lightProbeIndices, const SPtr<GpuBuffer>& shCoeffs, const SPtr<GpuBuffer>& volumes, 
+		const Skybox* skybox, const SPtr<RenderTexture>& output)
+	{
+		const RendererViewProperties& viewProps = view.getProperties();
+
+		mGBufferParams.bind(gbuffer);
+
+		float skyBrightness = 1.0f;
+		if (mSkyOnly)
+		{
+			SPtr<Texture> skyIrradiance;
+			if (skybox != nullptr)
+			{
+				skyIrradiance = skybox->getIrradiance();
+				skyBrightness = skybox->getBrightness();
+			}
+
+			if(skyIrradiance == nullptr)
+				skyIrradiance = RendererTextures::defaultIndirect;
+
+			mParamSkyIrradianceTex.set(skyIrradiance);
+		}
+		else
+		{
+			mParamInputTex.set(lightProbeIndices);
+			mParamSHCoeffsBuffer.set(shCoeffs);
+			mParamVolumeBuffer.set(volumes);
+		}
+
+		gIrradianceEvaluateParamDef.gSkyBrightness.set(mParamBuffer, skyBrightness);
+		mParamBuffer->flushToGPU();
+
+		mParamsSet->setParamBlockBuffer("PerCamera", view.getPerViewBuffer(), true);
+
+		// Render
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(output, 0, RT_COLOR0);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+
+		gRendererUtility().drawScreenQuad(Rect2(0.0f, 0.0f, (float)viewProps.viewRect.width, 
+			(float)viewProps.viewRect.height));
+
+		rapi.setRenderTarget(nullptr);
+	}
+
+	IrradianceEvaluateMat* IrradianceEvaluateMat::getVariation(UINT32 msaaCount, bool skyOnly)
+	{
+		if(skyOnly)
+		{
+			if (msaaCount > 1)
+				return get(VAR_MSAA_Sky);
+
+			return get(VAR_NoMSAA_Sky);
+		}
+		else
+		{
+			if (msaaCount > 1)
+				return get(VAR_MSAA_Probes);
+
+			return get(VAR_NoMSAA_Probes);
+		}
+	}
+
+	LightProbes::LightProbes()
+		:mTetrahedronVolumeDirty(false), mMaxCoefficients(0), mMaxTetrahedra(0)
+	{ }
+
+	void LightProbes::notifyAdded(LightProbeVolume* volume)
 	{
 		UINT32 handle = (UINT32)mVolumes.size();
 
@@ -121,7 +237,7 @@ namespace bs { namespace ct
 		notifyDirty(volume);
 	}
 
-	void LightProbes::notifyDirty(const SPtr<LightProbeVolume>& volume)
+	void LightProbes::notifyDirty(LightProbeVolume* volume)
 	{
 		UINT32 handle = volume->getRendererId();
 		mVolumes[handle].isDirty = true;
@@ -129,11 +245,11 @@ namespace bs { namespace ct
 		mTetrahedronVolumeDirty = true;
 	}
 
-	void LightProbes::notifyRemoved(const SPtr<LightProbeVolume>& volume)
+	void LightProbes::notifyRemoved(LightProbeVolume* volume)
 	{
 		UINT32 handle = volume->getRendererId();
 
-		LightProbeVolume* lastVolume = mVolumes.back().volume.get();
+		LightProbeVolume* lastVolume = mVolumes.back().volume;
 		UINT32 lastHandle = lastVolume->getRendererId();
 		
 		if (handle != lastHandle)
@@ -151,163 +267,175 @@ namespace bs { namespace ct
 
 	void LightProbes::updateProbes()
 	{
-		if(mTetrahedronVolumeDirty)
+		if (!mTetrahedronVolumeDirty)
+			return;
+
+		// Move all coefficients into the global buffer
+		UINT32 numCoeffs = 0;
+		for(auto& entry : mVolumes)
 		{
-			// Move all coefficients into the global buffer
-			UINT32 numCoeffs = 0;
-			for(auto& entry : mVolumes)
-			{
-				UINT32 numProbes = (UINT32)entry.volume->getLightProbePositions().size();
-				numCoeffs += numProbes;
-			}
+			UINT32 numProbes = (UINT32)entry.volume->getLightProbePositions().size();
+			numCoeffs += numProbes;
+		}
 
-			if(numCoeffs > mMaxCoefficients)
-			{
-				UINT32 newSize = Math::divideAndRoundUp(numCoeffs, 32U) * 32U;
-				resizeCoefficientBuffer(newSize);
-			}
+		if(numCoeffs > mMaxCoefficients)
+		{
+			UINT32 newSize = Math::divideAndRoundUp(numCoeffs, 32U) * 32U;
+			resizeCoefficientBuffer(newSize);
+		}
 
-			UINT8* dest = (UINT8*)mProbeCoefficientsGPU->lock(0, mProbeCoefficientsGPU->getSize(), GBL_WRITE_ONLY_DISCARD);
-			for(auto& entry : mVolumes)
-			{
-				UINT32 numProbes = (UINT32)entry.volume->getLightProbePositions().size();
-				SPtr<GpuBuffer> localBuffer = entry.volume->getCoefficientsBuffer();
-				
-				// Note: Some of the coefficients might still be dirty (unrendered). Check for this and write them as black?
-				UINT32 size = numProbes * sizeof(LightProbeSHCoefficients);
-				UINT8* src = (UINT8*)localBuffer->lock(0, size, GBL_READ_ONLY);
-				memcpy(dest, src, size);
-
-				localBuffer->unlock();
-				dest += size;
-			}
-			mProbeCoefficientsGPU->unlock();
+		UINT8* dest = (UINT8*)mProbeCoefficientsGPU->lock(0, mProbeCoefficientsGPU->getSize(), GBL_WRITE_ONLY_DISCARD);
+		for(auto& entry : mVolumes)
+		{
+			UINT32 numProbes = (UINT32)entry.volume->getLightProbePositions().size();
+			SPtr<GpuBuffer> localBuffer = entry.volume->getCoefficientsBuffer();
+			
+			// Note: Some of the coefficients might still be dirty (unrendered). Check for this and write them as black?
+			UINT32 size = numProbes * sizeof(LightProbeSHCoefficients);
+			UINT8* src = (UINT8*)localBuffer->lock(0, size, GBL_READ_ONLY);
+			memcpy(dest, src, size);
+
+			localBuffer->unlock();
+			dest += size;
+		}
+		mProbeCoefficientsGPU->unlock();
 
-			// Gather all positions
-			UINT32 bufferOffset = 0;
-			for(auto& entry : mVolumes)
+		// Gather all positions
+		UINT32 bufferOffset = 0;
+		for(auto& entry : mVolumes)
+		{
+			const Vector<LightProbeInfo>& infos = entry.volume->getLightProbeInfos();
+			const Vector<Vector3>& positions = entry.volume->getLightProbePositions();
+			UINT32 numProbes = entry.volume->getNumActiveProbes();
+			
+			if (numProbes == 0)
+				continue;
+
+			Vector3 offset = entry.volume->getPosition();
+			Quaternion rotation = entry.volume->getRotation();
+			for(UINT32 i = 0; i < numProbes; i++)
 			{
-				const Vector<LightProbeInfo>& infos = entry.volume->getLightProbeInfos();
-				const Vector<Vector3>& positions = entry.volume->getLightProbePositions();
-				UINT32 numProbes = entry.volume->getNumActiveProbes();
-				
-				if (numProbes == 0)
-					continue;
-
-				Vector3 offset = entry.volume->getPosition();
-				Quaternion rotation = entry.volume->getRotation();
-				for(UINT32 i = 0; i < numProbes; i++)
-				{
-					Vector3 localPos = positions[i];
-					Vector3 transformedPos = rotation.rotate(localPos) + offset;
-					mTempTetrahedronPositions.push_back(transformedPos);
-					mTempTetrahedronBufferIndices.push_back(bufferOffset + infos[i].bufferIdx);
-				}
-
-				bufferOffset += (UINT32)positions.size();
+				Vector3 localPos = positions[i];
+				Vector3 transformedPos = rotation.rotate(localPos) + offset;
+				mTempTetrahedronPositions.push_back(transformedPos);
+				mTempTetrahedronBufferIndices.push_back(bufferOffset + infos[i].bufferIdx);
 			}
 
-			mTetrahedronInfos.clear();
+			bufferOffset += (UINT32)positions.size();
+		}
 
-			UINT32 innerVertexCount = (UINT32)mTempTetrahedronPositions.size();
-			generateTetrahedronData(mTempTetrahedronPositions, mTetrahedronInfos, false);
+		mTetrahedronInfos.clear();
 
-			// Generate a mesh out of all the tetrahedron triangles
-			// Note: Currently the entire volume is rendered as a single large mesh, which will isn't optimal as we can't
-			// perform frustum culling. A better option would be to split the mesh into multiple smaller volumes, do
-			// frustum culling and possibly even sort by distance from camera.
-			UINT32 numTetrahedra = (UINT32)mTetrahedronInfos.size();
+		UINT32 innerVertexCount = (UINT32)mTempTetrahedronPositions.size();
+		generateTetrahedronData(mTempTetrahedronPositions, mTetrahedronInfos, false);
 
-			UINT32 numVertices = numTetrahedra * 4 * 3;
-			UINT32 numIndices = numTetrahedra * 4 * 3;
+		// Generate a mesh out of all the tetrahedron triangles
+		// Note: Currently the entire volume is rendered as a single large mesh, which will isn't optimal as we can't
+		// perform frustum culling. A better option would be to split the mesh into multiple smaller volumes, do
+		// frustum culling and possibly even sort by distance from camera.
+		UINT32 numTetrahedra = (UINT32)mTetrahedronInfos.size();
 
-			SPtr<VertexDataDesc> vertexDesc = bs_shared_ptr_new<VertexDataDesc>();
-			vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
-			vertexDesc->addVertElem(VET_UINT1, VES_TEXCOORD);
+		UINT32 numVertices = numTetrahedra * 4 * 3;
+		UINT32 numIndices = numTetrahedra * 4 * 3;
 
-			SPtr<MeshData> meshData = MeshData::create(numVertices, numIndices, vertexDesc);
-			auto posIter = meshData->getVec3DataIter(VES_POSITION);
-			auto idIter = meshData->getDWORDDataIter(VES_TEXCOORD);
+		SPtr<VertexDataDesc> vertexDesc = bs_shared_ptr_new<VertexDataDesc>();
+		vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
+		vertexDesc->addVertElem(VET_UINT1, VES_TEXCOORD);
 
-			UINT32 tetIdx = 0;
-			for(auto& entry : mTetrahedronInfos)
-			{
-				const Tetrahedron& volume = entry.volume;
+		SPtr<MeshData> meshData = MeshData::create(numVertices, numIndices, vertexDesc);
+		auto posIter = meshData->getVec3DataIter(VES_POSITION);
+		auto idIter = meshData->getDWORDDataIter(VES_TEXCOORD);
 
-				Vector3 center(BsZero);
-				for(UINT32 i = 0; i < 4; i++)
-					center += mTempTetrahedronPositions[volume.vertices[i]];
+		UINT32 tetIdx = 0;
+		for(auto& entry : mTetrahedronInfos)
+		{
+			const Tetrahedron& volume = entry.volume;
 
-				center /= 4.0f;
+			Vector3 center(BsZero);
+			for(UINT32 i = 0; i < 4; i++)
+				center += mTempTetrahedronPositions[volume.vertices[i]];
 
-				static const UINT32 Permutations[4][3] = 
-				{
-					{ 0, 1, 2 },
-					{ 0, 1, 3 },
-					{ 0, 2, 3 },
-					{ 1, 2, 3 }
-				};
+			center /= 4.0f;
 
-				for(UINT32 i = 0; i < 4; i++)
-				{
-					Vector3 A = mTempTetrahedronPositions[volume.vertices[Permutations[i][0]]];
-					Vector3 B = mTempTetrahedronPositions[volume.vertices[Permutations[i][1]]];
-					Vector3 C = mTempTetrahedronPositions[volume.vertices[Permutations[i][2]]];
+			static const UINT32 Permutations[4][3] = 
+			{
+				{ 0, 1, 2 },
+				{ 0, 1, 3 },
+				{ 0, 2, 3 },
+				{ 1, 2, 3 }
+			};
 
-					// Make sure the triangle is clockwise
-					Vector3 e0 = A - C;
-					Vector3 e1 = B - C;
+			for(UINT32 i = 0; i < 4; i++)
+			{
+				Vector3 A = mTempTetrahedronPositions[volume.vertices[Permutations[i][0]]];
+				Vector3 B = mTempTetrahedronPositions[volume.vertices[Permutations[i][1]]];
+				Vector3 C = mTempTetrahedronPositions[volume.vertices[Permutations[i][2]]];
 
-					Vector3 normal = e0.cross(e1);
-					if (normal.dot(A - center) < 0.0f)
-						std::swap(B, C);
+				// Make sure the triangle is clockwise
+				Vector3 e0 = A - C;
+				Vector3 e1 = B - C;
 
-					posIter.addValue(A);
-					posIter.addValue(B);
-					posIter.addValue(C);
+				Vector3 normal = e0.cross(e1);
+				if (normal.dot(A - center) < 0.0f)
+					std::swap(B, C);
 
-					idIter.addValue(tetIdx);
-					idIter.addValue(tetIdx);
-					idIter.addValue(tetIdx);
-				}
+				posIter.addValue(A);
+				posIter.addValue(B);
+				posIter.addValue(C);
 
-				tetIdx++;
+				idIter.addValue(tetIdx);
+				idIter.addValue(tetIdx);
+				idIter.addValue(tetIdx);
 			}
 
-			mVolumeMesh = Mesh::create(meshData);
+			tetIdx++;
+		}
+
+		mVolumeMesh = Mesh::create(meshData);
+
+		// Map vertices to actual SH coefficient indices, and write GPU buffer with tetrahedron information
+		if (numTetrahedra > mMaxTetrahedra)
+		{
+			UINT32 newSize = Math::divideAndRoundUp(numTetrahedra, 64U) * 64U;
+			resizeTetrahedronBuffer(newSize);
+		}
 
-			// Map vertices to actual SH coefficient indices, and write GPU buffer with tetrahedron information
-			if (numTetrahedra > mMaxTetrahedra)
+		TetrahedronDataGPU* dst = (TetrahedronDataGPU*)mTetrahedronInfosGPU->lock(0, mTetrahedronInfosGPU->getSize(), 
+			GBL_WRITE_ONLY_DISCARD);
+		for (auto& entry : mTetrahedronInfos)
+		{
+			for(UINT32 i = 0; i < 4; ++i)
 			{
-				UINT32 newSize = Math::divideAndRoundUp(numTetrahedra, 64U) * 64U;
-				resizeTetrahedronBuffer(newSize);
+				// Check for outer vertices, which have no SH data associated with them
+				if (entry.volume.vertices[i] >= (INT32)innerVertexCount)
+					entry.volume.vertices[i] = -1;
+				else
+					entry.volume.vertices[i] = mTempTetrahedronBufferIndices[i];
 			}
 
-			TetrahedronDataGPU* dst = (TetrahedronDataGPU*)mTetrahedronInfosGPU->lock(0, mTetrahedronInfosGPU->getSize(), 
-				GBL_WRITE_ONLY_DISCARD);
-			for (auto& entry : mTetrahedronInfos)
-			{
-				for(UINT32 i = 0; i < 4; ++i)
-				{
-					// Check for outer vertices, which have no SH data associated with them
-					if (entry.volume.vertices[i] >= (INT32)innerVertexCount)
-						entry.volume.vertices[i] = -1;
-					else
-						entry.volume.vertices[i] = mTempTetrahedronBufferIndices[i];
-				}
+			memcpy(dst->indices, entry.volume.vertices, sizeof(UINT32) * 4);
+			memcpy(&dst->transform, &entry.transform, sizeof(float) * 12);
 
-				memcpy(dst->indices, entry.volume.vertices, sizeof(UINT32) * 4);
-				memcpy(&dst->transform, &entry.transform, sizeof(float) * 12);
+			dst++;
+		}
 
-				dst++;
-			}
+		mTetrahedronInfosGPU->unlock();
 
-			mTetrahedronInfosGPU->unlock();
+		mTempTetrahedronPositions.clear();
+		mTempTetrahedronBufferIndices.clear();
+		mTetrahedronVolumeDirty = false;
+	}
 
-			mTempTetrahedronPositions.clear();
-			mTempTetrahedronBufferIndices.clear();
-			mTetrahedronVolumeDirty = false;
+	bool LightProbes::hasAnyProbes() const
+	{
+		for(auto& entry : mVolumes)
+		{
+			UINT32 numProbes = entry.volume->getNumActiveProbes();
+			if (numProbes > 0)
+				return true;
 		}
+
+		return false;
 	}
 
 	void LightProbes::resizeTetrahedronBuffer(UINT32 count)

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

@@ -116,7 +116,7 @@ namespace bs { namespace ct
 		material->setSamplerState("gDepthBufferSamp", ss);
 	}
 
-	void GBufferParams::bind(const GBufferInput& gbuffer)
+	void GBufferParams::bind(const GBufferTextures& gbuffer)
 	{
 		mGBufferA.set(gbuffer.albedo);
 		mGBufferB.set(gbuffer.normals);
@@ -280,7 +280,7 @@ namespace bs { namespace ct
 	}
 
 	void TiledDeferredLightingMat::execute(const RendererView& view, const VisibleLightData& lightData, 
-		const GBufferInput& gbuffer, const SPtr<Texture>& lightAccumTex, const SPtr<GpuBuffer>& lightAccumBuffer)
+		const GBufferTextures& gbuffer, const SPtr<Texture>& lightAccumTex, const SPtr<GpuBuffer>& lightAccumBuffer)
 	{
 		const RendererViewProperties& viewProps = view.getProperties();
 		const RenderSettings& settings = view.getRenderSettings();

+ 2 - 2
Source/RenderBeast/Source/BsPostProcessing.cpp

@@ -1372,7 +1372,7 @@ namespace bs { namespace ct
 		// Do nothing
 	}
 
-	void SSRStencilMat::execute(const RendererView& view, GBufferInput gbuffer, 
+	void SSRStencilMat::execute(const RendererView& view, GBufferTextures gbuffer, 
 		const ScreenSpaceReflectionsSettings& settings)
 	{
 		mGBufferParams.bind(gbuffer);
@@ -1408,7 +1408,7 @@ namespace bs { namespace ct
 		// Do nothing
 	}
 
-	void SSRTraceMat::execute(const RendererView& view, GBufferInput gbuffer, const SPtr<Texture>& sceneColor, 
+	void SSRTraceMat::execute(const RendererView& view, GBufferTextures gbuffer, const SPtr<Texture>& sceneColor, 
 			const SPtr<Texture>& hiZ, const ScreenSpaceReflectionsSettings& settings, 
 			const SPtr<RenderTarget>& destination)
 	{

+ 10 - 4
Source/RenderBeast/Source/BsRenderBeast.cpp

@@ -95,6 +95,7 @@ namespace bs { namespace ct
 		RenderCompositor::registerNodeType<RCNodeHiZ>();
 		RenderCompositor::registerNodeType<RCNodeSSAO>();
 		RenderCompositor::registerNodeType<RCNodeClusteredForward>();
+		RenderCompositor::registerNodeType<RCNodeIndirectLighting>();
 	}
 
 	void RenderBeast::destroyCore()
@@ -187,17 +188,17 @@ namespace bs { namespace ct
 
 	void RenderBeast::notifyLightProbeVolumeAdded(LightProbeVolume* volume)
 	{
-		assert(false); // TODO
+		mScene->registerLightProbeVolume(volume);
 	}
 
-	void RenderBeast::notifyLightProbeVolumeUpdated(LightProbeVolume* volume, bool coefficientsUpdated)
+	void RenderBeast::notifyLightProbeVolumeUpdated(LightProbeVolume* volume)
 	{
-		assert(false); // TODO
+		mScene->updateLightProbeVolume(volume);
 	}
 
 	void RenderBeast::notifyLightProbeVolumeRemoved(LightProbeVolume* volume)
 	{
-		assert(false); // TODO
+		mScene->unregisterLightProbeVolume(volume);
 	}
 
 	void RenderBeast::notifySkyboxAdded(Skybox* skybox)
@@ -359,6 +360,10 @@ namespace bs { namespace ct
 		SPtr<GpuParamBlockBuffer> perCameraBuffer = view.getPerViewBuffer();
 		perCameraBuffer->flushToGPU();
 
+		// Make sure light probe data is up to date
+		if(view.getRenderSettings().enableIndirectLighting)
+			mScene->updateLightProbes();
+
 		view.beginFrame();
 
 		RenderCompositorNodeInputs inputs(viewGroup, view, sceneInfo, *mCoreOptions, frameInfo);
@@ -568,6 +573,7 @@ namespace bs { namespace ct
 		SPtr<RenderSettings> settings = bs_shared_ptr_new<RenderSettings>();
 		settings->enableHDR = hdr;
 		settings->enableShadows = false; // Note: If I ever change this I need to make sure that shadow map rendering is aware of this view (currently it is only aware of main camera views)
+		settings->enableIndirectLighting = false;
 
 		Matrix4 viewOffsetMat = Matrix4::translation(-position);
 

+ 89 - 9
Source/RenderBeast/Source/BsRenderCompositor.cpp

@@ -10,7 +10,6 @@
 #include "BsRenderBeastOptions.h"
 #include "BsCamera.h"
 #include "BsRendererScene.h"
-#include "BsIBLUtility.h"
 #include "BsRenderBeast.h"
 #include "BsBitwise.h"
 #include "BsRendererTextures.h"
@@ -18,6 +17,7 @@
 #include "BsGpuParamsSet.h"
 #include "BsRendererExtension.h"
 #include "BsSkybox.h"
+#include "BsLightProbes.h"
 
 namespace bs { namespace ct
 {
@@ -486,7 +486,7 @@ namespace bs { namespace ct
 		const RendererViewProperties& viewProps = inputs.view.getProperties();
 		TiledDeferredLightingMat* tiledDeferredMat = TiledDeferredLightingMat::getVariation(viewProps.numSamples);
 
-		GBufferInput gbuffer;
+		GBufferTextures gbuffer;
 		gbuffer.albedo = gbufferNode->albedoTex->texture;
 		gbuffer.normals = gbufferNode->normalTex->texture;
 		gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
@@ -558,7 +558,7 @@ namespace bs { namespace ct
 			mRenderTarget = RenderTexture::create(lightOcclusionRTDesc);
 		}
 
-		GBufferInput gbuffer;
+		GBufferTextures gbuffer;
 		gbuffer.albedo = gbufferNode->albedoTex->texture;
 		gbuffer.normals = gbufferNode->normalTex->texture;
 		gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
@@ -648,6 +648,91 @@ namespace bs { namespace ct
 		return { RCNodeLightAccumulation::getNodeId() };
 	}
 
+	void RCNodeIndirectLighting::render(const RenderCompositorNodeInputs& inputs)
+	{
+		if (!inputs.view.getRenderSettings().enableIndirectLighting)
+			return;
+
+		RCNodeGBuffer* gbufferNode = static_cast<RCNodeGBuffer*>(inputs.inputNodes[0]);
+		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[1]);
+		RCNodeLightAccumulation* lightAccumNode = static_cast <RCNodeLightAccumulation*>(inputs.inputNodes[2]);
+
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+		const RendererViewProperties& viewProps = inputs.view.getProperties();
+
+		const LightProbes& lightProbes = inputs.scene.lightProbes;
+
+		SPtr<GpuBuffer> shCoeffs = lightProbes.getSHCoefficientsBuffer();
+		SPtr<GpuBuffer> volumeInfos = lightProbes.getTetrahedonInfosBuffer();
+		SPtr<Mesh> volumeMesh = lightProbes.getTetrahedraVolumeMesh();
+
+		IrradianceEvaluateMat* evaluateMat;
+		SPtr<PooledRenderTexture> volumeIndices;
+		if(lightProbes.hasAnyProbes())
+		{
+			POOLED_RENDER_TEXTURE_DESC volumeIndicesDesc;
+			POOLED_RENDER_TEXTURE_DESC depthDesc;
+			TetrahedraRenderMat::getOutputDesc(inputs.view, volumeIndicesDesc, depthDesc);
+
+			volumeIndices = resPool.get(volumeIndicesDesc);
+			SPtr<PooledRenderTexture> depthTex = resPool.get(depthDesc);
+
+			RENDER_TEXTURE_DESC rtDesc;
+			rtDesc.colorSurfaces[0].texture = volumeIndices->texture;
+			rtDesc.depthStencilSurface.texture = depthTex->texture;
+
+			SPtr<RenderTexture> rt = RenderTexture::create(rtDesc);
+
+			TetrahedraRenderMat* renderTetrahedra = TetrahedraRenderMat::getVariation(viewProps.numSamples > 1);
+			renderTetrahedra->execute(inputs.view, sceneDepthNode->depthTex->texture, volumeMesh, rt);
+
+			rt = nullptr;
+			resPool.release(depthTex);
+
+			evaluateMat = IrradianceEvaluateMat::getVariation(viewProps.numSamples, false);
+		}
+		else // Sky only
+		{
+			evaluateMat = IrradianceEvaluateMat::getVariation(viewProps.numSamples, true);
+		}
+
+		GBufferTextures gbuffer;
+		gbuffer.albedo = gbufferNode->albedoTex->texture;
+		gbuffer.normals = gbufferNode->normalTex->texture;
+		gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
+		gbuffer.depth = sceneDepthNode->depthTex->texture;
+
+		SPtr<Texture> volumeIndicesTex;
+		if (volumeIndices)
+			volumeIndicesTex = volumeIndices->texture;
+
+		evaluateMat->execute(inputs.view, gbuffer, volumeIndicesTex, shCoeffs, volumeInfos, inputs.scene.skybox, 
+			lightAccumNode->renderTarget);
+
+		if(volumeIndices)
+			resPool.release(volumeIndices);
+	}
+
+	void RCNodeIndirectLighting::clear()
+	{
+		// Do nothing
+	}
+
+	SmallVector<StringID, 4> RCNodeIndirectLighting::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(RCNodeStandardDeferredLighting::getNodeId());
+
+		if (view.getProperties().numSamples > 1)
+			deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+
+		return deps;
+	}
+
 	void RCNodeTiledDeferredIBL::render(const RenderCompositorNodeInputs& inputs)
 	{
 		RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
@@ -686,7 +771,7 @@ namespace bs { namespace ct
 		deps.push_back(RCNodeGBuffer::getNodeId());
 		deps.push_back(RCNodeSceneDepth::getNodeId());
 		deps.push_back(RCNodeLightAccumulation::getNodeId());
-		deps.push_back(RCNodeStandardDeferredLighting::getNodeId());
+		deps.push_back(RCNodeIndirectLighting::getNodeId());
 
 		return deps;
 	}
@@ -734,12 +819,8 @@ namespace bs { namespace ct
 			viewProps.renderingReflections);
 
 		SPtr<Texture> skyFilteredRadiance;
-		SPtr<Texture> skyIrradiance;
 		if(sceneInfo.skybox)
-		{
 			skyFilteredRadiance = sceneInfo.skybox->getFilteredRadiance();
-			skyIrradiance = sceneInfo.skybox->getIrradiance();
-		}
 
 		// Prepare objects for rendering
 		const VisibilityInfo& visibility = inputs.view.getVisibilityMasks();
@@ -776,7 +857,6 @@ namespace bs { namespace ct
 				iblParams.reflectionProbesParam.set(visibleReflProbeData.getProbeBuffer());
 
 				iblParams.skyReflectionsTexParam.set(skyFilteredRadiance);
-				iblParams.skyIrradianceTexParam.set(skyIrradiance);
 
 				iblParams.reflectionProbeCubemapsTexParam.set(sceneInfo.reflProbeCubemapsTex);
 				iblParams.preintegratedEnvBRDFParam.set(RendererTextures::preintegratedEnvGF);

+ 20 - 0
Source/RenderBeast/Source/BsRendererScene.cpp

@@ -470,6 +470,26 @@ namespace bs {	namespace ct
 			probe->arrayDirty = false;
 	}
 
+	void RendererScene::registerLightProbeVolume(LightProbeVolume* volume)
+	{
+		mInfo.lightProbes.notifyAdded(volume);
+	}
+
+	void RendererScene::updateLightProbeVolume(LightProbeVolume* volume)
+	{
+		mInfo.lightProbes.notifyDirty(volume);
+	}
+
+	void RendererScene::unregisterLightProbeVolume(LightProbeVolume* volume)
+	{
+		mInfo.lightProbes.notifyRemoved(volume);
+	}
+
+	void RendererScene::updateLightProbes()
+	{
+		mInfo.lightProbes.updateProbes();
+	}
+
 	void RendererScene::registerSkybox(Skybox* skybox)
 	{
 		mInfo.skybox = skybox;

+ 66 - 1
Source/RenderBeast/Source/BsRendererTextures.cpp

@@ -6,6 +6,7 @@
 #include "BsMath.h"
 #include "BsTexture.h"
 #include "BsPixelData.h"
+#include "BsIBLUtility.h"
 
 namespace bs { namespace ct
 {
@@ -172,19 +173,83 @@ namespace bs { namespace ct
 		return texture;
 	}
 
+	SPtr<Texture> generateDefaultIndirect()
+	{
+		TEXTURE_DESC dummySkyDesc;
+		dummySkyDesc.type = TEX_TYPE_CUBE_MAP;
+		dummySkyDesc.format = PF_RG11B10F;
+		dummySkyDesc.width = 2;
+		dummySkyDesc.height = 2;
+
+		// Note: Eventually replace this with a time of day model
+		float intensity = 10.0f;
+		Color skyColor = Color::White * intensity;
+		SPtr<Texture> skyTexture = Texture::create(dummySkyDesc);
+		
+		UINT32 sides[] = { CF_PositiveX, CF_NegativeX, CF_PositiveZ, CF_NegativeZ };
+		for(UINT32 i = 0; i < 4; ++i)
+		{
+			PixelData data = skyTexture->lock(GBL_WRITE_ONLY_DISCARD, 0, sides[i]);
+
+			data.setColorAt(skyColor, 0, 0);
+			data.setColorAt(skyColor, 1, 0);
+			data.setColorAt(Color::Black, 0, 1);
+			data.setColorAt(Color::Black, 1, 1);
+
+			skyTexture->unlock();
+		}
+
+		{
+			PixelData data = skyTexture->lock(GBL_WRITE_ONLY_DISCARD, 0, CF_PositiveY);
+			
+			data.setColorAt(skyColor, 0, 0);
+			data.setColorAt(skyColor, 1, 0);
+			data.setColorAt(skyColor, 0, 1);
+			data.setColorAt(skyColor, 1, 1);
+
+			skyTexture->unlock();
+		}
+
+		{
+			PixelData data = skyTexture->lock(GBL_WRITE_ONLY_DISCARD, 0, CF_NegativeY);
+			
+			data.setColorAt(Color::Black, 0, 0);
+			data.setColorAt(Color::Black, 1, 0);
+			data.setColorAt(Color::Black, 0, 1);
+			data.setColorAt(Color::Black, 1, 1);
+
+			skyTexture->unlock();
+		}
+
+		TEXTURE_DESC irradianceCubemapDesc;
+		irradianceCubemapDesc.type = TEX_TYPE_CUBE_MAP;
+		irradianceCubemapDesc.format = PF_RG11B10F;
+		irradianceCubemapDesc.width = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
+		irradianceCubemapDesc.height = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
+		irradianceCubemapDesc.numMips = 0;
+		irradianceCubemapDesc.usage = TU_STATIC | TU_RENDERTARGET;
+
+		SPtr<Texture> irradiance = Texture::create(irradianceCubemapDesc);
+		gIBLUtility().filterCubemapForIrradiance(skyTexture, irradiance);
+
+		return irradiance;
+	}
+
 	SPtr<Texture> RendererTextures::preintegratedEnvGF;
 	SPtr<Texture> RendererTextures::ssaoRandomization4x4;
+	SPtr<Texture> RendererTextures::defaultIndirect;
 
 	void RendererTextures::startUp()
 	{
 		preintegratedEnvGF = generatePreintegratedEnvBRDF();
 		ssaoRandomization4x4 = generate4x4RandomizationTexture();
+		defaultIndirect = generateDefaultIndirect();
 	}
 
 	void RendererTextures::shutDown()
 	{
 		preintegratedEnvGF = nullptr;
 		ssaoRandomization4x4 = nullptr;
-		
+		defaultIndirect = nullptr;		
 	}
 }}

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

@@ -807,7 +807,7 @@ namespace bs { namespace ct
 	}
 
 	void ShadowRendering::renderShadowOcclusion(const SceneInfo& sceneInfo, UINT32 shadowQuality, 
-		const RendererLight& rendererLight, UINT32 viewIdx, GBufferInput gbuffer) const
+		const RendererLight& rendererLight, UINT32 viewIdx, GBufferTextures gbuffer) const
 	{
 		const Light* light = rendererLight.internal;
 		UINT32 lightIdx = light->getRendererId();

+ 3 - 3
Source/RenderBeast/Source/BsStandardDeferredLighting.cpp

@@ -30,7 +30,7 @@ namespace bs { namespace ct {
 		variations.add(VAR_NoMSAA);
 	}
 
-	void DirectionalLightMat::bind(const GBufferInput& gBufferInput, const SPtr<Texture>& lightOcclusion, 
+	void DirectionalLightMat::bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 		const SPtr<GpuParamBlockBuffer>& perCamera)
 	{
 		RendererUtility::instance().setPass(mMaterial, 0);
@@ -88,7 +88,7 @@ namespace bs { namespace ct {
 		variations.add(VAR_NoMSAA_Outside);
 	}
 
-	void PointLightMat::bind(const GBufferInput& gBufferInput, const SPtr<Texture>& lightOcclusion, 
+	void PointLightMat::bind(const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion, 
 		const SPtr<GpuParamBlockBuffer>& perCamera)
 	{
 		RendererUtility::instance().setPass(mMaterial, 0);
@@ -129,7 +129,7 @@ namespace bs { namespace ct {
 	}
 
 	void StandardDeferred::renderLight(LightType lightType, const RendererLight& light, const RendererView& view, 
-		const GBufferInput& gBufferInput, const SPtr<Texture>& lightOcclusion)
+		const GBufferTextures& gBufferInput, const SPtr<Texture>& lightOcclusion)
 	{
 		const auto& viewProps = view.getProperties();