diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 5a051fb5783ccc3d89324582350108841abb6263..8ffe501747fe516de9ab10834de788f110ad8722 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -31,7 +31,7 @@ int main(int argc, const char** argv) {
 
 	vkcv::asset::Scene mesh;
 
-	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
 	int result = vkcv::asset::loadScene(path, mesh);
 
 	if (result == 1) {
@@ -45,19 +45,19 @@ int main(int argc, const char** argv) {
 	assert(!mesh.vertexGroups.empty());
 	auto vertexBuffer = core.createBuffer<uint8_t>(
 			vkcv::BufferType::VERTEX,
-			mesh.vertexGroups[2].vertexBuffer.data.size(),
+			mesh.vertexGroups[0].vertexBuffer.data.size(),
 			vkcv::BufferMemoryType::DEVICE_LOCAL
 	);
 	
-	vertexBuffer.fill(mesh.vertexGroups[2].vertexBuffer.data);
+	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
 
 	auto indexBuffer = core.createBuffer<uint8_t>(
 			vkcv::BufferType::INDEX,
-			mesh.vertexGroups[2].indexBuffer.data.size(),
+			mesh.vertexGroups[0].indexBuffer.data.size(),
 			vkcv::BufferMemoryType::DEVICE_LOCAL
 	);
 	
-	indexBuffer.fill(mesh.vertexGroups[2].indexBuffer.data);
+	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
 
 	// an example attachment for passes that output to the window
 	const vkcv::AttachmentDescription present_color_attachment(
@@ -84,7 +84,7 @@ int main(int argc, const char** argv) {
     firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
     firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
 	
-	auto& attributes = mesh.vertexGroups[2].vertexBuffer.attributes;
+	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
 	
 	std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
 		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
@@ -120,7 +120,7 @@ int main(int argc, const char** argv) {
 	
 	// FIXME There should be a test here to make sure there is at least 1
 	// texture in the mesh.
-	vkcv::asset::Texture &tex = mesh.textures[2];
+	vkcv::asset::Texture &tex = mesh.textures[0];
 	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
 	texture.fill(tex.data.data());
 
@@ -145,7 +145,7 @@ int main(int argc, const char** argv) {
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
-	const vkcv::Mesh renderMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[2].numIndices);
+	const vkcv::Mesh renderMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
 
 	vkcv::DescriptorSetUsage    descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
 	vkcv::DrawcallInfo          drawcall(renderMesh, { descriptorUsage });
diff --git a/projects/first_scene/resources/Sponza/Sponza.bin b/projects/first_scene/resources/Sponza/Sponza.bin
index 1e9e8d5e275f0990f3dc36eb71a1636b0056c5b4..d82bf62514ae745dd5ba264826018f8606ca84a5 100644
--- a/projects/first_scene/resources/Sponza/Sponza.bin
+++ b/projects/first_scene/resources/Sponza/Sponza.bin
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:8201cff0dda3905de8032e03b86f51e609c34882b8ebd2d69c8a138e0b3d345e
+oid sha256:c59c3abc98b4c7f175917347fd5e945be793ac20d19ec3b9300431895454d8d4
 size 5758512
diff --git a/projects/first_scene/resources/Sponza/Sponza.gltf b/projects/first_scene/resources/Sponza/Sponza.gltf
index d8e5491c4c70e1906ab390652b47568d2c779cc1..011646f909a0e41ff26dfd02268d11158018f999 100644
--- a/projects/first_scene/resources/Sponza/Sponza.gltf
+++ b/projects/first_scene/resources/Sponza/Sponza.gltf
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:65179ea2afaee94d2e369ca423a295580b41d68a5d68f972777d7118e86d856d
-size 767861
+oid sha256:34412196de818da80f0ff4e6249ccda9f0178aff41d3922f9d089e1050671a8e
+size 768569
diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp
index 8b805d84970a33e7fe257da09453edc9484dae8e..209820f62023ab940c076f9bd0fa82eeb029bdb4 100644
--- a/projects/first_scene/src/main.cpp
+++ b/projects/first_scene/src/main.cpp
@@ -29,7 +29,12 @@ int main(int argc, const char** argv) {
 		true
 	);
 
-	vkcv::CameraManager cameraManager(window, static_cast<float>(window.getWidth()), static_cast<float>(window.getHeight()));
+	//vkcv::CameraManager cameraManager(window, static_cast<float>(window.getWidth()), static_cast<float>(window.getHeight()));
+	vkcv::camera::CameraManager cameraManager(window);
+	uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
 
 	window.initEvents();
 
@@ -57,31 +62,31 @@ int main(int argc, const char** argv) {
 
 	assert(!scene.vertexGroups.empty());
 	std::vector<std::vector<uint8_t>> vBuffers;
-    std::vector<std::vector<uint8_t>> iBuffers;
+	std::vector<std::vector<uint8_t>> iBuffers;
 
-    std::vector<vkcv::VertexBufferBinding> vBufferBindings;
-    std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
-    std::vector<vkcv::VertexAttribute> vAttributes;
+	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
+	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
+	std::vector<vkcv::asset::VertexAttribute> vAttributes;
 
-    for (int i = 0; i < scene.vertexGroups.size(); i++){
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
 
-        vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
-        iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
+		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
+		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
 
-        auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
+		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
 
-        std::sort(attributes.begin(), attributes.end(), [](const vkcv::VertexAttribute& x, const vkcv::VertexAttribute& y) {
-            return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-        });
-    }
+		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+			});
+	}
 
-    std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
-    for(const vkcv::asset::VertexGroup &group : scene.vertexGroups){
-        vertexBuffers.push_back(core.createBuffer<uint8_t>(
-            vkcv::BufferType::VERTEX,
-            group.vertexBuffer.data.size()));
-        vertexBuffers.back().fill(group.vertexBuffer.data);
-    }
+	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
+	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
+		vertexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			group.vertexBuffer.data.size()));
+		vertexBuffers.back().fill(group.vertexBuffer.data);
+	}
 
 	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
 	for (const auto& dataBuffer : iBuffers) {
@@ -91,16 +96,16 @@ int main(int argc, const char** argv) {
 		indexBuffers.back().fill(dataBuffer);
 	}
 
-    int vertexBufferIndex = 0;
-    for (const auto &vertexGroup : scene.vertexGroups){
-        for (const auto &attribute : vertexGroup.vertexBuffer.attributes){
-            vAttributes.push_back(attribute);
-            vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
-        }
-        vertexBufferBindings.push_back(vBufferBindings);
-        vBufferBindings.clear();
-        vertexBufferIndex++;
-    }
+	int vertexBufferIndex = 0;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+			vAttributes.push_back(attribute);
+			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
+		}
+		vertexBufferBindings.push_back(vBufferBindings);
+		vBufferBindings.clear();
+		vertexBufferIndex++;
+	}
 
 	// an example attachment for passes that output to the window
 	const vkcv::AttachmentDescription present_color_attachment(
@@ -108,11 +113,11 @@ int main(int argc, const char** argv) {
 		vkcv::AttachmentOperation::CLEAR,
 		core.getSwapchainImageFormat()
 	);
-	
+
 	const vkcv::AttachmentDescription depth_attachment(
-			vkcv::AttachmentOperation::STORE,
-			vkcv::AttachmentOperation::CLEAR,
-			vk::Format::eD32Sfloat
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		vk::Format::eD32Sfloat
 	);
 
 	vkcv::PassConfig scenePassDefinition({ present_color_attachment, depth_attachment });
@@ -125,53 +130,62 @@ int main(int argc, const char** argv) {
 
 	vkcv::ShaderProgram sceneShaderProgram{};
 	sceneShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-    sceneShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
-    sceneShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
-    sceneShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+	sceneShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+	//sceneShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
+	//sceneShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+
+	const std::vector<vkcv::VertexAttachment> vertexAttachments = sceneShaderProgram.getVertexAttachments();
+	std::vector<vkcv::VertexBinding> bindings;
+	for (size_t i = 0; i < vertexAttachments.size(); i++) {
+		bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+	}
+
+	const vkcv::VertexLayout sceneLayout(bindings);
 
 	uint32_t setID = 0;
+
 	std::vector<vkcv::DescriptorBinding> descriptorBindings = { sceneShaderProgram.getReflectedDescriptors()[setID] };
 
-    vkcv::SamplerHandle sampler = core.createSampler(
-        vkcv::SamplerFilterType::LINEAR,
-        vkcv::SamplerFilterType::LINEAR,
-        vkcv::SamplerMipmapMode::LINEAR,
-        vkcv::SamplerAddressMode::REPEAT
-    );
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
 
-    std::vector<vkcv::Image> sceneImages;
-    std::vector<vkcv::DescriptorSetHandle> descriptorSets;
-    for (const auto& vertexGroup : scene.vertexGroups) {
-        descriptorSets.push_back(core.createDescriptorSet(descriptorBindings));
+	std::vector<vkcv::Image> sceneImages;
+	std::vector<vkcv::DescriptorSetHandle> descriptorSets;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		descriptorSets.push_back(core.createDescriptorSet(descriptorBindings));
 
-        const auto &material = scene.materials[vertexGroup.materialIndex];
+		const auto& material = scene.materials[vertexGroup.materialIndex];
 
-        int baseColorIndex = material.baseColor;
-        if (baseColorIndex < 0) {
-            vkcv_log(vkcv::LogLevel::WARNING ,"Material lacks base color");
-            baseColorIndex = 0;
-        }
+		int baseColorIndex = material.baseColor;
+		if (baseColorIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
+			baseColorIndex = 0;
+		}
 
-        vkcv::asset::Texture &sceneTexture = scene.textures[baseColorIndex];
+		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
 
-        sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
-        sceneImages.back().fill(sceneTexture.data.data());
+		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
+		sceneImages.back().fill(sceneTexture.data.data());
 
-        vkcv::DescriptorWrites setWrites;
-        setWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) };
-        setWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, sampler) };
-        core.writeDescriptorSet(descriptorSets.back(), setWrites);
-    }
+		vkcv::DescriptorWrites setWrites;
+		setWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) };
+		setWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, sampler) };
+		core.writeDescriptorSet(descriptorSets.back(), setWrites);
+	}
 
-	const vkcv::PipelineConfig scenePipelineDefinition(
+	const vkcv::PipelineConfig scenePipelineDefsinition{
 		sceneShaderProgram,
-        UINT32_MAX,
-        UINT32_MAX,
+		UINT32_MAX,
+		UINT32_MAX,
 		scenePass,
-		vAttributes,
+		{sceneLayout},
 		{ core.getDescriptorSet(descriptorSets[0]).layout },
-		true);
-	vkcv::PipelineHandle scenePipeline = core.createGraphicsPipeline(scenePipelineDefinition);
+		true };
+	vkcv::PipelineHandle scenePipeline = core.createGraphicsPipeline(scenePipelineDefsinition);
 	
 	if (!scenePipeline) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
@@ -223,8 +237,8 @@ int main(int argc, const char** argv) {
 		auto end = std::chrono::system_clock::now();
 		auto deltatime = end - start;
 		start = end;
-		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
-		const glm::mat4 vp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		glm::mat4 vp = cameraManager.getActiveCamera().getMVP();
 
 		mvp.clear();
         for (const auto& m : modelMatrices) {