diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 01e59191af54d0562a9b834d52f96050d0499136..8a165adf43561b1204490a12afa00d2a3fabdbf4 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -258,5 +258,6 @@ namespace vkcv
 
 		void submitCommandStream(const CommandStreamHandle handle);
 		void prepareSwapchainImageForPresent(const CommandStreamHandle handle);
+		void prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image);
     };
 }
diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp
index f464997b8985c5750aac34d19f4cd71eb20bd403..7dc6f2200db7efc1abdddab81145daec27540c49 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -27,7 +27,7 @@ namespace vkcv {
             const ShaderProgram&                        shaderProgram,
             uint32_t                                    width,
             uint32_t                                    height,
-            PassHandle                                  &passHandle,
+            const PassHandle                            &passHandle,
             const std::vector<VertexAttribute>          &vertexAttributes,
             const std::vector<vk::DescriptorSetLayout>  &descriptorLayouts,
             bool                                        useDynamicViewport);
diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp
index ef5d1f00ea3eeb97d97d8824439ded1ed326f33c..459125bbd3208ffb40815e1e3fd4c9615ce21724 100644
--- a/include/vkcv/ShaderProgram.hpp
+++ b/include/vkcv/ShaderProgram.hpp
@@ -58,10 +58,12 @@ namespace vkcv {
         void reflectShader(ShaderStage shaderStage);
 
         const VertexLayout &getVertexLayout() const;
+		size_t getPushConstantSize() const;
 
 	private:
         std::unordered_map<ShaderStage, Shader> m_Shaders;
 
         VertexLayout m_VertexLayout;
+		size_t m_pushConstantSize = 0;
 	};
 }
diff --git a/projects/cmd_sync_test/resources/shaders/compile.bat b/projects/cmd_sync_test/resources/shaders/compile.bat
index b4521235c40fe5fb163bab874560c2f219b7517f..516c2f2f78001e1a5d182356e7c3fe82d66a45ee 100644
--- a/projects/cmd_sync_test/resources/shaders/compile.bat
+++ b/projects/cmd_sync_test/resources/shaders/compile.bat
@@ -1,3 +1,5 @@
 %VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
 %VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
+%VULKAN_SDK%\Bin32\glslc.exe shadow.vert -o shadow_vert.spv
+%VULKAN_SDK%\Bin32\glslc.exe shadow.frag -o shadow_frag.spv
 pause
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/frag.spv b/projects/cmd_sync_test/resources/shaders/frag.spv
index 087e4e22fb2fcec27d99b3ff2aa1a705fe755796..ff3110571871d65ce119dc6c5006e7e67aa53546 100644
Binary files a/projects/cmd_sync_test/resources/shaders/frag.spv and b/projects/cmd_sync_test/resources/shaders/frag.spv differ
diff --git a/projects/cmd_sync_test/resources/shaders/shader.frag b/projects/cmd_sync_test/resources/shaders/shader.frag
index 71a1de69c57c0d7b7d4665095410e7acaf8dbd62..95f1b3319e1ca5c7c34ff94e5e7198819c0233c1 100644
--- a/projects/cmd_sync_test/resources/shaders/shader.frag
+++ b/projects/cmd_sync_test/resources/shaders/shader.frag
@@ -3,12 +3,42 @@
 
 layout(location = 0) in vec3 passNormal;
 layout(location = 1) in vec2 passUV;
+layout(location = 2) in vec3 passPos;
 
 layout(location = 0) out vec3 outColor;
 
 layout(set=0, binding=0) uniform texture2D  meshTexture;
 layout(set=0, binding=1) uniform sampler    textureSampler;
+layout(set=0, binding=2) uniform sunBuffer {
+    vec3 L; float padding;
+    mat4 lightMatrix;
+};
+layout(set=0, binding=3) uniform texture2D  shadowMap;
+layout(set=0, binding=4) uniform sampler    shadowMapSampler;
+
+float shadowTest(vec3 worldPos){
+    vec4 lightPos = lightMatrix * vec4(worldPos, 1);
+    lightPos /= lightPos.w;
+    lightPos.xy = lightPos.xy * 0.5 + 0.5;
+    
+    if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){
+        return 1;
+    }
+    
+    lightPos.z = clamp(lightPos.z, 0, 1);
+    
+    float shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy).r;
+    float bias = 0.01f;
+    shadowMapSample += bias;
+    return shadowMapSample < lightPos.z ? 0 : 1;
+}
 
 void main()	{
-	outColor = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
+    vec3 N = normalize(passNormal);
+    vec3 sunColor = vec3(1);
+    vec3 sun = sunColor * clamp(dot(N, L), 0, 1);
+    sun *= shadowTest(passPos);
+    vec3 ambient = vec3(0.1);
+    vec3 albedo = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
+	outColor = albedo * (sun + ambient);
 }
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shader.vert b/projects/cmd_sync_test/resources/shaders/shader.vert
index 76855152253b48b7400f016d063ed4f0e507435e..0ab82c203806356d0f35dc52c0a6988b286d90d1 100644
--- a/projects/cmd_sync_test/resources/shaders/shader.vert
+++ b/projects/cmd_sync_test/resources/shaders/shader.vert
@@ -7,13 +7,16 @@ layout(location = 2) in vec2 inUV;
 
 layout(location = 0) out vec3 passNormal;
 layout(location = 1) out vec2 passUV;
