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

Merge pull request #42 from godlikepanos/volumetric_lighting

Volumetric fog and lighting
Panagiotis Christopoulos Charitos пре 7 година
родитељ
комит
5052248a89
55 измењених фајлова са 1529 додато и 1359 уклоњено
  1. 4 2
      CMakeLists.txt
  2. BIN
      engine_data/blue_noise_rgb8_16x16x16_3d.ankitex
  3. 1 1
      samples/common/Framework.cpp
  4. 2 1
      samples/sponza/Main.cpp
  5. 2 1
      sandbox/Main.cpp
  6. 10 0
      shaders/ClearTextureCompute.glslp
  7. 4 2
      shaders/ClusteredShadingCommon.glsl
  8. 67 26
      shaders/DepthDownscale.glslp
  9. 21 8
      shaders/ForwardShadingCommonFrag.glsl
  10. 1 1
      shaders/ForwardShadingParticles.glslp
  11. 0 55
      shaders/ForwardShadingUpscale.glslp
  12. 0 48
      shaders/ForwardShadingVolumetricUpscale.glslp
  13. 2 0
      shaders/LightFunctions.glsl
  14. 50 0
      shaders/LightShadingApplyFog.glslp
  15. 0 192
      shaders/VolumetricFog.glslp
  16. 78 0
      shaders/VolumetricFogAccumulation.glslp
  17. 229 0
      shaders/VolumetricLightingAccumulation.glslp
  18. 28 10
      shaders/glsl_cpp_common/ClusteredShading.h
  19. 10 6
      shaders/glsl_cpp_common/Common.h
  20. 2 1
      src/anki/Renderer.h
  21. 4 0
      src/anki/core/Config.cpp
  22. 12 0
      src/anki/gr/Enums.h
  23. 5 5
      src/anki/gr/Sampler.h
  24. 6 2
      src/anki/gr/gl/SamplerImpl.cpp
  25. 16 5
      src/anki/gr/vulkan/SamplerFactory.cpp
  26. 12 0
      src/anki/gr/vulkan/TextureImpl.cpp
  27. 2 1
      src/anki/renderer/Common.h
  28. 79 92
      src/anki/renderer/DepthDownscale.cpp
  29. 6 25
      src/anki/renderer/DepthDownscale.h
  30. 16 117
      src/anki/renderer/ForwardShading.cpp
  31. 2 54
      src/anki/renderer/ForwardShading.h
  32. 176 150
      src/anki/renderer/Indirect.cpp
  33. 4 9
      src/anki/renderer/LensFlare.cpp
  34. 71 51
      src/anki/renderer/LightShading.cpp
  35. 13 17
      src/anki/renderer/LightShading.h
  36. 27 12
      src/anki/renderer/Renderer.cpp
  37. 12 5
      src/anki/renderer/Renderer.h
  38. 14 0
      src/anki/renderer/RendererObject.h
  39. 2 1
      src/anki/renderer/ShadowMapping.cpp
  40. 0 238
      src/anki/renderer/Volumetric.cpp
  41. 0 113
      src/anki/renderer/Volumetric.h
  42. 103 0
      src/anki/renderer/VolumetricFog.cpp
  43. 98 0
      src/anki/renderer/VolumetricFog.h
  44. 149 0
      src/anki/renderer/VolumetricLightingAccumulation.cpp
  45. 62 0
      src/anki/renderer/VolumetricLightingAccumulation.h
  46. 1 1
      src/anki/resource/TextureResource.cpp
  47. 0 7
      src/anki/scene/SceneGraph.h
  48. 7 1
      src/anki/scene/components/MoveComponent.cpp
  49. 0 5
      src/anki/scene/components/MoveComponent.h
  50. 2 29
      src/anki/scene/components/SceneComponent.cpp
  51. 2 42
      src/anki/scene/components/SceneComponent.h
  52. 1 1
      src/anki/ui/Canvas.cpp
  53. 19 19
      tests/gr/Gr.cpp
  54. 3 3
      tools/texture/create_atlas.py
  55. 92 0
      tools/texture/noise_array.py

+ 4 - 2
CMakeLists.txt

@@ -67,6 +67,8 @@ endif()
 # Configuration                                                                #
 ################################################################################
 
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
 option(ANKI_EXTRA_CHECKS "Debugging checks (assertions)" OFF)
 option(ANKI_LTO "LTO compilation" OFF)
 
@@ -264,7 +266,7 @@ add_subdirectory(thirdparty/freetype)
 message("++ End configuring freetype")
 
 # glslang
-message("++ Configuring glslang")	
+message("++ Configuring glslang")
 add_subdirectory(thirdparty/glslang)
 message("++ End configuring glslang")
 
@@ -420,7 +422,7 @@ if(SDL)
 	set(THIRD_PARTY_LIBS ${THIRD_PARTY_LIBS} SDL2-static)
 endif()
 
-set(THIRD_PARTY_LIBS ${THIRD_PARTY_LIBS} BulletSoftBody BulletDynamics BulletCollision LinearMath 
+set(THIRD_PARTY_LIBS ${THIRD_PARTY_LIBS} BulletSoftBody BulletDynamics BulletCollision LinearMath
 	ankispirvcross ankitinyxml2 ankilua ankiz glslang SPIRV OGLCompiler OSDependent ankitinyexpr)
 
 # Add anki sub libraries

BIN
engine_data/blue_noise_rgb8_16x16x16_3d.ankitex


+ 1 - 1
samples/common/Framework.cpp

@@ -32,7 +32,7 @@ Error SampleApp::init(int argc, char** argv, CString sampleName)
 	getInput().moveCursor(Vec2(0.0f));
 
 	// Some renderer stuff
-	getMainRenderer().getOffscreenRenderer().getVolumetric().setFogParticleColor(Vec3(1.0f, 0.9f, 0.9f) * 0.0001f);
+	getMainRenderer().getOffscreenRenderer().getVolumetricFog().setFogParticleColor(Vec3(1.0f, 0.9f, 0.9f));
 
 	ANKI_CHECK(sampleExtraInit());
 

+ 2 - 1
samples/sponza/Main.cpp

@@ -17,7 +17,8 @@ public:
 		ANKI_CHECK(getResourceManager().loadResource("assets/scene.lua", script));
 		ANKI_CHECK(getScriptManager().evalString(script->getSource()));
 
-		getMainRenderer().getOffscreenRenderer().getVolumetric().setFogParticleColor(Vec3(1.0, 0.9, 0.9) * 0.009);
+		getMainRenderer().getOffscreenRenderer().getVolumetricFog().setFogParticleColor(Vec3(1.0, 0.9, 0.9));
+		getMainRenderer().getOffscreenRenderer().getVolumetricFog().setParticleDensity(2.0f);
 		return Error::NONE;
 	}
 };

+ 2 - 1
sandbox/Main.cpp

@@ -45,7 +45,8 @@ Error MyApp::init(int argc, char* argv[])
 	MainRenderer& renderer = getMainRenderer();
 	ResourceManager& resources = getResourceManager();
 
-	renderer.getOffscreenRenderer().getVolumetric().setFogParticleColor(Vec3(1.0, 0.9, 0.9) * 0.009);
+	renderer.getOffscreenRenderer().getVolumetricFog().setFogParticleColor(Vec3(1.0, 0.9, 0.9));
+	renderer.getOffscreenRenderer().getVolumetricFog().setParticleDensity(1.0f);
 
 	if(getenv("PROFILE"))
 	{

+ 10 - 0
shaders/ClearTextureCompute.glslp

@@ -5,6 +5,8 @@
 
 // A slow compute program to clear an image with a contant color
 
+#pragma anki mutator IS_2D 0 1
+
 #pragma anki start comp
 #include <shaders/Common.glsl>
 
@@ -16,11 +18,19 @@ struct PushConsts
 };
 ANKI_PUSH_CONSTANTS(PushConsts, u_regs);
 
+#if IS_2D
 layout(ANKI_IMAGE_BINDING(0, 0)) uniform writeonly image2D u_img;
+#else
+layout(ANKI_IMAGE_BINDING(0, 0)) uniform writeonly image3D u_img;
+#endif
 
 void main()
 {
+#if IS_2D
 	imageStore(u_img, IVec2(gl_GlobalInvocationID.xy), u_regs.m_clearColor);
+#else
+	imageStore(u_img, IVec3(gl_GlobalInvocationID), u_regs.m_clearColor);
+#endif
 }
 
 #pragma anki end

+ 4 - 2
shaders/ClusteredShadingCommon.glsl

@@ -23,12 +23,14 @@ layout(ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING), std140, row_major) unifor
 #	define u_near UNIFORM(u_lightingUniforms.m_rendererSizeTimeNear.w)
 #	define u_far UNIFORM(u_lightingUniforms.m_cameraPosFar.w)
 #	define u_cameraPos UNIFORM(u_lightingUniforms.m_cameraPosFar.xyz)
-#	define u_clusterCountX UNIFORM(u_lightingUniforms.m_tileCount.x)
-#	define u_clusterCountY UNIFORM(u_lightingUniforms.m_tileCount.y)
+#	define u_clusterCountX UNIFORM(u_lightingUniforms.m_clusterCount.x)
+#	define u_clusterCountY UNIFORM(u_lightingUniforms.m_clusterCount.y)
 #	define u_clustererMagic u_lightingUniforms.m_clustererMagicValues
+#	define u_prevClustererMagic u_lightingUniforms.m_prevClustererMagicValues
 #	define u_time UNIFORM(u_lightingUniforms.m_rendererSizeTimeNear.z)
 #	define u_unprojectionParams UNIFORM(u_lightingUniforms.m_unprojectionParams)
 #	define u_rendererSize u_lightingUniforms.m_rendererSizeTimeNear.xy
+#	define u_lightVolumeLastCluster UNIFORM(u_lightingUniforms.m_lightVolumeLastClusterPad3.x)
 
 #	define u_viewMat u_lightingUniforms.m_viewMat
 #	define u_invViewMat u_lightingUniforms.m_invViewMat

+ 67 - 26
shaders/DepthDownscale.glslp

@@ -3,63 +3,104 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#pragma anki mutator TYPE 0 1 // 0: write to depth&color, 1: write to depth
 #pragma anki mutator SAMPLE_RESOLVE_TYPE 0 1 2 // 0: average, 1: min, 2: max
-#pragma anki mutator COPY_TO_CLIENT 0 1
 
-#pragma anki start vert
-#include <shaders/QuadVert.glsl>
-#pragma anki end
-
-#pragma anki start frag
+#pragma anki start comp
 #include <shaders/Common.glsl>
 
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
 #define AVG 0
 #define MIN 1
 #define MAX 2
 
-#if COPY_TO_CLIENT
 struct PushConsts
 {
-	UVec4 textureSize;
+	UVec4 m_writeImgSizes;
+	UVec4 m_copyToClientLevelWriteLevel1Pad2;
 };
 ANKI_PUSH_CONSTANTS(PushConsts, u_regs);
+#define u_level0WriteImgSize u_regs.m_writeImgSizes.xy
+#define u_level1WriteImgSize u_regs.m_writeImgSizes.zw
+#define u_copyToClientLevel u_regs.m_copyToClientLevelWriteLevel1Pad2.x
+#define u_writeLevel1 (u_regs.m_copyToClientLevelWriteLevel1Pad2.y == 1u)
 
 layout(std430, ANKI_SS_BINDING(0, 0)) writeonly buffer s1_
 {
 	F32 u_clientBuf[];
 };
-#endif
-
-layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_depthRt;
 
-layout(location = 0) in Vec2 in_uv;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_readTex;
+layout(ANKI_IMAGE_BINDING(0, 0)) writeonly uniform image2D u_level0WriteImg;
+layout(ANKI_IMAGE_BINDING(0, 1)) writeonly uniform image2D u_level1WriteImg;
 
-layout(location = 0) out F32 out_color;
+shared F32 s_depths[gl_WorkGroupSize.y][gl_WorkGroupSize.x];
 
-void main()
+// Resolve depths into one value
+F32 resolveDepths(Vec4 depths)
 {
-	Vec4 depths = textureGather(u_depthRt, in_uv, 0);
-
 #if SAMPLE_RESOLVE_TYPE == MIN
 	Vec2 mind2 = min(depths.xy, depths.zw);
-	out_color = min(mind2.x, mind2.y);
+	F32 depth = min(mind2.x, mind2.y);
 #elif SAMPLE_RESOLVE_TYPE == MAX
 	Vec2 max2 = max(depths.xy, depths.zw);
-	out_color = max(max2.x, max2.y);
+	F32 depth = max(max2.x, max2.y);
 #elif SAMPLE_RESOLVE_TYPE == AVG
-	out_color = dot(depths, Vec4(1.0 / 4.0));
+	F32 depth = dot(depths, Vec4(1.0 / 4.0));
 #else
 #	error See file
 #endif
 
-#if TYPE == 0
-	gl_FragDepth = out_color;
-#endif
+	return depth;
+}
 
-#if COPY_TO_CLIENT
-	u_clientBuf[U32(gl_FragCoord.y) * u_regs.textureSize.x + U32(gl_FragCoord.x)] = out_color;
-#endif
+void main()
+{
+	// Read depth
+	Vec2 readUv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(u_level0WriteImgSize);
+	Vec4 depths = textureGather(u_readTex, readUv, 0);
+
+	// Resolve & store the 1st level
+	F32 depth = resolveDepths(depths);
+	s_depths[gl_LocalInvocationID.y][gl_LocalInvocationID.x] = depth;
+
+	if(all(lessThan(gl_GlobalInvocationID.xy, u_level0WriteImgSize)))
+	{
+		imageStore(u_level0WriteImg, IVec2(gl_GlobalInvocationID.xy), Vec4(depth));
+
+		if(u_copyToClientLevel == 0u)
+		{
+			U32 idx = gl_GlobalInvocationID.y * u_level0WriteImgSize.x + gl_GlobalInvocationID.x;
+			u_clientBuf[idx] = depth;
+		}
+	}
+
+	// Sync
+	memoryBarrierShared();
+	barrier();
+
+	// Resolve 2nd level
+	if(u_writeLevel1 && all(equal(gl_LocalInvocationID.xy & UVec2(1u), UVec2(0u))))
+	{
+		depths.x = depth;
+		depths.y = s_depths[gl_LocalInvocationID.y + 0u][gl_LocalInvocationID.x + 1u];
+		depths.z = s_depths[gl_LocalInvocationID.y + 1u][gl_LocalInvocationID.x + 1u];
+		depths.w = s_depths[gl_LocalInvocationID.y + 1u][gl_LocalInvocationID.x + 0u];
+
+		depth = resolveDepths(depths);
+
+		UVec2 writeUv = gl_GlobalInvocationID.xy >> 1u;
+		if(all(lessThan(writeUv, u_level1WriteImgSize)))
+		{
+			imageStore(u_level1WriteImg, IVec2(writeUv), Vec4(depth));
+
+			if(u_copyToClientLevel == 1u)
+			{
+				U32 idx = writeUv.y * u_level1WriteImgSize.x + writeUv.x;
+				u_clientBuf[idx] = depth;
+			}
+		}
+	}
 }
 
 #pragma anki end

+ 21 - 8
shaders/ForwardShadingCommonFrag.glsl

@@ -10,23 +10,24 @@
 #include <shaders/Functions.glsl>
 
 // Global resources
-layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D anki_msDepthRt;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_gbufferDepthRt;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler3D u_lightVol;
 #define LIGHT_SET 0
 #define LIGHT_UBO_BINDING 0
 #define LIGHT_SS_BINDING 0
-#define LIGHT_TEX_BINDING 1
+#define LIGHT_TEX_BINDING 2
 #define LIGHT_LIGHTS
 #define LIGHT_COMMON_UNIS
 #include <shaders/ClusteredShadingCommon.glsl>
 
 #define anki_u_time u_time
-#define RENDERER_SIZE (u_rendererSize * 0.5)
+#define RENDERER_SIZE (u_rendererSize)
 
 layout(location = 0) out Vec4 out_color;
 
 void writeGBuffer(in Vec4 color)
 {
-	out_color = Vec4(color.rgb, 1.0 - color.a);
+	out_color = Vec4(color.rgb, color.a);
 }
 
 Vec4 readAnimatedTextureRgba(sampler2DArray tex, F32 period, Vec2 uv, F32 time)
@@ -36,8 +37,10 @@ Vec4 readAnimatedTextureRgba(sampler2DArray tex, F32 period, Vec2 uv, F32 time)
 	return texture(tex, Vec3(uv, layer));
 }
 
