From af5d1d149f58afd1bd17e547be3d07ed91444562 Mon Sep 17 00:00:00 2001
From: Tobias Frisch <tfrisch@uni-koblenz.de>
Date: Tue, 29 Nov 2022 18:48:11 +0100
Subject: [PATCH] Add sponza as example scene for ray tracing

Signed-off-by: Tobias Frisch <tfrisch@uni-koblenz.de>
---
 modules/scene/include/vkcv/scene/Mesh.hpp     | 11 ++-
 modules/scene/include/vkcv/scene/Node.hpp     | 28 +++++++-
 modules/scene/include/vkcv/scene/Scene.hpp    | 28 +++++++-
 modules/scene/src/vkcv/scene/Mesh.cpp         | 11 ++-
 modules/scene/src/vkcv/scene/Node.cpp         | 45 +++++++++++-
 modules/scene/src/vkcv/scene/Scene.cpp        | 35 ++++++++-
 projects/rt_ambient_occlusion/CMakeLists.txt  |  2 -
 .../resources/Sponza/Sponza.bin               |  3 +
 .../resources/Sponza/Sponza.gltf              |  3 +
 .../resources/shaders/ambientOcclusion.rchit  | 71 +++++++++++++------
 .../resources/shaders/ambientOcclusion.rgen   | 40 +++++------
 .../resources/shaders/ambientOcclusion.rmiss  |  1 -
 projects/rt_ambient_occlusion/src/main.cpp    | 71 ++++++++++++++++---
 src/vkcv/AccelerationStructureManager.cpp     | 65 +++++++++--------
 14 files changed, 315 insertions(+), 99 deletions(-)
 create mode 100644 projects/rt_ambient_occlusion/resources/Sponza/Sponza.bin
 create mode 100644 projects/rt_ambient_occlusion/resources/Sponza/Sponza.gltf

diff --git a/modules/scene/include/vkcv/scene/Mesh.hpp b/modules/scene/include/vkcv/scene/Mesh.hpp
index d0eb3597..a0ad087f 100644
--- a/modules/scene/include/vkcv/scene/Mesh.hpp
+++ b/modules/scene/include/vkcv/scene/Mesh.hpp
@@ -20,6 +20,13 @@ namespace vkcv::scene {
      */
 	typedef typename event_function<const glm::mat4&, const glm::mat4&, PushConstants&, vkcv::Drawcall&>::type RecordMeshDrawcallFunction;
 	
+	/**
+	* An event function type to be called on per geometry while creating an
+	* acceleration structure using the given instance index and geometry index
+	* of each geometry in a bottom-level acceleration structure.
+	*/
+	typedef typename event_function<size_t, size_t, const vkcv::GeometryData&, const glm::mat4&>::type ProcessGeometryFunction;
+	
 	class Node;
 
     /**
@@ -92,9 +99,11 @@ namespace vkcv::scene {
 		 *
 		 * @param[in,out] core Core instance
 		 * @param[out] accelerationStructures List of acceleration structures
+		 * @param[in] process Geometry processing event function
 		 */
 		void appendAccelerationStructures(Core& core,
-				std::vector<AccelerationStructureHandle>&accelerationStructures) const;
+				std::vector<AccelerationStructureHandle>&accelerationStructures,
+				const ProcessGeometryFunction &process) const;
 
         /**
          * Return the amount of drawcalls of the mesh
diff --git a/modules/scene/include/vkcv/scene/Node.hpp b/modules/scene/include/vkcv/scene/Node.hpp
index ff38328a..f1cd316f 100644
--- a/modules/scene/include/vkcv/scene/Node.hpp
+++ b/modules/scene/include/vkcv/scene/Node.hpp
@@ -50,6 +50,30 @@ namespace vkcv::scene {
          * @param[in,out] scene Scene
          */
 		explicit Node(Scene& scene);
