Просмотр исходного кода

Merge pull request #39 from godlikepanos/motion_blur

Motion blur
Panagiotis Christopoulos Charitos 7 лет назад
Родитель
Сommit
f4c8141af6
74 измененных файлов с 488 добавлено и 210 удалено
  1. 1 0
      samples/physics_playground/assets/dynamic-material.ankimtl
  2. 1 0
      samples/physics_playground/assets/sky-material.ankimtl
  3. 1 0
      samples/physics_playground/assets/walls-material.ankimtl
  4. 1 0
      samples/simple_scene/assets/room-material.ankimtl
  5. 10 10
      samples/simple_scene/assets/scene.lua
  6. 1 0
      samples/sponza/assets/Material__57_001-material.ankimtl
  7. 1 0
      samples/sponza/assets/arch-material.ankimtl
  8. 1 0
      samples/sponza/assets/bricks-material.ankimtl
  9. 1 0
      samples/sponza/assets/ceiling-material.ankimtl
  10. 1 0
      samples/sponza/assets/chain-material.ankimtl
  11. 1 0
      samples/sponza/assets/column_a-material.ankimtl
  12. 1 0
      samples/sponza/assets/column_b-material.ankimtl
  13. 1 0
      samples/sponza/assets/column_c-material.ankimtl
  14. 1 0
      samples/sponza/assets/details-material.ankimtl
  15. 1 0
      samples/sponza/assets/fabric_a-material.ankimtl
  16. 1 0
      samples/sponza/assets/fabric_c-material.ankimtl
  17. 1 0
      samples/sponza/assets/fabric_d-material.ankimtl
  18. 1 0
      samples/sponza/assets/fabric_e-material.ankimtl
  19. 1 0
      samples/sponza/assets/fabric_f-material.ankimtl
  20. 1 0
      samples/sponza/assets/flagpole-material.ankimtl
  21. 1 0
      samples/sponza/assets/floor-material.ankimtl
  22. 1 0
      samples/sponza/assets/leaf-material.ankimtl
  23. 1 0
      samples/sponza/assets/lion-material.ankimtl
  24. 1 0
      samples/sponza/assets/lion_stand-material.ankimtl
  25. 1 0
      samples/sponza/assets/roof-material.ankimtl
  26. 1 0
      samples/sponza/assets/vase-material.ankimtl
  27. 1 0
      samples/sponza/assets/vase_hanging-material.ankimtl
  28. 1 0
      samples/sponza/assets/vase_round_001-material.ankimtl
  29. 1 0
      samples/sponza/assets/writings-material.ankimtl
  30. 14 12
      shaders/FinalComposite.glslp
  31. 16 6
      shaders/Functions.glsl
  32. 18 11
      shaders/GBufferCommonFrag.glsl
  33. 14 0
      shaders/GBufferCommonVert.glsl
  34. 17 4
      shaders/GBufferGeneric.glslp
  35. 53 0
      shaders/MotionBlur.glsl
  36. 7 2
      shaders/Pack.glsl
  37. 2 2
      src/anki/core/Config.cpp
  38. 10 0
      src/anki/gr/gl/Common.cpp
  39. 1 1
      src/anki/renderer/Common.cpp
  40. 1 1
      src/anki/renderer/Common.h
  41. 1 1
      src/anki/renderer/Dbg.cpp
  42. 3 1
      src/anki/renderer/Drawer.cpp
  43. 1 0
      src/anki/renderer/Drawer.h
  44. 25 11
      src/anki/renderer/FinalComposite.cpp
  45. 0 2
      src/anki/renderer/FinalComposite.h
  46. 3 2
      src/anki/renderer/ForwardShading.cpp
  47. 13 11
      src/anki/renderer/GBuffer.cpp
  48. 3 2
      src/anki/renderer/Indirect.cpp
  49. 1 1
      src/anki/renderer/LensFlare.cpp
  50. 7 6
      src/anki/renderer/LightShading.cpp
  51. 1 0
      src/anki/renderer/RenderQueue.h
  52. 11 7
      src/anki/renderer/Renderer.cpp
  53. 18 9
      src/anki/renderer/Renderer.h
  54. 1 0
      src/anki/renderer/ShadowMapping.cpp
  55. 5 4
      src/anki/renderer/Ssr.cpp
  56. 2 1
      src/anki/renderer/TemporalAA.cpp
  57. 39 8
      src/anki/resource/MaterialResource.cpp
  58. 4 2
      src/anki/resource/MaterialResource.h
  59. 2 1
      src/anki/resource/ModelResource.cpp
  60. 1 1
      src/anki/resource/ParticleEmitterResource.cpp
  61. 18 7
      src/anki/resource/RenderingKey.h
  62. 0 1
      src/anki/resource/ShaderProgramResource.cpp
  63. 0 3
      src/anki/resource/ShaderProgramResource.h
  64. 64 42
      src/anki/scene/ModelNode.cpp
  65. 1 0
      src/anki/scene/ParticleEmitterNode.cpp
  66. 27 26
      src/anki/scene/SceneGraph.cpp
  67. 1 0
      src/anki/scene/Visibility.cpp
  68. 4 3
      src/anki/scene/components/FrustumComponent.cpp
  69. 12 6
      src/anki/scene/components/FrustumComponent.h
  70. 22 2
      src/anki/scene/components/RenderComponent.cpp
  71. 1 0
      src/anki/scene/components/RenderComponent.h
  72. 4 0
      src/anki/util/Array.h
  73. 1 1
      thirdparty
  74. 1 0
      tools/scene/ExporterMaterial.cpp

+ 1 - 0
samples/physics_playground/assets/dynamic-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/physics_playground/assets/sky-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/physics_playground/assets/walls-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/simple_scene/assets/room-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 10 - 10
samples/simple_scene/assets/scene.lua

@@ -5,34 +5,34 @@ local node
 local inst
 local lcomp
 
-node = scene:newSectorNode("sector0", "assets/sector.ankimesh")
+node = scene:newModelNode("roomroom-materialnone0", "assets/roomroom-material.ankimdl")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(0, 0, 0, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
 trf:setRotation(rot)
-trf:setScale(1.16458)
+trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newModelNode("roomroom-materialnone0", "assets/roomroom-material.ankimdl")
+node = scene:newModelNode("columnroom-materialnone1", "assets/columnroom-material.ankimdl")
 trf = Transform.new()
-trf:setOrigin(Vec4.new(0, 0, 0, 0))
+trf:setOrigin(Vec4.new(4.90187, 5.43441, -4.52919, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
 trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newModelNode("columnroom-materialnone1", "assets/columnroom-material.ankimdl")
+node = scene:newModelNode("sectorportal-materialnone2", "assets/sectorportal-material.ankimdl")
 trf = Transform.new()
-trf:setOrigin(Vec4.new(4.90187, 5.43441, -4.52919, 0))
+trf:setOrigin(Vec4.new(0, 0, 0, 0))
 rot = Mat3x4.new()
 rot:setAll(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0)
 trf:setRotation(rot)
-trf:setScale(1)
+trf:setScale(1.16458)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newModelNode("columnroom-materialnone2", "assets/columnroom-material.ankimdl")
+node = scene:newModelNode("columnroom-materialnone3", "assets/columnroom-material.ankimdl")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-4.59369, 5.43441, -4.49454, 0))
 rot = Mat3x4.new()
@@ -41,7 +41,7 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newModelNode("columnroom-materialnone3", "assets/columnroom-material.ankimdl")
+node = scene:newModelNode("columnroom-materialnone4", "assets/columnroom-material.ankimdl")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-4.61101, 5.43441, 4.79029, 0))
 rot = Mat3x4.new()
@@ -50,7 +50,7 @@ trf:setRotation(rot)
 trf:setScale(1)
 node:getSceneNodeBase():getMoveComponent():setLocalTransform(trf)
 
-node = scene:newModelNode("columnroom-materialnone4", "assets/columnroom-material.ankimdl")
+node = scene:newModelNode("columnroom-materialnone5", "assets/columnroom-material.ankimdl")
 trf = Transform.new()
 trf:setOrigin(Vec4.new(4.95098, 5.43441, 4.79029, 0))
 rot = Mat3x4.new()

+ 1 - 0
samples/sponza/assets/Material__57_001-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/arch-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/bricks-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/ceiling-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/chain-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/column_a-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/column_b-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/column_c-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/details-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/fabric_a-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/fabric_c-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/fabric_d-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/fabric_e-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/fabric_f-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/flagpole-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/floor-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/leaf-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/lion-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/lion_stand-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/roof-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/vase-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/vase_hanging-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/vase_round_001-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 1 - 0
samples/sponza/assets/writings-material.ankimtl

@@ -14,6 +14,7 @@
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		
 	

+ 14 - 12
shaders/FinalComposite.glslp

@@ -4,7 +4,6 @@
 // http://www.anki3d.org/LICENSE
 
 #pragma anki mutator BLUE_NOISE 0 1
-#pragma anki mutator SHARPEN_ENABLED 0 1
 #pragma anki mutator BLOOM_ENABLED 0 1
 #pragma anki mutator DBG_ENABLED 0 1
 
@@ -19,20 +18,26 @@
 #include <shaders/Common.glsl>
 #include <shaders/Tonemapping.glsl>
 #include <shaders/Functions.glsl>
+#include <shaders/MotionBlur.glsl>
 
-layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_isRt;
+layout(ANKI_TEX_BINDING(0, 0)) uniform sampler2D u_lightShadingRt;
 layout(ANKI_TEX_BINDING(0, 1)) uniform sampler2D u_ppsBloomLfRt;
 layout(ANKI_TEX_BINDING(0, 2)) uniform sampler3D u_lut;
 layout(ANKI_TEX_BINDING(0, 3)) uniform sampler2DArray u_blueNoise;
+layout(ANKI_TEX_BINDING(0, 4)) uniform sampler2D u_velocityRt;
+layout(ANKI_TEX_BINDING(0, 5)) uniform sampler2D u_depthRt;
 #if DBG_ENABLED
-layout(ANKI_TEX_BINDING(0, 5)) uniform sampler2D u_dbgRt;
+layout(ANKI_TEX_BINDING(0, 6)) uniform sampler2D u_dbgRt;
 #endif
 