+layout(location = 2) out vec3 passPos;
 
 layout( push_constant ) uniform constants{
     mat4 mvp;
+    mat4 model;
 };
 
 void main()	{
 	gl_Position = mvp * vec4(inPosition, 1.0);
 	passNormal  = inNormal;
     passUV      = inUV;
+    passPos     = (model * vec4(inPosition, 1)).xyz;
 }
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shadow.frag b/projects/cmd_sync_test/resources/shaders/shadow.frag
new file mode 100644
index 0000000000000000000000000000000000000000..848f853f556660b4900b5db7fb6fc98d57c1cd5b
--- /dev/null
+++ b/projects/cmd_sync_test/resources/shaders/shadow.frag
@@ -0,0 +1,6 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+void main()	{
+
+}
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shadow.vert b/projects/cmd_sync_test/resources/shaders/shadow.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8
--- /dev/null
+++ b/projects/cmd_sync_test/resources/shaders/shadow.vert
@@ -0,0 +1,12 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+}
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shadow_frag.spv b/projects/cmd_sync_test/resources/shaders/shadow_frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6be3bd2518a3b1f234e39aea2503ba86cfb3314b
Binary files /dev/null and b/projects/cmd_sync_test/resources/shaders/shadow_frag.spv differ
diff --git a/projects/cmd_sync_test/resources/shaders/shadow_vert.spv b/projects/cmd_sync_test/resources/shaders/shadow_vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..afaa0824ee9be2c22209d611943c6512587dce24
Binary files /dev/null and b/projects/cmd_sync_test/resources/shaders/shadow_vert.spv differ
diff --git a/projects/cmd_sync_test/resources/shaders/vert.spv b/projects/cmd_sync_test/resources/shaders/vert.spv
index 374c023e14b351eb43cbcda5951cbb8b3d6f96a1..5e514eef5983927316465679af5461f507497130 100644
Binary files a/projects/cmd_sync_test/resources/shaders/vert.spv and b/projects/cmd_sync_test/resources/shaders/vert.spv differ
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
index c3455be6dadb8f8c0ffd34e52080c80570b3ee58..d28adb371fd7e58487abae3f8d39ed5507481f92 100644
--- a/projects/cmd_sync_test/src/main.cpp
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -20,6 +20,7 @@ int main(int argc, const char** argv) {
 
 	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
 	cameraManager.getCamera().setPosition(glm::vec3(0.f, 0.f, 3.f));
+	cameraManager.getCamera().setNearFar(0.1, 30);
 
 	window.initEvents();
 
@@ -103,8 +104,11 @@ int main(int argc, const char** argv) {
 	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
 	
 	std::vector<vkcv::DescriptorBinding> descriptorBindings = {
-		vkcv::DescriptorBinding(vkcv::DescriptorType::IMAGE_SAMPLED,	1, vkcv::ShaderStage::FRAGMENT),
-		vkcv::DescriptorBinding(vkcv::DescriptorType::SAMPLER,			1, vkcv::ShaderStage::FRAGMENT)};
+		vkcv::DescriptorBinding(vkcv::DescriptorType::IMAGE_SAMPLED,    1, vkcv::ShaderStage::FRAGMENT),
+		vkcv::DescriptorBinding(vkcv::DescriptorType::SAMPLER,          1, vkcv::ShaderStage::FRAGMENT),
+		vkcv::DescriptorBinding(vkcv::DescriptorType::UNIFORM_BUFFER,   1, vkcv::ShaderStage::FRAGMENT),
+		vkcv::DescriptorBinding(vkcv::DescriptorType::IMAGE_SAMPLED,    1, vkcv::ShaderStage::FRAGMENT) ,
+		vkcv::DescriptorBinding(vkcv::DescriptorType::SAMPLER,          1, vkcv::ShaderStage::FRAGMENT) };
 	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
 
 	const vkcv::PipelineConfig trianglePipelineDefinition(
@@ -132,10 +136,12 @@ int main(int argc, const char** argv) {
 		vkcv::SamplerAddressMode::REPEAT
 	);
 
-	vkcv::DescriptorWrites setWrites;
-	setWrites.sampledImageWrites	= { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
-	setWrites.samplerWrites			= { vkcv::SamplerDescriptorWrite(1, sampler) };
-	core.writeResourceDescription(descriptorSet, 0, setWrites);
+    vkcv::SamplerHandle shadowSampler = core.createSampler(
+        vkcv::SamplerFilterType::NEAREST,
+        vkcv::SamplerFilterType::NEAREST,
+        vkcv::SamplerMipmapMode::NEAREST,
+        vkcv::SamplerAddressMode::CLAMP_TO_EDGE
+    );
 
 	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
 
@@ -147,19 +153,69 @@ int main(int argc, const char** argv) {
 		glm::vec3( 0.f, -2.f, 0.f),
 		glm::vec3( 3.f,  0.f, 0.f),
 		glm::vec3(-3.f,  0.f, 0.f),
-		glm::vec3( 0.f,  2.f, 0.f)
+		glm::vec3( 0.f,  2.f, 0.f),
+		glm::vec3( 0.f, -5.f, 0.f)
 	};
 
 	std::vector<glm::mat4> modelMatrices;
 	std::vector<vkcv::DrawcallInfo> drawcalls;
+	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
 	for (const auto& position : instancePositions) {
 		modelMatrices.push_back(glm::translate(glm::mat4(1.f), position));
 		drawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, { descriptorUsage }));
+		shadowDrawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, {}));
 	}
 
