diff --git a/.gitmodules b/.gitmodules
index 983b753744e8767da0ec3c959c32a3766ee346f6..62938a4b1ff2c6787b619cc2c18ef91cb0f0f679 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -15,4 +15,7 @@
 	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
+	url = https://github.com/g-truc/glm.git
+[submodule "modules/shader_compiler/lib/glslang"]
+	path = modules/shader_compiler/lib/glslang
+	url = https://github.com/KhronosGroup/glslang.git
diff --git a/config/Sources.cmake b/config/Sources.cmake
index 4f673e00d1e42e733534480d6085affd651a8c04..62cec249367995db0217c71455cfcee982c65af3 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -41,7 +41,6 @@ set(vkcv_sources
 		${vkcv_source}/vkcv/ShaderProgram.cpp
 
 		${vkcv_include}/vkcv/PipelineConfig.hpp
-		${vkcv_source}/vkcv/PipelineConfig.cpp
 
 		${vkcv_source}/vkcv/PipelineManager.hpp
 		${vkcv_source}/vkcv/PipelineManager.cpp
diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp
index 7dc6f2200db7efc1abdddab81145daec27540c49..93523128a67d7b9667c342fb7c10203f4b9a43dd 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -7,38 +7,20 @@
 
 #include <vector>
 #include <cstdint>
-#include "vkcv/Handles.hpp"
+#include "Handles.hpp"
 #include "ShaderProgram.hpp"
-#include <vkcv/VertexLayout.hpp>
+#include "VertexLayout.hpp"
 
 namespace vkcv {
 
     struct PipelineConfig {
-        /**
-         *  Constructor for the pipeline. Creates a pipeline using @p vertexCode, @p fragmentCode as well as the
-         *  dimensions of the application window @p width and @p height. A handle for the Render Pass is also needed, @p passHandle.
-         *
-         * @param shaderProgram shaders of the pipeline
-         * @param height height of the application window
-         * @param width width of the application window
-         * @param passHandle handle for Render Pass
-         */
-        PipelineConfig(
-            const ShaderProgram&                        shaderProgram,
-            uint32_t                                    width,
-            uint32_t                                    height,
-            const PassHandle                            &passHandle,
-            const std::vector<VertexAttribute>          &vertexAttributes,
-            const std::vector<vk::DescriptorSetLayout>  &descriptorLayouts,
-            bool                                        useDynamicViewport);
-
-        ShaderProgram                           m_ShaderProgram;
-        uint32_t                                m_Height;
-        uint32_t                                m_Width;
-        PassHandle                              m_PassHandle;
-        std::vector<VertexAttribute>            m_VertexAttributes;
-        std::vector<vk::DescriptorSetLayout>    m_DescriptorLayouts;
-        bool                                    m_UseDynamicViewport;
+        ShaderProgram                         m_ShaderProgram;
+        uint32_t                              m_Width;
+		uint32_t                              m_Height;
+        PassHandle                            m_PassHandle;
+        VertexLayout                          m_VertexLayout;
+        std::vector<vk::DescriptorSetLayout>  m_DescriptorLayouts;
+        bool                                  m_UseDynamicViewport;
 
     };
 
diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp
index ce28cccf07e22dda21fd14d0bddd0ba6e9842328..78b1f02169fe630427b9f66150e32078d42b7b3f 100644
--- a/include/vkcv/ShaderProgram.hpp
+++ b/include/vkcv/ShaderProgram.hpp
@@ -12,9 +12,9 @@
 #include <filesystem>
 #include <vulkan/vulkan.hpp>
 #include <spirv_cross.hpp>
-#include "vkcv/VertexLayout.hpp"
-#include "vkcv/ShaderStage.hpp"
-#include "vkcv/DescriptorConfig.hpp"
+#include "VertexLayout.hpp"
+#include "ShaderStage.hpp"
+#include "DescriptorConfig.hpp"
 
 namespace vkcv {
 
@@ -48,17 +48,23 @@ namespace vkcv {
 
         bool existsShader(ShaderStage shaderStage) const;
 
-        void reflectShader(ShaderStage shaderStage);
-
-        const VertexLayout &getVertexLayout() const;
+        const std::vector<VertexAttachment> &getVertexAttachments() const;
 		size_t getPushConstantSize() const;
 
-        const std::vector<std::vector<DescriptorBinding>> getReflectedDescriptors() const;
+        const std::vector<std::vector<DescriptorBinding>>& getReflectedDescriptors() const;
 
 	private:
+	    /**
+	     * Called after successfully adding a shader to the program.
+	     * Fills vertex input attachments and descriptor sets (if present).
+	     * @param shaderStage the stage to reflect data from
+	     */
+        void reflectShader(ShaderStage shaderStage);
+
         std::unordered_map<ShaderStage, Shader> m_Shaders;
 
-        VertexLayout m_VertexLayout;
+        // contains all vertex input attachments used in the vertex buffer
+        std::vector<VertexAttachment> m_VertexAttachments;
         std::vector<std::vector<DescriptorBinding>> m_DescriptorSets;
 		size_t m_pushConstantSize = 0;
 	};
diff --git a/include/vkcv/VertexLayout.hpp b/include/vkcv/VertexLayout.hpp
index c9388d9de075dd4451f39e8283ac2bf1099beff6..0600b99a24a327605e89b2e8ec304c20dbf7ad2e 100644
--- a/include/vkcv/VertexLayout.hpp
+++ b/include/vkcv/VertexLayout.hpp
@@ -1,64 +1,66 @@
 #pragma once
 
-#include <unordered_map>
 #include <vector>
 #include <iostream>
 #include <string>
 
-namespace vkcv {
+namespace vkcv{
+    enum class VertexAttachmentFormat{
+        FLOAT,
+        FLOAT2,
+        FLOAT3,
+        FLOAT4,
+        INT,
+        INT2,
+        INT3,
+        INT4
+    };
 
-/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
-enum class PrimitiveType : uint32_t {
-	UNDEFINED = 0,
-	POSITION = 1,
-	NORMAL = 2,
-	TEXCOORD_0 = 3,
-	TEXCOORD_1 = 4
-};
+	uint32_t getFormatSize(VertexAttachmentFormat format);
 
-/* These integer values are used the same way in OpenGL, Vulkan and glTF. This
- * enum is not needed for translation, it's only for the programmers
- * convenience (easier to read in if/switch statements etc). While this enum
- * exists in (almost) the same definition in the fx-gltf library, we want to
- * avoid exposing that dependency, thus it is re-defined here. */
-enum class ComponentType : uint16_t {
-	NONE=0, INT8=5120, UINT8=5121, INT16=5122, UINT16=5123,
-	UINT32=5125, FLOAT32=5126
-};
+    struct VertexAttachment{
+        friend struct VertexBinding;
+        /**
+         * Describes an individual vertex input attribute/attachment.
+         * @param inputLocation its location in the vertex shader.
+         * @param name the name referred to in the shader.
+         * @param format the format (and therefore, the size) this attachment is in.
+         * The offset is calculated when a collection of attachments forms a binding, hence the friend declaration.
+         */
+        VertexAttachment(uint32_t inputLocation, const std::string &name, VertexAttachmentFormat format) noexcept;
+        VertexAttachment() = delete;
 
-/* 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
-	ComponentType componentType;	// eg. 5126 for float
-	uint8_t  componentCount;	// eg. 3 for vec3
-} VertexAttribute;
+        uint32_t                inputLocation;
+        std::string             name;
+        VertexAttachmentFormat  format;
+        uint32_t                offset;
+    };
 
-enum class VertexFormat {
-	FLOAT, FLOAT2, FLOAT3, FLOAT4,
-	INT, INT2, INT3, INT4
-};
+    struct VertexBinding{
+        /**
+         * Describes all vertex input attachments _one_ buffer contains to create a vertex buffer binding.
+         * NOTE: multiple vertex layouts may contain various (mutually exclusive) vertex input attachments
+         * to form one complete vertex buffer binding!
+         * @param bindingLocation its entry in the buffers that make up the whole vertex buffer.
+         * @param attachments the vertex input attachments this specific buffer layout contains.
+         */
+        VertexBinding(uint32_t bindingLocation, const std::vector<VertexAttachment> &attachments) noexcept;
+        VertexBinding() = delete;
 
-uint32_t getFormatSize(VertexFormat format);
+        uint32_t                        bindingLocation;
+        uint32_t                        stride;
+        std::vector<VertexAttachment>   vertexAttachments;
+    };
 
-struct VertexInputAttachment{
-	VertexInputAttachment() = delete;
-	VertexInputAttachment(uint32_t location, uint32_t binding, std::string name, VertexFormat format, uint32_t offset) noexcept;
-
-	uint32_t location;
-	uint32_t binding;
-    std::string name;
-	VertexFormat format;
-	uint32_t offset;
-};
-
-struct VertexLayout{
-	VertexLayout() noexcept;
-	VertexLayout(const std::vector<VertexInputAttachment> &inputs) noexcept;
-	std::unordered_map<uint32_t, VertexInputAttachment> attachmentMap;
-	uint32_t stride;
-};
+    struct VertexLayout{
+        /**
+         * Describes the complete layout of one vertex, e.g. all of the vertex input attachments used,
+         * and all of the buffer bindings that refer to the attachments (for when multiple buffers are used).
+         * @param bindings bindings the complete vertex buffer is comprised of.
+         */
+        VertexLayout() noexcept;
+        VertexLayout(const std::vector<VertexBinding> &bindings) noexcept;
 
+        std::vector<VertexBinding> vertexBindings;
+    };
 }
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index f29ff2fc86c88aa8bae2560f199d3882c9919b65..e8efea4981da3ffb338d508431ed4f92805ed5cd 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -2,4 +2,5 @@
 # Add new modules here:
 add_subdirectory(asset_loader)
 add_subdirectory(camera)
+add_subdirectory(shader_compiler)
 add_subdirectory(testing)
diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index 597cf4c8e237e9fafb9a43f63c64b769623ab314..f4330c014a7fec04d2f809fb3abe932675021109 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -9,7 +9,6 @@
 #include <vector>
 #include <array>
 #include <cstdint>
-#include <vkcv/VertexLayout.hpp>
 
 /* These macros define limits of the following structs. Implementations can
  * test against these limits when performing sanity checks. The main constraint
@@ -70,12 +69,28 @@ typedef struct {
 	std::vector<uint8_t> data;	// binary data of the decoded texture
 } Texture;
 
+
 /* The asset loader module only supports the PBR-MetallicRoughness model for
- * materials.
- * NOTE: Only a single UV-texture is currently supported to reduce the
- * complexity at first. Later, there will need to be an association between
- * each of the texture targets in the Material struct and a VertexAttribute of
- * a VertexBuffer that defines the UV coordinates for that texture. */
+ * materials.*/
+
+/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
+enum class PrimitiveType : uint32_t {
+    UNDEFINED = 0,
+    POSITION = 1,
+    NORMAL = 2,
+    TEXCOORD_0 = 3,
+	TEXCOORD_1 = 4
+};
+/* 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;
+
 typedef struct {
 	uint16_t textureMask;	// bit mask with active texture targets
 	// Indices into the Mesh.textures array
@@ -126,7 +141,7 @@ typedef struct {
 	} indexBuffer;
 	struct {
 		std::vector<uint8_t> data; // binary data of the vertex buffer
-		std::vector<VertexAttribute> attributes;
+		std::vector<VertexAttribute> attributes; // description of one
 	} vertexBuffer;
 	struct { float x, y, z; } min;	// bounding box lower left
 	struct { float x, y, z; } max;	// bounding box upper right
diff --git a/modules/camera/CMakeLists.txt b/modules/camera/CMakeLists.txt
index 28080bf2b1cd3bbc88d6c13d7ef26a43d7c3e19a..73f2dd1c81be9c6cadf563f7936bfaba8c1d0025 100644
--- a/modules/camera/CMakeLists.txt
+++ b/modules/camera/CMakeLists.txt
@@ -11,10 +11,13 @@ 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
+		${vkcv_camera_include}/vkcv/camera/CameraController.hpp
+		${vkcv_camera_include}/vkcv/camera/PilotCameraController.hpp
+		${vkcv_camera_source}/vkcv/camera/PilotCameraController.cpp
+		${vkcv_camera_include}/vkcv/camera/TrackballCameraController.hpp
+		${vkcv_camera_source}/vkcv/camera/TrackballCameraController.cpp
 )
 
 # adding source files to the project
diff --git a/modules/camera/include/vkcv/camera/Camera.hpp b/modules/camera/include/vkcv/camera/Camera.hpp
index ff8fda811eaad7a99dbb940601f9e5904025255e..dc9f2dcb3038655f51fb2404abc21f98a2120399 100644
--- a/modules/camera/include/vkcv/camera/Camera.hpp
+++ b/modules/camera/include/vkcv/camera/Camera.hpp
@@ -1,101 +1,199 @@
 #pragma once
 
+#define GLM_DEPTH_ZERO_TO_ONE
+#define GLM_FORCE_LEFT_HANDED
 #include <glm/glm.hpp>
 #include <glm/gtc/matrix_transform.hpp>
 #include <glm/gtc/matrix_access.hpp>
 
-namespace vkcv {
+namespace vkcv::camera {
 
-    class Camera {
+    /**
+     * @brief Used to create a camera which governs the view and projection matrices.
+     */
+    class Camera final {
     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_up;
         glm::vec3 m_position;
-        float m_cameraSpeed;
+        glm::vec3 m_center;
+
         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;
+	
+		/**
+		 * @brief Sets the view matrix of the camera to @p view
+		 * @param[in] view The view matrix
+		 */
+		void setView(const glm::mat4& view);
+	
+		/**
+		 * @brief Sets the projection matrix of the camera to @p projection
+		 * @param[in] projection The projection matrix
+		 */
+		void setProjection(const glm::mat4& projection);
 
     public:
-        Camera();
 
-        virtual ~Camera();
+        /**
+         * @brief The default constructor of the camera
+         */
+        Camera();
 
+        /**
+         * @brief The destructor of the camera (default behavior)
+         */
+        ~Camera();
+        
+        /**
+         * @brief Sets the perspective object according to @p fov, @p ratio, @p near and @p far. This leads to changes in the projection matrix of the camera
+         * @param[in] fov The desired field of view in radians
+         * @param[in] ratio The aspect ratio
+         * @param[in] near Distance to near clipping plane
+         * @param[in] far Distance to far clipping plane
+         */
         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);
-
+        /**
+         * @brief Gets the view matrix of the camera
+         * @return The view matrix of the camera
+         */
+        const glm::mat4& getView() const;
+
+        /**
+         * @brief Sets the view matrix of the camera according to @p position, @p center and @p up
+         * @param[in] position The position of the camera
+         * @param[in] center The target position the camera is looking at
+         * @param[in] up The vector that defines which direction is 'up' depending on the camera's orientation
+         */
+        void lookAt(const glm::vec3& position, const glm::vec3& center, const glm::vec3& up);
+
+        /**
+         * @brief Gets the current projection of the camera
+         * @return The current projection matrix
+         */
         const glm::mat4& getProjection() const;
 
-        void setProjection(const glm::mat4 projection);
-
+        /**
+         * @brief Gets the model-view-projection matrix of the camera with y-axis-correction applied
+         * @return The model-view-projection matrix
+         */
+        glm::mat4 getMVP() const;
+
+        /**
+         * @brief Gets the near and far bounds of the view frustum of the camera.
+         * @param[out] near The near bound of the view frustum
+         * @param[out] far The far bound of the view frustum
+         */
         void getNearFar(float &near, float &far) const;
 
-        void setUp(const glm::vec3 &Up);
-
+        /**
+         * @brief Gets the current field of view of the camera in radians
+         * @return[in] The current field of view in radians
+         */
         float getFov() const;
 
+        /**
+         * @brief Sets the field of view of the camera to @p fov in radians
+         * @param[in] fov The new field of view in radians
+         */
         void setFov(float fov);
-        
-        void changeFov(double fov);
-
-        void updateRatio(int width, int height);
 
+        /**
+         * @brief Gets the current aspect ratio of the camera
+         * @return The current aspect ratio of the camera
+         */
         float getRatio() const;
 
+        /**
+         * @brief Updates the aspect ratio of the camera with @p ratio and, thus, changes the projection matrix
+         * @param[in] ratio The new aspect ratio of the camera
+         */
+        void setRatio(float ratio);
+
+        /**
+         * @brief Sets @p near and @p far as new values for the view frustum of the camera. This leads to changes in the projection matrix according to these two values.
+         * @param[in] near The new near bound of the view frustum
+         * @param[in] far The new far bound of the view frustum
+         */
         void setNearFar(float near, float far);
 
+        /**
+         * @brief Gets the current front vector of the camera in world space
+         * @return The current front vector of the camera
+         */
         glm::vec3 getFront() const;
-
-        glm::vec3 getPosition() const;
-
-        void setPosition( glm::vec3 position );
-
+        
+        /**
+         * @brief Sets the front vector of the camera in world space to @p front
+         * @param[in] front The new front vector of the camera
+         */
+        void setFront(const glm::vec3& front);
+
+        /**
+         * @brief Gets the current position of the camera in world space
+         * @return The current position of the camera in world space
+         */
+        const glm::vec3& getPosition() const;
+
+        /**
+         * @brief Sets the position of the camera to @p position
+         * @param[in] position The new position of the camera
+         */
+        void setPosition( const glm::vec3& position );
+
+        /**
+         * @brief Gets the center point.
+         * @return The center point.
+         */
+        const glm::vec3& getCenter() const;
+
+        /**
+         * @brief Sets @p center as the new center point.
+         * @param[in] center The new center point.
+         */
+        void setCenter(const glm::vec3& center);
+
+        /**
+         * @brief Gets the pitch value of the camera in degrees.
+         * @return The pitch value in degrees.
+         */
         float getPitch() const;
 
+        /**
+         * @brief Sets the pitch value of the camera to @p pitch in degrees.
+         * @param[in] pitch The new pitch value in degrees.
+         */
         void setPitch(float pitch);
 
+        /**
+         * @brief Gets the yaw value of the camera in degrees.
+         * @return The yaw value in degrees.
+         */
         float getYaw() const;
 
+        /**
+         * @brief Sets the yaw value of the camera to @p yaw.
+         * @param[in] yaw The new yaw value in degrees.
+         */
         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);
-
-
+        /**
+         * @brief Gets the up vector.
+         * @return The up vector.
+         */
+        const glm::vec3& getUp() const;
+
+        /**
+         * @brief Sets @p up as the new up vector.
+         * @param[in] up The new up vector.
+         */
+        void setUp(const glm::vec3 &up);
     };
 
 }
diff --git a/modules/camera/include/vkcv/camera/CameraController.hpp b/modules/camera/include/vkcv/camera/CameraController.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5fe7aba586068beff15525617d8e4817662746b7
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/CameraController.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "Camera.hpp"
+#include "vkcv/Window.hpp"
+
+namespace vkcv::camera {
+
+    /**
+     * @brief Used as a base class for defining camera controller classes with different behaviors, e.g. the
+     * #PilotCameraController.
+     */
+    class CameraController {
+
+    public:
+
+        /**
+         * @brief The constructor of the #CameraController (default behavior).
+         */
+        CameraController() = default;
+
+        /**
+         * @brief Updates @p camera in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         * @param[in] camera The camera object.
+         */
+        virtual void updateCamera(double deltaTime, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for key events.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        virtual void keyCallback(int key, int scancode, int action, int mods, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for mouse scrolling events.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         * @param[in] camera The camera object.
+         */
+        virtual void scrollCallback( double offsetX, double offsetY, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for mouse movement events.
+         * @param[in] x The horizontal mouse position.
+         * @param[in] y The vertical mouse position.
+         * @param[in] camera The camera object.
+         */
+        virtual void mouseMoveCallback(double offsetX, double offsetY, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for mouse button events.
+         * @param[in] button The mouse button.
+         * @param[in] action The button action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        virtual void mouseButtonCallback(int button, int action, int mods, Camera &camera) = 0;
+    };
+
+}
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/CameraManager.hpp b/modules/camera/include/vkcv/camera/CameraManager.hpp
index 4e52eccea25e8544a9a5cc89d0dc74ddd0e023c6..0c5041795ece9cd84e740a04c0d64e4964d0f4c8 100644
--- a/modules/camera/include/vkcv/camera/CameraManager.hpp
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -1,40 +1,184 @@
 #pragma once
 
-#include "TrackballCamera.hpp"
+#include "PilotCameraController.hpp"
+#include "TrackballCameraController.hpp"
+#include "CameraController.hpp"
 #include "vkcv/Window.hpp"
 #include <GLFW/glfw3.h>
 #include <functional>
 
-namespace vkcv{
+namespace vkcv::camera {
 
+    /**
+     * @brief Used for specifying existing types of camera controllers when adding a new controller object to the
+     * #CameraManager.
+     */
+    enum class ControllerType {
+        NONE,
+        PILOT,
+        TRACKBALL,
+        TRACE
+    };
+
+    /**
+     * @brief Used for managing an arbitrary amount of camera controllers.
+     */
     class CameraManager{
     private:
-        std::function<void(int, int, int, int)> e_keyHandle;
-        std::function<void(double, double)> e_mouseMoveHandle;
-        std::function<void(double, double)> e_mouseScrollHandle;
-        std::function<void(int, int, int)> e_mouseButtonHandle;
-        std::function<void(int, int)> e_resizeHandle;
-
-        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();
+        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;
+        std::function<void(int, int)> m_resizeHandle;
+
+        Window& m_window;
+        std::vector<Camera> m_cameras;
+        std::vector<ControllerType> m_cameraControllerTypes;
+        uint32_t m_activeCameraIndex;
+
+        PilotCameraController m_pilotController;
+        TrackballCameraController m_trackController;
+
+        double m_lastX;
+        double m_lastY;
+
+        /**
+         * @brief Binds the camera object to the window event handles.
+         */
+        void bindCameraToEvents();
+
+        /**
+         * @brief A callback function for key events. Currently, cycling between all existing camera controllers via Tab,
+         * window closure via Esc and polling key events from the active camera controller are supported.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         */
         void keyCallback(int key, int scancode, int action, int mods);
-        void scrollCallback( double offsetX, double offsetY);
-        void mouseMoveCallback( double offsetX, double offsetY);
+
+        /**
+         * @brief A callback function for mouse scrolling events.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         */
+        void scrollCallback(double offsetX, double offsetY);
+
+        /**
+         * @brief A callback function for mouse movement events.
+         * @param[in] x The horizontal mouse position.
+         * @param[in] y The vertical mouse position.
+         */
+        void mouseMoveCallback(double x, double y);
+
+        /**
+         * @brief A callback function for mouse button events.
+         * @param[in] button The mouse button.
+         * @param[in] action The button action.
+         * @param[in] mods The modifier bits.
+         */
         void mouseButtonCallback(int button, int action, int mods);
+
+        /**
+         * @brief A callback function for handling the window resizing event. Each existing camera is resized in respect
+         * of the window size.
+         * @param[in] width The new width of the window.
+         * @param[in] height The new height of the window.
+         */
         void resizeCallback(int width, int height);
+	
+		/**
+		 * @brief Gets a camera controller object of specified @p controllerType.
+		 * @param[in] controllerType The type of the camera controller.
+		 * @return The specified camera controller object.
+		 */
+		CameraController& getControllerByType(ControllerType controllerType);
+        
+        /**
+         * @briof A method to get the currently active controller for the active camera.
+         * @return Reference to the active #CameraController
+         */
+        CameraController& getActiveController();
 
     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();
+        /**
+         * @brief The constructor of the #CameraManager.
+         * @param[in] window The window.
+         */
+        CameraManager(Window &window);
+
+        /**
+         * @brief The destructor of the #CameraManager. Destroying the #CameraManager leads to deletion of all stored
+         * camera objects and camera controller objects.
+         */
+        ~CameraManager();
+
+        /**
+         * @brief Adds a new camera object to the #CameraManager and binds it to a camera controller object of specified
+         * @p controllerType.
+         * @param[in] controllerType The type of the camera controller.
+         * @return The index of the newly created camera object.
+         */
+		uint32_t addCamera(ControllerType controllerType = ControllerType::NONE);
+	
+		/**
+		 * @brief Adds a new camera object to the #CameraManager and binds it to a camera controller object of specified
+		 * @p controllerType.
+		 * @param[in] controllerType The type of the camera controller.
+		 * @param[in] camera The new camera object.
+		 * @return The index of the newly bound camera object.
+		 */
+		uint32_t addCamera(ControllerType controllerType, const Camera& camera);
+
+        /**
+         * @brief Gets the stored camera object located at @p cameraIndex.
+         * @param[in] cameraIndex The camera index.
+         * @return The camera object at @p cameraIndex.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        Camera& getCamera(uint32_t cameraIndex);
+
+        /**
+         * @brief Gets the stored camera object set as the active camera.
+         * @return The active camera.
+         */
+        Camera& getActiveCamera();
+
+        /**
+         * @brief Sets the stored camera object located at @p cameraIndex as the active camera.
+         * @param[in] cameraIndex The camera index.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        void setActiveCamera(uint32_t cameraIndex);
+
+        /**
+         * @brief Gets the index of the stored active camera object.
+         * @return The active camera index.
+         */
+        uint32_t getActiveCameraIndex() const;
+
+        /**
+         * @brief Binds a stored camera object located at @p cameraIndex to a camera controller of specified
+         * @p controllerType.
+         * @param[in] cameraIndex The camera index.
+         * @param[in] controllerType The type of the camera controller.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        void setControllerType(uint32_t cameraIndex, ControllerType controllerType);
+
+        /**
+         * @brief Gets the currently bound camera controller type of the stored camera object located at @p cameraIndex.
+         * @param[in] cameraIndex The camera index.
+         * @return The type of the camera controller of the specified camera object.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        ControllerType getControllerType(uint32_t cameraIndex);
+
+        /**
+         * @brief Updates all stored camera controllers in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         */
+        void update(double deltaTime);
     };
 }
diff --git a/modules/camera/include/vkcv/camera/PilotCameraController.hpp b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c6a9f7c7ffa9a3be77f12c29e456291fb8f6b845
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
@@ -0,0 +1,138 @@
+#pragma once
+
+#include <vkcv/camera/CameraController.hpp>
+
+namespace vkcv::camera {
+
+    /**
+     * @brief Used to move around a camera object in world space.
+     */
+    class PilotCameraController final : public CameraController {
+    private:
+        // camera movement indicators
+        bool m_forward;
+        bool m_backward;
+        bool m_upward;
+        bool m_downward;
+        bool m_left;
+        bool m_right;
+
+        bool m_rotationActive;
+
+        float m_cameraSpeed;
+
+        int m_fov_nsteps;
+        float m_fov_min;
+        float m_fov_max;
+
+        /**
+         * @brief Indicates forward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveForward(int action);
+
+        /**
+         * @brief Indicates backward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveBackward(int action);
+
+        /**
+         * @brief Indicates left movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveLeft(int action);
+
+        /**
+         * @brief Indicates right movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveRight(int action);
+
+        /**
+         * @brief Indicates upward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveUpward(int action);
+
+        /**
+         * @brief Indicates downward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveDownward(int action);
+
+    public:
+
+        /**
+         * @brief The default constructor of the #PilotCameraController.
+         */
+        PilotCameraController();
+
+        /**
+         * @brief The destructor of the #PilotCameraController (default behavior).
+         */
+        ~PilotCameraController() = default;
+
+        /**
+         * @brief Changes the field of view of @p camera with an @p offset in degrees.
+         * @param[in] offset The offset in degrees.
+         * @param[in] camera The camera object.
+         */
+        void changeFov(double offset, Camera &camera);
+
+        /**
+         * @brief Pans the view of @p camera according to the pitch and yaw values and additional offsets @p xOffset
+         * and @p yOffset.
+         * @param[in] xOffset The offset added to the yaw value.
+         * @param[in] yOffset The offset added to the pitch value.
+         * @param[in] camera The camera object.
+         */
+        void panView(double xOffset, double yOffset, Camera &camera);
+
+        /**
+         * @brief Updates @p camera in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         * @param[in] camera The camera object.
+         */
+        void updateCamera(double deltaTime, Camera &camera);
+
+        /**
+         * @brief A callback function for key events. Currently, 3D camera movement via W, A, S, D, E, Q are supported.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        void keyCallback(int key, int scancode, int action, int mods, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse scrolling events. Currently, this leads to changes in the field of view
+         * of @p camera.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         * @param[in] camera The camera object.
+         */
+        void scrollCallback(double offsetX, double offsetY, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse movement events. Currently, this leads to panning the view of the camera,
+         * if #mouseButtonCallback(int button, int action, int mods) enabled panning.
+         * @param[in] x The horizontal mouse position
+         * @param[in] y The vertical mouse position
+         * @param[in] camera The camera object.
+         */
+        void mouseMoveCallback(double x, double y, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse button events. Currently, the right mouse button enables panning the
+         * view of the camera as long as it is pressed.
+         * @param[in] button The mouse button
+         * @param[in] action The button action
+         * @param[in] mods The modifier bits
+         * @param[in] camera The camera object.
+         */
+        void mouseButtonCallback(int button, int action, int mods, Camera &camera);
+    };
+
+}
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/TrackballCamera.hpp b/modules/camera/include/vkcv/camera/TrackballCamera.hpp
deleted file mode 100644
index c9e269e9f7ad708c68158d5b358efbf37c5bb7a9..0000000000000000000000000000000000000000
--- a/modules/camera/include/vkcv/camera/TrackballCamera.hpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#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/include/vkcv/camera/TrackballCameraController.hpp b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0211043a9c6b862df8e500af190ad1f75a3c78aa
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
@@ -0,0 +1,100 @@
+#pragma once
+
+#include "CameraController.hpp"
+
+namespace vkcv::camera {
+
+    /**
+     * @brief Used to orbit a camera around its center point.
+     */
+    class TrackballCameraController final : public CameraController {
+    private:
+        bool m_rotationActive;
+
+        float m_cameraSpeed;
+        float m_scrollSensitivity;
+        float m_radius;
+
+        /**
+         * @brief Updates the current radius of @p camera in respect to the @p offset.
+         * @param[in] offset The offset between the old and new radius.
+         * @param[in] camera The camera object.
+         */
+        void updateRadius(double offset, Camera &camera);
+
+    public:
+
+        /**
+         * @brief The default constructor of the #TrackballCameraController.
+         */
+        TrackballCameraController();
+
+        /**
+         * @brief The destructor of the #TrackballCameraController (default behavior).
+         */
+        ~TrackballCameraController() = default;
+
+        /**
+         * @brief Sets @p radius as the new radius for orbiting around the camera's center point.
+         * @param[in] radius The new radius.
+         */
+        void setRadius(const float radius);
+
+        /**
+         * @brief Pans the view of @p camera according to the pitch and yaw values and additional offsets @p xOffset
+         * and @p yOffset.
+         * @param[in] xOffset The offset added to the yaw value.
+         * @param[in] yOffset The offset added to the pitch value.
+         * @param[in] camera The camera object.
+         */
+        void panView(double xOffset, double yOffset, Camera &camera);
+
+        /**
+         * @brief Updates @p camera in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         * @param[in] camera The camera object
+         */
+        void updateCamera(double deltaTime, Camera &camera);
+
+        /**
+         * @brief A callback function for key events. Currently, the trackball camera does not support camera movement.
+         * It can only orbit around its center point.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        void keyCallback(int key, int scancode, int action, int mods, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse scrolling events. Currently, this leads to changes in the field of view
+         * of the camera object.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         * @param[in] camera The camera object.
+         */
+        void scrollCallback(double offsetX, double offsetY, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse movement events. Currently, this leads to panning the view of the
+         * camera, if #mouseButtonCallback(int button, int action, int mods) enabled panning.
+         * @param[in] xoffset The horizontal mouse position.
+         * @param[in] yoffset The vertical mouse position.
+         * @param[in] camera The camera object.
+         */
+        void mouseMoveCallback(double xoffset, double yoffset, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse button events. Currently, the right mouse button enables panning the
+         * view of the camera as long as it is pressed.
+         * @param[in] button The mouse button.
+         * @param[in] action The button action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        void mouseButtonCallback(int button, int action, int mods, Camera &camera);
+
+    };
+
+}
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/Camera.cpp b/modules/camera/src/vkcv/camera/Camera.cpp
index af89ed86881acc8b754a041d32599a78caac57b4..eb1857968b2284287691c6ed41ba168db30d3f84 100644
--- a/modules/camera/src/vkcv/camera/Camera.cpp
+++ b/modules/camera/src/vkcv/camera/Camera.cpp
@@ -1,44 +1,28 @@
 #include "vkcv/camera/Camera.hpp"
-#include <iostream>
 
-namespace vkcv {
+#define _USE_MATH_DEFINES
+#include <math.h>
 
-    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;
+namespace vkcv::camera {
 
-        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() {
+        lookAt(
+			glm::vec3(0.0f, 0.0f, -1.0f),
+			glm::vec3(0.0f, 0.0f, 0.0f),
+			glm::vec3(0.0f, 1.0f, 0.0f)
+		);
+  
+		setFront(glm::normalize(m_center - m_position));
     }
 
     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::lookAt(const glm::vec3& position, const glm::vec3& center, const glm::vec3& up) {
+    	m_position = position;
+    	m_center = center;
+    	m_up = up;
+    	
+		setView(glm::lookAt(position, center, up));
     }
 
     void Camera::getNearFar( float &near, float &far) const {
@@ -46,98 +30,111 @@ namespace vkcv {
         far = m_far;
     }
 
-
-    const glm::mat4 Camera::getView() const {
+    const glm::mat4& Camera::getView() const {
         return m_view;
     }
+    
+    void Camera::setView(const glm::mat4 &view) {
+		m_view = view;
+	}
 
     const glm::mat4& Camera::getProjection() const {
         return m_projection;
     }
 
-    void Camera::setProjection(const glm::mat4 projection){
-        m_projection = projection;
+    void Camera::setProjection(const glm::mat4& projection) {
+        m_projection =  projection;
     }
 
-    float Camera::getFov() const {
-        return m_fov;
+    glm::mat4 Camera::getMVP() const {
+		const glm::mat4 y_correction (
+				1.0f,  0.0f,  0.0f,  0.0f,
+				0.0f, -1.0f,  0.0f,  0.0f,
+				0.0f,  0.0f,  1.0f,  0.0f,
+				0.0f,  0.0f,  0.0f,  1.0f
+		);
+    	
+        return y_correction * m_projection * m_view;
     }
 
-    void Camera::setFov( float fov){
-        m_fov = fov;
-        setPerspective( m_fov, m_ratio, m_near, m_far);
+    float Camera::getFov() const {
+    	const float tanHalfFovy = 1.0f / m_projection[1][1];
+    	float halfFovy = std::atan(tanHalfFovy);
+    	
+    	if (halfFovy < 0) {
+    		halfFovy += static_cast<float>(M_PI);
+    	}
+    	
+        return halfFovy * 2.0f;
     }
 
-    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::setFov( float fov){
+        setPerspective(fov, getRatio(), m_near, m_far);
     }
 
-    void Camera::updateRatio( int width, int height){
-        m_width = width;
-        m_height = height;
-        m_ratio = static_cast<float>(width)/glm::max(height, 1);
-        setPerspective( m_fov, m_ratio, m_near, m_far);
+    float Camera::getRatio() const {
+    	const float aspectProduct = 1.0f / m_projection[0][0];
+		const float tanHalfFovy = 1.0f / m_projection[1][1];
+		
+        return aspectProduct / tanHalfFovy;
     }
 
-    float Camera::getRatio() const {
-        return m_ratio;
+    void Camera::setRatio(float ratio){
+        setPerspective( getFov(), ratio, m_near, m_far);
     }
 
-    void Camera::setNearFar( float near, float far){
-        m_near = near;
-        m_far = far;
-        setPerspective(m_fov, m_ratio, m_near, m_far);
+    void Camera::setNearFar(float near, float far){
+        setPerspective(getFov(), getRatio(), near, 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);
+    void Camera::setPerspective(float fov, float ratio, float near, float far) {
+		m_near = near;
+		m_far = far;
+		setProjection(glm::perspective(fov, ratio, near, 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));
+        direction.x = std::sin(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
+        direction.y = std::sin(glm::radians(m_pitch));
+        direction.z = std::cos(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
         return glm::normalize(direction);
     }
+    
+    void Camera::setFront(const glm::vec3 &front) {
+		m_pitch = std::atan2(front.y, std::sqrt(front.x * front.x + front.z * front.z));
+		m_yaw = std::atan2(front.x, front.z);
+    }
 
-    glm::vec3 Camera::getPosition() const {
+    const glm::vec3& Camera::getPosition() const {
         return m_position;
     }
 
-    void Camera::setPosition( glm::vec3 position ){
-        m_position = position;
+    void Camera::setPosition( const glm::vec3& position ){
+		lookAt(position, m_center, m_up);
     }
 
-    void Camera::setUp(const glm::vec3 &up) {
-        m_up = up;
+    const glm::vec3& Camera::getCenter() const {
+        return m_center;
     }
 
+    void Camera::setCenter(const glm::vec3& center) {
+		lookAt(m_position, center, m_up);
+    }
+	
+	const glm::vec3& Camera::getUp() const {
+		return m_up;
+	}
+	
+	void Camera::setUp(const glm::vec3 &up) {
+		lookAt(m_position, m_center, 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;
     }
 
@@ -149,31 +146,4 @@ namespace vkcv {
         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
index 2631890d646fbf27a4fbb14cfeef706678d8918c..561596c25a66334b56b3253b998c8c63428ef121 100644
--- a/modules/camera/src/vkcv/camera/CameraManager.cpp
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -1,88 +1,158 @@
-#include <iostream>
+
 #include "vkcv/camera/CameraManager.hpp"
 
-namespace vkcv{
+#include <vkcv/Logger.hpp>
+
+namespace vkcv::camera {
 
-    CameraManager::CameraManager(Window &window, float width, float height, glm::vec3 up, glm::vec3 position):
-    m_window(window), m_width(width), m_height(height)
+    CameraManager::CameraManager(Window& window)
+    : m_window(window)
     {
-        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();
+        bindCameraToEvents();
+        m_activeCameraIndex = 0;
+        m_lastX = static_cast<float>(window.getWidth()) / 2.0f;
+        m_lastY = static_cast<float>(window.getHeight()) / 2.0f;
     }
 
-    void CameraManager::bindCamera(){
-        e_keyHandle = m_window.e_key.add( [&](int key, int scancode, int action, int mods) { this->keyCallback(key, scancode, action, mods); });
-        e_mouseMoveHandle = m_window.e_mouseMove.add( [&]( double offsetX, double offsetY) {this->mouseMoveCallback( offsetX, offsetY);} );
-        e_mouseScrollHandle =  m_window.e_mouseScroll.add([&](double offsetX, double offsetY) {this->scrollCallback( offsetX, offsetY);} );
-        e_mouseButtonHandle = m_window.e_mouseButton.add([&] (int button, int action, int mods) {this->mouseButtonCallback( button,  action,  mods);});
-        e_resizeHandle = m_window.e_resize.add([&] (int width, int height) {this->resizeCallback( width, height);});
+    CameraManager::~CameraManager() {}
+
+    void CameraManager::bindCameraToEvents() {
+        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);});
+        m_resizeHandle = m_window.e_resize.add([&](int width, int height) {this->resizeCallback(width, height);});
+    }
+
+    void CameraManager::resizeCallback(int width, int height) {
+        for (size_t i = 0; i < m_cameras.size(); i++) {
+            getCamera(i).setRatio(static_cast<float>(width) / static_cast<float>(height));;
+        }
     }
 
     void CameraManager::mouseButtonCallback(int button, int action, int mods){
-        if(button == GLFW_MOUSE_BUTTON_2 && m_roationActive == false && action == GLFW_PRESS){
+        if(button == GLFW_MOUSE_BUTTON_2 && 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){
+        }
+        else if(button == GLFW_MOUSE_BUTTON_2 && action == GLFW_RELEASE){
             glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
-            m_roationActive = false;
         }
+		getActiveController().mouseButtonCallback(button, action, mods, getActiveCamera());
     }
 
     void CameraManager::mouseMoveCallback(double x, double y){
-
-        float xoffset = x - m_lastX;
-        float yoffset = m_lastY - y;
+        auto xoffset = static_cast<float>(x - m_lastX);
+		auto yoffset = static_cast<float>(y - m_lastY);
         m_lastX = x;
         m_lastY = y;
+		getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera());
+    }
 
-        if(!m_roationActive){
-            return;
+    void CameraManager::scrollCallback(double offsetX, double offsetY) {
+		getActiveController().scrollCallback(offsetX, offsetY, getActiveCamera());
+    }
+
+    void CameraManager::keyCallback(int key, int scancode, int action, int mods)  {
+        switch (action) {
+            case GLFW_RELEASE:
+                switch (key) {
+                    case GLFW_KEY_TAB:
+                        if (m_activeCameraIndex + 1 == m_cameras.size()) {
+                            m_activeCameraIndex = 0;
+                        }
+                        else {
+                            m_activeCameraIndex++;
+                        }
+                        return;
+                    case GLFW_KEY_ESCAPE:
+                        glfwSetWindowShouldClose(m_window.getWindow(), 1);
+                        return;
+					default:
+						break;
+                }
+            default:
+				getActiveController().keyCallback(key, scancode, action, mods, getActiveCamera());
+                break;
         }
+    }
+    
+    CameraController& CameraManager::getActiveController() {
+    	const ControllerType type = getControllerType(getActiveCameraIndex());
+    	return getControllerByType(type);
+    }
+	
+	uint32_t CameraManager::addCamera(ControllerType controllerType) {
+    	const float ratio = static_cast<float>(m_window.getWidth()) / static_cast<float>(m_window.getHeight());
+    	
+        Camera camera;
+        camera.setPerspective(glm::radians(60.0f), ratio, 0.1f, 10.0f);
+        return addCamera(controllerType, camera);
+    }
+    
+    uint32_t CameraManager::addCamera(ControllerType controllerType, const Camera &camera) {
+    	const uint32_t index = static_cast<uint32_t>(m_cameras.size());
+    	m_cameras.push_back(camera);
+		m_cameraControllerTypes.push_back(controllerType);
+		return index;
+    }
 
-        float sensitivity = 0.05f;
-        xoffset *= sensitivity;
-        yoffset *= sensitivity;
+    Camera& CameraManager::getCamera(uint32_t cameraIndex) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+        	vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+        	return getActiveCamera();
+        }
+        
+        return m_cameras[cameraIndex];
+    }
 
-        m_camera.panView( xoffset , yoffset );
+    Camera& CameraManager::getActiveCamera() {
+        return m_cameras[getActiveCameraIndex()];
     }
 
-    void CameraManager::scrollCallback(double offsetX, double offsetY) {
-        m_camera.changeFov(offsetY);
+    void CameraManager::setActiveCamera(uint32_t cameraIndex) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+			return;
+        }
+        
+        m_activeCameraIndex = cameraIndex;
     }
 
-    void CameraManager::keyCallback(int key, int scancode, int action, int mods) {
+    uint32_t CameraManager::getActiveCameraIndex() const {
+        return m_activeCameraIndex;
+    }
 
-        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;
+    void CameraManager::setControllerType(uint32_t cameraIndex, ControllerType controllerType) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+			return;
         }
+        
+        m_cameraControllerTypes[cameraIndex] = controllerType;
     }
 
-    void CameraManager::resizeCallback(int width, int height){
-            m_camera.updateRatio(width, height);
+    ControllerType CameraManager::getControllerType(uint32_t cameraIndex) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+			return ControllerType::NONE;
+        }
+        
+        return m_cameraControllerTypes[cameraIndex];
     }
 
-    Camera &CameraManager::getCamera(){
-        return m_camera;
+    CameraController& CameraManager::getControllerByType(ControllerType controllerType) {
+        switch(controllerType) {
+            case ControllerType::PILOT:
+                return m_pilotController;
+            case ControllerType::TRACKBALL:
+                return m_trackController;
+            default:
+                return m_pilotController;
+        }
     }
 
+    void CameraManager::update(double deltaTime) {
+		getActiveController().updateCamera(deltaTime, getActiveCamera());
+	}
+	
 }
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/PilotCameraController.cpp b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1a50a0efa4b4e75adb81ce869d6b927bd0046758
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
@@ -0,0 +1,155 @@
+#include "vkcv/camera/PilotCameraController.hpp"
+
+#include <GLFW/glfw3.h>
+
+namespace vkcv::camera {
+
+    PilotCameraController::PilotCameraController() {
+        m_forward = false;
+        m_backward = false;
+        m_upward = false;
+        m_downward = false;
+        m_left = false;
+        m_right = false;
+
+        m_rotationActive = false;
+
+        m_cameraSpeed = 2.0f;
+
+        m_fov_nsteps = 100;
+        m_fov_min = 10;
+        m_fov_max = 120;
+    }
+
+    void PilotCameraController::changeFov(double offset, Camera &camera){
+        float fov = camera.getFov();
+        float fov_range = m_fov_max - m_fov_min;
+        float fov_stepsize = glm::radians(fov_range) / static_cast<float>(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);
+        }
+        camera.setFov(fov);
+    }
+
+    void PilotCameraController::panView(double xOffset, double yOffset, Camera &camera) {
+        // handle yaw rotation
+        float yaw = camera.getYaw() + xOffset;
+        if (yaw < -180.0f) {
+            yaw += 360.0f;
+        }
+        else if (yaw > 180.0f) {
+            yaw -= 360.0f;
+        }
+        camera.setYaw(yaw);
+
+        // handle pitch rotation
+        float pitch = camera.getPitch() - yOffset;
+        if (pitch > 89.0f) {
+            pitch = 89.0f;
+        }
+        if (pitch < -89.0f) {
+            pitch = -89.0f;
+        }
+        camera.setPitch(pitch);
+    }
+    
+    constexpr float getDirectionFactor(bool positive, bool negative) {
+    	return static_cast<float>(positive) - static_cast<float>(negative);
+    }
+
+    void PilotCameraController::updateCamera(double deltaTime, Camera &camera) {
+		glm::vec3 position = camera.getPosition();
+	
+		const glm::vec3 front = camera.getFront();
+		const glm::vec3 up = camera.getUp();
+		const glm::vec3 left = glm::normalize(glm::cross(front, up));
+	
+		const float distance = m_cameraSpeed * static_cast<float>(deltaTime);
+	
+		position += distance * getDirectionFactor(m_forward, m_backward) * front;
+		position += distance * getDirectionFactor(m_left, m_right) * left;
+		position += distance * getDirectionFactor(m_upward, m_downward) * up;
+	
+		camera.lookAt(position, position + front, up);
+    }
+
+    void PilotCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) {
+        switch (key) {
+            case GLFW_KEY_W:
+                moveForward(action);
+                break;
+            case GLFW_KEY_S:
+                moveBackward(action);
+                break;
+            case GLFW_KEY_A:
+                moveLeft(action);
+                break;
+            case GLFW_KEY_D:
+                moveRight(action);
+                break;
+            case GLFW_KEY_E:
+                moveUpward(action);
+                break;
+            case GLFW_KEY_Q:
+                moveDownward(action);
+                break;
+            default:
+                break;
+        }
+    }
+
+    void PilotCameraController::scrollCallback(double offsetX, double offsetY, Camera &camera) {
+        changeFov(offsetY, camera);
+    }
+
+    void PilotCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
+        if(!m_rotationActive){
+            return;
+        }
+
+        float sensitivity = 0.05f;
+        xoffset *= sensitivity;
+        yoffset *= sensitivity;
+
+        panView(xoffset , yoffset, camera);
+    }
+
+    void PilotCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
+        if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == false && action == GLFW_PRESS){
+            m_rotationActive = true;
+        }
+        else if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == true && action == GLFW_RELEASE){
+            m_rotationActive = false;
+        }
+    }
+
+
+    void PilotCameraController::moveForward(int action){
+        m_forward = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveBackward(int action){
+        m_backward = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveLeft(int action){
+        m_left = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveRight(int action){
+        m_right = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveUpward(int action){
+        m_upward = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveDownward(int action){
+        m_downward = static_cast<bool>(action);
+    }
+
+}
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/TrackballCamera.cpp b/modules/camera/src/vkcv/camera/TrackballCamera.cpp
deleted file mode 100644
index 3bbb8611dd234499fb9ba08ba87009c8c76660f6..0000000000000000000000000000000000000000
--- a/modules/camera/src/vkcv/camera/TrackballCamera.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-#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/camera/src/vkcv/camera/TrackballCameraController.cpp b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..201c6ecdc1c703dbcd53b7dc4b179c86576f2312
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
@@ -0,0 +1,100 @@
+#include "vkcv/camera/TrackballCameraController.hpp"
+
+#include <GLFW/glfw3.h>
+
+namespace vkcv::camera {
+
+    TrackballCameraController::TrackballCameraController() {
+        m_rotationActive = false;
+        m_radius = 3.0f;
+        m_cameraSpeed = 2.5f;
+        m_scrollSensitivity = 0.2f;
+    }
+
+    void TrackballCameraController::setRadius(const float radius) {
+        if (radius < 0.1f) {
+            m_radius = 0.1f;
+        }
+        else {
+            m_radius = radius;
+        }
+    }
+
+    void TrackballCameraController::panView(double xOffset, double yOffset, Camera &camera) {
+        // handle yaw rotation
+        float yaw = camera.getYaw() + xOffset * m_cameraSpeed;
+        if (yaw < 0.0f) {
+            yaw += 360.0f;
+        }
+        else if (yaw > 360.0f) {
+            yaw -= 360.0f;
+        }
+        camera.setYaw(yaw);
+
+        // handle pitch rotation
+        float pitch = camera.getPitch() + yOffset * m_cameraSpeed;
+        if (pitch < 0.0f) {
+            pitch += 360.0f;
+        }
+        else if (pitch > 360.0f) {
+            pitch -= 360.0f;
+        }
+        camera.setPitch(pitch);
+    }
+
+    void TrackballCameraController::updateRadius(double offset, Camera &camera) {
+        glm::vec3 cameraPosition = camera.getPosition();
+        glm::vec3 cameraCenter = camera.getCenter();
+        float radius = glm::length(cameraCenter - cameraPosition);  // get current camera radius
+        setRadius(radius - offset * m_scrollSensitivity);
+    }
+
+    void TrackballCameraController::updateCamera(double deltaTime, Camera &camera) {
+		float yaw = camera.getYaw();
+		float pitch = camera.getPitch();
+		
+		const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
+		const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
+	
+		const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(yaw), yAxis);
+		const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(pitch), xAxis);
+		const glm::vec3 translation = glm::vec3(
+				rotationX * glm::vec4(0.0f, 0.0f, m_radius, 0.0f)
+		);
+		
+		const glm::vec3 center = camera.getCenter();
+		const glm::vec3 position = center + translation;
+		const glm::vec3 up = glm::vec3(
+				rotationX * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)
+		);
+		
+		camera.lookAt(position, center, up);
+    }
+
+    void TrackballCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) {}
+
+    void TrackballCameraController::scrollCallback(double offsetX, double offsetY, Camera &camera) {
+        updateRadius(offsetY, camera);
+    }
+
+    void TrackballCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
+        if(!m_rotationActive){
+            return;
+        }
+
+        float sensitivity = 0.05f;
+        xoffset *= sensitivity;
+        yoffset *= sensitivity;
+
+        panView(xoffset , yoffset, camera);
+    }
+
+    void TrackballCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
+        if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == false && action == GLFW_PRESS){
+            m_rotationActive = true;
+        }
+        else if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == true && action == GLFW_RELEASE){
+            m_rotationActive = false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/shader_compiler/CMakeLists.txt b/modules/shader_compiler/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4b674ec41ed4ea5f42dc73187c212e6a69952cec
--- /dev/null
+++ b/modules/shader_compiler/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_shader_compiler)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_shader_compiler_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_shader_compiler_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_shader_compiler_sources
+		${vkcv_shader_compiler_include}/vkcv/shader/GLSLCompiler.hpp
+		${vkcv_shader_compiler_source}/vkcv/shader/GLSLCompiler.cpp
+)
+
+# adding source files to the module
+add_library(vkcv_shader_compiler STATIC ${vkcv_shader_compiler_sources})
+
+# Setup some path variables to load libraries
+set(vkcv_shader_compiler_lib lib)
+set(vkcv_shader_compiler_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_shader_compiler_lib})
+
+# Check and load GLSLANG
+include(config/GLSLANG.cmake)
+
+# link the required libraries to the module
+target_link_libraries(vkcv_shader_compiler ${vkcv_shader_compiler_libraries} vkcv)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_shader_compiler SYSTEM BEFORE PRIVATE ${vkcv_shader_compiler_includes} ${vkcv_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_shader_compiler BEFORE PUBLIC ${vkcv_shader_compiler_include})
diff --git a/modules/shader_compiler/config/GLSLANG.cmake b/modules/shader_compiler/config/GLSLANG.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..50b9fd46bd0db9421c632aa0b80fb8df7e3f2123
--- /dev/null
+++ b/modules/shader_compiler/config/GLSLANG.cmake
@@ -0,0 +1,28 @@
+
+if (EXISTS "${vkcv_shader_compiler_lib_path}/glslang")
+	set(SKIP_GLSLANG_INSTALL ON CACHE INTERNAL "")
+	set(ENABLE_SPVREMAPPER OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_BINARIES OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_JS OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_WEBMIN OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_WEBMIN_DEVEL OFF CACHE INTERNAL "")
+	set(ENABLE_EMSCRIPTEN_SINGLE_FILE OFF CACHE INTERNAL "")
+	set(ENABLE_EMSCRIPTEN_ENVIRONMENT_NODE OFF CACHE INTERNAL "")
+	set(ENABLE_HLSL OFF CACHE INTERNAL "")
+	set(ENABLE_RTTI OFF CACHE INTERNAL "")
+	set(ENABLE_EXCEPTIONS OFF CACHE INTERNAL "")
+	set(ENABLE_OPT OFF CACHE INTERNAL "")
+	set(ENABLE_PCH OFF CACHE INTERNAL "")
+	set(ENABLE_CTEST OFF CACHE INTERNAL "")
+	set(USE_CCACHE OFF CACHE INTERNAL "")
+	
+	set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "")
+	set(BUILD_EXTERNAL OFF CACHE INTERNAL "")
+	
+	add_subdirectory(${vkcv_shader_compiler_lib}/glslang)
+	
+	list(APPEND vkcv_shader_compiler_libraries glslang SPIRV)
+	list(APPEND vkcv_shader_compiler_includes ${vkcv_shader_compiler_lib})
+else()
+	message(WARNING "GLSLANG is required..! Update the submodules!")
+endif ()
diff --git a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d7b7af7178531aea358cecbc8b86a29527173014
--- /dev/null
+++ b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <vkcv/Event.hpp>
+
+namespace vkcv::shader {
+	
+	typedef typename event_function<ShaderStage, const std::filesystem::path&>::type ShaderCompiledFunction;
+	
+	class Compiler {
+	private:
+	public:
+		virtual void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath,
+							 const ShaderCompiledFunction& compiled, bool update = false) = 0;
+		
+	};
+	
+}
diff --git a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7105d93a0c3e153bf3abe1d624d0c13c6f09ac6d
--- /dev/null
+++ b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <filesystem>
+
+#include <vkcv/ShaderStage.hpp>
+#include "Compiler.hpp"
+
+namespace vkcv::shader {
+	
+	class GLSLCompiler {
+	private:
+	public:
+		GLSLCompiler();
+		
+		GLSLCompiler(const GLSLCompiler& other);
+		GLSLCompiler(GLSLCompiler&& other) = default;
+	
+		~GLSLCompiler();
+		
+		GLSLCompiler& operator=(const GLSLCompiler& other);
+		GLSLCompiler& operator=(GLSLCompiler&& other) = default;
+		
+		void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath,
+					 const ShaderCompiledFunction& compiled, bool update = false);
+		
+	};
+	
+}
diff --git a/modules/shader_compiler/lib/glslang b/modules/shader_compiler/lib/glslang
new file mode 160000
index 0000000000000000000000000000000000000000..fe15158676657bf965e41c32e15ae5db7ea2ab6a
--- /dev/null
+++ b/modules/shader_compiler/lib/glslang
@@ -0,0 +1 @@
+Subproject commit fe15158676657bf965e41c32e15ae5db7ea2ab6a
diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7214e208b068ba7b9002363e594f76d66123bffd
--- /dev/null
+++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
@@ -0,0 +1,267 @@
+
+#include "vkcv/shader/GLSLCompiler.hpp"
+
+#include <fstream>
+#include <glslang/SPIRV/GlslangToSpv.h>
+
+#include <vkcv/Logger.hpp>
+
+namespace vkcv::shader {
+	
+	static uint32_t s_CompilerCount = 0;
+	
+	GLSLCompiler::GLSLCompiler() {
+		if (s_CompilerCount == 0) {
+			glslang::InitializeProcess();
+		}
+		
+		s_CompilerCount++;
+	}
+	
+	GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) {
+		s_CompilerCount++;
+	}
+	
+	GLSLCompiler::~GLSLCompiler() {
+		s_CompilerCount--;
+		
+		if (s_CompilerCount == 0) {
+			glslang::FinalizeProcess();
+		}
+	}
+	
+	GLSLCompiler &GLSLCompiler::operator=(const GLSLCompiler &other) {
+		s_CompilerCount++;
+		return *this;
+	}
+	
+	constexpr EShLanguage findShaderLanguage(ShaderStage shaderStage) {
+		switch (shaderStage) {
+			case ShaderStage::VERTEX:
+				return EShLangVertex;
+			case ShaderStage::TESS_CONTROL:
+				return EShLangTessControl;
+			case ShaderStage::TESS_EVAL:
+				return EShLangTessEvaluation;
+			case ShaderStage::GEOMETRY:
+				return EShLangGeometry;
+			case ShaderStage::FRAGMENT:
+				return EShLangFragment;
+			case ShaderStage::COMPUTE:
+				return EShLangCompute;
+			default:
+				return EShLangCount;
+		}
+	}
+	
+	static void initResources(TBuiltInResource& resources) {
+		resources.maxLights = 32;
+		resources.maxClipPlanes = 6;
+		resources.maxTextureUnits = 32;
+		resources.maxTextureCoords = 32;
+		resources.maxVertexAttribs = 64;
+		resources.maxVertexUniformComponents = 4096;
+		resources.maxVaryingFloats = 64;
+		resources.maxVertexTextureImageUnits = 32;
+		resources.maxCombinedTextureImageUnits = 80;
+		resources.maxTextureImageUnits = 32;
+		resources.maxFragmentUniformComponents = 4096;
+		resources.maxDrawBuffers = 32;
+		resources.maxVertexUniformVectors = 128;
+		resources.maxVaryingVectors = 8;
+		resources.maxFragmentUniformVectors = 16;
+		resources.maxVertexOutputVectors = 16;
+		resources.maxFragmentInputVectors = 15;
+		resources.minProgramTexelOffset = -8;
+		resources.maxProgramTexelOffset = 7;
+		resources.maxClipDistances = 8;
+		resources.maxComputeWorkGroupCountX = 65535;
+		resources.maxComputeWorkGroupCountY = 65535;
+		resources.maxComputeWorkGroupCountZ = 65535;
+		resources.maxComputeWorkGroupSizeX = 1024;
+		resources.maxComputeWorkGroupSizeY = 1024;
+		resources.maxComputeWorkGroupSizeZ = 64;
+		resources.maxComputeUniformComponents = 1024;
+		resources.maxComputeTextureImageUnits = 16;
+		resources.maxComputeImageUniforms = 8;
+		resources.maxComputeAtomicCounters = 8;
+		resources.maxComputeAtomicCounterBuffers = 1;
+		resources.maxVaryingComponents = 60;
+		resources.maxVertexOutputComponents = 64;
+		resources.maxGeometryInputComponents = 64;
+		resources.maxGeometryOutputComponents = 128;
+		resources.maxFragmentInputComponents = 128;
+		resources.maxImageUnits = 8;
+		resources.maxCombinedImageUnitsAndFragmentOutputs = 8;
+		resources.maxCombinedShaderOutputResources = 8;
+		resources.maxImageSamples = 0;
+		resources.maxVertexImageUniforms = 0;
+		resources.maxTessControlImageUniforms = 0;
+		resources.maxTessEvaluationImageUniforms = 0;
+		resources.maxGeometryImageUniforms = 0;
+		resources.maxFragmentImageUniforms = 8;
+		resources.maxCombinedImageUniforms = 8;
+		resources.maxGeometryTextureImageUnits = 16;
+		resources.maxGeometryOutputVertices = 256;
+		resources.maxGeometryTotalOutputComponents = 1024;
+		resources.maxGeometryUniformComponents = 1024;
+		resources.maxGeometryVaryingComponents = 64;
+		resources.maxTessControlInputComponents = 128;
+		resources.maxTessControlOutputComponents = 128;
+		resources.maxTessControlTextureImageUnits = 16;
+		resources.maxTessControlUniformComponents = 1024;
+		resources.maxTessControlTotalOutputComponents = 4096;
+		resources.maxTessEvaluationInputComponents = 128;
+		resources.maxTessEvaluationOutputComponents = 128;
+		resources.maxTessEvaluationTextureImageUnits = 16;
+		resources.maxTessEvaluationUniformComponents = 1024;
+		resources.maxTessPatchComponents = 120;
+		resources.maxPatchVertices = 32;
+		resources.maxTessGenLevel = 64;
+		resources.maxViewports = 16;
+		resources.maxVertexAtomicCounters = 0;
+		resources.maxTessControlAtomicCounters = 0;
+		resources.maxTessEvaluationAtomicCounters = 0;
+		resources.maxGeometryAtomicCounters = 0;
+		resources.maxFragmentAtomicCounters = 8;
+		resources.maxCombinedAtomicCounters = 8;
+		resources.maxAtomicCounterBindings = 1;
+		resources.maxVertexAtomicCounterBuffers = 0;
+		resources.maxTessControlAtomicCounterBuffers = 0;
+		resources.maxTessEvaluationAtomicCounterBuffers = 0;
+		resources.maxGeometryAtomicCounterBuffers = 0;
+		resources.maxFragmentAtomicCounterBuffers = 1;
+		resources.maxCombinedAtomicCounterBuffers = 1;
+		resources.maxAtomicCounterBufferSize = 16384;
+		resources.maxTransformFeedbackBuffers = 4;
+		resources.maxTransformFeedbackInterleavedComponents = 64;
+		resources.maxCullDistances = 8;
+		resources.maxCombinedClipAndCullDistances = 8;
+		resources.maxSamples = 4;
+		resources.maxMeshOutputVerticesNV = 256;
+		resources.maxMeshOutputPrimitivesNV = 512;
+		resources.maxMeshWorkGroupSizeX_NV = 32;
+		resources.maxMeshWorkGroupSizeY_NV = 1;
+		resources.maxMeshWorkGroupSizeZ_NV = 1;
+		resources.maxTaskWorkGroupSizeX_NV = 32;
+		resources.maxTaskWorkGroupSizeY_NV = 1;
+		resources.maxTaskWorkGroupSizeZ_NV = 1;
+		resources.maxMeshViewCountNV = 4;
+		resources.limits.nonInductiveForLoops = 1;
+		resources.limits.whileLoops = 1;
+		resources.limits.doWhileLoops = 1;
+		resources.limits.generalUniformIndexing = 1;
+		resources.limits.generalAttributeMatrixVectorIndexing = 1;
+		resources.limits.generalVaryingIndexing = 1;
+		resources.limits.generalSamplerIndexing = 1;
+		resources.limits.generalVariableIndexing = 1;
+		resources.limits.generalConstantMatrixVectorIndexing = 1;
+	}
+	
+	static std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) {
+		std::ifstream file (shaderPath.string(), std::ios::ate);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return std::vector<char>{};
+		}
+		
+		std::streamsize fileSize = file.tellg();
+		std::vector<char> buffer (fileSize);
+		
+		file.seekg(0);
+		file.read(buffer.data(), fileSize);
+		file.close();
+		
+		return buffer;
+	}
+	
+	static bool writeSpirvCode(const std::filesystem::path &shaderPath, const std::vector<uint32_t>& spirv) {
+		std::ofstream file (shaderPath.string(), std::ios::out | std::ios::binary);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return false;
+		}
+		
+		const auto fileSize = static_cast<std::streamsize>(
+				sizeof(uint32_t) * spirv.size()
+		);
+		
+		file.seekp(0);
+		file.write(reinterpret_cast<const char*>(spirv.data()), fileSize);
+		file.close();
+		
+		return true;
+	}
+	
+	void GLSLCompiler::compile(ShaderStage shaderStage, const std::filesystem::path &shaderPath,
+							   const ShaderCompiledFunction& compiled, bool update) {
+		const EShLanguage language = findShaderLanguage(shaderStage);
+		
+		if (language == EShLangCount) {
+			vkcv_log(LogLevel::ERROR, "Shader stage not supported (%s)", shaderPath.string().c_str());
+			return;
+		}
+		
+		const std::vector<char> code = readShaderCode(shaderPath);
+		
+		glslang::TShader shader (language);
+		glslang::TProgram program;
+		
+		const char *shaderStrings [1];
+		shaderStrings[0] = code.data();
+		
+		shader.setStrings(shaderStrings, 1);
+		
+		TBuiltInResource resources = {};
+		initResources(resources);
+		
+		const auto messages = (EShMessages) (
+				EShMsgSpvRules |
+				EShMsgVulkanRules
+		);
+		
+		if (!shader.parse(&resources, 100, false, messages)) {
+			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)",
+					 shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
+			return;
+		}
+		
+		program.addShader(&shader);
+		
+		if (!program.link(messages)) {
+			vkcv_log(LogLevel::ERROR, "Shader linking failed {\n%s\n%s\n} (%s)",
+					 shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
+			return;
+		}
+		
+		const glslang::TIntermediate* intermediate = program.getIntermediate(language);
+		
+		if (!intermediate) {
+			vkcv_log(LogLevel::ERROR, "No valid intermediate representation (%s)", shaderPath.string().c_str());
+			return;
+		}
+		
+		std::vector<uint32_t> spirv;
+		glslang::GlslangToSpv(*intermediate, spirv);
+		
+		const std::filesystem::path tmp_path (std::tmpnam(nullptr));
+		
+		if (!writeSpirvCode(tmp_path, spirv)) {
+			vkcv_log(LogLevel::ERROR, "Spir-V could not be written to disk (%s)", shaderPath.string().c_str());
+			return;
+		}
+		
+		if (compiled) {
+			compiled(shaderStage, tmp_path);
+		}
+		
+		std::filesystem::remove(tmp_path);
+		
+		if (update) {
+			// TODO: Shader hot compilation during runtime
+		}
+	}
+	
+}
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
index 4c77e6aefa3bd2edda0220798633db87ee737d3c..c33f1adc985b40566aa25c5f51e2a1c622dfa280 100644
--- a/projects/cmd_sync_test/src/main.cpp
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -18,9 +18,15 @@ int main(int argc, const char** argv) {
 		true
 	);
 
-	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
-	cameraManager.getCamera().setPosition(glm::vec3(0.f, 0.f, 3.f));
-	cameraManager.getCamera().setNearFar(0.1, 30);
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+    
+    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
+    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
+	cameraManager.getCamera(camIndex).setYaw(180.0f);
+	
+	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
 
 	window.initEvents();
 
@@ -65,7 +71,7 @@ int main(int argc, const char** argv) {
 	
 	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
 	
-	std::sort(attributes.begin(), attributes.end(), [](const vkcv::VertexAttribute& x, const vkcv::VertexAttribute& y) {
+	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);
 	});
 
@@ -89,34 +95,43 @@ int main(int argc, const char** argv) {
 			vk::Format::eD32Sfloat
 	);
 
-	vkcv::PassConfig trianglePassDefinition({ present_color_attachment, depth_attachment });
-	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
+	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
 
-	if (!trianglePass) {
+	if (!firstMeshPass) {
 		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
-	vkcv::ShaderProgram triangleShaderProgram{};
-	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+	vkcv::ShaderProgram firstMeshProgram{};
+    firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+    firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.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 firstMeshLayout (bindings);
 
-	std::vector<vkcv::DescriptorBinding> descriptorBindings = { triangleShaderProgram.getReflectedDescriptors()[0] };
+	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[0] };
 	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
 
-	const vkcv::PipelineConfig trianglePipelineDefinition(
-		triangleShaderProgram, 
+	const vkcv::PipelineConfig firstMeshPipelineConfig {
+        firstMeshProgram,
 		windowWidth,
 		windowHeight,
-		trianglePass,
-		attributes,
+        firstMeshPass,
+        firstMeshLayout,
 		{ core.getDescriptorSet(descriptorSet).layout },
-		true);
-	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+		true
+	};
+	
+	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
 	
-	if (!trianglePipeline) {
+	if (!firstMeshPipeline) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
@@ -172,8 +187,6 @@ int main(int argc, const char** argv) {
 	vkcv::ShaderProgram shadowShader;
 	shadowShader.addShader(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow_vert.spv");
 	shadowShader.addShader(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow_frag.spv");
-    shadowShader.reflectShader(vkcv::ShaderStage::VERTEX);
-    shadowShader.reflectShader(vkcv::ShaderStage::FRAGMENT);
 
 	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
 	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
@@ -184,14 +197,16 @@ int main(int argc, const char** argv) {
 
 	const uint32_t shadowMapResolution = 1024;
 	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
-	const vkcv::PipelineConfig shadowPipeConfig(
+	const vkcv::PipelineConfig shadowPipeConfig {
 		shadowShader, 
 		shadowMapResolution, 
 		shadowMapResolution, 
-		shadowPass, 
-		attributes,
-		{}, 
-		false);
+		shadowPass,
+        firstMeshLayout,
+		{},
+		false
+	};
+	
 	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
 
 	struct LightInfo {
@@ -231,10 +246,13 @@ int main(int argc, const char** argv) {
 		
 		auto end = std::chrono::system_clock::now();
 		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		
 		start = end;
-		cameraManager.getCamera().updateView(deltatime.count() * 0.000001);
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
 
-		const float sunTheta = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime).count() * 0.001f;
+		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
+		
+		const float sunTheta = 0.001f * static_cast<float>(duration.count());
 		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
 
 		const float shadowProjectionSize = 5.f;
@@ -256,7 +274,7 @@ int main(int argc, const char** argv) {
 		lightInfo.lightMatrix = projectionLight * viewLight;
 		lightBuffer.fill({ lightInfo });
 
-		const glm::mat4 viewProjectionCamera = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
 
 		mainPassMatrices.clear();
 		mvpLight.clear();
@@ -284,8 +302,8 @@ int main(int argc, const char** argv) {
 
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
-			trianglePass,
-			trianglePipeline,
+            firstMeshPass,
+            firstMeshPipeline,
 			pushConstantData,
 			drawcalls,
 			renderTargets);
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 611ab6cc91450e109bb06c1b5c7dcf2802259d7c..beb0bab651e375516ed59ff30b1c5e742076bcd9 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -18,8 +18,6 @@ int main(int argc, const char** argv) {
 		true
 	);
 
-	vkcv::CameraManager cameraManager(window, static_cast<float>(window.getWidth()), static_cast<float>(window.getHeight()));
-
 	window.initEvents();
 
 	vkcv::Core core = vkcv::Core::create(
@@ -74,41 +72,48 @@ int main(int argc, const char** argv) {
 			vk::Format::eD32Sfloat
 	);
 
-	vkcv::PassConfig trianglePassDefinition({ present_color_attachment, depth_attachment });
-	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
+	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
 
-	if (!trianglePass) {
+	if (!firstMeshPass) {
 		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
-	vkcv::ShaderProgram triangleShaderProgram{};
-	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+	vkcv::ShaderProgram firstMeshProgram{};
+    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;
 	
-	std::sort(attributes.begin(), attributes.end(), [](const vkcv::VertexAttribute& x, const vkcv::VertexAttribute& y) {
+	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);
 	});
 
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.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 firstMeshLayout (bindings);
+
 	uint32_t setID = 0;
-	std::vector<vkcv::DescriptorBinding> descriptorBindings = { triangleShaderProgram.getReflectedDescriptors()[setID] };
+	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[setID] };
 	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
 
-	const vkcv::PipelineConfig trianglePipelineDefinition(
-		triangleShaderProgram,
+	const vkcv::PipelineConfig firstMeshPipelineConfig {
+        firstMeshProgram,
         UINT32_MAX,
         UINT32_MAX,
-		trianglePass,
-		mesh.vertexGroups[2].vertexBuffer.attributes,
+        firstMeshPass,
+        {firstMeshLayout},
 		{ core.getDescriptorSet(descriptorSet).layout },
-		true);
-	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+		true
+	};
+	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
 	
-	if (!trianglePipeline) {
+	if (!firstMeshPipeline) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
@@ -127,10 +132,9 @@ int main(int argc, const char** argv) {
 	);
 
 	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
-		vkcv::VertexBufferBinding( mesh.vertexGroups[2].vertexBuffer.attributes[0].offset, vertexBuffer.getVulkanHandle() ),
-		vkcv::VertexBufferBinding( mesh.vertexGroups[2].vertexBuffer.attributes[1].offset, vertexBuffer.getVulkanHandle() ),
-		vkcv::VertexBufferBinding( mesh.vertexGroups[2].vertexBuffer.attributes[2].offset, vertexBuffer.getVulkanHandle() )
-	};
+		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[0].offset), vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[1].offset), vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[2].offset), vertexBuffer.getVulkanHandle()) };
 
 	vkcv::DescriptorWrites setWrites;
 	setWrites.sampledImageWrites    = { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
@@ -146,7 +150,14 @@ int main(int argc, const char** argv) {
 	vkcv::DescriptorSetUsage    descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
 	vkcv::DrawcallInfo          drawcall(renderMesh, { descriptorUsage });
 
-	auto start = std::chrono::system_clock::now();
+    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));
+
+    auto start = std::chrono::system_clock::now();
+    
 	while (window.isWindowOpen()) {
         vkcv::Window::pollEvents();
 		
@@ -166,10 +177,11 @@ int main(int argc, const char** argv) {
 		}
   
 		auto end = std::chrono::system_clock::now();
-		auto deltatime = end - start;
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		
 		start = end;
-		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
-		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+        glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
 		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
 
@@ -178,8 +190,8 @@ int main(int argc, const char** argv) {
 
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
-			trianglePass,
-			trianglePipeline,
+			firstMeshPass,
+			firstMeshPipeline,
 			pushConstantData,
 			{ drawcall },
 			renderTargets);
diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt
index e7c8373e085df6497060b8d1d8164cf740dfb01f..7e606b2348ea82486c2a57ee1062ef34150e46a0 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} ${vkcv_camera_include})
+target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera)
+target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera vkcv_shader_compiler)
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index ca10434bb9e486649411bcaac54c432744a914bb..1c6f96041874a262b481727caf41e0b1142f5570 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -4,6 +4,8 @@
 #include <vkcv/camera/CameraManager.hpp>
 #include <chrono>
 
+#include <vkcv/shader/GLSLCompiler.hpp>
+
 int main(int argc, const char** argv) {
 	const char* applicationName = "First Triangle";
 
@@ -16,10 +18,8 @@ int main(int argc, const char** argv) {
 		false
 	);
 
-	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
-
 	window.initEvents();
-
+	
 	vkcv::Core core = vkcv::Core::create(
 		window,
 		applicationName,
@@ -92,21 +92,28 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 
-	// Graphics Pipeline
 	vkcv::ShaderProgram triangleShaderProgram{};
-	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/vert.spv"));
-	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/frag.spv"));
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
-
-	const vkcv::PipelineConfig trianglePipelineDefinition(
+	vkcv::shader::GLSLCompiler compiler;
+	
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/shader.vert"),
+					 [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		 triangleShaderProgram.addShader(shaderStage, path);
+	});
+	
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/shader.frag"),
+					 [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		triangleShaderProgram.addShader(shaderStage, path);
+	});
+
+	const vkcv::PipelineConfig trianglePipelineDefinition {
 		triangleShaderProgram,
 		(uint32_t)windowWidth,
 		(uint32_t)windowHeight,
 		trianglePass,
 		{},
 		{},
-		false);
+		false
+	};
 
 	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
 
@@ -119,7 +126,6 @@ int main(int argc, const char** argv) {
 	// Compute Pipeline
 	vkcv::ShaderProgram computeShaderProgram{};
 	computeShaderProgram.addShader(vkcv::ShaderStage::COMPUTE, std::filesystem::path("shaders/comp.spv"));
-	computeShaderProgram.reflectShader(vkcv::ShaderStage::COMPUTE);
 
 	// take care, assuming shader has exactly one descriptor set
 	vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeShaderProgram.getReflectedDescriptors()[0]);
@@ -164,6 +170,12 @@ int main(int argc, const char** argv) {
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camIndex).setPosition(glm::vec3(0, 0, -2));
+
 	while (window.isWindowOpen())
 	{
         window.pollEvents();
@@ -174,10 +186,11 @@ int main(int argc, const char** argv) {
 		}
 		
         auto end = std::chrono::system_clock::now();
-        auto deltatime = end - start;
+        auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
         start = end;
-        cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
-		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+		
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+        glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
 		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp
deleted file mode 100644
index ad8437ca2a6c07862f66485c74c89ccba0d69ebe..0000000000000000000000000000000000000000
--- a/src/vkcv/PipelineConfig.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @authors Mara Vogt, Mark Mints
- * @file src/vkcv/Pipeline.cpp
- * @brief Pipeline class to handle shader stages
- */
-
-#include "vkcv/PipelineConfig.hpp"
-
-namespace vkcv {
-
-    PipelineConfig::PipelineConfig(
-		const ShaderProgram&                        shaderProgram,
-		uint32_t                                    width,
-		uint32_t                                    height,
-		const PassHandle                            &passHandle,
-		const std::vector<VertexAttribute>          &vertexAttributes,
-		const std::vector<vk::DescriptorSetLayout>  &descriptorLayouts,
-		bool                                        useDynamicViewport)
-		:
-		m_ShaderProgram(shaderProgram),
-		m_Height(height),
-		m_Width(width),
-		m_PassHandle(passHandle),
-		m_VertexAttributes(vertexAttributes),
-		m_DescriptorLayouts(descriptorLayouts),
-		m_UseDynamicViewport(useDynamicViewport)
-		{}
-}
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 24ac7970d739d46ab7cecca7bb783bb0037c43cb..949e5b9f713f3cac72d67b8a22cae46fc12aef0d 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -7,8 +7,7 @@ namespace vkcv
 
     PipelineManager::PipelineManager(vk::Device device) noexcept :
     m_Device{device},
-    m_Pipelines{},
-    m_Configs{}
+    m_Pipelines{}
     {}
 
     PipelineManager::~PipelineManager() noexcept
@@ -19,23 +18,23 @@ namespace vkcv
     }
 
 	// currently assuming default 32 bit formats, no lower precision or normalized variants supported
-	vk::Format vertexFormatToVulkanFormat(const VertexFormat format) {
+	vk::Format vertexFormatToVulkanFormat(const VertexAttachmentFormat format) {
 		switch (format) {
-		case VertexFormat::FLOAT:
+		case VertexAttachmentFormat::FLOAT:
 			return vk::Format::eR32Sfloat;
-		case VertexFormat::FLOAT2:
+		case VertexAttachmentFormat::FLOAT2:
 			return vk::Format::eR32G32Sfloat;
-		case VertexFormat::FLOAT3:
+		case VertexAttachmentFormat::FLOAT3:
 			return vk::Format::eR32G32B32Sfloat;
-		case VertexFormat::FLOAT4:
+		case VertexAttachmentFormat::FLOAT4:
 			return vk::Format::eR32G32B32A32Sfloat;
-		case VertexFormat::INT:
+		case VertexAttachmentFormat::INT:
 			return vk::Format::eR32Sint;
-		case VertexFormat::INT2:
+		case VertexAttachmentFormat::INT2:
 			return vk::Format::eR32G32Sint;
-		case VertexFormat::INT3:
+		case VertexAttachmentFormat::INT3:
 			return vk::Format::eR32G32B32Sint;
-		case VertexFormat::INT4:
+		case VertexAttachmentFormat::INT4:
 			return vk::Format::eR32G32B32A32Sint;
 		default:
 			vkcv_log(LogLevel::WARNING, "Unknown vertex format");
@@ -94,24 +93,24 @@ namespace vkcv
         std::vector<vk::VertexInputAttributeDescription>	vertexAttributeDescriptions;
 		std::vector<vk::VertexInputBindingDescription>		vertexBindingDescriptions;
 
-        VertexLayout layout = config.m_ShaderProgram.getVertexLayout();
-        std::unordered_map<uint32_t, VertexInputAttachment> attachments = layout.attachmentMap;
+        const VertexLayout &layout = config.m_VertexLayout;
 
-		for (int i = 0; i < attachments.size(); i++) {
-			VertexInputAttachment &attachment = attachments.at(i);
-
-            uint32_t	location		= attachment.location;
-            uint32_t	binding			= i;
-            vk::Format	vertexFormat	= vertexFormatToVulkanFormat(attachment.format);
-
-			//FIXME: hoping that order is the same and compatible: add explicit mapping and validation
-			const VertexAttribute attribute = config.m_VertexAttributes[i];
-
-            vertexAttributeDescriptions.emplace_back(location, binding, vertexFormatToVulkanFormat(attachment.format), 0);
-			vertexBindingDescriptions.emplace_back(vk::VertexInputBindingDescription(
-				binding,
-				attribute.stride + getFormatSize(attachment.format),
-				vk::VertexInputRate::eVertex));
+        // iterate over the layout's specified, mutually exclusive buffer bindings that make up a vertex buffer
+        for (const auto &vertexBinding : layout.vertexBindings)
+        {
+            vertexBindingDescriptions.emplace_back(vertexBinding.bindingLocation,
+                                                   vertexBinding.stride,
+                                                   vk::VertexInputRate::eVertex);
+
+            // iterate over the bindings' specified, mutually exclusive vertex input attachments that make up a vertex
+            for(const auto &vertexAttachment: vertexBinding.vertexAttachments)
+            {
+                vertexAttributeDescriptions.emplace_back(vertexAttachment.inputLocation,
+                                                         vertexBinding.bindingLocation,
+                                                         vertexFormatToVulkanFormat(vertexAttachment.format),
+                                                         vertexAttachment.offset % vertexBinding.stride);
+
+            }
         }
 
         // Handover Containers to PipelineVertexInputStateCreateIngo Struct
@@ -271,8 +270,7 @@ namespace vkcv
         m_Device.destroy(fragmentModule);
         
         const uint64_t id = m_Pipelines.size();
-        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout });
-        m_Configs.push_back(config);
+        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, config });
         return PipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
     }
 
@@ -320,10 +318,17 @@ namespace vkcv
 		}
     }
 
-    const PipelineConfig &PipelineManager::getPipelineConfig(const PipelineHandle &handle) const
+    const PipelineConfig& PipelineManager::getPipelineConfig(const PipelineHandle &handle) const
     {
         const uint64_t id = handle.getId();
-        return m_Configs.at(id);
+        
+        if (id >= m_Pipelines.size()) {
+        	static PipelineConfig dummyConfig;
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return dummyConfig;
+        }
+        
+        return m_Pipelines[id].m_config;
     }
 
     PipelineHandle PipelineManager::createComputePipeline(
@@ -373,7 +378,7 @@ namespace vkcv
         m_Device.destroy(computeModule);
 
         const uint64_t id = m_Pipelines.size();
-        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout });
+        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, PipelineConfig() });
 
         return PipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
     }
diff --git a/src/vkcv/PipelineManager.hpp b/src/vkcv/PipelineManager.hpp
index 634f5f4e6464532306e35fd10d9a1623df6ace16..b153eb4632b844e84b92953fe8abf6666a13e0c9 100644
--- a/src/vkcv/PipelineManager.hpp
+++ b/src/vkcv/PipelineManager.hpp
@@ -14,11 +14,11 @@ namespace vkcv
     	struct Pipeline {
 			vk::Pipeline m_handle;
 			vk::PipelineLayout m_layout;
+			PipelineConfig m_config;
     	};
     	
         vk::Device m_Device;
         std::vector<Pipeline> m_Pipelines;
-        std::vector<PipelineConfig> m_Configs;
         
         void destroyPipelineById(uint64_t id);
 
diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp
index 7c54c301d1301127273303128b0c10a9c2c53942..971797d9a42d071a1730ebf31a0b554f92fa361f 100644
--- a/src/vkcv/ShaderProgram.cpp
+++ b/src/vkcv/ShaderProgram.cpp
@@ -14,32 +14,36 @@ namespace vkcv {
      * @param[in] relative path to the shader code
      * @return vector of chars as a buffer for the code
      */
-	std::vector<char> readShaderCode(const std::filesystem::path &shaderPath)
-	{
-		std::ifstream file(shaderPath.string(), std::ios::ate | std::ios::binary);
+	std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) {
+		std::ifstream file (shaderPath.string(), std::ios::ate | std::ios::binary);
+		
 		if (!file.is_open()) {
 			vkcv_log(LogLevel::ERROR, "The file could not be opened");
 			return std::vector<char>{};
 		}
+		
 		size_t fileSize = (size_t)file.tellg();
 		std::vector<char> buffer(fileSize);
+		
 		file.seekg(0);
 		file.read(buffer.data(), fileSize);
+		file.close();
+		
         return buffer;
 	}
 
-	VertexFormat convertFormat(spirv_cross::SPIRType::BaseType basetype, uint32_t vecsize){
+	VertexAttachmentFormat convertFormat(spirv_cross::SPIRType::BaseType basetype, uint32_t vecsize){
         switch (basetype) {
             case spirv_cross::SPIRType::Int:
                 switch (vecsize) {
                     case 1:
-                        return VertexFormat::INT;
+                        return VertexAttachmentFormat::INT;
                     case 2:
-                        return VertexFormat::INT2;
+                        return VertexAttachmentFormat::INT2;
                     case 3:
-                        return VertexFormat::INT3;
+                        return VertexAttachmentFormat::INT3;
                     case 4:
-                        return VertexFormat::INT4;
+                        return VertexAttachmentFormat::INT4;
                     default:
                         break;
                 }
@@ -47,13 +51,13 @@ namespace vkcv {
             case spirv_cross::SPIRType::Float:
                 switch (vecsize) {
                     case 1:
-                        return VertexFormat::FLOAT;
+                        return VertexAttachmentFormat::FLOAT;
                     case 2:
-                        return VertexFormat::FLOAT2;
+                        return VertexAttachmentFormat::FLOAT2;
                     case 3:
-                        return VertexFormat::FLOAT3;
+                        return VertexAttachmentFormat::FLOAT3;
                     case 4:
-                        return VertexFormat::FLOAT4;
+                        return VertexAttachmentFormat::FLOAT4;
                     default:
                         break;
                 }
@@ -63,12 +67,12 @@ namespace vkcv {
         }
 		
 		vkcv_log(LogLevel::WARNING, "Unknown vertex format");
-        return VertexFormat::FLOAT;
+        return VertexAttachmentFormat::FLOAT;
 	}
 
 	ShaderProgram::ShaderProgram() noexcept :
 	m_Shaders{},
-    m_VertexLayout{},
+    m_VertexAttachments{},
     m_DescriptorSets{}
 	{}
 
@@ -85,6 +89,7 @@ namespace vkcv {
 		} else {
             Shader shader{shaderCode, shaderStage};
             m_Shaders.insert(std::make_pair(shaderStage, shader));
+            reflectShader(shaderStage);
             return true;
         }
 	}
@@ -107,30 +112,31 @@ namespace vkcv {
         auto shaderCodeChar = m_Shaders.at(shaderStage).shaderCode;
         std::vector<uint32_t> shaderCode;
 
-        for (uint32_t i = 0; i < shaderCodeChar.size()/4; i++) {
+        for (uint32_t i = 0; i < shaderCodeChar.size()/4; i++)
             shaderCode.push_back(((uint32_t*) shaderCodeChar.data())[i]);
-        }
 
         spirv_cross::Compiler comp(move(shaderCode));
         spirv_cross::ShaderResources resources = comp.get_shader_resources();
 
         //reflect vertex input
-		if (shaderStage == ShaderStage::VERTEX) {
-			std::vector<VertexInputAttachment> inputVec;
-			uint32_t offset = 0;
-
-			for (uint32_t i = 0; i < resources.stage_inputs.size(); i++) {
-				auto& u = resources.stage_inputs[i];
-				const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
-				VertexInputAttachment input = VertexInputAttachment(comp.get_decoration(u.id, spv::DecorationLocation),
-					0,
-                    u.name,
-					convertFormat(base_type.basetype, base_type.vecsize),
-					offset);
-				inputVec.push_back(input);
-				offset += base_type.vecsize * base_type.width / 8;
-			}
-			m_VertexLayout = VertexLayout(inputVec);
+		if (shaderStage == ShaderStage::VERTEX)
+		{
+			// spirv-cross API (hopefully) returns the stage_inputs in order
+			for (uint32_t i = 0; i < resources.stage_inputs.size(); i++)
+			{
+                // spirv-cross specific objects
+				auto& stage_input = resources.stage_inputs[i];
+				const spirv_cross::SPIRType& base_type = comp.get_type(stage_input.base_type_id);
+
+				// vertex input location
+				const uint32_t attachment_loc = comp.get_decoration(stage_input.id, spv::DecorationLocation);
+                // vertex input name
+                const std::string attachment_name = stage_input.name;
+				// vertex input format (implies its size)
+				const VertexAttachmentFormat attachment_format = convertFormat(base_type.basetype, base_type.vecsize);
+
+                m_VertexAttachments.emplace_back(attachment_loc, attachment_name, attachment_format);
+            }
 		}
 
 		//reflect descriptor sets (uniform buffer, storage buffer, sampler, sampled image, storage image)
@@ -142,7 +148,8 @@ namespace vkcv {
             std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
                 DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::UNIFORM_BUFFER, base_type.vecsize, shaderStage));
             bindings.push_back(descriptor);
-            if (comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) 
+                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
         }
 
         for (uint32_t i = 0; i < resources.storage_buffers.size(); i++) {
@@ -201,14 +208,17 @@ namespace vkcv {
 		}
     }
 
-    const VertexLayout& ShaderProgram::getVertexLayout() const{
-        return m_VertexLayout;
+    const std::vector<VertexAttachment> &ShaderProgram::getVertexAttachments() const
+    {
+        return m_VertexAttachments;
 	}
 
-    const std::vector<std::vector<DescriptorBinding>> ShaderProgram::getReflectedDescriptors() const {
+    const std::vector<std::vector<DescriptorBinding>>& ShaderProgram::getReflectedDescriptors() const {
         return m_DescriptorSets;
     }
-	size_t ShaderProgram::getPushConstantSize() const {
+
+	size_t ShaderProgram::getPushConstantSize() const
+	{
 		return m_pushConstantSize;
 	}
 }
diff --git a/src/vkcv/VertexLayout.cpp b/src/vkcv/VertexLayout.cpp
index 3c39ad0d39c4b458526ceed54422d320ee6d0e0d..fa079a3264ae47b32461bda26485adb97b0be280 100644
--- a/src/vkcv/VertexLayout.cpp
+++ b/src/vkcv/VertexLayout.cpp
@@ -6,23 +6,23 @@
 #include "vkcv/Logger.hpp"
 
 namespace vkcv {
-    uint32_t getFormatSize(VertexFormat format) {
+    uint32_t getFormatSize(VertexAttachmentFormat format) {
         switch (format) {
-            case VertexFormat::FLOAT:
+            case VertexAttachmentFormat::FLOAT:
                 return 4;
-            case VertexFormat::FLOAT2:
+            case VertexAttachmentFormat::FLOAT2:
                 return 8;
-            case VertexFormat::FLOAT3:
+            case VertexAttachmentFormat::FLOAT3:
                 return 12;
-            case VertexFormat::FLOAT4:
+            case VertexAttachmentFormat::FLOAT4:
                 return 16;
-            case VertexFormat::INT:
+            case VertexAttachmentFormat::INT:
                 return 4;
-            case VertexFormat::INT2:
+            case VertexAttachmentFormat::INT2:
                 return 8;
-            case VertexFormat::INT3:
+            case VertexAttachmentFormat::INT3:
                 return 12;
-            case VertexFormat::INT4:
+            case VertexAttachmentFormat::INT4:
                 return 16;
             default:
 				vkcv_log(LogLevel::WARNING, "No format given");
@@ -30,25 +30,33 @@ namespace vkcv {
         }
     }
 
-    VertexInputAttachment::VertexInputAttachment(uint32_t location, uint32_t binding, std::string name, VertexFormat format, uint32_t offset) noexcept:
-            location{location},
-            binding{binding},
+    VertexAttachment::VertexAttachment(uint32_t inputLocation, const std::string &name, VertexAttachmentFormat format) noexcept:
+            inputLocation{inputLocation},
             name{name},
             format{format},
-            offset{offset}
-            {}
-
-    VertexLayout::VertexLayout() noexcept :
-    stride{0},
-    attachmentMap()
+            offset{0}
     {}
 
-    VertexLayout::VertexLayout(const std::vector<VertexInputAttachment> &inputs) noexcept {
-        stride = 0;
-        for (const auto &input : inputs) {
-            attachmentMap.insert(std::make_pair(input.location, input));
-            stride += getFormatSize(input.format);
+
+    VertexBinding::VertexBinding(uint32_t bindingLocation, const std::vector<VertexAttachment> &attachments) noexcept :
+    bindingLocation{bindingLocation},
+    stride{0},
+    vertexAttachments{attachments}
+    {
+        uint32_t offset = 0;
+        for (auto &attachment : vertexAttachments)
+        {
+            offset += getFormatSize(attachment.format);
+            attachment.offset = offset;
         }
+        stride = offset;
     }
 
+    VertexLayout::VertexLayout() noexcept :
+    vertexBindings{}
+    {}
+
+    VertexLayout::VertexLayout(const std::vector<VertexBinding> &bindings) noexcept :
+    vertexBindings{bindings}
+    {}
 }
\ No newline at end of file