diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index ab0d2aca0eccbd3c6adfea191cefa2c63623b0b9..f3b1eb7c6f81139985c7d7d304836a9b3924a94b 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -21,6 +21,11 @@
 
 namespace vkcv
 {
+	struct VertexBufferBinding {
+		vk::DeviceSize	offset;
+		BufferHandle	buffer;
+	};
+
     // forward declarations
     class PassManager;
     class PipelineManager;
@@ -188,7 +193,7 @@ namespace vkcv
 		*/
 		void renderMesh(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle,
 			const int width, const int height, const size_t pushConstantSize, const void* pushConstantData, 
-			const BufferHandle vertexBuffer, const BufferHandle indexBuffer, const size_t indexCount);
+			const std::vector<VertexBufferBinding> & vertexBufferBindings, const BufferHandle indexBuffer, const size_t indexCount);
 
 		/**
 		 * @brief end recording and present image
diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp
index 26ad7053d5a732968093b0f8ffb2c23a0d70255d..fdf5df7403ed91fd48eef1e593c567c602957937 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -9,17 +9,11 @@
 #include <cstdint>
 #include "vkcv/Handles.hpp"
 #include "ShaderProgram.hpp"
+#include <vkcv/VertexLayout.hpp>
 
 namespace vkcv {
 
-    class PipelineConfig {
-
-    public:
-        /**
-         *  Default constructer is deleted!
-         */
-        PipelineConfig() = delete;
-
+    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.
@@ -29,12 +23,18 @@ namespace vkcv {
          * @param width width of the application window
          * @param passHandle handle for Render Pass
          */
-        PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle);
+        PipelineConfig(
+			const ShaderProgram&				shaderProgram, 
+			uint32_t							width, 
+			uint32_t							height, 
+			PassHandle							&passHandle,
+			const std::vector<VertexAttribute>	&vertexAttributes);
 
-		ShaderProgram m_ShaderProgram;
-        uint32_t m_Height;
-        uint32_t m_Width;
-        PassHandle m_PassHandle;
+		ShaderProgram					m_ShaderProgram;
+        uint32_t						m_Height;
+        uint32_t						m_Width;
+        PassHandle						m_PassHandle;
+		std::vector<VertexAttribute>	m_vertexAttributes;
     };
 
 }
\ No newline at end of file
diff --git a/include/vkcv/VertexLayout.hpp b/include/vkcv/VertexLayout.hpp
index fceaa9cf5498f068b5c767534be0957fed96a033..5db87574c6285d3c7d8f3310041e0b23fa7b0bb1 100644
--- a/include/vkcv/VertexLayout.hpp
+++ b/include/vkcv/VertexLayout.hpp
@@ -3,9 +3,21 @@
 #include <unordered_map>
 #include <vector>
 #include <iostream>