-Vec3 computeLightColor(Vec3 diffCol, Vec3 worldPos)
+// Iterate the clusters to compute the light color
+Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 {
+	diffCol = diffuseLambert(diffCol);
 	Vec3 outColor = Vec3(0.0);
 
 	// Find the cluster and then the light counts
@@ -57,7 +60,7 @@ Vec3 computeLightColor(Vec3 diffCol, Vec3 worldPos)
 	{
 		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
 
-		Vec3 diffC = diffuseLambert(diffCol) * light.m_diffuseColorTileSize.rgb;
+		Vec3 diffC = diffCol * light.m_diffuseColorTileSize.rgb;
 
 		Vec3 frag2Light = light.m_posRadius.xyz - worldPos;
 		F32 att = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
@@ -83,7 +86,7 @@ Vec3 computeLightColor(Vec3 diffCol, Vec3 worldPos)
 	{
 		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
 
-		Vec3 diffC = diffuseLambert(diffCol) * light.m_diffuseColorShadowmapId.rgb;
+		Vec3 diffC = diffCol * light.m_diffuseColorShadowmapId.rgb;
 
 		Vec3 frag2Light = light.m_posRadius.xyz - worldPos;
 		F32 att = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
@@ -110,6 +113,16 @@ Vec3 computeLightColor(Vec3 diffCol, Vec3 worldPos)
 	return outColor;
 }
 
+// Just read the light color from the vol texture
+Vec3 computeLightColorLow(Vec3 diffCol, Vec3 worldPos)
+{
+	Vec2 uv = gl_FragCoord.xy / RENDERER_SIZE;
+	Vec3 uv3d = computeClustererVolumeTextureUvs(u_clustererMagic, uv, worldPos, u_lightVolumeLastCluster + 1u);
+
+	Vec3 light = textureLod(u_lightVol, uv3d, 0.0).rgb;
+	return diffuseLambert(diffCol) * light;
+}
+
 void particleAlpha(Vec4 color, Vec4 scaleColor, Vec4 biasColor)
 {
 	writeGBuffer(color * scaleColor + biasColor);
@@ -120,7 +133,7 @@ void fog(Vec3 color, F32 fogAlphaScale, F32 fogDistanceOfMaxThikness, F32 zVSpac
 	const Vec2 screenSize = 1.0 / RENDERER_SIZE;
 
 	Vec2 texCoords = gl_FragCoord.xy * screenSize;
-	F32 depth = texture(anki_msDepthRt, texCoords).r;
+	F32 depth = texture(u_gbufferDepthRt, texCoords).r;
 	F32 zFeatherFactor;
 
 	Vec4 fragPosVspace4 = u_invProjMat * Vec4(Vec3(UV_TO_NDC(texCoords), depth), 1.0);

+ 1 - 1
shaders/ForwardShadingParticles.glslp

@@ -54,7 +54,7 @@ void main()
 #endif
 
 #if LIGHT
-	texCol.rgb = computeLightColor(texCol.rgb, in_worldPos);
+	texCol.rgb = computeLightColorLow(texCol.rgb, in_worldPos);
 #endif
 
 	Vec4 colScale = colorScale;

+ 0 - 55
shaders/ForwardShadingUpscale.glslp

@@ -1,55 +0,0 @@
-// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma anki input const Vec2 SRC_SIZE
-#pragma anki input const Vec2 FB_SIZE
-#pragma anki input const U32 NOISE_TEX_SIZE
-
-#pragma anki start vert
-#include <shaders/QuadVert.glsl>
-#pragma anki end
-
-#pragma anki start frag
-#include <shaders/Common.glsl>
-#include <shaders/Functions.glsl>
-
-#define EXPENSIVE_PASS 0
-#define BLUE_NOISE 0
-
-layout(location = 0) in Vec2 in_uv;
-
-layout(location = 0) out Vec4 out_color;
-
-layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_depthFullTex;
-layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_depthHalfTex;
-layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_fsRt;
-#if BLUE_NOISE
-layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2DArray u_noiseTex;
-#endif
-
-layout(ANKI_UBO_BINDING(0, 0)) uniform u0_
-{
-	Vec4 u_linearizeCfFarPad1;
-};
-
-void main()
-{
-#if EXPENSIVE_PASS
-	out_color =
-		bilateralUpsample(u_depthFullTex, u_depthHalfTex, u_fsRt, 1.0 / Vec2(SRC_SIZE), in_uv, u_linearizeCfFarPad1.xy);
-#else
-	const F32 thresholdMeters = 5.0 / 100.0; // 5 centimeters
-	F32 threshold = thresholdMeters / u_linearizeCfFarPad1.z;
-	out_color = nearestDepthUpscale(in_uv, u_depthFullTex, u_depthHalfTex, u_fsRt, u_linearizeCfFarPad1.xy, threshold);
-#endif
-
-#if BLUE_NOISE
-	Vec3 blueNoise = texture(u_noiseTex, Vec3(FB_SIZE / Vec2(NOISE_TEX_SIZE) * in_uv, 0.0), 0.0).rgb;
-	blueNoise = blueNoise * 2.0 - 1.0;
-	blueNoise = sign(blueNoise) * (1.0 - sqrt(1.0 - abs(blueNoise)));
-	out_color.rgb += blueNoise / 32.0;
-#endif
-}
-#pragma anki end

+ 0 - 48
shaders/ForwardShadingVolumetricUpscale.glslp

@@ -1,48 +0,0 @@
-// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma anki input const Vec2 SRC_SIZE
-#pragma anki input const Vec2 FB_SIZE
-#pragma anki input const U32 NOISE_TEX_SIZE
-
-#pragma anki start vert
-#include <shaders/QuadVert.glsl>
-#pragma anki end
-
-#pragma anki start frag
-#include <shaders/Functions.glsl>
-
-#define BLUE_NOISE 0
-
-layout(location = 0) in Vec2 in_uv;
-layout(location = 0) out Vec4 out_color;
-
-layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_depthFullTex;
-layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_depthHalfTex;
-layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_colorTex;
-#if BLUE_NOISE
-layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2DArray u_noiseTex;
-#endif
-
-layout(ANKI_UBO_BINDING(0, 0)) uniform u0_
-{
-	Vec4 u_linearizeCfPad2;
-};
-
-void main()
-{
-	Vec3 col =
-		bilateralUpsample(u_depthFullTex, u_depthHalfTex, u_colorTex, 1.0 / SRC_SIZE, in_uv, u_linearizeCfPad2.xy).rgb;
-
-#if BLUE_NOISE
-	Vec3 blueNoise = texture(u_noiseTex, Vec3(FB_SIZE / Vec2(NOISE_TEX_SIZE) * in_uv, 0.0), 0.0).rgb;
-	blueNoise = blueNoise * 2.0 - 1.0;
-	blueNoise = sign(blueNoise) * (1.0 - sqrt(1.0 - abs(blueNoise)));
-	col += blueNoise / 16.0;
-#endif
-
-	out_color = Vec4(col, 0.0);
-}
-#pragma anki end

+ 2 - 0
shaders/LightFunctions.glsl

@@ -12,7 +12,9 @@
 
 const F32 LIGHT_FRUSTUM_NEAR_PLANE = 0.1 / 4.0;
 const U32 SHADOW_SAMPLE_COUNT = 16;
+#if !defined(ESM_CONSTANT)
 const F32 ESM_CONSTANT = 40.0;
+#endif
 
 // Fresnel term unreal
 Vec3 F_Unreal(Vec3 specular, F32 VoH)

+ 50 - 0
shaders/LightShadingApplyFog.glslp

@@ -0,0 +1,50 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki input const U32 FOG_LAST_CLASTER
+
+#pragma anki start vert
+#include <shaders/QuadVert.glsl>
+#pragma anki end
+
+#pragma anki start frag
+
+#include <shaders/glsl_cpp_common/ClusteredShading.h>
+#include <shaders/Common.glsl>
+
+layout(location = 0) in Vec2 in_uv;
+layout(location = 0) out Vec4 out_color;
+
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_depthRt;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler3D u_fogVolume;
+
+struct PushConsts
+{
+	ClustererMagicValues m_clustererMagic;
+	Mat4 m_invViewProjMat;
+};
+ANKI_PUSH_CONSTANTS(PushConsts, u_regs);
+#define u_clustererMagic u_regs.m_clustererMagic
+#define u_invViewProjMat u_regs.m_invViewProjMat
+
+void main()
+{
+	F32 depth = textureLod(u_depthRt, in_uv, 0.0).r;
+
+	// Get world position
+	Vec4 worldPos4 = u_invViewProjMat * Vec4(UV_TO_NDC(in_uv), depth, 1.0);
+	Vec3 worldPos = worldPos4.xyz / worldPos4.w;
+
+	// Read the volume
+	Vec3 uv3d = computeClustererVolumeTextureUvs(u_clustererMagic, in_uv, worldPos, FOG_LAST_CLASTER + 1u);
+	Vec4 fogVals = textureLod(u_fogVolume, uv3d, 0.0);
+	Vec3 inScattering = fogVals.rgb;
+	F32 transmittance = fogVals.a;
+
+	// Apply the fog
+	out_color = Vec4(inScattering, transmittance);
+}
+
+#pragma anki end

+ 0 - 192
shaders/VolumetricFog.glslp

@@ -1,192 +0,0 @@
-// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma anki mutator ENABLE_SHADOWS 0 1
-
-#pragma anki input const UVec2 FB_SIZE
-#pragma anki input const UVec3 CLUSTER_COUNT
-#pragma anki input const U32 NOISE_MAP_SIZE
-
-#pragma anki start vert
-#include <shaders/QuadVert.glsl>
-#pragma anki end
-
-#pragma anki start frag
-#include <shaders/Common.glsl>
-#include <shaders/Functions.glsl>
-
-#define LIGHT_TEX_BINDING 3
-#define LIGHT_UBO_BINDING 0
-#define LIGHT_SS_BINDING 0
-#define LIGHT_SET 0
-#define LIGHT_LIGHTS
-#define LIGHT_COMMON_UNIS
-#include <shaders/ClusteredShadingCommon.glsl>
-
-layout(location = 0) in Vec2 in_uv;
-
-layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_msDepthRt;
-layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2DArray u_noiseTex;
-layout(ANKI_TEX_BINDING(0, 2)) uniform sampler2D u_historyRt;
-
-layout(std140, ANKI_UBO_BINDING(0, 3), row_major) uniform ubo0_
-{
-	Vec4 u_linearizeNoiseTexOffsetLayer;
-	Vec4 u_fogParticleColorPad1;
-};
-
-#define u_linearize UNIFORM(u_linearizeNoiseTexOffsetLayer.xy)
-#define u_noiseYOffset UNIFORM(u_linearizeNoiseTexOffsetLayer.z)
-#define u_noiseLayer UNIFORM(u_linearizeNoiseTexOffsetLayer.w)
-#define u_fogParticleColor UNIFORM(u_fogParticleColorPad1.rgb)
-
-layout(location = 0) out Vec3 out_color;
-
-const U32 MAX_ITERATIONS = 256u;
-const F32 JITTER_DISTANCE = 10.0; // In meters
-const F32 HISTORY_FEEDBACK = 1.0 / 16.0;
-
-// Return the diffuse color without taking into account the diffuse term of the particles.
-Vec3 computeLightColor(Vec3 fragPos, U32 idxOffset)
-{
-	Vec3 color = Vec3(0.0);
-
-	// Skip decals
-	U32 count = u_lightIndices[idxOffset];
-	idxOffset += count + 1u;
-
-	// Point lights
-	count = u_lightIndices[idxOffset++];
-	U32 idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
-	{
-		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
-
-		Vec3 frag2Light = light.m_posRadius.xyz - fragPos;
-		F32 factor = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
-
-#if ENABLE_SHADOWS
-		if(light.m_diffuseColorTileSize.w >= 0.0)
-		{
-			factor *= computeShadowFactorOmni(
-				frag2Light, light.m_radiusPad1.x, light.m_atlasTiles, light.m_diffuseColorTileSize.w, u_shadowTex);
-		}
-#endif
-
-		color += light.m_diffuseColorTileSize.rgb * factor;
-	}
-
-	// Spot lights
-	count = u_lightIndices[idxOffset++];
-	idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
-	{
-		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
-
-		Vec3 frag2Light = light.m_posRadius.xyz - fragPos;
-		F32 factor = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
-
-		Vec3 l = normalize(frag2Light);
-
-		factor *=
-			computeSpotFactor(l, light.m_outerCosInnerCos.x, light.m_outerCosInnerCos.y, light.m_lightDirRadius.xyz);
-
-#if ENABLE_SHADOWS
-		F32 shadowmapLayerIdx = light.m_diffuseColorShadowmapId.w;
-		if(shadowmapLayerIdx >= 0.0)
-		{
-			factor *= computeShadowFactorSpot(light.m_texProjectionMat, fragPos, light.m_lightDirRadius.w, u_shadowTex);
-		}
-#endif
-
-		color += light.m_diffuseColorShadowmapId.rgb * factor;
-	}
-
-	return color;
-}
-
-Vec3 readHistory(Vec3 worldPos, out F32 historyFeedback)
-{
-	Vec4 v4 = u_prevViewProjMat * Vec4(worldPos, 1.0);
-	Vec2 ndc = v4.xy / v4.w;
-
-	Vec2 oldUv = NDC_TO_UV(ndc);
-	Vec3 history = textureLod(u_historyRt, oldUv, 0.0).rgb;
-
-	// Compute the history blend. If clip falls outside NDC then it's 1.0 (use only current fog term) and if it's
-	// inside NDC then use the HISTORY_FEEDBACK value
-	Vec2 posNdc = abs(ndc);
-	historyFeedback = max(posNdc.x, posNdc.y);
-	historyFeedback = min(floor(historyFeedback), 1.0 - HISTORY_FEEDBACK);
-	historyFeedback += HISTORY_FEEDBACK;
-
-	return history;
-}
-
-void main()
-{
-	F32 depth = textureLod(u_msDepthRt, in_uv, 0.0).r;
-	Vec2 ndc = UV_TO_NDC(in_uv);
-
-	// Compute some cluster stuff
-	U32 i = U32(in_uv.x * F32(CLUSTER_COUNT.x));
-	U32 j = U32(in_uv.y * F32(CLUSTER_COUNT.y));
-	U32 ij = j * CLUSTER_COUNT.x + i;
-
-	// Get a rand jitter distance
-	Vec3 noiseTexUv = Vec3(Vec2(FB_SIZE) / Vec2(NOISE_MAP_SIZE) * in_uv + Vec2(0.0, u_noiseYOffset), u_noiseLayer);
-	F32 randFactor = texture(u_noiseTex, noiseTexUv).r;
-	F32 randDistance = JITTER_DISTANCE * randFactor;
-
-	// Get world position
-	Vec4 worldPos4 = u_invViewProjMat * Vec4(ndc, depth, 1.0);
-	Vec3 worldPos = worldPos4.xyz / worldPos4.w;
-
-	// Compute the distances from the camera
-	F32 maxDistFromTheCamera = length(worldPos - u_cameraPos);
-
-	worldPos4 = u_invViewProjMat * Vec4(ndc, EPSILON, 1.0);
-	Vec3 nearWorldPos = worldPos4.xyz / worldPos4.w;
-	F32 minDistFromTheCamera = length(nearWorldPos - u_cameraPos);
-
-	// Ray march
-	Vec3 crntColor = Vec3(0.0);
-	const F32 FRACTION = 1.0 / F32(MAX_ITERATIONS - 1u);
-	ANKI_LOOP for(F32 f = FRACTION; f < 1.0; f += FRACTION)
-	{
-		// Compute new world pos
-		F32 f2 = f * f; // Higher detail closer to the camera
-		F32 distFromTheCamera = (u_far - minDistFromTheCamera - JITTER_DISTANCE) * f;
-		distFromTheCamera += minDistFromTheCamera + randDistance;
-
-		if(distFromTheCamera >= maxDistFromTheCamera)
-		{
-			break;
-		}
-
-		Vec3 newWorldPos = u_cameraPos + normalize(worldPos - u_cameraPos) * distFromTheCamera;
-
-		// Compute cluster idx
-		U32 k = computeClusterK(u_clustererMagic, newWorldPos);
-		U32 clusterIdx = k * (CLUSTER_COUNT.x * CLUSTER_COUNT.y) + ij;
-		U32 lightIdxOffset = u_clusters[clusterIdx];
-
-		// Do lighting
-		crntColor += computeLightColor(newWorldPos, lightIdxOffset);
-	}
-
-	crntColor *= diffuseLambert(u_fogParticleColor);
-
-	// Read history
-	F32 historyFeedback;
-	Vec3 history = readHistory(worldPos, historyFeedback);
-
-	// Fix ghosting
-	history = max(history, crntColor);
-
-	// Blend
-	out_color = mix(history, crntColor, historyFeedback);
-}
-#pragma anki end

+ 78 - 0
shaders/VolumetricFogAccumulation.glslp

@@ -0,0 +1,78 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki input const UVec2 WORKGROUP_SIZE
+#pragma anki input const UVec3 VOLUME_SIZE
+#pragma anki input const UVec3 CLUSTER_COUNT
+#pragma anki input const UVec3 FRACTION
+#pragma anki input const U32 FINAL_CLUSTER_Z
+
+#pragma anki start comp
+
+#include <shaders/glsl_cpp_common/ClusteredShading.h>
+#include <shaders/Common.glsl>
+
+layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = 1) in;
+
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler3D u_lightVolume;
+layout(ANKI_IMAGE_BINDING(0, 0)) writeonly uniform image3D u_fogVolume;
+
+struct PushConsts
+{
+	Vec4 m_fogScatteringCoeffFogAbsorptionCoeffDensityPad1;
+	Vec4 m_fogDiffusePad1;
+	ClustererMagicValues m_clustererMagic;
+};
+ANKI_PUSH_CONSTANTS(PushConsts, u_regs);
+#define u_fogScatteringCoeff u_regs.m_fogScatteringCoeffFogAbsorptionCoeffDensityPad1.x
+#define u_fogAbsorptionCoeff u_regs.m_fogScatteringCoeffFogAbsorptionCoeffDensityPad1.y
+#define u_density u_regs.m_fogScatteringCoeffFogAbsorptionCoeffDensityPad1.z
+#define u_fogDiffuse u_regs.m_fogDiffusePad1.xyz
+#define u_clustererMagic u_regs.m_clustererMagic
+
+void main()
+{
+	if(any(greaterThanEqual(gl_GlobalInvocationID.xy, VOLUME_SIZE.xy)))
+	{
+		return;
+	}
+
+	Vec2 uv = (Vec2(gl_GlobalInvocationID.xy) + 0.5) / Vec2(VOLUME_SIZE.xy);
+
+	Vec4 colorAndDensityFront = Vec4(0.0);
+	ANKI_LOOP for(U32 i = 0u; i < VOLUME_SIZE.z; ++i)
+	{
+		// Compute the cluster K limits of this cluster fragment
+		F32 clusterKNear = F32(i) * (F32(FINAL_CLUSTER_Z + 1u) / F32(VOLUME_SIZE.z));
+		F32 clusterKFar = F32(i + 1u) * (F32(FINAL_CLUSTER_Z + 1u) / F32(VOLUME_SIZE.z));
+
+		// Compute the min and max Z in view space if this cluster fragment
+		F32 zVSpaceNear = -computeClusterNearf(u_clustererMagic, clusterKNear);
+		F32 zVSpaceFar = -computeClusterNearf(u_clustererMagic, clusterKFar);
+
+		// Compute the thikness of this fragment
+		F32 layerThinkness = abs(zVSpaceNear - zVSpaceFar);
+
+		// Scattering & absorption
+		F32 scattering = u_density * u_fogScatteringCoeff * layerThinkness;
+		F32 absorption = u_density * u_fogAbsorptionCoeff * layerThinkness;
+
+		// Read the light value
+		Vec3 light = textureLod(u_lightVolume, Vec3(uv, clusterKFar / F32(FINAL_CLUSTER_Z + 1u)), 0.0).rgb;
+		light *= u_fogDiffuse / PI;
+
+		// Integrate
+		Vec4 colorAndDensityBack = Vec4(light * scattering, scattering + absorption);
+
+		Vec3 l = colorAndDensityFront.rgb + saturate(exp(-colorAndDensityFront.a)) * colorAndDensityBack.rgb;
+		colorAndDensityFront = Vec4(l.rgb, colorAndDensityFront.a + colorAndDensityBack.a);
+
+		// Write the value
+		Vec4 valToWrite = Vec4(colorAndDensityFront.rgb, saturate(exp(-colorAndDensityFront.a)));
+		imageStore(u_fogVolume, IVec3(UVec3(gl_GlobalInvocationID.xy, i)), valToWrite);
+	}
+}
+
+#pragma anki end

+ 229 - 0
shaders/VolumetricLightingAccumulation.glslp

@@ -0,0 +1,229 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// This shader accumulates the lighting for every cluster fraction
+
+#pragma anki mutator ENABLE_SHADOWS 0 1
+
+#pragma anki input const UVec3 VOLUME_SIZE
+#pragma anki input const UVec3 CLUSTER_COUNT
+#pragma anki input const U32 FINAL_CLUSTER_Z
+#pragma anki input const UVec3 FRACTION
+#pragma anki input const UVec3 WORKGROUP_SIZE
+#pragma anki input const UVec3 NOISE_TEX_SIZE
+
+#pragma anki start comp
+
+// Lower the ESM constant to smooth the shadows
+#define ESM_CONSTANT 30.0
+
+const F32 PHASE_FUNCTION_ANISOTROPY = 0.3;
+
+layout(local_size_x = WORKGROUP_SIZE.x, local_size_y = WORKGROUP_SIZE.y, local_size_z = WORKGROUP_SIZE.z) in;
+
+layout(ANKI_IMAGE_BINDING(0, 0)) writeonly uniform image3D u_volume;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler3D u_noiseTex;
+layout(ANKI_TEX_BINDING(0, 1)) uniform sampler3D u_prevVolume;
+
+struct PushConsts
+{
+	Vec4 m_noiseOffsetPad3;
+};
+ANKI_PUSH_CONSTANTS(PushConsts, u_regs);
+
+#define u_noiseOffset u_regs.m_noiseOffsetPad3.x
+
+#define LIGHT_TEX_BINDING 2
+#define LIGHT_UBO_BINDING 0
+#define LIGHT_SS_BINDING 0
+#define LIGHT_SET 0
+#define LIGHT_LIGHTS
+#define LIGHT_COMMON_UNIS
+#define LIGHT_INDIRECT
+#include <shaders/ClusteredShadingCommon.glsl>
+
+Vec3 g_globalInvocationID = Vec3(gl_GlobalInvocationID);
+
+Vec3 readRand()
+{
+	Vec3 uv = (g_globalInvocationID + 0.5) / Vec3(NOISE_TEX_SIZE);
+	uv.z += u_noiseOffset;
+	return textureLod(u_noiseTex, uv, 0.0).rgb;
+}
+
+Vec3 worldPosInsideCluster(Vec3 relativePos)
+{
+	// Compute the cluster Z as float
+	F32 clusterKNear = g_globalInvocationID.z * (F32(FINAL_CLUSTER_Z + 1u) / F32(VOLUME_SIZE.z));
+	F32 clusterKFar = (g_globalInvocationID.z + 1.0) * (F32(FINAL_CLUSTER_Z + 1u) / F32(VOLUME_SIZE.z));
+	F32 clusterK = mix(clusterKNear, clusterKFar, relativePos.z);
+
+	// Get a Z value
+	F32 zVSpace = -computeClusterNearf(u_clustererMagic, clusterK);
+
+	// Get a XY value
+	Vec2 uvMin = g_globalInvocationID.xy / Vec2(VOLUME_SIZE.xy);
+	Vec2 uvMax = uvMin + 1.0 / Vec2(VOLUME_SIZE.xy);
+	Vec2 uv = mix(uvMin, uvMax, relativePos.xy);
+	Vec2 ndc = UV_TO_NDC(uv);
+	Vec2 xyZVspace = ndc * u_unprojectionParams.xy * zVSpace;
+
+	// Get the pos
+	Vec4 worldPos4 = u_invViewMat * Vec4(xyZVspace, zVSpace, 1.0);
+	Vec3 worldPos = worldPos4.xyz;
+
+	return worldPos;
+}
+
+// https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter16.html
+F32 phaseFunction(Vec3 viewDir, Vec3 lightDir, F32 g)
+{
+	F32 g2 = g * g;
+	F32 cosTheta = max(0.0, dot(viewDir, lightDir));
+	F32 cosTheta2 = cosTheta * cosTheta;
+
+	F32 a = (3.0 * (1.0 - g2)) / (2.0 * (2.0 + g2));
+	F32 b = (1.0 + cosTheta2) / pow(1.0 + g2 - 2.0 * g * cosTheta, 3.0 / 2.0);
+
+	return saturate(a * b);
+}
+
+Vec3 accumulateLights(U32 clusterIdx, Vec3 worldPos)
+{
+	Vec3 color = Vec3(0.0);
+	Vec3 viewDir = normalize(u_cameraPos - worldPos);
+
+	// Get ID offset
+	U32 idxOffset = u_clusters[clusterIdx];
+
+	// Skip decals
+	U32 count = u_lightIndices[idxOffset];
+	idxOffset += count + 1u;
+
+	// Point lights
+	count = u_lightIndices[idxOffset++];
+	U32 idxOffsetEnd = idxOffset + count;
+	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	{
+		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
+
+		Vec3 frag2Light = light.m_posRadius.xyz - worldPos;
+		F32 factor = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
+
+		factor *= phaseFunction(viewDir, normalize(worldPos - light.m_posRadius.xyz), PHASE_FUNCTION_ANISOTROPY);
+
+#if ENABLE_SHADOWS
+		if(light.m_diffuseColorTileSize.w >= 0.0)
+		{
+			factor *= computeShadowFactorOmni(
+				frag2Light, light.m_radiusPad1.x, light.m_atlasTiles, light.m_diffuseColorTileSize.w, u_shadowTex);
+		}
+#endif
+
+		color += light.m_diffuseColorTileSize.rgb * factor;
+	}
+
+	// Spot lights
+	count = u_lightIndices[idxOffset++];
+	idxOffsetEnd = idxOffset + count;
+	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	{
+		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
+
+		Vec3 frag2Light = light.m_posRadius.xyz - worldPos;
+		F32 factor = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
+
+		Vec3 l = normalize(frag2Light);
+
+		factor *=
+			computeSpotFactor(l, light.m_outerCosInnerCos.x, light.m_outerCosInnerCos.y, light.m_lightDirRadius.xyz);
+
+		factor *= phaseFunction(viewDir, light.m_lightDirRadius.xyz, PHASE_FUNCTION_ANISOTROPY);
+
+#if ENABLE_SHADOWS
+		F32 shadowmapLayerIdx = light.m_diffuseColorShadowmapId.w;
+		if(shadowmapLayerIdx >= 0.0)
+		{
+			factor *=
+				computeShadowFactorSpot(light.m_texProjectionMat, worldPos, light.m_lightDirRadius.w, u_shadowTex);
+		}
+#endif
+
+		color += light.m_diffuseColorShadowmapId.rgb * factor;
+	}
+
+	// Probes
+	F32 totalBlendWeight = EPSILON;
+	Vec3 diffIndirect = Vec3(0.0);
+	count = u_lightIndices[idxOffset++];
+	idxOffsetEnd = idxOffset + count;
+	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	{
+		ReflectionProbe probe = u_reflectionProbes[u_lightIndices[idxOffset++]];
+		Vec3 aabbMin = probe.m_aabbMinPad1.xyz;
+		Vec3 aabbMax = probe.m_aabbMaxPad1.xyz;
+		Vec3 probeOrigin = probe.m_positionCubemapIndex.xyz;
+		F32 cubemapIndex = probe.m_positionCubemapIndex.w;
+
+		F32 blendWeight = computeProbeBlendWeight(worldPos, aabbMin, aabbMax, 0.2);
+		totalBlendWeight += blendWeight;
+
+		Vec3 c = textureLod(u_irradianceTex, Vec4(viewDir, cubemapIndex), 0.0).rgb;
+		c *= PI; // Irradiance is pre-divided with PI so fix it
+		diffIndirect += c * blendWeight;
+	}
+
+	diffIndirect /= totalBlendWeight;
+	color += diffIndirect;
+
+	return color;
+}
+
+void main()
+{
+	if(any(greaterThanEqual(gl_GlobalInvocationID.xyz, VOLUME_SIZE)))
+	{
+		return;
+	}
+
+	// Find the cluster
+	UVec3 clusterXYZ = gl_GlobalInvocationID / FRACTION;
+	U32 clusterIdx = clusterXYZ.z * (CLUSTER_COUNT.x * CLUSTER_COUNT.y) + clusterXYZ.y * CLUSTER_COUNT.x + clusterXYZ.x;
+
+	// Find a random pos inside the cluster
+	Vec3 worldPos = worldPosInsideCluster(readRand());
+
+	// Get lighting
+	Vec3 color = accumulateLights(clusterIdx, worldPos);
+
+	// Read the prev result
+	{
+		// Better get a new world pos in the center of the cluster. Using worldPos creates noisy results
+		Vec3 midWPos = worldPosInsideCluster(Vec3(0.5));
+
+		// Compute UV
+		Vec4 prevClipPos4 = u_prevViewProjMat * Vec4(midWPos, 1.0);
+		Vec2 prevUv = NDC_TO_UV(prevClipPos4.xy / prevClipPos4.w);
+
+		// Compute new Z tex coord
+		F32 k = computeClusterKf(u_prevClustererMagic, midWPos);
+		k /= F32(FINAL_CLUSTER_Z + 1u);
+
+		// Read prev
+		Vec3 uvw = Vec3(prevUv, k);
+		if(all(lessThan(abs(uvw), Vec3(1.0))))
+		{
+			Vec3 prev = textureLod(u_prevVolume, uvw, 0.0).rgb;
+
+			// Modulate
+			color = mix(prev, color, 1.0 / 16.0);
+		}
+	}
+
+	// Write result
+	imageStore(u_volume, IVec3(gl_GlobalInvocationID), Vec4(color, 0.0));
+}
+
+#pragma anki end

+ 28 - 10
shaders/glsl_cpp_common/ClusteredShading.h

@@ -25,6 +25,7 @@ struct PointLight
 	UVec2 m_atlasTiles; // x: encodes 6 uints with atlas tile indices in the x dir. y: same for y dir.
 };
 const U32 SIZEOF_POINT_LIGHT = 3 * SIZEOF_VEC4;
+ANKI_SHADER_STATIC_ASSERT(sizeof(PointLight) == SIZEOF_POINT_LIGHT)
 
 // Spot light
 struct SpotLight
@@ -36,6 +37,7 @@ struct SpotLight
 	Mat4 m_texProjectionMat;
 };
 const U32 SIZEOF_SPOT_LIGHT = 4 * SIZEOF_VEC4 + SIZEOF_MAT4;
+ANKI_SHADER_STATIC_ASSERT(sizeof(SpotLight) == SIZEOF_SPOT_LIGHT)
 
 // Representation of a reflection probe
 struct ReflectionProbe
@@ -44,7 +46,8 @@ struct ReflectionProbe
 	Vec4 m_aabbMinPad1;
 	Vec4 m_aabbMaxPad1;
 };
