Browse Source

WIP: Adding alternatives to existing compute shaders, in order to support older hardware and mobiles
- Added an alternative shader for:
- Eye adaptation
- Color LUT generation (using a 2D, unwrapped color LUT)
- Spherical harmonic calculations for irradiance filtering
- Updated BSL compiler preprocessor so it supports basic #if directives

BearishSun 8 years ago
parent
commit
3d6cceab7d
83 changed files with 1349 additions and 245 deletions
  1. BIN
      Data/Engine/Includes/PPEyeAdaptationCommon.bslinc.asset
  2. BIN
      Data/Engine/Includes/ReflectionCubemapCommon.bslinc.asset
  3. 49 0
      Data/Engine/ShaderDependencies.json
  4. BIN
      Data/Engine/Shaders/IrradianceAccumulateCubeSH.bsl.asset
  5. BIN
      Data/Engine/Shaders/IrradianceAccumulateSH.bsl.asset
  6. BIN
      Data/Engine/Shaders/IrradianceComputeSH.bsl.asset
  7. BIN
      Data/Engine/Shaders/IrradianceComputeSHFrag.bsl.asset
  8. BIN
      Data/Engine/Shaders/IrradianceComputeSH_1.bsl.asset
  9. BIN
      Data/Engine/Shaders/IrradianceProjectSH.bsl.asset
  10. BIN
      Data/Engine/Shaders/IrradianceReduceSH.bsl.asset
  11. BIN
      Data/Engine/Shaders/IrradianceReduceSH_1.bsl.asset
  12. BIN
      Data/Engine/Shaders/LightGridLLCreation.bsl.asset
  13. BIN
      Data/Engine/Shaders/PPBuildHiZ.bsl.asset
  14. BIN
      Data/Engine/Shaders/PPCreateTonemapLUT.bsl.asset
  15. BIN
      Data/Engine/Shaders/PPCreateTonemapLUT_1.bsl.asset
  16. BIN
      Data/Engine/Shaders/PPEyeAdaptation.bsl.asset
  17. BIN
      Data/Engine/Shaders/PPEyeAdaptationBasic.bsl.asset
  18. BIN
      Data/Engine/Shaders/PPEyeAdaptationBasicSetup.bsl.asset
  19. BIN
      Data/Engine/Shaders/PPTonemapping.bsl.asset
  20. BIN
      Data/Engine/Shaders/PPTonemapping_1.bsl.asset
  21. BIN
      Data/Engine/Shaders/PPTonemapping_10.bsl.asset
  22. BIN
      Data/Engine/Shaders/PPTonemapping_11.bsl.asset
  23. BIN
      Data/Engine/Shaders/PPTonemapping_12.bsl.asset
  24. BIN
      Data/Engine/Shaders/PPTonemapping_13.bsl.asset
  25. BIN
      Data/Engine/Shaders/PPTonemapping_14.bsl.asset
  26. BIN
      Data/Engine/Shaders/PPTonemapping_15.bsl.asset
  27. BIN
      Data/Engine/Shaders/PPTonemapping_2.bsl.asset
  28. BIN
      Data/Engine/Shaders/PPTonemapping_3.bsl.asset
  29. BIN
      Data/Engine/Shaders/PPTonemapping_4.bsl.asset
  30. BIN
      Data/Engine/Shaders/PPTonemapping_5.bsl.asset
  31. BIN
      Data/Engine/Shaders/PPTonemapping_6.bsl.asset
  32. BIN
      Data/Engine/Shaders/PPTonemapping_7.bsl.asset
  33. BIN
      Data/Engine/Shaders/PPTonemapping_8.bsl.asset
  34. BIN
      Data/Engine/Shaders/PPTonemapping_9.bsl.asset
  35. BIN
      Data/Engine/Shaders/ReflectionCubeDownsample.bsl.asset
  36. BIN
      Data/Engine/Shaders/ReflectionCubeImportanceSample.bsl.asset
  37. BIN
      Data/Engine/Shaders/ShadowProject.bsl.asset
  38. BIN
      Data/Engine/Shaders/ShadowProject_1.bsl.asset
  39. BIN
      Data/Engine/Shaders/ShadowProject_10.bsl.asset
  40. BIN
      Data/Engine/Shaders/ShadowProject_11.bsl.asset
  41. BIN
      Data/Engine/Shaders/ShadowProject_12.bsl.asset
  42. BIN
      Data/Engine/Shaders/ShadowProject_13.bsl.asset
  43. BIN
      Data/Engine/Shaders/ShadowProject_14.bsl.asset
  44. BIN
      Data/Engine/Shaders/ShadowProject_15.bsl.asset
  45. BIN
      Data/Engine/Shaders/ShadowProject_2.bsl.asset
  46. BIN
      Data/Engine/Shaders/ShadowProject_3.bsl.asset
  47. BIN
      Data/Engine/Shaders/ShadowProject_4.bsl.asset
  48. BIN
      Data/Engine/Shaders/ShadowProject_5.bsl.asset
  49. BIN
      Data/Engine/Shaders/ShadowProject_6.bsl.asset
  50. BIN
      Data/Engine/Shaders/ShadowProject_7.bsl.asset
  51. BIN
      Data/Engine/Shaders/ShadowProject_8.bsl.asset
  52. BIN
      Data/Engine/Shaders/ShadowProject_9.bsl.asset
  53. BIN
      Data/Engine/Shaders/TiledDeferredImageBasedLighting.bsl.asset
  54. BIN
      Data/Engine/Shaders/TiledDeferredImageBasedLighting_1.bsl.asset
  55. BIN
      Data/Engine/Shaders/TiledDeferredImageBasedLighting_2.bsl.asset
  56. BIN
      Data/Engine/Shaders/TiledDeferredImageBasedLighting_3.bsl.asset
  57. BIN
      Data/Engine/Shaders/TiledDeferredLighting.bsl.asset
  58. BIN
      Data/Engine/Shaders/TiledDeferredLighting_1.bsl.asset
  59. BIN
      Data/Engine/Shaders/TiledDeferredLighting_2.bsl.asset
  60. BIN
      Data/Engine/Shaders/TiledDeferredLighting_3.bsl.asset
  61. BIN
      Data/Engine/Shaders/Transparent.bsl.asset
  62. 25 1
      Data/Raw/Engine/DataList.json
  63. 35 0
      Data/Raw/Engine/Includes/PPEyeAdaptationCommon.bslinc
  64. 25 0
      Data/Raw/Engine/Includes/ReflectionCubemapCommon.bslinc
  65. 42 0
      Data/Raw/Engine/Shaders/IrradianceAccumulateCubeSH.bsl
  66. 48 0
      Data/Raw/Engine/Shaders/IrradianceAccumulateSH.bsl
  67. 0 25
      Data/Raw/Engine/Shaders/IrradianceComputeSH.bsl
  68. 51 0
      Data/Raw/Engine/Shaders/IrradianceComputeSHFrag.bsl
  69. 53 12
      Data/Raw/Engine/Shaders/PPCreateTonemapLUT.bsl
  70. 3 31
      Data/Raw/Engine/Shaders/PPEyeAdaptation.bsl
  71. 79 0
      Data/Raw/Engine/Shaders/PPEyeAdaptationBasic.bsl
  72. 29 0
      Data/Raw/Engine/Shaders/PPEyeAdaptationBasicSetup.bsl
  73. 24 0
      Data/Raw/Engine/Shaders/PPTonemapping.bsl
  74. 17 0
      Source/BansheeSL/BsASTFX.c
  75. 1 0
      Source/BansheeSL/BsASTFX.h
  76. 19 7
      Source/BansheeSL/BsLexerFX.l
  77. 267 96
      Source/RenderBeast/BsPostProcessing.cpp
  78. 106 12
      Source/RenderBeast/BsPostProcessing.h
  79. 1 1
      Source/RenderBeast/BsRenderBeast.cpp
  80. 257 25
      Source/RenderBeast/BsRenderBeastIBLUtility.cpp
  81. 113 0
      Source/RenderBeast/BsRenderBeastIBLUtility.h
  82. 96 33
      Source/RenderBeast/BsRenderCompositor.cpp
  83. 9 2
      Source/RenderBeast/BsRenderCompositor.h

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


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


+ 49 - 0
Data/Engine/ShaderDependencies.json

@@ -89,6 +89,22 @@
         }
     ],
     "FlatFramebufferToTexture.bsl": null,
+    "IrradianceAccumulateCubeSH.bsl": [
+        {
+            "Path": "PPBase.bslinc"
+        },
+        {
+            "Path": "ReflectionCubemapCommon.bslinc"
+        }
+    ],
+    "IrradianceAccumulateSH.bsl": [
+        {
+            "Path": "PPBase.bslinc"
+        },
+        {
+            "Path": "ReflectionCubemapCommon.bslinc"
+        }
+    ],
     "IrradianceComputeSH.bsl": [
         {
             "Path": "SHCommon.bslinc"
@@ -97,6 +113,17 @@
             "Path": "ReflectionCubemapCommon.bslinc"
         }
     ],
