From 336b4751a3b160e5240e46210d182625584f9b65 Mon Sep 17 00:00:00 2001
From: Alexander Gauggel <agauggel@uni-koblenz.de>
Date: Mon, 21 Jun 2021 17:23:15 +0200
Subject: [PATCH] [#82] Add secondary bounce

---
 .../voxelization/resources/shaders/brdf.inc   |   7 +-
 .../resources/shaders/shader.frag             |  63 +-------
 .../voxelization/resources/shaders/voxel.inc  | 136 +++++++++++++++++-
 .../resources/shaders/voxelBufferToImage.comp |  13 +-
 .../resources/shaders/voxelReset.comp         |   8 +-
 .../shaders/voxelSecondaryBounce.comp         |  46 ++++++
 .../resources/shaders/voxelVisualisation.vert |   2 +-
 .../resources/shaders/voxelization.frag       |   6 +-
 .../resources/shaders/voxelization.vert       |   2 +-
 projects/voxelization/src/Voxelization.cpp    |  56 +++++++-
 projects/voxelization/src/Voxelization.hpp    |  13 +-
 projects/voxelization/src/main.cpp            |  15 +-
 12 files changed, 280 insertions(+), 87 deletions(-)
 create mode 100644 projects/voxelization/resources/shaders/voxelSecondaryBounce.comp

diff --git a/projects/voxelization/resources/shaders/brdf.inc b/projects/voxelization/resources/shaders/brdf.inc
index f8f1388b..fc97b601 100644
--- a/projects/voxelization/resources/shaders/brdf.inc
+++ b/projects/voxelization/resources/shaders/brdf.inc
@@ -1,3 +1,6 @@
+#ifndef BRDF_INC
+#define BRDF_INC
+
 const float pi = 3.1415; 
 
 vec3 lambertBRDF(vec3 albedo){
@@ -23,4 +26,6 @@ float GGXSmithShadowingPart(float r, float cosTheta){
 
 float GGXSmithShadowing(float r, float NoV, float NoL){
     return GGXSmithShadowingPart(r, NoV) * GGXSmithShadowingPart(r, NoL);
-}
\ No newline at end of file
+}
+
+#endif // #ifndef BRDF_INC
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shader.frag b/projects/voxelization/resources/shaders/shader.frag
index 39af585b..e189ba85 100644
--- a/projects/voxelization/resources/shaders/shader.frag
+++ b/projects/voxelization/resources/shaders/shader.frag
@@ -32,51 +32,6 @@ layout(set=0, binding=6) uniform VoxelInfoBuffer{
     VoxelInfo voxelInfo;
 };
 
-vec3 voxelConeTrace(texture3D voxelTexture, sampler voxelSampler, vec3 direction, vec3 startPosition, float coneAngleRadian){
-
-    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             = 2 * sqrt(3 * pow(voxelSize, 2));
-    vec3 color          = vec3(0);
-    float a             = 0;
-    
-    float coneAngleHalf = coneAngleRadian * 0.5f;
-    
-    int maxSamples = 128;
-    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;
-        
-        float minStepSize   = 0.15;
-        d                   += max(coneDiameter, minStepSize);
-        samplePos           = startPosition + d * direction;
-        sampleUV            = worldToVoxelCoordinates(samplePos, voxelInfo);
-    }
-    return color;
-}
-
-float degreeToRadian(float d){
-    return d / 180.f * pi;
-}
-
 vec3 cookTorrance(vec3 f0, float r, vec3 N, vec3 V, vec3 L){
     
     vec3 H  = normalize(L + V);
@@ -135,26 +90,12 @@ void main()	{
     up              = cross(N, right); 
     mat3 toSurface  = mat3(right, up, N);
     
-    float coneAngle = degreeToRadian(60.f);
-    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;
-        vec3 trace              = voxelConeTrace(voxelTexture, voxelSampler, sampleDirection, passPos, coneAngle);
-        diffuseTrace            += trace * (1 - fresnelSchlick(NoV, f0)) * (1 - fresnelSchlick(cos(phi), f0));
-    }
+    vec3 diffuseTrace = diffuseVoxelTraceHemisphere(toSurface, passPos, voxelTexture, voxelSampler, voxelInfo);
     
     vec3 R                      = reflect(-V, N);
     float reflectionConeAngle   = degreeToRadian(roughnessToConeAngle(r));
     vec3 offsetTraceStart       = passPos + N_geo * 0.1f;
-    vec3 specularTrace          = voxelConeTrace(voxelTexture, voxelSampler, R, offsetTraceStart, reflectionConeAngle);
+    vec3 specularTrace          = voxelConeTrace(R, offsetTraceStart, reflectionConeAngle, voxelTexture, voxelSampler, voxelInfo);
     vec3 reflectionBRDF         = cookTorrance(f0, r, N, V, R);
     
 	outColor = 
diff --git a/projects/voxelization/resources/shaders/voxel.inc b/projects/voxelization/resources/shaders/voxel.inc
index 6a244113..8742473d 100644
--- a/projects/voxelization/resources/shaders/voxel.inc
+++ b/projects/voxelization/resources/shaders/voxel.inc
@@ -1,8 +1,16 @@
+#include "brdf.inc"
+
 struct VoxelInfo{
     vec3 offset;
     float extent;
 };
 
+struct PackedVoxelData{
+    uint color;
+    uint normal;
+    uint albedo;
+};
+
 uint flattenVoxelUVToIndex(ivec3 UV, ivec3 voxelImageSize){
     return UV.x + UV.y * voxelImageSize.x + UV.z *  voxelImageSize.x*  voxelImageSize.y;
 }
@@ -15,6 +23,10 @@ ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
     return ivec3(voxelCoordinates * voxelImageResolution);
 }
 
+vec3 voxelCoordinatesToWorldPosition(ivec3 coord, int voxelResolution, VoxelInfo voxelInfo, float voxelHalfSize){
+    return (vec3(coord) / voxelResolution - 0.5) * voxelInfo.extent + voxelHalfSize + voxelInfo.offset;
+}
+
 // packed voxel data: 
 // 1 bit opacity
 // 7 bit exposure
@@ -23,7 +35,7 @@ ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
 // 8 bit red
 float maxExposure = 16.f;
 
-uint packVoxelInfo(vec3 color){
+uint packVoxelColor(vec3 color){
     
     color               = clamp(color, vec3(0), vec3(maxExposure));
     float maxComponent  = max(max(max(color.r, color.g), color.b), 1.f);
@@ -37,7 +49,7 @@ uint packVoxelInfo(vec3 color){
     return opaqueBit | exposureBits | blueBits | greenBits | redBits;
 }
 
-vec4 unpackVoxelInfo(uint packed){
+vec4 unpackVoxelColor(uint packed){
     vec4 rgba;
     rgba.r = (packed >> 0  & 0x000000FF) / 255.f;
     rgba.g = (packed >> 8  & 0x000000FF) / 255.f;
@@ -47,4 +59,124 @@ vec4 unpackVoxelInfo(uint packed){
     rgba.rgb *= (packed >> 24 & 0x0000007F) / 127.f * maxExposure; 
     
     return rgba;
+}
+
+uint packSNormInto9Bits(float x){
+    uint lengthBits = 0x000000FF & uint(abs(x) * 255.f);
+    uint signBits   = (x < 0 ? 1 : 0)  << 8;
+    return lengthBits | signBits;
+}
+
+float unpack9LowBitsIntoSNorm(uint bits){
+    bits = (0x000001FF & bits);
+    float length    = bits / 255.f;
+    float sign      = (bits >> 8) == 0 ? 1 : -1;
+    return sign * length;
+}
+
+// normals are packed with 9 bits each, 8 for length and 1 for sign
+uint packVoxelNormal(vec3 N){
+    N           = clamp(N, vec3(0), vec3(1));
+    uint xBits  = packSNormInto9Bits(N.x) << 0;
+    uint yBits  = packSNormInto9Bits(N.y) << 9;
+    uint zBits  = packSNormInto9Bits(N.z) << 18;
+    return zBits | yBits | xBits;
+}
+
+vec3 unpackVoxelNormal(uint packed){
+    vec3 N;
+    N.x  = unpack9LowBitsIntoSNorm(packed >> 0);
+    N.y  = unpack9LowBitsIntoSNorm(packed >> 9);
+    N.z  = unpack9LowBitsIntoSNorm(packed >> 18);
+    return normalize(N);
+}
+
+uint packUNormInto8Bits(float x){
+    return 0x000000FF & uint(abs(x) * 255.f);
+}
+
+float unpack8LowBitsIntoUNorm(uint bits){
+    bits = (0x000000FF & bits);
+    return bits / 255.f;
+}
+
+// albedo is packed with 8 bits each
+uint packVoxelAlbedo(vec3 albedo){
+    albedo      = clamp(albedo, vec3(0), vec3(1));
+    uint rBits  = packUNormInto8Bits(albedo.r) << 0;
+    uint gBits  = packUNormInto8Bits(albedo.g) << 8;
+    uint bBits  = packUNormInto8Bits(albedo.b) << 16;
+    return bBits | gBits | rBits;
+}
+
+vec3 unpackVoxelAlbedo(uint packed){
+    vec3 albedo;
+    albedo.r  = unpack8LowBitsIntoUNorm(packed >> 0);
+    albedo.g  = unpack8LowBitsIntoUNorm(packed >> 8);
+    albedo.b  = unpack8LowBitsIntoUNorm(packed >> 16);
+    return albedo;
+}
+
+vec3 voxelConeTrace(vec3 direction, vec3 startPosition, float coneAngleRadian, texture3D voxelTexture, sampler voxelSampler, VoxelInfo voxelInfo){
+
+    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             = 2 * sqrt(3 * pow(voxelSize, 2));
+    vec3 color          = vec3(0);
+    float a             = 0;
+    
+    float coneAngleHalf = coneAngleRadian * 0.5f;
+    
+    int maxSamples = 128;
+    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;
+        
+        float minStepSize   = 0.15;
+        d                   += max(coneDiameter, minStepSize);
+        samplePos           = startPosition + d * direction;
+        sampleUV            = worldToVoxelCoordinates(samplePos, voxelInfo);
+    }
+    return color;
+}
+
+float degreeToRadian(float d){
+    return d / 180.f * pi;
+}
+
+vec3 diffuseVoxelTraceHemisphere(mat3 toSurface, vec3 position, texture3D voxelTexture, sampler voxelSampler, VoxelInfo voxelInfo){
+    float coneAngle = degreeToRadian(60.f);
+    vec3 diffuseTrace = vec3(0);
+    {
+        vec3 sampleDirection    = toSurface * vec3(0, 0, 1);
+        float weight            = pi / 4.f;
+        diffuseTrace            += weight * voxelConeTrace(sampleDirection, position, coneAngle, voxelTexture, voxelSampler, voxelInfo);
+    }
+    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;
+        vec3 trace              = voxelConeTrace(sampleDirection, position, coneAngle, voxelTexture, voxelSampler, voxelInfo);
+        diffuseTrace            += trace;
+    }
+    return diffuseTrace;
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelBufferToImage.comp b/projects/voxelization/resources/shaders/voxelBufferToImage.comp
index 5e829888..2c2cffe8 100644
--- a/projects/voxelization/resources/shaders/voxelBufferToImage.comp
+++ b/projects/voxelization/resources/shaders/voxelBufferToImage.comp
@@ -3,7 +3,7 @@
 #include "voxel.inc"
 
 layout(set=0, binding=0, std430) buffer voxelBuffer{
-    uint packedVoxelData[];
+    PackedVoxelData packedVoxelData[];
 };
 
 layout(set=0, binding=1, rgba16f) uniform image3D voxelImage;
@@ -19,6 +19,15 @@ void main(){
     ivec3 UV = ivec3(gl_GlobalInvocationID);
     uint flatIndex = flattenVoxelUVToIndex(UV, voxelImageSize);
     
-    vec4 color = unpackVoxelInfo(packedVoxelData[flatIndex]);
+    vec4 color = unpackVoxelColor(packedVoxelData[flatIndex].color);
+    
+    // for proper visualisation voxel secondary bounce should be disabled, otherwise it adds color
+    
+    // for debugging: write normal into image, so voxel visualisation draws normal
+    // color = vec4(unpackVoxelNormal(packedVoxelData[flatIndex].normal), color.a); 
+    
+    // for debugging: write albedo into image, so voxel visualisation draws albedo
+    // color = vec4(unpackVoxelAlbedo(packedVoxelData[flatIndex].albedo), color.a); 
+    
     imageStore(voxelImage, UV, vec4(color));
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelReset.comp b/projects/voxelization/resources/shaders/voxelReset.comp
index 14b78d65..79eda9ec 100644
--- a/projects/voxelization/resources/shaders/voxelReset.comp
+++ b/projects/voxelization/resources/shaders/voxelReset.comp
@@ -1,7 +1,9 @@
 #version 450
+#extension GL_GOOGLE_include_directive : enable
+#include "voxel.inc"
 
 layout(set=0, binding=0) buffer voxelizationBuffer{
-    uint isFilled[];
+    PackedVoxelData packedVoxelData[];
 };
 
 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
@@ -15,5 +17,7 @@ void main(){
     if(gl_GlobalInvocationID.x> voxelCount){
         return;
     }  
-    isFilled[gl_GlobalInvocationID.x] = 0;
+    packedVoxelData[gl_GlobalInvocationID.x].color     = 0;
+    packedVoxelData[gl_GlobalInvocationID.x].normal    = 0;
+    packedVoxelData[gl_GlobalInvocationID.x].albedo    = 0;
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelSecondaryBounce.comp b/projects/voxelization/resources/shaders/voxelSecondaryBounce.comp
new file mode 100644
index 00000000..3e10ecb8
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelSecondaryBounce.comp
@@ -0,0 +1,46 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+#include "voxel.inc"
+#include "brdf.inc"
+
+layout(set=0, binding=0, std430) buffer voxelBuffer{
+    PackedVoxelData packedVoxelData[];
+};
+layout(set=0, binding=1) uniform texture3D          voxelImageIn;
+layout(set=0, binding=2) uniform sampler            voxelSampler;
+layout(set=0, binding=3, rgba16f) uniform image3D   voxelImageOut;
+layout(set=0, binding=4) uniform voxelizationInfo{
+    VoxelInfo voxelInfo;
+};
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+void main(){
+
+    ivec3 voxelImageSize = imageSize(voxelImageOut);
+    if(any(greaterThanEqual(gl_GlobalInvocationID, voxelImageSize))){
+        return;
+    }
+    ivec3 UV = ivec3(gl_GlobalInvocationID);
+    
+    vec4 color = texelFetch(sampler3D(voxelImageIn, voxelSampler), UV, 0);
+    
+    if(color.a > 0){
+        uint flatIndex  = flattenVoxelUVToIndex(UV, voxelImageSize);
+        vec3 N          = unpackVoxelNormal(packedVoxelData[flatIndex].normal);
+        
+        float halfVoxelSize = voxelInfo.extent / float(voxelImageSize.x) * 0.5f;
+        vec3 pos            = voxelCoordinatesToWorldPosition(UV, voxelImageSize.x, voxelInfo, halfVoxelSize);
+        
+        vec3 up         = N.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);
+    
+        vec3 secondaryBounce    = diffuseVoxelTraceHemisphere(toSurface, pos, voxelImageIn, voxelSampler, voxelInfo);
+        vec3 albedo             = unpackVoxelAlbedo(packedVoxelData[flatIndex].albedo);
+        color.rgb               += lambertBRDF(albedo) * secondaryBounce;
+    }
+    
+    imageStore(voxelImageOut, UV, color);
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.vert b/projects/voxelization/resources/shaders/voxelVisualisation.vert
index 8377143f..e26e2209 100644
--- a/projects/voxelization/resources/shaders/voxelVisualisation.vert
+++ b/projects/voxelization/resources/shaders/voxelVisualisation.vert
@@ -25,7 +25,7 @@ void main()	{
     int index2D         = gl_VertexIndex % slicePixelCount;
     int y               = index2D / voxelResolution;
     int x               = index2D % voxelResolution;
-    vec3 position       = (vec3(x, y, z) / voxelResolution - 0.5) * voxelInfo.extent + passCubeHalf + voxelInfo.offset;
+    vec3 position       = voxelCoordinatesToWorldPosition(ivec3(x, y, z), voxelResolution, voxelInfo, passCubeHalf);
 	gl_Position         = vec4(position, 1.0);
     
     vec4 voxelColor = imageLoad(voxelImage, ivec3(x,y,z));
diff --git a/projects/voxelization/resources/shaders/voxelization.frag b/projects/voxelization/resources/shaders/voxelization.frag
index 59cd07e1..3e5ce1fc 100644
--- a/projects/voxelization/resources/shaders/voxelization.frag
+++ b/projects/voxelization/resources/shaders/voxelization.frag
@@ -13,7 +13,7 @@ layout(location = 1) in vec2 passUV;
 layout(location = 2) in vec3 passN;
 
 layout(set=0, binding=0, std430) buffer voxelizationBuffer{
-    uint packedVoxelData[];
+    PackedVoxelData packedVoxelData[];
 };
 
 layout(set=0, binding=1) uniform voxelizationInfo{
@@ -46,5 +46,7 @@ void main()	{
     vec3 color  = albedo * sun;
     color       = lambertBRDF(albedo) * sun;
     
-    atomicMax(packedVoxelData[flatIndex], packVoxelInfo(color));
+    atomicMax(packedVoxelData[flatIndex].color, packVoxelColor(color));
+    atomicMax(packedVoxelData[flatIndex].normal, packVoxelNormal(N));
+    atomicMax(packedVoxelData[flatIndex].albedo, packVoxelAlbedo(albedo));
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.vert b/projects/voxelization/resources/shaders/voxelization.vert
index 1302a424..221d0f6d 100644
--- a/projects/voxelization/resources/shaders/voxelization.vert
+++ b/projects/voxelization/resources/shaders/voxelization.vert
@@ -18,5 +18,5 @@ void main()	{
 	gl_Position = mvp * vec4(inPosition, 1.0);
     passPos     = (model * vec4(inPosition, 1)).xyz;
     passUV      = inUV;
-    passN       = inNormal;
+    passN       = mat3(model) * inNormal;
 }
\ No newline at end of file
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
index 824c3856..a14a880c 100644
--- a/projects/voxelization/src/Voxelization.cpp
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -59,6 +59,16 @@ vkcv::ShaderProgram loadVoxelBufferToImageShader() {
 	return shader;
 }
 
+vkcv::ShaderProgram loadSecondaryBounceShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/voxelSecondaryBounce.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
 const uint32_t voxelResolution = 128;
 uint32_t voxelCount = voxelResolution * voxelResolution * voxelResolution;
 const vk::Format voxelizationDummyFormat = vk::Format::eR8Unorm;
@@ -68,10 +78,12 @@ Voxelization::Voxelization(
 	const Dependencies& dependencies,
 	vkcv::BufferHandle  lightInfoBuffer,
 	vkcv::ImageHandle   shadowMap,
-	vkcv::SamplerHandle shadowSampler)
+	vkcv::SamplerHandle shadowSampler,
+	vkcv::SamplerHandle voxelSampler)
 	:
 	m_corePtr(corePtr), 
 	m_voxelImage(m_corePtr->createImage(vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, true, true)),
+	m_voxelImageIntermediate(m_corePtr->createImage(vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, true, true)),
 	m_dummyRenderTarget(m_corePtr->createImage(voxelizationDummyFormat, voxelResolution, voxelResolution, 1, false, false, true)),
 	m_voxelInfoBuffer(m_corePtr->createBuffer<VoxelizationInfo>(vkcv::BufferType::UNIFORM, 1)),
 	m_voxelBuffer(m_corePtr->createBuffer<VoxelBufferContent>(vkcv::BufferType::STORAGE, voxelCount)){
@@ -112,7 +124,7 @@ Voxelization::Voxelization(
 	};
 	voxelizationDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(4, shadowMap) };
 	voxelizationDescriptorWrites.samplerWrites      = { vkcv::SamplerDescriptorWrite(5, shadowSampler) };
-	voxelizationDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, m_voxelImage.getHandle()) };
+	voxelizationDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, m_voxelImageIntermediate.getHandle()) };
 	m_corePtr->writeDescriptorSet(m_voxelizationDescriptorSet, voxelizationDescriptorWrites);
 
 	vkcv::ShaderProgram voxelVisualisationShader = loadVoxelVisualisationShader();
@@ -167,7 +179,7 @@ Voxelization::Voxelization(
 	resetVoxelWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
 	m_corePtr->writeDescriptorSet(m_voxelResetDescriptorSet, resetVoxelWrites);
 
-
+	// buffer to image
 	vkcv::ShaderProgram bufferToImageShader = loadVoxelBufferToImageShader();
 
 	m_bufferToImageDescriptorSet = m_corePtr->createDescriptorSet(bufferToImageShader.getReflectedDescriptors()[0]);
@@ -177,8 +189,24 @@ Voxelization::Voxelization(
 
 	vkcv::DescriptorWrites bufferToImageDescriptorWrites;
 	bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
-	bufferToImageDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(1, m_voxelImage.getHandle()) };
+	bufferToImageDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(1, m_voxelImageIntermediate.getHandle()) };
 	m_corePtr->writeDescriptorSet(m_bufferToImageDescriptorSet, bufferToImageDescriptorWrites);
+
+	// secondary bounce
+	vkcv::ShaderProgram secondaryBounceShader = loadSecondaryBounceShader();
+
+	m_secondaryBounceDescriptorSet = m_corePtr->createDescriptorSet(secondaryBounceShader.getReflectedDescriptors()[0]);
+	m_secondaryBouncePipe = m_corePtr->createComputePipeline(
+		secondaryBounceShader,
+		{ m_corePtr->getDescriptorSet(m_secondaryBounceDescriptorSet).layout });
+
+	vkcv::DescriptorWrites secondaryBounceDescriptorWrites;
+	secondaryBounceDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	secondaryBounceDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, m_voxelImageIntermediate.getHandle()) };
+	secondaryBounceDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, voxelSampler) };
+	secondaryBounceDescriptorWrites.storageImageWrites  = { vkcv::StorageImageDescriptorWrite(3, m_voxelImage.getHandle()) };
+	secondaryBounceDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(4, m_voxelInfoBuffer.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_secondaryBounceDescriptorSet, secondaryBounceDescriptorWrites);
 }
 
 void Voxelization::voxelizeMeshes(
@@ -229,7 +257,6 @@ void Voxelization::voxelizeMeshes(
 	resetVoxelDispatchCount[1] = 1;
 	resetVoxelDispatchCount[2] = 1;
 
-	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
 	m_corePtr->recordComputeDispatchToCmdStream(
 		cmdStream,
 		m_voxelResetPipe,
@@ -249,6 +276,7 @@ void Voxelization::voxelizeMeshes(
 			}));
 	}
 
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImageIntermediate.getHandle());
 	m_corePtr->recordDrawcallsToCmdStream(
 		cmdStream,
 		m_voxelizationPass,
@@ -271,8 +299,26 @@ void Voxelization::voxelizeMeshes(
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).vulkanHandle) },
 		vkcv::PushConstantData(nullptr, 0));
 