-const U32 SIZEOF_REFLECTION_PROBE = 2 * SIZEOF_VEC4;
+const U32 SIZEOF_REFLECTION_PROBE = 3 * SIZEOF_VEC4;
+ANKI_SHADER_STATIC_ASSERT(sizeof(ReflectionProbe) == SIZEOF_REFLECTION_PROBE)
 
 // Decal
 struct Decal
@@ -55,6 +58,7 @@ struct Decal
 	Vec4 m_blendFactors;
 };
 const U32 SIZEOF_DECAL = 3 * SIZEOF_VEC4 + SIZEOF_MAT4;
+ANKI_SHADER_STATIC_ASSERT(sizeof(Decal) == SIZEOF_DECAL)
 
 // Common uniforms for light shading passes
 struct LightingUniforms
@@ -63,7 +67,9 @@ struct LightingUniforms
 	Vec4 m_rendererSizeTimeNear;
 	Vec4 m_cameraPosFar;
 	ClustererMagicValues m_clustererMagicValues;
-	UVec4 m_tileCount;
+	ClustererMagicValues m_prevClustererMagicValues;
+	UVec4 m_clusterCount;
+	UVec4 m_lightVolumeLastClusterPad3;
 	Mat4 m_viewMat;
 	Mat4 m_invViewMat;
 	Mat4 m_projMat;
@@ -74,11 +80,15 @@ struct LightingUniforms
 	Mat4 m_prevViewProjMatMulInvViewProjMat; // Used to re-project previous frames
 };
 
-ANKI_SHADER_FUNC_INLINE U32 computeClusterK(ClustererMagicValues magic, Vec3 worldPos)
+ANKI_SHADER_FUNC_INLINE F32 computeClusterKf(ClustererMagicValues magic, Vec3 worldPos)
 {
 	F32 fz = sqrt(dot(magic.m_val0.xyz(), worldPos) - magic.m_val0.w());
-	U32 z = U32(fz);
-	return z;
+	return fz;
+}
+
+ANKI_SHADER_FUNC_INLINE U32 computeClusterK(ClustererMagicValues magic, Vec3 worldPos)
+{
+	return U32(computeClusterKf(magic, worldPos));
 }
 
 // Compute cluster index
@@ -86,20 +96,28 @@ ANKI_SHADER_FUNC_INLINE U32 computeClusterIndex(
 	ClustererMagicValues magic, Vec2 uv, Vec3 worldPos, U32 clusterCountX, U32 clusterCountY)
 {
 	UVec2 xy = UVec2(uv * Vec2(clusterCountX, clusterCountY));
+	U32 k = computeClusterK(magic, worldPos);
+	return k * (clusterCountX * clusterCountY) + xy.y() * clusterCountX + xy.x();
+}
 
-	return computeClusterK(magic, worldPos) * (clusterCountX * clusterCountY) + xy.y() * clusterCountX + xy.x();
+// Compute the Z of the near plane given a cluster idx
+ANKI_SHADER_FUNC_INLINE F32 computeClusterNearf(ClustererMagicValues magic, F32 fk)
+{
+	return magic.m_val1.x() * fk * fk + magic.m_val1.y();
 }
 
 // Compute the Z of the near plane given a cluster idx
 ANKI_SHADER_FUNC_INLINE F32 computeClusterNear(ClustererMagicValues magic, U32 k)
 {
-	F32 fk = F32(k);
-	return magic.m_val1.x() * fk * fk + magic.m_val1.y();
+	return computeClusterNearf(magic, F32(k));
 }
 
-ANKI_SHADER_FUNC_INLINE F32 computeClusterFar(ClustererMagicValues magic, U32 k)
+// Compute the UV coordinates of a volume texture that encloses the clusterer
+ANKI_SHADER_FUNC_INLINE Vec3 computeClustererVolumeTextureUvs(
+	ClustererMagicValues magic, Vec2 uv, Vec3 worldPos, U32 clusterCountZ)
 {
-	return computeClusterNear(magic, k + 1u);
+	F32 k = computeClusterKf(magic, worldPos);
+	return Vec3(uv, k / F32(clusterCountZ));
 }
 
 ANKI_END_NAMESPACE

+ 10 - 6
shaders/glsl_cpp_common/Common.h

@@ -15,9 +15,11 @@
 #	define ANKI_END_NAMESPACE }
 #	define ANKI_SHADER_FUNC_INLINE inline
 
-#	define ANKI_SHADER_INOUT(type_) type&
-#	define ANKI_SHADER_IN(type_) const type&
-#	define ANKI_SHADER_OUT(type_) type&
+#	define ANKI_SHADER_INOUT(type_) type_&
+#	define ANKI_SHADER_IN(type_) const type_&
+#	define ANKI_SHADER_OUT(type_) type_&
+
+#	define ANKI_SHADER_STATIC_ASSERT(cond_) static_assert(cond_, "See file");
 
 ANKI_BEGIN_NAMESPACE
 template<typename T>
@@ -39,9 +41,11 @@ ANKI_END_NAMESPACE
 #	define ANKI_END_NAMESPACE
 #	define ANKI_SHADER_FUNC_INLINE
 
-#	define ANKI_SHADER_INOUT(type_) inout type
-#	define ANKI_SHADER_IN(type_) in type
-#	define ANKI_SHADER_OUT(type_) out type
+#	define ANKI_SHADER_INOUT(type_) inout type_
+#	define ANKI_SHADER_IN(type_) in type_
+#	define ANKI_SHADER_OUT(type_) out type_
+
+#	define ANKI_SHADER_STATIC_ASSERT(cond_)
 #endif
 
 //

+ 2 - 1
src/anki/Renderer.h

@@ -8,7 +8,7 @@
 #include <anki/renderer/GBuffer.h>
 #include <anki/renderer/Common.h>
 #include <anki/renderer/LightShading.h>
-#include <anki/renderer/Volumetric.h>
+#include <anki/renderer/VolumetricFog.h>
 #include <anki/renderer/ForwardShading.h>
 #include <anki/renderer/FinalComposite.h>
 #include <anki/renderer/ClusterBin.h>
@@ -29,5 +29,6 @@
 #include <anki/renderer/Tonemapping.h>
 #include <anki/renderer/RendererObject.h>
 #include <anki/renderer/Bloom.h>
+#include <anki/renderer/VolumetricLightingAccumulation.h>
 
 /// @defgroup renderer Renderering system

+ 4 - 0
src/anki/core/Config.cpp

@@ -20,6 +20,10 @@ Config::Config()
 	newOption("r.clusterSizeZ", 32);
 	newOption("r.avgObjectsPerCluster", 16);
 
+	newOption("r.volumetricLightingAccumulation.clusterFractionXY", 4);
+	newOption("r.volumetricLightingAccumulation.clusterFractionZ", 4);
+	newOption("r.volumetricLightingAccumulation.finalClusterInZ", 26);
+
 	newOption("r.shadowMapping.enabled", true);
 	newOption("r.shadowMapping.resolution", 512);
 	newOption("r.shadowMapping.tileCountPerRowOrColumn", 8);

+ 12 - 0
src/anki/gr/Enums.h

@@ -425,6 +425,18 @@ enum class SamplingFilter : U8
 	BASE ///< Only for mipmaps
 };
 
+enum class SamplingAddressing : U8
+{
+	CLAMP,
+	REPEAT,
+	BLACK,
+	WHITE,
+
+	COUNT,
+	FIRST = 0,
+	LAST = COUNT - 1,
+};
+
 enum class ShaderType : U8
 {
 	VERTEX,

+ 5 - 5
src/anki/gr/Sampler.h

@@ -23,7 +23,7 @@ public:
 	SamplingFilter m_mipmapFilter = SamplingFilter::BASE;
 	CompareOperation m_compareOperation = CompareOperation::ALWAYS;
 	I8 m_anisotropyLevel = 0;
-	Bool8 m_repeat = true; ///< Repeat or clamp.
+	SamplingAddressing m_addressing = SamplingAddressing::REPEAT;
 	U8 _m_padding[3] = {0, 0, 0};
 
 	SamplerInitInfo() = default;
@@ -36,11 +36,11 @@ public:
 	U64 computeHash() const
 	{
 		const U8* const first = reinterpret_cast<const U8* const>(&m_minLod);
-		const U8* const last = reinterpret_cast<const U8* const>(&m_repeat) + sizeof(m_repeat);
+		const U8* const last = reinterpret_cast<const U8* const>(&m_addressing) + sizeof(m_addressing);
 		const U size = last - first;
-		ANKI_ASSERT(
-			size
-			== sizeof(F32) * 2 + sizeof(SamplingFilter) * 2 + sizeof(CompareOperation) + sizeof(I8) + sizeof(Bool8));
+		ANKI_ASSERT(size
+					== sizeof(F32) * 2 + sizeof(SamplingFilter) * 2 + sizeof(CompareOperation) + sizeof(I8)
+						   + sizeof(SamplingAddressing));
 		return anki::computeHash(first, size);
 	}
 };

+ 6 - 2
src/anki/gr/gl/SamplerImpl.cpp

@@ -14,18 +14,22 @@ void SamplerImpl::init(const SamplerInitInfo& sinit)
 	glGenSamplers(1, &m_glName);
 	ANKI_ASSERT(m_glName);
 
-	if(sinit.m_repeat)
+	if(sinit.m_addressing == SamplingAddressing::REPEAT)
 	{
 		glSamplerParameteri(m_glName, GL_TEXTURE_WRAP_S, GL_REPEAT);
 		glSamplerParameteri(m_glName, GL_TEXTURE_WRAP_T, GL_REPEAT);
 		glSamplerParameteri(m_glName, GL_TEXTURE_WRAP_R, GL_REPEAT);
 	}