+		
+		/**
+		 * Return the amount of nodes managed by this node and its children.
+		 *
+		 * @return Amount of nodes
+		 */
+		[[nodiscard]]
+		size_t getNodeCount() const;
+		
+		/**
+		 * Return the amount of meshes managed by this node and its children.
+		 *
+		 * @return Amount of meshes
+		 */
+		[[nodiscard]]
+		size_t getMeshCount() const;
+		
+		/**
+		 * Return the amount of mesh parts managed by this node and its children.
+		 *
+		 * @return Amount of mesh parts
+		 */
+		[[nodiscard]]
+		size_t getMeshPartCount() const;
 
         /**
          * Add a given mesh to this node for drawcall recording.
@@ -88,9 +112,11 @@ namespace vkcv::scene {
 		 *
 		 * @param[in,out] core Core instance
 		 * @param[out] accelerationStructures List of acceleration structures
+		 * @param[in] process Geometry processing event function
 		 */
 		void appendAccelerationStructures(Core& core,
-				std::vector<AccelerationStructureHandle>&accelerationStructures) const;
+				std::vector<AccelerationStructureHandle>&accelerationStructures,
+				const ProcessGeometryFunction &process) const;
 
         /**
          * Splits child nodes into tree based graphs of nodes
diff --git a/modules/scene/include/vkcv/scene/Scene.hpp b/modules/scene/include/vkcv/scene/Scene.hpp
index 7c1806e5..a6feb833 100644
--- a/modules/scene/include/vkcv/scene/Scene.hpp
+++ b/modules/scene/include/vkcv/scene/Scene.hpp
@@ -148,6 +148,30 @@ namespace vkcv::scene {
          * @return Reference to this scene
          */
         Scene& operator=(Scene&& other) noexcept;
+		
+		/**
+		 * Return the amount of nodes managed by this scene in total.
+		 *
+		 * @return Amount of nodes
+		 */
+		[[nodiscard]]
+		size_t getNodeCount() const;
+		
+		/**
+		 * Return the amount of meshes managed by this scene in total.
+		 *
+		 * @return Amount of meshes
+		 */
+		[[nodiscard]]
+		size_t getMeshCount() const;
+		
+		/**
+		 * Return the amount of mesh parts managed by this scene in total.
+		 *
+		 * @return Amount of mesh parts
+		 */
+		[[nodiscard]]
+		size_t getMeshPartCount() const;
 
         /**
          * Return the amount of materials managed by this scene.
@@ -193,9 +217,11 @@ namespace vkcv::scene {
 		 * which contains all of its meshes as bottom-level acceleration structures with
 		 * its given geometry.
 		 *
+		 * @param[in] process Geometry processing event function
 		 * @return Acceleration structure
 		 */