-layout(std140, ANKI_UBO_BINDING(0, 0)) uniform u0_
+struct PushConsts
 {
-	Vec4 u_blueNoiseLayerPad3;
+	Vec4 m_blueNoiseLayerPad3;
+	Mat4 m_prevViewProjMatMulInvViewProjMat;
 };
 
+ANKI_PUSH_CONSTANTS(PushConsts, u_regs);
+
 #define TONEMAPPING_BINDING 1
 #include <shaders/TonemappingResources.glsl>
 
@@ -53,11 +58,8 @@ void main()
 {
 	Vec2 uv = in_uv.xy;
 
-#if SHARPEN_ENABLED
-	out_color = readSharpen(u_isRt, uv, 0.15, true);
-#else
-	out_color = textureLod(u_isRt, uv, 0.0).rgb;
-#endif
+	out_color =
+		motionBlur(u_velocityRt, u_lightShadingRt, u_depthRt, uv, u_regs.m_prevViewProjMatMulInvViewProjMat, 32u);
 
 	out_color = tonemap(out_color, UNIFORM(u_exposureThreshold0));
 
@@ -69,7 +71,7 @@ void main()
 	out_color = colorGrading(out_color);
 
 #if BLUE_NOISE
-	Vec3 blueNoise = textureLod(u_blueNoise, Vec3(FB_SIZE / Vec2(64.0) * uv, u_blueNoiseLayerPad3.x), 0.0).rgb;
+	Vec3 blueNoise = textureLod(u_blueNoise, Vec3(FB_SIZE / Vec2(64.0) * uv, u_regs.m_blueNoiseLayerPad3.x), 0.0).rgb;
 	blueNoise = blueNoise * 2.0 - 1.0;
 	blueNoise = sign(blueNoise) * (1.0 - sqrt(1.0 - abs(blueNoise)));
 
@@ -78,7 +80,7 @@ void main()
 
 #if 0
 	{
-		out_color = Vec3(u_averageLuminance / 1.0);
+		out_color = textureLod(u_lightShadingRt, uv, 0.0).rgb;
 	}
 #endif
 

+ 16 - 6
shaders/Functions.glsl

@@ -354,9 +354,9 @@ F32 areaElement(F32 x, F32 y)
 	return atan(x * y, sqrt(x * x + y * y + 1.0));
 }
 
-/// Compute the solid angle of a cube. Solid angle is the area of a sphere when projected into a cubemap. It's also the
-/// delta omega (dω) in the irradiance integral and other integrals that operate in a sphere.
-/// http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/
+// Compute the solid angle of a cube. Solid angle is the area of a sphere when projected into a cubemap. It's also the
+// delta omega (dω) in the irradiance integral and other integrals that operate in a sphere.
+// http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/
 F32 cubeCoordSolidAngle(Vec2 norm, F32 cubeFaceSize)
 {
 	Vec2 invSize = Vec2(1.0 / cubeFaceSize);
@@ -365,9 +365,9 @@ F32 cubeCoordSolidAngle(Vec2 norm, F32 cubeFaceSize)
 	return areaElement(v0.x, v0.y) - areaElement(v0.x, v1.y) - areaElement(v1.x, v0.y) + areaElement(v1.x, v1.y);
 }
 
-/// Intersect a ray against an AABB. The ray is inside the AABB. The function returns the distance 'a' where the
-/// intersection point is rayOrigin + rayDir * a
-/// https://community.arm.com/graphics/b/blog/posts/reflections-based-on-local-cubemaps-in-unity
+// Intersect a ray against an AABB. The ray is inside the AABB. The function returns the distance 'a' where the
+// intersection point is rayOrigin + rayDir * a
+// https://community.arm.com/graphics/b/blog/posts/reflections-based-on-local-cubemaps-in-unity
 F32 rayAabbIntersectionInside(Vec3 rayOrigin, Vec3 rayDir, Vec3 aabbMin, Vec3 aabbMax)
 {
 	Vec3 intersectMaxPointPlanes = (aabbMax - rayOrigin) / rayDir;
@@ -376,3 +376,13 @@ F32 rayAabbIntersectionInside(Vec3 rayOrigin, Vec3 rayDir, Vec3 aabbMin, Vec3 aa
 	F32 distToIntersect = min(min(largestParams.x, largestParams.y), largestParams.z);
 	return distToIntersect;
 }
+
+// A convenience macro to skip out of bounds invocations on post-process compute shaders.
+#define SKIP_OUT_OF_BOUNDS_INVOCATIONS() \
+	if((FB_SIZE.x % WORKGROUP_SIZE.x) != 0u || (FB_SIZE.y % WORKGROUP_SIZE.y) != 0u) \
+	{ \
+		if(gl_GlobalInvocationID.x >= FB_SIZE.x || gl_GlobalInvocationID.y >= FB_SIZE.y) \
+		{ \
+			return; \
+		} \
+	}

+ 18 - 11
shaders/GBufferCommonFrag.glsl

@@ -21,15 +21,20 @@ layout(location = 3) in mediump Vec3 in_bitangent;
 layout(location = 4) in mediump F32 in_distFromTheCamera; // Parallax
 layout(location = 5) in mediump Vec3 in_eyeTangentSpace; // Parallax
 layout(location = 6) in mediump Vec3 in_normalTangentSpace; // Parallax
+
+#	if VELOCITY
+layout(location = 7) in mediump Vec2 in_velocity; // Velocity
+#	endif
 #endif // PASS == PASS_GB_FS
 
 //
 // Output
 //
 #if PASS == PASS_GB_FS || PASS == PASS_EZ
-layout(location = 0) out Vec4 out_msRt0;
-layout(location = 1) out Vec4 out_msRt1;
-layout(location = 2) out Vec4 out_msRt2;
+layout(location = 0) out Vec4 out_gbuffer0;
+layout(location = 1) out Vec4 out_gbuffer1;
+layout(location = 2) out Vec4 out_gbuffer2;
+layout(location = 3) out Vec2 out_gbuffer3;
 #endif
 
 //
@@ -136,13 +141,14 @@ Vec2 computeTextureCoordParallax(in sampler2D heightMap, in Vec2 uv, in F32 heig
 }
 
 // Write the data to FAIs
-void writeRts(in Vec3 diffColor, // from 0 to 1
-	in Vec3 normal,
-	in Vec3 specularColor,
-	in F32 roughness,
-	in F32 subsurface,
-	in Vec3 emission,
-	in F32 metallic)
+void writeRts(Vec3 diffColor,
+	Vec3 normal,
+	Vec3 specularColor,
+	F32 roughness,
+	F32 subsurface,
+	Vec3 emission,
+	F32 metallic,
+	Vec2 velocity)
 {
 	GbufferInfo g;
 	g.m_diffuse = diffColor;
@@ -152,6 +158,7 @@ void writeRts(in Vec3 diffColor, // from 0 to 1
 	g.m_subsurface = subsurface;
 	g.m_emission = (emission.r + emission.g + emission.b) / 3.0;
 	g.m_metallic = metallic;
-	writeGBuffer(g, out_msRt0, out_msRt1, out_msRt2);
+	g.m_velocity = velocity;
+	writeGBuffer(g, out_gbuffer0, out_gbuffer1, out_gbuffer2, out_gbuffer3);
 }
 #endif // PASS == PASS_GB_FS

+ 14 - 0
shaders/GBufferCommonVert.glsl

@@ -50,6 +50,10 @@ layout(location = 3) out mediump Vec3 out_bitangent;
 layout(location = 4) out mediump F32 out_distFromTheCamera; // Parallax
 layout(location = 5) out mediump Vec3 out_eyeTangentSpace; // Parallax
 layout(location = 6) out mediump Vec3 out_normalTangentSpace; // Parallax
+
+#	if VELOCITY
+layout(location = 7) out mediump Vec2 out_velocity; // Velocity
+#	endif
 #endif
 
 //
@@ -131,3 +135,13 @@ void skinning()
 #	endif
 }
 #endif
+
+#if VELOCITY && PASS == PASS_GB_FS
+void velocity(Mat4 prevMvp)
+{
+	Vec4 v4 = prevMvp * Vec4(g_position, 1.0);
+	Vec2 prevNdc = v4.xy / v4.w;
+
+	out_velocity = NDC_TO_UV(prevNdc);
+}
+#endif

+ 17 - 4
shaders/GBufferGeneric.glslp

@@ -14,10 +14,12 @@
 #pragma anki mutator PARALLAX 0 1
 #pragma anki mutator EMISSIVE_TEX 0 1
 #pragma anki mutator BONES 0 1
+#pragma anki mutator VELOCITY 0 1
 
 #pragma anki input instanced Mat4 mvp
 #pragma anki input instanced Mat3 rotationMat "PASS == 0"
 #pragma anki input instanced Mat4 modelViewMat "PASS == 0 && PARALLAX == 1"
+#pragma anki input instanced Mat4 prevMvp "PASS == 0 && VELOCITY == 1"
 
 #pragma anki input const Vec3 diffColor "DIFFUSE_TEX == 0 && PASS == 0"
 #pragma anki input const Vec3 specColor "SPECULAR_TEX == 0 && PASS == 0"
@@ -49,6 +51,10 @@ void main()
 #	if PARALLAX
 	parallax(modelViewMat);
 #	endif
+
+#	if VELOCITY
+	velocity(prevMvp);
+#	endif
 #else
 	gl_Position = mvp * Vec4(g_position, 1.0);
 #endif
@@ -93,11 +99,18 @@ void main()
 	Vec3 emission = texture(emissiveTex, uv).rgb;
 #	endif
 
-	writeRts(diffColor, normal, specColor, roughness, subsurface, emission, metallic);
+#	if VELOCITY
+	Vec2 velocity = in_velocity;
+#	else
+	Vec2 velocity = Vec2(-1.0);
+#	endif
+
+	writeRts(diffColor, normal, specColor, roughness, subsurface, emission, metallic, velocity);
 #elif PASS == PASS_EZ
-	out_msRt0 = Vec4(0.0);
-	out_msRt1 = Vec4(0.0);
-	out_msRt2 = Vec4(0.0);
+	out_gbuffer0 = Vec4(0.0);
+	out_gbuffer1 = Vec4(0.0);
+	out_gbuffer2 = Vec4(0.0);
+	out_gbuffer3 = Vec2(0.0);
 #endif
 }
 