-	else
+	else if(sinit.m_addressing == SamplingAddressing::CLAMP)
 	{
 		glSamplerParameteri(m_glName, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 		glSamplerParameteri(m_glName, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 		glSamplerParameteri(m_glName, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
 	}
+	else
+	{
+		ANKI_ASSERT(!"TODO");
+	}
 
 	// Set filtering type
 	GLenum minFilter = GL_NONE;

+ 16 - 5
src/anki/gr/vulkan/SamplerFactory.cpp

@@ -42,13 +42,24 @@ Error MicroSampler::init(const SamplerInitInfo& inf)
 		ci.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
 	}
 
-	if(inf.m_repeat)
-	{
-		ci.addressModeU = ci.addressModeV = ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
-	}
-	else
+	switch(inf.m_addressing)
 	{
+	case SamplingAddressing::CLAMP:
 		ci.addressModeU = ci.addressModeV = ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		break;
+	case SamplingAddressing::REPEAT:
+		ci.addressModeU = ci.addressModeV = ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		break;
+	case SamplingAddressing::BLACK:
+		ci.addressModeU = ci.addressModeV = ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+		ci.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+		break;
+	case SamplingAddressing::WHITE:
+		ci.addressModeU = ci.addressModeV = ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+		ci.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		break;
+	default:
+		ANKI_ASSERT(0);
 	}
 
 	ci.mipLodBias = 0.0;

+ 12 - 0
src/anki/gr/vulkan/TextureImpl.cpp

@@ -660,6 +660,11 @@ VkImageLayout TextureImpl::computeLayout(TextureUsageBit usage, U level) const
 	{
 		out = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
 	}
+	else
+	{
+		// Can't set it to something, chose general
+		out = VK_IMAGE_LAYOUT_GENERAL;
+	}
 
 	ANKI_ASSERT(out != VK_IMAGE_LAYOUT_MAX_ENUM);
 	return out;
@@ -690,6 +695,13 @@ VkImageView TextureImpl::getOrCreateView(const TextureSubresourceInfo& subresour
 
 		m_viewsMap.emplace(getAllocator(), subresource, view);
 
+#if 0
+		printf("Creating image view %p. Texture %p %s\n",
+		static_cast<void*>(view),
+		static_cast<void*>(m_imageHandle),
+		getName() ? getName().cstr() : "Unnamed");
+#endif
+
 		return view;
 	}
 }

+ 2 - 1
src/anki/renderer/Common.h

@@ -32,11 +32,12 @@ class FinalComposite;
 class Dbg;
 class Indirect;
 class DownscaleBlur;
-class Volumetric;
+class VolumetricFog;
 class DepthDownscale;
 class TemporalAA;
 class UiStage;
 class Ssr;
+class VolumetricLightingAccumulation;
 
 class RenderingContext;
 class DebugDrawer;

+ 79 - 92
src/anki/renderer/DepthDownscale.cpp

@@ -12,8 +12,6 @@ namespace anki
 
 DepthDownscale::~DepthDownscale()
 {
-	m_passes.destroy(getAllocator());
-
 	if(m_copyToBuff.m_buffAddr)
 	{
 		m_copyToBuff.m_buff->unmap();
@@ -22,64 +20,30 @@ DepthDownscale::~DepthDownscale()
 
 Error DepthDownscale::initInternal(const ConfigSet&)
 {
-	const U width = m_r->getWidth() / 2;
-	const U height = m_r->getHeight() / 2;
-
-	const U mipCount = computeMaxMipmapCount2d(width, height, HIERARCHICAL_Z_MIN_HEIGHT);
-
-	const U lastMipWidth = width >> (mipCount - 1);
-	const U lastMipHeight = height >> (mipCount - 1);
+	const U width = m_r->getWidth() >> 1;
+	const U height = m_r->getHeight() >> 1;
 
-	ANKI_R_LOGI("Initializing HiZ. Mip count %u, last mip size %ux%u", mipCount, lastMipWidth, lastMipHeight);
+	m_mipCount = computeMaxMipmapCount2d(width, height, HIERARCHICAL_Z_MIN_HEIGHT);
 
-	m_passes.create(getAllocator(), mipCount);
+	const U lastMipWidth = width >> (m_mipCount - 1);
+	const U lastMipHeight = height >> (m_mipCount - 1);
 
-	// Create RT descrs
-	m_depthRtDescr =
-		m_r->create2DRenderTargetDescription(width, height, GBUFFER_DEPTH_ATTACHMENT_PIXEL_FORMAT, "Half depth");
-	m_depthRtDescr.bake();
+	ANKI_R_LOGI("Initializing HiZ. Mip count %u, last mip size %ux%u", m_mipCount, lastMipWidth, lastMipHeight);
 
+	// Create RT descr
 	m_hizRtDescr = m_r->create2DRenderTargetDescription(width, height, Format::R32_SFLOAT, "HiZ");
-	m_hizRtDescr.m_mipmapCount = mipCount;
+	m_hizRtDescr.m_mipmapCount = m_mipCount;
 	m_hizRtDescr.bake();
 
-	// Create FB descr
-	m_passes[0].m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-	m_passes[0].m_fbDescr.m_colorAttachmentCount = 1;
-	m_passes[0].m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-	m_passes[0].m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
-	m_passes[0].m_fbDescr.bake();
-
-	for(U i = 1; i < m_passes.getSize(); ++i)
-	{
-		m_passes[i].m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-		m_passes[i].m_fbDescr.m_colorAttachments[0].m_surface.m_level = i;
-		m_passes[i].m_fbDescr.m_colorAttachmentCount = 1;
-		m_passes[i].m_fbDescr.bake();
-	}
-
 	// Progs
 	ANKI_CHECK(getResourceManager().loadResource("shaders/DepthDownscale.glslp", m_prog));
 
-	ShaderProgramResourceMutationInitList<3> mutations(m_prog);
-	mutations.add("COPY_TO_CLIENT", 0).add("TYPE", 0).add("SAMPLE_RESOLVE_TYPE", 2);
+	ShaderProgramResourceMutationInitList<1> mutations(m_prog);
+	mutations.add("SAMPLE_RESOLVE_TYPE", 2);
 
 	const ShaderProgramResourceVariant* variant;
 	m_prog->getOrCreateVariant(mutations.get(), variant);
-	m_passes[0].m_grProg = variant->getProgram();
-
-	for(U i = 1; i < m_passes.getSize(); ++i)
-	{
-		mutations[1].m_value = 1;
-
-		if(i == m_passes.getSize() - 1)
-		{
-			mutations[0].m_value = 1;
-		}
-
-		m_prog->getOrCreateVariant(mutations.get(), variant);
-		m_passes[i].m_grProg = variant->getProgram();
-	}
+	m_grProg = variant->getProgram();
 
 	// Copy to buffer
 	{
@@ -118,42 +82,49 @@ Error DepthDownscale::init(const ConfigSet& cfg)
 void DepthDownscale::populateRenderGraph(RenderingContext& ctx)
 {
 	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
-	m_runCtx.m_pass = 0;
+	m_runCtx.m_mip = 0;
 
 	static const Array<CString, 5> passNames = {{"HiZ #0", "HiZ #1", "HiZ #2", "HiZ #3", "HiZ #4"}};
 
 	// Create render targets
-	m_runCtx.m_halfDepthRt = rgraph.newRenderTarget(m_depthRtDescr);
 	m_runCtx.m_hizRt = rgraph.newRenderTarget(m_hizRtDescr);
 
-	// First render pass
+	// Every pass can do MIPS_WRITTEN_PER_PASS mips
+	for(U i = 0; i < m_mipCount; i += MIPS_WRITTEN_PER_PASS)
 	{
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[0]);
-
-		pass.setFramebufferInfo(m_passes[0].m_fbDescr, {{m_runCtx.m_hizRt}}, m_runCtx.m_halfDepthRt);
-		pass.setWork(runCallback, this, 0);
+		const U mipsToFill = (i + 1 < m_mipCount) ? MIPS_WRITTEN_PER_PASS : 1;
 
-		TextureSubresourceInfo subresource = TextureSubresourceInfo(DepthStencilAspectBit::DEPTH); // First mip
+		ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass(passNames[i / MIPS_WRITTEN_PER_PASS]);
 
-		pass.newDependency({m_r->getGBuffer().getDepthRt(), TextureUsageBit::SAMPLED_FRAGMENT, subresource});
-		pass.newDependency({m_runCtx.m_halfDepthRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, subresource});
-		pass.newDependency({m_runCtx.m_hizRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, subresource});
-	}
+		if(i == 0)
+		{
+			pass.newDependency({m_r->getGBuffer().getDepthRt(),
+				TextureUsageBit::SAMPLED_COMPUTE,
+				TextureSubresourceInfo(DepthStencilAspectBit::DEPTH)});
+		}
+		else
+		{
+			TextureSubresourceInfo subresource;
+			subresource.m_firstMipmap = i - 1;
 
-	// Rest of the passes
-	for(U i = 1; i < m_passes.getSize(); ++i)
-	{
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[i]);
+			pass.newDependency({m_runCtx.m_hizRt, TextureUsageBit::SAMPLED_COMPUTE, subresource});
+		}
 
-		pass.setFramebufferInfo(m_passes[i].m_fbDescr, {{m_runCtx.m_hizRt}}, {});
-		pass.setWork(runCallback, this, 0);
+		TextureSubresourceInfo subresource;
+		subresource.m_firstMipmap = i;
+		pass.newDependency({m_runCtx.m_hizRt, TextureUsageBit::IMAGE_COMPUTE_WRITE, subresource});
 
-		TextureSubresourceInfo subresourceRead, subresourceWrite;
-		subresourceRead.m_firstMipmap = i - 1;
-		subresourceWrite.m_firstMipmap = i;
+		if(mipsToFill == MIPS_WRITTEN_PER_PASS)
+		{
+			subresource.m_firstMipmap = i + 1;
+			pass.newDependency({m_runCtx.m_hizRt, TextureUsageBit::IMAGE_COMPUTE_WRITE, subresource});
+		}
 
-		pass.newDependency({m_runCtx.m_hizRt, TextureUsageBit::SAMPLED_FRAGMENT, subresourceRead});
-		pass.newDependency({m_runCtx.m_hizRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, subresourceWrite});
+		auto callback = [](RenderPassWorkContext& rgraphCtx) {
+			DepthDownscale* const self = static_cast<DepthDownscale*>(rgraphCtx.m_userData);
+			self->run(rgraphCtx);
+		};
+		pass.setWork(callback, this, 0);
 	}
 }
 
@@ -161,44 +132,60 @@ void DepthDownscale::run(RenderPassWorkContext& rgraphCtx)
 {
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 
-	const U passIdx = m_runCtx.m_pass++;
+	const U level = m_runCtx.m_mip;
+	m_runCtx.m_mip += MIPS_WRITTEN_PER_PASS;
+	const U mipsToFill = (level + 1 < m_mipCount) ? MIPS_WRITTEN_PER_PASS : 1;
+	const U copyToClientLevel = (level + mipsToFill == m_mipCount) ? mipsToFill - 1 : MAX_U;
+
+	const U level0Width = m_r->getWidth() >> (level + 1);
+	const U level0Height = m_r->getHeight() >> (level + 1);
+	const U level1Width = level0Width >> 1;
+	const U level1Height = level0Height >> 1;
+
+	cmdb->bindShaderProgram(m_grProg);
 
-	cmdb->bindShaderProgram(m_passes[passIdx].m_grProg);
-	cmdb->setViewport(0, 0, m_r->getWidth() >> (passIdx + 1), m_r->getHeight() >> (passIdx + 1));
+	// Uniforms
+	struct PushConsts
+	{
+		UVec4 m_writeImgSizes;
+		UVec4 m_copyToClientLevelWriteLevel1Pad2;
+	} regs;
+
+	regs.m_writeImgSizes = UVec4(level0Width, level0Height, level1Width, level1Height);
+	regs.m_copyToClientLevelWriteLevel1Pad2.x() = copyToClientLevel;
+	regs.m_copyToClientLevelWriteLevel1Pad2.y() = mipsToFill == MIPS_WRITTEN_PER_PASS;
+	cmdb->setPushConstants(&regs, sizeof(regs));
 
-	if(passIdx == 0)
+	// Bind input texure
+	if(level == 0)
 	{
 		rgraphCtx.bindTextureAndSampler(0,
 			0,
 			m_r->getGBuffer().getDepthRt(),
 			TextureSubresourceInfo(DepthStencilAspectBit::DEPTH),
 			m_r->getNearestSampler());
-
-		cmdb->setDepthCompareOperation(CompareOperation::ALWAYS);
 	}
 	else
 	{
-		TextureSubresourceInfo sampleSubresource;
-		sampleSubresource.m_firstMipmap = passIdx - 1;
-
-		rgraphCtx.bindTextureAndSampler(0, 0, m_runCtx.m_hizRt, sampleSubresource, m_r->getNearestSampler());
+		TextureSubresourceInfo subresource;
+		subresource.m_firstMipmap = level - 1;
+		rgraphCtx.bindTextureAndSampler(0, 0, m_runCtx.m_hizRt, subresource, m_r->getNearestSampler());
 	}
 
-	if(passIdx == m_passes.getSize() - 1)
-	{
-		UVec4 size(m_copyToBuff.m_lastMipWidth, m_copyToBuff.m_lastMipHeight, 0, 0);
-		cmdb->setPushConstants(&size, sizeof(size));
+	// 1st level
+	TextureSubresourceInfo subresource;
+	subresource.m_firstMipmap = level;
+	rgraphCtx.bindImage(0, 0, m_runCtx.m_hizRt, subresource);
 
-		cmdb->bindStorageBuffer(0, 0, m_copyToBuff.m_buff, 0, m_copyToBuff.m_buff->getSize());
-	}
+	// 2nd level
+	subresource.m_firstMipmap = (mipsToFill == MIPS_WRITTEN_PER_PASS) ? level + 1 : level; // Bind the next or the same
+	rgraphCtx.bindImage(0, 1, m_runCtx.m_hizRt, subresource);
 
-	drawQuad(cmdb);
+	// Client buffer
+	cmdb->bindStorageBuffer(0, 0, m_copyToBuff.m_buff, 0, m_copyToBuff.m_buff->getSize());
 
-	// Restore state
-	if(passIdx == 0)
-	{
-		cmdb->setDepthCompareOperation(CompareOperation::LESS);
-	}
+	// Done
+	dispatchPPCompute(cmdb, 8, 8, level0Width, level0Height);
 }
 
 } // end namespace anki

+ 6 - 25
src/anki/renderer/DepthDownscale.h

@@ -34,12 +34,6 @@ anki_internal:
 	/// Populate the rendergraph.
 	void populateRenderGraph(RenderingContext& ctx);
 
-	/// Return a depth buffer that is a quarter of the resolution of the renderer.
-	RenderTargetHandle getHalfDepthRt() const
-	{
-		return m_runCtx.m_halfDepthRt;
-	}
-
 	/// Return a FP color render target with hierarchical Z (min Z) in it's mips.
 	RenderTargetHandle getHiZRt() const
 	{
@@ -48,7 +42,7 @@ anki_internal:
 
 	U32 getMipmapCount() const
 	{
-		return m_passes.getSize();
+		return m_mipCount;
 	}
 
 	void getClientDepthMapInfo(F32*& depthValues, U32& width, U32& height) const
@@ -60,25 +54,18 @@ anki_internal:
 	}
 
 private:
-	RenderTargetDescription m_depthRtDescr;
+	static const U32 MIPS_WRITTEN_PER_PASS = 2;
+
 	RenderTargetDescription m_hizRtDescr;
 	ShaderProgramResourcePtr m_prog;
-
-	class Pass
-	{
-	public:
-		FramebufferDescription m_fbDescr;
-		ShaderProgramPtr m_grProg;
-	};
-
-	DynamicArray<Pass> m_passes;
+	ShaderProgramPtr m_grProg;
+	U32 m_mipCount = 0;
 
 	class
 	{
 	public:
-		RenderTargetHandle m_halfDepthRt;
 		RenderTargetHandle m_hizRt;
-		U m_pass;
+		U8 m_mip;
 	} m_runCtx; ///< Run context.
 
 	class
@@ -92,12 +79,6 @@ private:
 	ANKI_USE_RESULT Error initInternal(const ConfigSet& cfg);
 
 	void run(RenderPassWorkContext& rgraphCtx);
-
-	/// A RenderPassWorkCallback for half depth main pass.
-	static void runCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		static_cast<DepthDownscale*>(rgraphCtx.m_userData)->run(rgraphCtx);
-	}
 };
 /// @}
 

+ 16 - 117
src/anki/renderer/ForwardShading.cpp

@@ -9,9 +9,9 @@
 #include <anki/renderer/GBuffer.h>
 #include <anki/renderer/LightShading.h>
 #include <anki/renderer/ShadowMapping.h>
-#include <anki/renderer/Volumetric.h>
 #include <anki/renderer/DepthDownscale.h>
 #include <anki/renderer/LensFlare.h>
+#include <anki/renderer/VolumetricLightingAccumulation.h>
 
 namespace anki
 {
@@ -23,90 +23,10 @@ ForwardShading::~ForwardShading()
 Error ForwardShading::init(const ConfigSet& cfg)
 {
 	ANKI_R_LOGI("Initializing forward shading");
-
-	Error err = initInternal(cfg);
-	if(err)
-	{
-		ANKI_R_LOGE("Failed to initialize forward shading");
-	}
-
-	return err;
-}
-
-Error ForwardShading::initInternal(const ConfigSet&)
-{
-	m_width = m_r->getWidth() / FS_FRACTION;
-	m_height = m_r->getHeight() / FS_FRACTION;
-
-	// Create RT descr
-	m_rtDescr = m_r->create2DRenderTargetDescription(
-		m_width, m_height, FORWARD_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT, "forward");
-	m_rtDescr.bake();
-
-	// Create FB descr
-	m_fbDescr.m_colorAttachmentCount = 1;
-	m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::CLEAR;
-	m_fbDescr.m_colorAttachments[0].m_clearValue.m_colorf = {{0.0, 0.0, 0.0, 1.0}};
-	m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::LOAD;
-	m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
-	m_fbDescr.bake();
-
-	ANKI_CHECK(initVol());
-
-	return Error::NONE;
-}
-
-Error ForwardShading::initVol()
-{
-	ANKI_CHECK(getResourceManager().loadResource("engine_data/BlueNoiseLdrRgb64x64.ankitex", m_vol.m_noiseTex));
-
-	ANKI_CHECK(getResourceManager().loadResource("shaders/ForwardShadingVolumetricUpscale.glslp", m_vol.m_prog));
-
-	ShaderProgramResourceConstantValueInitList<3> consts(m_vol.m_prog);
-	consts.add("NOISE_TEX_SIZE", U32(m_vol.m_noiseTex->getWidth()))
-		.add("SRC_SIZE", Vec2(m_r->getWidth() / VOLUMETRIC_FRACTION, m_r->getHeight() / VOLUMETRIC_FRACTION))
-		.add("FB_SIZE", Vec2(m_width, m_height));
-
-	const ShaderProgramResourceVariant* variant;
-	m_vol.m_prog->getOrCreateVariant(consts.get(), variant);
-	m_vol.m_grProg = variant->getProgram();
-
 	return Error::NONE;
 }
 
-void ForwardShading::drawVolumetric(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx)
-{
-	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-
-	cmdb->setViewport(0, 0, m_width, m_height);
-	cmdb->bindShaderProgram(m_vol.m_grProg);
-	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ONE);
-	cmdb->setDepthWrite(false);
-	cmdb->setDepthCompareOperation(CompareOperation::ALWAYS);
-
-	Vec4* unis = allocateAndBindUniforms<Vec4*>(sizeof(Vec4), cmdb, 0, 0);
-	computeLinearizeDepthOptimal(ctx.m_renderQueue->m_cameraNear, ctx.m_renderQueue->m_cameraFar, unis->x(), unis->y());
-
-	rgraphCtx.bindTextureAndSampler(
-		0, 0, m_r->getDepthDownscale().getHiZRt(), HIZ_HALF_DEPTH, m_r->getNearestSampler());
-	rgraphCtx.bindTextureAndSampler(
-		0, 1, m_r->getDepthDownscale().getHiZRt(), HIZ_QUARTER_DEPTH, m_r->getNearestSampler());
-	rgraphCtx.bindColorTextureAndSampler(0, 2, m_r->getVolumetric().getRt(), m_r->getLinearSampler());
-	cmdb->bindTextureAndSampler(0,
-		3,
-		m_vol.m_noiseTex->getGrTextureView(),
-		m_r->getTrilinearRepeatSampler(),
-		TextureUsageBit::SAMPLED_FRAGMENT);
-
-	drawQuad(cmdb);
-
-	// Restore state
-	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
-	cmdb->setDepthWrite(true);
-	cmdb->setDepthCompareOperation(CompareOperation::LESS);
-}
-
-void ForwardShading::run(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx)
+void ForwardShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx)
 {
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 	const U threadId = rgraphCtx.m_currentSecondLevelCommandBufferIndex;
@@ -117,22 +37,21 @@ void ForwardShading::run(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 
 	if(start != end)
 	{
+		cmdb->setDepthWrite(false);
+		cmdb->setBlendFactors(0, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA);
+
 		const ClusterBinOut& rsrc = ctx.m_clusterBinOut;
 		rgraphCtx.bindTextureAndSampler(
 			0, 0, m_r->getDepthDownscale().getHiZRt(), HIZ_HALF_DEPTH, m_r->getLinearSampler());
-		rgraphCtx.bindColorTextureAndSampler(0, 1, m_r->getShadowMapping().getShadowmapRt(), m_r->getLinearSampler());
+		rgraphCtx.bindColorTextureAndSampler(
+			0, 1, m_r->getVolumetricLightingAccumulation().getRt(), m_r->getLinearSampler());
+		rgraphCtx.bindColorTextureAndSampler(0, 2, m_r->getShadowMapping().getShadowmapRt(), m_r->getLinearSampler());
 		bindUniforms(cmdb, 0, 0, ctx.m_lightShadingUniformsToken);
 		bindUniforms(cmdb, 0, 1, rsrc.m_pointLightsToken);
 		bindUniforms(cmdb, 0, 2, rsrc.m_spotLightsToken);
 		bindStorage(cmdb, 0, 0, rsrc.m_clustersToken);
 		bindStorage(cmdb, 0, 1, rsrc.m_indicesToken);
 
-		cmdb->setViewport(0, 0, m_width, m_height);
-		cmdb->setBlendFactors(
-			0, BlendFactor::ONE_MINUS_SRC_ALPHA, BlendFactor::SRC_ALPHA, BlendFactor::DST_ALPHA, BlendFactor::ZERO);
-		cmdb->setBlendOperation(0, BlendOperation::ADD);
-		cmdb->setDepthWrite(false);
-
 		// Start drawing
 		m_r->getSceneDrawer().drawRange(Pass::FS,
 			ctx.m_matrices.m_view,
@@ -141,43 +60,23 @@ void ForwardShading::run(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 			cmdb,
 			ctx.m_renderQueue->m_forwardShadingRenderables.getBegin() + start,
 			ctx.m_renderQueue->m_forwardShadingRenderables.getBegin() + end);
+
+		// Restore state
+		cmdb->setDepthWrite(true);
+		cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
 	}
 
-	if(threadId == threadCount - 1)
+	if(threadId == threadCount - 1 && ctx.m_renderQueue->m_lensFlares.getSize())
 	{
-		drawVolumetric(ctx, rgraphCtx);
-
-		if(ctx.m_renderQueue->m_lensFlares.getSize())
-		{
-			m_r->getLensFlare().runDrawFlares(ctx, cmdb);
-		}
+		m_r->getLensFlare().runDrawFlares(ctx, cmdb);
 	}
 }
 
-void ForwardShading::populateRenderGraph(RenderingContext& ctx)
+void ForwardShading::setDependencies(const RenderingContext& ctx, GraphicsRenderPassDescription& pass)
 {
-	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
-	m_runCtx.m_ctx = &ctx;
-
-	// Create RT
-	m_runCtx.m_rt = rgraph.newRenderTarget(m_rtDescr);
-
-	// Create pass
-	GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("Forward shading");
-
-	pass.setWork(runCallback,
-		this,
-		computeNumberOfSecondLevelCommandBuffers(ctx.m_renderQueue->m_forwardShadingRenderables.getSize()));
-	pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rt}}, m_r->getDepthDownscale().getHalfDepthRt());
-
-	pass.newDependency({m_runCtx.m_rt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE});
-	pass.newDependency({m_r->getDepthDownscale().getHalfDepthRt(),
-		TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ,
-		TextureSubresourceInfo(DepthStencilAspectBit::DEPTH)});
 	pass.newDependency({m_r->getDepthDownscale().getHiZRt(), TextureUsageBit::SAMPLED_FRAGMENT, HIZ_HALF_DEPTH});
 	pass.newDependency({m_r->getDepthDownscale().getHiZRt(), TextureUsageBit::SAMPLED_FRAGMENT, HIZ_QUARTER_DEPTH});
