diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..620ef7d8332699a31fb89fa5f93c99ad389e262e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,9 @@
+*.blend filter=lfs diff=lfs merge=lfs -text
+*.blend1 filter=lfs diff=lfs merge=lfs -text
+*.bin filter=lfs diff=lfs merge=lfs -text
+*.glb filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.tif filter=lfs diff=lfs merge=lfs -text
+*.psd filter=lfs diff=lfs merge=lfs -text
+*.gltf filter=lfs diff=lfs merge=lfs
diff --git a/.gitignore b/.gitignore
index f64181f0e271d7eba3ba146d8dc9cbf229c5f285..d2bf98a016f588760241f9dc7f90f6197c458404 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,7 @@
+<<<<<<< HEAD
+=======
 
+>>>>>>> develop
 # IDE specific files
 .project
 .cproject
diff --git a/.gitmodules b/.gitmodules
index 323286b592292b798a8b6ca03dde3651dd36239e..983b753744e8767da0ec3c959c32a3766ee346f6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,15 @@
 [submodule "lib/SPIRV-Cross"]
 	path = lib/SPIRV-Cross
 	url = https://github.com/KhronosGroup/SPIRV-Cross.git
+[submodule "modules/asset_loader/lib/fx-gltf"]
+	path = modules/asset_loader/lib/fx-gltf
+	url = https://github.com/jessey-git/fx-gltf.git
+[submodule "modules/asset_loader/lib/json"]
+	path = modules/asset_loader/lib/json
+	url = https://github.com/nlohmann/json.git
+[submodule "modules/asset_loader/lib/stb"]
+	path = modules/asset_loader/lib/stb
+	url = https://github.com/nothings/stb.git
+[submodule "modules/camera/lib/glm"]
+	path = modules/camera/lib/glm
+	url = https://github.com/g-truc/glm.git
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 569efcf20fe84a64205b7060bbb89587cbe579da..ddcb8274be02c5265924bc9b69c39f788ba50132 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,12 +72,15 @@ endif()
 
 # add include directories from dependencies as system includes
 target_include_directories(vkcv SYSTEM BEFORE PRIVATE ${vkcv_includes})
+message(STATUS ${vkcv_includes})
 
 # add the own include directory for public headers
 target_include_directories(vkcv BEFORE PUBLIC ${vkcv_include})
+message(STATUS ${vkcv_include})
 
 # link the framework using all required libraries
 target_link_libraries(vkcv ${vkcv_libraries})
+message(STATUS ${vkcv_libraries})
 
 # add sub-projects/examples as targets
 add_subdirectory(projects)
