diff --git a/projects/voxelization/resources/shaders/brdf.inc b/projects/voxelization/resources/shaders/brdf.inc
new file mode 100644
index 0000000000000000000000000000000000000000..f8f1388bbda7bb5f6e90176bb503bf9209065147
--- /dev/null
+++ b/projects/voxelization/resources/shaders/brdf.inc
@@ -0,0 +1,26 @@
+const float pi = 3.1415; 
+
+vec3 lambertBRDF(vec3 albedo){
+    return albedo / pi;
+}
+
+vec3 fresnelSchlick(float cosTheta, vec3 f0){
+    return f0 + (vec3(1) - f0) * pow(1 - cosTheta, 5);
+}
+
+float GGXDistribution(float r, float NoH){
+    float r2    = r * r;
+    float denom = pi * pow(NoH * NoH * (r2 - 1) + 1, 2);
+    return r2 / max(denom, 0.000001);
+}
+
+float GGXSmithShadowingPart(float r, float cosTheta){
+    float nom   = cosTheta * 2;
+    float r2    = r * r;
+    float denom = cosTheta + sqrt(r2 + (1 - r2) * cosTheta * cosTheta);
+    return nom / max(denom, 0.000001);
+}
+
+float GGXSmithShadowing(float r, float NoV, float NoL){
+    return GGXSmithShadowingPart(r, NoV) * GGXSmithShadowingPart(r, NoL);
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shader.frag b/projects/voxelization/resources/shaders/shader.frag
index 445caea832331b510e33df21f9787a3be2d5099f..aba54a23963c02ef8ae08a7409e0ff83318f2039 100644
--- a/projects/voxelization/resources/shaders/shader.frag
+++ b/projects/voxelization/resources/shaders/shader.frag
@@ -5,6 +5,8 @@
 #include "perMeshResources.inc"
 #include "lightInfo.inc"
 #include "shadowMapping.inc"
+#include "brdf.inc"
+#include "voxel.inc"
 
 layout(location = 0) in vec3 passNormal;
 layout(location = 1) in vec2 passUV;
@@ -23,31 +25,51 @@ layout(set=0, binding=3) uniform cameraBuffer {
     vec3 cameraPos;
 };
 
-const float pi = 3.1415; 
+layout(set=0, binding=4) uniform texture3D  voxelTexture;
+layout(set=0, binding=5) uniform sampler    voxelSampler;
 
-vec3 lambertBRDF(vec3 albedo){
-    return albedo / pi;
-}
-
-vec3 fresnelSchlick(float cosTheta, vec3 f0){
-    return f0 + (vec3(1) - f0) * pow(1 - cosTheta, 5);
-}
-
-float GGXDistribution(float r, float NoH){
-    float r2    = r * r;
-    float denom = pi * pow(NoH * NoH * (r2 - 1) + 1, 2);
-    return r2 / max(denom, 0.000001);
-}
+layout(set=0, binding=6) uniform VoxelInfoBuffer{
+    VoxelInfo voxelInfo;
+};
 
-float GGXSmithShadowingPart(float r, float cosTheta){
-    float nom   = cosTheta * 2;
-    float r2    = r * r;
-    float denom = cosTheta + sqrt(r2 + (1 - r2) * cosTheta * cosTheta);
-    return nom / max(denom, 0.000001);
-}
+vec3 voxelConeTrace(texture3D voxelTexture, sampler voxelSampler, vec3 direction, vec3 startPosition, float coneAngleRadian){
 
-float GGXSmithShadowing(float r, float NoV, float NoL){
-    return GGXSmithShadowingPart(r, NoV) * GGXSmithShadowingPart(r, NoL);
+    int voxelResolution =  textureSize(sampler3D(voxelTexture, voxelSampler), 0).x;
+    float voxelSize     = voxelInfo.extent / voxelResolution;
+    float maxMip        = float(log2(voxelResolution));
+    float maxStableMip  = 4;    // must be the same as in Voxelization::voxelizeMeshes
+    maxMip              = min(maxMip, maxStableMip);
+    float d             = sqrt(3 * pow(voxelSize, 2));
+    vec3 color          = vec3(0);
+    float a             = 0;
+    
+    float coneAngleHalf = coneAngleRadian * 0.5f;
+    
+    int maxSamples = 16;
+    for(int i = 0; i < maxSamples; i++){
+        
+        vec3 samplePos      = startPosition + d * direction;
+        vec3 sampleUV       = worldToVoxelCoordinates(samplePos, voxelInfo);
+        
+        if(a >= 0.95 || any(lessThan(sampleUV, vec3(0))) || any(greaterThan(sampleUV, vec3(1)))){
+            break;
+        }
+        
+        float coneDiameter  = 2 * tan(coneAngleHalf) * d;
+        float mip           = log2(coneDiameter / voxelSize);
+        mip                 = min(mip, maxMip);
+    
+        vec4 voxelSample    = textureLod(sampler3D(voxelTexture, voxelSampler), sampleUV , mip);
+        
+        color               += (1 - a) * voxelSample.rgb;
+        voxelSample.a       = pow(voxelSample.a, 0.6);
+        a                   += (1 - a) * voxelSample.a;
+        
+        d                   += coneDiameter;
+        samplePos           = startPosition + d * direction;
+        sampleUV            = worldToVoxelCoordinates(samplePos, voxelInfo);
+    }
+    return color;
 }
 
 void main()	{
@@ -86,11 +108,31 @@ void main()	{
     
     vec3 sun        = lightInfo.sunStrength * lightInfo.sunColor * NoL;
     sun             *= shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler);
-    vec3 ambient    = vec3(0.05);
     
     vec3 F_in       = fresnelSchlick(NoL, f0);
     vec3 F_out      = fresnelSchlick(NoV, f0);
     vec3 diffuse    = lambertBRDF(albedo) * (1 - F_in) * (1 - F_out);
     
-	outColor        = (diffuse + specular) * sun + lambertBRDF(albedo) * ambient;
+    vec3 up         = N_geo.y >= 0.99 ? vec3(1, 0, 0) : vec3(0, 1, 0);
+    vec3 right      = normalize(cross(up, N));
+    up              = cross(N, right); 
+    mat3 toSurface  = mat3(right, up, N);
+    
+    float coneAngle = 60.f / 180.f * pi;
+    vec3 diffuseTrace = vec3(0);
+    {
+        vec3 sampleDirection    = toSurface * vec3(0, 0, 1);
+        float weight            = pi / 4.f;
+        diffuseTrace            += weight * voxelConeTrace(voxelTexture, voxelSampler, sampleDirection, passPos, coneAngle);
+    }
+    for(int i = 0; i < 6;i++){
+        float theta             = 2 * pi / i;
+        float phi               = pi / 3;   // 60 degrees
+        vec3 sampleDirection    = toSurface * vec3(cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi));
+        float weight            = pi * (3.f / 4.f) / i;
+        diffuseTrace            += voxelConeTrace(voxelTexture, voxelSampler, sampleDirection, passPos, coneAngle);
+    }
+    
+	outColor = (diffuse + specular) * sun + diffuse * diffuseTrace;
+    //outColor = diffuseTrace;
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxel.inc b/projects/voxelization/resources/shaders/voxel.inc
index 25c0a82bbc887913a4d69ccdeee2b0d8934828c8..6a2441135754984fe05db42c8828bc98bf1570c2 100644
--- a/projects/voxelization/resources/shaders/voxel.inc
+++ b/projects/voxelization/resources/shaders/voxel.inc
@@ -7,6 +7,14 @@ uint flattenVoxelUVToIndex(ivec3 UV, ivec3 voxelImageSize){
     return UV.x + UV.y * voxelImageSize.x + UV.z *  voxelImageSize.x*  voxelImageSize.y;
 }
 
+vec3 worldToVoxelCoordinates(vec3 world, VoxelInfo info){
+    return (world - info.offset) / info.extent + 0.5f;
+}
+
+ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
+    return ivec3(voxelCoordinates * voxelImageResolution);
+}
+
 // packed voxel data: 
 // 1 bit opacity
 // 7 bit exposure
diff --git a/projects/voxelization/resources/shaders/voxelization.frag b/projects/voxelization/resources/shaders/voxelization.frag
index a49b13185ec26b069661141cfdbbfbbe45d14fd3..59cd07e11a2d8e01ae6ddf3cced77b52b0eca17e 100644
--- a/projects/voxelization/resources/shaders/voxelization.frag
+++ b/projects/voxelization/resources/shaders/voxelization.frag
@@ -6,6 +6,7 @@
 #include "perMeshResources.inc"
 #include "lightInfo.inc"
 #include "shadowMapping.inc"
+#include "brdf.inc"
 
 layout(location = 0) in vec3 passPos;
 layout(location = 1) in vec2 passUV;
@@ -28,14 +29,6 @@ layout(set=0, binding=3) uniform sunBuffer {
 layout(set=0, binding=4) uniform texture2D  shadowMap;
 layout(set=0, binding=5) uniform sampler    shadowMapSampler;
 
-vec3 worldToVoxelCoordinates(vec3 world, VoxelInfo info){
-    return (world - info.offset) / info.extent + 0.5f;
-}
-
-ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
-    return ivec3(voxelCoordinates * voxelImageResolution);
-}
-
 void main()	{
     vec3 voxelCoordinates = worldToVoxelCoordinates(passPos, voxelInfo);
     ivec3 voxelImageSize = imageSize(voxelImage);
@@ -51,7 +44,7 @@ void main()	{
     float NoL   = clamp(dot(N, lightInfo.L), 0, 1);
     vec3 sun    = lightInfo.sunStrength * lightInfo.sunColor * NoL * shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler);
     vec3 color  = albedo * sun;
-    color = albedo * sun;
+    color       = lambertBRDF(albedo) * sun;
     
     atomicMax(packedVoxelData[flatIndex], packVoxelInfo(color));
 }
\ No newline at end of file
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
index a04131cedfdc508e14a3dbd97da642e1af48f8da..15a6a8f5e3dfd4c0b78594cb8cf7f37c05ff1119 100644
--- a/projects/voxelization/src/Voxelization.cpp
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -182,8 +182,9 @@ Voxelization::Voxelization(
 }
 
 void Voxelization::voxelizeMeshes(
-	vkcv::CommandStreamHandle                       cmdStream, 
-	const glm::vec3&                                cameraPosition, 
+	vkcv::CommandStreamHandle                       cmdStream,
+	const glm::vec3&                                cameraPosition,
+	const glm::vec3&                                cameraLookDirection,
 	const std::vector<vkcv::Mesh>&                  meshes,
 	const std::vector<glm::mat4>&                   modelMatrices,
 	const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets) {
@@ -192,8 +193,12 @@ void Voxelization::voxelizeMeshes(
 	voxelizationInfo.extent = m_voxelExtent;
 
 	// move voxel offset with camera in voxel sized steps
-	const float voxelSize = m_voxelExtent / voxelResolution;
-	voxelizationInfo.offset = glm::floor(cameraPosition / voxelSize) * voxelSize;
+	const float voxelSize       = m_voxelExtent / voxelResolution;
+	const int   maxStableMip    = 4;	// must be the same as in voxelConeTrace shader function
+	const float snapSize        = voxelSize * exp2(maxStableMip);
+
+	const glm::vec3 voxelVolumeCenter   = cameraPosition + (1.f / 3.f) * m_voxelExtent * cameraLookDirection;
+	voxelizationInfo.offset             = glm::floor(voxelVolumeCenter / snapSize) * snapSize;
 
 	m_voxelInfoBuffer.fill({ voxelizationInfo });
 
@@ -268,6 +273,7 @@ void Voxelization::voxelizeMeshes(
 	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle());
 
 	m_voxelImage.recordMipChainGeneration(cmdStream);
+	m_corePtr->prepareImageForSampling(cmdStream, m_voxelImage.getHandle());
 }
 
 void Voxelization::renderVoxelVisualisation(
@@ -294,6 +300,7 @@ void Voxelization::renderVoxelVisualisation(
 		vkcv::Mesh({}, nullptr, drawVoxelCount),
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle) });
 
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
 	m_corePtr->recordDrawcallsToCmdStream(
 		cmdStream,
 		m_visualisationPass,
@@ -305,4 +312,12 @@ void Voxelization::renderVoxelVisualisation(
 
 void Voxelization::setVoxelExtent(float extent) {
 	m_voxelExtent = extent;
+}
+
+vkcv::ImageHandle Voxelization::getVoxelImageHandle() const {
+	return m_voxelImage.getHandle();
+}
+
+vkcv::BufferHandle Voxelization::getVoxelInfoBufferHandle() const {
+	return m_voxelInfoBuffer.getHandle();
 }
\ No newline at end of file
diff --git a/projects/voxelization/src/Voxelization.hpp b/projects/voxelization/src/Voxelization.hpp
index 25830b171edb9154e37b2d597c2bbbf2daea6b2e..bb0b5c5a91f59080d278a752e9637f32e194f620 100644
--- a/projects/voxelization/src/Voxelization.hpp
+++ b/projects/voxelization/src/Voxelization.hpp
@@ -17,8 +17,9 @@ public:
 		vkcv::SamplerHandle shadowSampler);
 
 	void voxelizeMeshes(
-		vkcv::CommandStreamHandle                       cmdStream, 
-		const glm::vec3&                                cameraPosition, 
+		vkcv::CommandStreamHandle                       cmdStream,
+		const glm::vec3&                                cameraPosition,
+		const glm::vec3&                                cameraLookDirection,
 		const std::vector<vkcv::Mesh>&                  meshes,
 		const std::vector<glm::mat4>&                   modelMatrices,
 		const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets);
@@ -31,6 +32,9 @@ public:
 
 	void setVoxelExtent(float extent);
 
+	vkcv::ImageHandle getVoxelImageHandle() const;
+	vkcv::BufferHandle getVoxelInfoBufferHandle() const;
+
 private:
 	vkcv::Core* m_corePtr;
 
@@ -63,5 +67,5 @@ private:
 	};
 	vkcv::Buffer<VoxelizationInfo> m_voxelInfoBuffer;
 
-	float m_voxelExtent = 20.f;
+	float m_voxelExtent = 30.f;
 };