+ 53 - 0
shaders/MotionBlur.glsl

@@ -0,0 +1,53 @@
+// 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 <shaders/Common.glsl>
+
+// Perform motion blur.
+Vec3 motionBlur(sampler2D velocityTex,
+	sampler2D toBlurTex,
+	sampler2D depthTex,
+	Vec2 nowUv,
+	Mat4 prevViewProjMatMulInvViewProjMat,
+	U32 maxSamples)
+{
+	// Compute previous UV
+	Vec2 pastUv = textureLod(velocityTex, nowUv, 0.0).rg;
+
+	ANKI_BRANCH if(pastUv.x < 0.0)
+	{
+		F32 depth = textureLod(depthTex, nowUv, 0.0).r;
+
+		Vec4 v4 = prevViewProjMatMulInvViewProjMat * Vec4(UV_TO_NDC(nowUv), depth, 1.0);
+		pastUv = NDC_TO_UV(v4.xy / v4.w);
+	}
+
+	// March direction
+	Vec2 dir = pastUv - nowUv;
+
+	Vec2 slopes = abs(dir);
+
+	// Compute the sample count
+	Vec2 sampleCount2D = slopes * Vec2(FB_SIZE);
+	F32 sampleCountf = max(sampleCount2D.x, sampleCount2D.y);
+	sampleCountf = clamp(sampleCountf, 1.0, F32(maxSamples));
+	sampleCountf = round(sampleCountf);
+
+	// Loop
+	Vec3 outColor = Vec3(0.0);
+	ANKI_LOOP for(F32 s = 0.0; s < sampleCountf; s += 1.0)
+	{
+		F32 f = s / sampleCountf;
+		Vec2 sampleUv = nowUv + dir * f;
+
+		outColor += textureLod(toBlurTex, sampleUv, 0.0).rgb;
+	}
+
+	outColor /= sampleCountf;
+
+	return outColor;
+}

+ 7 - 2
shaders/Pack.glsl

@@ -135,16 +135,19 @@ struct GbufferInfo
 	F32 m_metallic;
 	F32 m_subsurface;
 	F32 m_emission;
+	Vec2 m_velocity;
 };
 
 // Populate the G buffer
-void writeGBuffer(GbufferInfo g, out Vec4 rt0, out Vec4 rt1, out Vec4 rt2)
+void writeGBuffer(GbufferInfo g, out Vec4 rt0, out Vec4 rt1, out Vec4 rt2, out Vec2 rt3)
 {
 	rt0 = Vec4(g.m_diffuse, g.m_subsurface);
 	rt1 = Vec4(g.m_roughness, g.m_metallic, g.m_specular.x, 0.0);
 
 	Vec3 encNorm = signedOctEncode(g.m_normal);
 	rt2 = Vec4(encNorm.xy, g.m_emission / MAX_EMISSION, encNorm.z);
+
+	rt3 = g.m_velocity;
 }
 
 // Read from G-buffer
@@ -161,7 +164,7 @@ F32 readRoughnessFromGBuffer(sampler2D rt1, Vec2 uv)
 	return r;
 }
 
