diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 1e036f799d6769a88e4350afdf182ef397400907..77370fb39b1a5a300614fe9cfb0b505e5ef2dd67 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -207,7 +207,7 @@ namespace vkcv
         [[nodiscard]]
         SamplerHandle createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
 									SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode,
-									float mipLodBias = 0.0f);
+									float mipLodBias = 0.0f, SamplerBorderColor borderColor = SamplerBorderColor::INT_ZERO_OPAQUE);
 
         /**
          * Creates an #Image with a given format, width, height and depth.
diff --git a/include/vkcv/Sampler.hpp b/include/vkcv/Sampler.hpp
index 007ed5ae4737275ddacbc5881a2c4c202b8806a4..e4f10cd9d3f1dd60021e62842acaa07d2aefb5ce 100644
--- a/include/vkcv/Sampler.hpp
+++ b/include/vkcv/Sampler.hpp
@@ -16,7 +16,19 @@ namespace vkcv {
 		REPEAT = 1,
 		MIRRORED_REPEAT = 2,
 		CLAMP_TO_EDGE = 3,
-		MIRROR_CLAMP_TO_EDGE = 4
+		MIRROR_CLAMP_TO_EDGE = 4,
+		CLAMP_TO_BORDER = 5
+	};
+	
+	enum class SamplerBorderColor {
+		INT_ZERO_OPAQUE = 1,
+		INT_ZERO_TRANSPARENT = 2,
+		
+		FLOAT_ZERO_OPAQUE = 3,
+		FLOAT_ZERO_TRANSPARENT = 4,
+		
+		INT_ONE_OPAQUE = 5,
+		FLOAT_ONE_OPAQUE = 6
 	};
 
 }
diff --git a/projects/wobble_bobble/shaders/grid.frag b/projects/wobble_bobble/shaders/grid.frag
index 0d616c0390a59a3fffda6a468340d318c8f6b279..d204843203422f5d0668e1c0470eafd7f266ac0f 100644
--- a/projects/wobble_bobble/shaders/grid.frag
+++ b/projects/wobble_bobble/shaders/grid.frag
@@ -1,7 +1,8 @@
 #version 450
 
 layout(location = 0) in vec2 passPos;
-layout(location = 1) in float passMass;
+layout(location = 1) in vec3 passVelocity;
+layout(location = 2) in float passMass;
 
 layout(location = 0) out vec3 outColor;
 
@@ -15,7 +16,7 @@ void main()	{
     float z = sqrt(0.25 - value * value);
 
     if (value < 0.5f) {
-        outColor = vec3(passPos.x + 0.5f, passPos.y + 0.5f, z * 2.0f);
+        outColor = (passVelocity.xyz - passVelocity.yzx) * 100.0f; // vec3(passPos.x + 0.5f, passPos.y + 0.5f, z * 2.0f);
     } else {
         discard;
     }
diff --git a/projects/wobble_bobble/shaders/grid.vert b/projects/wobble_bobble/shaders/grid.vert
index 61f9f9b764b61927596f499f7f766d377efb40e3..b504f81a63799afb657bd09db1de48a1df622eac 100644
--- a/projects/wobble_bobble/shaders/grid.vert
+++ b/projects/wobble_bobble/shaders/grid.vert
@@ -6,7 +6,8 @@ layout(set=0, binding=1) uniform sampler gridSampler;
 layout(location = 0) in vec2 vertexPos;
 
 layout(location = 0) out vec2 passPos;
-layout(location = 1) out float passMass;
+layout(location = 1) out vec3 passVelocity;
+layout(location = 2) out float passMass;
 
 layout( push_constant ) uniform constants{
     mat4 mvp;
@@ -35,6 +36,7 @@ void main()	{
     float mass = gridData.w;
 
     passPos = vertexPos;
+    passVelocity = gridData.xyz;
     passMass = mass;
 
     // align voxel to face camera
diff --git a/projects/wobble_bobble/shaders/particle.inc b/projects/wobble_bobble/shaders/particle.inc
index 00b7ab27d4e6fe205ec8c695be9cc3d35b36a7e0..6ce314ab8da3a8bb0a780ddaad09ecc75bf2e51c 100644
--- a/projects/wobble_bobble/shaders/particle.inc
+++ b/projects/wobble_bobble/shaders/particle.inc
@@ -51,4 +51,49 @@ float voxel_particle_weight(vec3 voxel, ParticleMinimal particle) {
 	return weight_C(delta.x) * weight_C(delta.y) * weight_C(delta.z);
 }
 
+float grad_weight_A(float x) {
+	return -1.0f;
+}
+
+float grad_weight_B(float x) {
+	if (x < 0.5f) {
+		return -2.0f * x;
+	} else
+	if (x < 1.5f) {
+		return -1.5f + x;
+	} else {
+		return 0.0f;
+	}
+}
+
+float grad_weight_C(float x) {
+	if (x < 1.0f) {
+		return 1.5f * x * x - 2.0f * x;
+	} else
+	if (x < 2.0f) {
+		float y = (2.0f - x);
+		return -0.5f * y * y;
+	} else {
+		return 0.0f;
+	}
+}
+
+float voxel_particle_grad_weight(vec3 voxel, ParticleMinimal particle) {
+	if (particle.size <= 0.0f) {
+		return 0.0f;
+	}
+	
+	vec3 sign_delta = (particle.position - voxel) / particle.size;
+	vec3 delta = abs(sign_delta);
+	vec3 sign = sign(sign_delta);
+	
+	vec3 weight = vec3(
+		weight_C(delta.x),
+		weight_C(delta.y),
+		weight_C(delta.z)
+	);
+	
+	return dot(weight, sign);
+}
+
 #endif // PARTICLE_INC
\ No newline at end of file
diff --git a/projects/wobble_bobble/shaders/update_grid_forces.comp b/projects/wobble_bobble/shaders/update_grid_forces.comp
index 4920e493fab3f26d40a59aa5cdb6fc41a6892917..2010bf16e23901089f3a92a80754e925f687a070 100644
--- a/projects/wobble_bobble/shaders/update_grid_forces.comp
+++ b/projects/wobble_bobble/shaders/update_grid_forces.comp
@@ -64,11 +64,15 @@ void main()	{
             mat3 delta = elasticity_module * epsilon;
             mat3 delta_cauchy = determinant(deformation) * delta * inverse(transpose(deformation));
 
-            force -= (
+            vec3 weight_gradient = vec3(
+                voxel_particle_grad_weight(position, shared_particles[gl_LocalInvocationIndex].minimal)
+            );
+
+            /*force -= (
                 volume *
                 delta_cauchy *
-                vec3(voxel_particle_weight(position, shared_particles[gl_LocalInvocationIndex].minimal))
-            );
+                weight_gradient
+            );*/
         }
     }
 
diff --git a/projects/wobble_bobble/shaders/update_particle_velocities.comp b/projects/wobble_bobble/shaders/update_particle_velocities.comp
index acf72765aa2b1504f2ff9e931599af1c47cb9c1e..14a65bf9e186c831d834e74d954d769b0f08f21e 100644
--- a/projects/wobble_bobble/shaders/update_particle_velocities.comp
+++ b/projects/wobble_bobble/shaders/update_particle_velocities.comp
@@ -25,6 +25,10 @@ void main()	{
         vec3 velocity_pic = vec3(0.0f);
         uint i, j, k;
 
+        //for (i = 0; i < gridResolution.x; i++) {
+        //    for (j = 0; j < gridResolution.y; j++) {
+        //        for (k = 0; k < gridResolution.z; k++) {
+        //          vec3 voxel = vec3(i, j, k) / gridResolution;
         for (i = -gridWindow.x; i <= gridWindow.x; i++) {
             for (j = -gridWindow.y; j <= gridWindow.y; j++) {
                 for (k = -gridWindow.z; k <= gridWindow.z; k++) {
@@ -39,7 +43,7 @@ void main()	{
             }
         }
 
-        particles[gl_GlobalInvocationID.x].minimal.velocity = velocity_pic;
+        //particles[gl_GlobalInvocationID.x].minimal.velocity = velocity_pic;
     }
 
     memoryBarrierBuffer();
diff --git a/projects/wobble_bobble/src/main.cpp b/projects/wobble_bobble/src/main.cpp
index 4fae63957d298cd6f2881a6874300df1249115f9..f4ce59aae398352d1fa30ae032980d0c5378341d 100644
--- a/projects/wobble_bobble/src/main.cpp
+++ b/projects/wobble_bobble/src/main.cpp
@@ -40,7 +40,7 @@ void distributeParticles(Particle *particles, size_t count, const glm::vec3& cen
 		
 		particles[i].position = center + offset;
 		particles[i].size = size;
-		particles[i].velocity = glm::vec3(0.0f);
+		particles[i].velocity = glm::vec3(0.0f, 0.01f, 0.0f);
 		
 		volume += size;
 	}
@@ -167,7 +167,9 @@ int main(int argc, const char **argv) {
 			vkcv::SamplerFilterType::LINEAR,
 			vkcv::SamplerFilterType::LINEAR,
 			vkcv::SamplerMipmapMode::NEAREST,
-			vkcv::SamplerAddressMode::REPEAT
+			vkcv::SamplerAddressMode::CLAMP_TO_BORDER,
+			0.0f,
+			vkcv::SamplerBorderColor::FLOAT_ZERO_TRANSPARENT
 	);
 	
 	vkcv::shader::GLSLCompiler compiler;
@@ -411,7 +413,7 @@ int main(int argc, const char **argv) {
 	));
 	
 	bool initializedParticleVolumes = false;
-	bool renderGrid = false;
+	bool renderGrid = true;
 	
 	auto start = std::chrono::system_clock::now();
 	auto current = start;
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 12d2090de47e5d002734c9a44773affdb4f99b3e..25b461ce9c984a5c125154b14416cf358546d7d2 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -763,8 +763,8 @@ namespace vkcv
 
 	SamplerHandle Core::createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
 									  SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode,
-									  float mipLodBias) {
-		return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode, mipLodBias);
+									  float mipLodBias, SamplerBorderColor borderColor) {
+		return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode, mipLodBias, borderColor);
 	}
 
 	Image Core::createImage(
diff --git a/src/vkcv/SamplerManager.cpp b/src/vkcv/SamplerManager.cpp
index 792e6f16b4a05af41a164a1eda9dd7423594857e..9a80635744e5a3dd0b6bd8db476cec841b1c317d 100644
--- a/src/vkcv/SamplerManager.cpp
+++ b/src/vkcv/SamplerManager.cpp
@@ -18,11 +18,13 @@ namespace vkcv {
 												SamplerFilterType minFilter,
 												SamplerMipmapMode mipmapMode,
 												SamplerAddressMode addressMode,
-												float mipLodBias) {
+												float mipLodBias,
+												SamplerBorderColor borderColor) {
 		vk::Filter vkMagFilter;
 		vk::Filter vkMinFilter;
 		vk::SamplerMipmapMode vkMipmapMode;
 		vk::SamplerAddressMode vkAddressMode;
+		vk::BorderColor vkBorderColor;
 		
 		switch (magFilter) {
 			case SamplerFilterType::NEAREST:
@@ -70,6 +72,32 @@ namespace vkcv {
 			case SamplerAddressMode::MIRROR_CLAMP_TO_EDGE:
 				vkAddressMode = vk::SamplerAddressMode::eMirrorClampToEdge;
 				break;
+			case SamplerAddressMode::CLAMP_TO_BORDER:
+				vkAddressMode = vk::SamplerAddressMode::eClampToBorder;
+				break;
+			default:
+				return SamplerHandle();
+		}
+		
+		switch (borderColor) {
+			case SamplerBorderColor::INT_ZERO_OPAQUE:
+				vkBorderColor = vk::BorderColor::eIntOpaqueBlack;
+				break;
+			case SamplerBorderColor::INT_ZERO_TRANSPARENT:
+				vkBorderColor = vk::BorderColor::eIntTransparentBlack;
+				break;
+			case SamplerBorderColor::FLOAT_ZERO_OPAQUE:
+				vkBorderColor = vk::BorderColor::eFloatOpaqueBlack;
+				break;
+			case SamplerBorderColor::FLOAT_ZERO_TRANSPARENT:
+				vkBorderColor = vk::BorderColor::eFloatTransparentBlack;
+				break;
+			case SamplerBorderColor::INT_ONE_OPAQUE:
+				vkBorderColor = vk::BorderColor::eIntOpaqueWhite;
+				break;
+			case SamplerBorderColor::FLOAT_ONE_OPAQUE:
+				vkBorderColor = vk::BorderColor::eFloatOpaqueWhite;
+				break;
 			default:
 				return SamplerHandle();
 		}
@@ -89,7 +117,7 @@ namespace vkcv {
 				vk::CompareOp::eAlways,
 				-1000.0f,
 				1000.0f,
-				vk::BorderColor::eIntOpaqueBlack,
+				vkBorderColor,
 				false
 		);
 		
diff --git a/src/vkcv/SamplerManager.hpp b/src/vkcv/SamplerManager.hpp
index aea47a03714b417314a09dfc0be855df31fbb557..128faa711993ac052cf774a1d31144d19362658f 100644
--- a/src/vkcv/SamplerManager.hpp
+++ b/src/vkcv/SamplerManager.hpp
@@ -30,10 +30,11 @@ namespace vkcv {
 		SamplerManager& operator=(SamplerManager&& other) = delete;
 		
 		SamplerHandle createSampler(SamplerFilterType magFilter,
-							  		SamplerFilterType minFilter,
-							  		SamplerMipmapMode mipmapMode,
-							  		SamplerAddressMode addressMode,
-							  		float mipLodBias);
+									SamplerFilterType minFilter,
+									SamplerMipmapMode mipmapMode,
+									SamplerAddressMode addressMode,
+									float mipLodBias,
+									SamplerBorderColor borderColor);
 		
 		[[nodiscard]]
 		vk::Sampler getVulkanSampler(const SamplerHandle& handle) const;