\ No newline at end of file
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index 281faaf1d234aa0bf5e2b8fed6de56921904a9b4..57e25066c23119e3ed54054c7e0206e59c0496d8 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -168,16 +168,6 @@ int main(int argc, const char** argv) {
 	vkcv::DescriptorSetHandle forwardShadingDescriptorSet = 
 		core.createDescriptorSet({ forwardProgram.getReflectedDescriptors()[0] });
 
-	vkcv::Buffer<glm::vec3> cameraPosBuffer = core.createBuffer<glm::vec3>(vkcv::BufferType::UNIFORM, 1);
-
-	vkcv::DescriptorWrites forwardDescriptorWrites;
-	forwardDescriptorWrites.uniformBufferWrites = { 
-		vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()),
-		vkcv::UniformBufferDescriptorWrite(3, cameraPosBuffer.getHandle()) };
-	forwardDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()) };
-	forwardDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, shadowSampler) };
-	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
-
 	vkcv::SamplerHandle colorSampler = core.createSampler(
 		vkcv::SamplerFilterType::LINEAR,
 		vkcv::SamplerFilterType::LINEAR,
@@ -251,7 +241,7 @@ int main(int argc, const char** argv) {
 		perMeshDescriptorSets.push_back(materialDescriptorSets[vertexGroup.materialIndex]);
 	}
 
-	const vkcv::PipelineConfig forwardPipelineConfig {
+	vkcv::PipelineConfig forwardPipelineConfig {
 		forwardProgram,
 		windowWidth,
 		windowHeight,
@@ -361,11 +351,33 @@ int main(int argc, const char** argv) {
 		shadowMap.getHandle(),
 		shadowSampler);
 
+	vkcv::Buffer<glm::vec3> cameraPosBuffer = core.createBuffer<glm::vec3>(vkcv::BufferType::UNIFORM, 1);
+
+	vkcv::SamplerHandle voxelSampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::CLAMP_TO_EDGE);
+
+	// write forward pass descriptor set
+	vkcv::DescriptorWrites forwardDescriptorWrites;
+	forwardDescriptorWrites.uniformBufferWrites = {
+		vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()),
+		vkcv::UniformBufferDescriptorWrite(3, cameraPosBuffer.getHandle()),
+		vkcv::UniformBufferDescriptorWrite(6, voxelization.getVoxelInfoBufferHandle()) };
+	forwardDescriptorWrites.sampledImageWrites = {
+		vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()),
+		vkcv::SampledImageDescriptorWrite(4, voxelization.getVoxelImageHandle()) };
+	forwardDescriptorWrites.samplerWrites = { 
+		vkcv::SamplerDescriptorWrite(2, shadowSampler),
+		vkcv::SamplerDescriptorWrite(5, voxelSampler) };
+	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
+
 	vkcv::gui::GUI gui(core, window);
 
 	glm::vec2 lightAngles(90.f, 0.f);
 	int voxelVisualisationMip = 0;