-// Read from the G buffer
+// Read part of the G-buffer
 void readGBuffer(sampler2D rt0, sampler2D rt1, sampler2D rt2, Vec2 uv, F32 lod, out GbufferInfo g)
 {
 	Vec4 comp = textureLod(rt0, uv, 0.0);
@@ -177,6 +180,8 @@ void readGBuffer(sampler2D rt0, sampler2D rt1, sampler2D rt2, Vec2 uv, F32 lod,
 	g.m_normal = signedOctDecode(comp.xyw);
 	g.m_emission = comp.z * MAX_EMISSION;
 
+	g.m_velocity = Vec2(FLT_MAX); // Put something random
+
 	// Fix roughness
 	g.m_roughness = g.m_roughness * (1.0 - MIN_ROUGHNESS) + MIN_ROUGHNESS;
 

+ 2 - 2
src/anki/core/Config.cpp

@@ -31,11 +31,11 @@ Config::Config()
 	newOption("r.bloom.threshold", 2.5);
 	newOption("r.bloom.scale", 2.5);
 
-	newOption("r.finalComposite.sharpen", false);
-
 	newOption("r.indirect.reflectionResolution", 128);
 	newOption("r.indirect.maxSimultaneousProbeCount", 32);
 
+	newOption("r.motionBlur.maxSamples", 32);
+
 	newOption("r.dbg.enabled", false);
 
 	// Scene

+ 10 - 0
src/anki/gr/gl/Common.cpp

@@ -362,6 +362,16 @@ void convertTextureInformation(
 		internalFormat = GL_R16;
 		type = GL_UNSIGNED_SHORT;
 		break;
+	case Format::R16G16_UNORM:
+		format = GL_RG;
+		internalFormat = GL_RG16;
+		type = GL_UNSIGNED_SHORT;
+		break;
+	case Format::R16G16_SNORM:
+		format = GL_RG;
+		internalFormat = GL_RG16_SNORM;
+		type = GL_SHORT;
+		break;
 	case Format::R16G16B16_SFLOAT:
 		format = GL_RGB;
 		internalFormat = GL_RGB16F;

+ 1 - 1
src/anki/renderer/Common.cpp

@@ -9,6 +9,6 @@ namespace anki
 {
 
 const Array<Format, GBUFFER_COLOR_ATTACHMENT_COUNT> MS_COLOR_ATTACHMENT_PIXEL_FORMATS = {
-	{Format::R8G8B8A8_UNORM, Format::R8G8B8A8_UNORM, Format::A2B10G10R10_UNORM_PACK32}};
+	{Format::R8G8B8A8_UNORM, Format::R8G8B8A8_UNORM, Format::A2B10G10R10_UNORM_PACK32, Format::R16G16_SNORM}};
 
 } // end namespace anki

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

@@ -84,7 +84,7 @@ inline void computeLinearizeDepthOptimal(F32 near, F32 far, F32& a, F32& b)
 	b = far / near;
 }
 
-const U GBUFFER_COLOR_ATTACHMENT_COUNT = 3;
+const U GBUFFER_COLOR_ATTACHMENT_COUNT = 4;
 
 /// Downsample and blur down to a texture with size DOWNSCALE_BLUR_DOWN_TO
 const U DOWNSCALE_BLUR_DOWN_TO = 32;

+ 1 - 1
src/anki/renderer/Dbg.cpp

@@ -76,7 +76,7 @@ void Dbg::run(RenderPassWorkContext& rgraphCtx, const RenderingContext& ctx)
 	dctx.m_cameraTransform = ctx.m_renderQueue->m_viewMatrix.getInverse();
 	dctx.m_stagingGpuAllocator = &m_r->getStagingGpuMemoryManager();
 	dctx.m_commandBuffer = cmdb;
-	dctx.m_key = RenderingKey(Pass::GB_FS, 0, 1);
+	dctx.m_key = RenderingKey(Pass::GB_FS, 0, 1, false, false);
 	dctx.m_debugDraw = true;
 	dctx.m_debugDrawFlags = m_debugDrawFlags;
 

+ 3 - 1
src/anki/renderer/Drawer.cpp

@@ -41,6 +41,7 @@ RenderableDrawer::~RenderableDrawer()
 void RenderableDrawer::drawRange(Pass pass,
 	const Mat4& viewMat,
 	const Mat4& viewProjMat,
+	const Mat4& prevViewProjMat,
 	CommandBufferPtr cmdb,
 	const RenderableQueueElement* begin,
 	const RenderableQueueElement* end)
@@ -51,10 +52,11 @@ void RenderableDrawer::drawRange(Pass pass,
 	ctx.m_queueCtx.m_viewMatrix = viewMat;
 	ctx.m_queueCtx.m_viewProjectionMatrix = viewProjMat;
 	ctx.m_queueCtx.m_projectionMatrix = Mat4::getIdentity(); // TODO
+	ctx.m_queueCtx.m_previousViewProjectionMatrix = prevViewProjMat;
 	ctx.m_queueCtx.m_cameraTransform = ctx.m_queueCtx.m_viewMatrix.getInverse();
 	ctx.m_queueCtx.m_stagingGpuAllocator = &m_r->getStagingGpuMemoryManager();
 	ctx.m_queueCtx.m_commandBuffer = cmdb;
-	ctx.m_queueCtx.m_key = RenderingKey(pass, 0, 1);
+	ctx.m_queueCtx.m_key = RenderingKey(pass, 0, 1, false, false);
 	ctx.m_queueCtx.m_debugDraw = false;
 
 	for(; begin != end; ++begin)

+ 1 - 0
src/anki/renderer/Drawer.h

@@ -35,6 +35,7 @@ public:
 	void drawRange(Pass pass,
 		const Mat4& viewMat,
 		const Mat4& viewProjMat,
+		const Mat4& prevViewProjMat,
 		CommandBufferPtr cmdb,
 		const RenderableQueueElement* begin,
 		const RenderableQueueElement* end);

+ 25 - 11
src/anki/renderer/FinalComposite.cpp

@@ -35,7 +35,6 @@ Error FinalComposite::initInternal(const ConfigSet& config)
 	ANKI_ASSERT("Initializing PPS");
 
 	ANKI_CHECK(loadColorGradingTexture("engine_data/DefaultLut.ankitex"));
-	m_sharpenEnabled = config.getNumber("r.finalComposite.sharpen");
 
 	m_fbDescr.m_colorAttachmentCount = 1;
 	m_fbDescr.m_colorAttachments[0].m_loadOperation = AttachmentLoadOperation::DONT_CARE;
@@ -46,11 +45,8 @@ Error FinalComposite::initInternal(const ConfigSet& config)
 	// Progs
 	ANKI_CHECK(getResourceManager().loadResource("shaders/FinalComposite.glslp", m_prog));
 
-	ShaderProgramResourceMutationInitList<4> mutations(m_prog);
-	mutations.add("BLUE_NOISE", 1)
-		.add("SHARPEN_ENABLED", m_sharpenEnabled)
-		.add("BLOOM_ENABLED", 1)
-		.add("DBG_ENABLED", 0);
+	ShaderProgramResourceMutationInitList<3> mutations(m_prog);
+	mutations.add("BLUE_NOISE", 1).add("BLOOM_ENABLED", 1).add("DBG_ENABLED", 0);
 
 	ShaderProgramResourceConstantValueInitList<2> consts(m_prog);
 	consts.add("LUT_SIZE", U32(LUT_SIZE)).add("FB_SIZE", UVec2(m_r->getWidth(), m_r->getHeight()));
@@ -59,7 +55,7 @@ Error FinalComposite::initInternal(const ConfigSet& config)
 	m_prog->getOrCreateVariant(mutations.get(), consts.get(), variant);
 	m_grProgs[0] = variant->getProgram();
 
-	mutations[3].m_value = 1;
+	mutations[2].m_value = 1;
 	m_prog->getOrCreateVariant(mutations.get(), consts.get(), variant);
 	m_grProgs[1] = variant->getProgram();
 
@@ -93,6 +89,8 @@ void FinalComposite::run(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 	CommandBufferPtr& cmdb = rgraphCtx.m_commandBuffer;
 	const Bool dbgEnabled = m_r->getDbg().getEnabled();
 
+	cmdb->bindShaderProgram(m_grProgs[dbgEnabled]);
+
 	// Bind stuff
 	rgraphCtx.bindColorTextureAndSampler(0, 0, m_r->getTemporalAA().getRt(), m_r->getLinearSampler());
 
@@ -101,19 +99,32 @@ void FinalComposite::run(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 		0, 2, m_lut->getGrTextureView(), m_r->getLinearSampler(), TextureUsageBit::SAMPLED_FRAGMENT);
 	cmdb->bindTextureAndSampler(
 		0, 3, m_blueNoise->getGrTextureView(), m_blueNoise->getSampler(), TextureUsageBit::SAMPLED_FRAGMENT);
+	rgraphCtx.bindColorTextureAndSampler(0, 4, m_r->getGBuffer().getColorRt(3), m_r->getNearestSampler());
+	rgraphCtx.bindTextureAndSampler(0,
+		5,
+		m_r->getGBuffer().getDepthRt(),
+		TextureSubresourceInfo(DepthStencilAspectBit::DEPTH),
+		m_r->getNearestSampler());
+
 	if(dbgEnabled)
 	{
-		rgraphCtx.bindColorTextureAndSampler(0, 5, m_r->getDbg().getRt(), m_r->getLinearSampler());
+		rgraphCtx.bindColorTextureAndSampler(0, 6, m_r->getDbg().getRt(), m_r->getLinearSampler());
 	}
 
 	rgraphCtx.bindUniformBuffer(0, 1, m_r->getTonemapping().getAverageLuminanceBuffer());
 
-	Vec4* uniforms = allocateAndBindUniforms<Vec4*>(sizeof(Vec4), cmdb, 0, 0);
-	uniforms->x() = F32(m_r->getFrameCount() % m_blueNoise->getLayerCount());
+	struct PushConsts
+	{
+		Vec4 m_blueNoiseLayerPad3;
+		Mat4 m_prevViewProjMatMulInvViewProjMat;
+	} pconsts;
+	pconsts.m_blueNoiseLayerPad3.x() = F32(m_r->getFrameCount() % m_blueNoise->getLayerCount());
+	pconsts.m_prevViewProjMatMulInvViewProjMat = ctx.m_matrices.m_jitter * ctx.m_prevMatrices.m_viewProjection
+												 * ctx.m_matrices.m_viewProjectionJitter.getInverse();
+	cmdb->setPushConstants(&pconsts, sizeof(pconsts));
 
 	cmdb->setViewport(0, 0, ctx.m_outRenderTargetWidth, ctx.m_outRenderTargetHeight);
 
-	cmdb->bindShaderProgram(m_grProgs[dbgEnabled]);
 	drawQuad(cmdb);
 
 	// Draw UI
@@ -141,6 +152,9 @@ void FinalComposite::populateRenderGraph(RenderingContext& ctx)
 
 	pass.newConsumer({m_r->getTemporalAA().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 	pass.newConsumer({m_r->getBloom().getRt(), TextureUsageBit::SAMPLED_FRAGMENT});
+
+	pass.newConsumer({m_r->getGBuffer().getColorRt(3), TextureUsageBit::SAMPLED_FRAGMENT});
+	pass.newConsumer({m_r->getGBuffer().getDepthRt(), TextureUsageBit::SAMPLED_FRAGMENT});
 }
 
 } // end namespace anki

+ 0 - 2
src/anki/renderer/FinalComposite.h

@@ -43,8 +43,6 @@ private:
 	TextureResourcePtr m_lut; ///< Color grading lookup texture.
 	TextureResourcePtr m_blueNoise;
 
-	Bool8 m_sharpenEnabled = false;
-
 	class
 	{
 	public:

+ 3 - 2
src/anki/renderer/ForwardShading.cpp

@@ -135,8 +135,9 @@ void ForwardShading::run(RenderingContext& ctx, RenderPassWorkContext& rgraphCtx
 
 		// Start drawing
 		m_r->getSceneDrawer().drawRange(Pass::GB_FS,
-			ctx.m_renderQueue->m_viewMatrix,
-			ctx.m_viewProjMatJitter,
+			ctx.m_matrices.m_view,
+			ctx.m_matrices.m_viewProjectionJitter,
+			ctx.m_prevMatrices.m_viewProjectionJitter,
 			cmdb,
 			ctx.m_renderQueue->m_forwardShadingRenderables.getBegin() + start,
 			ctx.m_renderQueue->m_forwardShadingRenderables.getBegin() + end);

+ 13 - 11
src/anki/renderer/GBuffer.cpp

@@ -39,7 +39,8 @@ Error GBuffer::initInternal(const ConfigSet& initializer)
 		m_r->getWidth(), m_r->getHeight(), GBUFFER_DEPTH_ATTACHMENT_PIXEL_FORMAT, "GBuffer depth");
 	m_depthRtDescr.bake();
 
-	static const char* rtNames[GBUFFER_COLOR_ATTACHMENT_COUNT] = {"GBuffer rt0", "GBuffer rt1", "GBuffer rt2"};
+	static const Array<const char*, GBUFFER_COLOR_ATTACHMENT_COUNT> rtNames = {
+		{"GBuffer rt0", "GBuffer rt1", "GBuffer rt2", "GBuffer rt3"}};
 	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
 	{
 		m_colorRtDescrs[i] = m_r->create2DRenderTargetDescription(
@@ -54,12 +55,11 @@ Error GBuffer::initInternal(const ConfigSet& initializer)
 #endif
 
 	m_fbDescr.m_colorAttachmentCount = GBUFFER_COLOR_ATTACHMENT_COUNT;
-	m_fbDescr.m_colorAttachments[0].m_loadOperation = loadop;
-	m_fbDescr.m_colorAttachments[0].m_clearValue.m_colorf = {{1.0, 0.0, 0.0, 0.0}};
-	m_fbDescr.m_colorAttachments[1].m_loadOperation = loadop;
-	m_fbDescr.m_colorAttachments[1].m_clearValue.m_colorf = {{0.0, 1.0, 0.0, 0.0}};
-	m_fbDescr.m_colorAttachments[2].m_loadOperation = loadop;
-	m_fbDescr.m_colorAttachments[2].m_clearValue.m_colorf = {{0.0, 0.0, 1.0, 0.0}};
+	for(U i = 0; i < GBUFFER_COLOR_ATTACHMENT_COUNT; ++i)
+	{
+		m_fbDescr.m_colorAttachments[i].m_loadOperation = loadop;
+		m_fbDescr.m_colorAttachments[i].m_clearValue.m_colorf = {{1.0, 0.0, 1.0, 0.0}};
+	}
 	m_fbDescr.m_depthStencilAttachment.m_loadOperation = AttachmentLoadOperation::CLEAR;
 	m_fbDescr.m_depthStencilAttachment.m_clearValue.m_depthStencil.m_depth = 1.0;
 	m_fbDescr.m_depthStencilAttachment.m_aspect = DepthStencilAspectBit::DEPTH;
@@ -103,8 +103,9 @@ void GBuffer::runInThread(const RenderingContext& ctx, RenderPassWorkContext& rg
 
 		ANKI_ASSERT(earlyZStart < earlyZEnd && earlyZEnd <= I32(earlyZCount));
 		m_r->getSceneDrawer().drawRange(Pass::EZ,
-			ctx.m_renderQueue->m_viewMatrix,
-			ctx.m_viewProjMatJitter,
+			ctx.m_matrices.m_view,
+			ctx.m_matrices.m_viewProjectionJitter,
+			ctx.m_prevMatrices.m_viewProjectionJitter,
 			cmdb,
 			ctx.m_renderQueue->m_earlyZRenderables.getBegin() + earlyZStart,
 			ctx.m_renderQueue->m_earlyZRenderables.getBegin() + earlyZEnd);
@@ -126,8 +127,9 @@ void GBuffer::runInThread(const RenderingContext& ctx, RenderPassWorkContext& rg
 
 		ANKI_ASSERT(colorStart < colorEnd && colorEnd <= I32(ctx.m_renderQueue->m_renderables.getSize()));
 		m_r->getSceneDrawer().drawRange(Pass::GB_FS,
-			ctx.m_renderQueue->m_viewMatrix,
-			ctx.m_viewProjMatJitter,
+			ctx.m_matrices.m_view,
+			ctx.m_matrices.m_viewProjectionJitter,
+			ctx.m_prevMatrices.m_viewProjectionJitter,
 			cmdb,
 			ctx.m_renderQueue->m_renderables.getBegin() + colorStart,
 			ctx.m_renderQueue->m_renderables.getBegin() + colorEnd);

+ 3 - 2
src/anki/renderer/Indirect.cpp

@@ -350,6 +350,7 @@ void Indirect::runGBuffer(CommandBufferPtr& cmdb)
 			m_r->getSceneDrawer().drawRange(Pass::GB_FS,
 				rqueue.m_viewMatrix,
 				rqueue.m_viewProjectionMatrix,
+				Mat4::getIdentity(), // Don't care about prev mats
 				cmdb,
 				rqueue.m_renderables.getBegin(),
 				rqueue.m_renderables.getEnd());
@@ -380,8 +381,8 @@ void Indirect::runLightShading(U32 faceIdx, RenderPassWorkContext& rgraphCtx)
 	rgraphCtx.bindColorTextureAndSampler(
 		GBUFFER_RT2_BINDING.x(), GBUFFER_RT2_BINDING.y(), m_ctx.m_gbufferColorRts[2], m_r->getNearestSampler());
 
-	rgraphCtx.bindTextureAndSampler(0,
-		GBUFFER_COLOR_ATTACHMENT_COUNT,
+	rgraphCtx.bindTextureAndSampler(GBUFFER_DEPTH_BINDING.x(),
+		GBUFFER_DEPTH_BINDING.y(),
 		m_ctx.m_gbufferDepthRt,
 		TextureSubresourceInfo(DepthStencilAspectBit::DEPTH),
 		m_r->getNearestSampler());

+ 1 - 1
src/anki/renderer/LensFlare.cpp

@@ -96,7 +96,7 @@ void LensFlare::updateIndirectInfo(const RenderingContext& ctx, RenderPassWorkCo
 
 	// Write flare info
 	Vec4* flarePositions = allocateAndBindStorage<Vec4*>(sizeof(Mat4) + count * sizeof(Vec4), cmdb, 0, 0);
-	*reinterpret_cast<Mat4*>(flarePositions) = ctx.m_viewProjMatJitter;
+	*reinterpret_cast<Mat4*>(flarePositions) = ctx.m_matrices.m_viewProjectionJitter;
 	flarePositions += 4;
 
 	for(U i = 0; i < count; ++i)

+ 7 - 6
src/anki/renderer/LightShading.cpp

@@ -265,15 +265,16 @@ void LightShading::updateCommonBlock(RenderingContext& ctx)
 	blk->m_viewMat = ctx.m_renderQueue->m_viewMatrix;
 	blk->m_invViewMat = ctx.m_renderQueue->m_viewMatrix.getInverse();
 
-	blk->m_projMat = ctx.m_projMatJitter;
-	blk->m_invProjMat = ctx.m_projMatJitter.getInverse();
+	blk->m_projMat = ctx.m_matrices.m_projectionJitter;
+	blk->m_invProjMat = ctx.m_matrices.m_projectionJitter.getInverse();
 
-	blk->m_viewProjMat = ctx.m_viewProjMatJitter;
-	blk->m_invViewProjMat = ctx.m_viewProjMatJitter.getInverse();
+	blk->m_viewProjMat = ctx.m_matrices.m_viewProjectionJitter;
+	blk->m_invViewProjMat = ctx.m_matrices.m_viewProjectionJitter.getInverse();
 
-	blk->m_prevViewProjMat = ctx.m_prevViewProjMat;
+	blk->m_prevViewProjMat = ctx.m_prevMatrices.m_viewProjectionJitter;
 
-	blk->m_prevViewProjMatMulInvViewProjMat = ctx.m_prevViewProjMat * ctx.m_viewProjMatJitter.getInverse();
+	blk->m_prevViewProjMatMulInvViewProjMat =
+		ctx.m_prevMatrices.m_viewProjection * ctx.m_matrices.m_viewProjectionJitter.getInverse();
 }
 
 } // end namespace anki

+ 1 - 0
src/anki/renderer/RenderQueue.h

@@ -22,6 +22,7 @@ public:
 	Mat4 m_viewMatrix;
 	Mat4 m_projectionMatrix;
 	Mat4 m_viewProjectionMatrix;
+	Mat4 m_previousViewProjectionMatrix;
 };
 
 /// Some options that can be used as hints in debug drawcalls.

+ 11 - 7
src/anki/renderer/Renderer.cpp

@@ -240,12 +240,16 @@ void Renderer::initJitteredMats()
 
 Error Renderer::populateRenderGraph(RenderingContext& ctx)
 {
-	ctx.m_jitterMat = m_jitteredMats8x[m_frameCount & (8 - 1)];
-	ctx.m_projMatJitter = ctx.m_jitterMat * ctx.m_renderQueue->m_projectionMatrix;
-	ctx.m_viewProjMatJitter = ctx.m_projMatJitter * ctx.m_renderQueue->m_viewMatrix;
+	ctx.m_matrices.m_cameraTransform = ctx.m_renderQueue->m_cameraTransform;
+	ctx.m_matrices.m_view = ctx.m_renderQueue->m_viewMatrix;
+	ctx.m_matrices.m_projection = ctx.m_renderQueue->m_projectionMatrix;
+	ctx.m_matrices.m_viewProjection = ctx.m_renderQueue->m_viewProjectionMatrix;
 
-	ctx.m_prevViewProjMat = m_prevViewProjMat;
-	ctx.m_prevCamTransform = m_prevCamTransform;
+	ctx.m_matrices.m_jitter = m_jitteredMats8x[m_frameCount & (m_jitteredMats8x.getSize() - 1)];
+	ctx.m_matrices.m_projectionJitter = ctx.m_matrices.m_jitter * ctx.m_matrices.m_projection;
+	ctx.m_matrices.m_viewProjectionJitter = ctx.m_matrices.m_projectionJitter * ctx.m_matrices.m_view;
+
+	ctx.m_prevMatrices = m_prevMatrices;
 
 	// Check if resources got loaded
 	if(m_prevLoadRequestCount != m_resources->getLoadingRequestCount()
@@ -298,8 +302,8 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 void Renderer::finalize(const RenderingContext& ctx)
 {
 	++m_frameCount;
-	m_prevViewProjMat = ctx.m_renderQueue->m_viewProjectionMatrix;
-	m_prevCamTransform = ctx.m_renderQueue->m_cameraTransform;
+
+	m_prevMatrices = ctx.m_matrices;
 
 	// Inform about the HiZ map. Do it as late as possible
 	if(ctx.m_renderQueue->m_fillCoverageBufferCallback)

+ 18 - 9
src/anki/renderer/Renderer.h

@@ -26,6 +26,20 @@ class UiManager;
 /// @addtogroup renderer
 /// @{
 
+/// Matrices.
+class RenderingContextMatrices
+{
+public:
+	Mat4 m_cameraTransform = Mat4::getIdentity();
+	Mat4 m_view = Mat4::getIdentity();
+	Mat4 m_projection = Mat4::getIdentity();
+	Mat4 m_viewProjection = Mat4::getIdentity();
+
+	Mat4 m_jitter = Mat4::getIdentity();
+	Mat4 m_projectionJitter = Mat4::getIdentity();
+	Mat4 m_viewProjectionJitter = Mat4::getIdentity();
+};
+
 /// Rendering context.
 class RenderingContext
 {
@@ -36,18 +50,14 @@ public:
 
 	RenderGraphDescription m_renderGraphDescr;
 
+	RenderingContextMatrices m_matrices;
+	RenderingContextMatrices m_prevMatrices;
+
 	/// The render target that the Renderer will populate.
 	RenderTargetHandle m_outRenderTarget;
 	U32 m_outRenderTargetWidth = 0;
 	U32 m_outRenderTargetHeight = 0;
 
-	// Extra matrices
-	Mat4 m_projMatJitter;
-	Mat4 m_viewProjMatJitter;
-	Mat4 m_jitterMat;
-	Mat4 m_prevViewProjMat;
-	Mat4 m_prevCamTransform;
-
 	Vec4 m_unprojParams;
 
 	RenderingContext(const StackAllocator<U8>& alloc)
@@ -374,8 +384,7 @@ private:
 	U64 m_prevAsyncTasksCompleted = 0;
 	Bool m_resourcesDirty = true;
 
-	Mat4 m_prevViewProjMat = Mat4::getIdentity();
-	Mat4 m_prevCamTransform = Mat4::getIdentity();
+	RenderingContextMatrices m_prevMatrices;
 
 	Array<Mat4, 16> m_jitteredMats16x;
 	Array<Mat4, 8> m_jitteredMats8x;

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

@@ -193,6 +193,7 @@ void ShadowMapping::runShadowMapping(RenderPassWorkContext& rgraphCtx)
 		m_r->getSceneDrawer().drawRange(Pass::SM,
 			work.m_renderQueue->m_viewMatrix,
 			work.m_renderQueue->m_viewProjectionMatrix,
+			Mat4::getIdentity(), // Don't care about prev matrices here
 			cmdb,
 			work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement,
 			work.m_renderQueue->m_renderables.getBegin() + work.m_firstRenderableElement

+ 5 - 4
src/anki/renderer/Ssr.cpp

@@ -105,10 +105,11 @@ void Ssr::run(RenderPassWorkContext& rgraphCtx)
 	// Bind uniforms
 	SsrUniforms* unis = allocateAndBindUniforms<SsrUniforms*>(sizeof(SsrUniforms), cmdb, 0, 0);
 	unis->m_nearPad3 = Vec4(ctx.m_renderQueue->m_cameraNear);
-	unis->m_prevViewProjMatMulInvViewProjMat = ctx.m_prevViewProjMat * ctx.m_viewProjMatJitter.getInverse();
-	unis->m_projMat = ctx.m_projMatJitter;
-	unis->m_invProjMat = ctx.m_projMatJitter.getInverse();
-	unis->m_normalMat = Mat3x4(ctx.m_renderQueue->m_viewMatrix.getRotationPart());
+	unis->m_prevViewProjMatMulInvViewProjMat =
+		ctx.m_prevMatrices.m_viewProjection * ctx.m_matrices.m_viewProjectionJitter.getInverse();
+	unis->m_projMat = ctx.m_matrices.m_projectionJitter;
+	unis->m_invProjMat = ctx.m_matrices.m_projectionJitter.getInverse();
+	unis->m_normalMat = Mat3x4(ctx.m_matrices.m_view.getRotationPart());
 
 	// Dispatch
 	const U sizeX = (m_r->getWidth() / SSR_FRACTION + m_workgroupSize[0] - 1) / m_workgroupSize[0];

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

@@ -80,7 +80,8 @@ void TemporalAA::run(const RenderingContext& ctx, RenderPassWorkContext& rgraphC
 	rgraphCtx.bindColorTextureAndSampler(0, 2, m_runCtx.m_historyRt, m_r->getLinearSampler());
 
 	Mat4* unis = allocateAndBindUniforms<Mat4*>(sizeof(Mat4), cmdb, 0, 0);
-	*unis = ctx.m_jitterMat * ctx.m_prevViewProjMat * ctx.m_viewProjMatJitter.getInverse();
+	*unis = ctx.m_matrices.m_jitter * ctx.m_prevMatrices.m_viewProjection
+			* ctx.m_matrices.m_viewProjectionJitter.getInverse();
 
 	rgraphCtx.bindUniformBuffer(0, 1, m_r->getTonemapping().getAverageLuminanceBuffer());
 

+ 39 - 8
src/anki/resource/MaterialResource.cpp

@@ -26,7 +26,8 @@ static const Array<BuiltinVarInfo, U(BuiltinMaterialVariableId::COUNT) - 1> BUIL
 		{"NORMAL_MATRIX", ShaderVariableDataType::MAT3, true},
 		{"ROTATION_MATRIX", ShaderVariableDataType::MAT3, true},
 		{"CAMERA_ROTATION_MATRIX", ShaderVariableDataType::MAT3, false},
-		{"CAMERA_POSITION", ShaderVariableDataType::VEC3, false}}};
+		{"CAMERA_POSITION", ShaderVariableDataType::VEC3, false},
+		{"PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX", ShaderVariableDataType::MAT4, true}}};
 
 MaterialVariable::MaterialVariable()
 {
@@ -117,7 +118,8 @@ Error MaterialResource::parseMutators(XmlElement mutatorsEl)
 			return Error::USER_DATA;
 		}
 
-		if(mutatorName == "INSTANCE_COUNT" || mutatorName == "PASS" || mutatorName == "LOD" || mutatorName == "BONES")
+		if(mutatorName == "INSTANCE_COUNT" || mutatorName == "PASS" || mutatorName == "LOD" || mutatorName == "BONES"
+			|| mutatorName == "VELOCITY")
 		{
 			ANKI_RESOURCE_LOGE("Cannot list builtin mutator \"%s\"", &mutatorName[0]);
 			return Error::USER_DATA;
@@ -246,6 +248,27 @@ Error MaterialResource::parseMutators(XmlElement mutatorsEl)
 		++builtinMutatorCount;
 	}
 
+	m_velocityMutator = m_prog->tryFindMutator("VELOCITY");
+	if(m_velocityMutator)
+	{
+		if(m_velocityMutator->getValues().getSize() != 2)
+		{
+			ANKI_RESOURCE_LOGE("Mutator VELOCITY should have 2 values in the program");
+			return Error::USER_DATA;
+		}
+
+		for(U i = 0; i < m_velocityMutator->getValues().getSize(); ++i)
+		{
+			if(m_velocityMutator->getValues()[i] != I(i))
+			{
+				ANKI_RESOURCE_LOGE("Values of the VELOCITY mutator in the program are not the expected");
+				return Error::USER_DATA;
+			}
+		}
+
+		++builtinMutatorCount;
+	}
+
 	if(m_mutations.getSize() + builtinMutatorCount != m_prog->getMutators().getSize())
 	{
 		ANKI_RESOURCE_LOGE("Some mutatators are unacounted for");
@@ -493,7 +516,7 @@ Error MaterialResource::parseInputs(XmlElement inputsEl, Bool async)
 	return Error::NONE;
 }
 
-const MaterialVariant& MaterialResource::getOrCreateVariant(const RenderingKey& key_, Bool skinned) const
+const MaterialVariant& MaterialResource::getOrCreateVariant(const RenderingKey& key_) const
 {
 	RenderingKey key = key_;
 	key.m_lod = min<U>(m_lodCount - 1, key.m_lod);
@@ -503,18 +526,19 @@ const MaterialVariant& MaterialResource::getOrCreateVariant(const RenderingKey&
 		ANKI_ASSERT(key.m_instanceCount == 1);
 	}
 
-	ANKI_ASSERT(!skinned || m_bonesMutator);
+	ANKI_ASSERT(!key.m_skinned || m_bonesMutator);
+	ANKI_ASSERT(!key.m_velocity || m_velocityMutator);
 
 	key.m_instanceCount = 1 << getInstanceGroupIdx(key.m_instanceCount);
 
-	MaterialVariant& variant =
-		m_variantMatrix[U(key.m_pass)][key.m_lod][getInstanceGroupIdx(key.m_instanceCount)][skinned];
+	MaterialVariant& variant = m_variantMatrix[U(key.m_pass)][key.m_lod][getInstanceGroupIdx(key.m_instanceCount)]
+											  [key.m_skinned][key.m_velocity];
 	LockGuard<SpinLock> lock(m_variantMatrixMtx);
 
 	if(variant.m_variant == nullptr)
 	{
 		const U mutatorCount = m_mutations.getSize() + ((m_instanceMutator) ? 1 : 0) + ((m_passMutator) ? 1 : 0)
-							   + ((m_lodMutator) ? 1 : 0) + ((m_bonesMutator) ? 1 : 0);
+							   + ((m_lodMutator) ? 1 : 0) + ((m_bonesMutator) ? 1 : 0) + ((m_velocityMutator) ? 1 : 0);
 
 		DynamicArrayAuto<ShaderProgramResourceMutation> mutations(getTempAllocator());
 		mutations.create(mutatorCount);
@@ -548,7 +572,14 @@ const MaterialVariant& MaterialResource::getOrCreateVariant(const RenderingKey&
 		if(m_bonesMutator)
 		{
 			mutations[count].m_mutator = m_bonesMutator;
-			mutations[count].m_value = skinned != 0;
+			mutations[count].m_value = key.m_skinned != 0;
+			++count;
+		}
+
+		if(m_velocityMutator)
+		{
+			mutations[count].m_mutator = m_velocityMutator;
+			mutations[count].m_value = key.m_velocity != 0;
 			++count;
 		}
 

+ 4 - 2
src/anki/resource/MaterialResource.h

@@ -40,6 +40,7 @@ enum class BuiltinMaterialVariableId : U8
 	ROTATION_MATRIX,
 	CAMERA_ROTATION_MATRIX,
 	CAMERA_POSITION,
+	PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX,
 	COUNT
 };
 
@@ -236,7 +237,7 @@ public:
 		return m_prog->isInstanced();
 	}
 
-	const MaterialVariant& getOrCreateVariant(const RenderingKey& key, Bool skinned = false) const;
+	const MaterialVariant& getOrCreateVariant(const RenderingKey& key) const;
 
 	const DynamicArray<MaterialVariable>& getVariables() const
 	{
@@ -265,11 +266,12 @@ private:
 	const ShaderProgramResourceMutator* m_passMutator = nullptr;
 	const ShaderProgramResourceMutator* m_instanceMutator = nullptr;
 	const ShaderProgramResourceMutator* m_bonesMutator = nullptr;
+	const ShaderProgramResourceMutator* m_velocityMutator = nullptr;
 
 	DynamicArray<ShaderProgramResourceMutation> m_mutations;
 
 	/// Matrix of variants.
-	mutable Array4d<MaterialVariant, U(Pass::COUNT), MAX_LOD_COUNT, MAX_INSTANCE_GROUPS, 2> m_variantMatrix;
+	mutable Array5d<MaterialVariant, U(Pass::COUNT), MAX_LOD_COUNT, MAX_INSTANCE_GROUPS, 2, 2> m_variantMatrix;
 	mutable SpinLock m_variantMatrixMtx;
 
 	DynamicArray<MaterialVariable> m_vars; ///< Non-const vars.

+ 2 - 1
src/anki/resource/ModelResource.cpp

@@ -35,8 +35,9 @@ void ModelPatch::getRenderingDataSub(
 	{
 		RenderingKey mtlKey = key;
 		mtlKey.m_lod = min<U>(key.m_lod, m_mtl->getLodCount() - 1);
+		mtlKey.m_skinned = m_model->getSkeleton().isCreated();
 
-		const MaterialVariant& variant = m_mtl->getOrCreateVariant(mtlKey, m_model->getSkeleton().isCreated());
+		const MaterialVariant& variant = m_mtl->getOrCreateVariant(mtlKey);
 
 		inf.m_program = variant.getShaderProgram();
 	}

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

@@ -200,7 +200,7 @@ void ParticleEmitterResource::getRenderingInfo(U lod, ShaderProgramPtr& prog) co
 {
 	lod = min<U>(lod, m_lodCount - 1);
 
-	RenderingKey key(Pass::GB_FS, lod, 1);
+	RenderingKey key(Pass::GB_FS, lod, 1, false, false);
 	const MaterialVariant& variant = m_material->getOrCreateVariant(key);
 	prog = variant.getShaderProgram();
 }

+ 18 - 7
src/anki/resource/RenderingKey.h

@@ -6,6 +6,7 @@
 #pragma once
 
 #include <anki/resource/Common.h>
+#include <anki/util/Hash.h>
 
 namespace anki
 {
@@ -26,40 +27,50 @@ public:
 	Pass m_pass;
 	U8 m_lod;
 	U8 m_instanceCount;
+	Bool8 m_skinned;
+	Bool8 m_velocity;
 
-	RenderingKey(Pass pass, U8 lod, U instanceCount)
+	RenderingKey(Pass pass, U8 lod, U instanceCount, Bool8 skinned, Bool8 velocity)
 		: m_pass(pass)
 		, m_lod(lod)
 		, m_instanceCount(instanceCount)
+		, m_skinned(skinned)
+		, m_velocity(velocity)
 	{
 		ANKI_ASSERT(m_instanceCount <= MAX_INSTANCES && m_instanceCount != 0);
 		ANKI_ASSERT(m_lod <= MAX_LOD_COUNT);
 	}
 
 	RenderingKey()
-		: RenderingKey(Pass::GB_FS, 0, 1)
+		: RenderingKey(Pass::GB_FS, 0, 1, false, false)
 	{
 	}
 
 	RenderingKey(const RenderingKey& b)
-		: RenderingKey(b.m_pass, b.m_lod, b.m_instanceCount)
+		: RenderingKey(b.m_pass, b.m_lod, b.m_instanceCount, b.m_skinned, b.m_velocity)
 	{
 	}
+
+	Bool operator==(const RenderingKey& b) const
+	{
+		return m_pass == b.m_pass && m_lod == b.m_lod && m_instanceCount == b.m_instanceCount
+			   && m_skinned == b.m_skinned && m_velocity == b.m_velocity;
+	}
 };
 
 template<>
 constexpr Bool isPacked<RenderingKey>()
 {
-	return sizeof(RenderingKey) == 3;
+	return sizeof(RenderingKey) == 5;
 }
 
 /// The hash function
 class RenderingKeyHasher
 {
 public:
-	PtrSize operator()(const RenderingKey& key) const
+	U64 operator()(const RenderingKey& key) const
 	{
-		return U8(key.m_pass) | (key.m_lod << 8) | (key.m_instanceCount << 16);
+		return computeHash(&key, sizeof(key));
 	}
 };
 
@@ -69,7 +80,7 @@ class RenderingKeyEqual
 public:
 	Bool operator()(const RenderingKey& a, const RenderingKey& b) const
 	{
-		return a.m_pass == b.m_pass && a.m_lod == b.m_lod && a.m_instanceCount == b.m_instanceCount;
+		return a == b;
 	}
 };
 

+ 0 - 1
src/anki/resource/ShaderProgramResource.cpp

@@ -4,7 +4,6 @@
 // http://www.anki3d.org/LICENSE
 
 #include <anki/resource/ShaderProgramResource.h>
-#include <anki/resource/RenderingKey.h>
 #include <anki/resource/ResourceManager.h>
 #include <anki/util/Filesystem.h>
 #include <tinyexpr.h>

+ 0 - 3
src/anki/resource/ShaderProgramResource.h

@@ -16,9 +16,6 @@ struct te_variable;
 namespace anki
 {
 
-// Forward
-class RenderingKey;
-
 /// @addtogroup resource
 /// @{
 

+ 64 - 42
src/anki/scene/ModelNode.cpp

@@ -78,12 +78,38 @@ void ModelPatchNode::drawCallback(RenderQueueDrawContext& ctx, ConstWeakArray<vo
 	// That will not work on multi-draw and instanced at the same time. Make sure that there is no multi-draw anywhere
 	ANKI_ASSERT(self.m_modelPatch->getSubMeshCount() == 1);
 
+	// Transforms
+	Array<Mat4, MAX_INSTANCES> trfs;
+	Array<Mat4, MAX_INSTANCES> prevTrfs;
+	const MoveComponent& movec = self.getParent()->getComponentAt<MoveComponent>(0);
+	trfs[0] = Mat4(movec.getWorldTransform());
+	prevTrfs[0] = Mat4(movec.getPreviousWorldTransform());
+	Bool moved = trfs[0] != prevTrfs[0]; // If at least one is moved then it's dynamic
+	for(U i = 1; i < userData.getSize(); ++i)
+	{
+		const ModelPatchNode& self2 = *static_cast<const ModelPatchNode*>(userData[i]);
+		const MoveComponent& movec = self2.getParent()->getComponentAt<MoveComponent>(0);
+		trfs[i] = Mat4(movec.getWorldTransform());
+		prevTrfs[i] = Mat4(movec.getPreviousWorldTransform());
+
+		moved = moved || (trfs[i] != prevTrfs[i]);
+	}
+
 	ModelRenderingInfo modelInf;
+	ctx.m_key.m_velocity = moved;
 	self.m_modelPatch->getRenderingDataSub(ctx.m_key, WeakArray<U8>(), modelInf);
 
 	// Program
 	cmdb->bindShaderProgram(modelInf.m_program);
 
+	// Uniforms
+	static_cast<const MaterialRenderComponent&>(self.getComponentAt<RenderComponent>(1))
+		.allocateAndSetupUniforms(self.m_modelPatch->getMaterial()->getDescriptorSetIndex(),
+			ctx,
+			ConstWeakArray<Mat4>(&trfs[0], userData.getSize()),
+			ConstWeakArray<Mat4>(&prevTrfs[0], userData.getSize()),
+			*ctx.m_stagingGpuAllocator);
+
 	// Set attributes
 	for(U i = 0; i < modelInf.m_vertexAttributeCount; ++i)
 	{
@@ -102,21 +128,6 @@ void ModelPatchNode::drawCallback(RenderQueueDrawContext& ctx, ConstWeakArray<vo
 	// Index buffer
 	cmdb->bindIndexBuffer(modelInf.m_indexBuffer, 0, IndexType::U16);
 
-	// Uniforms
-	Array<Mat4, MAX_INSTANCES> trfs;
-	trfs[0] = Mat4(self.getParent()->getComponentAt<MoveComponent>(0).getWorldTransform());
-	for(U i = 1; i < userData.getSize(); ++i)
-	{
-		const ModelPatchNode& self2 = *static_cast<const ModelPatchNode*>(userData[i]);
-		trfs[i] = Mat4(self2.getParent()->getComponentAt<MoveComponent>(0).getWorldTransform());
-	}
-
-	static_cast<const MaterialRenderComponent&>(self.getComponentAt<RenderComponent>(1))
-		.allocateAndSetupUniforms(self.m_modelPatch->getMaterial()->getDescriptorSetIndex(),
-			ctx,
-			ConstWeakArray<Mat4>(&trfs[0], userData.getSize()),
-			*ctx.m_stagingGpuAllocator);
-
 	// Draw
 	cmdb->drawElements(PrimitiveTopology::TRIANGLES,
 		modelInf.m_indicesCountArray[0],
@@ -266,12 +277,50 @@ void ModelNode::drawCallback(RenderQueueDrawContext& ctx, ConstWeakArray<void*>
 		// anywhere
 		ANKI_ASSERT(patch->getSubMeshCount() == 1);
 
+		// Transforms
+		Array<Mat4, MAX_INSTANCES> trfs;
+		Array<Mat4, MAX_INSTANCES> prevTrfs;
+		const MoveComponent& movec = self.getComponent<MoveComponent>();
+		trfs[0] = Mat4(movec.getWorldTransform());
+		prevTrfs[0] = Mat4(movec.getPreviousWorldTransform());
+		Bool moved = trfs[0] != prevTrfs[0];
+		for(U i = 1; i < userData.getSize(); ++i)
+		{
+			const ModelNode& self2 = *static_cast<const ModelNode*>(userData[i]);
+			const MoveComponent& movec = self2.getComponent<MoveComponent>();
+			trfs[i] = Mat4(movec.getWorldTransform());
+			prevTrfs[i] = Mat4(movec.getPreviousWorldTransform());
+
+			moved = moved || (trfs[i] != prevTrfs[i]);
+		}
+
+		// Bones storage
+		if(self.m_model->getSkeleton())
+		{
+			const SkinComponent& skinc = self.getComponentAt<SkinComponent>(0);
+			StagingGpuMemoryToken token;
+			void* trfs = ctx.m_stagingGpuAllocator->allocateFrame(
+				skinc.getBoneTransforms().getSize() * sizeof(Mat4), StagingGpuMemoryType::STORAGE, token);
+			memcpy(trfs, &skinc.getBoneTransforms()[0], skinc.getBoneTransforms().getSize() * sizeof(Mat4));
+
+			cmdb->bindStorageBuffer(0, 0, token.m_buffer, token.m_offset, token.m_range);
+		}
+
+		ctx.m_key.m_velocity = moved;
 		ModelRenderingInfo modelInf;
 		patch->getRenderingDataSub(ctx.m_key, WeakArray<U8>(), modelInf);
 
 		// Program
 		cmdb->bindShaderProgram(modelInf.m_program);
 
+		// Uniforms
+		static_cast<const MaterialRenderComponent&>(self.getComponent<RenderComponent>())
+			.allocateAndSetupUniforms(patch->getMaterial()->getDescriptorSetIndex(),
+				ctx,
+				ConstWeakArray<Mat4>(&trfs[0], userData.getSize()),
+				ConstWeakArray<Mat4>(&prevTrfs[0], userData.getSize()),
+				*ctx.m_stagingGpuAllocator);
+
 		// Set attributes
 		for(U i = 0; i < modelInf.m_vertexAttributeCount; ++i)
 		{
@@ -291,33 +340,6 @@ void ModelNode::drawCallback(RenderQueueDrawContext& ctx, ConstWeakArray<void*>
 		// Index buffer
 		cmdb->bindIndexBuffer(modelInf.m_indexBuffer, 0, IndexType::U16);
 
-		// Uniforms
-		Array<Mat4, MAX_INSTANCES> trfs;
-		trfs[0] = Mat4(self.getComponent<MoveComponent>().getWorldTransform());
-		for(U i = 1; i < userData.getSize(); ++i)
-		{
-			const ModelNode& self2 = *static_cast<const ModelNode*>(userData[i]);
-			trfs[i] = Mat4(self2.getComponent<MoveComponent>().getWorldTransform());
-		}
-
-		static_cast<const MaterialRenderComponent&>(self.getComponent<RenderComponent>())
-			.allocateAndSetupUniforms(patch->getMaterial()->getDescriptorSetIndex(),
-				ctx,
-				ConstWeakArray<Mat4>(&trfs[0], userData.getSize()),
-				*ctx.m_stagingGpuAllocator);
-
-		// Bones storage
-		if(self.m_model->getSkeleton())
-		{
-			const SkinComponent& skinc = self.getComponentAt<SkinComponent>(0);
-			StagingGpuMemoryToken token;
-			void* trfs = ctx.m_stagingGpuAllocator->allocateFrame(
-				skinc.getBoneTransforms().getSize() * sizeof(Mat4), StagingGpuMemoryType::STORAGE, token);
-			memcpy(trfs, &skinc.getBoneTransforms()[0], skinc.getBoneTransforms().getSize() * sizeof(Mat4));
-
-			cmdb->bindStorageBuffer(0, 0, token.m_buffer, token.m_offset, token.m_range);
-		}
-
 		// Draw
 		cmdb->drawElements(PrimitiveTopology::TRIANGLES,
 			modelInf.m_indicesCountArray[0],

+ 1 - 0
src/anki/scene/ParticleEmitterNode.cpp

@@ -311,6 +311,7 @@ void ParticleEmitterNode::drawCallback(RenderQueueDrawContext& ctx, ConstWeakArr
 			.allocateAndSetupUniforms(self.m_particleEmitterResource->getMaterial()->getDescriptorSetIndex(),
 				ctx,
 				trf,
+				trf,
 				*ctx.m_stagingGpuAllocator);
 
 		// Draw

+ 27 - 26
src/anki/scene/SceneGraph.cpp

@@ -272,7 +272,8 @@ Error SceneGraph::updateNode(Second prevTime, Second crntTime, SceneNode& node)
 	// Update children
 	if(!err)
 	{
-		err = node.visitChildren([&](SceneNode& child) -> Error { return updateNode(prevTime, crntTime, child); });
+		err = node.visitChildrenMaxDepth(
+			0, [&](SceneNode& child) -> Error { return updateNode(prevTime, crntTime, child); });
 	}
 
 	// Frame update
@@ -288,46 +289,46 @@ Error SceneGraph::updateNodes(UpdateSceneNodesCtx& ctx) const
 {
 	ANKI_TRACE_SCOPED_EVENT(SCENE_NODES_UPDATE);
 
-	IntrusiveList<SceneNode>::Iterator& it = ctx.m_crntNode;
 	IntrusiveList<SceneNode>::ConstIterator end = m_nodes.getEnd();
-	SpinLock& lock = ctx.m_crntNodeLock;
 
 	Bool quit = false;
 	Error err = Error::NONE;
 	while(!quit && !err)
 	{
-		// Fetch a few scene nodes
-		Array<SceneNode*, NODE_UPDATE_BATCH> nodes = {{
-			nullptr,
-		}};
-		lock.lock();
-		for(SceneNode*& node : nodes)
-		{
-			if(it != end)
-			{
-				node = &(*it);
-				++it;
-			}
-		}
-		lock.unlock();
+		// Fetch a batch of scene nodes that don't have parent
+		Array<SceneNode*, NODE_UPDATE_BATCH> batch;
+		U batchSize = 0;
 
-		// Process nodes
-		U count = 0;
-		for(U i = 0; i < nodes.getSize(); ++i)
 		{
-			if(nodes[i])
+			LockGuard<SpinLock> lock(ctx.m_crntNodeLock);
+
+			while(1)
 			{
-				if(nodes[i]->getParent() == nullptr)
+				if(batchSize == batch.getSize())
+				{
+					break;
+				}
+
+				if(ctx.m_crntNode == end)
 				{
-					err = updateNode(ctx.m_prevUpdateTime, ctx.m_crntTime, *nodes[i]);
+					quit = true;
+					break;
 				}
-				++count;
+
+				SceneNode& node = *ctx.m_crntNode;
+				if(node.getParent() == nullptr)
+				{
+					batch[batchSize++] = &node;
+				}
+
+				++ctx.m_crntNode;
 			}
 		}
 
-		if(ANKI_UNLIKELY(count == 0))
+		// Process nodes
+		for(U i = 0; i < batchSize && !err; ++i)
 		{
-			quit = true;
+			err = updateNode(ctx.m_prevUpdateTime, ctx.m_crntTime, *batch[i]);
 		}
 	}
 

+ 1 - 0
src/anki/scene/Visibility.cpp

@@ -36,6 +36,7 @@ void VisibilityContext::submitNewWork(const FrustumComponent& frc, RenderQueue&
 	rqueue.m_viewMatrix = frc.getViewMatrix();
 	rqueue.m_projectionMatrix = frc.getProjectionMatrix();
 	rqueue.m_viewProjectionMatrix = frc.getViewProjectionMatrix();
+	rqueue.m_previousViewProjectionMatrix = frc.getPreviousViewProjectionMatrix();
 	rqueue.m_cameraNear = frc.getFrustum().getNear();
 	rqueue.m_cameraFar = frc.getFrustum().getFar();
 

+ 4 - 3
src/anki/scene/components/FrustumComponent.cpp

@@ -29,22 +29,23 @@ FrustumComponent::~FrustumComponent()
 Error FrustumComponent::update(Second, Second, Bool& updated)
 {
 	updated = false;
+	m_prevViewProjMat = m_viewProjMat;
 
 	if(m_flags.get(SHAPE_MARKED_FOR_UPDATE))
 	{
 		updated = true;
-		m_pm = m_frustum->calculateProjectionMatrix();
+		m_projMat = m_frustum->calculateProjectionMatrix();
 	}
 
 	if(m_flags.get(TRANSFORM_MARKED_FOR_UPDATE))
 	{
 		updated = true;
-		m_vm = Mat4(m_frustum->getTransform().getInverse());
+		m_viewMat = Mat4(m_frustum->getTransform().getInverse());
 	}
 
 	if(updated)
 	{
-		m_vpm = m_pm * m_vm;
+		m_viewProjMat = m_projMat * m_viewMat;
 		m_flags.unset(SHAPE_MARKED_FOR_UPDATE | TRANSFORM_MARKED_FOR_UPDATE);
 	}
 

+ 12 - 6
src/anki/scene/components/FrustumComponent.h

@@ -66,17 +66,22 @@ public:
 
 	const Mat4& getProjectionMatrix() const
 	{
-		return m_pm;
+		return m_projMat;
 	}
 
 	const Mat4& getViewMatrix() const
 	{
-		return m_vm;
+		return m_viewMat;
 	}
 
 	const Mat4& getViewProjectionMatrix() const
 	{
-		return m_vpm;
+		return m_viewProjMat;
+	}
+
+	const Mat4& getPreviousViewProjectionMatrix() const
+	{
+		return m_prevViewProjMat;
 	}
 
 	/// Get the origin for sorting and visibility tests
@@ -185,9 +190,10 @@ private:
 	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(Flags, friend)
 
 	Frustum* m_frustum;
-	Mat4 m_pm = Mat4::getIdentity(); ///< Projection matrix
-	Mat4 m_vm = Mat4::getIdentity(); ///< View matrix
-	Mat4 m_vpm = Mat4::getIdentity(); ///< View projection matrix
+	Mat4 m_projMat = Mat4::getIdentity(); ///< Projection matrix
+	Mat4 m_viewMat = Mat4::getIdentity(); ///< View matrix
+	Mat4 m_viewProjMat = Mat4::getIdentity(); ///< View projection matrix
+	Mat4 m_prevViewProjMat = Mat4::getIdentity();
 
 	BitMask<U16> m_flags;
 

+ 22 - 2
src/anki/scene/components/RenderComponent.cpp

@@ -33,10 +33,14 @@ MaterialRenderComponent::~MaterialRenderComponent()
 	m_vars.destroy(getAllocator());
 }
 
-void MaterialRenderComponent::allocateAndSetupUniforms(
-	U set, const RenderQueueDrawContext& ctx, ConstWeakArray<Mat4> transforms, StagingGpuMemoryManager& alloc) const
+void MaterialRenderComponent::allocateAndSetupUniforms(U set,
+	const RenderQueueDrawContext& ctx,
+	ConstWeakArray<Mat4> transforms,
+	ConstWeakArray<Mat4> prevTransforms,
+	StagingGpuMemoryManager& alloc) const
 {
 	ANKI_ASSERT(transforms.getSize() <= MAX_INSTANCES);
+	ANKI_ASSERT(prevTransforms.getSize() == transforms.getSize());
 
 	const MaterialVariant& variant = m_mtl->getOrCreateVariant(ctx.m_key);
 	const ShaderProgramResourceVariant& progVariant = variant.getShaderProgramResourceVariant();
@@ -196,6 +200,22 @@ void MaterialRenderComponent::allocateAndSetupUniforms(
 				progVariant.writeShaderBlockMemory(progvar, &mvp[0], transforms.getSize(), uniformsBegin, uniformsEnd);
 				break;
 			}
+			case BuiltinMaterialVariableId::PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX:
+			{
+				ANKI_ASSERT(prevTransforms.getSize() > 0);
+
+				DynamicArrayAuto<Mat4> mvp(getFrameAllocator());
+				mvp.create(prevTransforms.getSize());
+
+				for(U i = 0; i < prevTransforms.getSize(); i++)
+				{
+					mvp[i] = ctx.m_previousViewProjectionMatrix * prevTransforms[i];
+				}
+
+				progVariant.writeShaderBlockMemory(
+					progvar, &mvp[0], prevTransforms.getSize(), uniformsBegin, uniformsEnd);
+				break;
+			}
 			case BuiltinMaterialVariableId::MODEL_VIEW_MATRIX:
 			{
 				ANKI_ASSERT(transforms.getSize() > 0);

+ 1 - 0
src/anki/scene/components/RenderComponent.h

@@ -123,6 +123,7 @@ public:
 	void allocateAndSetupUniforms(U set,
 		const RenderQueueDrawContext& ctx,
 		ConstWeakArray<Mat4> transforms,
+		ConstWeakArray<Mat4> prevTransforms,
 		StagingGpuMemoryManager& alloc) const;
 
 private:

+ 4 - 0
src/anki/util/Array.h

@@ -158,6 +158,10 @@ using Array3d = Array<Array<Array<T, K>, J>, I>;
 /// 4D Array. @code Array4d<X, 10, 2, 3, 4> a; @endcode is equivelent to @code X a[10][2][3][4]; @endcode
 template<typename T, PtrSize I, PtrSize J, PtrSize K, PtrSize L>
 using Array4d = Array<Array<Array<Array<T, L>, K>, J>, I>;
+
+/// 5D Array. @code Array5d<X, 10, 2, 3, 4, 5> a; @endcode is equivelent to @code X a[10][2][3][4][5]; @endcode
+template<typename T, PtrSize I, PtrSize J, PtrSize K, PtrSize L, PtrSize M>
+using Array5d = Array<Array<Array<Array<Array<T, M>, L>, K>, J>, I>;
 /// @}
 
 } // end namespace anki

+ 1 - 1
thirdparty

@@ -1 +1 @@
-Subproject commit 9463ab160a76834c4ec4d70ef6949ba35ee06010
+Subproject commit d3db5c96229f84ef75ad4dd783aa684c714e75a2

+ 1 - 0
tools/scene/ExporterMaterial.cpp

@@ -22,6 +22,7 @@ const char* MATERIAL_TEMPLATE = R"(<?xml version="1.0" encoding="UTF-8" ?>
 	
 	<inputs>
 		<input shaderInput="mvp" builtin="MODEL_VIEW_PROJECTION_MATRIX"/>
+		<input shaderInput="prevMvp" builtin="PREVIOUS_MODEL_VIEW_PROJECTION_MATRIX"/>
 		<input shaderInput="rotationMat" builtin="ROTATION_MATRIX"/>
 		%parallaxInput%