diff --git a/include/vkcv/VertexLayout.hpp b/include/vkcv/VertexLayout.hpp
index 9f609b48472386cd7628ff40b5fa4b90bc91649a..0600b99a24a327605e89b2e8ec304c20dbf7ad2e 100644
--- a/include/vkcv/VertexLayout.hpp
+++ b/include/vkcv/VertexLayout.hpp
@@ -63,4 +63,4 @@ namespace vkcv{
 
         std::vector<VertexBinding> vertexBindings;
     };
-}
\ No newline at end of file
+}
diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index d687bbf60a03a59eee8c29f9b676f10cc7f2f2b3..4107d57ee97a6efe0475c6d9dbd80d2603e0afe8 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -1,15 +1,16 @@
 #pragma once
 /**
- * @authors Trevor Hollmann
+ * @authors Trevor Hollmann, Mara Vogt, Susanne DÃķtsch
  * @file include/vkcv/asset/asset_loader.h
  * @brief Interface of the asset loader module for the vkcv framework.
  */
 
 #include <string>
 #include <vector>
+#include <array>
 #include <cstdint>
 
-/* These macros define limits of the following structs. Implementations can
+/** These macros define limits of the following structs. Implementations can
  * test against these limits when performing sanity checks. The main constraint
  * expressed is that of the data type: Material indices are identified by a
  * uint8_t in the VertexGroup struct, so there can't be more than UINT8_MAX
@@ -19,17 +20,18 @@
 #define MAX_MATERIALS_PER_MESH UINT8_MAX
 #define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX
 
-/* LOADING MESHES
+/** LOADING MESHES
  * The description of meshes is a hierarchy of structures with the Mesh at the
  * top.
  *
  * Each Mesh has an array of one or more vertex groups (called "primitives" in
- * glTF parlance) and an array of zero or more Materials.
+ * glTF parlance). Specifically, it has an array of indices into an array of
+ * vertex groups defined by the Scene struct.
  *
  * Each vertex group describes a part of the meshes vertices by defining how
  * they should be rendered (as points, lines, triangles), how many indices and
  * vertices there are, how the content of the vertex buffer is to be
- * interpreted and which material from the Meshes materials array should be
+ * interpreted and which material from the Scenes materials array should be
  * used for the surface of the vertices.
  * As a bonus there is also the axis aligned bounding box of the vertices.
  *
@@ -43,37 +45,99 @@
 
 namespace vkcv::asset {
 
-/* This enum matches modes in fx-gltf, the library returns a standard mode
+/** This enum matches modes in fx-gltf, the library returns a standard mode
  * (TRIANGLES) if no mode is given in the file. */
-enum PrimitiveMode {
+enum class PrimitiveMode : uint8_t {
 	POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
 	TRIANGLEFAN
 };
-/* The indices in the index buffer can be of different bit width. */
-enum IndexType { UINT32=0, UINT16=1, UINT8=2 };
 
-/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
+/** The indices in the index buffer can be of different bit width. */
+enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 };
+
+typedef struct {
+	// TODO define struct for samplers (low priority)
+	// NOTE: glTF defines samplers based on OpenGL, which can not be
+	// directly translated to Vulkan. Specifically, OpenGL (and glTF)
+	// define a different set of Min/Mag-filters than Vulkan.
+} Sampler;
+
+/** struct for defining the loaded texture */
+typedef struct {
+	int sampler;		// index into the sampler array of the Scene
+	uint8_t channels;	// number of channels
+	uint16_t w, h;		// width and height of the texture
+	std::vector<uint8_t> data;	// binary data of the decoded texture
+} Texture;
+
+/** The asset loader module only supports the PBR-MetallicRoughness model for
+ * materials.*/
+typedef struct {
+	uint16_t textureMask;	// bit mask with active texture targets
+	// Indices into the Array.textures array
+	int baseColor, metalRough, normal, occlusion, emissive;
+	// Scaling factors for each texture target
+	struct { float r, g, b, a; } baseColorFactor;
+	float metallicFactor, roughnessFactor;
+	float normalScale;
+	float occlusionStrength;
+	struct { float r, g, b; } emissiveFactor;
+} Material;
+
+/** Flags for the bit-mask in the Material struct. To check if a material has a
+ * certain texture target, you can use the hasTexture() function below, passing
+ * the material struct and the enum. */
+enum class PBRTextureTarget {
+	baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16
+};
+
+/** This macro translates the index of an enum in the defined order to an
+ * integer with a single bit set in the corresponding place. It is used for
+ * working with the bitmask of texture targets ("textureMask") in the Material
+ * struct:
+ * 	Material mat = ...;
+ * 	if (mat.textureMask & bitflag(PBRTextureTarget::baseColor)) {...}
+ * However, this logic is also encapsulated in the convenience-function
+ * materialHasTexture() so users of the asset loader module can avoid direct
+ * contact with bit-level operations. */
+#define bitflag(ENUM) (0x1u << ((unsigned)(ENUM)))
+
+/** To signal that a certain texture target is active in a Material struct, its
+ * bit is set in the textureMask. You can use this function to check that:
+ * Material mat = ...;
+ * if (materialHasTexture(&mat, baseColor)) {...} */
+bool materialHasTexture(const Material *const m, const PBRTextureTarget t);
+
+/** 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_0 = 3,
+    TEXCOORD_1 = 4
 };
-/* This struct describes one vertex attribute of a vertex buffer. */
+
+/** 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
+};
+
+/** 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
+    ComponentType componentType;		// eg. 5126 for float
     uint8_t  componentCount;	// eg. 3 for vec3
 } VertexAttribute;
 
-typedef struct {
-	// TODO not yet needed for the first (unlit) triangle
-} Material;
-
-/* This struct represents one (possibly the only) part of a mesh. There is
+/** This struct represents one (possibly the only) part of a mesh. There is
  * always one vertexBuffer and zero or one indexBuffer (indexed rendering is
  * common but not always used). If there is no index buffer, this is indicated
  * by indexBuffer.data being empty. Each vertex buffer can have one or more
@@ -91,34 +155,39 @@ typedef struct {
 	} vertexBuffer;
 	struct { float x, y, z; } min;	// bounding box lower left
 	struct { float x, y, z; } max;	// bounding box upper right
-	uint8_t materialIndex;		// index to one of the meshes materials
+	int materialIndex;		// index to one of the materials
 } VertexGroup;
 
-/* This struct represents a single mesh loaded from a glTF file. It consists of
- * at least one VertexVroup and any number of Materials. */
+/** This struct represents a single mesh as it was loaded from a glTF file. It
+ * consists of at least one VertexGroup, which then references other resources
+ * such as Materials. */
 typedef struct {
 	std::string name;
-	std::vector<VertexGroup> vertexGroups;
-	std::vector<Material> materials;
-	// FIXME Dirty hack to get one(!) texture for our cube demo
-	// hardcoded to always have RGBA channel layout
-	struct {
-		int w, h, ch;	// width, height and channels of image
-		uint8_t *img;	// raw bytes, free after use (deal with it)
-	} texture_hack;
+	std::array<float, 16> modelMatrix;
+	std::vector<int> vertexGroups;
 } Mesh;
 