-		AccelerationStructureHandle createAccelerationStructure() const;
+		AccelerationStructureHandle createAccelerationStructure(
+				const ProcessGeometryFunction &process = nullptr) const;
 
         /**
          * Instantiation function to create a new scene instance.
diff --git a/modules/scene/src/vkcv/scene/Mesh.cpp b/modules/scene/src/vkcv/scene/Mesh.cpp
index 4accf44b..03eb8a59 100644
--- a/modules/scene/src/vkcv/scene/Mesh.cpp
+++ b/modules/scene/src/vkcv/scene/Mesh.cpp
@@ -124,7 +124,8 @@ namespace vkcv::scene {
 	}
 	
 	void Mesh::appendAccelerationStructures(Core &core,
-			std::vector<AccelerationStructureHandle> &accelerationStructures) const {
+			std::vector<AccelerationStructureHandle> &accelerationStructures,
+			const ProcessGeometryFunction &process) const {
 		std::vector<GeometryData> geometryData;
 		geometryData.reserve(m_parts.size());
 		
@@ -149,7 +150,15 @@ namespace vkcv::scene {
 		);
 		
 		if (handle) {
+			const size_t instanceIndex = accelerationStructures.size();
+			
 			accelerationStructures.push_back(handle);
+			
+			if (process) {
+				for (size_t i = 0; i < geometryData.size(); i++) {
+					process(instanceIndex, i, geometryData[i], m_transform);
+				}
+			}
 		}
 	}
 	
diff --git a/modules/scene/src/vkcv/scene/Node.cpp b/modules/scene/src/vkcv/scene/Node.cpp
index 550a02c0..5ccf89c0 100644
--- a/modules/scene/src/vkcv/scene/Node.cpp
+++ b/modules/scene/src/vkcv/scene/Node.cpp
@@ -58,6 +58,44 @@ namespace vkcv::scene {
 		return *this;
 	}
 	
+	size_t Node::getNodeCount() const {
+		size_t count = 1;
+		
+		for (const auto& node : m_nodes) {
+			count += node.getNodeCount();
+		}
+		
+		return count;
+	}
+	
+	size_t Node::getMeshCount() const {
+		size_t count = 0;
+		
+		for (auto& mesh : m_meshes) {
+			count++;
+		}
+		
+		for (const auto& node : m_nodes) {
+			count += node.getMeshCount();
+		}
+		
+		return count;
+	}
+	
+	size_t Node::getMeshPartCount() const {
+		size_t count = 0;
+		
+		for (auto& mesh : m_meshes) {
+			count += mesh.m_parts.size();
+		}
+		
+		for (const auto& node : m_nodes) {
+			count += node.getMeshPartCount();
+		}
+		
+		return count;
+	}
+	
 	void Node::addMesh(const Mesh& mesh) {
 		if (m_meshes.empty()) {
 			m_bounds = mesh.getBounds();
@@ -110,13 +148,14 @@ namespace vkcv::scene {
 	}
 	
 	void Node::appendAccelerationStructures(Core& core,
-			std::vector<AccelerationStructureHandle> &accelerationStructures) const {
+			std::vector<AccelerationStructureHandle> &accelerationStructures,
+			const ProcessGeometryFunction &process) const {
 		for (auto& mesh : m_meshes) {
-			mesh.appendAccelerationStructures(core, accelerationStructures);
+			mesh.appendAccelerationStructures(core, accelerationStructures, process);
 		}
 		
 		for (auto& node : m_nodes) {
-			node.appendAccelerationStructures(core, accelerationStructures);
+			node.appendAccelerationStructures(core, accelerationStructures, process);
 		}
 	}
 	
diff --git a/modules/scene/src/vkcv/scene/Scene.cpp b/modules/scene/src/vkcv/scene/Scene.cpp
index abd0774e..b2b65a4a 100644
--- a/modules/scene/src/vkcv/scene/Scene.cpp
+++ b/modules/scene/src/vkcv/scene/Scene.cpp
@@ -99,6 +99,36 @@ namespace vkcv::scene {
 		}
 	}
 	
+	size_t Scene::getNodeCount() const {
+		size_t count = 0;
+		
+		for (const auto& node : m_nodes) {
+			count += node.getNodeCount();
+		}
+		
+		return count;
+	}
+	
+	size_t Scene::getMeshCount() const {
+		size_t count = 0;
+		
+		for (const auto& node : m_nodes) {
+			count += node.getMeshCount();
+		}
+		
+		return count;
+	}
+	
+	size_t Scene::getMeshPartCount() const {
+		size_t count = 0;
+		
+		for (const auto& node : m_nodes) {
+			count += node.getMeshPartCount();
+		}
+		
+		return count;
+	}
+	
 	size_t Scene::getMaterialCount() const {
 		return m_materials.size();
 	}
@@ -150,11 +180,12 @@ namespace vkcv::scene {
 		m_core->recordEndDebugLabel(cmdStream);
 	}
 	
-	AccelerationStructureHandle Scene::createAccelerationStructure() const {
+	AccelerationStructureHandle Scene::createAccelerationStructure(
+			const ProcessGeometryFunction &process) const {
 		std::vector<AccelerationStructureHandle> accelerationStructures;
 		
 		for (auto& node : m_nodes) {
-			node.appendAccelerationStructures(*m_core, accelerationStructures);
+			node.appendAccelerationStructures(*m_core, accelerationStructures, process);
 		}
 		
 		return m_core->createAccelerationStructure(accelerationStructures);
diff --git a/projects/rt_ambient_occlusion/CMakeLists.txt b/projects/rt_ambient_occlusion/CMakeLists.txt
index 6ed4c40c..ce4a2fce 100644
--- a/projects/rt_ambient_occlusion/CMakeLists.txt
+++ b/projects/rt_ambient_occlusion/CMakeLists.txt
@@ -14,7 +14,6 @@ target_include_directories(rt_ambient_occlusion SYSTEM BEFORE PRIVATE
 		${vkcv_includes}
 		${vkcv_asset_loader_include}
 		${vkcv_camera_include}
-		${vkcv_geometry_include}
 		${vkcv_scene_include}
 		${vkcv_shader_compiler_include}
 		${vkcv_scene_include})
@@ -24,7 +23,6 @@ target_link_libraries(rt_ambient_occlusion
 		vkcv ${vkcv_libraries}
 		vkcv_asset_loader ${vkcv_asset_loader_libraries}
 		vkcv_camera
-		vkcv_geometry
 		vkcv_scene
 		vkcv_shader_compiler
 		vkcv_scene)
diff --git a/projects/rt_ambient_occlusion/resources/Sponza/Sponza.bin b/projects/rt_ambient_occlusion/resources/Sponza/Sponza.bin
new file mode 100644
index 00000000..cfedd26c
--- /dev/null
+++ b/projects/rt_ambient_occlusion/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/rt_ambient_occlusion/resources/Sponza/Sponza.gltf b/projects/rt_ambient_occlusion/resources/Sponza/Sponza.gltf
new file mode 100644
index 00000000..a220d7fe
--- /dev/null
+++ b/projects/rt_ambient_occlusion/resources/Sponza/Sponza.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:36d0bfcec05a3a66f11bcd22eb47f2cbb7a47f53552bb1f8132b4e10b5231ec9
+size 705584
diff --git a/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rchit b/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rchit
index 9bc4f664..9c8a2403 100644
--- a/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rchit
+++ b/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rchit
@@ -1,54 +1,81 @@
 #version 460
 #extension GL_EXT_ray_tracing : require
+#extension GL_EXT_nonuniform_qualifier : enable
 #extension GL_EXT_scalar_block_layout : require
 #extension GL_EXT_shader_16bit_storage : require
 
+#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
+#extension GL_EXT_buffer_reference2 : require
+
 hitAttributeEXT vec2 attributes;
 
 layout(location = 0) rayPayloadInEXT Payload {
   float hitSky;
   vec3 worldPosition;
   vec3 worldNormal;
-  uvec4 hit;
 } payload;
 
-layout(binding = 2, set = 0, scalar) buffer rtVertices
-{
-    float vertices[];
+layout(buffer_reference, scalar) buffer Vertices {
+    float v[];
+};
+
+layout(buffer_reference, scalar) buffer Indices {
+    uint16_t i[];
+    //uint i[];
+};
+
+struct ObjDesc {
+    uint64_t vertexAddress;
+    uint64_t indexAddress;
+    uint vertexStride;
+    uint pad0;
+    uint pad1;
+    uint pad2;
+    mat4 transform;
+};
+
+layout(binding = 2, set = 0, scalar) buffer rtObjects {
+    ObjDesc objects[];
 };
 
-layout(binding = 3, set = 0, scalar) buffer rtIndices
-{
-    uint16_t indices[];
+layout(binding = 3, set = 0, scalar) buffer rtInstanceCount {
+    int instanceCount;
 };
 
 void main() {
-    payload.worldPosition = vec3(1.0, 0.0, 0.5);
+    int instanceIndex = gl_InstanceID + gl_GeometryIndexEXT * instanceCount;
 
-    payload.hit = uvec4(
-        gl_PrimitiveID,
-        gl_InstanceID,
-        gl_InstanceCustomIndexEXT,
-        gl_GeometryIndexEXT
-    );
+    if (instanceIndex >= objects.length()) {
+        payload.hitSky = 1.0f;
+        return;
+    }
+
+    ObjDesc obj = objects[nonuniformEXT(instanceIndex)];
+    Indices indices = Indices(obj.indexAddress);
+    Vertices vertices = Vertices(obj.vertexAddress);
 
-    ivec3 indicesVec = ivec3(indices[3 * gl_PrimitiveID + 0], indices[3 * gl_PrimitiveID + 1], indices[3 * gl_PrimitiveID + 2]);
+    const uint stride = obj.vertexStride;
+
+    ivec3 indicesVec = ivec3(
+        indices.i[3 * gl_PrimitiveID + 0],
+        indices.i[3 * gl_PrimitiveID + 1],
+        indices.i[3 * gl_PrimitiveID + 2]
+    );
 
     // current triangle
-    const vec3 v0 = vec3(vertices[3 * indicesVec.x + 0],vertices[3 * indicesVec.x + 1],vertices[3 * indicesVec.x + 2]);
-    const vec3 v1 = vec3(vertices[3 * indicesVec.y + 0],vertices[3 * indicesVec.y + 1],vertices[3 * indicesVec.y + 2]);
-    const vec3 v2 = vec3(vertices[3 * indicesVec.z + 0],vertices[3 * indicesVec.z + 1],vertices[3 * indicesVec.z + 2]);
+    const vec3 v0 = vec3(vertices.v[stride * indicesVec.x + 0], vertices.v[stride * indicesVec.x + 1], vertices.v[stride * indicesVec.x + 2]);
+    const vec3 v1 = vec3(vertices.v[stride * indicesVec.y + 0], vertices.v[stride * indicesVec.y + 1], vertices.v[stride * indicesVec.y + 2]);
+    const vec3 v2 = vec3(vertices.v[stride * indicesVec.z + 0], vertices.v[stride * indicesVec.z + 1], vertices.v[stride * indicesVec.z + 2]);
 
     // use barycentric coordinates to compute intersection
     const vec3 barycentrics = vec3(1.0 - attributes.x - attributes.y, attributes.xy);
     const vec3 objectPosition = v0 * barycentrics.x + v1 * barycentrics.y + v2 * barycentrics.z;
 
-    payload.worldPosition = gl_ObjectToWorldEXT * vec4(objectPosition, 1.0);
+    payload.worldPosition = vec3(gl_ObjectToWorldEXT * obj.transform * vec4(objectPosition, 1));
 
     const vec3 objectNormal = cross(v1 - v0, v2 - v0);
+    const vec3 worldNormal = normalize(vec3(objectNormal * gl_WorldToObjectEXT * obj.transform));
 
-    payload.worldNormal = normalize((objectNormal * gl_WorldToObjectEXT).xyz);
-    payload.worldNormal = faceforward(payload.worldNormal, gl_WorldRayDirectionEXT, payload.worldNormal);
-
+    payload.worldNormal = worldNormal;
     payload.hitSky = 0.0f;
 }
diff --git a/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rgen b/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rgen
index 41061e0e..f0646e77 100644
--- a/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rgen
+++ b/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rgen
@@ -8,7 +8,6 @@ layout(location = 0) rayPayloadEXT Payload {
   float hitSky;
   vec3 worldPosition;
   vec3 worldNormal;
-  uvec4 hit;
 } payload;
 
 layout(binding = 0, set = 0, rgba16) uniform image2D outImg;            // the output image -> maybe use 16 bit values?
@@ -50,7 +49,7 @@ vec2 random(){
  * @param[in,out] pos The position of intersection
  * @param[in,out] norm The normal at the position of intersection
  */