-	pass.newDependency({m_r->getVolumetric().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
-	pass.newDependency({m_r->getShadowMapping().getShadowmapRt(), TextureUsageBit::SAMPLED_FRAGMENT});
+	pass.newDependency({m_r->getVolumetricLightingAccumulation().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 
 	if(ctx.m_renderQueue->m_lensFlares.getSize())
 	{

+ 2 - 54
src/anki/renderer/ForwardShading.h

@@ -26,61 +26,9 @@ anki_internal:
 
 	ANKI_USE_RESULT Error init(const ConfigSet& initializer);
 
-	/// Populate the rendergraph.
-	void populateRenderGraph(RenderingContext& ctx);
+	void setDependencies(const RenderingContext& ctx, GraphicsRenderPassDescription& pass);
 
-	void drawUpscale(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx);
-
-	U32 getWidth() const
-	{
-		return m_width;
-	}
-
-	U32 getHeight() const
-	{
-		return m_height;
-	}
-
-	RenderTargetHandle getRt() const
-	{
-		return m_runCtx.m_rt;
-	}
-
-private:
-	U32 m_width;
-	U32 m_height;
-
-	FramebufferDescription m_fbDescr;
-	RenderTargetDescription m_rtDescr;
-
-	class Vol
-	{
-	public:
-		ShaderProgramResourcePtr m_prog;
-		ShaderProgramPtr m_grProg;
-		TextureResourcePtr m_noiseTex;
-	} m_vol;
-
-	class
-	{
-	public:
-		RenderTargetHandle m_rt;
-		RenderingContext* m_ctx = nullptr;
-	} m_runCtx;
-
-	ANKI_USE_RESULT Error initInternal(const ConfigSet& initializer);
-	ANKI_USE_RESULT Error initVol();
-
-	/// A RenderPassWorkCallback.
-	static void runCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		ForwardShading* self = scast<ForwardShading*>(rgraphCtx.m_userData);
-		self->run(*self->m_runCtx.m_ctx, rgraphCtx);
-	}
-
-	void run(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx);
-
-	void drawVolumetric(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx);
+	void run(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx);
 };
 /// @}
 

+ 176 - 150
src/anki/renderer/Indirect.cpp

@@ -59,7 +59,7 @@ Error Indirect::initInternal(const ConfigSet& config)
 	sinit.m_mipmapFilter = SamplingFilter::BASE;
 	sinit.m_minLod = 0.0;
 	sinit.m_maxLod = 1.0;
-	sinit.m_repeat = false;
+	sinit.m_addressing = SamplingAddressing::CLAMP;
 	m_integrationLutSampler = getGrManager().newSampler(sinit);
 
 	return Error::NONE;
@@ -492,191 +492,217 @@ void Indirect::populateRenderGraph(RenderingContext& rctx)
 	prepareProbes(rctx, probeToUpdate, probeToUpdateCacheEntryIdx);
 
 	// Render a probe if needed
-	if(probeToUpdate)
+	if(!probeToUpdate)
 	{
-		m_ctx.m_cacheEntryIdx = probeToUpdateCacheEntryIdx;
-		m_ctx.m_probe = probeToUpdate;
+		// Just import and exit
 
-		if(!m_cacheEntries[probeToUpdateCacheEntryIdx].m_lightShadingFbDescrs[0].isBacked())
+		m_ctx.m_lightShadingRt = rgraph.importRenderTarget(m_lightShading.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
+		m_ctx.m_irradianceRt = rgraph.importRenderTarget(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
+
+		return;
+	}
+
+	m_ctx.m_cacheEntryIdx = probeToUpdateCacheEntryIdx;
+	m_ctx.m_probe = probeToUpdate;
+
+	if(!m_cacheEntries[probeToUpdateCacheEntryIdx].m_lightShadingFbDescrs[0].isBacked())
+	{
+		initCacheEntry(probeToUpdateCacheEntryIdx);
+	}
+
+	// G-buffer pass
+	{
+		// RTs
+		Array<RenderTargetHandle, MAX_COLOR_ATTACHMENTS> rts;
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
 		{
-			initCacheEntry(probeToUpdateCacheEntryIdx);
+			m_ctx.m_gbufferColorRts[i] = rgraph.newRenderTarget(m_gbuffer.m_colorRtDescrs[i]);
+			rts[i] = m_ctx.m_gbufferColorRts[i];
 		}
+		m_ctx.m_gbufferDepthRt = rgraph.newRenderTarget(m_gbuffer.m_depthRtDescr);
 
-		// G-buffer pass
-		{
-			// RTs
-			Array<RenderTargetHandle, MAX_COLOR_ATTACHMENTS> rts;
-			for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
-			{
-				m_ctx.m_gbufferColorRts[i] = rgraph.newRenderTarget(m_gbuffer.m_colorRtDescrs[i]);
-				rts[i] = m_ctx.m_gbufferColorRts[i];
-			}
-			m_ctx.m_gbufferDepthRt = rgraph.newRenderTarget(m_gbuffer.m_depthRtDescr);
+		// Pass
+		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("GI gbuff");
+		pass.setFramebufferInfo(m_gbuffer.m_fbDescr, rts, m_ctx.m_gbufferDepthRt);
+		pass.setWork(runGBufferCallback, this, 0);
 
-			// Pass
-			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("GI gbuff");
-			pass.setFramebufferInfo(m_gbuffer.m_fbDescr, rts, m_ctx.m_gbufferDepthRt);
-			pass.setWork(runGBufferCallback, this, 0);
+		for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+		{
+			pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
+		}
 
-			for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
-			{
-				pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
-			}
+		TextureSubresourceInfo subresource(DepthStencilAspectBit::DEPTH);
+		pass.newDependency({m_ctx.m_gbufferDepthRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, subresource});
+	}
 
-			TextureSubresourceInfo subresource(DepthStencilAspectBit::DEPTH);
-			pass.newDependency(
-				{m_ctx.m_gbufferDepthRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, subresource});
-		}
+	// Light shading passes
+	{
+		Array<RenderPassWorkCallback, 6> callbacks = {{runLightShadingCallback<0>,
+			runLightShadingCallback<1>,
+			runLightShadingCallback<2>,
+			runLightShadingCallback<3>,
+			runLightShadingCallback<4>,
+			runLightShadingCallback<5>}};
+
+		// RT
+		m_ctx.m_lightShadingRt = rgraph.importRenderTarget(m_lightShading.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
 
-		// Light shading passes
+		// Passes
+		static const Array<CString, 6> passNames = {{"GI LightShad #0",
+			"GI LightShad #1",
+			"GI LightShad #2",
+			"GI LightShad #3",
+			"GI LightShad #4",
+			"GI LightShad #5"}};
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 		{
-			Array<RenderPassWorkCallback, 6> callbacks = {{runLightShadingCallback<0>,
-				runLightShadingCallback<1>,
-				runLightShadingCallback<2>,
-				runLightShadingCallback<3>,
-				runLightShadingCallback<4>,
-				runLightShadingCallback<5>}};
-
-			// RT
-			m_ctx.m_lightShadingRt =
-				rgraph.importRenderTarget(m_lightShading.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
-
-			// Passes
-			static const Array<CString, 6> passNames = {{"GI LightShad #0",
-				"GI LightShad #1",
-				"GI LightShad #2",
-				"GI LightShad #3",
-				"GI LightShad #4",
-				"GI LightShad #5"}};
-			for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
+			pass.setFramebufferInfo(m_cacheEntries[probeToUpdateCacheEntryIdx].m_lightShadingFbDescrs[faceIdx],
+				{{m_ctx.m_lightShadingRt}},
+				{});
+			pass.setWork(callbacks[faceIdx], this, 0);
+
+			TextureSubresourceInfo subresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+			pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, subresource});
+
+			for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
 			{
-				GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
-				pass.setFramebufferInfo(m_cacheEntries[probeToUpdateCacheEntryIdx].m_lightShadingFbDescrs[faceIdx],
-					{{m_ctx.m_lightShadingRt}},
-					{});
-				pass.setWork(callbacks[faceIdx], this, 0);
-
-				TextureSubresourceInfo subresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
-				pass.newDependency(
-					{m_ctx.m_lightShadingRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, subresource});
-
-				for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
-				{
-					pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::SAMPLED_FRAGMENT});
-				}
-				pass.newDependency({m_ctx.m_gbufferDepthRt,
-					TextureUsageBit::SAMPLED_FRAGMENT,
-					TextureSubresourceInfo(DepthStencilAspectBit::DEPTH)});
+				pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::SAMPLED_FRAGMENT});
 			}
+			pass.newDependency({m_ctx.m_gbufferDepthRt,
+				TextureUsageBit::SAMPLED_FRAGMENT,
+				TextureSubresourceInfo(DepthStencilAspectBit::DEPTH)});
 		}
+	}
 
-		// Irradiance passes
+	// Irradiance passes
+	{
+		static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceCallback<0>,
+			runIrradianceCallback<1>,
+			runIrradianceCallback<2>,
+			runIrradianceCallback<3>,
+			runIrradianceCallback<4>,
+			runIrradianceCallback<5>}};
+
+		// Rt
+		m_ctx.m_irradianceRt = rgraph.importRenderTarget(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
+
+		static const Array<CString, 6> passNames = {
+			{"GI Irr/ce #0", "GI Irr/ce #1", "GI Irr/ce #2", "GI Irr/ce #3", "GI Irr/ce #4", "GI Irr/ce #5"}};
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 		{
-			static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceCallback<0>,
-				runIrradianceCallback<1>,
-				runIrradianceCallback<2>,
-				runIrradianceCallback<3>,
-				runIrradianceCallback<4>,
-				runIrradianceCallback<5>}};
-
-			// Rt
-			m_ctx.m_irradianceRt = rgraph.importRenderTarget(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
-
-			static const Array<CString, 6> passNames = {
-				{"GI Irr/ce #0", "GI Irr/ce #1", "GI Irr/ce #2", "GI Irr/ce #3", "GI Irr/ce #4", "GI Irr/ce #5"}};
-			for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
-			{
-				GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
+			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
 
-				pass.setFramebufferInfo(m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceFbDescrs[faceIdx],
-					{{m_ctx.m_irradianceRt}},
-					{});
+			pass.setFramebufferInfo(
+				m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceFbDescrs[faceIdx], {{m_ctx.m_irradianceRt}}, {});
 
-				pass.setWork(callbacks[faceIdx], this, 0);
+			pass.setWork(callbacks[faceIdx], this, 0);
 
-				// Read a cube but only one layer and level
-				TextureSubresourceInfo readSubresource;
-				readSubresource.m_faceCount = 6;
-				readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
-				pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
+			// Read a cube but only one layer and level
+			TextureSubresourceInfo readSubresource;
+			readSubresource.m_faceCount = 6;
+			readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
+			pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
 
-				TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
-				pass.newDependency(
-					{m_ctx.m_irradianceRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, writeSubresource});
-			}
+			TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+			pass.newDependency({m_ctx.m_irradianceRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, writeSubresource});
 		}
+	}
 
-		// Write irradiance back to refl
+	// Write irradiance back to refl
+	{
+		static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceToReflCallback<0>,
+			runIrradianceToReflCallback<1>,
+			runIrradianceToReflCallback<2>,
+			runIrradianceToReflCallback<3>,
+			runIrradianceToReflCallback<4>,
+			runIrradianceToReflCallback<5>}};
+
+		// Rt
+		static const Array<CString, 6> passNames = {{"GI Irr2Refl #0",
+			"GI Irr2Refl #1",
+			"GI Irr2Refl #2",
+			"GI Irr2Refl #3",
+			"GI Irr2Refl #4",
+			"GI Irr2Refl #5"}};
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 		{
-			static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceToReflCallback<0>,
-				runIrradianceToReflCallback<1>,
-				runIrradianceToReflCallback<2>,
-				runIrradianceToReflCallback<3>,
-				runIrradianceToReflCallback<4>,
-				runIrradianceToReflCallback<5>}};
-
-			// Rt
-			static const Array<CString, 6> passNames = {{"GI Irr2Refl #0",
-				"GI Irr2Refl #1",
-				"GI Irr2Refl #2",
-				"GI Irr2Refl #3",
-				"GI Irr2Refl #4",
-				"GI Irr2Refl #5"}};
-			for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
-			{
-				GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
+			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
 
-				pass.setFramebufferInfo(m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceToReflFbDescrs[faceIdx],
-					{{m_ctx.m_lightShadingRt}},
-					{});
+			pass.setFramebufferInfo(m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceToReflFbDescrs[faceIdx],
+				{{m_ctx.m_lightShadingRt}},
+				{});
 
-				pass.setWork(callbacks[faceIdx], this, 0);
+			pass.setWork(callbacks[faceIdx], this, 0);
 
-				for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
-				{
-					pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::SAMPLED_FRAGMENT});
-				}
+			for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+			{
+				pass.newDependency({m_ctx.m_gbufferColorRts[i], TextureUsageBit::SAMPLED_FRAGMENT});
+			}
 
-				TextureSubresourceInfo readSubresource;
-				readSubresource.m_faceCount = 6;
-				readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
-				pass.newDependency({m_ctx.m_irradianceRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
+			TextureSubresourceInfo readSubresource;
+			readSubresource.m_faceCount = 6;
+			readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
+			pass.newDependency({m_ctx.m_irradianceRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
 
-				TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
-				pass.newDependency(
-					{m_ctx.m_lightShadingRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, writeSubresource});
-			}
+			TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+			pass.newDependency(
+				{m_ctx.m_lightShadingRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ_WRITE, writeSubresource});
 		}
+	}
 
-		// Mipmapping "passes"
+	// Irradiance passes 2nd bounce
+	{
+		static const Array<RenderPassWorkCallback, 6> callbacks = {{runIrradianceCallback<0>,
+			runIrradianceCallback<1>,
+			runIrradianceCallback<2>,
+			runIrradianceCallback<3>,
+			runIrradianceCallback<4>,
+			runIrradianceCallback<5>}};
+
+		static const Array<CString, 6> passNames = {
+			{"GI Irr 2nd #0", "GI Irr 2nd #1", "GI Irr 2nd #2", "GI Irr 2nd #3", "GI Irr 2nd #4", "GI Irr 2nd #5"}};
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
 		{
-			static const Array<RenderPassWorkCallback, 6> callbacks = {{runMipmappingOfLightShadingCallback<0>,
-				runMipmappingOfLightShadingCallback<1>,
-				runMipmappingOfLightShadingCallback<2>,
-				runMipmappingOfLightShadingCallback<3>,
-				runMipmappingOfLightShadingCallback<4>,
-				runMipmappingOfLightShadingCallback<5>}};
-
-			static const Array<CString, 6> passNames = {
-				{"GI Mip #0", "GI Mip #1", "GI Mip #2", "GI Mip #3", "GI Mip #4", "GI Mip #5"}};
-			for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
-			{
-				GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
-				pass.setWork(callbacks[faceIdx], this, 0);
+			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
 
-				TextureSubresourceInfo subresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
-				subresource.m_mipmapCount = m_lightShading.m_mipCount;
+			pass.setFramebufferInfo(
+				m_cacheEntries[probeToUpdateCacheEntryIdx].m_irradianceFbDescrs[faceIdx], {{m_ctx.m_irradianceRt}}, {});
 
-				pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::GENERATE_MIPMAPS, subresource});
-			}
+			pass.setWork(callbacks[faceIdx], this, 0);
+
+			// Read a cube but only one layer and level
+			TextureSubresourceInfo readSubresource;
+			readSubresource.m_faceCount = 6;
+			readSubresource.m_firstLayer = probeToUpdateCacheEntryIdx;
+			pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::SAMPLED_FRAGMENT, readSubresource});
+
+			TextureSubresourceInfo writeSubresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+			pass.newDependency({m_ctx.m_irradianceRt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE, writeSubresource});
 		}
 	}
-	else
+
+	// Mipmapping "passes"
 	{
-		// Just import
+		static const Array<RenderPassWorkCallback, 6> callbacks = {{runMipmappingOfLightShadingCallback<0>,
+			runMipmappingOfLightShadingCallback<1>,
+			runMipmappingOfLightShadingCallback<2>,
+			runMipmappingOfLightShadingCallback<3>,
+			runMipmappingOfLightShadingCallback<4>,
+			runMipmappingOfLightShadingCallback<5>}};
+
+		static const Array<CString, 6> passNames = {
+			{"GI Mip #0", "GI Mip #1", "GI Mip #2", "GI Mip #3", "GI Mip #4", "GI Mip #5"}};
+		for(U faceIdx = 0; faceIdx < 6; ++faceIdx)
+		{
+			GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass(passNames[faceIdx]);
+			pass.setWork(callbacks[faceIdx], this, 0);
 
-		m_ctx.m_lightShadingRt = rgraph.importRenderTarget(m_lightShading.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
-		m_ctx.m_irradianceRt = rgraph.importRenderTarget(m_irradiance.m_cubeArr, TextureUsageBit::SAMPLED_FRAGMENT);
+			TextureSubresourceInfo subresource(TextureSurfaceInfo(0, 0, faceIdx, probeToUpdateCacheEntryIdx));
+			subresource.m_mipmapCount = m_lightShading.m_mipCount;
+
+			pass.newDependency({m_ctx.m_lightShadingRt, TextureUsageBit::GENERATE_MIPMAPS, subresource});
+		}
 	}
 }
 

+ 4 - 9
src/anki/renderer/LensFlare.cpp