+/** The scene struct is simply a collection of objects in the scene as well as
+ * the resources used by those objects.
+ * For now the only type of object are the meshes and they are represented in a
+ * flat array.
+ * Note that parent-child relations are not yet possible. */
+typedef struct {
+	std::vector<Mesh> meshes;
+	std::vector<VertexGroup> vertexGroups;
+	std::vector<Material> materials;
+	std::vector<Texture> textures;
+	std::vector<Sampler> samplers;
+} Scene;
 
 /**
- * In its first iteration the asset loader module will only allow loading
- * single meshes, one per glTF file.
- * It will later be extended to allow loading entire scenes from glTF files.
+ * Load every mesh from the glTF file, as well as materials and textures.
  *
- * @param path must be the path to a glTF file containing a single mesh.
- * @param mesh is a reference to a Mesh struct that will be filled with the
+ * @param path must be the path to a glTF or glb file.
+ * @param scene is a reference to a Scene struct that will be filled with the
  * 	content of the glTF file being loaded.
  * */
-int loadMesh(const std::string &path, Mesh &mesh);
+int loadScene(const std::string &path, Scene &scene);
 
 
 }
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index f3823cc8f3fe54b53835f356dd14a086515118dd..97fd39515290ac9235b3936d44d3e40a584ef84f 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -5,9 +5,10 @@
 #include <fx/gltf.h>
 #define STB_IMAGE_IMPLEMENTATION
 #define STBI_ONLY_JPEG
+#define STBI_ONLY_PNG
 #include <stb_image.h>
-
 #include <vkcv/Logger.hpp>
+#include <algorithm>
 
 namespace vkcv::asset {
 
@@ -51,160 +52,318 @@ void print_what (const std::exception& e, const std::string &path) {
 	}
 }
 
-int loadMesh(const std::string &path, Mesh &mesh) {
-	fx::gltf::Document object;
-
-	try {
-		if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
-			object = fx::gltf::LoadFromBinary(path);
-		} else {
-			object = fx::gltf::LoadFromText(path);
-		}
-	} catch (const std::system_error &err) {
-		print_what(err, path);
-		return 0;
-	} catch (const std::exception &e) {
-		print_what(e, path);
-		return 0;
-	}
-
-	// TODO Temporary restriction: Only one mesh per glTF file allowed
-	// currently. Later, we want to support whole scenes with more than
-	// just meshes.
-	if (object.meshes.size() != 1) return 0;
-
-	fx::gltf::Mesh const &objectMesh = object.meshes[0];
-	// TODO We want to support more than one vertex group per mesh
-	// eventually... right now this is hard-coded to use only the first one
-	// because we only care about the example triangle and cube
-	fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[0];
-	fx::gltf::Accessor posAccessor;
-	
-	std::vector<VertexAttribute> vertexAttributes;
-	vertexAttributes.reserve(objectPrimitive.attributes.size());
-	
-	for (auto const & attrib : objectPrimitive.attributes) {
-		fx::gltf::Accessor accessor =  object.accessors[attrib.second];
-		VertexAttribute attribute;
-
-		if (attrib.first == "POSITION") {
-			attribute.type = PrimitiveType::POSITION;
-			posAccessor = accessor;
-		} else if (attrib.first == "NORMAL") {
-			attribute.type = PrimitiveType::NORMAL;
-		} else if (attrib.first == "TEXCOORD_0") {
-			attribute.type = PrimitiveType::TEXCOORD_0;
-		} else {
-			return 0;
-		}
-		
-		attribute.offset = object.bufferViews[accessor.bufferView].byteOffset;
-		attribute.length = object.bufferViews[accessor.bufferView].byteLength;
-		attribute.stride = object.bufferViews[accessor.bufferView].byteStride;
-		attribute.componentType = static_cast<uint16_t>(accessor.componentType);
-		
-		if (convertTypeToInt(accessor.type) != 10) {
-			attribute.componentCount = convertTypeToInt(accessor.type);
-		} else {
-			return 0;
-		}
-		
-		vertexAttributes.push_back(attribute);
-	}
-
-	// TODO consider the case where there is no index buffer (not all
-	// meshes have to use indexed rendering)
-	const fx::gltf::Accessor &indexAccessor = object.accessors[objectPrimitive.indices];
-	const fx::gltf::BufferView &indexBufferView = object.bufferViews[indexAccessor.bufferView];
-	const fx::gltf::Buffer &indexBuffer = object.buffers[indexBufferView.buffer];
-	
-	std::vector<uint8_t> indexBufferData;
-	indexBufferData.resize(indexBufferView.byteLength);
-	{
-		const size_t off = indexBufferView.byteOffset;
-		const void *const ptr = ((char*)indexBuffer.data.data()) + off;
-		if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
-			vkcv_log(LogLevel::ERROR, "Copying index buffer data");
-			return 0;
-		}
-	}
-
-	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(vertexBuffer.byteLength);
-	{
-		const size_t off = 0;
-		const void *const ptr = ((char*)vertexBuffer.data.data()) + off;
-		if (!memcpy(vertexBufferData.data(), ptr, vertexBuffer.byteLength)) {
-			vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
-			return 0;
-		}
-	}
-
-	IndexType indexType;
-	switch(indexAccessor.componentType) {
+/** Translate the component type used in the index accessor of fx-gltf to our
+ * enum for index type. The reason we have defined an incompatible enum that
+ * needs translation is that only a subset of component types is valid for
+ * indices and we want to catch these incompatibilities here. */
+enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &t)
+{
+	switch (t) {
 	case fx::gltf::Accessor::ComponentType::UnsignedByte:
-		indexType = UINT8; break;
+		return IndexType::UINT8;
 	case fx::gltf::Accessor::ComponentType::UnsignedShort:
-		indexType = UINT16; break;
+		return IndexType::UINT16;
 	case fx::gltf::Accessor::ComponentType::UnsignedInt:
-		indexType = UINT32; break;
+		return IndexType::UINT32;
 	default:
-		vkcv_log(LogLevel::ERROR, "Index type (%u) not supported",
-				 static_cast<uint16_t>(indexAccessor.componentType));
-		return 0;
+        std::cerr << "ERROR: Index type not supported: " <<
+			static_cast<uint16_t>(t) << std::endl;
+		return IndexType::UNDEFINED;
 	}
+}
 