diff --git a/README.md b/README.md
index 520b6242f86ccc3db6e1de78cf16ec26d550efb2..4289a74308776017f099d048cf749c9693e6609f 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,12 @@
 
 ![Vulkan-Chan](https://gitlab.uni-koblenz.de/uploads/-/system/project/avatar/3712/VulkanChan.jpg)
 
+## Repository
+
+Git LFS is used for bigger resource files like meshes and textures. So you need to install Git LFS and use `git lfs install` after cloning.
+
+More information about Git LFS [here](https://git-lfs.github.com/).
+
 ## Build
 
 Git submodules are used for libraries. 
diff --git a/include/vkcv/BufferManager.hpp b/include/vkcv/BufferManager.hpp
index eb8cb178d1b844fff57892c7312ed71d325900cc..abda720f98f649d64b17a15848ae99320e11ae3e 100644
--- a/include/vkcv/BufferManager.hpp
+++ b/include/vkcv/BufferManager.hpp
@@ -6,6 +6,7 @@
 namespace vkcv
 {
 	enum class BufferType {
+		INDEX,
 		VERTEX,
 		UNIFORM,
 		STORAGE,
@@ -61,6 +62,26 @@ namespace vkcv
 		 */
 		uint64_t createBuffer(BufferType type, size_t size, BufferMemoryType memoryType);
 		
+		/**
+		 * Returns the Vulkan buffer handle of a buffer
+		 * represented by a given buffer handle id.
+		 *
+		 * @param id Buffer handle id
+		 * @return Vulkan buffer handle
+		 */
+		[[nodiscard]]
+		vk::Buffer getBuffer(uint64_t id) const;
+		
+		/**
+		 * Returns the Vulkan device memory handle of a buffer
+		 * represented by a given buffer handle id.
+		 *
+		 * @param id Buffer handle id
+		 * @return Vulkan device memory handle
+		 */
+		[[nodiscard]]
+		vk::DeviceMemory getDeviceMemory(uint64_t id) const;
+		
 		/**
 		 * Fills a buffer represented by a given buffer
 		 * handle id with custom data.
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index e2fc5c587247a915f5bca345f8077f33ee5d8977..66db4776d053352d8ccb2eea5e09c3fb77b68561 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -187,7 +187,7 @@ namespace vkcv
 		 * @brief render a beautiful triangle
 		*/
 		void renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle,
-			const int width, const int height);
+			const int width, const int height, const size_t pushConstantSize, const void* pushConstantData);
 
 		/**
 		 * @brief end recording and present image
diff --git a/include/vkcv/Event.hpp b/include/vkcv/Event.hpp
index 092f271c025e63be80b994b6b3921795c3e99671..0836e836e84ff7dfc4931a7cedd65497bf9a89cf 100644
--- a/include/vkcv/Event.hpp
+++ b/include/vkcv/Event.hpp
@@ -34,8 +34,9 @@ namespace vkcv {
          * adds a function handle to the event to be called
          * @param handle of the function
          */
-        void add(typename event_function<T...>::type handle) {
+        typename event_function<T...>::type add(typename event_function<T...>::type handle) {
             this->m_handles.push_back(handle);
+            return handle;
         }
 
         /**
diff --git a/include/vkcv/Window.hpp b/include/vkcv/Window.hpp
index bb8081c166360e97595ff6994971390e53d6aa32..7428c7c73eb481f7352821faed36257211dfd5bf 100644
--- a/include/vkcv/Window.hpp
+++ b/include/vkcv/Window.hpp
@@ -12,7 +12,7 @@
 struct GLFWwindow;
 
 namespace vkcv {
-	
+
     class Window final {
     private:
         GLFWwindow *m_window;
@@ -32,6 +32,16 @@ namespace vkcv {
          */
         static void onMouseMoveEvent(GLFWwindow *window, double x, double y);
 
+        /**
+         * mouseButton callback for mouse buttons
+         * @param[in] button The [mouse button](@ref buttons) that was pressed or released.
+         * @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`.  Future releases may add more actions.
+         * @param[in] mods Bit field describing which [modifier keys](@ref mods) were held down.
+         */
+        static void onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods);
+
+        static void onMouseScrollEvent(GLFWwindow *callbackWindow, double xoffset, double yoffset);
+
         /**
          * resize callback for the resize option of the window
          * @param[in] window The window that was resized.
@@ -81,7 +91,9 @@ namespace vkcv {
         /**
          * basic events of the window
          */
+        event< int, int, int> e_mouseButton;
         event< double, double > e_mouseMove;
+        event< double, double > e_mouseScroll;
         event< int, int > e_resize;
         event< int, int, int, int > e_key;
 
@@ -129,5 +141,5 @@ namespace vkcv {
          */
         virtual ~Window();
     };
-    
+
 }
\ No newline at end of file
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index 3309d860b300dd378460338d9fece48066c38993..f29ff2fc86c88aa8bae2560f199d3882c9919b65 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -1,3 +1,5 @@
 
 # Add new modules here:
+add_subdirectory(asset_loader)
+add_subdirectory(camera)
 add_subdirectory(testing)
diff --git a/modules/asset_loader/CMakeLists.txt b/modules/asset_loader/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8d4c0d6c104187de2d807cceceff529d83d236d6
--- /dev/null
+++ b/modules/asset_loader/CMakeLists.txt
@@ -0,0 +1,40 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_asset_loader)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_asset_loader_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_asset_loader_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_asset_loader_sources
+		${vkcv_asset_loader_include}/vkcv/asset/asset_loader.hpp
+		${vkcv_asset_loader_source}/vkcv/asset/asset_loader.cpp
+)
+
+# adding source files to the module
+add_library(vkcv_asset_loader STATIC ${vkcv_asset_loader_sources})
+
+# Setup some path variables to load libraries
+set(vkcv_asset_loader_lib lib)
+set(vkcv_asset_loader_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_asset_loader_lib})
+
+# Check and load NLOHMANN_JSON
+include(config/NLOHMANN_JSON.cmake)
+
+# Check and load FX-GLTF
+include(config/FX_GLTF.cmake)
+
+# Check and load STB
+include(config/STB.cmake)
+
+# link the required libraries to the module
+target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries})
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_asset_loader BEFORE PUBLIC ${vkcv_asset_loader_include})
diff --git a/modules/asset_loader/config/FX_GLTF.cmake b/modules/asset_loader/config/FX_GLTF.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..37cd162422d8277022067498f5d5ba3e26e2ae1b
--- /dev/null
+++ b/modules/asset_loader/config/FX_GLTF.cmake
@@ -0,0 +1,12 @@
+
+if (EXISTS "${vkcv_asset_loader_lib_path}/fx-gltf")
+	set(FX_GLTF_INSTALL OFF CACHE INTERNAL "")
+	set(FX_GLTF_USE_INSTALLED_DEPS OFF CACHE INTERNAL "")
+	set(BUILD_TESTING OFF CACHE INTERNAL "")
+	
+	add_subdirectory(${vkcv_asset_loader_lib}/fx-gltf)
+	
+	list(APPEND vkcv_asset_loader_libraries fx-gltf)
+else()
+	message(WARNING "FX-GLTF is required..! Update the submodules!")
+endif ()
diff --git a/modules/asset_loader/config/NLOHMANN_JSON.cmake b/modules/asset_loader/config/NLOHMANN_JSON.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..018f6a19809fd3e53e6e790a6fe6447348e43c09
--- /dev/null
+++ b/modules/asset_loader/config/NLOHMANN_JSON.cmake
@@ -0,0 +1,10 @@
+
+if (EXISTS "${vkcv_asset_loader_lib_path}/json")
+	set(JSON_BuildTests OFF CACHE INTERNAL "")
+	
+	add_subdirectory(${vkcv_asset_loader_lib}/json)
+	
+	list(APPEND vkcv_asset_loader_libraries nlohmann_json::nlohmann_json)
+else()
+	message(WARNING "NLOHMANN_JSON is required..! Update the submodules!")
+endif ()
diff --git a/modules/asset_loader/config/STB.cmake b/modules/asset_loader/config/STB.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..da20d3ec07f98c865b4c6e38518f668b226cbfb9
--- /dev/null
+++ b/modules/asset_loader/config/STB.cmake
@@ -0,0 +1,6 @@
+
+if (EXISTS "${vkcv_asset_loader_lib_path}/stb")
+	list(APPEND vkcv_asset_loader_includes ${vkcv_asset_loader_lib}/stb)
+else()
+	message(WARNING "STB is required..! Update the submodules!")
+endif ()
diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6df014563a8991e464e02eb10f210c079913d3cb
--- /dev/null
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -0,0 +1,119 @@
+#pragma once
+/**
+ * @authors Trevor Hollmann
+ * @file include/vkcv/asset/asset_loader.h
+ * @brief Interface of the asset loader module for the vkcv framework.
+ */
+
+#include <string>
+#include <vector>
+#include <cstdint>
+
+/* These macros define limits of the following structs. Implementations can
+ * test against these limits when performing sanity checks. The main constraint
+ * expressed is that of the data type: Material indices are identified by a
+ * uint8_t in the VertexGroup struct, so there can't be more than UINT8_MAX
+ * materials in the mesh. Should these limits be too narrow, the data type has
+ * to be changed, but the current ones should be generous enough for most use
+ * cases. */
+#define MAX_MATERIALS_PER_MESH UINT8_MAX
+#define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX
+
+/* LOADING MESHES
+ * The description of meshes is a hierarchy of structures with the Mesh at the
+ * top.
+ *
+ * Each Mesh has an array of one or more vertex groups (called "primitives" in
+ * glTF parlance) and an array of zero or more Materials.
+ *
+ * Each vertex group describes a part of the meshes vertices by defining how
+ * they should be rendered (as points, lines, triangles), how many indices and
+ * vertices there are, how the content of the vertex buffer is to be
+ * interpreted and which material from the Meshes materials array should be
+ * used for the surface of the vertices.
+ * As a bonus there is also the axis aligned bounding box of the vertices.
+ *
+ * The vertex buffer is presented as a single block of binary data with a given
+ * length in bytes.
+ * The layout of the vertex buffer is described by an array of VertexAttribute
+ * structs that define the type of attribute, the offset, length and stride in
+ * bytes and number and type of components of the attribute.
+ * These values can directly be given to vulkan when describing the content of
+ * vertex buffers. */
+
+namespace vkcv::asset {
+
+/* This enum matches modes in fx-gltf, the library returns a standard mode
+ * (TRIANGLES) if no mode is given in the file. */
+enum PrimitiveMode {
+	POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
+	TRIANGLEFAN
+};
+/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
+enum PrimitiveType { POSITION=1, NORMAL, TEXCOORD_0 };
+/* The indices in the index buffer can be of different bit width. */
+enum IndexType { UINT32=0, UINT16=1, UINT8=2 };
+
+typedef struct {
+	// TODO not yet needed for the first (unlit) triangle
+} Material;
+
+/* This struct describes one vertex attribute of a vertex buffer. */
+typedef struct {
+	PrimitiveType type;		// POSITION, NORMAL, ...
+	uint32_t offset;		// offset in bytes
+	uint32_t length;		// length of ... in bytes
+	uint32_t stride;		// stride in bytes
+	uint16_t componentType;		// eg. 5126 for float
+	uint8_t  componentCount;	// eg. 3 for vec3
+} VertexAttribute;
+
+/* This struct represents one (possibly the only) part of a mesh. There is
+ * always one vertexBuffer and zero or one indexBuffer (indexed rendering is
+ * common but not always used). If there is no index buffer, this is indicated
+ * by indexBuffer.data being empty. Each vertex buffer can have one or more
+ * vertex attributes. */
+typedef struct {
+	enum PrimitiveMode mode;	// draw as points, lines or triangle?
+	size_t numIndices, numVertices;
+	struct {
+		enum IndexType type;	// data type of the indices
+		std::vector<uint8_t> data; // binary data of the index buffer
+	} indexBuffer;
+	struct {
+		std::vector<uint8_t> data; // binary data of the vertex buffer
+		std::vector<VertexAttribute> attributes;
+	} vertexBuffer;
+	struct { float x, y, z; } min;	// bounding box lower left
+	struct { float x, y, z; } max;	// bounding box upper right
+	uint8_t materialIndex;		// index to one of the meshes materials
+} VertexGroup;
+
+/* This struct represents a single mesh loaded from a glTF file. It consists of
+ * at least one VertexVroup and any number of Materials. */
+typedef struct {
+	std::string name;
+	std::vector<VertexGroup> vertexGroups;
+	std::vector<Material> materials;
+	// FIXME Dirty hack to get one(!) texture for our cube demo
+	// hardcoded to always have RGBA channel layout
+	struct {
+		int w, h, ch;	// width, height and channels of image
+		uint8_t *img;	// raw bytes, free after use (deal with it)
+	} texture_hack;
+} Mesh;
+
+
+/**
+ * In its first iteration the asset loader module will only allow loading
+ * single meshes, one per glTF file.
+ * It will later be extended to allow loading entire scenes from glTF files.
+ *
+ * @param path must be the path to a glTF file containing a single mesh.
+ * @param mesh is a reference to a Mesh struct that will be filled with the
+ * 	content of the glTF file being loaded.
+ * */
+int loadMesh(const std::string &path, Mesh &mesh);
+
+
+}
diff --git a/modules/asset_loader/lib/fx-gltf b/modules/asset_loader/lib/fx-gltf
new file mode 160000
index 0000000000000000000000000000000000000000..f4f18f2017a049a23748c9c9aad42ba2de20bfd5
--- /dev/null
+++ b/modules/asset_loader/lib/fx-gltf
@@ -0,0 +1 @@
+Subproject commit f4f18f2017a049a23748c9c9aad42ba2de20bfd5
diff --git a/modules/asset_loader/lib/json b/modules/asset_loader/lib/json
new file mode 160000
index 0000000000000000000000000000000000000000..0972f7ff0e651f09a306dba791cc42024b8642c1
--- /dev/null
+++ b/modules/asset_loader/lib/json
@@ -0,0 +1 @@
+Subproject commit 0972f7ff0e651f09a306dba791cc42024b8642c1
diff --git a/modules/asset_loader/lib/stb b/modules/asset_loader/lib/stb
new file mode 160000
index 0000000000000000000000000000000000000000..c9064e317699d2e495f36ba4f9ac037e88ee371a
--- /dev/null
+++ b/modules/asset_loader/lib/stb
@@ -0,0 +1 @@
+Subproject commit c9064e317699d2e495f36ba4f9ac037e88ee371a
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d0949cbc3454283b0084c57abf95a06a036c4e9b
--- /dev/null
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -0,0 +1,207 @@
+#include "vkcv/asset/asset_loader.hpp"
+#include <iostream>
+#include <string.h>	// memcpy(3)
+#include <stdlib.h>	// calloc(3)
+#include <fx/gltf.h>
+#define STB_IMAGE_IMPLEMENTATION
+#define STBI_ONLY_JPEG
+#include <stb_image.h>
+
+namespace vkcv::asset {
+
+/**
+* convert the accessor type from the fx-gltf library to an unsigned int
+* @param type
+* @return unsigned integer representation
+*/
+// TODO Return proper error code (we need to define those as macros or enums,
+// will discuss during the next core meeting if that should happen on the scope
+// of the vkcv framework or just this module)
+uint8_t convertTypeToInt(const fx::gltf::Accessor::Type type) {
+	switch (type) {
+	case fx::gltf::Accessor::Type::None :
+		return 0;
+	case fx::gltf::Accessor::Type::Scalar :
+		return 1;
+	case fx::gltf::Accessor::Type::Vec2 :
+		return 2;
+	case fx::gltf::Accessor::Type::Vec3 :
+		return 3;
+	case fx::gltf::Accessor::Type::Vec4 :
+		return 4;
+	default: return 10; // TODO add cases for matrices (or maybe change the type in the struct itself)
+	}
+}
+
+/**
+ * This function unrolls nested exceptions via recursion and prints them
+ * @param e error code
+ * @param path path to file that is responsible for error
+ */
+void print_what (const std::exception& e, const std::string &path) {
+	fprintf(stderr, "ERROR loading file %s: %s\n", path.c_str(), e.what());
+	try {
+		std::rethrow_if_nested(e);
+	} catch (const std::exception& nested) {
+		std::cerr << "nested: ";
+		print_what(nested, path);
+	}
+}
+
+int loadMesh(const std::string &path, Mesh &mesh) {
+	fx::gltf::Document object;
+
+	try {
+		if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
+			object = fx::gltf::LoadFromBinary(path);
+		} else {
+			object = fx::gltf::LoadFromText(path);
+		}
+	} catch (const std::system_error &err) {
+		print_what(err, path);
+		return 0;
+	} catch (const std::exception &e) {
+		print_what(e, path);
+		return 0;
+	}
+
+	// TODO Temporary restriction: Only one mesh per glTF file allowed
+	// currently. Later, we want to support whole scenes with more than
+	// just meshes.
+	if (object.meshes.size() != 1) return 0;
+
+	fx::gltf::Mesh const &objectMesh = object.meshes[0];
+	// TODO We want to support more than one vertex group per mesh
+	// eventually... right now this is hard-coded to use only the first one
+	// because we only care about the example triangle and cube
+	fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[0];
+	fx::gltf::Accessor posAccessor;
+	
+	std::vector<VertexAttribute> vertexAttributes;
+	vertexAttributes.reserve(objectPrimitive.attributes.size());
+	
+	for (auto const & attrib : objectPrimitive.attributes) {
+		fx::gltf::Accessor accessor =  object.accessors[attrib.second];
+		VertexAttribute attribute;
+
+		if (attrib.first == "POSITION") {
+			attribute.type = POSITION;
+			posAccessor = accessor;
+		} else if (attrib.first == "NORMAL") {
+			attribute.type = NORMAL;
+		} else if (attrib.first == "TEXCOORD_0") {
+			attribute.type = TEXCOORD_0;
+		} else {
+			return 0;
+		}
+		
+		attribute.offset = object.bufferViews[accessor.bufferView].byteOffset;
+		attribute.length = object.bufferViews[accessor.bufferView].byteLength;
+		attribute.stride = object.bufferViews[accessor.bufferView].byteStride;
+		attribute.componentType = static_cast<uint16_t>(accessor.componentType);
+		
+		if (convertTypeToInt(accessor.type) != 10) {
+			attribute.componentCount = convertTypeToInt(accessor.type);
+		} else {
+			return 0;
+		}
+		
+		vertexAttributes.push_back(attribute);
+	}
+
+	// TODO consider the case where there is no index buffer (not all
+	// meshes have to use indexed rendering)
+	const fx::gltf::Accessor &indexAccessor = object.accessors[objectPrimitive.indices];
+	const fx::gltf::BufferView &indexBufferView = object.bufferViews[indexAccessor.bufferView];
+	const fx::gltf::Buffer &indexBuffer = object.buffers[indexBufferView.buffer];
+	
+	std::vector<uint8_t> indexBufferData;
+	indexBufferData.resize(indexBufferView.byteLength);
+	{
+		const size_t off = indexBufferView.byteOffset;
+		const void *const ptr = ((char*)indexBuffer.data.data()) + off;
+		if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
+			std::cerr << "ERROR copying index buffer data.\n";
+			return 0;
+		}
+	}
+
+	const fx::gltf::BufferView& vertexBufferView = object.bufferViews[posAccessor.bufferView];
+	const fx::gltf::Buffer& vertexBuffer = object.buffers[vertexBufferView.buffer];
+	
+	std::vector<uint8_t> vertexBufferData;
+	vertexBufferData.resize(vertexBufferView.byteLength);
+	{
+		const size_t off = vertexBufferView.byteOffset;
+		const void *const ptr = ((char*)vertexBuffer.data.data()) + off;
+		if (!memcpy(vertexBufferData.data(), ptr, vertexBufferView.byteLength)) {
+			std::cerr << "ERROR copying vertex buffer data.\n";
+			return 0;
+		}
+	}
+
+	IndexType indexType;
+	switch(indexAccessor.componentType) {
+	case fx::gltf::Accessor::ComponentType::UnsignedByte:
+		indexType = UINT8; break;
+	case fx::gltf::Accessor::ComponentType::UnsignedShort:
+		indexType = UINT16; break;
+	case fx::gltf::Accessor::ComponentType::UnsignedInt:
+		indexType = UINT32; break;
+	default:
+		std::cerr << "ERROR: Index type not supported: " <<
+			static_cast<uint16_t>(indexAccessor.componentType) <<
+			std::endl;
+		return 0;
+	}
+
+	const size_t numVertexGroups = objectMesh.primitives.size();
+	
+	std::vector<VertexGroup> vertexGroups;
+	vertexGroups.reserve(numVertexGroups);
+	
+	vertexGroups.push_back({
+		static_cast<PrimitiveMode>(objectPrimitive.mode),
+		object.accessors[objectPrimitive.indices].count,
+		posAccessor.count,
+		{indexType, indexBufferData},
+		{vertexBufferData, vertexAttributes},
+		{posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
+		{posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
+		static_cast<uint8_t>(objectPrimitive.material)
+	});
+	
+	std::vector<Material> materials;
+
+	mesh = {
+		object.meshes[0].name,
+		vertexGroups,
+		materials,
+		0, 0, 0, NULL
+	};
+
+	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
+	// fail quietly if there is no texture
+	if (object.textures.size()) {
+		const std::string mime_type("image/jpeg");
+		const fx::gltf::Texture &tex = object.textures[0];
+		const fx::gltf::Image &img = object.images[tex.source];
+#ifndef NDEBUG
+		printf("texture name=%s sampler=%u source=%u\n",
+				tex.name.c_str(), tex.sampler, tex.source);
+		printf("image   name=%s uri=%s mime=%s\n", img.name.c_str(),
+				img.uri.c_str(), img.mimeType.c_str());
+#endif
+		
+		size_t pos = path.find_last_of("/");
+		auto dir = path.substr(0, pos);
+		
+		mesh.texture_hack.img = stbi_load((dir + "/" + img.uri).c_str(),
+				&mesh.texture_hack.w, &mesh.texture_hack.h,
+				&mesh.texture_hack.ch, 4);
+	}
+	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
+	return 1;
+}
+
+}
diff --git a/modules/camera/CMakeLists.txt b/modules/camera/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..28080bf2b1cd3bbc88d6c13d7ef26a43d7c3e19a
--- /dev/null
+++ b/modules/camera/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_camera)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_camera_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_camera_include ${PROJECT_SOURCE_DIR}/include)
+
+set(vkcv_camera_sources
+		${vkcv_camera_include}/vkcv/camera/Camera.hpp
+		${vkcv_camera_source}/vkcv/camera/Camera.cpp
+		${vkcv_camera_include}/vkcv/camera/TrackballCamera.hpp
+		${vkcv_camera_source}/vkcv/camera/TrackballCamera.cpp
+		${vkcv_camera_include}/vkcv/camera/CameraManager.hpp
+		${vkcv_camera_source}/vkcv/camera/CameraManager.cpp
+)
+
+# adding source files to the project
+add_library(vkcv_camera STATIC ${vkcv_camera_sources})
+
+# Setup some path variables to load libraries
+set(vkcv_camera_lib lib)
+set(vkcv_camera_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_camera_lib})
+
+include(config/GLM.cmake)
+
+target_link_libraries(vkcv_camera PUBLIC ${vkcv_camera_libraries} vkcv)
+
+target_include_directories(vkcv_camera SYSTEM BEFORE PRIVATE ${vkcv_camera_includes} ${vkcv_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_camera BEFORE PUBLIC ${vkcv_camera_include} ${vkcv_camera_includes})
+
diff --git a/modules/camera/config/GLM.cmake b/modules/camera/config/GLM.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..c4d14392ef0ea24243a45b19cd8583d90d3267be
--- /dev/null
+++ b/modules/camera/config/GLM.cmake
@@ -0,0 +1,15 @@
+
+find_package(glm QUIET)
+
+if (glm_FOUND)
+    list(APPEND vkcv_camera_includes ${GLM_INCLUDE_DIRS})
+    list(APPEND vkcv_camera_libraries glm)
+else()
+    if (EXISTS "${vkcv_camera_lib_path}/glm")
+        add_subdirectory(${vkcv_camera_lib}/glm)
+        
+        list(APPEND vkcv_camera_libraries glm)
+    else()
+        message(WARNING "GLM is required..! Update the submodules!")
+    endif ()
+endif ()
diff --git a/modules/camera/include/vkcv/camera/Camera.hpp b/modules/camera/include/vkcv/camera/Camera.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7e177b9a2fbde0890e0c8ea6a1d9a19d6e277c7c
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/Camera.hpp
@@ -0,0 +1,101 @@
+#pragma once
+
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/matrix_access.hpp>
+
+namespace vkcv {
+
+    class Camera {
+    protected:
+		glm::mat4 m_view;
+		glm::mat4 m_projection;
+
+		int m_width;
+		int m_height;
+
+		float m_near;
+		float m_far;
+		float m_fov;
+		float m_ratio;
+
+        glm::vec3 m_up;
+        glm::vec3 m_position;
+        float m_cameraSpeed;
+        float m_pitch;
+        float m_yaw;
+
+        int m_fov_nsteps;
+        float m_fov_min;
+        float m_fov_max;
+
+        bool m_forward;
+        bool m_backward;
+        bool m_left;
+        bool m_right;
+
+    public:
+        Camera();
+
+        virtual ~Camera();
+
+        void setPerspective(float fov, float ratio, float near, float far);
+
+        const glm::mat4 getView() const;
+
+        void getView(glm::vec3 &x, glm::vec3 &y, glm::vec3 &z, glm::vec3 &pos);
+
+        glm::mat4 updateView(double deltatime);
+
+        void lookAt(glm::vec3 position, glm::vec3 center, glm::vec3 up);
+
+        const glm::mat4& getProjection() const;
+
+        void setProjection(const glm::mat4 projection);
+
+        void getNearFar(float &near, float &far) const;
+
+        void setUp(const glm::vec3 &Up);
+
+        float getFov() const;
+
+        void setFov(float fov);
+        
+        void changeFov(double fov);
+
+        void updateRatio(float ratio);
+
+        float getRatio() const;
+
+        void setNearFar(float near, float far);
+
+        glm::vec3 getFront() const;
+
+        glm::vec3 getPosition() const;
+
+        void setPosition( glm::vec3 position );
+
+        float getPitch() const;
+
+        void setPitch(float pitch);
+
+        float getYaw() const;
+
+        void setYaw(float yaw);
+
+        void panView( double xOffset, double yOffset );
+
+        void updatePosition(double deltatime);
+
+        void moveForward(int action);
+
+        void moveBackward(int action);
+
+        void moveLeft(int action);
+
+        void moveRight(int action);
+
+
+    };
+
+}
diff --git a/modules/camera/include/vkcv/camera/CameraManager.hpp b/modules/camera/include/vkcv/camera/CameraManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9511b752e972afb1e10f41a118433a4e8933fd65
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "TrackballCamera.hpp"
+#include "vkcv/Window.hpp"
+#include <GLFW/glfw3.h>
+#include <functional>
+
+namespace vkcv{
+
+    class CameraManager{
+    private:
+        std::function<void(int, int, int, int)> m_keyHandle;
+        std::function<void(double, double)> m_mouseMoveHandle;
+        std::function<void(double, double)> m_mouseScrollHandle;
+        std::function<void(int, int, int)> m_mouseButtonHandle;
+
+        Window &m_window;
+        Camera m_camera;
+        float m_width;
+        float m_height;
+//        std::shared_ptr<vkcv::TrackballCamera> m_trackball;
+
+        bool m_roationActive = false;
+        double m_lastX ;
+        double m_lastY ;
+
+        void bindCamera();
+        void keyCallback(int key, int scancode, int action, int mods);
+        void scrollCallback( double offsetX, double offsetY);
+        void mouseMoveCallback( double offsetX, double offsetY);
+        void mouseButtonCallback(int button, int action, int mods);
+
+    public:
+        CameraManager(Window &window, float width, float height, glm::vec3 up = glm::vec3(0.0f,-1.0f,0.0f), glm::vec3 position = glm::vec3(0.0f,0.0f,0.0f));
+
+        Camera &getCamera();
+    };
+}
diff --git a/modules/camera/include/vkcv/camera/TrackballCamera.hpp b/modules/camera/include/vkcv/camera/TrackballCamera.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c9e269e9f7ad708c68158d5b358efbf37c5bb7a9
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/TrackballCamera.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include "Camera.hpp"
+
+namespace vkcv {
+
+    class TrackballCamera : public vkcv::Camera {
+    public:
+
+        TrackballCamera( int width, int height, glm::mat4 projection);
+
+        TrackballCamera(int width, int height);
+
+        ~TrackballCamera();
+
+        float getSensitivity() const;
+
+        void setSensitivity(float sensitivity);
+
+        float getStepSize() const;
+
+        void setStepSize(float stepSize);
+
+        float getTheta() const;
+
+        void setTheta(float theta);
+
+        float getPhi() const;
+
+        void setPhi(float phi);
+
+        float getRadius() const;
+
+        void setRadius(float radius);
+
+        const glm::vec3& getCenter() const;
+
+        void setCenter(const glm::vec3 &center);
+
+    private:
+        float m_sensitivity;
+        float m_stepSize, m_theta, m_phi, m_radius;
+        glm::vec3 m_center;
+
+        float m_oldX;
+        float m_oldY;
+    };
+
+}
\ No newline at end of file
diff --git a/modules/camera/lib/glm b/modules/camera/lib/glm
new file mode 160000
index 0000000000000000000000000000000000000000..66062497b104ca7c297321bd0e970869b1e6ece5
--- /dev/null
+++ b/modules/camera/lib/glm
@@ -0,0 +1 @@
+Subproject commit 66062497b104ca7c297321bd0e970869b1e6ece5
diff --git a/modules/camera/src/vkcv/camera/Camera.cpp b/modules/camera/src/vkcv/camera/Camera.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc8a8498e67a6bd751f5a6ed1d4c4fba0279a68d
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/Camera.cpp
@@ -0,0 +1,177 @@
+#include "vkcv/camera/Camera.hpp"
+#include <iostream>
+
+namespace vkcv {
+
+    Camera::Camera(){
+        m_up = glm::vec3(0.0f, -1.0f, 0.0f);
+        m_position = glm::vec3(0.0f, 0.0f, 0.0f);
+        m_cameraSpeed = 2.f;
+        // front
+        m_pitch = 0.0;
+        m_yaw = 180.0;
+
+        m_fov_nsteps = 100;
+        m_fov_min = 10;
+        m_fov_max = 120;
+
+        m_forward = false;
+        m_backward = false;
+        m_left = false;
+        m_right = false;
+    }
+
+    Camera::~Camera() = default;
+
+    void Camera::lookAt(glm::vec3 position, glm::vec3 center, glm::vec3 up){
+        m_view = glm::lookAt(position, center, up);
+    }
+
+    glm::mat4 Camera::updateView(double deltatime){
+        updatePosition(deltatime);
+        return m_view = glm::lookAt(m_position, m_position + getFront() , m_up);
+    }
+
+    void Camera::getView(glm::vec3 &x, glm::vec3 &y, glm::vec3 &z, glm::vec3 &pos){
+        x = glm::vec3( glm::row(m_view, 0));
+        y = glm::vec3( glm::row(m_view, 1));
+        z = glm::vec3( glm::row(m_view, 2));
+        pos = glm::vec3( glm::column(m_view, 3));
+        glm::mat3 mat_inv = glm::inverse( glm::mat3(m_view));
+        pos = -mat_inv * pos;
+    }
+
+    void Camera::getNearFar( float &near, float &far) const {
+        near = m_near;
+        far = m_far;
+    }
+
+
+    const glm::mat4 Camera::getView() const {
+        return m_view;
+    }
+
+    const glm::mat4& Camera::getProjection() const {
+        return m_projection;
+    }
+
+    void Camera::setProjection(const glm::mat4 projection){
+        m_projection = projection;
+    }
+
+    float Camera::getFov() const {
+        return m_fov;
+    }
+
+    void Camera::setFov( float fov){
+        m_fov = fov;
+        setPerspective( m_fov, m_ratio, m_near, m_far);
+    }
+
+    void Camera::changeFov(double offset){
+        float fov = m_fov;
+        float fov_range = m_fov_max - m_fov_min;
+        float fov_stepsize = glm::radians(fov_range)/m_fov_nsteps;
+        fov -= (float) offset*fov_stepsize;
+        if (fov < glm::radians(m_fov_min)) {
+            fov = glm::radians(m_fov_min);
+        }
+        if (fov > glm::radians(m_fov_max)) {
+            fov = glm::radians(m_fov_max);
+        }
+        setFov(fov);
+    }
+
+    void Camera::updateRatio( float ratio){
+        m_ratio = ratio;
+        setPerspective( m_fov, m_ratio, m_near, m_far);
+    }
+
+    float Camera::getRatio() const {
+        return 0.0f;
+    }
+
+    void Camera::setNearFar( float near, float far){
+        m_near = near;
+        m_far = far;
+        setPerspective(m_fov, m_ratio, m_near, m_far);
+    }
+
+    void Camera::setPerspective(float fov, float ratio, float near, float far){
+        m_fov = fov;
+        m_ratio = ratio;
+        m_near = near;
+        m_far = far;
+        m_projection = glm::perspective( m_fov, ratio, m_near, m_far);
+    }
+
+    glm::vec3 Camera::getFront() const {
+        glm::vec3 direction;
+        direction.x = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
+        direction.y = sin(glm::radians(m_pitch));
+        direction.z = cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
+        return glm::normalize(direction);
+    }
+
+    glm::vec3 Camera::getPosition() const {
+        return m_position;
+    }
+
+    void Camera::setPosition( glm::vec3 position ){
+        m_position = position;
+    }
+
+    void Camera::setUp(const glm::vec3 &up) {
+        m_up = up;
+    }
+
+    float Camera::getPitch() const {
+        return m_pitch;
+    }
+
+    void Camera::setPitch(float pitch) {
+        if (pitch > 89.0f) {
+            pitch = 89.0f;
+        }
+        if (pitch < -89.0f) {
+            pitch = -89.0f;
+        }
+        m_pitch = pitch;
+    }
+
+    float Camera::getYaw() const {
+        return m_yaw;
+    }
+
+    void Camera::setYaw(float yaw) {
+        m_yaw = yaw;
+    }
+
+    void Camera::panView(double xOffset, double yOffset) {
+        m_yaw += xOffset;
+        m_pitch += yOffset;
+    }
+
+    void Camera::updatePosition(double deltatime ){
+        m_position += (m_cameraSpeed * getFront() * static_cast<float> (m_forward) * static_cast<float>(deltatime));
+        m_position -= (m_cameraSpeed * getFront() * static_cast<float> (m_backward) * static_cast<float>(deltatime));
+        m_position -= (glm::normalize(glm::cross(getFront(), m_up)) * m_cameraSpeed * static_cast<float> (m_left) * static_cast<float>(deltatime));
+        m_position += (glm::normalize(glm::cross(getFront(), m_up)) * m_cameraSpeed * static_cast<float> (m_right) * static_cast<float>(deltatime));
+    }
+
+    void Camera::moveForward(int action){
+        m_forward = static_cast<bool>(action);
+    }
+
+    void Camera::moveBackward(int action){
+        m_backward = static_cast<bool>(action);
+    }
+
+    void Camera::moveLeft(int action){
+        m_left = static_cast<bool>(action);
+    }
+
+    void Camera::moveRight(int action){
+        m_right = static_cast<bool>(action);
+    }
+}
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/CameraManager.cpp b/modules/camera/src/vkcv/camera/CameraManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..18f499a2b34b64c1442c5d9e267d6476b8d69199
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -0,0 +1,82 @@
+#include <iostream>
+#include "vkcv/camera/CameraManager.hpp"
+
+namespace vkcv{
+
+    CameraManager::CameraManager(Window &window, float width, float height, glm::vec3 up, glm::vec3 position):
+    m_window(window), m_width(width), m_height(height)
+    {
+        m_camera.setUp(up);
+        m_camera.setPosition(position);
+        m_camera.setPerspective( glm::radians(60.0f), m_width / m_height, 0.1f, 10.f);
+        m_lastX = width/2.0;
+        m_lastY = height/2.0;
+        bindCamera();
+    }
+
+    void CameraManager::bindCamera(){
+        m_keyHandle = m_window.e_key.add( [&](int key, int scancode, int action, int mods) { this->keyCallback(key, scancode, action, mods); });
+        m_mouseMoveHandle = m_window.e_mouseMove.add( [&]( double offsetX, double offsetY) {this->mouseMoveCallback( offsetX, offsetY);} );
+        m_mouseScrollHandle =  m_window.e_mouseScroll.add([&](double offsetX, double offsetY) {this->scrollCallback( offsetX, offsetY);} );
+        m_mouseButtonHandle = m_window.e_mouseButton.add([&] (int button, int action, int mods) {this->mouseButtonCallback( button,  action,  mods);});
+    }
+
+    void CameraManager::mouseButtonCallback(int button, int action, int mods){
+        if(button == GLFW_MOUSE_BUTTON_2 && m_roationActive == false && action == GLFW_PRESS){
+            glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
+            m_roationActive = true;
+        }else if(button == GLFW_MOUSE_BUTTON_2 && m_roationActive == true && action == GLFW_RELEASE){
+            glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+            m_roationActive = false;
+        }
+    }
+
+    void CameraManager::mouseMoveCallback(double x, double y){
+
+        float xoffset = x - m_lastX;
+        float yoffset = m_lastY - y;
+        m_lastX = x;
+        m_lastY = y;
+
+        if(!m_roationActive){
+            return;
+        }
+
+        float sensitivity = 0.05f;
+        xoffset *= sensitivity;
+        yoffset *= sensitivity;
+
+        m_camera.panView( xoffset , yoffset );
+    }
+
+    void CameraManager::scrollCallback(double offsetX, double offsetY) {
+        m_camera.changeFov(offsetY);
+    }
+
+    void CameraManager::keyCallback(int key, int scancode, int action, int mods) {
+
+        switch (key) {
+            case GLFW_KEY_W:
+                m_camera.moveForward(action);
+                break;
+            case GLFW_KEY_S:
+                m_camera.moveBackward(action);
+                break;
+            case GLFW_KEY_A:
+                m_camera.moveLeft(action);
+                break;
+            case GLFW_KEY_D:
+                m_camera.moveRight(action);
+                break;
+            case GLFW_KEY_ESCAPE:
+                glfwSetWindowShouldClose(m_window.getWindow(), 1);
+                break;
+            default:
+                break;
+        }
+    }
+    Camera &CameraManager::getCamera(){
+        return m_camera;
+    }
+
+}
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/TrackballCamera.cpp b/modules/camera/src/vkcv/camera/TrackballCamera.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3bbb8611dd234499fb9ba08ba87009c8c76660f6
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/TrackballCamera.cpp
@@ -0,0 +1,146 @@
+#include "vkcv/camera/TrackballCamera.hpp"
+
+namespace vkcv{
+
+    TrackballCamera::TrackballCamera( int width, int height, glm::mat4 projection)
+    {
+        setPosition( glm::vec3(0.0f, 0.0f, 5.0) );
+        m_center = glm::vec3( 0.0f, 0.0f, 0.0f);
+        m_up = glm::vec3(0.0f, 1.0f, 0.0f);
+
+        m_width = width;
+        m_height = height;
+
+        m_sensitivity = 0.010f;
+        m_stepSize = 0.1f;
+        m_theta = glm::pi<float>() / 2.0f;
+        m_phi = 0.f;
+        m_radius = 1.5;
+
+        m_view = glm::lookAt( m_center + getPosition(), m_center, m_up);
+
+        m_oldX = width/2.f;
+        m_oldY = height/2.f;
+
+        setProjection(projection);
+    }
+
+
+    TrackballCamera::TrackballCamera(int width, int height)
+    {
+        setPosition(    glm::vec3(0.0f, 0.0f, 5.0));
+        m_center = glm::vec3( 0.0f, 0.0f, 0.0f);
+        m_up = glm::vec3(0.0f, 1.0f, 0.0f);
+
+        m_width = width;
+        m_height = height;
+
+        m_sensitivity = 0.010f;
+        m_stepSize = 0.1f;
+        m_theta = glm::pi<float>() / 2.0f;
+        m_phi = 0.f;
+        m_radius = 1.5;
+
+        m_view = glm::lookAt( m_center + getPosition(), m_center, m_up);
+
+        m_oldX = width/2.f;
+        m_oldY = height/2.f;
+
+        m_fov = glm::radians(60.f);
+        m_ratio = m_width / (float) m_height;
+        m_near = 0.001f;
+        m_far = 10.f;
+        glm::mat4 projection = glm::perspective(m_fov, m_ratio, m_near, m_far);
+
+        setProjection(projection);
+    }
+
+    TrackballCamera::~TrackballCamera()
+    {
+    }
+
+	// TODO: Can be done as events... (mouseMove, mouseDown, mouseUp)
+    /*void TrackballCamera::update( GLFWwindow* window) {
+
+        double x, y;
+
+        glfwGetCursorPos( window, &x, &y);
+        if (glfwGetMouseButton( window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
+        {
+            float changeX = ((float) x - m_oldX) * m_sensitivity;
+            float changeY = ((float) y - m_oldY) * m_sensitivity;
+
+            m_theta -= changeY;
+            if (m_theta < 0.01f) m_theta = 0.01f;
+            else if (m_theta > glm::pi<float>() - 0.01f) m_theta = glm::pi<float>() - 0.01f;
+
+            m_phi -= changeX;
+            if (m_phi < 0) m_phi += 2*glm::pi<float>();
+            else if (m_phi > 2*glm::pi<float>()) m_phi -= 2*glm::pi<float>();
+        }
+
+        m_oldX = (float) x;
+        m_oldY = (float) y;
+
+        if (glfwGetKey( window, GLFW_KEY_UP) == GLFW_PRESS)
+            m_radius -= m_stepSize;
+        if (glfwGetKey( window, GLFW_KEY_DOWN) == GLFW_PRESS)
+            m_radius += m_stepSize;
+        if (m_radius < 0.1f) m_radius = 0.1f;
+
+        m_position.x = m_center.x + m_radius * sin(m_theta) * sin(m_phi);
+        m_position.y = m_center.y + m_radius * cos(m_theta);
+        m_position.z = m_center.z + m_radius * sin(m_theta) * cos(m_phi);
+
+        m_view = glm::lookAt( m_position, m_center, m_up);
+
+    }*/
+
+    float TrackballCamera::getSensitivity() const {
+        return m_sensitivity;
+    }
+
+    void TrackballCamera::setSensitivity(float sensitivity) {
+        m_sensitivity = sensitivity;
+    }
+
+    float TrackballCamera::getStepSize() const {
+        return m_stepSize;
+    }
+
+    void TrackballCamera::setStepSize(float stepSize) {
+        m_stepSize = stepSize;
+    }
+
+    float TrackballCamera::getTheta() const {
+        return m_theta;
+    }
+
+    void TrackballCamera::setTheta(float theta) {
+        m_theta = theta;
+    }
+
+    float TrackballCamera::getPhi() const {
+        return m_phi;
+    }
+
+    void TrackballCamera::setPhi(float phi) {
+        m_phi = phi;
+    }
+
+    float TrackballCamera::getRadius() const {
+        return m_radius;
+    }
+
+    void TrackballCamera::setRadius(float radius) {
+        m_radius = radius;
+    }
+
+    const glm::vec3& TrackballCamera::getCenter() const {
+        return m_center;
+    }
+
+    void TrackballCamera::setCenter(const glm::vec3 &center) {
+        m_center = center;
+    }
+}
\ No newline at end of file
diff --git a/modules/testing/CMakeLists.txt b/modules/testing/CMakeLists.txt
index e869c7974fb0e91894d796e045c9104564173873..a22e547646fd4ef59860245d51365b98df59b578 100644
--- a/modules/testing/CMakeLists.txt
+++ b/modules/testing/CMakeLists.txt
@@ -18,3 +18,4 @@ add_library(vkcv_testing STATIC ${vkcv_testing_sources})
 
 # add the own include directory for public headers
 target_include_directories(vkcv_testing BEFORE PUBLIC ${vkcv_testing_include})
+
diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index f7687bca4ba7cb05835c8afda1396ad319a035c7..7ca73a0811df7f1568508b56312ce3348237a695 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -1,3 +1,4 @@
 
 # Add new projects/examples here:
 add_subdirectory(first_triangle)
+add_subdirectory(first_mesh)
diff --git a/projects/first_mesh/.gitignore b/projects/first_mesh/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..85ce58a41ce7dcbe4f4a41bbf4779d82cede0106
--- /dev/null
+++ b/projects/first_mesh/.gitignore
@@ -0,0 +1 @@
+first_mesh
\ No newline at end of file
diff --git a/projects/first_mesh/CMakeLists.txt b/projects/first_mesh/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ae9c5604cb83c6f3a16773e896521117460839f7
--- /dev/null
+++ b/projects/first_mesh/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.16)
+project(first_mesh)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# this should fix the execution path to load local files from the project
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+
+# adding source files to the project
+add_executable(first_mesh src/main.cpp)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(first_mesh PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(first_mesh PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+    
+    # in addition to setting the output directory, the working directory has to be set
+	# by default visual studio sets the working directory to the build directory, when using the debugger
+	set_target_properties(first_mesh PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries})
diff --git a/projects/first_mesh/resources/cube/boards2_vcyc_jpg.jpg b/projects/first_mesh/resources/cube/boards2_vcyc_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_mesh/resources/cube/boards2_vcyc_jpg.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_mesh/resources/cube/cube.bin b/projects/first_mesh/resources/cube/cube.bin
new file mode 100644
index 0000000000000000000000000000000000000000..3303cd8635848bee18e10ab8754d5e4e7218db92
--- /dev/null
+++ b/projects/first_mesh/resources/cube/cube.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9bb9b6b8bbe50a0aaa517057f245ee844f80afa7426dacb2aed4128f71629ce4
+size 840
diff --git a/projects/first_mesh/resources/cube/cube.blend b/projects/first_mesh/resources/cube/cube.blend
new file mode 100644
index 0000000000000000000000000000000000000000..62ccb2c742094bcfb5ed194ab905bffae86bfd65
--- /dev/null
+++ b/projects/first_mesh/resources/cube/cube.blend
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a6c1e245f259c610528c9485db6688928faac0ab2addee9e3c2dde7740e4dd09
+size 774920
diff --git a/projects/first_mesh/resources/cube/cube.blend1 b/projects/first_mesh/resources/cube/cube.blend1
new file mode 100644
index 0000000000000000000000000000000000000000..13f21dcca218d7bc7a07a8a9682b5e1d9e607736
--- /dev/null
+++ b/projects/first_mesh/resources/cube/cube.blend1
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f4496f423569b8ca81f3b3a55fad00f925557e0193fb9dbe6cdce7e71fb48f7b
+size 774920
diff --git a/projects/first_mesh/resources/cube/cube.glb b/projects/first_mesh/resources/cube/cube.glb
new file mode 100644
index 0000000000000000000000000000000000000000..66a42c65e71dcf375e04cc378256024dd3c7834d
--- /dev/null
+++ b/projects/first_mesh/resources/cube/cube.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:198568b715f397d78f7c358c0f709a419e7fd677e54cdec7c19f71b5ed264897
+size 1194508
diff --git a/projects/first_mesh/resources/cube/cube.gltf b/projects/first_mesh/resources/cube/cube.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..428176144843dd06c78fe1d11a6392a0ea02b22d
--- /dev/null
+++ b/projects/first_mesh/resources/cube/cube.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f82f455647a84ca6242882ae26a79a499d3ce594f8de317ab89488c5b79721ac
+size 2823
diff --git a/projects/first_mesh/resources/triangle/Triangle.bin b/projects/first_mesh/resources/triangle/Triangle.bin
new file mode 100644
index 0000000000000000000000000000000000000000..57f26ad96592b64377e6aa93823d96a94e6c5022
--- /dev/null
+++ b/projects/first_mesh/resources/triangle/Triangle.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:412ebd5f7242c266b4957e7e26be13aa331dbcb7bbb854ab334a2437ae8ed959
+size 104
diff --git a/projects/first_mesh/resources/triangle/Triangle.blend b/projects/first_mesh/resources/triangle/Triangle.blend
new file mode 100644
index 0000000000000000000000000000000000000000..2421dc5e1bb029d73a9ec09cc4530c5196851fd7
--- /dev/null
+++ b/projects/first_mesh/resources/triangle/Triangle.blend
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:387e544df173219fbf292a64a6656d1d782bbf71a5a9e9fdef0a308f47b05477
+size 758144
diff --git a/projects/first_mesh/resources/triangle/Triangle.glb b/projects/first_mesh/resources/triangle/Triangle.glb
new file mode 100644
index 0000000000000000000000000000000000000000..4148620cd6af0dadbc791aa1c52bb5431a40884b
--- /dev/null
+++ b/projects/first_mesh/resources/triangle/Triangle.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f4be087a605212d139416b5352a018283b26b99260cbcddb7013a1beeb331227
+size 980
diff --git a/projects/first_mesh/resources/triangle/Triangle.gltf b/projects/first_mesh/resources/triangle/Triangle.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..a188e6ee16a5e8486cf307c7bda8cfd99bdbeea6
--- /dev/null
+++ b/projects/first_mesh/resources/triangle/Triangle.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d5fc354e040f79cff329e919677b194c75e3a522c6406f75c1108ad9575f12ec
+size 2202
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b37429e1f7080b70aaab514125f86fbe03f329b5
--- /dev/null
+++ b/projects/first_mesh/src/main.cpp
@@ -0,0 +1,76 @@
+#include <iostream>
+#include <stdio.h>
+#include <vkcv/asset/asset_loader.hpp>
+
+
+int main(int argc, const char** argv) {
+	vkcv::asset::Mesh mesh;
+	
+	const char *path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+	int result = vkcv::asset::loadMesh(path, mesh);
+	
+	if (result == 1) {
+		std::cout << "Mesh loading successful!" << std::endl;
+	} else {
+		std::cout << "Mesh loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	/* Demonstration of how to use the vkcv::asset::Mesh struct. */
+	const char *primitive_modes[] = {
+		"points", "lines", "line loop", "line strip", "triangles",
+		"triangle strip", "triangle fan"
+	};
+	const char *primitive_types[] = {
+		"unknown", "position", "normal", "texcoord0"
+	};
+	printf("Mesh %s (%s) has %lu vertex group(s) and %lu material(s):\n",
+			mesh.name.c_str(), path, mesh.vertexGroups.size(),
+			mesh.materials.size());
+	for (size_t i = 0; i < mesh.vertexGroups.size(); i++) {
+		printf("--- vertex group %lu ---\n", i);
+		const auto &vg = mesh.vertexGroups[i];
+		printf("primitive mode: %d (%s)\n", vg.mode,
+				primitive_modes[vg.mode]);
+		printf("index buffer: %lu bytes for %lu indices ",
+				vg.indexBuffer.data.size(), vg.numIndices);
+		const auto itype = vg.indexBuffer.type;
+		printf("(%s @ %p)\n",
+				itype == vkcv::asset::UINT32 ? "UINT32" :
+				itype == vkcv::asset::UINT16 ? "UINT16" :
+				"UINT8", vg.indexBuffer.data.data());
+		printf("\tindices: ");
+		const size_t n = vg.numIndices;
+		if (vg.indexBuffer.type == vkcv::asset::UINT32) {
+			uint32_t *idx = (uint32_t*)vg.indexBuffer.data.data();
+			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
+		} else
+		if (vg.indexBuffer.type == vkcv::asset::UINT16) {
+			uint16_t *idx = (uint16_t*)vg.indexBuffer.data.data();
+			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
+		} else
+		if (vg.indexBuffer.type == vkcv::asset::UINT8) {
+			uint8_t *idx = (uint8_t*)vg.indexBuffer.data.data();
+			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
+		} else {
+			fprintf(stderr, "ERROR Invalid IndexType: %d\n",
+					vg.indexBuffer.type);
+			return 0;
+		}
+		printf("\n");
+		printf("vertex buffer: %lu bytes for %lu vertices with %lu "
+				"attributes (starting at %p)\n",
+				vg.vertexBuffer.data.size(), vg.numVertices,
+				vg.vertexBuffer.attributes.size(),
+				vg.vertexBuffer.data.data());
+		printf("attributes:\toffset\tlength\tstride\tcomponents\n");
+		for (const auto att : vg.vertexBuffer.attributes) {
+			printf("%11s\t%u\t%u\t%u\t%hhux%hu\n",
+					primitive_types[att.type],
+					att.offset, att.length, att.stride,
+					att.componentCount, att.componentType);
+		}
+	}
+	
+	return 0;
+}
diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt
index 400f7b1be1e5063777acb4c489d247ec8db34b1d..e7c8373e085df6497060b8d1d8164cf740dfb01f 100644
--- a/projects/first_triangle/CMakeLists.txt
+++ b/projects/first_triangle/CMakeLists.txt
@@ -22,7 +22,7 @@ if(MSVC)
 endif()
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include})
+target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_triangle vkcv ${vkcv_libraries} vkcv_testing)
+target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera)
diff --git a/projects/first_triangle/shaders/shader.vert b/projects/first_triangle/shaders/shader.vert
index 1d278d5f41f803bb657a303dcc95ffcd2a92fd6e..e129186a4f9b9f8ceca180391210ccaf2953c08e 100644
--- a/projects/first_triangle/shaders/shader.vert
+++ b/projects/first_triangle/shaders/shader.vert
@@ -3,11 +3,15 @@
 
 layout(location = 0) out vec3 fragColor;
 
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
 void main()	{
     vec3 positions[3] = {
-        vec3(-0.5, 0.5, 0),
-        vec3( 0.5, 0.5, 0),
-        vec3(0, -0.5, 0)
+        vec3(-0.5, 0.5, -1),
+        vec3( 0.5, 0.5, -1),
+        vec3(0, -0.5, -1)
     };
     
     vec3 colors[3] = {
@@ -16,6 +20,6 @@ void main()	{
         vec3(0, 0, 1)
     };
 
-	gl_Position = vec4(positions[gl_VertexIndex], 1.0);
+	gl_Position = mvp * vec4(positions[gl_VertexIndex], 1.0);
 	fragColor = colors[gl_VertexIndex];
 }
\ No newline at end of file
diff --git a/projects/first_triangle/shaders/vert.spv b/projects/first_triangle/shaders/vert.spv
index bd1e0e682c52e6e38a5f5aba4eeaf8e73a70d741..03af5758ffff1b5b6505fe98b02044849026832d 100644
Binary files a/projects/first_triangle/shaders/vert.spv and b/projects/first_triangle/shaders/vert.spv differ
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index a515005e18ddaee99959a5c97b6978a0054ee574..e7664abd3f2bd74e0ad04629391d5f3ad108d4c2 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -1,9 +1,8 @@
 #include <iostream>
 #include <vkcv/Core.hpp>
-#include <vkcv/Window.hpp>
-#include <vkcv/ShaderProgram.hpp>
 #include <GLFW/glfw3.h>
-#include <vkcv/DescriptorConfig.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
 
 int main(int argc, const char** argv) {
     const char* applicationName = "First Triangle";
@@ -17,6 +16,8 @@ int main(int argc, const char** argv) {
 		false
     );
 
+    vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
+
     window.initEvents();
 
 	vkcv::Core core = vkcv::Core::create(
@@ -130,11 +131,18 @@ int main(int argc, const char** argv) {
 	 *
 	 * PipelineHandle trianglePipeline = core.CreatePipeline(trianglePipeline);
 	 */
-
+    auto start = std::chrono::system_clock::now();
 	while (window.isWindowOpen())
 	{
 		core.beginFrame();
-	    core.renderTriangle(trianglePass, trianglePipeline, windowWidth, windowHeight);
+        window.pollEvents();
+        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 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+
+	    core.renderTriangle(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp);
 	    core.endFrame();
 	}
 	return 0;
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index cb2425a51ee177d062120b0eb61ae089f6f1692f..cb7c7356aef589ee8aef03b904d48021bdb75057 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -178,6 +178,26 @@ namespace vkcv {
 		);
 	}
 	
+	vk::Buffer BufferManager::getBuffer(uint64_t id) const {
+		if (id >= m_buffers.size()) {
+			return nullptr;
+		}
+		
+		auto& buffer = m_buffers[id];
+		
+		return buffer.m_handle;
+	}
+	
+	vk::DeviceMemory BufferManager::getDeviceMemory(uint64_t id) const {
+		if (id >= m_buffers.size()) {
+			return nullptr;
+		}
+		
+		auto& buffer = m_buffers[id];
+		
+		return buffer.m_memory;
+	}
+	
 	void BufferManager::fillBuffer(uint64_t id, void *data, size_t size, size_t offset) {
 		if (size == 0) {
 			size = SIZE_MAX;
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index fa53b4faa666134bde9230863f78c96e648c9037..0ee414fb974676b1d2df820e69218f5bb247341b 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -158,13 +158,13 @@ namespace vkcv
     	if (acquireSwapchainImage() != Result::SUCCESS) {
     		return;
     	}
-		m_window.pollEvents();
 		m_Context.getDevice().waitIdle();	// FIMXE: this is a sin against graphics programming, but its getting late - Alex
 		destroyTemporaryFramebuffers();
 	}
 
 	void Core::renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, 
-		const int width, const int height) {
+		const int width, const int height, const size_t pushConstantSize, const void *pushConstantData) {
+
 		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
 			return;
 		}
@@ -172,6 +172,7 @@ namespace vkcv
 		const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle);
 		const vk::ImageView imageView	= m_swapchainImageViews[m_currentSwapchainImageIndex];
 		const vk::Pipeline pipeline		= m_PipelineManager->getVkPipeline(pipelineHandle);
+        const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
 
 		const vk::Framebuffer framebuffer = createFramebuffer(m_Context.getDevice(), renderpass, width, height, imageView);
@@ -180,16 +181,17 @@ namespace vkcv
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
-		submitCommands(submitInfo, [renderpass, renderArea, imageView, framebuffer, pipeline](const vk::CommandBuffer& cmdBuffer) {
+		submitCommands(submitInfo, [renderpass, renderArea, imageView, framebuffer, pipeline, pipelineLayout, pushConstantSize, pushConstantData](const vk::CommandBuffer& cmdBuffer) {
 
 			const std::array<float, 4> clearColor = { 0.f, 0.f, 0.f, 1.f };
 			const vk::ClearValue clearValues(clearColor);
-			
+
 			const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, 1, &clearValues);
 			const vk::SubpassContents subpassContents = {};
 			cmdBuffer.beginRenderPass(beginInfo, subpassContents, {});
-			
+
 			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
+            cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
 			cmdBuffer.draw(3, 1, 0, 0, {});
 			cmdBuffer.endRenderPass();
 		}, nullptr);
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 84b302867744ff750d8f1554b333de4e03a2dfbe..238a4ad73607d0d47f08b62a9541594cf57783da 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -160,14 +160,14 @@ namespace vkcv
                 { 1.f,1.f,1.f,1.f }
         );
 
+		const size_t matrixPushConstantSize = 4 * 4 * sizeof(float);
+		const vk::PushConstantRange pushConstantRange(vk::ShaderStageFlagBits::eAll, 0, matrixPushConstantSize);
+
         // pipeline layout
         vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
-                {},
-                0,
-                {},
-                0,
-                {}
-        );
+			{},
+			{},
+			(pushConstantRange));
         vk::PipelineLayout vkPipelineLayout{};
         if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
         {
diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp
index 87f302c146f79f82a9b5334fd6a651fa00a92616..c21271b78f7501721d5c0496d0344dd68e2e7e52 100644
--- a/src/vkcv/Window.cpp
+++ b/src/vkcv/Window.cpp
@@ -46,17 +46,30 @@ namespace vkcv {
         glfwSetWindowUserPointer(m_window, this);
 
         // combine Callbacks with Events
+        glfwSetMouseButtonCallback(m_window, Window::onMouseButtonEvent);
+
         glfwSetCursorPosCallback(m_window, Window::onMouseMoveEvent);
 
         glfwSetWindowSizeCallback(m_window, Window::onResize);
 
         glfwSetKeyCallback(m_window, Window::onKeyEvent);
+
+        glfwSetScrollCallback(m_window, Window::onMouseScrollEvent);
     }
 
     void Window::pollEvents() {
         glfwPollEvents();
     }
 
+    void Window::onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods) {
+
+        auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+
+        if (window != nullptr) {
+            window->e_mouseButton(button, action, mods);
+        }
+    }
+
     void Window::onMouseMoveEvent(GLFWwindow *callbackWindow, double x, double y) {
 
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
@@ -66,6 +79,14 @@ namespace vkcv {
         }
     }
 
+    void Window::onMouseScrollEvent(GLFWwindow *callbackWindow, double xoffset, double yoffset) {
+        auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+
+        if (window != nullptr) {
+            window->e_mouseScroll(xoffset, yoffset);
+        }
+    }
+
     void Window::onResize(GLFWwindow *callbackWindow, int width, int height) {
 
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
@@ -103,4 +124,4 @@ namespace vkcv {
     GLFWwindow *Window::getWindow() const {
         return m_window;
     }
-}
+}
\ No newline at end of file