-	std::vector<glm::mat4> mvpMatrices;
+	modelMatrices.back() *= glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f));
+
+	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
+	std::vector<glm::mat4> mvpLight;
+
+	vkcv::ShaderProgram shadowShader;
+	shadowShader.addShader(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow_vert.spv");
+	shadowShader.addShader(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow_frag.spv");
+    shadowShader.reflectShader(vkcv::ShaderStage::VERTEX);
+    shadowShader.reflectShader(vkcv::ShaderStage::FRAGMENT);
+
+	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
+	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
+		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
+	};
+	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
+	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
+
+	const uint32_t shadowMapResolution = 1024;
+	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
+	const vkcv::PipelineConfig shadowPipeConfig(
+		shadowShader, 
+		shadowMapResolution, 
+		shadowMapResolution, 
+		shadowPass, 
+		attributes,
+		{}, 
+		false);
+	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
+
+	struct LightInfo {
+		glm::vec3 direction;
+		float padding;
+		glm::mat4 lightMatrix;
+	};
+	LightInfo lightInfo;
+	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
+
+	vkcv::DescriptorWrites setWrites;
+	setWrites.sampledImageWrites    = { 
+        vkcv::SampledImageDescriptorWrite(0, texture.getHandle()),
+        vkcv::SampledImageDescriptorWrite(3, shadowMap.getHandle()) };
+	setWrites.samplerWrites         = { 
+        vkcv::SamplerDescriptorWrite(1, sampler), 
+        vkcv::SamplerDescriptorWrite(4, shadowSampler) };
+    setWrites.uniformBufferWrites   = { vkcv::UniformBufferDescriptorWrite(2, lightBuffer.getHandle()) };
+	core.writeResourceDescription(descriptorSet, 0, setWrites);
 
 	auto start = std::chrono::system_clock::now();
+	const auto appStartTime = start;
 	while (window.isWindowOpen()) {
 		vkcv::Window::pollEvents();
 		
@@ -180,16 +236,54 @@ int main(int argc, const char** argv) {
 		start = end;
 		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
 
-		const glm::mat4 viewProjection = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
-		mvpMatrices.clear();
+		const float sunTheta = (end - appStartTime).count() * 0.0000001;
+		lightInfo.direction = glm::normalize(glm::vec3(cos(sunTheta), 1, sin(sunTheta)));
+
+		const float shadowProjectionSize = 5.f;
+		glm::mat4 projectionLight = glm::ortho(
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize);
+		
+		glm::mat4 vulkanCorrectionMatrix(1.f);
+		vulkanCorrectionMatrix[2][2] = 0.5;
+		vulkanCorrectionMatrix[3][2] = 0.5;
+		projectionLight = vulkanCorrectionMatrix * projectionLight;
+
+		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
+
+		lightInfo.lightMatrix = projectionLight * viewLight;
+		lightBuffer.fill({ lightInfo });
+
+		const glm::mat4 viewProjectionCamera = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+
+		mainPassMatrices.clear();
+		mvpLight.clear();
 		for (const auto& m : modelMatrices) {
-			mvpMatrices.push_back(viewProjection * m);
+			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
+			mvpLight.push_back(lightInfo.lightMatrix* m);
 		}
-		vkcv::PushConstantData pushConstantData((void*)mvpMatrices.data(), sizeof(glm::mat4));
-        const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+
+		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+
+		vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
 
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			shadowPass,
+			shadowPipe,
+			shadowPushConstantData,
+			shadowDrawcalls,
+			{ shadowMap.getHandle() });
+
+		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
+
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
 			trianglePass,
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 908cb617052e67f9ffb62775845ff8e861300d66..9ed83d2a224119bd20fcfc81c5720b425de06bb6 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -375,7 +375,9 @@ namespace vkcv
 		const FinishCommandFunction &finish) {
 
 		m_CommandStreamManager->recordCommandsToStream(cmdStreamHandle, record);
-		m_CommandStreamManager->addFinishCallbackToStream(cmdStreamHandle, finish);
+		if (finish) {
+			m_CommandStreamManager->addFinishCallbackToStream(cmdStreamHandle, finish);
+		}
 	}
 
 	void Core::submitCommandStream(const CommandStreamHandle handle) {
@@ -457,4 +459,10 @@ namespace vkcv
 			recordSwapchainImageLayoutTransition(cmdBuffer, vk::ImageLayout::ePresentSrcKHR);
 		});
 	}