-	const size_t numVertexGroups = objectMesh.primitives.size();
-	
-	std::vector<VertexGroup> vertexGroups;
-	vertexGroups.reserve(numVertexGroups);
-	
-	vertexGroups.push_back({
-		static_cast<PrimitiveMode>(objectPrimitive.mode),
-		object.accessors[objectPrimitive.indices].count,
-		posAccessor.count,
-		{indexType, indexBufferData},
-		{vertexBufferData, vertexAttributes},
-		{posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
-		{posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
-		static_cast<uint8_t>(objectPrimitive.material)
-	});
-	
-	std::vector<Material> materials;
-
-	mesh = {
-		object.meshes[0].name,
-		vertexGroups,
-		materials,
-		0, 0, 0, NULL
-	};
-
-	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
-	// fail quietly if there is no texture
-	if (object.textures.size()) {
-		const std::string mime_type("image/jpeg");
-		const fx::gltf::Texture &tex = object.textures[0];
-		const fx::gltf::Image &img = object.images[tex.source];
-#ifndef NDEBUG
-		printf("texture name=%s sampler=%u source=%u\n",
-				tex.name.c_str(), tex.sampler, tex.source);
-		printf("image   name=%s uri=%s mime=%s\n", img.name.c_str(),
-				img.uri.c_str(), img.mimeType.c_str());
-#endif
-		
-		size_t pos = path.find_last_of("/");
-		auto dir = path.substr(0, pos);
-		
-		mesh.texture_hack.img = stbi_load((dir + "/" + img.uri).c_str(),
-				&mesh.texture_hack.w, &mesh.texture_hack.h,
-				&mesh.texture_hack.ch, 4);
-	}
-	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
-	return 1;
+/**
+ * This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given.
+ * @param translation possible translation vector (default 0,0,0)
+ * @param scale possible scale vector (default 1,1,1)
+ * @param rotation possible rotation, given in quaternion (default 0,0,0,1)
+ * @param matrix possible modelmatrix (default identity)
+ * @return model Matrix as an array of floats
+ */
+std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){
+    std::array<float, 16> modelMatrix = {1,0,0,0,
+                                         0,1,0,0,
+                                         0,0,1,0,
+                                         0,0,0,1};
+    if (matrix != modelMatrix){
+        return matrix;
+    } else {
+        // translation
+        modelMatrix[3] = translation[0];
+        modelMatrix[7] = translation[1];
+        modelMatrix[11] = translation[2];
+        // rotation and scale
+        auto a = rotation[0];
+        auto q1 = rotation[1];
+        auto q2 = rotation[2];
+        auto q3 = rotation[3];
+
+        modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
+        modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
+        modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
+
+        modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
+        modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
+        modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
+
+        modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
+        modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
+        modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
+
+        // flip y, because GLTF uses y up, but vulkan -y up
+        modelMatrix[5] *= -1;
+
+        return modelMatrix;
+    }
+
+}
+
+bool materialHasTexture(const Material *const m, const PBRTextureTarget t)
+{
+	return m->textureMask & bitflag(t);
+}
+
+int loadScene(const std::string &path, Scene &scene){
+    fx::gltf::Document sceneObjects;
+
+    try {
+        if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
+            sceneObjects = fx::gltf::LoadFromBinary(path);
+        } else {
+            sceneObjects = fx::gltf::LoadFromText(path);
+        }
+    } catch (const std::system_error &err) {
+        print_what(err, path);
+        return 0;
+    } catch (const std::exception &e) {
+        print_what(e, path);
+        return 0;
+    }
+    size_t pos = path.find_last_of("/");
+    auto dir = path.substr(0, pos);
+
+    // file has to contain at least one mesh
+    if (sceneObjects.meshes.size() == 0) return 0;
+
+    fx::gltf::Accessor posAccessor;
+    std::vector<VertexAttribute> vertexAttributes;
+    std::vector<Material> materials;
+    std::vector<Texture> textures;
+    std::vector<Sampler> samplers;
+    std::vector<Mesh> meshes;
+    std::vector<VertexGroup> vertexGroups;
+    int groupCount = 0;
+
+    Mesh mesh = {};
+
+    for(int i = 0; i < sceneObjects.meshes.size(); i++){
+        std::vector<int> vertexGroupsIndices;
+        fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i];
+
+        for(int j = 0; j < objectMesh.primitives.size(); j++){
+            fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[j];
+            vertexAttributes.clear();
+            vertexAttributes.reserve(objectPrimitive.attributes.size());
+
+            for (auto const & attrib : objectPrimitive.attributes) {
+
+                fx::gltf::Accessor accessor =  sceneObjects.accessors[attrib.second];
+                VertexAttribute attribute;
+
+                if (attrib.first == "POSITION") {
+                    attribute.type = PrimitiveType::POSITION;
+                    posAccessor = accessor;
+                } else if (attrib.first == "NORMAL") {
+                    attribute.type = PrimitiveType::NORMAL;
+                } else if (attrib.first == "TEXCOORD_0") {
+                    attribute.type = PrimitiveType::TEXCOORD_0;
+                } else if (attrib.first == "TEXCOORD_1") {
+                    attribute.type = PrimitiveType::TEXCOORD_1;
+                } else {
+                    return 0;
+                }
+
+                attribute.offset = sceneObjects.bufferViews[accessor.bufferView].byteOffset;
+                attribute.length = sceneObjects.bufferViews[accessor.bufferView].byteLength;
+                attribute.stride = sceneObjects.bufferViews[accessor.bufferView].byteStride;
+		        attribute.componentType = static_cast<ComponentType>(accessor.componentType);
+
+                if (convertTypeToInt(accessor.type) != 10) {
+                    attribute.componentCount = convertTypeToInt(accessor.type);
+                } else {
+                    return 0;
+                }
+
+                vertexAttributes.push_back(attribute);
+            }
+
+            IndexType indexType;
+            std::vector<uint8_t> indexBufferData = {};
+            if (objectPrimitive.indices >= 0){ // if there is no index buffer, -1 is returned from fx-gltf
+                const fx::gltf::Accessor &indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
+                const fx::gltf::BufferView &indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
+                const fx::gltf::Buffer &indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
+
+                indexBufferData.resize(indexBufferView.byteLength);
+                {
+                    const size_t off = indexBufferView.byteOffset;
+                    const void *const ptr = ((char*)indexBuffer.data.data()) + off;
+                    if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
+                        vkcv_log(LogLevel::ERROR, "Copying index buffer data");
+                        return 0;
+                    }
+                }
+
+                indexType = getIndexType(indexAccessor.componentType);
+                if (indexType == IndexType::UNDEFINED){
+                    vkcv_log(LogLevel::ERROR, "Index Type undefined.");
+                    return 0;
+                }
+            }
+
+            const fx::gltf::BufferView&	vertexBufferView	= sceneObjects.bufferViews[posAccessor.bufferView];
+            const fx::gltf::Buffer&		vertexBuffer		= sceneObjects.buffers[vertexBufferView.buffer];
+
+            // only copy relevant part of vertex data
+            uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
+            uint32_t relevantBufferEnd = 0;
+            for (const auto &attribute : vertexAttributes) {
+                relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
+                const uint32_t attributeEnd = attribute.offset + attribute.length;
+                relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd);    // TODO: need to incorporate stride?
+            }
+            const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
+
+            // FIXME: This only works when all vertex attributes are in one buffer
+            std::vector<uint8_t> vertexBufferData;
+            vertexBufferData.resize(relevantBufferSize);
+            {
+                const void *const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset;
+                if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) {
+                    vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
+                    return 0;
+                }
+            }
+
+            // make vertex attributes relative to copied section
+            for (auto &attribute : vertexAttributes) {
+                attribute.offset -= relevantBufferOffset;
+            }
+
+            const size_t numVertexGroups = objectMesh.primitives.size();
+            vertexGroups.reserve(numVertexGroups);
+
+            vertexGroups.push_back({
+                static_cast<PrimitiveMode>(objectPrimitive.mode),
+                sceneObjects.accessors[objectPrimitive.indices].count,
+                posAccessor.count,
+                {indexType, indexBufferData},
+                {vertexBufferData, vertexAttributes},
+                {posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
+                {posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
+                static_cast<uint8_t>(objectPrimitive.material)
+            });
+
+            vertexGroupsIndices.push_back(groupCount);
+            groupCount++;
+        }
+
+        mesh.name = sceneObjects.meshes[i].name;
+        mesh.vertexGroups = vertexGroupsIndices;
+
+        meshes.push_back(mesh);
+    }
+
+    for(int m = 0; m < sceneObjects.nodes.size(); m++) {
+        meshes[sceneObjects.nodes[m].mesh].modelMatrix = computeModelMatrix(sceneObjects.nodes[m].translation,
+                                                                            sceneObjects.nodes[m].scale,
+                                                                            sceneObjects.nodes[m].rotation,
+                                                                            sceneObjects.nodes[m].matrix);
+    }
+
+    if (sceneObjects.textures.size() > 0){
+        textures.reserve(sceneObjects.textures.size());
+
+        for(int k = 0; k < sceneObjects.textures.size(); k++){
+            const fx::gltf::Texture &tex = sceneObjects.textures[k];
+            const fx::gltf::Image &img = sceneObjects.images[tex.source];
+            std::string img_uri = dir + "/" + img.uri;
+            int w, h, c;
+            uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4);
+            c = 4;	// FIXME hardcoded to always have RGBA channel layout
+            if (!data) {
+                vkcv_log(LogLevel::ERROR, "Loading texture image data.")
+                return 0;
+            }
+            const size_t byteLen = w * h * c;
+
+            std::vector<uint8_t> imgdata;
+            imgdata.resize(byteLen);
+            if (!memcpy(imgdata.data(), data, byteLen)) {
+                vkcv_log(LogLevel::ERROR, "Copying texture image data")
+                free(data);
+                return 0;
+            }
+            free(data);
+
+            textures.push_back({
+                0,
+                static_cast<uint8_t>(c),
+                static_cast<uint16_t>(w),
+                static_cast<uint16_t>(h),
+                imgdata
+            });
+
+        }
+    }
+
+    if (sceneObjects.materials.size() > 0){
+        materials.reserve(sceneObjects.materials.size());
+
+        for (int l = 0; l < sceneObjects.materials.size(); l++){
+            fx::gltf::Material material = sceneObjects.materials[l];
+	    // TODO I think we shouldn't set the index for a texture target if
+	    // it isn't defined. So we need to test first if there is a normal
+	    // texture before assigning material.normalTexture.index.
+	    // About the bitmask: If a normal texture is there, modify the
+	    // materials textureMask like this:
+	    // 		mat.textureMask |= bitflag(asset::normal);
+            materials.push_back({
+               0,
+               material.pbrMetallicRoughness.baseColorTexture.index,
+               material.pbrMetallicRoughness.metallicRoughnessTexture.index,
+               material.normalTexture.index,
+               material.occlusionTexture.index,
+               material.emissiveTexture.index,
+               {
+                   material.pbrMetallicRoughness.baseColorFactor[0],
+                   material.pbrMetallicRoughness.baseColorFactor[1],
+                   material.pbrMetallicRoughness.baseColorFactor[2],
+                   material.pbrMetallicRoughness.baseColorFactor[3]
+               },
+               material.pbrMetallicRoughness.metallicFactor,
+               material.pbrMetallicRoughness.roughnessFactor,
+               material.normalTexture.scale,
+               material.occlusionTexture.strength,
+               {
+                   material.emissiveFactor[0],
+                   material.emissiveFactor[1],
+                   material.emissiveFactor[2]
+               }
+
+            });
+        }
+    }
+
+    scene = {
+            meshes,
+            vertexGroups,
+            materials,
+            textures,
+            samplers
+    };
+
+    return 1;
 }
 
 }
diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index ccbdaf4101c5dabb3e9d43788e255eab85ad5776..474c9764bd29053c95ca2c4511d1287359350d02 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -2,4 +2,5 @@
 # Add new projects/examples here:
 add_subdirectory(first_triangle)
 add_subdirectory(first_mesh)
+add_subdirectory(first_scene)
 add_subdirectory(cmd_sync_test)
\ No newline at end of file
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
index 43941368ab8832f3273c276bb0dbc63d652a16a8..c33f1adc985b40566aa25c5f51e2a1c622dfa280 100644
--- a/projects/cmd_sync_test/src/main.cpp
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -39,10 +39,10 @@ int main(int argc, const char** argv) {
 		{ "VK_KHR_swapchain" }
 	);
 
-	vkcv::asset::Mesh mesh;
+	vkcv::asset::Scene mesh;
 
 	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
-	int result = vkcv::asset::loadMesh(path, mesh);
+	int result = vkcv::asset::loadScene(path, mesh);
 
 	if (result == 1) {
 		std::cout << "Mesh loading successful!" << std::endl;
@@ -136,8 +136,11 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 	
-	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
-	texture.fill(mesh.texture_hack.img);
+	//vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
+	//texture.fill(mesh.texture_hack.img);
+    vkcv::asset::Texture &tex = mesh.textures[0];
+    vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+    texture.fill(tex.data.data());
 
 	vkcv::SamplerHandle sampler = core.createSampler(
 		vkcv::SamplerFilterType::LINEAR,
diff --git a/projects/first_mesh/resources/Szene/Szene.bin b/projects/first_mesh/resources/Szene/Szene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..c87d27637516b0bbf864251dd162773f5cc53e06
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/Szene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ee4742718f720d589a2a03f5d879f8c50ba9057718d191a43b046eaa9071080d
+size 70328
diff --git a/projects/first_mesh/resources/Szene/Szene.gltf b/projects/first_mesh/resources/Szene/Szene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..e5a32b29af5d0a2ac5f497e60b4b92c1873e1df9
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/Szene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75ba834118792ebbacf528a1690c7d04df4b4c8122b9f99a9aa9a9d075d2c86a
+size 7421
diff --git a/projects/first_mesh/resources/Szene/boards2_vcyc.jpg b/projects/first_mesh/resources/Szene/boards2_vcyc.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/boards2_vcyc.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg b/projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 3c1c73014501fd5922105ef35fe53e196b9cfb9b..8ffe501747fe516de9ab10834de788f110ad8722 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -29,10 +29,10 @@ int main(int argc, const char** argv) {
 		{ "VK_KHR_swapchain" }
 	);
 
-	vkcv::asset::Mesh mesh;
+	vkcv::asset::Scene mesh;
 
 	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
-	int result = vkcv::asset::loadMesh(path, mesh);
+	int result = vkcv::asset::loadScene(path, mesh);
 
 	if (result == 1) {
 		std::cout << "Mesh loading successful!" << std::endl;
@@ -118,8 +118,11 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 	
-	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
-	texture.fill(mesh.texture_hack.img);
+	// FIXME There should be a test here to make sure there is at least 1
+	// texture in the mesh.
+	vkcv::asset::Texture &tex = mesh.textures[0];
+	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+	texture.fill(tex.data.data());
 
 	vkcv::SamplerHandle sampler = core.createSampler(
 		vkcv::SamplerFilterType::LINEAR,
@@ -129,9 +132,9 @@ int main(int argc, const char** argv) {
 	);
 
 	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
-			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::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()) };
@@ -194,7 +197,6 @@ int main(int argc, const char** argv) {
 			renderTargets);
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-
 		core.endFrame();
 	}
 	
diff --git a/projects/first_scene/.gitignore b/projects/first_scene/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b0d802667a184267e6ee764264e976dfead8e9dd
--- /dev/null
+++ b/projects/first_scene/.gitignore
@@ -0,0 +1 @@
+first_scene
diff --git a/projects/first_scene/CMakeLists.txt b/projects/first_scene/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b90739750011a36b4c1d9e0bff7cba986074228
--- /dev/null
+++ b/projects/first_scene/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.16)
+project(first_scene)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# this should fix the execution path to load local files from the project
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+
+# adding source files to the project
+add_executable(first_scene src/main.cpp)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(first_scene PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(first_scene PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+    
+    # in addition to setting the output directory, the working directory has to be set
+	# by default visual studio sets the working directory to the build directory, when using the debugger
+	set_target_properties(first_scene PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png b/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..8258525f22097f2382ec5c26ad0df7eb50718642
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0ce87f6407ee40ffa60983587aeb52333d59b4b1c01a53e11f4bb227ba1099d9
+size 109
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png b/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..620fe7621cf35409ae8c04099dc8bc4bbc04343b
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:68a0064d457a6f7994814b07d943deda778754128935689874334300ede6161d
+size 2332064
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png b/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5570e88c569036b9d00155ef6113013aa23f2503
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:42c2715635081eb29c4489ce631798b0e9c881460efc0aa63d0e81641a0dcfe9
+size 108
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png b/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..d07681f5bfa25c279321c3074e21e4900c5eb0fa
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:15b0133e140899c47ccf35b0f99a7e337e3110ae089f45d27faf9983f3e0a1f7
+size 770758
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..1845e8a7d586a0d23300ad9d04a82f1d5048fcf5
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ea7c82c0f9e25afa401470df1fb6903f508fa138d21ad30f57a9153b0395b198
+size 521315
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..1c800c0489693b66d6163bdc2156d453b68ca19b
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:119efbbc020244ff9b7ff16ac9795a6d4b1808d1b90d81d20d2c874d0dc8a924
+size 1693468
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..36f46ebf25158c78bc26d83860e1c08b2c9e96cf
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:99896468c7d47dd5391d585eecf149f420eca3bfec31923c21fa86c45fe02d0f
+size 108
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..28c205d4e70867ec58448ca8ba2c2117c611a367
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b31618aa5adce4ad476bec2c03718c5ae097250e784344f2d298b8a74c3bfd46
+size 90
diff --git a/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png b/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..e0104189a1190c160152101e9e328e81ed89121b
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6eff6ccd12d8b39d60ae5ee91edd73d4d7838fcb5d9bc6ff0e671bdf009134e9
+size 109
diff --git a/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png b/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa13483d25595ee6b3a00e7fe6ce48aed7b6aaca
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fe92c40ff4032fdaf10eeafd943657a0c6e0bfb3f38770f5654aa943a660f421
+size 59419
diff --git a/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png b/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..b91d0ac62fbeabbef1ed78f1343f919836cbca40
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4ca7d436a68a2a1237aee6e763b2954f01666b21f1dbd46929a322ea277483d2
+size 779227
diff --git a/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png b/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..6efd967984ee2e68b89de9e471b93396c13ca69a
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b01fc6482054c64d7407b283731e57fce0601a8db28b6781c14fae3c6b30b0fe
+size 504362
diff --git a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0e0c4f4134cb99d3765d6f078f71efab8861bf1
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:df138ee68c1d455652d1b9ae3dd03e93fcd2f6a0d8a1f12e3710f39143088674
+size 1593466
diff --git a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f310653b5211575da3cab2f6deb47ec6826a936
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6af5da97cbb25d79aea2dde8dd71ecbd495334fe34e99497ba17821be93fd7fd
+size 2696676
diff --git a/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png b/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e4f0fcb312b8f8bd0df965bfe6b8ac62ec5df4d
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2796affdfdcf6bc805176d9f85505680b5ee52eeec625e9eaeea4f0ff3854883
+size 108
diff --git a/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png b/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..28c205d4e70867ec58448ca8ba2c2117c611a367
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b31618aa5adce4ad476bec2c03718c5ae097250e784344f2d298b8a74c3bfd46
+size 90
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.bin b/projects/first_scene/resources/Cutlery/cutlerySzene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..ab9a0aa47205e8f3064d2f16a950d4733fb4f472
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/cutlerySzene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f545b986e0a1ac5bff5d49693a52042aa37878425818f72c69c243da20d1f99d
+size 183324
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.glb b/projects/first_scene/resources/Cutlery/cutlerySzene.glb
new file mode 100644
index 0000000000000000000000000000000000000000..b0c5f345aaaa3f96d7158a0992ee124aae99a69a
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/cutlerySzene.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fb1bad604192ca36222c0ca485ba87b846ecbd11ee8254327e04e3c993b00116
+size 11150396
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.gltf b/projects/first_scene/resources/Cutlery/cutlerySzene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..53e339cda4511f3f1a8670b36469e184aac530e2
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/cutlerySzene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c77cd60e2327daca1a01044e45f2c38655f7b781bd07985fc0135328a8a96b57
+size 34312
diff --git a/projects/first_scene/resources/Sponza/Sponza.bin b/projects/first_scene/resources/Sponza/Sponza.bin
new file mode 100644
index 0000000000000000000000000000000000000000..cfedd26ca5a67b6d0a47d44d13a75e14a141717a
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/Sponza.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68
+size 6347696
diff --git a/projects/first_scene/resources/Sponza/Sponza.gltf b/projects/first_scene/resources/Sponza/Sponza.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..172ea07e21c94465211c860cd805355704cef230
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/Sponza.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787
+size 713962
diff --git a/projects/first_scene/resources/Sponza/background.png b/projects/first_scene/resources/Sponza/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..b64def129da38f4e23d89e21b4af1039008a4327
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/background.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686
+size 1417666
diff --git a/projects/first_scene/resources/Sponza/chain_texture.png b/projects/first_scene/resources/Sponza/chain_texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1e1768cff78e0614ad707eca8602a4c4edab5e5
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/chain_texture.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4
+size 490895
diff --git a/projects/first_scene/resources/Sponza/lion.png b/projects/first_scene/resources/Sponza/lion.png
new file mode 100644
index 0000000000000000000000000000000000000000..c49c7f0ed31e762e19284d0d3624fbc47664e56b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/lion.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652
+size 2088728
diff --git a/projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png b/projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..cde4c7a6511e9a5f03c63ad996437fcdba3ce2df
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851
+size 1928829
diff --git a/projects/first_scene/resources/Sponza/sponza_arch_diff.png b/projects/first_scene/resources/Sponza/sponza_arch_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bcd9bda2918d226039f9e2d03902d377b706fab6
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_arch_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57
+size 1596151
diff --git a/projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png b/projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..59de631ffac4414cabf69b2dc794c46fc187d6cb
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6
+size 1872074
diff --git a/projects/first_scene/resources/Sponza/sponza_column_a_diff.png b/projects/first_scene/resources/Sponza/sponza_column_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..01a82432d3f9939bbefe850bdb900f1ff9a3f6db
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_column_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826
+size 1840992
diff --git a/projects/first_scene/resources/Sponza/sponza_column_b_diff.png b/projects/first_scene/resources/Sponza/sponza_column_b_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..10a660cce2a5a9b8997772c746058ce23e7d45d7
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_column_b_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b
+size 2170433
diff --git a/projects/first_scene/resources/Sponza/sponza_column_c_diff.png b/projects/first_scene/resources/Sponza/sponza_column_c_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc46fd979044a938d3adca7601689e71504e48bf
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_column_c_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735
+size 2066950
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png b/projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..384c8c2c051160d530eb3ac8b05c9c60752a2d2b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb
+size 9510905
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_diff.png b/projects/first_scene/resources/Sponza/sponza_curtain_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..af842e9f5fe18c1f609875e00899a6770fa4488b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_curtain_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8
+size 9189263
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png b/projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c9b6391a199407637fa71033d79fb58b8b4f0d7
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476
+size 8785458
diff --git a/projects/first_scene/resources/Sponza/sponza_details_diff.png b/projects/first_scene/resources/Sponza/sponza_details_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..12656686362c3e0a297e060491f33bd7351551f9
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_details_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c
+size 1522643
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png b/projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..879d16ef84722a4fc13e83a771778de326e4bc54
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab
+size 2229440
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_diff.png b/projects/first_scene/resources/Sponza/sponza_fabric_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..3311287a219d2148620b87fe428fea071688d051
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_fabric_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287
+size 2267382
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png b/projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..de110f369004388dae4cd5067c63428db3a07834
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993
+size 2258220
diff --git a/projects/first_scene/resources/Sponza/sponza_flagpole_diff.png b/projects/first_scene/resources/Sponza/sponza_flagpole_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f6e0812a0df80346318baa3cb50a6888afc58f8
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_flagpole_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438
+size 1425673
diff --git a/projects/first_scene/resources/Sponza/sponza_floor_a_diff.png b/projects/first_scene/resources/Sponza/sponza_floor_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..788ed764f79ba724f04a2d603076a5b85013e188
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_floor_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab
+size 1996838
diff --git a/projects/first_scene/resources/Sponza/sponza_roof_diff.png b/projects/first_scene/resources/Sponza/sponza_roof_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5b84261fdd1cc776a94b3ce398c7806b895f9a3
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_roof_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b
+size 2320533
diff --git a/projects/first_scene/resources/Sponza/sponza_thorn_diff.png b/projects/first_scene/resources/Sponza/sponza_thorn_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9142674a7d4a6f94a48c5152cf0300743b597a
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_thorn_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e
+size 635001
diff --git a/projects/first_scene/resources/Sponza/vase_dif.png b/projects/first_scene/resources/Sponza/vase_dif.png
new file mode 100644
index 0000000000000000000000000000000000000000..61236a81cb324af8797b05099cd264cefe189e56
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_dif.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9
+size 1842101
diff --git a/projects/first_scene/resources/Sponza/vase_hanging.png b/projects/first_scene/resources/Sponza/vase_hanging.png
new file mode 100644
index 0000000000000000000000000000000000000000..36a3cee71d8213225090c74f8c0dce33b9d44378
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_hanging.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31
+size 1432804
diff --git a/projects/first_scene/resources/Sponza/vase_plant.png b/projects/first_scene/resources/Sponza/vase_plant.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ad95e702e229f1ebd803e5203a266d15f2c07b9
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_plant.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e
+size 998089
diff --git a/projects/first_scene/resources/Sponza/vase_round.png b/projects/first_scene/resources/Sponza/vase_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..c17953abc000c44b8991e23c136c2b67348f3d1b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_round.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a
+size 1871399
diff --git a/projects/first_scene/resources/Szene/Szene.bin b/projects/first_scene/resources/Szene/Szene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..c87d27637516b0bbf864251dd162773f5cc53e06
--- /dev/null
+++ b/projects/first_scene/resources/Szene/Szene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ee4742718f720d589a2a03f5d879f8c50ba9057718d191a43b046eaa9071080d
+size 70328
diff --git a/projects/first_scene/resources/Szene/Szene.gltf b/projects/first_scene/resources/Szene/Szene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..e5a32b29af5d0a2ac5f497e60b4b92c1873e1df9
--- /dev/null
+++ b/projects/first_scene/resources/Szene/Szene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75ba834118792ebbacf528a1690c7d04df4b4c8122b9f99a9aa9a9d075d2c86a
+size 7421
diff --git a/projects/first_scene/resources/Szene/boards2_vcyc.jpg b/projects/first_scene/resources/Szene/boards2_vcyc.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_scene/resources/Szene/boards2_vcyc.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg b/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_scene/resources/shaders/compile.bat b/projects/first_scene/resources/shaders/compile.bat
new file mode 100644
index 0000000000000000000000000000000000000000..b4521235c40fe5fb163bab874560c2f219b7517f
--- /dev/null
+++ b/projects/first_scene/resources/shaders/compile.bat
@@ -0,0 +1,3 @@
+%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
+%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
+pause
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/frag.spv b/projects/first_scene/resources/shaders/frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..087e4e22fb2fcec27d99b3ff2aa1a705fe755796
Binary files /dev/null and b/projects/first_scene/resources/shaders/frag.spv differ
diff --git a/projects/first_scene/resources/shaders/shader.frag b/projects/first_scene/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b5494bea7d6497e2e3dcd8559606864a71adb74e
--- /dev/null
+++ b/projects/first_scene/resources/shaders/shader.frag
@@ -0,0 +1,15 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform texture2D  meshTexture;
+layout(set=0, binding=1) uniform sampler    textureSampler;
+
+void main()	{
+	outColor = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
+    //outColor = passNormal * 0.5 + 0.5;
+}
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/shader.vert b/projects/first_scene/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..76855152253b48b7400f016d063ed4f0e507435e
--- /dev/null
+++ b/projects/first_scene/resources/shaders/shader.vert
@@ -0,0 +1,19 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec2 passUV;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+	passNormal  = inNormal;
+    passUV      = inUV;
+}
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/vert.spv b/projects/first_scene/resources/shaders/vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..374c023e14b351eb43cbcda5951cbb8b3d6f96a1
Binary files /dev/null and b/projects/first_scene/resources/shaders/vert.spv differ
diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a59edf7549dfd50877e78e3ca5071bba72098b3
--- /dev/null
+++ b/projects/first_scene/src/main.cpp
@@ -0,0 +1,266 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/Logger.hpp>
+
+glm::mat4 arrayTo4x4Matrix(std::array<float,16> array){
+    glm::mat4 matrix;
+    for (int i = 0; i < 4; i++){
+        for (int j = 0; j < 4; j++){
+            matrix[i][j] = array[j * 4 + i];
+        }
+    }
+    return matrix;
+}
+
+int main(int argc, const char** argv) {
+	const char* applicationName = "First Scene";
+
+	uint32_t windowWidth = 800;
+	uint32_t windowHeight = 600;
+
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		true
+	);
+
+	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));
+	cameraManager.getCamera(camIndex0).setNearFar(0.1f, 30.0f);
+	
+	cameraManager.getCamera(camIndex1).setNearFar(0.1f, 30.0f);
+
+	window.initEvents();
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
+	vkcv::asset::Scene scene;
+
+	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
+	int result = vkcv::asset::loadScene(path, scene);
+
+	if (result == 1) {
+		std::cout << "Mesh loading successful!" << std::endl;
+	}
+	else {
+		std::cout << "Mesh loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	assert(!scene.vertexGroups.empty());
+	std::vector<std::vector<uint8_t>> vBuffers;
+	std::vector<std::vector<uint8_t>> iBuffers;
+
+	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
+	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
+	std::vector<vkcv::asset::VertexAttribute> vAttributes;
+
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+
+		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
+		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
+
+		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
+
+		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+			});
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
+	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
+		vertexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			group.vertexBuffer.data.size()));
+		vertexBuffers.back().fill(group.vertexBuffer.data);
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
+	for (const auto& dataBuffer : iBuffers) {
+		indexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::INDEX,
+			dataBuffer.size()));
+		indexBuffers.back().fill(dataBuffer);
+	}
+
+	int vertexBufferIndex = 0;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+			vAttributes.push_back(attribute);
+			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
+		}
+		vertexBufferBindings.push_back(vBufferBindings);
+		vBufferBindings.clear();
+		vertexBufferIndex++;
+	}
+
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchainImageFormat()
+	);
+
+	const vkcv::AttachmentDescription depth_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		vk::Format::eD32Sfloat
+	);
+
+	vkcv::PassConfig scenePassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle scenePass = core.createPass(scenePassDefinition);
+
+	if (!scenePass) {
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram sceneShaderProgram{};
+	sceneShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+	sceneShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+
+	const std::vector<vkcv::VertexAttachment> vertexAttachments = sceneShaderProgram.getVertexAttachments();
+	std::vector<vkcv::VertexBinding> bindings;
+	for (size_t i = 0; i < vertexAttachments.size(); i++) {
+		bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+	}
+
+	const vkcv::VertexLayout sceneLayout(bindings);
+
+	uint32_t setID = 0;
+
+	std::vector<vkcv::DescriptorBinding> descriptorBindings = { sceneShaderProgram.getReflectedDescriptors()[setID] };
+
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+	std::vector<vkcv::Image> sceneImages;
+	std::vector<vkcv::DescriptorSetHandle> descriptorSets;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		descriptorSets.push_back(core.createDescriptorSet(descriptorBindings));
+
+		const auto& material = scene.materials[vertexGroup.materialIndex];
+
+		int baseColorIndex = material.baseColor;
+		if (baseColorIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
+			baseColorIndex = 0;
+		}
+
+		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
+
+		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
+		sceneImages.back().fill(sceneTexture.data.data());
+
+		vkcv::DescriptorWrites setWrites;
+		setWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) };
+		setWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, sampler) };
+		core.writeDescriptorSet(descriptorSets.back(), setWrites);
+	}
+
+	const vkcv::PipelineConfig scenePipelineDefsinition{
+		sceneShaderProgram,
+		UINT32_MAX,
+		UINT32_MAX,
+		scenePass,
+		{sceneLayout},
+		{ core.getDescriptorSet(descriptorSets[0]).layout },
+		true };
+	vkcv::PipelineHandle scenePipeline = core.createGraphicsPipeline(scenePipelineDefsinition);
+	
+	if (!scenePipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+    std::vector<vkcv::DrawcallInfo> drawcalls;
+	for(int i = 0; i < scene.vertexGroups.size(); i++){
+        vkcv::Mesh renderMesh(vertexBufferBindings[i], indexBuffers[i].getVulkanHandle(), scene.vertexGroups[i].numIndices);
+
+        vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSets[i]).vulkanHandle);
+
+	    drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage}));
+	}
+
+	std::vector<glm::mat4> modelMatrices;
+	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
+	for (const auto &mesh : scene.meshes) {
+		const glm::mat4 m = arrayTo4x4Matrix(mesh.modelMatrix);
+		for (const auto &vertexGroupIndex : mesh.vertexGroups) {
+			modelMatrices[vertexGroupIndex] = m;
+		}
+	}
+	std::vector<glm::mat4> mvp;
+
+	auto start = std::chrono::system_clock::now();
+	while (window.isWindowOpen()) {
+        vkcv::Window::pollEvents();
+		
+		if(window.getHeight() == 0 || window.getWidth() == 0)
+			continue;
+		
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+		
+		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
+			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
+			
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+  
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		
+		start = end;
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		glm::mat4 vp = cameraManager.getActiveCamera().getMVP();
+
+		mvp.clear();
+        for (const auto& m : modelMatrices) {
+            mvp.push_back(vp * m);
+        }
+
+		vkcv::PushConstantData pushConstantData((void*)mvp.data(), sizeof(glm::mat4));
+
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			scenePass,
+			scenePipeline,
+			pushConstantData,
+			drawcalls,
+			renderTargets);
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		core.endFrame();
+	}
+	
+	return 0;
+}