+	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImageIntermediate.getHandle());
+
+	// intermediate image mipchain
+	m_voxelImageIntermediate.recordMipChainGeneration(cmdStream);
+	m_corePtr->prepareImageForSampling(cmdStream, m_voxelImageIntermediate.getHandle());
+
+	// secondary bounce
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
+
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_secondaryBouncePipe,
+		bufferToImageDispatchCount,
+		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_secondaryBounceDescriptorSet).vulkanHandle) },
+		vkcv::PushConstantData(nullptr, 0));
+	m_voxelImage.recordMipChainGeneration(cmdStream);
+
 	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle());
 
+	// final image mipchain
 	m_voxelImage.recordMipChainGeneration(cmdStream);
 	m_corePtr->prepareImageForSampling(cmdStream, m_voxelImage.getHandle());
 }
diff --git a/projects/voxelization/src/Voxelization.hpp b/projects/voxelization/src/Voxelization.hpp
index bb0b5c5a..6f8d155d 100644
--- a/projects/voxelization/src/Voxelization.hpp
+++ b/projects/voxelization/src/Voxelization.hpp
@@ -14,7 +14,8 @@ public:
 		const Dependencies& dependencies, 
 		vkcv::BufferHandle  lightInfoBuffer,
 		vkcv::ImageHandle   shadowMap,