+
+	void Core::prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image) {
+		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eShaderReadOnlyOptimal, cmdBuffer);
+		}, nullptr);
+	}
 }
diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp
index 14259245272140d0e5b180c3c1f8c2614881a402..ad8437ca2a6c07862f66485c74c89ccba0d69ebe 100644
--- a/src/vkcv/PipelineConfig.cpp
+++ b/src/vkcv/PipelineConfig.cpp
@@ -9,13 +9,13 @@
 namespace vkcv {
 
     PipelineConfig::PipelineConfig(
-		const ShaderProgram&						shaderProgram,
-		uint32_t									width,
-		uint32_t									height,
-		PassHandle									&passHandle,
-		const std::vector<VertexAttribute>			&vertexAttributes,
-		const std::vector<vk::DescriptorSetLayout>	&descriptorLayouts,
-		bool										useDynamicViewport)
+		const ShaderProgram&                        shaderProgram,
+		uint32_t                                    width,
+		uint32_t                                    height,
+		const PassHandle                            &passHandle,
+		const std::vector<VertexAttribute>          &vertexAttributes,
+		const std::vector<vk::DescriptorSetLayout>  &descriptorLayouts,
+		bool                                        useDynamicViewport)
 		:
 		m_ShaderProgram(shaderProgram),
 		m_Height(height),
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 9afa2bc90dc5bdbaf88deee9de8b4ff71d436641..28a64a243b9a7a8fc9372409ef3783901219c868 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -174,7 +174,7 @@ namespace vkcv
                 { 1.f,1.f,1.f,1.f }
         );
 
-		const size_t matrixPushConstantSize = 4 * 4 * sizeof(float);
+		const size_t matrixPushConstantSize = config.m_ShaderProgram.getPushConstantSize();
 		const vk::PushConstantRange pushConstantRange(vk::ShaderStageFlagBits::eAll, 0, matrixPushConstantSize);
 
         // pipeline layout
diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp
index 5185b8b402eae5cd514689ba51a06e1a437271bf..3eea7ed21d99af2768d48e62af47cbdef94c7ef1 100644
--- a/src/vkcv/ShaderProgram.cpp
+++ b/src/vkcv/ShaderProgram.cpp
@@ -5,6 +5,7 @@
  */
 
 #include "vkcv/ShaderProgram.hpp"
+#include <algorithm>
 
 namespace vkcv {
     /**
@@ -128,9 +129,19 @@ namespace vkcv {
 
 			m_VertexLayout = VertexLayout(inputVec);
 		}
+		for (const auto &pushConstantBuffer : resources.push_constant_buffers) {
+			for (const auto &range : comp.get_active_buffer_ranges(pushConstantBuffer.id)) {
+				const size_t size = range.range + range.offset;
+				m_pushConstantSize = std::max(m_pushConstantSize, size);
+			}
+		}
     }
 
     const VertexLayout& ShaderProgram::getVertexLayout() const{
         return m_VertexLayout;
 	}
+
+	size_t ShaderProgram::getPushConstantSize() const {
+		return m_pushConstantSize;
+	}
 }