+    "IrradianceComputeSHFrag.bsl": [
+        {
+            "Path": "PPBase.bslinc"
+        },
+        {
+            "Path": "SHCommon.bslinc"
+        },
+        {
+            "Path": "ReflectionCubemapCommon.bslinc"
+        }
+    ],
     "IrradianceEvaluate.bsl": [
         {
             "Path": "PerCameraData.bslinc"
@@ -186,6 +213,9 @@
         }
     ],
     "PPCreateTonemapLUT.bsl": [
+        {
+            "Path": "PPBase.bslinc"
+        },
         {
             "Path": "PPWhiteBalance.bslinc"
         },
@@ -213,6 +243,25 @@
         }
     ],
     "PPEyeAdaptation.bsl": [
+        {
+            "Path": "PPEyeAdaptationCommon.bslinc"
+        },
+        {
+            "Path": "PPBase.bslinc"
+        }
+    ],
+    "PPEyeAdaptationBasic.bsl": [
+        {
+            "Path": "PPEyeAdaptationCommon.bslinc"
+        },
+        {
+            "Path": "PPBase.bslinc"
+        }
+    ],
+    "PPEyeAdaptationBasicSetup.bsl": [
+        {
+            "Path": "PPEyeAdaptationCommon.bslinc"
+        },
         {
             "Path": "PPBase.bslinc"
         }

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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


+ 25 - 1
Data/Raw/Engine/DataList.json

@@ -1,4 +1,4 @@
-{
+{
     "Cursors": [
         {
             "Path": "Arrow.psd",
@@ -155,6 +155,10 @@
         {
             "Path": "ImportanceSampling.bslinc",
             "UUID": "52a46920-860e-4d05-a7c3-34e926b02664"
+        },
+        {
+            "Path": "PPEyeAdaptationCommon.bslinc",
+            "UUID": "1a0c4fb5-49b6-7eb8-28b8-49b698a6b7fe"
         }
     ],
     "Shaders": [
@@ -373,6 +377,26 @@
         {
             "Path": "MSAACoverageStencil.bsl",
             "UUID": "f4344aa8-d286-4f10-b25d-a76302487fa8"
+        },
+        {
+            "Path": "IrradianceAccumulateCubeSH.bsl",
+            "UUID": "92c3b1a4-4f21-6566-7cbe-4f21bcc8e0aa"
+        },
+        {
+            "Path": "IrradianceAccumulateSH.bsl",
+            "UUID": "c52783b0-4974-f349-0fae-4974f81eb485"
+        },
+        {
+            "Path": "IrradianceComputeSHFrag.bsl",
+            "UUID": "cc8139f7-4a48-4a74-2691-4a48e288b8dd"
+        },
+        {
+            "Path": "PPEyeAdaptationBasic.bsl",
+            "UUID": "b00f745f-4d40-5ebf-bcae-4d40161ceb6b"
+        },
+        {
+            "Path": "PPEyeAdaptationBasicSetup.bsl",
+            "UUID": "86cda288-4fd5-4d0f-a5a2-4fd56d3b92ea"
         }
     ],
     "Skin": [

+ 35 - 0
Data/Raw/Engine/Includes/PPEyeAdaptationCommon.bslinc

@@ -0,0 +1,35 @@
+mixin PPEyeAdaptationParams
+{
+	code
+	{
+		[internal]
+		cbuffer EyeAdaptationParams
+		{
+			// [0]: x - histogram scale, y - histogram offset, z - histogram percent low, w - histogram percent high
+			// [1]: x - min adaptation, y - max adaptation, z - adaptation speed up, w - adaptation speed down
+			// [2]: x - exposure scale, y - frame time delta, z - min. allowed intensity, w - nothing
+			float4 gEyeAdaptationParams[3];
+		}
+		
+		/** 
+		 * Smooths out eye adaptation changes over multiple frames so they aren't as jarring.
+		 *
+		 * @param	old			Eye adaptation value from the previous frame.
+		 * @param	target		Ideal eye adaptation value for this frame.
+		 * @param	frameDelta	Time difference between this and last frame, in seconds.
+		 * @return				Smoothed eye adaptation.
+		 */
+		float smoothEyeAdaptation(float old, float target, float frameDelta)
+		{
+			float diff = target - old;
+
+			float speedUp = gEyeAdaptationParams[1].z;
+			float speedDown = gEyeAdaptationParams[1].w;
+
+			float adaptionSpeed = (diff > 0) ? speedUp : speedDown;
+			float scale = 1.0f - exp2(-frameDelta * adaptionSpeed);
+
+			return clamp(old + diff * scale, gEyeAdaptationParams[1].x, gEyeAdaptationParams[1].y);
+		}		
+	};
+};

+ 25 - 0
Data/Raw/Engine/Includes/ReflectionCubemapCommon.bslinc

@@ -22,6 +22,31 @@ mixin ReflectionCubemapCommon
 			return dir;
 		}
 		
+		/** 
+		 * Integrates area of a cube face projected onto the surface of the sphere, from [0, 0] to [u, v]. 
+		 * u & v expected in [-1, -1] to [1, 1] range.
+		 *
+		 * See http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/ for derivation.
+		 */
+		float integrateProjectedCubeArea(float u, float v)
+		{
+			return atan2(u * v, sqrt(u * u + v * v + 1.0f));
+		}
+		
+		/** Calculates solid angle of a texel projected onto a sphere. */
+		float texelSolidAngle(float u, float v, float invFaceSize)
+		{
+			float x0 = u - invFaceSize;
+			float x1 = u + invFaceSize;
+			float y0 = v - invFaceSize;
+			float y1 = v + invFaceSize;
+
+			return   integrateProjectedCubeArea(x1, y1)
+				   - integrateProjectedCubeArea(x0, y1)
+				   - integrateProjectedCubeArea(x1, y0)
+				   + integrateProjectedCubeArea(x0, y0);
+		}		
+		
 		/**
 		 * Calculates a mip level to sample from based on roughness value.
 		 *

+ 42 - 0
Data/Raw/Engine/Shaders/IrradianceAccumulateCubeSH.bsl

@@ -0,0 +1,42 @@
+#include "$ENGINE$\ReflectionCubemapCommon.bslinc"
+#include "$ENGINE$\PPBase.bslinc"
+
+technique IrradianceAccumulateCubeSH
+{
+	mixin PPBase;
+	mixin ReflectionCubemapCommon;
+
+	code
+	{
+		#define PI 3.1415926
+	
+		SamplerState gInputSamp;
+		TextureCube gInputTex;
+	
+		[internal]
+		cbuffer Params
+		{
+			uint gCubeFace;
+			uint gCubeMip;
+			float2 gHalfPixel;
+		}			
+				
+		float4 fsmain(VStoFS input) : SV_Target0
+		{		
+			float2 offset[4];
+			offset[0] = float2(gHalfPixel.x, gHalfPixel.y);
+			offset[1] = float2(-gHalfPixel.x, gHalfPixel.y);
+			offset[2] = float2(gHalfPixel.x, -gHalfPixel.y);
+			offset[3] = float2(-gHalfPixel.x, -gHalfPixel.y);
+			
+			float4 sum = gInputTex.SampleLevel(gInputSamp, float3(1, 0, 0), gCubeMip);
+			sum += gInputTex.SampleLevel(gInputSamp, float3(-1, 0, 0), gCubeMip);
+			sum += gInputTex.SampleLevel(gInputSamp, float3(0, 1, 0), gCubeMip);
+			sum += gInputTex.SampleLevel(gInputSamp, float3(0, -1, 0), gCubeMip);
+			sum += gInputTex.SampleLevel(gInputSamp, float3(0, 0, 1), gCubeMip);
+			sum += gInputTex.SampleLevel(gInputSamp, float3(0, 0, -1), gCubeMip);
+			
+			return float4(4 * PI * sum.rgb / max(sum.a, 0.0001f), 0.0f);
+		}
+	};
+};

+ 48 - 0
Data/Raw/Engine/Shaders/IrradianceAccumulateSH.bsl

@@ -0,0 +1,48 @@
+#include "$ENGINE$\ReflectionCubemapCommon.bslinc"
+#include "$ENGINE$\PPBase.bslinc"
+
+technique IrradianceAccumulateSH
+{
+	mixin PPBase;
+	mixin ReflectionCubemapCommon;
+
+	code
+	{
+		#define PI 3.1415926
+	
+		SamplerState gInputSamp;
+		TextureCube gInputTex;
+	
+		[internal]
+		cbuffer Params
+		{
+			uint gCubeFace;
+			uint gCubeMip;
+			float2 gHalfPixel;
+		}			
+		
+		float4 fsmain(VStoFS input) : SV_Target0
+		{		
+			float2 offset[4];
+			offset[0] = float2(gHalfPixel.x, gHalfPixel.y);
+			offset[1] = float2(-gHalfPixel.x, gHalfPixel.y);
+			offset[2] = float2(gHalfPixel.x, -gHalfPixel.y);
+			offset[3] = float2(-gHalfPixel.x, -gHalfPixel.y);
+			
+			float4 sum = 0;
+			[unroll]
+			for(int i = 0; i < 4; ++i)
+			{
+				float2 uv = saturate(input.uv0 + offset[i]) * 2.0f - 1.0f;
+				
+				float3 dir = getDirFromCubeFace(gCubeFace, uv);
+				dir = normalize(dir);
+				
+				float4 value = gInputTex.SampleLevel(gInputSamp, dir, gCubeMip).rgb;
+				sum += value;
+			}
+			
+			return sum / 4.0f;
+		}
+	};
+};

+ 0 - 25
Data/Raw/Engine/Shaders/IrradianceComputeSH.bsl

@@ -28,31 +28,6 @@ technique IrradianceComputeSH
 		}			
 		
 		groupshared SHCoeffsAndWeight sCoeffs[TILE_WIDTH * TILE_HEIGHT];
-
-		/** 
-		 * Integrates area of a cube face projected onto the surface of the sphere, from [0, 0] to [u, v]. 
-		 * u & v expected in [-1, -1] to [1, 1] range.
-		 *
-		 * See http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/ for derivation.
-		 */
-		float integrateProjectedCubeArea(float u, float v)
-		{
-			return atan2(u * v, sqrt(u * u + v * v + 1.0f));
-		}
-		
-		/** Calculates solid angle of a texel projected onto a sphere. */
-		float texelSolidAngle(float u, float v, float invFaceSize)
-		{
-			float x0 = u - invFaceSize;
-			float x1 = u + invFaceSize;
-			float y0 = v - invFaceSize;
-			float y1 = v + invFaceSize;
-
-			return   integrateProjectedCubeArea(x1, y1)
-				   - integrateProjectedCubeArea(x0, y1)
-				   - integrateProjectedCubeArea(x1, y0)
-				   + integrateProjectedCubeArea(x0, y0);
-		}
 		
 		[numthreads(TILE_WIDTH, TILE_HEIGHT, 1)]
 		void csmain(

+ 51 - 0
Data/Raw/Engine/Shaders/IrradianceComputeSHFrag.bsl

@@ -0,0 +1,51 @@
+#include "$ENGINE$\ReflectionCubemapCommon.bslinc"
+#define SH_ORDER 3
+#include "$ENGINE$\SHCommon.bslinc"
+#include "$ENGINE$\PPBase.bslinc"
+
+technique IrradianceComputeSHFrag
+{
+	mixin PPBase;
+	mixin ReflectionCubemapCommon;
+	mixin SHCommon;
+
+	code
+	{
+		SamplerState gInputSamp;
+		TextureCube gInputTex;
+	
+		[internal]
+		cbuffer Params
+		{
+			uint gCubeFace;
+			uint gFaceSize;
+			uint gCoeffIdx;
+		}			
+				
+		float4 fsmain(VStoFS input) : SV_Target0
+		{
+			// Move to [-1,1] range
+			float2 uv = input.uv0 * 2.0f - 1.0f;
+			
+			float3 dir = getDirFromCubeFace(gCubeFace, float2(uv.x, uv.y));
+			dir = normalize(dir);
+			
+			// Need to calculate solid angle (weight) of the texel, as cube face corners have
+			// much smaller solid angle, meaning many of them occupy the same area when projected
+			// on a sphere. Without weighing that area would look too bright.
+			float invFaceSize = 1.0f / gFaceSize;
+			float weight = texelSolidAngle(uv.x, uv.y, invFaceSize);
+			
+			SHVector shBasis = SHBasis(dir);
+			float3 radiance = gInputTex.SampleLevel(gInputSamp, dir, 0).rgb;
+						
+			float4 output;
+			output.r = shBasis.v[gCoeffIdx] * radiance.r * weight;
+			output.g = shBasis.v[gCoeffIdx] * radiance.g * weight;
+			output.b = shBasis.v[gCoeffIdx] * radiance.b * weight;
+			output.a = weight;
+			
+			return output;
+		}
+	};
+};

+ 53 - 12
Data/Raw/Engine/Shaders/PPCreateTonemapLUT.bsl

@@ -1,11 +1,17 @@
 #include "$ENGINE$\PPTonemapCommon.bslinc"
 #include "$ENGINE$\PPWhiteBalance.bslinc"
+#include "$ENGINE$\PPBase.bslinc"
 
 technique PPCreateTonemapLUT
 {
 	mixin PPTonemapCommon;
 	mixin PPWhiteBalance;
-
+	
+	#if VOLUME_LUT
+	#else
+	mixin PPBase;
+	#endif
+	
 	code
 	{
 		[internal]
@@ -70,21 +76,16 @@ technique PPCreateTonemapLUT
 			color = color * gGain + gOffset;
 
 			return color;
-		}		
-		
-		RWTexture3D<unorm float4> gOutputTex;
+		}
 		
-		[numthreads(8, 8, 1)]
-		void csmain(
-			uint3 dispatchThreadId : SV_DispatchThreadID,
-			uint threadIndex : SV_GroupIndex)
+		/** Generates tonemap information to the specified color (in log encoded space). */
+		float3 tonemapColor(float3 logColor)
 		{
 			// Constants
 			const float3x3 sRGBToACES2065Matrix = mul(XYZToACES2065Matrix, mul(D65ToD60Matrix, sRGBToXYZMatrix));
 			const float3x3 sRGBToACEScgMatrix = mul(XYZToACEScgMatrix, mul(D65ToD60Matrix, sRGBToXYZMatrix));
-			const float3x3 ACEScgTosRGBMatrix = mul(XYZTosRGBMatrix, mul(D60ToD65Matrix, ACEScgToXYZMatrix));
-			
-			float3 logColor = float3(dispatchThreadId.xyz / (float)(LUT_SIZE - 1));
+			const float3x3 ACEScgTosRGBMatrix = mul(XYZTosRGBMatrix, mul(D60ToD65Matrix, ACEScgToXYZMatrix));		
+		
 			float3 linearColor = LogToLinearColor(logColor);
 			
 			linearColor = WhiteBalance(linearColor);
@@ -106,9 +107,49 @@ technique PPCreateTonemapLUT
 				gammaColor = LinearToGammaRec709(gammaColor);
 			else
 				gammaColor = pow(gammaColor, 1.0f/2.2f);
-			
+				
+			return gammaColor;
+		}
+		
+		#if VOLUME_LUT
+		RWTexture3D<unorm float4> gOutputTex;
+		
+		[numthreads(8, 8, 1)]
+		void csmain(
+			uint3 dispatchThreadId : SV_DispatchThreadID,
+			uint threadIndex : SV_GroupIndex)
+		{
+			float3 logColor = float3(dispatchThreadId.xyz / (float)(LUT_SIZE - 1));
+			float3 gammaColor = tonemapColor(logColor);
+							
 			// TODO - Divide by 1.05f here and then re-apply it when decoding from the texture?
 			gOutputTex[dispatchThreadId] = float4(gammaColor, 1.0f);	
+		}
+		#else
+		float4 fsmain(VStoFS input) : SV_Target0
+		{
+			float3 logColor;
+			
+			float2 uv = input.uv0;
+			// Make sure uv maps to [0,1], as it's currently specified for pixel centers
+			// (This way we get non-extrapolated color values for 0 and 1)
+			uv -= float2(0.5f / (LUT_SIZE * LUT_SIZE), 0.5f / LUT_SIZE);
+			uv *= LUT_SIZE / (LUT_SIZE - 1);
+			
+			// Red goes from 0 to 1, in each slice along X (LUT_SIZE number of slices)
+			logColor.r = frac(uv.x * LUT_SIZE);
+			
+			// Green value is constant within each slice, and increases by 1/LUT_SIZE with each slice along X
+			logColor.g = uv.x - logColor.r / LUT_SIZE;
+			
+			// Blue increases linearly with y
+			logColor.b = uv.y;
+			
+			float3 gammaColor = tonemapColor(logColor);
+							
+			// TODO - Divide by 1.05f here and then re-apply it when decoding from the texture?
+			return float4(gammaColor, 1.0f);	
 		}	
+		#endif
 	};
 };

+ 3 - 31
Data/Raw/Engine/Shaders/PPEyeAdaptation.bsl

@@ -1,22 +1,15 @@
 #include "$ENGINE$\PPBase.bslinc"
+#include "$ENGINE$\PPEyeAdaptationCommon.bslinc"
 
 technique PPEyeAdaptation
 {
 	mixin PPBase;
+	mixin PPEyeAdaptationParams;
 
 	code
 	{
 		#define NUM_BUCKETS (THREADGROUP_SIZE_X * THREADGROUP_SIZE_Y)
 	
-		[internal]
-		cbuffer Input
-		{
-			// [0]: x - histogram scale, y - histogram offset, z - histogram percent low, w - histogram percent high
-			// [1]: x - min adaptation, y - max adaptation, z - adaptation speed up, w - adaptation speed down
-			// [2]: x - exposure scale, y - frame time delta, zw - nothing
-			float4 gEyeAdaptationParams[3];
-		}		
-		
 		Texture2D gHistogramTex;
 		
 		/** 
@@ -123,28 +116,7 @@ technique PPEyeAdaptation
 
 			return avgLuminance;
 		}
-		
-		/** 
-		 * Smooths out eye adaptation changes over multiple frames so they aren't as jarring.
-		 *
-		 * @param	old			Eye adaptation value from the previous frame.
-		 * @param	target		Ideal eye adaptation value for this frame.
-		 * @param	frameDelta	Time difference between this and last frame, in seconds.
-		 * @return				Smoothed eye adaptation.
-		 */
-		float smoothEyeAdaptation(float old, float target, float frameDelta)
-		{
-			float diff = target - old;
-
-			float speedUp = gEyeAdaptationParams[1].z;
-			float speedDown = gEyeAdaptationParams[1].w;
-
-			float adaptionSpeed = (diff > 0) ? speedUp : speedDown;
-			float scale = 1.0f - exp2(-frameDelta * adaptionSpeed);
-
-			return clamp(old + diff * scale, gEyeAdaptationParams[1].x, gEyeAdaptationParams[1].y);
-		}
-		
+				
 		float4 fsmain(VStoFS input) : SV_Target0
 		{
 			float exposureScale = gEyeAdaptationParams[2].x;

+ 79 - 0
Data/Raw/Engine/Shaders/PPEyeAdaptationBasic.bsl

@@ -0,0 +1,79 @@
+#include "$ENGINE$\PPBase.bslinc"
+#include "$ENGINE$\PPEyeAdaptationCommon.bslinc"
+
+technique PPEyeAdaptationBasic
+{
+	mixin PPBase;
+	mixin PPEyeAdaptationParams;
+
+	code
+	{
+		[internal]
+		cbuffer Input
+		{
+			int2 gInputTexSize;
+		}		
+	
+		Texture2D gCurFrameTex;
+		Texture2D gPrevFrameTex;
+		
+		/** 
+		 * Returns the value in range [0,1] on the triangle function with slope @p slope. 
+		 * @p t must be in range [0,1].
+		 */
+		float triangleFunc(float t, float slope)
+		{
+			return max(1.0f - 2.0f * slope * abs(t - 0.5f), 0.0f);
+		}		
+		
+		/** 
+		 * Calculates the average value of all pixels in the input texture. The pixels are
+		 * weighted using a triangle filter using @p slope as the line slope. @p slope of 0
+		 * means all pixels will be uniformly weighted.
+		 */
+		float calcWeightedAverageAlpha(Texture2D input, int2 inputSize, float slope)
+		{
+			float2 invSize = 1.0f / inputSize;
+
+			float sum = 0.0f;
+			float weightSum = 0.0f;
+			for (uint i = 0; i < inputSize.x; ++i)
+			{
+				float weightX = triangleFunc(i * invSize.x, slope);  
+				for (uint j = 0; j < inputSize.y; ++j)
+				{
+					float weightY = triangleFunc(j * invSize.y, slope);
+					float weight = max(weightX * weightY, 0.05f);
+	
+					float value = input.Load(int3(i, j, 0)).a;
+					
+					sum += value * weight;
+					weightSum += weight;
+				}
+			}
+
+			return sum / weightSum;
+		}		
+		
+		float4 fsmain(VStoFS input) : SV_Target0
+		{
+			float slope = 0.0f; // TODO - Allow custom slope?
+			float avgLuminance = calcWeightedAverageAlpha(gCurFrameTex, gInputTexSize, slope);
+		
+			// Scale back into normal range (was log2 encoded and scale into [0, 1] range)
+			avgLuminance = exp2((pos - gEyeAdaptationParams[0].y) / gEyeAdaptationParams[0].x);
+		
+			// Clamp to valid range
+			avgLuminance = clamp(avgLuminance, gEyeAdaptationParams[1].x, gEyeAdaptationParams[1].y);
+			
+			float exposureScale = gEyeAdaptationParams[2].x;
+			float oldExposure = gPrevFrameTex.Load(int3(0, 0, 0)).x;
+			float oldLuminance = exposureScale / oldExposure; // Assuming same exposure scale as last frame
+			
+			float frameDelta = gEyeAdaptationParams[2].y;
+			float smoothAdaptation = smoothEyeAdaptation(oldLuminance, avgLuminance, frameDelta);
+			
+			return exposureScale / smoothAdaptation; // Returns exposure
+		}	
+	};
+};

+ 29 - 0
Data/Raw/Engine/Shaders/PPEyeAdaptationBasicSetup.bsl

@@ -0,0 +1,29 @@
+#include "$ENGINE$\PPBase.bslinc"
+#include "$ENGINE$\PPEyeAdaptationCommon.bslinc"
+
+technique PPEyeAdaptationBasicSetup
+{
+	mixin PPBase;
+	mixin PPEyeAdaptationParams;
+
+	code
+	{
+		Texture2D gInputTex;
+		
+		[alias(gInputTex)]
+		SamplerState gInputSamp;
+		
+		float4 fsmain(VStoFS input) : SV_Target0
+		{
+			float4 value = gInputTex.Sample(gInputSamp, input.uv0);
+			
+			float luminance = dot(OutColor.xyz, float3(0.2126, 0.7152, 0.0722));
+			
+			float maxIntensity = gEyeAdaptationParams[2].z;
+			luminance = max(maxIntensity, luminance);
+			
+			// Store intensity as log, and scale to [0, 1] range
+			value.w = gEyeAdaptationParams[0].x * log2(luminance) + gEyeAdaptationParams[0].y;
+		}	
+	};
+};

+ 24 - 0
Data/Raw/Engine/Shaders/PPTonemapping.bsl

@@ -46,7 +46,12 @@ technique PPTonemapping
 		#endif
 		
 		SamplerState gColorLUTSamp;
+		
+		#if VOLUME_LUT
 		Texture3D gColorLUT;
+		#else
+		Texture2D gColorLUT;
+		#endif
 		
 		cbuffer Input
 		{
@@ -60,7 +65,26 @@ technique PPTonemapping
 			float3 logColor = LinearToLogColor(linearColor);
 			float3 UVW = logColor * ((LUT_SIZE - 1) / (float)LUT_SIZE) + (0.5f / LUT_SIZE);
 			
+			#if VOLUME_LUT
+			
 			float3 gradedColor = gColorLUT.Sample(gColorLUTSamp, UVW).rgb;
+			
+			#else
+			
+			float slice = floor(UVW.z * LUT_SIZE - 0.5f);
+			float sliceFrac = UVW.z * LUT_SIZE - 0.5f - slice;
+
+			float U = (UVW.x + slice) / LUT_SIZE;
+			float V = UVW.y;
+
+			// Blend between two slices (emulating a 3D texture sample)
+			float3 v0 = gColorLUT.Sample(gColorLUTSamp, float2(U, V)).rgb;
+			float3 v1 = gColorLUT.Sample(gColorLUTSamp, float2(U + 1.0f / LUT_SIZE, V)).rgb;
+
+			float3 gradedColor = lerp(v0, v1, sliceFrac);
+			
+			#endif
+			
 			return gradedColor;
 		}
 		

+ 17 - 0
Source/BansheeSL/BsASTFX.c

@@ -290,6 +290,23 @@ int hasDefine(ParseState* parseState, const char* value)
 	return 0;
 }
 
+int isDefineEnabled(ParseState* parseState, const char* value)
+{
+	for (int i = 0; i < parseState->numDefines; i++)
+	{
+		if (strcmp(parseState->defines[i].name, value) == 0)
+		{
+			if(parseState->defines[i].expr == 0)
+				return 0;
+
+			int val = atoi(parseState->defines[i].expr);
+			return val != 0;
+		}
+	}
+
+	return 0;
+}
+
 void removeDefine(ParseState* parseState, const char* value)
 {
 	for (int i = 0; i < parseState->numDefines; i++)

+ 1 - 0
Source/BansheeSL/BsASTFX.h

@@ -263,6 +263,7 @@ int getCodeBlockIndex(ParseState* parseState);
 void addDefine(ParseState* parseState, const char* value);
 void addDefineExpr(ParseState* parseState, const char* value);
 int hasDefine(ParseState* parseState, const char* value);
+int isDefineEnabled(ParseState* parseState, const char* value);
 void removeDefine(ParseState* parseState, const char* value);
 
 int pushConditional(ParseState* parseState, int state);

+ 19 - 7
Source/BansheeSL/BsLexerFX.l

@@ -30,7 +30,8 @@ DEFINE_EXPR		[^\r\n]*
 %x DEFINE_COND_EXPR
 %x UNDEF_COND
 %x CONDITIONAL_IF
-%x CONDITIONAL_IFN
+%x CONDITIONAL_IFDEF
+%x CONDITIONAL_IFNDEF
 %x CONDITIONAL_ELIF
 %x CONDITIONAL_IGNORE
 
@@ -207,27 +208,38 @@ RGBA			{ yylval->intValue = 0xF; return TOKEN_COLORMASK; }
 <UNDEF_COND>{IDENTIFIER}		{ removeDefine(yyextra, yytext); BEGIN(INITIAL); }
 <UNDEF_COND>.					{ return yytext[0]; }
 
-#ifdef							{ BEGIN(CONDITIONAL_IF); }
+#if								{ BEGIN(CONDITIONAL_IF); }
 <CONDITIONAL_IF>{WS}			{ /* Skip blank */ }
 <CONDITIONAL_IF>{IDENTIFIER}	{ 
+	int isEnabled = pushConditional(yyextra, isDefineEnabled(yyextra, yytext));
+	if(!isEnabled)
+		BEGIN(CONDITIONAL_IGNORE);
+	else
+		BEGIN(INITIAL);
+}
+<CONDITIONAL_IF>.
+
+#ifdef							{ BEGIN(CONDITIONAL_IFDEF); }
+<CONDITIONAL_IFDEF>{WS}			{ /* Skip blank */ }
+<CONDITIONAL_IFDEF>{IDENTIFIER}	{ 
 	int isEnabled = pushConditional(yyextra, hasDefine(yyextra, yytext));
 	if(!isEnabled)
 		BEGIN(CONDITIONAL_IGNORE);
 	else
 		BEGIN(INITIAL);
 }
-<CONDITIONAL_IF>.				{ return yytext[0]; }
+<CONDITIONAL_IFDEF>.				{ return yytext[0]; }
 
-#ifndef							{ BEGIN(CONDITIONAL_IFN); }
-<CONDITIONAL_IFN>{WS}			{ /* Skip blank */ }
-<CONDITIONAL_IFN>{IDENTIFIER}	{ 
+#ifndef								{ BEGIN(CONDITIONAL_IFNDEF); }
+<CONDITIONAL_IFNDEF>{WS}			{ /* Skip blank */ }
+<CONDITIONAL_IFNDEF>{IDENTIFIER}	{ 
 	int isEnabled = pushConditional(yyextra, !hasDefine(yyextra, yytext));
 	if(!isEnabled)
 		BEGIN(CONDITIONAL_IGNORE);
 	else
 		BEGIN(INITIAL);
 }
-<CONDITIONAL_IFN>.				{ return yytext[0]; }
+<CONDITIONAL_IFNDEF>.				{ return yytext[0]; }
 
 #else							{ 
 	if(!switchConditional(yyextra))

+ 267 - 96
Source/RenderBeast/BsPostProcessing.cpp

@@ -279,7 +279,7 @@ namespace bs { namespace ct
 		mParamBuffer = gEyeAdaptationParamDef.createBuffer();
 
 		SPtr<GpuParams> gpuParams = mParamsSet->getGpuParams();
-		gpuParams->setParamBlockBuffer("Input", mParamBuffer);
+		gpuParams->setParamBlockBuffer("EyeAdaptationParams", mParamBuffer);
 		gpuParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gHistogramTex", mReducedHistogramTex);
 	}
 
@@ -299,6 +299,27 @@ namespace bs { namespace ct
 		// Set parameters
 		mReducedHistogramTex.set(reducedHistogram);
 
+		populateParams(mParamBuffer, frameDelta, settings, exposureScale);
+
+		// Render
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(output, FBT_DEPTH | FBT_STENCIL);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+		gRendererUtility().drawScreenQuad();
+
+		rapi.setRenderTarget(nullptr);
+	}
+
+	POOLED_RENDER_TEXTURE_DESC EyeAdaptationMat::getOutputDesc()
+	{
+		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_R32F, 1, 1, TU_RENDERTARGET);
+	}
+
+	void EyeAdaptationMat::populateParams(const SPtr<GpuParamBlockBuffer>& paramBuffer, float frameDelta, 
+		const AutoExposureSettings& settings, float exposureScale)
+	{
 		Vector2 histogramScaleAndOffset = EyeAdaptHistogramMat::getHistogramScaleOffset(settings);
 
 		Vector4 eyeAdaptationParams[3];
@@ -319,16 +340,47 @@ namespace bs { namespace ct
 		eyeAdaptationParams[2].x = Math::pow(2.0f, exposureScale);
 		eyeAdaptationParams[2].y = frameDelta;
 
-		eyeAdaptationParams[2].z = 0.0f; // Unused
+		eyeAdaptationParams[2].z = Math::pow(2.0f, settings.histogramLog2Min);
 		eyeAdaptationParams[2].w = 0.0f; // Unused
 
-		gEyeAdaptationParamDef.gEyeAdaptationParams.set(mParamBuffer, eyeAdaptationParams[0], 0);
-		gEyeAdaptationParamDef.gEyeAdaptationParams.set(mParamBuffer, eyeAdaptationParams[1], 1);
-		gEyeAdaptationParamDef.gEyeAdaptationParams.set(mParamBuffer, eyeAdaptationParams[2], 2);
+		gEyeAdaptationParamDef.gEyeAdaptationParams.set(paramBuffer, eyeAdaptationParams[0], 0);
+		gEyeAdaptationParamDef.gEyeAdaptationParams.set(paramBuffer, eyeAdaptationParams[1], 1);
+		gEyeAdaptationParamDef.gEyeAdaptationParams.set(paramBuffer, eyeAdaptationParams[2], 2);
+	}
+
+	EyeAdaptationBasicSetupMat::EyeAdaptationBasicSetupMat()
+	{
+		mParamBuffer = gEyeAdaptationParamDef.createBuffer();
+
+		SPtr<GpuParams> gpuParams = mParamsSet->getGpuParams();
+		gpuParams->setParamBlockBuffer("EyeAdaptationParams", mParamBuffer);
+		gpuParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex", mInputTex);
+
+		SAMPLER_STATE_DESC desc;
+		desc.minFilter = FO_POINT;
+		desc.magFilter = FO_POINT;
+		desc.mipFilter = FO_POINT;
+
+		SPtr<SamplerState> samplerState = SamplerState::create(desc);
+		setSamplerState(gpuParams, GPT_FRAGMENT_PROGRAM, "gInputSamp", "gInputTex", samplerState);
+	}
+
+	void EyeAdaptationBasicSetupMat::_initVariations(ShaderVariations& variations)
+	{
+		// Do nothing
+	}
+
+	void EyeAdaptationBasicSetupMat::execute(const SPtr<Texture>& input, const SPtr<RenderTarget>& output, 
+		float frameDelta, const AutoExposureSettings& settings, float exposureScale)
+	{
+		// Set parameters
+		mInputTex.set(input);
+
+		EyeAdaptationMat::populateParams(mParamBuffer, frameDelta, settings, exposureScale);
 
 		// Render
 		RenderAPI& rapi = RenderAPI::instance();
-		rapi.setRenderTarget(output, FBT_DEPTH | FBT_STENCIL);
+		rapi.setRenderTarget(output);
 
 		gRendererUtility().setPass(mMaterial);
 		gRendererUtility().setPassParams(mParamsSet);
@@ -337,7 +389,61 @@ namespace bs { namespace ct
 		rapi.setRenderTarget(nullptr);
 	}
 
-	POOLED_RENDER_TEXTURE_DESC EyeAdaptationMat::getOutputDesc()
+	POOLED_RENDER_TEXTURE_DESC EyeAdaptationBasicSetupMat::getOutputDesc(const SPtr<Texture>& input)
+	{
+		auto& props = input->getProperties();
+		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, props.getWidth(), props.getHeight(), TU_RENDERTARGET);
+	}
+
+	EyeAdaptationBasicParamsMatDef gEyeAdaptationBasicParamsMatDef;
+
+	EyeAdaptationBasicMat::EyeAdaptationBasicMat()
+	{
+		mEyeAdaptationParamsBuffer = gEyeAdaptationParamDef.createBuffer();
+		mParamsBuffer = gEyeAdaptationBasicParamsMatDef.createBuffer();
+
+		SPtr<GpuParams> gpuParams = mParamsSet->getGpuParams();
+		gpuParams->setParamBlockBuffer("EyeAdaptationParams", mEyeAdaptationParamsBuffer);
+		gpuParams->setParamBlockBuffer("Input", mParamsBuffer);
+		gpuParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gCurFrameTex", mCurFrameTexParam);
+		gpuParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gPrevFrameTex", mPrevFrameTexParam);
+	}
+
+	void EyeAdaptationBasicMat::_initVariations(ShaderVariations& variations)
+	{
+		// Do nothing
+	}
+
+	void EyeAdaptationBasicMat::execute(const SPtr<Texture>& curFrame, const SPtr<Texture>& prevFrame, 
+		const SPtr<RenderTarget>& output, float frameDelta, const AutoExposureSettings& settings, float exposureScale)
+	{
+		// Set parameters
+		mCurFrameTexParam.set(curFrame);
+
+		if (prevFrame == nullptr) // Could be that this is the first run
+			mPrevFrameTexParam.set(Texture::WHITE);
+		else
+			mPrevFrameTexParam.set(prevFrame);
+
+		EyeAdaptationMat::populateParams(mEyeAdaptationParamsBuffer, frameDelta, settings, exposureScale);
+
+		auto& texProps = curFrame->getProperties();
+		Vector2I texSize = { (INT32)texProps.getWidth(), (INT32)texProps.getHeight() };
+
+		gEyeAdaptationBasicParamsMatDef.gInputTexSize.set(mParamsBuffer, texSize);
+
+		// Render
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(output);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+		gRendererUtility().drawScreenQuad();
+
+		rapi.setRenderTarget(nullptr);
+	}
+
+	POOLED_RENDER_TEXTURE_DESC EyeAdaptationBasicMat::getOutputDesc()
 	{
 		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_R32F, 1, 1, TU_RENDERTARGET);
 	}
@@ -345,27 +451,71 @@ namespace bs { namespace ct
 	CreateTonemapLUTParamDef gCreateTonemapLUTParamDef;
 	WhiteBalanceParamDef gWhiteBalanceParamDef;
 
+	ShaderVariation CreateTonemapLUTMat::VAR_3D = ShaderVariation({
+		ShaderVariation::Param("LUT_SIZE", LUT_SIZE),
+		ShaderVariation::Param("VOLUME_LUT", true),
+	});
+
+	ShaderVariation CreateTonemapLUTMat::VAR_Unwrapped2D = ShaderVariation({
+		ShaderVariation::Param("LUT_SIZE", LUT_SIZE),
+		ShaderVariation::Param("VOLUME_LUT", false),
+	});
+
 	CreateTonemapLUTMat::CreateTonemapLUTMat()
 	{
+		mIs3D = mVariation.getBool("VOLUME_LUT");
+
 		mParamBuffer = gCreateTonemapLUTParamDef.createBuffer();
 		mWhiteBalanceParamBuffer = gWhiteBalanceParamDef.createBuffer();
 
 		SPtr<GpuParams> params = mParamsSet->getGpuParams();
 		params->setParamBlockBuffer("Input", mParamBuffer);
 		params->setParamBlockBuffer("WhiteBalanceInput", mWhiteBalanceParamBuffer);
-		params->getLoadStoreTextureParam(GPT_COMPUTE_PROGRAM, "gOutputTex", mOutputTex);
+
+		if(mIs3D)
+			params->getLoadStoreTextureParam(GPT_COMPUTE_PROGRAM, "gOutputTex", mOutputTex);
 	}
 
 	void CreateTonemapLUTMat::_initVariations(ShaderVariations& variations)
 	{
-		ShaderVariation variation({
-			ShaderVariation::Param("LUT_SIZE", LUT_SIZE)
-		});
+		variations.add(VAR_3D);
+		variations.add(VAR_Unwrapped2D);
+	}
 
-		variations.add(variation);
+	void CreateTonemapLUTMat::execute3D(const SPtr<Texture>& output, const RenderSettings& settings)
+	{
+		assert(mIs3D);
+
+		populateParamBuffers(settings);
+
+		// Dispatch
+		mOutputTex.set(output);
+
+		RenderAPI& rapi = RenderAPI::instance();
+		
+		gRendererUtility().setComputePass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+		rapi.dispatchCompute(LUT_SIZE / 8, LUT_SIZE / 8, LUT_SIZE);
 	}
 
-	void CreateTonemapLUTMat::execute(const SPtr<Texture>& output, const RenderSettings& settings)
+	void CreateTonemapLUTMat::execute2D(const SPtr<RenderTexture>& output, const RenderSettings& settings)
+	{
+		assert(!mIs3D);
+
+		populateParamBuffers(settings);
+
+		// Render
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(output);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+		gRendererUtility().drawScreenQuad();
+
+		rapi.setRenderTarget(nullptr);
+	}
+
+	void CreateTonemapLUTMat::populateParamBuffers(const RenderSettings& settings)
 	{
 		// Set parameters
 		gCreateTonemapLUTParamDef.gGammaAdjustment.set(mParamBuffer, 2.2f / settings.gamma);
@@ -397,79 +547,53 @@ namespace bs { namespace ct
 		// Set white balance params
 		gWhiteBalanceParamDef.gWhiteTemp.set(mWhiteBalanceParamBuffer, settings.whiteBalance.temperature);
 		gWhiteBalanceParamDef.gWhiteOffset.set(mWhiteBalanceParamBuffer, settings.whiteBalance.tint);
+	}
 
-		// Dispatch
-		mOutputTex.set(output);
-
-		RenderAPI& rapi = RenderAPI::instance();
+	POOLED_RENDER_TEXTURE_DESC CreateTonemapLUTMat::getOutputDesc() const
+	{
+		if(mIs3D)
+			return POOLED_RENDER_TEXTURE_DESC::create3D(PF_RGBA8, LUT_SIZE, LUT_SIZE, LUT_SIZE, TU_LOADSTORE);
 		
-		gRendererUtility().setComputePass(mMaterial);
-		gRendererUtility().setPassParams(mParamsSet);
-		rapi.dispatchCompute(LUT_SIZE / 8, LUT_SIZE / 8, LUT_SIZE);
+		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA8, LUT_SIZE * LUT_SIZE, LUT_SIZE, TU_RENDERTARGET);
 	}
 
-	POOLED_RENDER_TEXTURE_DESC CreateTonemapLUTMat::getOutputDesc()
+	CreateTonemapLUTMat* CreateTonemapLUTMat::getVariation(bool is3D)
 	{
-		return POOLED_RENDER_TEXTURE_DESC::create3D(PF_RGBA8, LUT_SIZE, LUT_SIZE, LUT_SIZE, TU_LOADSTORE);
+		if(is3D)
+			return get(VAR_3D);
+		
+		return get(VAR_Unwrapped2D);
 	}
 
 	TonemappingParamDef gTonemappingParamDef;
 
-	ShaderVariation TonemappingMat::VAR_Gamma_AutoExposure_MSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", true),
-		ShaderVariation::Param("AUTO_EXPOSURE", true),
-		ShaderVariation::Param("MSAA", true),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
+#define VARIATION_MSAA(x, volumeLUT, gammaOnly, autoExposure, msaa)			\
+	ShaderVariation TonemappingMat::VAR_##x = ShaderVariation({				\
+		ShaderVariation::Param("VOLUME_LUT", volumeLUT),					\
+		ShaderVariation::Param("GAMMA_ONLY", gammaOnly),					\
+		ShaderVariation::Param("AUTO_EXPOSURE", autoExposure),				\
+		ShaderVariation::Param("MSAA", msaa),								\
+		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),	\
+	});																		\
 
-	ShaderVariation TonemappingMat::VAR_Gamma_AutoExposure_NoMSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", true),
-		ShaderVariation::Param("AUTO_EXPOSURE", true),
-		ShaderVariation::Param("MSAA", false),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
+#define VARIATION_AUTOEXPOSURE(x, volumeLUT, gammaOnly, autoExposure)		\
+	VARIATION_MSAA(x##_MSAA, volumeLUT, gammaOnly, autoExposure, true)		\
+	VARIATION_MSAA(x##_NoMSAA, volumeLUT, gammaOnly, autoExposure, false)	\
 
-	ShaderVariation TonemappingMat::VAR_Gamma_NoAutoExposure_MSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", true),
-		ShaderVariation::Param("AUTO_EXPOSURE", false),
-		ShaderVariation::Param("MSAA", true),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
+#define VARIATION_GAMMAONLY(x, volumeLUT, gammaOnly)						\
+	VARIATION_AUTOEXPOSURE(x##_AutoExposure, volumeLUT, gammaOnly, true)	\
+	VARIATION_AUTOEXPOSURE(x##_NoAutoExposure, volumeLUT, gammaOnly, false)	\
 
-	ShaderVariation TonemappingMat::VAR_Gamma_NoAutoExposure_NoMSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", true),
-		ShaderVariation::Param("AUTO_EXPOSURE", false),
-		ShaderVariation::Param("MSAA", false),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
+#define VARIATION_VOLUMELUT(x, volumeLUT)									\
+	VARIATION_GAMMAONLY(x##_Gamma, volumeLUT, true)							\
+	VARIATION_GAMMAONLY(x##_NoGamma, volumeLUT, false)						\
 
-	ShaderVariation TonemappingMat::VAR_NoGamma_AutoExposure_MSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", false),
-		ShaderVariation::Param("AUTO_EXPOSURE", true),
-		ShaderVariation::Param("MSAA", true),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
+	VARIATION_VOLUMELUT(VolumeLUT, true)
+	VARIATION_VOLUMELUT(NoVolumeLUT, false)
 
-	ShaderVariation TonemappingMat::VAR_NoGamma_AutoExposure_NoMSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", false),
-		ShaderVariation::Param("AUTO_EXPOSURE", true),
-		ShaderVariation::Param("MSAA", false),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
-
-	ShaderVariation TonemappingMat::VAR_NoGamma_NoAutoExposure_MSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", false),
-		ShaderVariation::Param("AUTO_EXPOSURE", false),
-		ShaderVariation::Param("MSAA", true),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
-
-	ShaderVariation TonemappingMat::VAR_NoGamma_NoAutoExposure_NoMSAA = ShaderVariation({
-		ShaderVariation::Param("GAMMA_ONLY", false),
-		ShaderVariation::Param("AUTO_EXPOSURE", false),
-		ShaderVariation::Param("MSAA", false),
-		ShaderVariation::Param("LUT_SIZE", CreateTonemapLUTMat::LUT_SIZE),
-	});
+#undef VARIATION_VOLUMELUT
+#undef VARIATION_GAMMAONLY
+#undef VARIATION_AUTOEXPOSURE
 
 	TonemappingMat::TonemappingMat()
 	{
@@ -486,14 +610,21 @@ namespace bs { namespace ct
 
 	void TonemappingMat::_initVariations(ShaderVariations& variations)
 	{
-		variations.add(VAR_Gamma_AutoExposure_MSAA);
-		variations.add(VAR_Gamma_AutoExposure_NoMSAA);
-		variations.add(VAR_Gamma_NoAutoExposure_MSAA);
-		variations.add(VAR_Gamma_NoAutoExposure_NoMSAA);
-		variations.add(VAR_NoGamma_AutoExposure_MSAA);
-		variations.add(VAR_NoGamma_AutoExposure_NoMSAA);
-		variations.add(VAR_NoGamma_NoAutoExposure_MSAA);
-		variations.add(VAR_NoGamma_NoAutoExposure_NoMSAA);
+#define VARIATION_GAMMA(x)									\
+		variations.add(VAR_##x##_AutoExposure_MSAA);		\
+		variations.add(VAR_##x##_AutoExposure_NoMSAA);		\
+		variations.add(VAR_##x##_NoAutoExposure_MSAA);		\
+		variations.add(VAR_##x##_NoAutoExposure_NoMSAA);	\
+
+#define VARIATION_VOLUMELUT(x)								\
+		VARIATION_GAMMA(x##_Gamma)							\
+		VARIATION_GAMMA(x##_NoGamma)
+
+		VARIATION_VOLUMELUT(VolumeLUT)
+		VARIATION_VOLUMELUT(NoVolumeLUT)
+
+#undef VARIATION_GAMMA
+#undef VARIATION_VOLUMELUT
 	}
 
 	void TonemappingMat::execute(const SPtr<Texture>& sceneColor, const SPtr<Texture>& eyeAdaptation, 
@@ -524,40 +655,80 @@ namespace bs { namespace ct
 			gRendererUtility().drawScreenQuad();
 	}
 
-	TonemappingMat* TonemappingMat::getVariation(bool gammaOnly, bool autoExposure, bool MSAA)
+	TonemappingMat* TonemappingMat::getVariation(bool volumeLUT, bool gammaOnly, bool autoExposure, bool MSAA)
 	{
-		if (gammaOnly)
+		if(volumeLUT)
 		{
-			if (autoExposure)
+			if (gammaOnly)
 			{
-				if (MSAA)
-					return get(VAR_Gamma_AutoExposure_MSAA);
+				if (autoExposure)
+				{
+					if (MSAA)
+						return get(VAR_VolumeLUT_Gamma_AutoExposure_MSAA);
+					else
+						return get(VAR_VolumeLUT_Gamma_AutoExposure_NoMSAA);
+				}
 				else
-					return get(VAR_Gamma_AutoExposure_NoMSAA);
+				{
+					if (MSAA)
+						return get(VAR_VolumeLUT_Gamma_NoAutoExposure_MSAA);
+					else
+						return get(VAR_VolumeLUT_Gamma_NoAutoExposure_NoMSAA);
+				}
 			}
 			else
 			{
-				if (MSAA)
-					return get(VAR_Gamma_NoAutoExposure_MSAA);
+				if (autoExposure)
+				{
+					if (MSAA)
+						return get(VAR_VolumeLUT_NoGamma_AutoExposure_MSAA);
+					else
+						return get(VAR_VolumeLUT_NoGamma_AutoExposure_NoMSAA);
+				}
 				else
-					return get(VAR_Gamma_NoAutoExposure_NoMSAA);
+				{
+					if (MSAA)
+						return get(VAR_VolumeLUT_NoGamma_NoAutoExposure_MSAA);
+					else
+						return get(VAR_VolumeLUT_NoGamma_NoAutoExposure_NoMSAA);
+				}
 			}
 		}
 		else
 		{
-			if (autoExposure)
+			if (gammaOnly)
 			{
-				if (MSAA)
-					return get(VAR_NoGamma_AutoExposure_MSAA);
+				if (autoExposure)
+				{
+					if (MSAA)
+						return get(VAR_NoVolumeLUT_Gamma_AutoExposure_MSAA);
+					else
+						return get(VAR_NoVolumeLUT_Gamma_AutoExposure_NoMSAA);
+				}
 				else
-					return get(VAR_NoGamma_AutoExposure_NoMSAA);
+				{
+					if (MSAA)
+						return get(VAR_NoVolumeLUT_Gamma_NoAutoExposure_MSAA);
+					else
+						return get(VAR_NoVolumeLUT_Gamma_NoAutoExposure_NoMSAA);
+				}
 			}
 			else
 			{
-				if (MSAA)
-					return get(VAR_NoGamma_NoAutoExposure_MSAA);
+				if (autoExposure)
+				{
+					if (MSAA)
+						return get(VAR_NoVolumeLUT_NoGamma_AutoExposure_MSAA);
+					else
+						return get(VAR_NoVolumeLUT_NoGamma_AutoExposure_NoMSAA);
+				}
 				else
-					return get(VAR_NoGamma_NoAutoExposure_NoMSAA);
+				{
+					if (MSAA)
+						return get(VAR_NoVolumeLUT_NoGamma_NoAutoExposure_MSAA);
+					else
+						return get(VAR_NoVolumeLUT_NoGamma_NoAutoExposure_NoMSAA);
+				}
 			}
 		}
 	}

+ 106 - 12
Source/RenderBeast/BsPostProcessing.h

@@ -141,11 +141,73 @@ namespace bs { namespace ct
 
 		/** Returns the texture descriptor that can be used for initializing the output render target. */
 		static POOLED_RENDER_TEXTURE_DESC getOutputDesc();
+
+		/** 
+		 * Populates the provided paramater buffer with eye adaptation parameters. The parameter buffer is expected to be
+		 * created with EyeAdaptationParamDef block definition.
+		 */
+		static void populateParams(const SPtr<GpuParamBlockBuffer>& paramBuffer, float frameDelta, 
+			const AutoExposureSettings& settings, float exposureScale);
 	private:
 		SPtr<GpuParamBlockBuffer> mParamBuffer;
 		GpuParamTexture mReducedHistogramTex;
 	};
 
+	/** 
+	 * Shader that computes luminance of all the pixels in the provided texture, and stores them in log2 format, scaled
+	 * to [0, 1] range (according to eye adapatation parameters) and stores those values in the alpha channel of the
+	 * output texture. Color channel is just a copy of the input texture. Resulting texture is intended to be provided
+	 * to the downsampling shader in order to calculate the average luminance, used for non-histogram eye adaptation
+	 * calculation (when compute shader is not available).
+	 */
+	class EyeAdaptationBasicSetupMat : public RendererMaterial<EyeAdaptationBasicSetupMat>
+	{
+		RMAT_DEF("PPEyeAdaptationBasicSetup.bsl");
+
+	public:
+		EyeAdaptationBasicSetupMat();
+
+		/** Executes the post-process effect with the provided parameters. */
+		void execute(const SPtr<Texture>& input, const SPtr<RenderTarget>& output, float frameDelta, 
+			const AutoExposureSettings& settings, float exposureScale);
+
+		/** Returns the texture descriptor that can be used for initializing the output render target. */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc(const SPtr<Texture>& input);
+	private:
+		SPtr<GpuParamBlockBuffer> mParamBuffer;
+		GpuParamTexture mInputTex;
+	};
+
+	BS_PARAM_BLOCK_BEGIN(EyeAdaptationBasicParamsMatDef)
+		BS_PARAM_BLOCK_ENTRY(Vector2I, gInputTexSize)
+	BS_PARAM_BLOCK_END
+
+	extern EyeAdaptationBasicParamsMatDef gEyeAdaptationBasicParamsMatDef;
+
+	/** 
+	 * Shader that computes eye adapatation value from a texture that has luminance encoded in its alpha channel (as done
+	 * by EyeAdaptationBasicSetupMat). The result is a 1x1 texture containing the eye adaptation value.
+	 */
+	class EyeAdaptationBasicMat : public RendererMaterial<EyeAdaptationBasicMat>
+	{
+		RMAT_DEF("PPEyeAdaptationBasic.bsl");
+
+	public:
+		EyeAdaptationBasicMat();
+
+		/** Executes the post-process effect with the provided parameters. */
+		void execute(const SPtr<Texture>& curFrame, const SPtr<Texture>& prevFrame, const SPtr<RenderTarget>& output, 
+			float frameDelta, const AutoExposureSettings& settings, float exposureScale);
+
+		/** Returns the texture descriptor that can be used for initializing the output render target. */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc();
+	private:
+		SPtr<GpuParamBlockBuffer> mEyeAdaptationParamsBuffer;
+		SPtr<GpuParamBlockBuffer> mParamsBuffer;
+		GpuParamTexture mCurFrameTexParam;
+		GpuParamTexture mPrevFrameTexParam;
+	};
+
 	BS_PARAM_BLOCK_BEGIN(CreateTonemapLUTParamDef)
 		BS_PARAM_BLOCK_ENTRY_ARRAY(Vector4, gTonemapParams, 2)
 		BS_PARAM_BLOCK_ENTRY(float, gGammaAdjustment)
@@ -176,19 +238,44 @@ namespace bs { namespace ct
 	public:
 		CreateTonemapLUTMat();
 
-		/** Executes the post-process effect with the provided parameters. */
-		void execute(const SPtr<Texture>& output, const RenderSettings& settings);
+		/** 
+		 * Executes the post-process effect with the provided parameters, generating a 3D LUT using a compute shader. 
+		 * Should only be called on the appropriate variation (3D one).
+		 */
+		void execute3D(const SPtr<Texture>& output, const RenderSettings& settings);
+
+		/** 
+		 * Executes the post-process effect with the provided parameters, generating an unwrapped 2D LUT without the use
+		 * of a compute shader. Should only be called on the appropriate variation (non-3D one).
+		 */
+		void execute2D(const SPtr<RenderTexture>& output, const RenderSettings& settings);
 
 		/** Returns the texture descriptor that can be used for initializing the output render target. */
-		static POOLED_RENDER_TEXTURE_DESC getOutputDesc();
+		POOLED_RENDER_TEXTURE_DESC getOutputDesc() const;
+
+		/** 
+		 * Returns the material variation matching the provided parameters. 
+		 * 
+		 * @param[in]	is3D	If true the material will generate a 3D LUT using a compute shader. Otherwise it will
+		 *						generate an unwrapped 2D LUT withou the use of a compute shader. Depending on this parameter
+		 *						you should call either execute3D() or execute2D() methods to render the material.
+		 */
+		static CreateTonemapLUTMat* getVariation(bool is3D);
 
 		/** Size of the 3D color lookup table. */
 		static const UINT32 LUT_SIZE = 32;
 	private:
+		/** Populates the parameter block buffers using the provided settings. */
+		void populateParamBuffers(const RenderSettings& settings);
+
 		SPtr<GpuParamBlockBuffer> mParamBuffer;
 		SPtr<GpuParamBlockBuffer> mWhiteBalanceParamBuffer;
 
 		GpuParamLoadStoreTexture mOutputTex;
+		bool mIs3D;
+
+		static ShaderVariation VAR_3D;
+		static ShaderVariation VAR_Unwrapped2D;
 	};
 
 	BS_PARAM_BLOCK_BEGIN(TonemappingParamDef)
@@ -212,7 +299,7 @@ namespace bs { namespace ct
 			const SPtr<RenderTarget>& output, const RenderSettings& settings);
 
 		/** Returns the material variation matching the provided parameters. */
-		static TonemappingMat* getVariation(bool gammaOnly, bool autoExposure, bool MSAA);
+		static TonemappingMat* getVariation(bool volumeLUT, bool gammaOnly, bool autoExposure, bool MSAA);
 
 	private:
 		SPtr<GpuParamBlockBuffer> mParamBuffer;
@@ -221,14 +308,21 @@ namespace bs { namespace ct
 		GpuParamTexture mColorLUT;
 		GpuParamTexture mEyeAdaptationTex;
 
-		static ShaderVariation VAR_Gamma_AutoExposure_MSAA;
-		static ShaderVariation VAR_Gamma_AutoExposure_NoMSAA;
-		static ShaderVariation VAR_Gamma_NoAutoExposure_MSAA;
-		static ShaderVariation VAR_Gamma_NoAutoExposure_NoMSAA;
-		static ShaderVariation VAR_NoGamma_AutoExposure_MSAA;
-		static ShaderVariation VAR_NoGamma_AutoExposure_NoMSAA;
-		static ShaderVariation VAR_NoGamma_NoAutoExposure_MSAA;
-		static ShaderVariation VAR_NoGamma_NoAutoExposure_NoMSAA;
+#define VARIATION_GAMMA(x)											\
+		static ShaderVariation VAR_##x##_AutoExposure_MSAA;			\
+		static ShaderVariation VAR_##x##_AutoExposure_NoMSAA;		\
+		static ShaderVariation VAR_##x##_NoAutoExposure_MSAA;		\
+		static ShaderVariation VAR_##x##_NoAutoExposure_NoMSAA;
+
+#define VARIATION_VOLUME_LUT(x)			\
+		VARIATION_GAMMA(##x##_Gamma)	\
+		VARIATION_GAMMA(##x##_NoGamma)
+
+		VARIATION_VOLUME_LUT(VolumeLUT)
+		VARIATION_VOLUME_LUT(NoVolumeLUT)
+
+#undef VARIATION_VOLUME_LUT
+#undef VARIATION_GAMMA
 	};
 
 	const int MAX_BLUR_SAMPLES = 128;

+ 1 - 1
Source/RenderBeast/BsRenderBeast.cpp

@@ -386,7 +386,7 @@ namespace bs { namespace ct
 
 		view.beginFrame();
 
-		RenderCompositorNodeInputs inputs(viewGroup, view, sceneInfo, *mCoreOptions, frameInfo);
+		RenderCompositorNodeInputs inputs(viewGroup, view, sceneInfo, *mCoreOptions, frameInfo, mFeatureSet);
 
 		// Register callbacks
 		if (viewProps.triggerCallbacks)

+ 257 - 25
Source/RenderBeast/BsRenderBeastIBLUtility.cpp

@@ -5,6 +5,7 @@
 #include "Material/BsGpuParamsSet.h"
 #include "Renderer/BsRendererUtility.h"
 #include "RenderAPI/BsGpuBuffer.h"
+#include "BsRenderBeast.h"
 
 namespace bs { namespace ct
 {
@@ -184,6 +185,153 @@ namespace bs { namespace ct
 		return get(VAR_Order5);
 	}
 
+	IrradianceComputeSHFragParamDef gIrradianceComputeSHFragParamDef;
+
+	IrradianceComputeSHFragMat::IrradianceComputeSHFragMat()
+	{
+		mParamBuffer = gIrradianceComputeSHFragParamDef.createBuffer();
+
+		SPtr<GpuParams> params = mParamsSet->getGpuParams();
+		params->setParamBlockBuffer("Params", mParamBuffer);
+		params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex", mInputTexture);
+	}
+
+	void IrradianceComputeSHFragMat::_initVariations(ShaderVariations& variations)
+	{
+		// Do nothing
+	}
+
+	void IrradianceComputeSHFragMat::execute(const SPtr<Texture>& source, UINT32 face, UINT32 coefficientIdx, 
+		const SPtr<RenderTarget>& output)
+	{
+		// Set parameters
+		mInputTexture.set(source);
+
+		gIrradianceComputeSHFragParamDef.gCubeFace.set(mParamBuffer, face);
+		gIrradianceComputeSHFragParamDef.gFaceSize.set(mParamBuffer, source->getProperties().getWidth());
+		gIrradianceComputeSHFragParamDef.gCoeffIdx.set(mParamBuffer, coefficientIdx);
+
+		// Render
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(output);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+		gRendererUtility().drawScreenQuad();
+
+		rapi.setRenderTarget(nullptr);
+	}
+
+	POOLED_RENDER_TEXTURE_DESC IrradianceComputeSHFragMat::getOutputDesc(const SPtr<Texture>& input)
+	{
+		auto& props = input->getProperties();
+		return POOLED_RENDER_TEXTURE_DESC::createCube(PF_RGBA16F, props.getWidth(), props.getHeight(), TU_RENDERTARGET);
+	}
+
+	IrradianceAccumulateSHParamDef gIrradianceAccumulateSHParamDef;
+
+	IrradianceAccumulateSHMat::IrradianceAccumulateSHMat()
+	{
+		mParamBuffer = gIrradianceAccumulateSHParamDef.createBuffer();
+
+		SPtr<GpuParams> params = mParamsSet->getGpuParams();
+		params->setParamBlockBuffer("Params", mParamBuffer);
+		params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex", mInputTexture);
+	}
+
+	void IrradianceAccumulateSHMat::_initVariations(ShaderVariations& variations)
+	{
+		// Do nothing
+	}
+
+	void IrradianceAccumulateSHMat::execute(const SPtr<Texture>& source, UINT32 face, UINT32 sourceMip, 
+		const SPtr<RenderTarget>& output)
+	{
+		// Set parameters
+		mInputTexture.set(source);
+
+		auto& props = source->getProperties();
+		Vector2 halfPixel(0.5f / props.getWidth(), 0.5f / props.getHeight());
+
+		gIrradianceAccumulateSHParamDef.gCubeFace.set(mParamBuffer, face);
+		gIrradianceAccumulateSHParamDef.gCubeMip.set(mParamBuffer, sourceMip);
+		gIrradianceAccumulateSHParamDef.gHalfPixel.set(mParamBuffer, halfPixel);
+
+		// Render
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(output);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+		gRendererUtility().drawScreenQuad();
+
+		rapi.setRenderTarget(nullptr);
+	}
+
+	POOLED_RENDER_TEXTURE_DESC IrradianceAccumulateSHMat::getOutputDesc(const SPtr<Texture>& input)
+	{
+		auto& props = input->getProperties();
+
+		// Assuming it's a cubemap
+		UINT32 size = std::max(1U, (UINT32)(props.getWidth() * 0.5f));
+
+		return POOLED_RENDER_TEXTURE_DESC::createCube(PF_RGBA16F, size, size, TU_RENDERTARGET);
+	}
+
+	IrradianceAccumulateCubeSHMat::IrradianceAccumulateCubeSHMat()
+	{
+		mParamBuffer = gIrradianceAccumulateSHParamDef.createBuffer();
+
+		SPtr<GpuParams> params = mParamsSet->getGpuParams();
+		params->setParamBlockBuffer("Params", mParamBuffer);
+		params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gInputTex", mInputTexture);
+	}
+
+	void IrradianceAccumulateCubeSHMat::_initVariations(ShaderVariations& variations)
+	{
+		// Do nothing
+	}
+
+	void IrradianceAccumulateCubeSHMat::execute(const SPtr<Texture>& source, UINT32 sourceMip, UINT32 coefficientIdx, 
+		const SPtr<RenderTarget>& output)
+	{
+		// Set parameters
+		mInputTexture.set(source);
+
+		auto& props = source->getProperties();
+		Vector2 halfPixel(0.5f / props.getWidth(), 0.5f / props.getHeight());
+
+		gIrradianceAccumulateSHParamDef.gCubeFace.set(mParamBuffer, 0);
+		gIrradianceAccumulateSHParamDef.gCubeMip.set(mParamBuffer, sourceMip);
+		gIrradianceAccumulateSHParamDef.gHalfPixel.set(mParamBuffer, halfPixel);
+
+		auto& rtProps = output->getProperties();
+
+		// Render to just one pixel corresponding to the coefficient
+		Rect2 viewRect;
+		viewRect.x = coefficientIdx / (float)rtProps.width;
+		viewRect.y = 0.0f;
+
+		viewRect.width = 1.0f / rtProps.width;
+		viewRect.height = 1.0f;
+
+		// Render
+		RenderAPI& rapi = RenderAPI::instance();
+		rapi.setRenderTarget(output);
+		rapi.setViewport(viewRect);
+
+		gRendererUtility().setPass(mMaterial);
+		gRendererUtility().setPassParams(mParamsSet);
+		gRendererUtility().drawScreenQuad();
+
+		rapi.setRenderTarget(nullptr);
+	}
+
+	POOLED_RENDER_TEXTURE_DESC IrradianceAccumulateCubeSHMat::getOutputDesc()
+	{
+		return POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA32F, 9, 1, TU_RENDERTARGET);
+	}
+
 	IrradianceReduceSHParamDef gIrradianceReduceSHParamDef;
 
 	ShaderVariation IrradianceReduceSHMat::VAR_Order3 = ShaderVariation({
@@ -342,45 +490,68 @@ namespace bs { namespace ct
 		rapi.setRenderTarget(nullptr);
 	}
 
+	bool supportsComputeSH()
+	{
+		return gRenderBeast()->getFeatureSet() == RenderBeastFeatureSet::Desktop;
+	}
+
 	void RenderBeastIBLUtility::filterCubemapForIrradiance(const SPtr<Texture>& cubemap, const SPtr<Texture>& output) const
 	{
-		IrradianceComputeSHMat* shCompute = IrradianceComputeSHMat::getVariation(5);
-		IrradianceReduceSHMat* shReduce = IrradianceReduceSHMat::getVariation(5);
-		IrradianceProjectSHMat* shProject = IrradianceProjectSHMat::get();
+		if(supportsComputeSH())
+		{
+			IrradianceComputeSHMat* shCompute = IrradianceComputeSHMat::getVariation(5);
+			IrradianceReduceSHMat* shReduce = IrradianceReduceSHMat::getVariation(5);
 
-		UINT32 numCoeffSets;
-		SPtr<GpuBuffer> coeffSetBuffer = shCompute->createOutputBuffer(cubemap, numCoeffSets);
-		for (UINT32 face = 0; face < 6; face++)
-			shCompute->execute(cubemap, face, coeffSetBuffer);
+			UINT32 numCoeffSets;
+			SPtr<GpuBuffer> coeffSetBuffer = shCompute->createOutputBuffer(cubemap, numCoeffSets);
+			for (UINT32 face = 0; face < 6; face++)
+				shCompute->execute(cubemap, face, coeffSetBuffer);
 
-		SPtr<GpuBuffer> coeffBuffer = shReduce->createOutputBuffer(1);
-		shReduce->execute(coeffSetBuffer, numCoeffSets, coeffBuffer, 0);
+			SPtr<GpuBuffer> coeffBuffer = shReduce->createOutputBuffer(1);
+			shReduce->execute(coeffSetBuffer, numCoeffSets, coeffBuffer, 0);
 
-		for (UINT32 face = 0; face < 6; face++)
+			IrradianceProjectSHMat* shProject = IrradianceProjectSHMat::get();
+			for (UINT32 face = 0; face < 6; face++)
+			{
+				RENDER_TEXTURE_DESC cubeFaceRTDesc;
+				cubeFaceRTDesc.colorSurfaces[0].texture = output;
+				cubeFaceRTDesc.colorSurfaces[0].face = face;
+				cubeFaceRTDesc.colorSurfaces[0].numFaces = 1;
+				cubeFaceRTDesc.colorSurfaces[0].mipLevel = 0;
+
+				SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc);
+				shProject->execute(coeffBuffer, face, target);
+			}
+		}
+		else
 		{
-			RENDER_TEXTURE_DESC cubeFaceRTDesc;
-			cubeFaceRTDesc.colorSurfaces[0].texture = output;
-			cubeFaceRTDesc.colorSurfaces[0].face = face;
-			cubeFaceRTDesc.colorSurfaces[0].numFaces = 1;
-			cubeFaceRTDesc.colorSurfaces[0].mipLevel = 0;
+			SPtr<Texture> shCoeffs = filterCubemapForIrradianceNonCompute(cubemap);
 
-			SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc);
-			shProject->execute(coeffBuffer, face, target);
+			// TODO - Re-project the coefficients
 		}
 	}
-
+	
 	void RenderBeastIBLUtility::filterCubemapForIrradiance(const SPtr<Texture>& cubemap, const SPtr<GpuBuffer>& output, 
 		UINT32 outputIdx) const
 	{
-		IrradianceComputeSHMat* shCompute = IrradianceComputeSHMat::getVariation(3);
-		IrradianceReduceSHMat* shReduce = IrradianceReduceSHMat::getVariation(3);
+		if(supportsComputeSH())
+		{
+			IrradianceComputeSHMat* shCompute = IrradianceComputeSHMat::getVariation(3);
+			IrradianceReduceSHMat* shReduce = IrradianceReduceSHMat::getVariation(3);
 
-		UINT32 numCoeffSets;
-		SPtr<GpuBuffer> coeffSetBuffer = shCompute->createOutputBuffer(cubemap, numCoeffSets);
-		for (UINT32 face = 0; face < 6; face++)
-			shCompute->execute(cubemap, face, coeffSetBuffer);
+			UINT32 numCoeffSets;
+			SPtr<GpuBuffer> coeffSetBuffer = shCompute->createOutputBuffer(cubemap, numCoeffSets);
+			for (UINT32 face = 0; face < 6; face++)
+				shCompute->execute(cubemap, face, coeffSetBuffer);
 
-		shReduce->execute(coeffSetBuffer, numCoeffSets, output, outputIdx);
+			shReduce->execute(coeffSetBuffer, numCoeffSets, output, outputIdx);
+		}
+		else
+		{
+			SPtr<Texture> shCoeffs = filterCubemapForIrradianceNonCompute(cubemap);
+			
+			// TODO - Output expects a buffer
+		}
 	}
 
 	void RenderBeastIBLUtility::scaleCubemap(const SPtr<Texture>& src, UINT32 srcMip, const SPtr<Texture>& dst, 
@@ -446,4 +617,65 @@ namespace bs { namespace ct
 			material->execute(src, face, srcMip, target);
 		}
 	}
+
+	SPtr<Texture> RenderBeastIBLUtility::filterCubemapForIrradianceNonCompute(const SPtr<Texture>& cubemap)
+	{
+		static const UINT32 NUM_COEFFS = 9;
+
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+		IrradianceComputeSHFragMat* shCompute = IrradianceComputeSHFragMat::get();
+		IrradianceAccumulateSHMat* shAccum = IrradianceAccumulateSHMat::get();
+		IrradianceAccumulateCubeSHMat* shAccumCube = IrradianceAccumulateCubeSHMat::get();
+
+		SPtr<PooledRenderTexture> finalCoeffs = resPool.get(shAccumCube->getOutputDesc());
+		for(UINT32 coeff = 0; coeff < NUM_COEFFS; ++coeff)
+		{
+			SPtr<PooledRenderTexture> coeffsTex = resPool.get(shCompute->getOutputDesc(cubemap));
+			
+			// Generate SH coefficients and weights per-texel
+			for(UINT32 face = 0; face < 6; face++)
+			{
+				RENDER_TEXTURE_DESC cubeFaceRTDesc;
+				cubeFaceRTDesc.colorSurfaces[0].texture = coeffsTex->texture;
+				cubeFaceRTDesc.colorSurfaces[0].face = face;
+				cubeFaceRTDesc.colorSurfaces[0].numFaces = 1;
+				cubeFaceRTDesc.colorSurfaces[0].mipLevel = 0;
+
+				SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc);
+				shCompute->execute(cubemap, face, coeff, target);
+			}
+
+			// Downsample, summing up coefficients and weights all the way down to 1x1
+			auto& sourceProps = cubemap->getProperties();
+			UINT32 numMips = PixelUtil::getMaxMipmaps(sourceProps.getWidth(), sourceProps.getHeight(), 1, 
+				sourceProps.getFormat());
+
+			SPtr<PooledRenderTexture> downsampleInput = coeffsTex;
+			coeffsTex = nullptr;
+
+			for(UINT32 mip = 0; mip < numMips; mip++)
+			{
+				SPtr<PooledRenderTexture> accumCoeffsTex = resPool.get(shAccum->getOutputDesc(downsampleInput->texture));
+
+				for(UINT32 face = 0; face < 6; face++)
+				{
+					RENDER_TEXTURE_DESC cubeFaceRTDesc;
+					cubeFaceRTDesc.colorSurfaces[0].texture = accumCoeffsTex->texture;
+					cubeFaceRTDesc.colorSurfaces[0].face = face;
+					cubeFaceRTDesc.colorSurfaces[0].numFaces = 1;
+					cubeFaceRTDesc.colorSurfaces[0].mipLevel = 0;
+
+					SPtr<RenderTarget> target = RenderTexture::create(cubeFaceRTDesc);
+					shAccum->execute(downsampleInput->texture, face, 0, target);
+				}
+
+				downsampleInput = accumCoeffsTex;
+			}
+
+			// Sum up all the faces and write the coefficient to the final texture
+			shAccumCube->execute(downsampleInput->texture, 0, coeff, finalCoeffs->renderTexture);
+		}
+
+		return finalCoeffs->texture;
+	}
 }}

+ 113 - 0
Source/RenderBeast/BsRenderBeastIBLUtility.h

@@ -6,6 +6,7 @@
 #include "Renderer/BsIBLUtility.h"
 #include "Renderer/BsRendererMaterial.h"
 #include "Renderer/BsParamBlocks.h"
+#include "BsGpuResourcePool.h"
 
 namespace bs { namespace ct
 {
@@ -193,6 +194,112 @@ namespace bs { namespace ct
 		static ShaderVariation VAR_Order5;
 	};
 
+	BS_PARAM_BLOCK_BEGIN(IrradianceComputeSHFragParamDef)
+		BS_PARAM_BLOCK_ENTRY(int, gCubeFace)
+		BS_PARAM_BLOCK_ENTRY(int, gFaceSize)
+		BS_PARAM_BLOCK_ENTRY(int, gCoeffIdx)
+	BS_PARAM_BLOCK_END
+
+	extern IrradianceComputeSHFragParamDef gIrradianceComputeSHFragParamDef;
+
+	/** 
+	 * Computes spherical harmonic coefficients from a radiance cubemap. This is an alternative to IrradianceComputeSHMat
+	 * that does not require compute shader support. 
+	 */
+	class IrradianceComputeSHFragMat : public RendererMaterial<IrradianceComputeSHFragMat>
+	{
+		RMAT_DEF("IrradianceComputeSHFrag.bsl")
+
+	public:
+		IrradianceComputeSHFragMat();
+
+		/** 
+		 * Computes spherical harmonic coefficients from a face of an input cube radiance texture and outputs them to the
+		 * specified face of the output cube texture. Only a single coefficient is output per execution. The output texture
+		 * will contain the coefficients for red, green and blue channels in the corresponding texture channels, and 
+		 * per-texel weight in the alpha channel. Output coefficients must be summed up and normalized before use (using 
+		 * IrradianceAccumulateCubeSH).
+		 */
+		void execute(const SPtr<Texture>& source, UINT32 face, UINT32 coefficientIdx, const SPtr<RenderTarget>& output);
+
+		/** 
+		 * Returns the texture descriptor that can be used for initializing the output render target. Note that the
+		 * output texture is a cubemap but the execute() method expects a render target that is a single face of a
+		 * cubemap. 
+		 */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc(const SPtr<Texture>& source);
+
+	private:
+		SPtr<GpuParamBlockBuffer> mParamBuffer;
+		GpuParamTexture mInputTexture;
+	};
+
+	BS_PARAM_BLOCK_BEGIN(IrradianceAccumulateSHParamDef)
+		BS_PARAM_BLOCK_ENTRY(int, gCubeFace)
+		BS_PARAM_BLOCK_ENTRY(int, gCubeMip)
+		BS_PARAM_BLOCK_ENTRY(Vector2, gHalfPixel)
+	BS_PARAM_BLOCK_END
+
+	extern IrradianceAccumulateSHParamDef gIrradianceAccumulateSHParamDef;
+
+	/** 
+	 * Downsamples a cubemap face containing SH coefficient and weight values as output by IrradianceComputeSHFragMat. Each
+	 * downsample sums up 2x2 pixel area coefficients/weights from the previous mip level.
+	 */
+	class IrradianceAccumulateSHMat : public RendererMaterial<IrradianceAccumulateSHMat>
+	{
+		RMAT_DEF("IrradianceAccumulateSH.bsl")
+
+	public:
+		IrradianceAccumulateSHMat();
+
+		/** 
+		 * Downsamples the provided face and mip level of the source texture and outputs the downsampled (i.e summed up)
+		 * values in the resulting output texture. 
+		 */
+		void execute(const SPtr<Texture>& source, UINT32 face, UINT32 sourceMip, const SPtr<RenderTarget>& output);
+
+		/** 
+		 * Returns the texture descriptor that can be used for initializing the output render target. Note the output
+		 * is a cubemap.
+		 */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc(const SPtr<Texture>& source);
+
+	private:
+		SPtr<GpuParamBlockBuffer> mParamBuffer;
+		GpuParamTexture mInputTexture;
+	};
+
+	/** 
+	 * Accumulates SH coefficient values from all six faces of a cubemap and normalizes them. The cubemap is expected to be
+	 * 1x1 in size (previously downsampled by IrradianceAccumulateSHMat). After this shader is ran for all SH coefficients
+	 * the output texture will contain final valid set of SH coefficients.
+	 */
+	class IrradianceAccumulateCubeSHMat : public RendererMaterial<IrradianceAccumulateCubeSHMat>
+	{
+		RMAT_DEF("IrradianceAccumulateCubeSH.bsl")
+
+	public:
+		IrradianceAccumulateCubeSHMat();
+
+		/** 
+		 * Sums up all faces of the input cube texture and writes the value to the corresponding index in the output
+		 * texture. The source mip should point to a mip level with size 1x1.
+		 */
+		void execute(const SPtr<Texture>& source, UINT32 sourceMip, UINT32 coefficientIdx, 
+			const SPtr<RenderTarget>& output);
+
+		/** 
+		 * Returns the texture descriptor that can be used for initializing the output render target. The render target
+		 * will be able to hold all required SH coefficients (even though execute() outputs just one coefficient at a time).
+		 */
+		static POOLED_RENDER_TEXTURE_DESC getOutputDesc();
+
+	private:
+		SPtr<GpuParamBlockBuffer> mParamBuffer;
+		GpuParamTexture mInputTexture;
+	};
+
 	BS_PARAM_BLOCK_BEGIN(IrradianceProjectSHParamDef)
 		BS_PARAM_BLOCK_ENTRY(int, gCubeFace)
 	BS_PARAM_BLOCK_END
@@ -248,6 +355,12 @@ namespace bs { namespace ct
 		 * @param[in]   dstMip	Determines which mip level of the destination texture to scale.
 		 */
 		static void downsampleCubemap(const SPtr<Texture>& src, UINT32 srcMip, const SPtr<Texture>& dst, UINT32 dstMip);
+
+		/** 
+		 * Generates irradiance SH coefficients from the input cubemap and writes them to a 1D texture. Does not make
+		 * use of the compute shader.
+		 */
+		static SPtr<Texture> filterCubemapForIrradianceNonCompute(const SPtr<Texture>& cubemap);
 	};
 
 	/** @} */

+ 96 - 33
Source/RenderBeast/BsRenderCompositor.cpp

@@ -1261,37 +1261,91 @@ namespace bs { namespace ct
 
 			downsampleMat->execute(sceneColor, downsampledScene->renderTexture);
 
-			// Generate histogram
-			SPtr<PooledRenderTexture> eyeAdaptHistogram = resPool.get(
-				EyeAdaptHistogramMat::getOutputDesc(downsampledScene->texture));
-			EyeAdaptHistogramMat* eyeAdaptHistogramMat = EyeAdaptHistogramMat::get();
-			eyeAdaptHistogramMat->execute(downsampledScene->texture, eyeAdaptHistogram->texture, settings.autoExposure);
-
-			// Reduce histogram
-			SPtr<PooledRenderTexture> reducedHistogram = resPool.get(EyeAdaptHistogramReduceMat::getOutputDesc());
-
-			SPtr<Texture> prevFrameEyeAdaptation;
-			if (prevEyeAdaptation != nullptr)
-				prevFrameEyeAdaptation = prevEyeAdaptation->texture;
-
-			EyeAdaptHistogramReduceMat* eyeAdaptHistogramReduce = EyeAdaptHistogramReduceMat::get();
-			eyeAdaptHistogramReduce->execute(downsampledScene->texture, eyeAdaptHistogram->texture, 
-				prevFrameEyeAdaptation, reducedHistogram->renderTexture);
-
-			resPool.release(downsampledScene);
-			downsampledScene = nullptr;
-
-			resPool.release(eyeAdaptHistogram);
-			eyeAdaptHistogram = nullptr;
+			if(useHistogramEyeAdapatation(inputs))
+			{
+				// Generate histogram
+				SPtr<PooledRenderTexture> eyeAdaptHistogram =
+					resPool.get(EyeAdaptHistogramMat::getOutputDesc(downsampledScene->texture));
+				EyeAdaptHistogramMat* eyeAdaptHistogramMat = EyeAdaptHistogramMat::get();
+				eyeAdaptHistogramMat->execute(downsampledScene->texture, eyeAdaptHistogram->texture, settings.autoExposure);
+
+				// Reduce histogram
+				SPtr<PooledRenderTexture> reducedHistogram = resPool.get(EyeAdaptHistogramReduceMat::getOutputDesc());
+
+				SPtr<Texture> prevFrameEyeAdaptation;
+				if (prevEyeAdaptation != nullptr)
+					prevFrameEyeAdaptation = prevEyeAdaptation->texture;
+
+				EyeAdaptHistogramReduceMat* eyeAdaptHistogramReduce = EyeAdaptHistogramReduceMat::get();
+				eyeAdaptHistogramReduce->execute(
+					downsampledScene->texture,
+					eyeAdaptHistogram->texture,
+					prevFrameEyeAdaptation,
+					reducedHistogram->renderTexture);
+
+				resPool.release(downsampledScene);
+				downsampledScene = nullptr;
+
+				resPool.release(eyeAdaptHistogram);
+				eyeAdaptHistogram = nullptr;
+
+				// Generate eye adaptation value
+				eyeAdaptation = resPool.get(EyeAdaptationMat::getOutputDesc());
+				EyeAdaptationMat* eyeAdaptationMat = EyeAdaptationMat::get();
+				eyeAdaptationMat->execute(
+					reducedHistogram->texture,
+					eyeAdaptation->renderTexture,
+					inputs.frameInfo.timeDelta,
+					settings.autoExposure,
+					settings.exposureScale);
+
+				resPool.release(reducedHistogram);
+				reducedHistogram = nullptr;
+			}
+			else
+			{
+				// Populate alpha values of the downsampled texture with luminance
+				SPtr<PooledRenderTexture> luminanceTex = 
+					resPool.get(EyeAdaptationBasicSetupMat::getOutputDesc(downsampledScene->texture));
+
+				EyeAdaptationBasicSetupMat* setupMat = EyeAdaptationBasicSetupMat::get();
+				setupMat->execute(
+					downsampledScene->texture,
+					luminanceTex->renderTexture,
+					inputs.frameInfo.timeDelta,
+					settings.autoExposure,
+					settings.exposureScale);
+
+				SPtr<Texture> downsampleInput = luminanceTex->texture;
+				luminanceTex = nullptr;
+
+				// Downsample some more
+				for(UINT32 i = 0; i < 5; i++)
+				{
+					downsampleMat = DownsampleMat::getVariation(1, false);
+					SPtr<PooledRenderTexture> downsampledLuminance = 
+						resPool.get(DownsampleMat::getOutputDesc(downsampleInput));
 
-			// Generate eye adaptation value
-			eyeAdaptation = resPool.get(EyeAdaptationMat::getOutputDesc());
-			EyeAdaptationMat* eyeAdaptationMat = EyeAdaptationMat::get();
-			eyeAdaptationMat->execute(reducedHistogram->texture, eyeAdaptation->renderTexture, inputs.frameInfo.timeDelta,
-				settings.autoExposure, settings.exposureScale);
+					downsampleMat->execute(downsampleInput, downsampledLuminance->renderTexture);
+					downsampleInput = downsampledLuminance->texture;
+				}
 
-			resPool.release(reducedHistogram);
-			reducedHistogram = nullptr;
+				// Generate eye adaptation value
+				EyeAdaptationBasicMat* eyeAdaptationMat = EyeAdaptationBasicMat::get();
+
+				SPtr<Texture> prevFrameEyeAdaptation;
+				if (prevEyeAdaptation != nullptr)
+					prevFrameEyeAdaptation = prevEyeAdaptation->texture;
+
+				eyeAdaptation = resPool.get(EyeAdaptationBasicMat::getOutputDesc());
+				eyeAdaptationMat->execute(
+					downsampleInput,
+					prevFrameEyeAdaptation,
+					eyeAdaptation->renderTexture,
+					inputs.frameInfo.timeDelta,
+					settings.autoExposure,
+					settings.exposureScale);
+			}
 		}
 		else
 		{
@@ -1302,6 +1356,7 @@ namespace bs { namespace ct
 			eyeAdaptation = nullptr;
 		}
 
+		bool volumeLUT = inputs.featureSet == RenderBeastFeatureSet::Desktop;
 		bool gammaOnly;
 		bool autoExposure;
 		if (hdr)
@@ -1313,11 +1368,14 @@ namespace bs { namespace ct
 
 				if (tonemapLUTDirty) // Rebuild LUT if PP settings changed
 				{
+					CreateTonemapLUTMat* createLUT = CreateTonemapLUTMat::getVariation(volumeLUT);
 					if(mTonemapLUT == nullptr)
-						mTonemapLUT = resPool.get(CreateTonemapLUTMat::getOutputDesc());
+						mTonemapLUT = resPool.get(createLUT->getOutputDesc());
 
-					CreateTonemapLUTMat* createLUT = CreateTonemapLUTMat::get();
-					createLUT->execute(mTonemapLUT->texture, settings);
+					if(volumeLUT)
+						createLUT->execute3D(mTonemapLUT->texture, settings);
+					else
+						createLUT->execute2D(mTonemapLUT->renderTexture, settings);
 
 					mTonemapLastUpdateHash = latestHash;
 				}
@@ -1344,7 +1402,7 @@ namespace bs { namespace ct
 			}
 		}
 
-		TonemappingMat* tonemapping = TonemappingMat::getVariation(gammaOnly, autoExposure, msaa);
+		TonemappingMat* tonemapping = TonemappingMat::getVariation(volumeLUT, gammaOnly, autoExposure, msaa);
 
 		SPtr<RenderTexture> ppOutput;
 		SPtr<Texture> ppLastFrame;
@@ -1371,6 +1429,11 @@ namespace bs { namespace ct
 
 		std::swap(eyeAdaptation, prevEyeAdaptation);
 	}
+	
+	bool RCNodeTonemapping::useHistogramEyeAdapatation(const RenderCompositorNodeInputs& inputs)
+	{
+		return inputs.featureSet == RenderBeastFeatureSet::Desktop;
+	}
 
 	SmallVector<StringID, 4> RCNodeTonemapping::getDependencies(const RendererView& view)
 	{

+ 9 - 2
Source/RenderBeast/BsRenderCompositor.h

@@ -24,8 +24,8 @@ namespace ct
 	struct RenderCompositorNodeInputs
 	{
 		RenderCompositorNodeInputs(const RendererViewGroup& viewGroup, const RendererView& view, const SceneInfo& scene, 
-			const RenderBeastOptions& options, const FrameInfo& frameInfo)
-			: viewGroup(viewGroup), view(view), scene(scene), options(options), frameInfo(frameInfo)
+			const RenderBeastOptions& options, const FrameInfo& frameInfo, RenderBeastFeatureSet featureSet)
+			: viewGroup(viewGroup), view(view), scene(scene), options(options), frameInfo(frameInfo), featureSet(featureSet)
 		{ }
 
 		const RendererViewGroup& viewGroup;
@@ -33,6 +33,7 @@ namespace ct
 		const SceneInfo& scene;
 		const RenderBeastOptions& options;
 		const FrameInfo& frameInfo;
+		const RenderBeastFeatureSet featureSet;
 
 		// Callbacks to external systems can hook into the compositor
 		SmallVector<RendererExtension*, 4> extPreBasePass;
@@ -517,6 +518,12 @@ namespace ct
 		/** @copydoc RenderCompositorNode::clear */
 		void clear() override;
 
+		/** 
+		 * Returns true if the more advanced (and more expensive) compute shader based method of computing eye adaptation
+		 * should be used. 
+		 */
+		bool useHistogramEyeAdapatation(const RenderCompositorNodeInputs& inputs);
+
 		SPtr<PooledRenderTexture> mTonemapLUT;
 		UINT64 mTonemapLastUpdateHash = -1;
 	};