diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurDummy.comp b/projects/indirect_dispatch/resources/shaders/motionBlurDummy.comp
new file mode 100644
index 0000000000000000000000000000000000000000..a3a21557cd0c8ebf29fe0c1b3c1475a70d9c3731
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/motionBlurDummy.comp
@@ -0,0 +1,38 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0)                    uniform texture2D   inColor;
+layout(set=0, binding=1)                    uniform texture2D   inMotion;
+layout(set=0, binding=2)                    uniform sampler     textureSampler;
+layout(set=0, binding=3, r11f_g11f_b10f)    uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage))))
+        return;
+   
+    ivec2   textureRes  = textureSize(sampler2D(inColor, textureSampler), 0);
+    ivec2   coord       = ivec2(gl_GlobalInvocationID.xy);
+    vec2    uv          = vec2(coord) / textureRes;
+
+    vec2    motion      = texture(sampler2D(inMotion, textureSampler), uv).rg;
+    float   blurFactor  = 5;
+    motion              *= blurFactor;
+
+    vec3        color       = vec3(0);
+    const int   sampleCount = 16;
+    
+    vec2 uvStart    = uv - motion;
+    vec2 uvEnd      = uv + motion;
+    
+    for(int i = 0; i < sampleCount; i++){
+        vec2 sampleUV   = mix(uvStart, uvEnd, i / float(sampleCount - 1));    
+        color           += texture(sampler2D(inColor, textureSampler), sampleUV).rgb;
+    }
+    
+    color /= sampleCount;
+
+    imageStore(outImage, coord, vec4(color, 0.f));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/motionVector.inc b/projects/indirect_dispatch/resources/shaders/motionVector.inc
new file mode 100644
index 0000000000000000000000000000000000000000..498478cbc38b9666366eaa3d3e1a715dfc30236b
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/motionVector.inc
@@ -0,0 +1,9 @@
+vec2 computeMotionVector(vec4 NDC, vec4 NDCPrevious){
+    vec2 ndc            = NDC.xy            / NDC.w;
+    vec2 ndcPrevious    = NDCPrevious.xy    / NDCPrevious.w;
+
+    vec2 uv         = ndc           * 0.5 + 0.5;
+    vec2 uvPrevious = ndcPrevious   * 0.5 + 0.5;
+
+	return uvPrevious - uv;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/prepass.frag b/projects/indirect_dispatch/resources/shaders/prepass.frag
index e9030883b9db701ae9c8ac34006b52dbcd492c64..ccfc84d982253f7b89551c099a92b5686a811163 100644
--- a/projects/indirect_dispatch/resources/shaders/prepass.frag
+++ b/projects/indirect_dispatch/resources/shaders/prepass.frag
@@ -1,5 +1,8 @@
 #version 450
-#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_separate_shader_objects   : enable
+#extension GL_GOOGLE_include_directive      : enable
+
+#include "motionVector.inc"
 
 layout(location = 0) in vec4 passNDC;
 layout(location = 1) in vec4 passNDCPrevious;
@@ -7,12 +10,5 @@ layout(location = 1) in vec4 passNDCPrevious;
 layout(location = 0) out vec2 outMotion;
 
 void main()	{
-
-    vec2 ndc            = passNDC.xy            / passNDC.w;
-    vec2 ndcPrevious    = passNDCPrevious.xy    / passNDCPrevious.w;
-
-    vec2 uv         = ndc           * 0.5 + 0.5;
-    vec2 uvPrevious = ndcPrevious   * 0.5 + 0.5;
-
-	outMotion = uvPrevious - uv;
+	outMotion = computeMotionVector(passNDC, passNDCPrevious);
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/skyPrepass.frag b/projects/indirect_dispatch/resources/shaders/skyPrepass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..64ec4f18bbcf89153d70019ace570da53d44a505
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/skyPrepass.frag
@@ -0,0 +1,14 @@
+#version 450
+#extension GL_ARB_separate_shader_objects   : enable
+#extension GL_GOOGLE_include_directive      : enable
+
+#include "motionVector.inc"
+
+layout(location = 0) out vec2 outMotion;
+
+layout(location = 0) in vec4 passNDC;
+layout(location = 1) in vec4 passNDCPrevious;
+
+void main()	{
+	outMotion = computeMotionVector(passNDC, passNDCPrevious);
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/skyPrepass.vert b/projects/indirect_dispatch/resources/shaders/skyPrepass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..31b9016a592d097825a09e1daa888cb7b72b2cbc
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/skyPrepass.vert
@@ -0,0 +1,22 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+
+layout( push_constant ) uniform constants{
+    mat4 viewProjection;
+    mat4 viewProjectionPrevious;
+};
+
+layout(location = 0) out vec4 passNDC;
+layout(location = 1) out vec4 passNDCPrevious;
+
+void main()	{
+	gl_Position     = viewProjection * vec4(inPosition, 0.0);
+    gl_Position.w   = gl_Position.z;
+    
+    passNDC         = gl_Position;
+    
+    passNDCPrevious     = viewProjectionPrevious * vec4(inPosition, 0.0);
+    passNDCPrevious.w   = passNDCPrevious.z;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp
index c74ffe2b966c0d0b8aac8b610290a52499c973b8..8ab6bb88255319429be0348bc1da5a6fe92732b0 100644
--- a/projects/indirect_dispatch/src/App.cpp
+++ b/projects/indirect_dispatch/src/App.cpp
@@ -23,18 +23,24 @@ App::App() :
 
 bool App::initialize() {
 
-	if (!loadMeshPass(m_core, &m_meshPassHandles))
+	if (!loadMeshPass(m_core, &m_meshPass))
 		return false;
 
-	if (!loadSkyPass(m_core, &m_skyPassHandles))
+	if (!loadSkyPass(m_core, &m_skyPass))
 		return false;
 
-	if (!loadPrePass(m_core, &m_prePassHandles))
+	if (!loadPrePass(m_core, &m_prePass))
 		false;
 
+	if (!loadSkyPrePass(m_core, &m_skyPrePass))
+		return false;
+
 	if (!loadComputePass(m_core, "resources/shaders/gammaCorrection.comp", &m_gammaCorrectionPass))
 		return false;
 
+	if(!loadComputePass(m_core, "resources/shaders/motionBlurDummy.comp", &m_motionBlurDummyPass))
+		return false;
+
 	if (!loadMesh(m_core, "resources/models/sphere.gltf", & m_sphereMesh))
 		return false;
 
@@ -106,8 +112,17 @@ void App::run() {
 
 		m_core.recordDrawcallsToCmdStream(
 			cmdStream,
-			m_prePassHandles.renderPass,
-			m_prePassHandles.pipeline,
+			m_prePass.renderPass,
+			m_prePass.pipeline,
+			prepassPushConstants,
+			{ sphereDrawcall },
+			prepassRenderTargets);
+
+		// sky prepass
+		m_core.recordDrawcallsToCmdStream(
+			cmdStream,
+			m_skyPrePass.renderPass,
+			m_skyPrePass.pipeline,
 			prepassPushConstants,
 			{ sphereDrawcall },
 			prepassRenderTargets);
@@ -122,8 +137,8 @@ void App::run() {
 
 		m_core.recordDrawcallsToCmdStream(
 			cmdStream,
-			m_meshPassHandles.renderPass,
-			m_meshPassHandles.pipeline,
+			m_meshPass.renderPass,
+			m_meshPass.pipeline,
 			meshPushConstants,
 			{ sphereDrawcall },
 			renderTargets);
@@ -134,14 +149,42 @@ void App::run() {
 
 		m_core.recordDrawcallsToCmdStream(
 			cmdStream,
-			m_skyPassHandles.renderPass,
-			m_skyPassHandles.pipeline,
+			m_skyPass.renderPass,
+			m_skyPass.pipeline,
 			skyPushConstants,
 			{ cubeDrawcall },
 			renderTargets);
 
+		// motion blur
+		vkcv::DescriptorWrites motionBlurDescriptorWrites;
+		motionBlurDescriptorWrites.sampledImageWrites = {
+			vkcv::SampledImageDescriptorWrite(0, m_renderTargets.colorBuffer),
+			vkcv::SampledImageDescriptorWrite(1, m_renderTargets.motionBuffer) };
+		motionBlurDescriptorWrites.samplerWrites = {
+			vkcv::SamplerDescriptorWrite(2, m_linearSampler) };
+		motionBlurDescriptorWrites.storageImageWrites = {
+			vkcv::StorageImageDescriptorWrite(3, m_renderTargets.motionBlurOutput) };
+
+		m_core.writeDescriptorSet(m_motionBlurDummyPass.descriptorSet, motionBlurDescriptorWrites);
+
+		uint32_t fullScreenImageDispatch[3] = {
+			static_cast<uint32_t>((m_windowWidth + 7) / 8),
+			static_cast<uint32_t>((m_windowHeight + 7) / 8),
+			static_cast<uint32_t>(1) };
+
+		m_core.prepareImageForStorage(cmdStream, m_renderTargets.motionBlurOutput);
+		m_core.prepareImageForSampling(cmdStream, m_renderTargets.colorBuffer);
+		m_core.prepareImageForSampling(cmdStream, m_renderTargets.motionBuffer);
+
+		m_core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			m_motionBlurDummyPass.pipeline,
+			fullScreenImageDispatch,
+			{ vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_motionBlurDummyPass.descriptorSet).vulkanHandle) },
+			vkcv::PushConstants(0));
+
 		// gamma correction
-		vkcv::ImageHandle gammaCorrectionInput = m_renderTargets.colorBuffer;
+		vkcv::ImageHandle gammaCorrectionInput = m_renderTargets.motionBlurOutput;
 		if (drawMotionVectors) {
 			gammaCorrectionInput = m_renderTargets.motionBuffer;
 		}
@@ -159,15 +202,10 @@ void App::run() {
 		m_core.prepareImageForSampling(cmdStream, gammaCorrectionInput);
 		m_core.prepareImageForStorage (cmdStream, swapchainInput);
 
-		uint32_t gammaCorrectionDispatch[3] = {
-			static_cast<uint32_t>((m_windowWidth  + 7) / 8),
-			static_cast<uint32_t>((m_windowHeight + 7) / 8),
-			static_cast<uint32_t>(1) };
-
 		m_core.recordComputeDispatchToCmdStream(
 			cmdStream,
 			m_gammaCorrectionPass.pipeline,
-			gammaCorrectionDispatch,
+			fullScreenImageDispatch,
 			{ vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_gammaCorrectionPass.descriptorSet).vulkanHandle) },
 			vkcv::PushConstants(0));
 
diff --git a/projects/indirect_dispatch/src/App.hpp b/projects/indirect_dispatch/src/App.hpp
index 78fe382b5d95db0b3b936df2b97820c4ee07183e..3dc0b908271297dfca22b5ff551c71d0a3ad6fe7 100644
--- a/projects/indirect_dispatch/src/App.hpp
+++ b/projects/indirect_dispatch/src/App.hpp
@@ -21,11 +21,13 @@ private:
 	MeshResources m_sphereMesh;
 	MeshResources m_cubeMesh;
 
-	GraphicPassHandles m_meshPassHandles;
-	GraphicPassHandles m_skyPassHandles;
-	GraphicPassHandles m_prePassHandles;
+	GraphicPassHandles m_meshPass;
+	GraphicPassHandles m_skyPass;
+	GraphicPassHandles m_prePass;
+	GraphicPassHandles m_skyPrePass;
 
 	ComputePassHandles m_gammaCorrectionPass;
+	ComputePassHandles m_motionBlurDummyPass;
 
 	RenderTargets       m_renderTargets;
 	vkcv::SamplerHandle m_linearSampler;
diff --git a/projects/indirect_dispatch/src/AppSetup.cpp b/projects/indirect_dispatch/src/AppSetup.cpp
index f827ae15ebdd57aae67daaea8930e3c8142a4367..d6176c937e2d333c899395421fc6c25ef1226211 100644
--- a/projects/indirect_dispatch/src/AppSetup.cpp
+++ b/projects/indirect_dispatch/src/AppSetup.cpp
@@ -180,6 +180,28 @@ bool loadPrePass(vkcv::Core& core, GraphicPassHandles* outHandles) {
 		outHandles);
 }
 
+bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles) {
+	assert(outHandles);
+
+	vkcv::AttachmentDescription motionAttachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::LOAD,
+		AppConfig::motionBufferFormat);
+
+	vkcv::AttachmentDescription depthAttachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::LOAD,
+		AppConfig::depthBufferFormat);
+
+	return loadGraphicPass(
+		core,
+		"resources/shaders/skyPrepass.vert",
+		"resources/shaders/skyPrepass.frag",
+		vkcv::PassConfig({ motionAttachment, depthAttachment }),
+		vkcv::DepthTest::LessEqual,
+		outHandles);
+}
+
 bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass) {
 
 	assert(outComputePass);
@@ -230,6 +252,14 @@ RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const
 		false,
 		true).getHandle();
 