-void TraceCameraRay(out bool hitSky, out vec3 pos, out vec3 norm, out uvec4 hit){
+void TraceCameraRay(out bool hitSky, out vec3 pos, out vec3 norm) {
   // Use a camera model to generate a ray for this pixel.
   vec2 uv = gl_LaunchIDEXT.xy + vec2(random()); // random breaks up aliasing
   uv /= vec2(gl_LaunchSizeEXT.xy);
@@ -77,10 +76,9 @@ void TraceCameraRay(out bool hitSky, out vec3 pos, out vec3 norm, out uvec4 hit)
               0);                   // Location of payload
 
   // Read the values from the payload:
-  hitSky    = (payload.hitSky > 0.0);
+  hitSky    = (payload.hitSky > 0.0f);
   pos       = payload.worldPosition;
   norm      = payload.worldNormal;
-  hit       = payload.hit;
 }
 
 /**
@@ -88,8 +86,8 @@ void TraceCameraRay(out bool hitSky, out vec3 pos, out vec3 norm, out uvec4 hit)
  * @param[in] orig The point of origin of the shadow ray.
  * @param[in] dir The direction of the shadow ray.
  */
-float CastShadowRay(vec3 orig, vec3 dir){
-  payload.hitSky = 0.0f;  // Assume ray is occluded
+float CastShadowRay(vec3 orig, vec3 dir) {
+  payload.hitSky = 0.0f;
   traceRayEXT(tlas,   // Acceleration structure
               gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT | gl_RayFlagsTerminateOnFirstHitEXT, // Ray flags, here saying "ignore any hit shaders and closest hit shaders, and terminate the ray on the first found intersection"
               0xFF,    // 8-bit instance mask, here saying "trace against all instances"
@@ -109,7 +107,8 @@ vec3 sampleCosineDistribution(vec2 xi){
 	return vec3(
 		sqrt(xi.x) * cos(phi),
 		sqrt(1 - xi.x),
-		sqrt(xi.x) * sin(phi));
+		sqrt(xi.x) * sin(phi)
+    );
 }
 
 struct Basis{
@@ -129,44 +128,39 @@ Basis buildBasisAroundNormal(vec3 N){
 
 vec3 sampleTangentToWorldSpace(vec3 tangentSpaceSample, vec3 N){
 	Basis tangentBasis = buildBasisAroundNormal(N);
-	return
+	return (
 		tangentBasis.right		* tangentSpaceSample.x +
 		tangentBasis.up			* tangentSpaceSample.y +
-		tangentBasis.forward 	* tangentSpaceSample.z;
+		tangentBasis.forward 	* tangentSpaceSample.z
+    );
 }
 
 void main(){
-    uint rayCount = 64;    // the amount of rays to be casted
+    uint rayCount = 16;    // the amount of rays to be casted
 
     initRandom(gl_LaunchIDEXT.xy);
 
     uvec2 pixel = gl_LaunchIDEXT.xy;
     bool pixelIsSky; // Does the pixel show the sky (not an object)?
     vec3 pos, norm;  // AO rays from where?
-    uvec4 hit;
-    TraceCameraRay(pixelIsSky, pos, norm, hit);
-    
-    if(pixelIsSky){
+    TraceCameraRay(pixelIsSky, pos, norm);
+
+    if (pixelIsSky){
         // Don't compute ambient occlusion for the sky
         imageStore(outImg, ivec2(pixel), vec4(0.8,0.8,0.8,1.0));
         return;
     }
 
-    imageStore(outImg, ivec2(pixel), vec4(vec3(
-        0,//float(hit.x) / 0xFFFF,
-        0,//float(hit.y) / 381,
-        hit.z > 0 || hit.w > 0? 1 : 0
-    ), 1));
-/*
     // Compute ambient occlusion
-    float aoValue = 0.0;
+    float aoValue = 0.0f;
     for(uint i = 0; i < rayCount; i++){
         vec3 sampleTangentSpace = sampleCosineDistribution(random());
         vec3 sampleWorldSpace   = sampleTangentToWorldSpace(sampleTangentSpace, norm);
-        aoValue                 += CastShadowRay(pos, sampleWorldSpace);
+
+        aoValue += CastShadowRay(pos, sampleWorldSpace);
     }
+
     aoValue /= rayCount;
     
     imageStore(outImg, ivec2(pixel), vec4(vec3(aoValue), 1));
-*/
 }
diff --git a/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rmiss b/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rmiss
index 5c97ae90..c107dbd0 100644
--- a/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rmiss
+++ b/projects/rt_ambient_occlusion/resources/shaders/ambientOcclusion.rmiss
@@ -5,7 +5,6 @@ layout(location = 0) rayPayloadInEXT Payload {
   float hitSky;
   vec3 worldPosition;
   vec3 worldNormal;
-  uvec4 hit;
 } payload;
 
 void main() {
diff --git a/projects/rt_ambient_occlusion/src/main.cpp b/projects/rt_ambient_occlusion/src/main.cpp
index c668ce40..4e587f2e 100644
--- a/projects/rt_ambient_occlusion/src/main.cpp
+++ b/projects/rt_ambient_occlusion/src/main.cpp
@@ -1,6 +1,5 @@
 #include <vkcv/Core.hpp>
 #include <vkcv/camera/CameraManager.hpp>
-#include <vkcv/geometry/Teapot.hpp>
 #include <vkcv/shader/GLSLCompiler.hpp>
 #include <vkcv/scene/Scene.hpp>
 
@@ -8,6 +7,16 @@
  * Note: This project is based on the following tutorial https://github.com/Apress/Ray-Tracing-Gems-II/tree/main/Chapter_16.
  */
 
+struct ObjDesc {
+	uint64_t vertexAddress;
+	uint64_t indexAddress;
+	uint32_t vertexStride;
+	uint32_t pad0;
+	uint32_t pad1;
+	uint32_t pad2;
+	glm::mat4 transform;
+};
+
 int main(int argc, const char** argv) {
 	const std::string applicationName = "Ray Tracing: Ambient Occlusion";
 	
@@ -42,6 +51,12 @@ int main(int argc, const char** argv) {
 			}
 	);
 	
+	features.requireFeature(
+			[](vk::PhysicalDeviceFeatures &features) {
+				features.setShaderInt64(true);
+			}
+	);
+	
 	vkcv::Core core = vkcv::Core::create(
 			applicationName,
 			VK_MAKE_VERSION(0, 0, 1),
@@ -53,15 +68,11 @@ int main(int argc, const char** argv) {
 	
 	vkcv::scene::Scene scene = vkcv::scene::Scene::load(
 			core,
-			"../first_scene/assets/Sponza/Sponza.gltf",
+			"resources/Sponza/Sponza.gltf",
 			{
 					vkcv::asset::PrimitiveType::POSITION
 			}
 	);
-	
-	vkcv::geometry::Teapot teapot (glm::vec3(0.0f), 1.0f);
-	vkcv::VertexData vertexData = teapot.generateVertexData(core);
-	vkcv::GeometryData geometryData = teapot.extractGeometryData(core, vertexData);
 
 	vkcv::camera::CameraManager cameraManager(core.getWindow(windowHandle));
 	auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
@@ -96,16 +107,54 @@ int main(int argc, const char** argv) {
 	descriptorSetHandles.push_back(shaderDescriptorSet);
 	descriptorSetLayoutHandles.push_back(shaderDescriptorSetLayout);
 	
-	vkcv::AccelerationStructureHandle blas = core.createAccelerationStructure({ geometryData });
-	vkcv::AccelerationStructureHandle tlas = core.createAccelerationStructure({ blas });
+	const uint32_t instanceCount = scene.getMeshCount();
+	const uint32_t geometryCount = scene.getMeshPartCount();
+	
+	std::vector<ObjDesc> objDescList;
+	objDescList.resize(instanceCount * static_cast<size_t>(
+			std::ceil(static_cast<float>(geometryCount) / instanceCount)
+	));
+	
+	vkcv::AccelerationStructureHandle scene_tlas = scene.createAccelerationStructure(
+			[&core, &objDescList, instanceCount](
+					size_t instanceIndex,
+					size_t geometryIndex,
+					const vkcv::GeometryData &geometry,
+					const glm::mat4 &transform
+			) {
+				ObjDesc obj {};
+				
+				obj.vertexAddress = core.getBufferDeviceAddress(
+						geometry.getVertexBufferBinding().m_buffer
+				);
+				
+				obj.indexAddress = core.getBufferDeviceAddress(
+						geometry.getIndexBuffer()
+				);
+				
+				obj.vertexStride = geometry.getVertexStride() / sizeof(float);
+				obj.transform = transform;
+				
+				objDescList[geometryIndex * instanceCount + instanceIndex] = obj;
+			}
+	);
+	
+	auto objDescBuffer = vkcv::buffer<ObjDesc>(
+			core,
+			vkcv::BufferType::STORAGE,
+			objDescList.size()
+	);
+	
+	objDescBuffer.fill(objDescList);
 	
-	vkcv::AccelerationStructureHandle scene_tlas = scene.createAccelerationStructure();
+	auto instanceCountBuffer = vkcv::buffer<uint32_t>(core, vkcv::BufferType::STORAGE, 1);
+	instanceCountBuffer.fill(&instanceCount);
 	
 	{
 		vkcv::DescriptorWrites writes;
 		writes.writeAcceleration(1, { scene_tlas });
-		writes.writeStorageBuffer(2, geometryData.getVertexBufferBinding().m_buffer);
-		writes.writeStorageBuffer(3, geometryData.getIndexBuffer());
+		writes.writeStorageBuffer(2, objDescBuffer.getHandle());
+		writes.writeStorageBuffer(3, instanceCountBuffer.getHandle());
 		core.writeDescriptorSet(descriptorSetHandles[0], writes);
 	}
 
diff --git a/src/vkcv/AccelerationStructureManager.cpp b/src/vkcv/AccelerationStructureManager.cpp
index 5a31fbd0..8c51a9ad 100644
--- a/src/vkcv/AccelerationStructureManager.cpp
+++ b/src/vkcv/AccelerationStructureManager.cpp
@@ -119,7 +119,7 @@ namespace vkcv {
 			Core& core,
 			BufferManager& bufferManager,
 			std::vector<vk::AccelerationStructureBuildGeometryInfoKHR> &geometryInfos,
-			const std::vector<std::vector<vk::AccelerationStructureBuildRangeInfoKHR>> &rangeInfos,
+			const std::vector<vk::AccelerationStructureBuildRangeInfoKHR> &rangeInfos,
 			size_t accelerationStructureSize,
 			size_t scratchBufferSize,
 			vk::AccelerationStructureTypeKHR accelerationStructureType) {
@@ -190,7 +190,7 @@ namespace vkcv {
 		pRangeInfos.resize(rangeInfos.size());
 		
 		for (size_t i = 0; i < rangeInfos.size(); i++) {
-			pRangeInfos[i] = rangeInfos[i].data();
+			pRangeInfos[i] = &(rangeInfos[i]);
 		}
 		
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Compute);
@@ -239,7 +239,7 @@ namespace vkcv {
 			const BufferHandle &transformBuffer) {
 		std::vector<vk::AccelerationStructureGeometryKHR> geometries;
 		std::vector<vk::AccelerationStructureBuildGeometryInfoKHR> geometryInfos;
-		std::vector<std::vector<vk::AccelerationStructureBuildRangeInfoKHR>> rangeInfos;
+		std::vector<vk::AccelerationStructureBuildRangeInfoKHR> rangeInfos;
 		
 		if (geometryData.empty()) {
 			return {};
@@ -267,6 +267,11 @@ namespace vkcv {
 		const auto &dynamicDispatch = getCore().getContext().getDispatchLoaderDynamic();
 		
 		geometries.reserve(geometryData.size());
+		rangeInfos.reserve(geometryData.size());
+		
+		std::vector<uint32_t> maxPrimitiveCount;
+		
+		maxPrimitiveCount.reserve(geometryData.size());
 		
 		for (const GeometryData &data : geometryData) {
 			const auto vertexBufferAddress = bufferManager.getBufferDeviceAddress(
@@ -300,46 +305,44 @@ namespace vkcv {
 			);
 			
 			geometries.push_back(asGeometry);
+			
+			const vk::AccelerationStructureBuildRangeInfoKHR asBuildRangeInfo (
+					static_cast<uint32_t>(data.getCount() / 3),
+					0,
+					0,
+					0
+			);
+			
+			rangeInfos.push_back(asBuildRangeInfo);
+			maxPrimitiveCount.push_back(asBuildRangeInfo.primitiveCount);
 		}
 		
-		geometryInfos.reserve(geometries.size());
-		rangeInfos.reserve(geometries.size());
-		
-		for (size_t i = 0; i < geometries.size(); i++) {
+		{
 			const vk::AccelerationStructureBuildGeometryInfoKHR asBuildGeometryInfo(
 					vk::AccelerationStructureTypeKHR::eBottomLevel,
 					vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace,
 					vk::BuildAccelerationStructureModeKHR::eBuild,
 					{},
 					{},
-					1,
-					&(geometries[i])
+					static_cast<uint32_t>(geometries.size()),
+					geometries.data()
 			);
 			
 			geometryInfos.push_back(asBuildGeometryInfo);
-			
-			const vk::AccelerationStructureBuildRangeInfoKHR asBuildRangeInfo (
-					static_cast<uint32_t>(geometryData[i].getCount() / 3),
-					0,
-					0,
-					0
-			);
-			
-			rangeInfos.push_back({ asBuildRangeInfo });
-			
-			vk::AccelerationStructureBuildSizesInfoKHR asBuildSizesInfo;
-			getCore().getContext().getDevice().getAccelerationStructureBuildSizesKHR(
-					vk::AccelerationStructureBuildTypeKHR::eDevice,
-					&(asBuildGeometryInfo),
-					&(asBuildRangeInfo.primitiveCount),
-					&(asBuildSizesInfo),
-					dynamicDispatch
-			);
-			
-			accelerationStructureSize += asBuildSizesInfo.accelerationStructureSize;
-			scratchBufferSize = std::max(scratchBufferSize, asBuildSizesInfo.buildScratchSize);
 		}
 		
+		vk::AccelerationStructureBuildSizesInfoKHR asBuildSizesInfo;
+		getCore().getContext().getDevice().getAccelerationStructureBuildSizesKHR(
+				vk::AccelerationStructureBuildTypeKHR::eDevice,
+				geometryInfos.data(),
+				maxPrimitiveCount.data(),
+				&(asBuildSizesInfo),
+				dynamicDispatch
+		);
+		
+		accelerationStructureSize += asBuildSizesInfo.accelerationStructureSize;
+		scratchBufferSize = std::max(scratchBufferSize, asBuildSizesInfo.buildScratchSize);
+		
 		const auto entry = buildAccelerationStructure(
 				getCore(),
 				bufferManager,
@@ -485,7 +488,7 @@ namespace vkcv {
 				getCore(),
 				bufferManager,
 				asBuildGeometryInfos,
-				{ { asBuildRangeInfo } },
+				{ asBuildRangeInfo },
 				asBuildSizesInfo.accelerationStructureSize,
 				asBuildSizesInfo.buildScratchSize,
 				vk::AccelerationStructureTypeKHR::eTopLevel
-- 
GitLab