-		vkcv::SamplerHandle shadowSampler);
+		vkcv::SamplerHandle shadowSampler,
+		vkcv::SamplerHandle voxelSampler);
 
 	void voxelizeMeshes(
 		vkcv::CommandStreamHandle                       cmdStream,
@@ -39,11 +40,14 @@ private:
 	vkcv::Core* m_corePtr;
 
 	struct VoxelBufferContent{
-		uint32_t isFilled;
+		uint32_t lightEncoded;
+		uint32_t normalEncoded;
+		uint32_t albedoEncoded;
 	};
 
+	vkcv::Image                         m_voxelImageIntermediate;
 	vkcv::Image                         m_voxelImage;
-    vkcv::Buffer<VoxelBufferContent>    m_voxelBuffer;
+	vkcv::Buffer<VoxelBufferContent>    m_voxelBuffer;
 
 	vkcv::Image                 m_dummyRenderTarget;
 	vkcv::PassHandle            m_voxelizationPass;
@@ -59,6 +63,9 @@ private:
 	vkcv::PassHandle            m_visualisationPass;
 	vkcv::PipelineHandle        m_visualisationPipe;
 
+	vkcv::PipelineHandle        m_secondaryBouncePipe;
+	vkcv::DescriptorSetHandle   m_secondaryBounceDescriptorSet;
+
 	vkcv::DescriptorSetHandle   m_visualisationDescriptorSet;
 
 	struct VoxelizationInfo {
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index 57e25066..bd6bc22a 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -340,6 +340,12 @@ int main(int argc, const char** argv) {
 		shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {}));
 	}
 
+	vkcv::SamplerHandle voxelSampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::CLAMP_TO_EDGE);
+
 	Voxelization::Dependencies voxelDependencies;
 	voxelDependencies.colorBufferFormat = colorBufferFormat;
 	voxelDependencies.depthBufferFormat = depthBufferFormat;
@@ -349,16 +355,11 @@ int main(int argc, const char** argv) {
 		voxelDependencies,
 		lightBuffer.getHandle(),
 		shadowMap.getHandle(),
-		shadowSampler);
+		shadowSampler,
+		voxelSampler);
 
 	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 = {
-- 
GitLab