+	targets.motionBlurOutput = core.createImage(
+		AppConfig::colorBufferFormat,
+		width,
+		height,
+		1,
+		false,
+		true).getHandle();
+
 	targets.motionBuffer = core.createImage(
 		AppConfig::motionBufferFormat,
 		width,
diff --git a/projects/indirect_dispatch/src/AppSetup.hpp b/projects/indirect_dispatch/src/AppSetup.hpp
index aa7be2fd50be2705fb05286cc3cb1b88fb12d09e..0253a35fe73ad9a1c9fb6a4c2b24262214c6ac6d 100644
--- a/projects/indirect_dispatch/src/AppSetup.hpp
+++ b/projects/indirect_dispatch/src/AppSetup.hpp
@@ -4,6 +4,7 @@
 struct RenderTargets {
 	vkcv::ImageHandle depthBuffer;
 	vkcv::ImageHandle colorBuffer;
+	vkcv::ImageHandle motionBlurOutput;
 	vkcv::ImageHandle motionBuffer;
 };
 
@@ -34,9 +35,10 @@ bool loadGraphicPass(
 	const vkcv::DepthTest       depthTest,
 	GraphicPassHandles*         outPassHandles);
 
-bool loadMeshPass(vkcv::Core& core, GraphicPassHandles* outHandles);
-bool loadSkyPass (vkcv::Core& core, GraphicPassHandles* outHandles);
-bool loadPrePass (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadMeshPass  (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadSkyPass   (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadPrePass   (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles);
 
 bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass);