diff --git a/projects/mesh_shader/resources/shaders/meshlet.inc b/projects/mesh_shader/resources/shaders/meshlet.inc
new file mode 100644
index 0000000000000000000000000000000000000000..0594f62ceead8ffca09b585305075eb6046f3c46
--- /dev/null
+++ b/projects/mesh_shader/resources/shaders/meshlet.inc
@@ -0,0 +1,8 @@
+struct Meshlet{
+    uint    vertexOffset;
+    uint    vertexCount;
+    uint    indexOffset;
+    uint    indexCount;
+    vec3    meanPosition;
+    float   boundingSphereRadius;
+};
\ No newline at end of file
diff --git a/projects/mesh_shader/resources/shaders/shader.mesh b/projects/mesh_shader/resources/shaders/shader.mesh
index 97e73cbddb3ed222ef44b72ba4275ffa1c58ff6d..30c98610f4776204ff526c57c1f793e371194629 100644
--- a/projects/mesh_shader/resources/shaders/shader.mesh
+++ b/projects/mesh_shader/resources/shaders/shader.mesh
@@ -1,16 +1,15 @@
 #version 460
 #extension GL_ARB_separate_shader_objects   : enable
+#extension GL_GOOGLE_include_directive      : enable
 #extension GL_NV_mesh_shader                : require
 
+#include "meshlet.inc"
+
 layout(local_size_x=32) in;
 
 layout(triangles) out;
 layout(max_vertices=64, max_primitives=126) out;
 
-layout( push_constant ) uniform constants{
-    mat4 mvp;
-};
-
 layout(location = 0) out vec3 passNormal[];
 layout(location = 1) out uint passTaskIndex[];
 
@@ -30,15 +29,6 @@ layout(std430, binding = 1) readonly buffer indexBuffer
     uint localIndices[]; // breaks for 16 bit indices
 };
 
-struct Meshlet{
-    uint    vertexOffset;
-    uint    vertexCount;
-    uint    indexOffset;
-    uint    indexCount;
-    vec3    meanPosition;
-    float   boundingSphereRadius;
-};
-
 layout(std430, binding = 2) readonly buffer meshletBuffer
 {
     Meshlet meshlets[];
@@ -46,6 +36,7 @@ layout(std430, binding = 2) readonly buffer meshletBuffer
 
 taskNV in Task {
   uint meshletIndices[32];
+  mat4 mvp;
 } IN;
 
 void main()	{
@@ -64,7 +55,7 @@ void main()	{
         uint vertexIndex    = meshlet.vertexOffset + workIndex;
         Vertex vertex       = vertices[vertexIndex];
     
-        gl_MeshVerticesNV[workIndex].gl_Position    = mvp * vec4(vertex.position, 1);
+        gl_MeshVerticesNV[workIndex].gl_Position    = IN.mvp * vec4(vertex.position, 1);
         passNormal[workIndex]                       = vertex.normal;
         passTaskIndex[workIndex]                    = meshletIndex;
     }
diff --git a/projects/mesh_shader/resources/shaders/shader.task b/projects/mesh_shader/resources/shaders/shader.task
index 0ac1169eca1507eb49231a7174530631fa2e8ecd..3f666fbbe6e4031ff2063171992c375301e45c7e 100644
--- a/projects/mesh_shader/resources/shaders/shader.task
+++ b/projects/mesh_shader/resources/shaders/shader.task
@@ -1,24 +1,79 @@
 #version 460
 #extension GL_ARB_separate_shader_objects   : enable
 #extension GL_NV_mesh_shader                : require
+#extension GL_GOOGLE_include_directive      : enable
+
+#include "meshlet.inc"
 
 layout(local_size_x=32) in;
 
 taskNV out Task {
   uint meshletIndices[32];
+  mat4 mvp;
 } OUT;
 
 layout( push_constant ) uniform constants{
-    mat4 mvp;
     uint meshletCount;
+    uint matrixIndex;
+};
+
+layout(std430, binding = 2) readonly buffer meshletBuffer
+{
+    Meshlet meshlets[];
+};
+
+struct Plane{
+    vec3 pointOnPlane;
+    float padding0;
+    vec3 normal;
+    float padding1;
+};
+
+layout(set=0, binding=3, std140) uniform cameraPlaneBuffer{
+    Plane cameraPlanes[6];
+};
+
+struct ObjectMatrices{
+    mat4 model;
+    mat4 mvp;
 };
 
+layout(std430, binding = 4) readonly buffer matrixBuffer
+{
+    ObjectMatrices objectMatrices[];
+};
+
+shared uint taskCount;
+
+bool isSphereInsideFrustum(vec3 spherePos, float sphereRadius, Plane cameraPlanes[6]){
+    bool isInside = true;
+    for(int i = 0; i < 6; i++){
+        Plane p     = cameraPlanes[i];
+        isInside    = isInside && dot(p.normal, spherePos - p.pointOnPlane) + sphereRadius < 0;
+    }
+    return isInside;
+}
+
 void main() {
+
+    if(gl_LocalInvocationID.x >= meshletCount){
+        return;
+    }
+    
+    uint meshletIndex   = gl_GlobalInvocationID.x;
+    Meshlet meshlet     = meshlets[meshletIndex]; 
+    
+    if(gl_LocalInvocationID.x == 0){
+        taskCount = 0;
+    }
+    
+    if(isSphereInsideFrustum(meshlet.meanPosition, meshlet.boundingSphereRadius, cameraPlanes)){
+        uint outIndex = atomicAdd(taskCount, 1);
+        OUT.meshletIndices[outIndex] = gl_GlobalInvocationID.x;
+    }
+
     if(gl_LocalInvocationID.x == 0){
-        int taskCount              = int(gl_WorkGroupID.x * 32);
-        // use signed ints to avoid underflow
-        int superflousTaskCount    = max(taskCount - int(meshletCount), 0);
-        gl_TaskCountNV              = 32 - superflousTaskCount;
+        gl_TaskCountNV              = taskCount;
+        OUT.mvp = objectMatrices[matrixIndex].mvp;
     }
-    OUT.meshletIndices[gl_LocalInvocationID.x] = gl_GlobalInvocationID.x;
 }
\ No newline at end of file
diff --git a/projects/mesh_shader/src/main.cpp b/projects/mesh_shader/src/main.cpp
index 37855e9b10d5c19a1c91ac66e60196826de0ed23..d08564f3f85eb34b08945fd2039ab308dbfc7f0e 100644
--- a/projects/mesh_shader/src/main.cpp
+++ b/projects/mesh_shader/src/main.cpp
@@ -10,6 +10,70 @@
 #include <vkcv/meshlet/Meshlet.hpp>
 //#include <vkcv/meshlet/Tipsify.hpp>
 
+struct Plane {
+	glm::vec3 pointOnPlane;
+	float padding0;
+	glm::vec3 normal;
+	float padding1;
+};
+
+struct CameraPlanes {
+	Plane planes[6];
+};
+
+CameraPlanes computeCameraPlanes(const vkcv::camera::Camera& camera) {
+	const float     fov     = camera.getFov();
+	const glm::vec3 pos     = camera.getPosition();
+	const float     ratio   = camera.getRatio();
+	const glm::vec3 forward = glm::normalize(camera.getFront());
+	float near;
+	float far;
+	camera.getNearFar(near, far);
+
+	glm::vec3 up    = glm::dot(forward, glm::vec3(0, -1, 0)) < 0.99 ? glm::vec3(0, -1, 0) : glm::vec3(1, 0, 0);
+	glm::vec3 right = glm::normalize(glm::cross(forward, up));
+	up              = glm::cross(forward, right);
+
+	const glm::vec3 nearCenter      = pos + forward * near;
+	const glm::vec3 farCenter       = pos + forward * far;
+
+	const float tanFovHalf          = glm::tan(fov / 2);
+
+	const glm::vec3 nearUpCenter    = nearCenter + up    * tanFovHalf * near;
+	const glm::vec3 nearDownCenter  = nearCenter - up    * tanFovHalf * near;
+	const glm::vec3 nearRightCenter = nearCenter + right * tanFovHalf * near * ratio;
+	const glm::vec3 nearLeftCenter  = nearCenter - right * tanFovHalf * near * ratio;
+
+	const glm::vec3 farUpCenter     = farCenter + up    * tanFovHalf * far;
+	const glm::vec3 farDownCenter   = farCenter - up    * tanFovHalf * far;
+	const glm::vec3 farRightCenter  = farCenter + right * tanFovHalf * far * ratio;
+	const glm::vec3 farLeftCenter   = farCenter - right * tanFovHalf * far * ratio;
+
+	CameraPlanes cameraPlanes;
+	// near
+	cameraPlanes.planes[0].pointOnPlane  = nearCenter;
+	cameraPlanes.planes[0].normal           = -forward;
+	// far
+	cameraPlanes.planes[1].pointOnPlane  = farCenter;
+	cameraPlanes.planes[1].normal           = forward;
+
+	// top
+	cameraPlanes.planes[2].pointOnPlane  = nearUpCenter;
+	cameraPlanes.planes[2].normal           = glm::normalize(glm::cross(farUpCenter - nearUpCenter, right));
+	// bot
+	cameraPlanes.planes[3].pointOnPlane  = nearDownCenter;
+	cameraPlanes.planes[3].normal           = glm::normalize(glm::cross(right, farDownCenter - nearDownCenter));
+
+	// right
+	cameraPlanes.planes[4].pointOnPlane  = nearRightCenter;
+	cameraPlanes.planes[4].normal           = glm::normalize(glm::cross(up, farRightCenter - nearRightCenter));
+	// left
+	cameraPlanes.planes[5].pointOnPlane  = nearLeftCenter;
+	cameraPlanes.planes[5].normal           = glm::normalize(glm::cross(farLeftCenter - nearLeftCenter, up));
+
+	return cameraPlanes;
+}
+
 int main(int argc, const char** argv) {
 	const char* applicationName = "Mesh shader";
 
@@ -194,11 +258,25 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 
+	vkcv::Buffer<CameraPlanes> cameraPlaneBuffer = core.createBuffer<CameraPlanes>(vkcv::BufferType::UNIFORM, 1);
+
+	struct ObjectMatrices {
+		glm::mat4 model;
+		glm::mat4 mvp;
+	};
+	const size_t objectCount = 1;
+	vkcv::Buffer<ObjectMatrices> matrixBuffer = core.createBuffer<ObjectMatrices>(vkcv::BufferType::STORAGE, objectCount);
+
     vkcv::DescriptorWrites meshShaderWrites;
 	meshShaderWrites.storageBufferWrites = {
-		vkcv::StorageBufferDescriptorWrite(0, meshShaderVertexBuffer.getHandle()), 
+		vkcv::StorageBufferDescriptorWrite(0, meshShaderVertexBuffer.getHandle()),
 		vkcv::StorageBufferDescriptorWrite(1, meshShaderIndexBuffer.getHandle()),
-		vkcv::StorageBufferDescriptorWrite(2, meshletBuffer.getHandle()) };
+		vkcv::StorageBufferDescriptorWrite(2, meshletBuffer.getHandle()),
+		vkcv::StorageBufferDescriptorWrite(4, matrixBuffer.getHandle()) 
+	};
+	meshShaderWrites.uniformBufferWrites = {
+		vkcv::UniformBufferDescriptorWrite(3, cameraPlaneBuffer.getHandle())
+	};
 
     core.writeDescriptorSet( meshShaderDescriptorSet, meshShaderWrites);
 
@@ -212,14 +290,14 @@ int main(int argc, const char** argv) {
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
-    vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	vkcv::camera::CameraManager cameraManager(window);
+	uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 	
 	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2));
 
 	while (window.isWindowOpen())
 	{
-        vkcv::Window::pollEvents();
+		vkcv::Window::pollEvents();
 
 		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
 		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
@@ -232,14 +310,22 @@ int main(int argc, const char** argv) {
 		
 		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
 
-		glm::mat4 modelMatrix = *reinterpret_cast<glm::mat4*>(&mesh.meshes.front().modelMatrix);
-		glm::mat4 mvp = cameraManager.getActiveCamera().getMVP() * modelMatrix;
+		const vkcv::camera::Camera& camera = cameraManager.getActiveCamera();
+
+		ObjectMatrices objectMatrices;
+		objectMatrices.model    = *reinterpret_cast<glm::mat4*>(&mesh.meshes.front().modelMatrix);
+		objectMatrices.mvp      = camera.getMVP() * objectMatrices.model;
+
+		matrixBuffer.fill({ objectMatrices });
 
 		struct PushConstants {
-			glm::mat4 mvp;
 			uint32_t meshletCount;
+			uint32_t matrixIndex;
 		};
-		PushConstants pushConstants{ mvp, meshShaderModelData.meshlets.size() };
+		PushConstants pushConstants{ meshShaderModelData.meshlets.size(), 0 };
+
+		const CameraPlanes cameraPlanes = computeCameraPlanes(camera);
+		cameraPlaneBuffer.fill({ cameraPlanes });
 
 		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);