-	float voxelizationExtent = 20.f;
+	float voxelizationExtent = 30.f;
 
 	auto start = std::chrono::system_clock::now();
 	const auto appStartTime = start;
@@ -454,6 +466,7 @@ int main(int argc, const char** argv) {
 		voxelization.voxelizeMeshes(
 			cmdStream, 
 			cameraManager.getActiveCamera().getPosition(), 
+			cameraManager.getActiveCamera().getFront(),
 			meshes, 
 			modelMatrices,
 			perMeshDescriptorSets);
@@ -501,7 +514,28 @@ int main(int argc, const char** argv) {
 		ImGui::Checkbox("Draw voxel visualisation", &renderVoxelVis);
 		ImGui::SliderInt("Visualisation mip",       &voxelVisualisationMip, 0, 7);
 		ImGui::DragFloat("Voxelization extent",     &voxelizationExtent, 1.f, 0.f);
+		voxelizationExtent = std::max(voxelizationExtent, 1.f);
 		voxelVisualisationMip = std::max(voxelVisualisationMip, 0);
+
+		if (ImGui::Button("Reload forward pass")) {
+
+			vkcv::ShaderProgram newForwardProgram;
+			compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"),
+				[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+				newForwardProgram.addShader(shaderStage, path);
+			});
+			compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
+				[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+				newForwardProgram.addShader(shaderStage, path);
+			});
+			forwardPipelineConfig.m_ShaderProgram = newForwardProgram;
+			vkcv::PipelineHandle newPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
+
+			if (newPipeline) {
+				forwardPipeline = newPipeline;
+			}
+		}
+
 		ImGui::End();
 
 		gui.endGUI();