@@ -146,11 +146,8 @@ void LensFlare::runDrawFlares(const RenderingContext& ctx, CommandBufferPtr& cmd
 	const U count = min<U>(ctx.m_renderQueue->m_lensFlares.getSize(), m_maxFlares);
 
 	cmdb->bindShaderProgram(m_realGrProg);
+	cmdb->setBlendFactors(0, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA);
 	cmdb->setDepthWrite(false);
-	cmdb->setDepthCompareOperation(CompareOperation::ALWAYS);
-	cmdb->setBlendFactors(
-		0, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA, BlendFactor::DST_ALPHA, BlendFactor::ONE);
-	cmdb->setBlendOperation(0, BlendOperation::ADD, BlendOperation::REVERSE_SUBTRACT);
 
 	for(U i = 0; i < count; ++i)
 	{
@@ -181,8 +178,8 @@ void LensFlare::runDrawFlares(const RenderingContext& ctx, CommandBufferPtr& cmd
 		// First flare
 		sprites[c].m_posScale = Vec4(posNdc, flareEl.m_firstFlareSize * Vec2(1.0f, m_r->getAspectRatio()));
 		sprites[c].m_depthPad3 = Vec4(0.0f);
-		F32 alpha = flareEl.m_colorMultiplier.w() * (1.0 - pow(absolute(posNdc.x()), 6.0f))
-					* (1.0 - pow(absolute(posNdc.y()), 6.0)); // Fade the flare on the edges
+		const F32 alpha = flareEl.m_colorMultiplier.w() * (1.0 - pow(absolute(posNdc.x()), 6.0f))
+						  * (1.0 - pow(absolute(posNdc.y()), 6.0)); // Fade the flare on the edges
 		sprites[c].m_color = Vec4(flareEl.m_colorMultiplier.xyz(), alpha);
 		++c;
 
@@ -199,10 +196,8 @@ void LensFlare::runDrawFlares(const RenderingContext& ctx, CommandBufferPtr& cmd
 	}
 
 	// Restore state
-	cmdb->setDepthWrite(true);
-	cmdb->setDepthCompareOperation(CompareOperation::LESS);
 	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
-	cmdb->setBlendOperation(0, BlendOperation::ADD);
+	cmdb->setDepthWrite(true);
 }
 
 } // end namespace anki

+ 71 - 51
src/anki/renderer/LightShading.cpp

@@ -10,6 +10,7 @@
 #include <anki/renderer/GBuffer.h>
 #include <anki/renderer/RenderQueue.h>
 #include <anki/renderer/ForwardShading.h>
+#include <anki/renderer/VolumetricFog.h>
 #include <anki/renderer/DepthDownscale.h>
 #include <anki/renderer/Ssao.h>
 #include <anki/renderer/Ssr.h>
@@ -31,7 +32,12 @@ LightShading::~LightShading()
 Error LightShading::init(const ConfigSet& config)
 {
 	ANKI_R_LOGI("Initializing light stage");
-	Error err = initInternal(config);
+	Error err = initLightShading(config);
+
+	if(!err)
+	{
+		err = initApplyFog(config);
+	}
 
 	if(err)
 	{
@@ -41,60 +47,65 @@ Error LightShading::init(const ConfigSet& config)
 	return err;
 }
 
-Error LightShading::initInternal(const ConfigSet& config)
+Error LightShading::initLightShading(const ConfigSet& config)
 {
 	// Load shaders and programs
-	ANKI_CHECK(getResourceManager().loadResource("shaders/LightShading.glslp", m_prog));
+	ANKI_CHECK(getResourceManager().loadResource("shaders/LightShading.glslp", m_lightShading.m_prog));
 
-	ShaderProgramResourceConstantValueInitList<5> consts(m_prog);
+	ShaderProgramResourceConstantValueInitList<5> consts(m_lightShading.m_prog);
 	consts.add("CLUSTER_COUNT_X", U32(m_r->getClusterCount()[0]))
 		.add("CLUSTER_COUNT_Y", U32(m_r->getClusterCount()[1]))
 		.add("CLUSTER_COUNT_Z", U32(m_r->getClusterCount()[2]))
 		.add("CLUSTER_COUNT", U32(m_r->getClusterCount()[3]))
 		.add("IR_MIPMAP_COUNT", U32(m_r->getIndirect().getReflectionTextureMipmapCount()));
 
-	m_prog->getOrCreateVariant(consts.get(), m_progVariant);
+	const ShaderProgramResourceVariant* variant;
+	m_lightShading.m_prog->getOrCreateVariant(consts.get(), variant);
+	m_lightShading.m_grProg = variant->getProgram();
 
 	// Create RT descr
-	m_rtDescr = m_r->create2DRenderTargetDescription(
+	m_lightShading.m_rtDescr = m_r->create2DRenderTargetDescription(
 		m_r->getWidth(), m_r->getHeight(), LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT, "Light Shading");
-	m_rtDescr.bake();
+	m_lightShading.m_rtDescr.bake();
 
 	// Create FB descr
-	m_fbDescr.m_colorAttachmentCount = 1;
-	m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-	m_fbDescr.bake();
+	m_lightShading.m_fbDescr.m_colorAttachmentCount = 1;
+	m_lightShading.m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
+	m_lightShading.m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::LOAD;
+	m_lightShading.m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
+	m_lightShading.m_fbDescr.bake();
 
-	// FS upscale
-	{
-		ANKI_CHECK(getResourceManager().loadResource("engine_data/BlueNoiseLdrRgb64x64.ankitex", m_fs.m_noiseTex));
+	return Error::NONE;
+}
 
-		// Shader
-		ANKI_CHECK(getResourceManager().loadResource("shaders/ForwardShadingUpscale.glslp", m_fs.m_prog));
+Error LightShading::initApplyFog(const ConfigSet& config)
+{
+	// Load shaders and programs
+	ANKI_CHECK(getResourceManager().loadResource("shaders/LightShadingApplyFog.glslp", m_applyFog.m_prog));
 
-		ShaderProgramResourceConstantValueInitList<3> consts(m_fs.m_prog);
-		consts.add("NOISE_TEX_SIZE", U32(m_fs.m_noiseTex->getWidth()))
-			.add("SRC_SIZE", Vec2(m_r->getWidth() / FS_FRACTION, m_r->getHeight() / FS_FRACTION))
-			.add("FB_SIZE", Vec2(m_r->getWidth(), m_r->getWidth()));
+	ShaderProgramResourceConstantValueInitList<1> consts(m_applyFog.m_prog);
+	consts.add("FOG_LAST_CLASTER", U32(m_r->getVolumetricFog().getFinalClusterInZ()));
 
-		const ShaderProgramResourceVariant* variant;
-		m_fs.m_prog->getOrCreateVariant(consts.get(), variant);
-		m_fs.m_grProg = variant->getProgram();
-	}
+	const ShaderProgramResourceVariant* variant;
+	m_applyFog.m_prog->getOrCreateVariant(consts.get(), variant);
+	m_applyFog.m_grProg = variant->getProgram();
 
 	return Error::NONE;
 }
 
-void LightShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx)
+void LightShading::run(RenderPassWorkContext& rgraphCtx)
 {
+	const RenderingContext& ctx = *m_runCtx.m_ctx;
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 	const ClusterBinOut& rsrc = ctx.m_clusterBinOut;
 
 	cmdb->setViewport(0, 0, m_r->getWidth(), m_r->getHeight());
 
-	// Do light shading
+	// Do light shading first
+	if(rgraphCtx.m_currentSecondLevelCommandBufferIndex == 0)
 	{
-		cmdb->bindShaderProgram(m_progVariant->getProgram());
+		cmdb->bindShaderProgram(m_lightShading.m_grProg);
+		cmdb->setDepthWrite(false);
 
 		// Bind textures
 		rgraphCtx.bindColorTextureAndSampler(0, 0, m_r->getGBuffer().getColorRt(0), m_r->getNearestSampler());
@@ -132,37 +143,41 @@ void LightShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgrap
 		drawQuad(cmdb);
 	}
 
-	// Forward shading
+	// Do the fog apply
+	if(rgraphCtx.m_currentSecondLevelCommandBufferIndex == rgraphCtx.m_secondLevelCommandBufferCount - 1u)
 	{
+		cmdb->bindShaderProgram(m_applyFog.m_grProg);
+
 		// Bind textures
 		rgraphCtx.bindTextureAndSampler(0,
 			0,
 			m_r->getGBuffer().getDepthRt(),
 			TextureSubresourceInfo(DepthStencilAspectBit::DEPTH),
 			m_r->getNearestSampler());
-		rgraphCtx.bindTextureAndSampler(
-			0, 1, m_r->getDepthDownscale().getHiZRt(), HIZ_HALF_DEPTH, m_r->getNearestSampler());
-		rgraphCtx.bindColorTextureAndSampler(0, 2, m_r->getForwardShading().getRt(), m_r->getLinearSampler());
-		cmdb->bindTextureAndSampler(0,
-			3,
-			m_fs.m_noiseTex->getGrTextureView(),
-			m_r->getTrilinearRepeatSampler(),
-			TextureUsageBit::SAMPLED_FRAGMENT);
+		rgraphCtx.bindColorTextureAndSampler(0, 1, m_r->getVolumetricFog().getRt(), m_r->getLinearSampler());
 
-		// Bind uniforms
-		Vec4* linearDepth = allocateAndBindUniforms<Vec4*>(sizeof(Vec4), cmdb, 0, 0);
-		computeLinearizeDepthOptimal(
-			ctx.m_renderQueue->m_cameraNear, ctx.m_renderQueue->m_cameraFar, linearDepth->x(), linearDepth->y());
-		linearDepth->z() = ctx.m_renderQueue->m_cameraFar;
+		// Uniforms
+		struct PushConsts
+		{
+			ClustererMagicValues m_clustererMagic;
+			Mat4 m_invViewProjMat;
+		} regs;
+		regs.m_clustererMagic = ctx.m_clusterBinOut.m_shaderMagicValues;
+		regs.m_invViewProjMat = ctx.m_matrices.m_viewProjectionJitter.getInverse();
 
-		// Other state & draw
+		cmdb->setPushConstants(&regs, sizeof(regs));
+
+		// finalPixelColor = pixelWithoutFog * transmitance + inScattering (see the shader)
 		cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::SRC_ALPHA);
-		cmdb->bindShaderProgram(m_fs.m_grProg);
+
 		drawQuad(cmdb);
+
+		// Reset state
+		cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
 	}
 
-	// Restore state
-	cmdb->setBlendFactors(0, BlendFactor::ONE, BlendFactor::ZERO);
+	// Forward shading last
+	m_r->getForwardShading().run(ctx, rgraphCtx);
 }
 
 void LightShading::populateRenderGraph(RenderingContext& ctx)
@@ -171,13 +186,16 @@ void LightShading::populateRenderGraph(RenderingContext& ctx)
 	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
 
 	// Create RT
-	m_runCtx.m_rt = rgraph.newRenderTarget(m_rtDescr);
+	m_runCtx.m_rt = rgraph.newRenderTarget(m_lightShading.m_rtDescr);
 
 	// Create pass
-	GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("Light Shading");
+	GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("Light&FW Shad.");
 
-	pass.setWork(runCallback, this, 0);
-	pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rt}}, {});
+	pass.setWork(
+		[](RenderPassWorkContext& rgraphCtx) { static_cast<LightShading*>(rgraphCtx.m_userData)->run(rgraphCtx); },
+		this,
+		computeNumberOfSecondLevelCommandBuffers(ctx.m_renderQueue->m_forwardShadingRenderables.getSize()));
+	pass.setFramebufferInfo(m_lightShading.m_fbDescr, {{m_runCtx.m_rt}}, {m_r->getGBuffer().getDepthRt()});
 
 	// Light shading
 	pass.newDependency({m_runCtx.m_rt, TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
@@ -185,7 +203,7 @@ void LightShading::populateRenderGraph(RenderingContext& ctx)
 	pass.newDependency({m_r->getGBuffer().getColorRt(1), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getGBuffer().getColorRt(2), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getGBuffer().getDepthRt(),
-		TextureUsageBit::SAMPLED_FRAGMENT,
+		TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_READ,
 		TextureSubresourceInfo(DepthStencilAspectBit::DEPTH)});
 	pass.newDependency({m_r->getShadowMapping().getShadowmapRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getSsao().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
@@ -195,9 +213,11 @@ void LightShading::populateRenderGraph(RenderingContext& ctx)
 	pass.newDependency({m_r->getIndirect().getReflectionRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newDependency({m_r->getIndirect().getIrradianceRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 
+	// Fog
+	pass.newDependency({m_r->getVolumetricFog().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
+
 	// For forward shading
-	pass.newDependency({m_r->getDepthDownscale().getHiZRt(), TextureUsageBit::SAMPLED_FRAGMENT, HIZ_HALF_DEPTH});
-	pass.newDependency({m_r->getForwardShading().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
+	m_r->getForwardShading().setDependencies(ctx, pass);
 }
 
 } // end namespace anki

+ 13 - 17
src/anki/renderer/LightShading.h

@@ -33,20 +33,23 @@ anki_internal:
 	}
 
 private:
-	RenderTargetDescription m_rtDescr;
-	FramebufferDescription m_fbDescr;
+	class
+	{
+	public:
+		RenderTargetDescription m_rtDescr;
+		FramebufferDescription m_fbDescr;
 
-	// Light shaders
-	ShaderProgramResourcePtr m_prog;
-	const ShaderProgramResourceVariant* m_progVariant = nullptr;
+		// Light shaders
+		ShaderProgramResourcePtr m_prog;
+		ShaderProgramPtr m_grProg;
+	} m_lightShading;
 
 	class
 	{
 	public:
 		ShaderProgramResourcePtr m_prog;
 		ShaderProgramPtr m_grProg;
-		TextureResourcePtr m_noiseTex;
-	} m_fs; ///< Apply forward shading.
+	} m_applyFog;
 
 	class
 	{
@@ -55,17 +58,10 @@ private:
 		RenderingContext* m_ctx;
 	} m_runCtx; ///< Run context.
 
-	/// Called by init
-	ANKI_USE_RESULT Error initInternal(const ConfigSet& initializer);
-
-	void run(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx);
+	ANKI_USE_RESULT Error initLightShading(const ConfigSet& config);
+	ANKI_USE_RESULT Error initApplyFog(const ConfigSet& config);
 
-	/// A RenderPassWorkCallback for the light pass.
-	static void runCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		LightShading* const self = scast<LightShading*>(rgraphCtx.m_userData);
-		self->run(*self->m_runCtx.m_ctx, rgraphCtx);
-	}
+	void run(RenderPassWorkContext& rgraphCtx);
 };
 /// @}
 

+ 27 - 12
src/anki/renderer/Renderer.cpp

@@ -22,11 +22,12 @@
 #include <anki/renderer/LensFlare.h>
 #include <anki/renderer/Dbg.h>
 #include <anki/renderer/DownscaleBlur.h>
-#include <anki/renderer/Volumetric.h>
+#include <anki/renderer/VolumetricFog.h>
 #include <anki/renderer/DepthDownscale.h>
 #include <anki/renderer/TemporalAA.h>
 #include <anki/renderer/UiStage.h>
 #include <anki/renderer/Ssr.h>
+#include <anki/renderer/VolumetricLightingAccumulation.h>
 #include <shaders/glsl_cpp_common/ClusteredShading.h>
 
 namespace anki
@@ -98,7 +99,7 @@ Error Renderer::initInternal(const ConfigSet& config)
 	{
 		TextureInitInfo texinit;
 		texinit.m_width = texinit.m_height = 4;
-		texinit.m_usage = TextureUsageBit::SAMPLED_FRAGMENT;
+		texinit.m_usage = TextureUsageBit::SAMPLED_ALL;
 		texinit.m_format = Format::R8G8B8A8_UNORM;
 		texinit.m_initialUsage = TextureUsageBit::SAMPLED_FRAGMENT;
 		TexturePtr tex = getGrManager().newTexture(texinit);
@@ -113,6 +114,9 @@ Error Renderer::initInternal(const ConfigSet& config)
 	ANKI_CHECK(m_resources->loadResource("shaders/ClearTextureCompute.glslp", m_clearTexComputeProg));
 
 	// Init the stages. Careful with the order!!!!!!!!!!
+	m_volLighting.reset(m_alloc.newInstance<VolumetricLightingAccumulation>(this));
+	ANKI_CHECK(m_volLighting->init(config));
+
 	m_indirect.reset(m_alloc.newInstance<Indirect>(this));
 	ANKI_CHECK(m_indirect->init(config));
 
@@ -125,15 +129,15 @@ Error Renderer::initInternal(const ConfigSet& config)
 	m_shadowMapping.reset(m_alloc.newInstance<ShadowMapping>(this));
 	ANKI_CHECK(m_shadowMapping->init(config));
 
+	m_volFog.reset(m_alloc.newInstance<VolumetricFog>(this));
+	ANKI_CHECK(m_volFog->init(config));
+
 	m_lightShading.reset(m_alloc.newInstance<LightShading>(this));
 	ANKI_CHECK(m_lightShading->init(config));
 
 	m_depth.reset(m_alloc.newInstance<DepthDownscale>(this));
 	ANKI_CHECK(m_depth->init(config));
 
-	m_vol.reset(m_alloc.newInstance<Volumetric>(this));
-	ANKI_CHECK(m_vol->init(config));
-
 	m_forwardShading.reset(m_alloc.newInstance<ForwardShading>(this));
 	ANKI_CHECK(m_forwardShading->init(config));
 
@@ -168,7 +172,7 @@ Error Renderer::initInternal(const ConfigSet& config)
 	ANKI_CHECK(m_uiStage->init(config));
 
 	SamplerInitInfo sinit("Renderer");
-	sinit.m_repeat = false;
+	sinit.m_addressing = SamplingAddressing::CLAMP;
 	sinit.m_mipmapFilter = SamplingFilter::BASE;
 	sinit.m_minMagFilter = SamplingFilter::NEAREST;
 	m_nearestSampler = m_gr->newSampler(sinit);
@@ -177,7 +181,7 @@ Error Renderer::initInternal(const ConfigSet& config)
 	m_linearSampler = m_gr->newSampler(sinit);
 
 	sinit.m_mipmapFilter = SamplingFilter::LINEAR;
-	sinit.m_repeat = true;
+	sinit.m_addressing = SamplingAddressing::REPEAT;
 	m_trilinearRepeatSampler = m_gr->newSampler(sinit);
 
 	sinit.m_mipmapFilter = SamplingFilter::NEAREST;
@@ -280,14 +284,14 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 
 	// Populate render graph. WARNING Watch the order
 	m_shadowMapping->populateRenderGraph(ctx);
+	m_volLighting->populateRenderGraph(ctx);
 	m_indirect->populateRenderGraph(ctx);
 	m_gbuffer->populateRenderGraph(ctx);
 	m_gbufferPost->populateRenderGraph(ctx);
 	m_depth->populateRenderGraph(ctx);
-	m_vol->populateRenderGraph(ctx);
+	m_volFog->populateRenderGraph(ctx);
 	m_ssao->populateRenderGraph(ctx);
 	m_lensFlare->populateRenderGraph(ctx);
-	m_forwardShading->populateRenderGraph(ctx);
 	m_ssr->populateRenderGraph(ctx);
 	m_lightShading->populateRenderGraph(ctx);
 	m_temporalAA->populateRenderGraph(ctx);
@@ -312,6 +316,10 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 	cin.m_threadHive = m_threadHive;
 	m_clusterBin.bin(cin, ctx.m_clusterBinOut);
 
+	ctx.m_prevClustererMagicValues =
+		(m_frameCount > 0) ? m_prevClustererMagicValues : ctx.m_clusterBinOut.m_shaderMagicValues;
+	m_prevClustererMagicValues = ctx.m_clusterBinOut.m_shaderMagicValues;
+
 	updateLightShadingUniforms(ctx);
 	m_stats.m_lightBinTime = HighRezTimer::getCurrentTime() - m_stats.m_lightBinTime;
 
@@ -487,8 +495,11 @@ TexturePtr Renderer::createAndClearRenderTarget(const TextureInitInfo& inf, cons
 				else
 				{
 					// Compute
+					ShaderProgramResourceMutationInitList<1> mutators(m_clearTexComputeProg);
+					mutators.add("IS_2D", U32((inf.m_type != TextureType::_3D) ? 1 : 0));
+
 					const ShaderProgramResourceVariant* variant;
-					m_clearTexComputeProg->getOrCreateVariant(variant);
+					m_clearTexComputeProg->getOrCreateVariant(mutators.get(), variant);
 
 					cmdb->bindShaderProgram(variant->getProgram());
 
@@ -500,7 +511,8 @@ TexturePtr Renderer::createAndClearRenderTarget(const TextureInitInfo& inf, cons
 					cmdb->setTextureSurfaceBarrier(
 						tex, TextureUsageBit::NONE, TextureUsageBit::IMAGE_COMPUTE_WRITE, surf);
 
-					cmdb->dispatchCompute(tex->getWidth() >> mip, tex->getHeight() >> mip, 1);
+					const U wgSizeZ = (inf.m_type == TextureType::_3D) ? (tex->getDepth() >> mip) : 1;
+					cmdb->dispatchCompute(tex->getWidth() >> mip, tex->getHeight() >> mip, wgSizeZ);
 
 					if(!!inf.m_initialUsage)
 					{
@@ -528,12 +540,15 @@ void Renderer::updateLightShadingUniforms(RenderingContext& ctx) const
 	blk->m_rendererSizeTimeNear =
 		Vec4(m_width, m_height, HighRezTimer::getCurrentTime(), ctx.m_renderQueue->m_cameraNear);
 
-	blk->m_tileCount = UVec4(m_clusterCount[0], m_clusterCount[1], m_clusterCount[2], m_clusterCount[3]);
+	blk->m_clusterCount = UVec4(m_clusterCount[0], m_clusterCount[1], m_clusterCount[2], m_clusterCount[3]);
 
 	blk->m_cameraPosFar =
 		Vec4(ctx.m_renderQueue->m_cameraTransform.getTranslationPart().xyz(), ctx.m_renderQueue->m_cameraFar);
 
 	blk->m_clustererMagicValues = ctx.m_clusterBinOut.m_shaderMagicValues;
+	blk->m_prevClustererMagicValues = ctx.m_prevClustererMagicValues;
+
+	blk->m_lightVolumeLastClusterPad3 = UVec4(m_volLighting->getFinalClusterInZ());
 
 	// Matrices
 	blk->m_viewMat = ctx.m_renderQueue->m_viewMatrix;

+ 12 - 5
src/anki/renderer/Renderer.h

@@ -60,6 +60,7 @@ public:
 	Vec4 m_unprojParams;
 
 	ClusterBinOut m_clusterBinOut;
+	ClustererMagicValues m_prevClustererMagicValues;
 
 	StagingGpuMemoryToken m_lightShadingUniformsToken;
 
@@ -78,8 +79,7 @@ public:
 	Second m_lightBinTime ANKI_DBG_NULLIFY;
 };
 
-/// Offscreen renderer. It is a class and not a namespace because we may need external renderers for security cameras
-/// for example
+/// Offscreen renderer.
 class Renderer
 {
 public:
@@ -92,6 +92,11 @@ public:
 		return *m_indirect;
 	}
 
+	VolumetricLightingAccumulation& getVolumetricLightingAccumulation()
+	{
+		return *m_volLighting;
+	}
+
 	ShadowMapping& getShadowMapping()
 	{
 		return *m_shadowMapping;
@@ -117,9 +122,9 @@ public:
 		return *m_forwardShading;
 	}
 
-	Volumetric& getVolumetric()
+	VolumetricFog& getVolumetricFog()
 	{
-		return *m_vol;
+		return *m_volFog;
 	}
 
 	Tonemapping& getTonemapping()
@@ -367,6 +372,7 @@ private:
 
 	/// @name Rendering stages
 	/// @{
+	UniquePtr<VolumetricLightingAccumulation> m_volLighting;
 	UniquePtr<Indirect> m_indirect;
 	UniquePtr<ShadowMapping> m_shadowMapping; ///< Shadow mapping.
 	UniquePtr<GBuffer> m_gbuffer; ///< Material rendering stage
@@ -375,7 +381,7 @@ private:
 	UniquePtr<LightShading> m_lightShading; ///< Illumination rendering stage
 	UniquePtr<DepthDownscale> m_depth;
 	UniquePtr<ForwardShading> m_forwardShading; ///< Forward shading.
-	UniquePtr<Volumetric> m_vol; ///< Volumetric effects.
+	UniquePtr<VolumetricFog> m_volFog; ///< Volumetric fog.
 	UniquePtr<LensFlare> m_lensFlare; ///< Forward shading lens flares.
 	UniquePtr<DownscaleBlur> m_downscale;
 	UniquePtr<TemporalAA> m_temporalAA;
@@ -404,6 +410,7 @@ private:
 	Bool m_resourcesDirty = true;
 
 	RenderingContextMatrices m_prevMatrices;
+	ClustererMagicValues m_prevClustererMagicValues;
 
 	Array<Mat4, 16> m_jitteredMats16x;
 	Array<Mat4, 8> m_jitteredMats8x;

+ 14 - 0
src/anki/renderer/RendererObject.h

@@ -65,6 +65,20 @@ protected:
 		cmdb->dispatchCompute(sizeX, sizeY, 1);
 	}
 
+	static void dispatchPPCompute(CommandBufferPtr& cmdb,
+		U32 workgroupSizeX,
+		U32 workgroupSizeY,
+		U32 workgroupSizeZ,
+		U32 outImageWidth,
+		U32 outImageHeight,
+		U32 outImageDepth)
+	{
+		const U sizeX = (outImageWidth + workgroupSizeX - 1) / workgroupSizeX;
+		const U sizeY = (outImageHeight + workgroupSizeY - 1) / workgroupSizeY;
+		const U sizeZ = (outImageDepth + workgroupSizeZ - 1) / workgroupSizeZ;
+		cmdb->dispatchCompute(sizeX, sizeY, sizeZ);
+	}
+
 	template<typename TPtr>
 	TPtr allocateUniforms(PtrSize size, StagingGpuMemoryToken& token)
 	{

+ 2 - 1
src/anki/renderer/ShadowMapping.cpp

@@ -76,7 +76,8 @@ Error ShadowMapping::initEsm(const ConfigSet& cfg)
 		TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(m_atlasResolution,
 			m_atlasResolution,
 			SHADOW_COLOR_PIXEL_FORMAT,
-			TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
+			TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE
+				| TextureUsageBit::SAMPLED_COMPUTE,
 			"esm");
 		texinit.m_initialUsage = TextureUsageBit::SAMPLED_FRAGMENT;
 		ClearValue clearVal;

+ 0 - 238
src/anki/renderer/Volumetric.cpp

@@ -1,238 +0,0 @@
-// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#include <anki/renderer/Volumetric.h>
-#include <anki/renderer/Renderer.h>
-#include <anki/renderer/DepthDownscale.h>
-#include <anki/renderer/ShadowMapping.h>
-#include <anki/renderer/LightShading.h>
-#include <anki/renderer/RenderQueue.h>
-
-namespace anki
-{
-
-Error Volumetric::initMain(const ConfigSet& config)
-{
-	// Misc
-	ANKI_CHECK(getResourceManager().loadResource("engine_data/BlueNoiseLdrRgb64x64.ankitex", m_main.m_noiseTex));
-
-	// Shaders
-	ANKI_CHECK(getResourceManager().loadResource("shaders/VolumetricFog.glslp", m_main.m_prog));
-
-	ShaderProgramResourceMutationInitList<1> mutators(m_main.m_prog);
-	mutators.add("ENABLE_SHADOWS", 1);
-
-	ShaderProgramResourceConstantValueInitList<3> consts(m_main.m_prog);
-	consts.add("FB_SIZE", UVec2(m_width, m_height))
-		.add("CLUSTER_COUNT", UVec3(m_r->getClusterCount()[0], m_r->getClusterCount()[1], m_r->getClusterCount()[2]))
-		.add("NOISE_MAP_SIZE", U32(m_main.m_noiseTex->getWidth()));
-
-	const ShaderProgramResourceVariant* variant;
-	m_main.m_prog->getOrCreateVariant(mutators.get(), consts.get(), variant);
-	m_main.m_grProg = variant->getProgram();
-
-	return Error::NONE;
-}
-
-Error Volumetric::initHBlur(const ConfigSet& config)
-{
-	// Progs
-	ANKI_CHECK(m_r->getResourceManager().loadResource("shaders/LumaAwareBlur.glslp", m_hblur.m_prog));
-
-	ShaderProgramResourceMutationInitList<3> mutators(m_hblur.m_prog);
-	mutators.add("HORIZONTAL", 1).add("KERNEL_SIZE", 11).add("COLOR_COMPONENTS", 3);
-	ShaderProgramResourceConstantValueInitList<1> consts(m_hblur.m_prog);
-	consts.add("TEXTURE_SIZE", UVec2(m_width, m_height));
-
-	const ShaderProgramResourceVariant* variant;
-	m_hblur.m_prog->getOrCreateVariant(mutators.get(), consts.get(), variant);
-	m_hblur.m_grProg = variant->getProgram();
-
-	return Error::NONE;
-}
-
-Error Volumetric::initVBlur(const ConfigSet& config)
-{
-	// Progs
-	ANKI_CHECK(m_r->getResourceManager().loadResource("shaders/LumaAwareBlur.glslp", m_vblur.m_prog));
-
-	ShaderProgramResourceMutationInitList<3> mutators(m_vblur.m_prog);
-	mutators.add("HORIZONTAL", 0).add("KERNEL_SIZE", 11).add("COLOR_COMPONENTS", 3);
-	ShaderProgramResourceConstantValueInitList<1> consts(m_vblur.m_prog);
-	consts.add("TEXTURE_SIZE", UVec2(m_width, m_height));
-
-	const ShaderProgramResourceVariant* variant;
-	m_vblur.m_prog->getOrCreateVariant(mutators.get(), consts.get(), variant);
-	m_vblur.m_grProg = variant->getProgram();
-
-	return Error::NONE;
-}
-
-Error Volumetric::init(const ConfigSet& config)
-{
-	m_width = m_r->getWidth() / VOLUMETRIC_FRACTION;
-	m_height = m_r->getHeight() / VOLUMETRIC_FRACTION;
-
-	ANKI_R_LOGI("Initializing volumetric pass. Size %ux%u", m_width, m_height);
-
-	for(U i = 0; i < 2; ++i)
-	{
-		// RT
-		TextureInitInfo rtInit = m_r->create2DRenderTargetInitInfo(m_width,
-			m_height,
-			LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT,
-			TextureUsageBit::SAMPLED_FRAGMENT | TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE,
-			"Volumetric");
-		rtInit.m_initialUsage = TextureUsageBit::SAMPLED_FRAGMENT;
-		m_rtTextures[i] = m_r->createAndClearRenderTarget(rtInit);
-	}
-
-	// FB
-	m_fbDescr.m_colorAttachmentCount = 1;
-	m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
-	m_fbDescr.bake();
-
-	Error err = initMain(config);
-
-	if(!err)
-	{
-		err = initHBlur(config);
-	}
-
-	if(!err)
-	{
-		err = initVBlur(config);
-	}
-
-	if(err)
-	{
-		ANKI_R_LOGE("Failed to initialize volumetric pass");
-	}
-
-	return err;
-}
-
-void Volumetric::runMain(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx)
-{
-	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-
-	cmdb->setViewport(0, 0, m_width, m_height);
-
-	rgraphCtx.bindTextureAndSampler(
-		0, 0, m_r->getDepthDownscale().getHiZRt(), HIZ_QUARTER_DEPTH, m_r->getLinearSampler());
-
-	cmdb->bindTextureAndSampler(0,
-		1,
-		m_main.m_noiseTex->getGrTextureView(),
-		m_r->getTrilinearRepeatSampler(),
-		TextureUsageBit::SAMPLED_FRAGMENT);
-	rgraphCtx.bindColorTextureAndSampler(0, 2, m_runCtx.m_rts[(m_r->getFrameCount() + 1) & 1], m_r->getLinearSampler());
-	rgraphCtx.bindColorTextureAndSampler(0, 3, m_r->getShadowMapping().getShadowmapRt(), m_r->getLinearSampler());
-
-	const ClusterBinOut& rsrc = ctx.m_clusterBinOut;
-	bindUniforms(cmdb, 0, 0, ctx.m_lightShadingUniformsToken);
-	bindUniforms(cmdb, 0, 1, rsrc.m_pointLightsToken);
-	bindUniforms(cmdb, 0, 2, rsrc.m_spotLightsToken);
-
-	struct Unis
-	{
-		Vec4 m_linearizeNoiseTexOffsetLayer;
-		Vec4 m_fogParticleColorPad1;
-	};
-
-	Unis* uniforms = allocateAndBindUniforms<Unis*>(sizeof(Unis), cmdb, 0, 3);
-	computeLinearizeDepthOptimal(ctx.m_renderQueue->m_cameraNear,
-		ctx.m_renderQueue->m_cameraFar,
-		uniforms->m_linearizeNoiseTexOffsetLayer.x(),
-		uniforms->m_linearizeNoiseTexOffsetLayer.y());
-	F32 texelOffset = 1.0 / m_main.m_noiseTex->getWidth();
-	uniforms->m_linearizeNoiseTexOffsetLayer.z() = m_r->getFrameCount() * texelOffset;
-	uniforms->m_linearizeNoiseTexOffsetLayer.w() = m_r->getFrameCount() & (m_main.m_noiseTex->getLayerCount() - 1);
-	uniforms->m_fogParticleColorPad1 = Vec4(m_main.m_fogParticleColor, 0.0);
-
-	bindStorage(cmdb, 0, 0, rsrc.m_clustersToken);
-	bindStorage(cmdb, 0, 1, rsrc.m_indicesToken);
-
-	cmdb->bindShaderProgram(m_main.m_grProg);
-
-	drawQuad(cmdb);
-}
-
-void Volumetric::runHBlur(RenderPassWorkContext& rgraphCtx)
-{
-	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-
-	rgraphCtx.bindColorTextureAndSampler(0, 0, m_runCtx.m_rts[m_r->getFrameCount() & 1], m_r->getLinearSampler());
-	cmdb->bindShaderProgram(m_hblur.m_grProg);
-	cmdb->setViewport(0, 0, m_width, m_height);
-
-	drawQuad(cmdb);
-}
-
-void Volumetric::runVBlur(RenderPassWorkContext& rgraphCtx)
-{
-	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
-
-	rgraphCtx.bindColorTextureAndSampler(0, 0, m_runCtx.m_rts[(m_r->getFrameCount() + 1) & 1], m_r->getLinearSampler());
-	cmdb->bindShaderProgram(m_vblur.m_grProg);
-	cmdb->setViewport(0, 0, m_width, m_height);
-
-	drawQuad(cmdb);
-}
-
-void Volumetric::populateRenderGraph(RenderingContext& ctx)
-{
-	m_runCtx.m_ctx = &ctx;
-	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
-
-	// Create RTs
-	const U rtToRenderIdx = m_r->getFrameCount() & 1;
-	m_runCtx.m_rts[rtToRenderIdx] = rgraph.importRenderTarget(m_rtTextures[rtToRenderIdx], TextureUsageBit::NONE);
-	const U rtToReadIdx = !rtToRenderIdx;
-	m_runCtx.m_rts[rtToReadIdx] =
-		rgraph.importRenderTarget(m_rtTextures[rtToReadIdx], TextureUsageBit::SAMPLED_FRAGMENT);
-
-	// Create main render pass
-	{
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("VOL main");
-
-		pass.setWork(runMainCallback, this, 0);
-		pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rts[rtToRenderIdx]}}, {});
-
-		pass.newDependency({m_r->getDepthDownscale().getHiZRt(), TextureUsageBit::SAMPLED_FRAGMENT, HIZ_QUARTER_DEPTH});
-		pass.newDependency({m_r->getShadowMapping().getShadowmapRt(), TextureUsageBit::SAMPLED_FRAGMENT});
-		pass.newDependency({m_runCtx.m_rts[rtToRenderIdx], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
-		pass.newDependency({m_runCtx.m_rts[rtToReadIdx], TextureUsageBit::SAMPLED_FRAGMENT});
-	}
-
-	// Create HBlur pass
-	{
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("VOL hblur");
-
-		pass.setWork(runHBlurCallback, this, 0);
-		pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rts[rtToReadIdx]}}, {});
-
-		pass.newDependency({m_runCtx.m_rts[rtToReadIdx], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
-		pass.newDependency({m_runCtx.m_rts[rtToRenderIdx], TextureUsageBit::SAMPLED_FRAGMENT});
-	}
-
-	// Create VBlur pass
-	{
-		GraphicsRenderPassDescription& pass = rgraph.newGraphicsRenderPass("VOL vblur");
-
-		pass.setWork(runVBlurCallback, this, 0);
-		pass.setFramebufferInfo(m_fbDescr, {{m_runCtx.m_rts[rtToRenderIdx]}}, {});
-
-		pass.newDependency({m_runCtx.m_rts[rtToRenderIdx], TextureUsageBit::FRAMEBUFFER_ATTACHMENT_WRITE});
-		pass.newDependency({m_runCtx.m_rts[rtToReadIdx], TextureUsageBit::SAMPLED_FRAGMENT});
-	}
-}
-
-RenderTargetHandle Volumetric::getRt() const
-{
-	return m_runCtx.m_rts[m_r->getFrameCount() & 1];
-}
-
-} // end namespace anki

+ 0 - 113
src/anki/renderer/Volumetric.h

@@ -1,113 +0,0 @@
-// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma once
-
-#include <anki/renderer/RendererObject.h>
-
-namespace anki
-{
-
-/// @addtogroup renderer
-/// @{
-
-/// Volumetric effects.
-class Volumetric : public RendererObject
-{
-public:
-	void setFogParticleColor(const Vec3& col)
-	{
-		m_main.m_fogParticleColor = col;
-	}
-
-anki_internal:
-
-	Volumetric(Renderer* r)
-		: RendererObject(r)
-	{
-	}
-
-	~Volumetric()
-	{
-	}
-
-	ANKI_USE_RESULT Error init(const ConfigSet& config);
-
-	/// Populate the rendergraph.
-	void populateRenderGraph(RenderingContext& ctx);
-
-	RenderTargetHandle getRt() const;
-
-private:
-	U32 m_width = 0, m_height = 0;
-
-	class
-	{
-	public:
-		Vec3 m_fogParticleColor = Vec3(1.0);
-		Mat3x4 m_prevCameraRot = Mat3x4::getIdentity();
-
-		ShaderProgramResourcePtr m_prog;
-		ShaderProgramPtr m_grProg;
-
-		TextureResourcePtr m_noiseTex;
-	} m_main; ///< Main noisy pass.
-
-	class
-	{
-	public:
-		ShaderProgramResourcePtr m_prog;
-		ShaderProgramPtr m_grProg;
-	} m_hblur; ///< Horizontal blur.
-
-	class
-	{
-	public:
-		ShaderProgramResourcePtr m_prog;
-		ShaderProgramPtr m_grProg;
-	} m_vblur; ///< Vertical blur.
-
-	class
-	{
-	public:
-		Array<RenderTargetHandle, 2> m_rts;
-		const RenderingContext* m_ctx = nullptr;
-	} m_runCtx; ///< Runtime context.
-
-	Array<TexturePtr, 2> m_rtTextures;
-	FramebufferDescription m_fbDescr;
-
-	ANKI_USE_RESULT Error initMain(const ConfigSet& set);
-	ANKI_USE_RESULT Error initVBlur(const ConfigSet& set);
-	ANKI_USE_RESULT Error initHBlur(const ConfigSet& set);
-
-	void runMain(const RenderingContext& ctx, RenderPassWorkContext& rgraphCtx);
-	void runHBlur(RenderPassWorkContext& rgraphCtx);
-	void runVBlur(RenderPassWorkContext& rgraphCtx);
-
-	/// A RenderPassWorkCallback for SSAO main pass.
-	static void runMainCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		Volumetric* const self = scast<Volumetric*>(rgraphCtx.m_userData);
-		self->runMain(*self->m_runCtx.m_ctx, rgraphCtx);
-	}
-
-	/// A RenderPassWorkCallback for SSAO HBlur.
-	static void runHBlurCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		Volumetric* const self = scast<Volumetric*>(rgraphCtx.m_userData);
-		self->runHBlur(rgraphCtx);
-	}
-
-	/// A RenderPassWorkCallback for SSAO VBlur.
-	static void runVBlurCallback(RenderPassWorkContext& rgraphCtx)
-	{
-		Volumetric* const self = scast<Volumetric*>(rgraphCtx.m_userData);
-		self->runVBlur(rgraphCtx);
-	}
-};
-/// @}
-
-} // end namespace anki

+ 103 - 0
src/anki/renderer/VolumetricFog.cpp

@@ -0,0 +1,103 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/renderer/VolumetricFog.h>
+#include <anki/renderer/Renderer.h>
+#include <anki/renderer/DepthDownscale.h>
+#include <anki/renderer/ShadowMapping.h>
+#include <anki/renderer/LightShading.h>
+#include <anki/renderer/RenderQueue.h>
+#include <anki/renderer/VolumetricLightingAccumulation.h>
+#include <anki/misc/ConfigSet.h>
+
+namespace anki
+{
+
+Error VolumetricFog::init(const ConfigSet& config)
+{
+	// Misc
+	const U fractionXY = config.getNumber("r.volumetricLightingAccumulation.clusterFractionXY");
+	ANKI_ASSERT(fractionXY >= 1);
+	const U fractionZ = config.getNumber("r.volumetricLightingAccumulation.clusterFractionZ");
+	ANKI_ASSERT(fractionZ >= 1);
+	m_finalClusterZ = config.getNumber("r.volumetricLightingAccumulation.finalClusterInZ");
+	ANKI_ASSERT(m_finalClusterZ > 0 && m_finalClusterZ < m_r->getClusterCount()[2]);
+
+	m_volumeSize[0] = m_r->getClusterCount()[0] * fractionXY;
+	m_volumeSize[1] = m_r->getClusterCount()[1] * fractionXY;
+	m_volumeSize[2] = (m_finalClusterZ + 1) * fractionZ;
+	ANKI_R_LOGI("Initializing volumetric fog. Size %ux%ux%u", m_volumeSize[0], m_volumeSize[1], m_volumeSize[2]);
+
+	// Shaders
+	ANKI_CHECK(getResourceManager().loadResource("shaders/VolumetricFogAccumulation.glslp", m_prog));
+
+	ShaderProgramResourceConstantValueInitList<5> consts(m_prog);
+	consts.add("VOLUME_SIZE", UVec3(m_volumeSize[0], m_volumeSize[1], m_volumeSize[2]))
+		.add("CLUSTER_COUNT", UVec3(m_r->getClusterCount()[0], m_r->getClusterCount()[1], m_r->getClusterCount()[2]))
+		.add("FINAL_CLUSTER_Z", U32(m_finalClusterZ))
+		.add("FRACTION", UVec3(fractionXY, fractionXY, fractionZ))
+		.add("WORKGROUP_SIZE", UVec2(m_workgroupSize[0], m_workgroupSize[1]));
+
+	const ShaderProgramResourceVariant* variant;
+	m_prog->getOrCreateVariant(consts.get(), variant);
+	m_grProg = variant->getProgram();
+
+	// RT descr
+	m_rtDescr =
+		m_r->create2DRenderTargetDescription(m_volumeSize[0], m_volumeSize[1], Format::R16G16B16A16_SFLOAT, "Fog");
+	m_rtDescr.m_depth = m_volumeSize[2];
+	m_rtDescr.m_type = TextureType::_3D;
+	m_rtDescr.bake();
+
+	return Error::NONE;
+}
+
+void VolumetricFog::run(RenderPassWorkContext& rgraphCtx)
+{
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+	const RenderingContext& ctx = *m_runCtx.m_ctx;
+
+	cmdb->bindShaderProgram(m_grProg);
+
+	rgraphCtx.bindImage(0, 0, m_runCtx.m_rt, TextureSubresourceInfo());
+
+	rgraphCtx.bindColorTextureAndSampler(
+		0, 0, m_r->getVolumetricLightingAccumulation().getRt(), m_r->getLinearSampler());
+
+	struct PushConsts
+	{
+		Vec4 m_fogScatteringCoeffFogAbsorptionCoeffDensityPad1;
+		Vec4 m_fogDiffusePad1;
+		ClustererMagicValues m_clustererMagic;
+	} regs;
+	regs.m_fogScatteringCoeffFogAbsorptionCoeffDensityPad1 =
+		Vec4(m_fogScatteringCoeff, m_fogAbsorptionCoeff, m_fogDensity, 0.0f);
+	regs.m_fogDiffusePad1 = Vec4(m_fogDiffuseColor, 0.0f);
+	regs.m_clustererMagic = ctx.m_clusterBinOut.m_shaderMagicValues;
+
+	cmdb->setPushConstants(&regs, sizeof(regs));
+
+	dispatchPPCompute(cmdb, m_workgroupSize[0], m_workgroupSize[1], m_volumeSize[0], m_volumeSize[1]);
+}
+
+void VolumetricFog::populateRenderGraph(RenderingContext& ctx)
+{
+	m_runCtx.m_ctx = &ctx;
+	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
+
+	m_runCtx.m_rt = rgraph.newRenderTarget(m_rtDescr);
+
+	ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("Vol fog");
+
+	auto callback = [](RenderPassWorkContext& rgraphCtx) -> void {
+		static_cast<VolumetricFog*>(rgraphCtx.m_userData)->run(rgraphCtx);
+	};
+	pass.setWork(callback, this, 0);
+
+	pass.newDependency({m_runCtx.m_rt, TextureUsageBit::IMAGE_COMPUTE_WRITE});
+	pass.newDependency({m_r->getVolumetricLightingAccumulation().getRt(), TextureUsageBit::SAMPLED_COMPUTE});
+}
+
+} // end namespace anki

+ 98 - 0
src/anki/renderer/VolumetricFog.h

@@ -0,0 +1,98 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/renderer/RendererObject.h>
+
+namespace anki
+{
+
+/// @addtogroup renderer
+/// @{
+
+/// VolumetricFog effects.
+class VolumetricFog : public RendererObject
+{
+public:
+	void setFogParticleColor(const Vec3& col)
+	{
+		m_fogDiffuseColor = col;
+	}
+
+	const Vec3& getFogParticleColor() const
+	{
+		return m_fogDiffuseColor;
+	}
+
+	void setParticleDensity(F32 d)
+	{
+		m_fogDensity = d;
+	}
+
+	F32 getParticleDensity() const
+	{
+		return m_fogDensity;
+	}
+
+anki_internal:
+	VolumetricFog(Renderer* r)
+		: RendererObject(r)
+	{
+	}
+
+	~VolumetricFog()
+	{
+	}
+
+	ANKI_USE_RESULT Error init(const ConfigSet& config);
+
+	/// Populate the rendergraph.
+	void populateRenderGraph(RenderingContext& ctx);
+
+	RenderTargetHandle getRt() const
+	{
+		return m_runCtx.m_rt;
+	}
+
+	const Array<U32, 3>& getVolumeSize() const
+	{
+		return m_volumeSize;
+	}
+
+	/// Get the last cluster split in Z axis that will be affected by lighting.
+	U32 getFinalClusterInZ() const
+	{
+		return m_finalClusterZ;
+	}
+
+private:
+	ShaderProgramResourcePtr m_prog;
+	ShaderProgramPtr m_grProg;
+
+	RenderTargetDescription m_rtDescr;
+
+	U32 m_finalClusterZ = 0;
+
+	Array<U32, 2> m_workgroupSize = {{8, 8}};
+	Array<U32, 3> m_volumeSize;
+
+	Vec3 m_fogDiffuseColor = Vec3(1.0f);
+	F32 m_fogDensity = 0.9f;
+	F32 m_fogScatteringCoeff = 0.01f;
+	F32 m_fogAbsorptionCoeff = 0.02f;
+
+	class
+	{
+	public:
+		RenderTargetHandle m_rt;
+		const RenderingContext* m_ctx = nullptr;
+	} m_runCtx; ///< Runtime context.
+
+	void run(RenderPassWorkContext& rgraphCtx);
+};
+/// @}
+
+} // end namespace anki

+ 149 - 0
src/anki/renderer/VolumetricLightingAccumulation.cpp

@@ -0,0 +1,149 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/renderer/VolumetricLightingAccumulation.h>
+#include <anki/renderer/ShadowMapping.h>
+#include <anki/renderer/Indirect.h>
+#include <anki/renderer/Renderer.h>
+#include <anki/resource/TextureResource.h>
+#include <anki/misc/ConfigSet.h>
+
+namespace anki
+{
+
+VolumetricLightingAccumulation::VolumetricLightingAccumulation(Renderer* r)
+	: RendererObject(r)
+{
+}
+
+VolumetricLightingAccumulation::~VolumetricLightingAccumulation()
+{
+}
+
+Error VolumetricLightingAccumulation::init(const ConfigSet& config)
+{
+	// Misc
+	const U fractionXY = config.getNumber("r.volumetricLightingAccumulation.clusterFractionXY");
+	ANKI_ASSERT(fractionXY >= 1);
+	const U fractionZ = config.getNumber("r.volumetricLightingAccumulation.clusterFractionZ");
+	ANKI_ASSERT(fractionZ >= 1);
+	m_finalClusterZ = config.getNumber("r.volumetricLightingAccumulation.finalClusterInZ");
+	ANKI_ASSERT(m_finalClusterZ > 0 && m_finalClusterZ < m_r->getClusterCount()[2]);
+
+	m_volumeSize[0] = m_r->getClusterCount()[0] * fractionXY;
+	m_volumeSize[1] = m_r->getClusterCount()[1] * fractionXY;
+	m_volumeSize[2] = (m_finalClusterZ + 1) * fractionZ;
+	ANKI_R_LOGI("Initializing volumetric lighting accumulation. Size %ux%ux%u",
+		m_volumeSize[0],
+		m_volumeSize[1],
+		m_volumeSize[2]);
+
+	ANKI_CHECK(getResourceManager().loadResource("engine_data/blue_noise_rgb8_16x16x16_3d.ankitex", m_noiseTex));
+
+	// Shaders
+	ANKI_CHECK(getResourceManager().loadResource("shaders/VolumetricLightingAccumulation.glslp", m_prog));
+
+	ShaderProgramResourceMutationInitList<1> mutators(m_prog);
+	mutators.add("ENABLE_SHADOWS", 1);
+
+	ShaderProgramResourceConstantValueInitList<6> consts(m_prog);
+	consts.add("VOLUME_SIZE", UVec3(m_volumeSize[0], m_volumeSize[1], m_volumeSize[2]))
+		.add("CLUSTER_COUNT", UVec3(m_r->getClusterCount()[0], m_r->getClusterCount()[1], m_r->getClusterCount()[2]))
+		.add("FINAL_CLUSTER_Z", U32(m_finalClusterZ))
+		.add("FRACTION", UVec3(fractionXY, fractionXY, fractionZ))
+		.add("WORKGROUP_SIZE", UVec3(m_workgroupSize[0], m_workgroupSize[1], m_workgroupSize[2]))
+		.add("NOISE_TEX_SIZE", UVec3(m_noiseTex->getWidth(), m_noiseTex->getHeight(), m_noiseTex->getDepth()));
+
+	const ShaderProgramResourceVariant* variant;
+	m_prog->getOrCreateVariant(mutators.get(), consts.get(), variant);
+	m_grProg = variant->getProgram();
+
+	// Create RTs
+	TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(m_volumeSize[0],
+		m_volumeSize[1],
+		LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT,
+		TextureUsageBit::IMAGE_COMPUTE_READ_WRITE | TextureUsageBit::SAMPLED_FRAGMENT
+			| TextureUsageBit::SAMPLED_COMPUTE,
+		"VolLight");
+	texinit.m_depth = m_volumeSize[2];
+	texinit.m_type = TextureType::_3D;
+	texinit.m_initialUsage = TextureUsageBit::SAMPLED_FRAGMENT;
+	m_rtTextures[0] = m_r->createAndClearRenderTarget(texinit);
+	m_rtTextures[1] = m_r->createAndClearRenderTarget(texinit);
+
+	return Error::NONE;
+}
+
+void VolumetricLightingAccumulation::populateRenderGraph(RenderingContext& ctx)
+{
+	m_runCtx.m_ctx = &ctx;
+	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
+
+	const U readRtIdx = m_r->getFrameCount() & 1;
+
+	m_runCtx.m_rts[0] = rgraph.importRenderTarget(m_rtTextures[readRtIdx], TextureUsageBit::SAMPLED_FRAGMENT);
+	m_runCtx.m_rts[1] = rgraph.importRenderTarget(m_rtTextures[!readRtIdx], TextureUsageBit::NONE);
+
+	ComputeRenderPassDescription& pass = rgraph.newComputeRenderPass("Vol light");
+
+	auto callback = [](RenderPassWorkContext& rgraphCtx) -> void {
+		static_cast<VolumetricLightingAccumulation*>(rgraphCtx.m_userData)->run(rgraphCtx);
+	};
+	pass.setWork(callback, this, 0);
+
+	pass.newDependency({m_runCtx.m_rts[0], TextureUsageBit::SAMPLED_COMPUTE});
+	pass.newDependency({m_runCtx.m_rts[1], TextureUsageBit::IMAGE_COMPUTE_WRITE});
+	pass.newDependency({m_r->getShadowMapping().getShadowmapRt(), TextureUsageBit::SAMPLED_COMPUTE});
+}
+
+void VolumetricLightingAccumulation::run(RenderPassWorkContext& rgraphCtx)
+{
+	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
+	RenderingContext& ctx = *m_runCtx.m_ctx;
+
+	cmdb->bindShaderProgram(m_grProg);
+
+	rgraphCtx.bindImage(0, 0, m_runCtx.m_rts[1], TextureSubresourceInfo());
+
+	cmdb->bindTextureAndSampler(
+		0, 0, m_noiseTex->getGrTextureView(), m_r->getTrilinearRepeatSampler(), TextureUsageBit::SAMPLED_COMPUTE);
+
+	rgraphCtx.bindColorTextureAndSampler(0, 1, m_runCtx.m_rts[0], m_r->getLinearSampler());
+
+	rgraphCtx.bindColorTextureAndSampler(0, 2, m_r->getShadowMapping().getShadowmapRt(), m_r->getLinearSampler());
+	cmdb->bindTextureAndSampler(
+		0, 3, m_r->getDummyTextureView(), m_r->getNearestSampler(), TextureUsageBit::SAMPLED_COMPUTE);
+	rgraphCtx.bindColorTextureAndSampler(0, 4, m_r->getIndirect().getIrradianceRt(), m_r->getTrilinearRepeatSampler());
+	cmdb->bindTextureAndSampler(
+		0, 5, m_r->getDummyTextureView(), m_r->getNearestSampler(), TextureUsageBit::SAMPLED_COMPUTE);
+
+	const ClusterBinOut& rsrc = ctx.m_clusterBinOut;
+	bindUniforms(cmdb, 0, 0, ctx.m_lightShadingUniformsToken);
+	bindUniforms(cmdb, 0, 1, rsrc.m_pointLightsToken);
+	bindUniforms(cmdb, 0, 2, rsrc.m_spotLightsToken);
+	bindUniforms(cmdb, 0, 3, rsrc.m_probesToken);
+	bindStorage(cmdb, 0, 0, rsrc.m_clustersToken);
+	bindStorage(cmdb, 0, 1, rsrc.m_indicesToken);
+
+	struct PushConsts
+	{
+		Vec4 m_noiseOffsetPad3;
+	} regs;
+	regs.m_noiseOffsetPad3 = Vec4(0.0f);
+	const F32 texelSize = 1.0f / m_noiseTex->getDepth();
+	regs.m_noiseOffsetPad3.x() = texelSize * F32(m_r->getFrameCount() % m_noiseTex->getDepth()) + texelSize / 2.0f;
+
+	cmdb->setPushConstants(&regs, sizeof(regs));
+
+	dispatchPPCompute(cmdb,
+		m_workgroupSize[0],
+		m_workgroupSize[1],
+		m_workgroupSize[2],
+		m_volumeSize[0],
+		m_volumeSize[1],
+		m_volumeSize[2]);
+}
+
+} // end namespace anki

+ 62 - 0
src/anki/renderer/VolumetricLightingAccumulation.h

@@ -0,0 +1,62 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/renderer/RendererObject.h>
+
+namespace anki
+{
+
+/// @addtogroup renderer
+/// @{
+
+/// Volumetric lighting. It accumulates lighting in a volume texture.
+class VolumetricLightingAccumulation : public RendererObject
+{
+anki_internal:
+	VolumetricLightingAccumulation(Renderer* r);
+	~VolumetricLightingAccumulation();
+
+	ANKI_USE_RESULT Error init(const ConfigSet& config);
+
+	/// Populate the rendergraph.
+	void populateRenderGraph(RenderingContext& ctx);
+
+	RenderTargetHandle getRt() const
+	{
+		return m_runCtx.m_rts[1];
+	}
+
+	/// Get the last cluster split in Z axis that will be affected by lighting.
+	U32 getFinalClusterInZ() const
+	{
+		return m_finalClusterZ;
+	}
+
+private:
+	ShaderProgramResourcePtr m_prog;
+	ShaderProgramPtr m_grProg;
+
+	Array<TexturePtr, 2> m_rtTextures;
+	TextureResourcePtr m_noiseTex;
+
+	U32 m_finalClusterZ = 0;
+
+	Array<U32, 3> m_workgroupSize = {{8, 8, 8}};
+	Array<U32, 3> m_volumeSize;
+
+	class
+	{
+	public:
+		RenderingContext* m_ctx = nullptr;
+		Array<RenderTargetHandle, 2> m_rts;
+	} m_runCtx; ///< Runtime context.
+
+	void run(RenderPassWorkContext& rgraphCtx);
+};
+/// @}
+
+} // end namespace anki

+ 1 - 1
src/anki/resource/TextureResource.cpp

@@ -185,7 +185,7 @@ Error TextureResource::load(const ResourceFilename& filename, Bool async)
 	SamplerInitInfo samplerInit("TextureRsrc");
 	samplerInit.m_minMagFilter = SamplingFilter::LINEAR;
 	samplerInit.m_mipmapFilter = SamplingFilter::LINEAR;
-	samplerInit.m_repeat = true;
+	samplerInit.m_addressing = SamplingAddressing::REPEAT;
 	samplerInit.m_anisotropyLevel = getManager().getTextureAnisotropy();
 	m_sampler = getManager().getGrManager().newSampler(samplerInit);
 

+ 0 - 7
src/anki/scene/SceneGraph.h

@@ -215,11 +215,6 @@ anki_internal:
 		return m_nodesUuid++;
 	}
 
-	SceneComponentLists& getSceneComponentLists()
-	{
-		return m_componentLists;
-	}
-
 	F32 getEarlyZDistance() const
 	{
 		return m_earlyZDist;
@@ -269,8 +264,6 @@ private:
 
 	U64 m_nodesUuid = 0;
 
-	SceneComponentLists m_componentLists;
-
 	F32 m_earlyZDist = -1.0;
 
 	SceneGraphStats m_stats;

+ 7 - 1
src/anki/scene/components/MoveComponent.cpp

@@ -20,9 +20,15 @@ MoveComponent::~MoveComponent()
 {
 }
 
-Error MoveComponent::update(Second, Second, Bool& updated)
+Error MoveComponent::update(Second prevTime, Second crntTime, Bool& updated)
 {
 	updated = updateWorldTransform(*m_node);
+
+	if(updated)
+	{
+		onMoveComponentUpdate(*m_node, prevTime, crntTime);
+	}
+
 	return Error::NONE;
 }
 

+ 0 - 5
src/anki/scene/components/MoveComponent.h

@@ -112,11 +112,6 @@ public:
 	/// @note Don't update if child because we start from roots and go to children and we don't want a child to be
 	///       updated before the parent
 	ANKI_USE_RESULT Error update(Second, Second, Bool& updated) override;
-
-	ANKI_USE_RESULT Error onUpdate(Second prevTime, Second crntTime) final
-	{
-		return onMoveComponentUpdate(*m_node, prevTime, crntTime);
-	}
 	/// @}
 
 	/// @name Mess with the local transform

+ 2 - 29
src/anki/scene/components/SceneComponent.cpp

@@ -12,22 +12,14 @@ namespace anki
 
 SceneComponent::SceneComponent(SceneComponentType type, SceneNode* node)
 	: m_node(node)
-	, m_type(type)
 	, m_uuid(node->getSceneGraph().getNewUuid())
 	, m_idx(node->getComponentCount())
+	, m_type(type)
 {
-	if(m_type != SceneComponentType::NONE)
-	{
-		m_node->getSceneGraph().getSceneComponentLists().insertNew(this);
-	}
 }
 
 SceneComponent::~SceneComponent()
 {
-	if(m_type != SceneComponentType::NONE)
-	{
-		m_node->getSceneGraph().getSceneComponentLists().remove(this);
-	}
 }
 
 Timestamp SceneComponent::getGlobalTimestamp() const
@@ -40,12 +32,7 @@ Error SceneComponent::updateReal(Second prevTime, Second crntTime, Bool& updated
 	Error err = update(prevTime, crntTime, updated);
 	if(!err && updated)
 	{
-		err = onUpdate(prevTime, crntTime);
-
-		if(!err)
-		{
-			m_timestamp = getGlobalTimestamp();
-		}
+		m_timestamp = getGlobalTimestamp();
 	}
 
 	return err;
@@ -71,18 +58,4 @@ SceneFrameAllocator<U8> SceneComponent::getFrameAllocator() const
 	return m_node->getFrameAllocator();
 }
 
-void SceneComponentLists::insertNew(SceneComponent* comp)
-{
-	ANKI_ASSERT(comp);
-
-	m_lists[comp->getType()].pushBack(comp);
-}
-
-void SceneComponentLists::remove(SceneComponent* comp)
-{
-	ANKI_ASSERT(comp);
-
-	m_lists[comp->getType()].erase(comp);
-}
-
 } // end namespace anki

+ 2 - 42
src/anki/scene/components/SceneComponent.h

@@ -8,7 +8,6 @@
 #include <anki/scene/Common.h>
 #include <anki/util/Functions.h>
 #include <anki/util/BitMask.h>
-#include <anki/util/List.h>
 
 namespace anki
 {
@@ -42,7 +41,7 @@ enum class SceneComponentType : U16
 };
 
 /// Scene node component
-class SceneComponent : public IntrusiveListEnabled<SceneComponent>
+class SceneComponent
 {
 public:
 	/// Construct the scene component.
@@ -72,12 +71,6 @@ public:
 		return Error::NONE;
 	}
 
-	/// Called if SceneComponent::update returned true.
-	virtual ANKI_USE_RESULT Error onUpdate(Second prevTime, Second crntTime)
-	{
-		return Error::NONE;
-	}
-
 	/// Called only by the SceneGraph
 	ANKI_USE_RESULT Error updateReal(Second prevTime, Second crntTime, Bool& updated);
 
@@ -115,42 +108,9 @@ protected:
 	Timestamp m_timestamp = 1; ///< Indicates when an update happened
 
 private:
-	SceneComponentType m_type;
 	U64 m_uuid;
 	U32 m_idx;
-};
-
-/// Multiple lists of all types of components.
-class SceneComponentLists : public NonCopyable
-{
-anki_internal:
-	SceneComponentLists()
-	{
-	}
-
-	~SceneComponentLists()
-	{
-	}
-
-	void insertNew(SceneComponent* comp);
-
-	void remove(SceneComponent* comp);
-
-	template<typename TSceneComponentType, typename Func>
-	void iterateComponents(Func func)
-	{
-		auto it = m_lists[TSceneComponentType::CLASS_TYPE].getBegin();
-		auto end = m_lists[TSceneComponentType::CLASS_TYPE].getEnd();
-
-		while(it != end)
-		{
-			func(static_cast<TSceneComponentType&>(*it));
-			++it;
-		}
-	}
-
-private:
-	Array<IntrusiveList<SceneComponent>, U(SceneComponentType::COUNT)> m_lists;
+	SceneComponentType m_type;
 };
 /// @}
 

+ 1 - 1
src/anki/ui/Canvas.cpp

@@ -58,7 +58,7 @@ Error Canvas::init(FontPtr font, U32 fontHeight, U32 width, U32 height)
 	SamplerInitInfo samplerInit("Canvas");
 	samplerInit.m_minMagFilter = SamplingFilter::LINEAR;
 	samplerInit.m_mipmapFilter = SamplingFilter::LINEAR;
-	samplerInit.m_repeat = true;
+	samplerInit.m_addressing = SamplingAddressing::REPEAT;
 	m_sampler = m_manager->getGrManager().newSampler(samplerInit);
 
 	return Error::NONE;

+ 19 - 19
tests/gr/Gr.cpp

@@ -67,7 +67,7 @@ void main()
 	out_color = u_color[gl_VertexID].rgb;
 
 	const vec2 POSITIONS[3] = vec2[](vec2(-1.0, 1.0), vec2(0.0, -1.0), vec2(1.0, 1.0));
-		
+
 	mat2 rot = mat2(
 		u_rotation2d.x, u_rotation2d.y, u_rotation2d.z, u_rotation2d.w);
 	vec2 pos = rot * POSITIONS[gl_VertexID % 3];
@@ -251,7 +251,7 @@ void main()
 	float factor = uv.x;
 	vec3 col0 = texture(u_tex0, uv).rgb;
 	vec3 col1 = texture(u_tex1, uv).rgb;
-	
+
 	out_color = vec4(col1 + col0, 1.0);
 })";
 
@@ -269,12 +269,12 @@ static const char* COMP_WRITE_IMAGE_SRC = R"(
 layout(ANKI_IMAGE_BINDING(0, 0), rgba8) writeonly uniform image2D u_img;
 
 layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-	
+
 layout(ANKI_SS_BINDING(1, 0)) buffer ss1_
 {
 	vec4 u_color;
 };
-	
+
 void main()
 {
 	imageStore(u_img, ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y), u_color);
@@ -943,7 +943,7 @@ ANKI_TEST(Gr, DrawWithTexture)
 	SamplerInitInfo samplerInit;
 	samplerInit.m_minMagFilter = SamplingFilter::NEAREST;
 	samplerInit.m_mipmapFilter = SamplingFilter::LINEAR;
-	samplerInit.m_repeat = false;
+	samplerInit.m_addressing = SamplingAddressing::CLAMP;
 	SamplerPtr sampler = gr->newSampler(samplerInit);
 
 	//
@@ -1457,7 +1457,7 @@ ANKI_TEST(Gr, 3DTextures)
 	SamplerInitInfo samplerInit;
 	samplerInit.m_minMagFilter = SamplingFilter::NEAREST;
 	samplerInit.m_mipmapFilter = SamplingFilter::BASE;
-	samplerInit.m_repeat = false;
+	samplerInit.m_addressing = SamplingAddressing::CLAMP;
 	SamplerPtr sampler = gr->newSampler(samplerInit);
 
 	//
@@ -1800,27 +1800,27 @@ ANKI_TEST(Gr, VkWorkarounds)
 layout(local_size_x = 8, local_size_y = 8, local_size_z = 2) in;
 
 layout(ANKI_TEX_BINDING(0, 0)) uniform usampler2D u_tex;
-	
+
 layout(ANKI_SS_BINDING(0, 0)) buffer s_
 {
 	uvec4 u_result;
 };
-	
+
 shared uint g_wrong;
-	
+
 void main()
 {
 	g_wrong = 0;
 	memoryBarrierShared();
 	barrier();
-	
+
 	int lod = -1;
 	uint idx;
-	
+
 	if(gl_LocalInvocationID.z == 0)
 	{
 		// First mip
-	
+
 		lod = 0;
 		idx = gl_LocalInvocationID.y * 8 + gl_LocalInvocationID.x;
 	}
@@ -1829,7 +1829,7 @@ void main()
 		lod = 1;
 		idx = gl_LocalInvocationID.y * 4 + gl_LocalInvocationID.x;
 	}
-	
+
 	if(lod != -1)
 	{
 		uvec3 col = texelFetch(u_tex, ivec2(gl_LocalInvocationID.x, gl_LocalInvocationID.y), lod).rgb;
@@ -1838,10 +1838,10 @@ void main()
 			atomicAdd(g_wrong, 1);
 		}
 	}
-	
+
 	memoryBarrierShared();
 	barrier();
-	
+
 	if(g_wrong != 0)
 	{
 		u_result = uvec4(1);
@@ -2063,12 +2063,12 @@ struct PC
 	mat4 mat;
 };
 ANKI_PUSH_CONSTANTS(PC, regs);
-	
+
 out gl_PerVertex
 {
 	vec4 gl_Position;
 };
-	
+
 layout(location = 0) out vec4 out_color;
 
 void main()
@@ -2076,7 +2076,7 @@ void main()
 	vec2 uv = vec2(gl_VertexID & 1, gl_VertexID >> 1) * 2.0;
 	vec2 pos = uv * 2.0 - 1.0;
 	gl_Position = vec4(pos, 0.0, 1.0);
-	
+
 	out_color = regs.color;
 }
 )";