-#include <vulkan/vulkan.hpp>
 
 namespace vkcv{
+
+	/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
+	enum class PrimitiveType { UNDEFINED, POSITION, NORMAL, TEXCOORD_0 };
+	/* 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;
+
     enum class VertexFormat{
         FLOAT,
         FLOAT2,
@@ -17,6 +29,8 @@ namespace vkcv{
         INT4
     };
 
+	uint32_t getFormatSize(VertexFormat format);
+
     struct VertexInputAttachment{
         VertexInputAttachment() = delete;
         VertexInputAttachment(uint32_t location, uint32_t binding, VertexFormat format, uint32_t offset) noexcept;
@@ -33,7 +47,4 @@ namespace vkcv{
         std::unordered_map<uint32_t, VertexInputAttachment> attachmentMap;
         uint32_t stride;
     };
-
-	// currently assuming default 32 bit formats, no lower precision or normalized variants supported
-	vk::Format vertexFormatToVulkanFormat(const VertexFormat format);
 }
\ No newline at end of file
diff --git a/modules/asset_loader/CMakeLists.txt b/modules/asset_loader/CMakeLists.txt
index 8d4c0d6c104187de2d807cceceff529d83d236d6..d2a9b817ea68c7851fd2123f76b378d8a4d85ac0 100644
--- a/modules/asset_loader/CMakeLists.txt
+++ b/modules/asset_loader/CMakeLists.txt
@@ -31,7 +31,7 @@ include(config/FX_GLTF.cmake)
 include(config/STB.cmake)
 
 # link the required libraries to the module
-target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries})
+target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv)
 
 # including headers of dependencies and the VkCV framework
 target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes})
diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index 6df014563a8991e464e02eb10f210c079913d3cb..24687e846ff9eae3275de357331a825f0b4ed2c3 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 #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
@@ -49,8 +50,6 @@ enum PrimitiveMode {
 	POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
 	TRIANGLEFAN
 };
-/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
-enum PrimitiveType { POSITION=1, NORMAL, TEXCOORD_0 };
 /* The indices in the index buffer can be of different bit width. */
 enum IndexType { UINT32=0, UINT16=1, UINT8=2 };
 
@@ -58,16 +57,6 @@ typedef struct {
 	// TODO not yet needed for the first (unlit) triangle
 } Material;
 
-/* This struct describes one vertex attribute of a vertex buffer. */
-typedef struct {
-	PrimitiveType type;		// POSITION, NORMAL, ...
-	uint32_t offset;		// offset in bytes
-	uint32_t length;		// length of ... in bytes
-	uint32_t stride;		// stride in bytes
-	uint16_t componentType;		// eg. 5126 for float
-	uint8_t  componentCount;	// eg. 3 for vec3
-} VertexAttribute;
-
 /* This struct represents one (possibly the only) part of a mesh. There is
  * always one vertexBuffer and zero or one indexBuffer (indexed rendering is
  * common but not always used). If there is no index buffer, this is indicated
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index d0949cbc3454283b0084c57abf95a06a036c4e9b..e660b442d0b9a0208f95c9d753ef19e926bcac44 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -85,12 +85,12 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		VertexAttribute attribute;
 
 		if (attrib.first == "POSITION") {
-			attribute.type = POSITION;
+			attribute.type = PrimitiveType::POSITION;
 			posAccessor = accessor;
 		} else if (attrib.first == "NORMAL") {
-			attribute.type = NORMAL;
+			attribute.type = PrimitiveType::NORMAL;
 		} else if (attrib.first == "TEXCOORD_0") {
-			attribute.type = TEXCOORD_0;
+			attribute.type = PrimitiveType::TEXCOORD_0;
 		} else {
 			return 0;
 		}
@@ -126,15 +126,16 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		}
 	}
 
-	const fx::gltf::BufferView& vertexBufferView = object.bufferViews[posAccessor.bufferView];
-	const fx::gltf::Buffer& vertexBuffer = object.buffers[vertexBufferView.buffer];
+	const fx::gltf::BufferView&	vertexBufferView	= object.bufferViews[posAccessor.bufferView];
+	const fx::gltf::Buffer&		vertexBuffer		= object.buffers[vertexBufferView.buffer];
 	
+	// FIXME: This only works when all vertex attributes are in one buffer
 	std::vector<uint8_t> vertexBufferData;
-	vertexBufferData.resize(vertexBufferView.byteLength);
+	vertexBufferData.resize(vertexBuffer.byteLength);
 	{
-		const size_t off = vertexBufferView.byteOffset;
+		const size_t off = 0;
 		const void *const ptr = ((char*)vertexBuffer.data.data()) + off;
-		if (!memcpy(vertexBufferData.data(), ptr, vertexBufferView.byteLength)) {
+		if (!memcpy(vertexBufferData.data(), ptr, vertexBuffer.byteLength)) {
 			std::cerr << "ERROR copying vertex buffer data.\n";
 			return 0;
 		}
diff --git a/projects/first_mesh/resources/shaders/frag.spv b/projects/first_mesh/resources/shaders/frag.spv
index e552acc2cc06e3adb7bcdbdea4256618a453fdca..50030afba4d1c527a1f9bc59e16cf50a50f72f94 100644
Binary files a/projects/first_mesh/resources/shaders/frag.spv and b/projects/first_mesh/resources/shaders/frag.spv differ
diff --git a/projects/first_mesh/resources/shaders/shader.frag b/projects/first_mesh/resources/shaders/shader.frag
index 5757b58893f96abfe4212dde1936a1f03bcd2c3d..ce1efbde47b86321fec089f1b2a7b8380b390a2f 100644
--- a/projects/first_mesh/resources/shaders/shader.frag
+++ b/projects/first_mesh/resources/shaders/shader.frag
@@ -7,5 +7,6 @@ layout(location = 1) in vec2 passUV;
 layout(location = 0) out vec3 outColor;
 
 void main()	{
-	outColor = passNormal;
+	outColor = 0.5 * passNormal + 0.5;
+    //outColor = vec3(abs(passUV), 0);
 }
\ No newline at end of file
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index e1cbc40e747f9f68e44db6ea9ca3dcd556148a4b..d0500a387f0941094c55605167b2778c5b728aaf 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -76,7 +76,7 @@ int main(int argc, const char** argv) {
 	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
 	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
 
-	const vkcv::PipelineConfig trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass);
+	const vkcv::PipelineConfig trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass, mesh.vertexGroups[0].vertexBuffer.attributes);
 	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
 	
 	if (!trianglePipeline) {
@@ -84,6 +84,12 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 
+	std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+		{ mesh.vertexGroups[0].vertexBuffer.attributes[0].offset, vertexBuffer.getHandle() },
+		{ mesh.vertexGroups[0].vertexBuffer.attributes[1].offset, vertexBuffer.getHandle() },
+		{ mesh.vertexGroups[0].vertexBuffer.attributes[2].offset, vertexBuffer.getHandle() }
+	};
+
 	auto start = std::chrono::system_clock::now();
 	while (window.isWindowOpen()) {
 		core.beginFrame();
@@ -94,7 +100,7 @@ int main(int argc, const char** argv) {
 		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
 		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
 
-		core.renderMesh(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, vertexBuffer.getHandle(), indexBuffer.getHandle(), mesh.vertexGroups[0].numIndices);
+		core.renderMesh(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, vertexBufferBindings, indexBuffer.getHandle(), mesh.vertexGroups[0].numIndices);
 		core.endFrame();
 	}
 	return 0;
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 13800254d78cb679c81d58a003089aa961986937..dc470d828ac108344ce8501020482e131d0045c5 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -164,7 +164,7 @@ namespace vkcv
 
 	void Core::renderMesh(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, 
 		const int width, const int height, const size_t pushConstantSize, const void *pushConstantData,
-		const BufferHandle vertexBuffer, const BufferHandle indexBuffer, const size_t indexCount) {
+		const std::vector<VertexBufferBinding>& vertexBufferBindings, const BufferHandle indexBuffer, const size_t indexCount) {
 
 		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
 			return;
@@ -175,17 +175,18 @@ namespace vkcv
 		const vk::Pipeline pipeline		= m_PipelineManager->getVkPipeline(pipelineHandle);
         const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
-		const vk::Buffer vulkanVertexBuffer	= m_BufferManager->getBuffer(vertexBuffer);
 		const vk::Buffer vulkanIndexBuffer	= m_BufferManager->getBuffer(indexBuffer);
 
 		const vk::Framebuffer framebuffer = createFramebuffer(m_Context.getDevice(), renderpass, width, height, imageView);
 		m_TemporaryFramebuffers.push_back(framebuffer);
 
+		auto &bufferManager = m_BufferManager;
+
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
 		submitCommands(submitInfo, [renderpass, renderArea, imageView, framebuffer, pipeline, pipelineLayout, 
-			pushConstantSize, pushConstantData, vulkanVertexBuffer, indexCount, vulkanIndexBuffer](const vk::CommandBuffer& cmdBuffer) {
+			pushConstantSize, pushConstantData, &vertexBufferBindings, indexCount, vulkanIndexBuffer, &bufferManager](const vk::CommandBuffer& cmdBuffer) {
 
 			const std::array<float, 4> clearColor = { 0.f, 0.f, 0.f, 1.f };
 			const vk::ClearValue clearValues(clearColor);
@@ -195,10 +196,15 @@ namespace vkcv
 			cmdBuffer.beginRenderPass(beginInfo, subpassContents, {});
 
 			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
+
+			for (uint32_t i = 0; i < vertexBufferBindings.size(); i++) {
+				const auto &vertexBinding = vertexBufferBindings[i];
+				const auto vertexBuffer = bufferManager->getBuffer(vertexBinding.buffer);
+				cmdBuffer.bindVertexBuffers(i, (vertexBuffer), (vertexBinding.offset));
+			}
 			
-			cmdBuffer.bindVertexBuffers(0, (vulkanVertexBuffer), { 0 });
 			cmdBuffer.bindIndexBuffer(vulkanIndexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
-            cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
+			cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
 			cmdBuffer.drawIndexed(indexCount, 1, 0, 0, {});
 			cmdBuffer.endRenderPass();
 		}, nullptr);
diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp
index c2b1415f10188f082d8eca576c2143e82f99e7fa..b5a8e98cea635005c2cb82c22921e6c6b6b3b8ed 100644
--- a/src/vkcv/PipelineConfig.cpp
+++ b/src/vkcv/PipelineConfig.cpp
@@ -8,10 +8,16 @@
 
 namespace vkcv {
 
-    PipelineConfig::PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle):
+    PipelineConfig::PipelineConfig(
+		const ShaderProgram& shaderProgram, 
+		uint32_t width, 
+		uint32_t height, 
+		PassHandle &passHandle, 
+		const std::vector<VertexAttribute> &vertexAttributes):
 		m_ShaderProgram(shaderProgram),
 		m_Height(height),
 		m_Width(width),
-		m_PassHandle(passHandle)
+		m_PassHandle(passHandle),
+		m_vertexAttributes(vertexAttributes)
 		{}
 }
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 8b6202eb901f26c84585597e327c297902e54b03..4ec492c238af1c5aa8dbade28496e0610f715174 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -23,6 +23,21 @@ namespace vkcv
         m_NextPipelineId = 0;
     }
 
+	// currently assuming default 32 bit formats, no lower precision or normalized variants supported
+	vk::Format vertexFormatToVulkanFormat(const VertexFormat format) {
+		switch (format) {
+		case VertexFormat::FLOAT: return vk::Format::eR32Sfloat;
+		case VertexFormat::FLOAT2: return vk::Format::eR32G32Sfloat;
+		case VertexFormat::FLOAT3: return vk::Format::eR32G32B32Sfloat;
+		case VertexFormat::FLOAT4: return vk::Format::eR32G32B32A32Sfloat;
+		case VertexFormat::INT: return vk::Format::eR32Sint;
+		case VertexFormat::INT2: return vk::Format::eR32G32Sint;
+		case VertexFormat::INT3: return vk::Format::eR32G32B32Sint;
+		case VertexFormat::INT4: return vk::Format::eR32G32B32A32Sint;
+		default: std::cerr << "Warning: Unknown vertex format" << std::endl; return vk::Format::eUndefined;
+		}
+	}
+
     PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, const vk::RenderPass &pass)
     {
         const bool existsVertexShader = config.m_ShaderProgram.existsShader(ShaderStage::VERTEX);
@@ -69,19 +84,27 @@ namespace vkcv
         // vertex input state
 
         // Fill up VertexInputBindingDescription and VertexInputAttributeDescription Containers
-        std::vector<vk::VertexInputBindingDescription> vertexBindingDescriptions;
-        std::vector<vk::VertexInputAttributeDescription> vertexAttributeDescriptions;
+        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;
 
-        for (auto& attachment: attachments) {
-            uint32_t location = attachment.second.location;
-            uint32_t binding = attachment.second.binding;
-            uint32_t offset = attachment.second.offset;
-            vk::Format vertexFormat = vertexFormatToVulkanFormat(attachment.second.format);
-            vertexBindingDescriptions.push_back({binding, layout.stride, vk::VertexInputRate::eVertex}); // TODO: What's about the input rate?
-            vertexAttributeDescriptions.push_back({location, binding, vk::Format::eR32G32B32Sfloat, offset});
+		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.push_back({location, binding, vertexFormatToVulkanFormat(attachment.format), 0});
+			vertexBindingDescriptions.push_back(vk::VertexInputBindingDescription(
+				binding,
+				attribute.stride + getFormatSize(attachment.format),
+				vk::VertexInputRate::eVertex));
         }
 
         // Handover Containers to PipelineVertexInputStateCreateIngo Struct
diff --git a/src/vkcv/VertexLayout.cpp b/src/vkcv/VertexLayout.cpp
index ef2fedf6fe85a35df40a284554df40659b23470c..b06c6743e1e19a5e282af248ab6b590eb97529fd 100644
--- a/src/vkcv/VertexLayout.cpp
+++ b/src/vkcv/VertexLayout.cpp
@@ -5,7 +5,7 @@
 #include "vkcv/VertexLayout.hpp"
 
 namespace vkcv {
-    uint32_t static getFormatSize(VertexFormat format) {
+    uint32_t getFormatSize(VertexFormat format) {
         switch (format) {
             case VertexFormat::FLOAT:
                 return 4;
@@ -50,18 +50,4 @@ namespace vkcv {
         }
     }
 
-	vk::Format vertexFormatToVulkanFormat(const VertexFormat format) {
-		switch (format) {
-			case VertexFormat::FLOAT	: return vk::Format::eR32Sfloat;
-			case VertexFormat::FLOAT2	: return vk::Format::eR32G32Sfloat;
-			case VertexFormat::FLOAT3	: return vk::Format::eR32G32B32Sfloat;
-			case VertexFormat::FLOAT4	: return vk::Format::eR32G32B32A32Sfloat;
-			case VertexFormat::INT		: return vk::Format::eR32Sint;
-			case VertexFormat::INT2		: return vk::Format::eR32G32Sint;
-			case VertexFormat::INT3		: return vk::Format::eR32G32B32Sint;
-			case VertexFormat::INT4		: return vk::Format::eR32G32B32A32Sint;
-			default: std::cerr << "Warning: Unknown vertex format" << std::endl; return vk::Format::eUndefined;
-		}
-	}
-
 }
\ No newline at end of file