@@ -2090,7 +2090,7 @@ struct PC
 	mat4 mat;
 };
 ANKI_PUSH_CONSTANTS(PC, regs);
-	
+
 layout(location = 0) in vec4 in_color;
 layout(location = 0) out vec4 out_color;
 

+ 3 - 3
tools/texture/create_atlas.py

@@ -31,7 +31,7 @@ class Frame:
 	@classmethod
 	def diagonal(self):
 		return sqrt(self.w * self.w + self.h * self.h)
-	
+
 	@classmethod
 	def area(self):
 		return self.w * self.h
@@ -177,7 +177,7 @@ def place_sub_images(ctx):
 		sub_image = ctx.sub_images[unplaced_imgs[0]]
 		unplaced_imgs.pop(0)
 
-		printi("Will try to place image \"%s\" of size %ux%d" % 
+		printi("Will try to place image \"%s\" of size %ux%d" %
 				(sub_image.image_name, sub_image.width, sub_image.height))
 
 		# Find best frame
@@ -288,7 +288,7 @@ def write_xml(ctx):
 def main():
 	""" The main """
 
-	ctx = parse_commandline();
+	ctx = parse_commandline()
 	load_images(ctx)
 	compute_atlas_rough_size(ctx)
 	place_sub_images(ctx)

+ 92 - 0
tools/texture/noise_array.py

@@ -0,0 +1,92 @@
+#!/usr/bin/python3
+
+# Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+# All rights reserved.
+# Code licensed under the BSD License.
+# http://www.anki3d.org/LICENSE
+
+import argparse
+from PIL import Image, ImageDraw
+from math import *
+import os
+
+class Context:
+	input = ""
+	out_prefix = ""
+	out_count = 0
+	out_format = ""
+
+	img = None
+	color_delta = 0.0
+
+ctx = Context()
+
+def parse_commandline():
+	""" Parse the command line arguments """
+
+	parser = argparse.ArgumentParser(description = "This program takes a blue noise texture and creates an array of " \
+			"noise textures",
+			formatter_class = argparse.ArgumentDefaultsHelpFormatter)
+
+	parser.add_argument("-i", "--input", required = True, help = "the input noise texture")
+
+	parser.add_argument("-o", "--output-prefix", required = True, help = "prefix of the output images")
+
+	parser.add_argument("-c", "--output-img-count", required = True, help = "number of output images")
+
+	parser.add_argument("-f", "--output-format", help = "output format", default = "")
+
+	args = parser.parse_args()
+
+	ctx.input = args.input
+	ctx.out_prefix = args.output_prefix
+	ctx.out_count = int(args.output_img_count)
+	ctx.out_format = args.output_format
+
+def init():
+	# Open image
+	ctx.img = Image.open(ctx.input)
+
+	# Color fmt
+	if ctx.img.mode != "RGB" and ctx.img.mode != "RGBA":
+		raise Exception("Unknown mode %s" % ctx.img.mode)
+
+	if ctx.out_format == "":
+		ctx.out_format = ctx.img.mode
+
+	# Color delta
+	ctx.color_delta = int(0xFF / (ctx.out_count + 1.0))
+
+def create_image(idx):
+	out_img = Image.new(ctx.img.mode, (ctx.img.width, ctx.img.height))
+
+	delta = idx * ctx.color_delta
+
+	for x in range(0, ctx.img.width):
+		for y in range(0, ctx.img.height):
+			pixel = ctx.img.getpixel((x, y))
+
+			r = int(pixel[0] + delta) % 0xFF
+			g = int(pixel[1] + delta) % 0xFF
+			b = int(pixel[2] + delta) % 0xFF
+
+			if ctx.img.mode == "RGBA":
+				a = int(pixel[3] + delta) % 0xFF
+
+			if ctx.out_format == "RGB":
+				out_img.putpixel((x, y), (r, g, b))
+			else:
+				out_img.putpixel((x, y), (r, g, b, a))
+
+	out_img.save("%s_%02d.png" % (ctx.out_prefix, idx))
+
+def main():
+	""" The main """
+
+	parse_commandline()
+	init()
+	for i in range(ctx.out_count):
+		create_image(i + 1)
+
+if __name__ == "__main__":
+	main()