struct Geometry { glm::vec4 signature; glm::vec4 vertex1_or_sphere_center; glm::vec4 vertex2_or_sphere_radius; glm::vec4 vertex3_or_nothing; }; struct GeometryCollection { Geometry geometry[1 << expOfTwoOfMaxGeometryElementsInCollection]; // geometry slots }; struct AABB { glm::vec4 position; // this is the point of the aabb with min x, y and z components glm::vec4 dimensions; // this is the amount to be added to aabbVertex to obtain the point with max x, y and z in the aabb }; struct alignas(16) NodeData { AABB aabb; alignas(sizeof(glm::uint32)) glm::uint32 left; // this can also be the content if right is zero alignas(sizeof(glm::uint32))glm::uint32 right; // this is zero if the node is a leaf }; struct BLAS { glm::mat4 ModelMatrix; GeometryCollection collection[1 << expOfTwoOfMaxCollectionElementsInBLAS]; NodeData tree[((1 << expOfTwoOfMaxCollectionElementsInBLAS) * 2) - 1]; }; struct TLAS { glm::mat4 ViewMatrix; NodeData tree[((1 << expOfTwoOfMaxBLASElementsInTLAS) * 2) - 1]; BLAS blas[1 << expOfTwoOfMaxBLASElementsInTLAS]; }; /******************************************************************************************/ #version 450 #define COMP_SIZE 32 #define expOfTwoOfMaxGeometryElementsInCollection 3 #define expOfTwoOfMaxCollectionElementsInBLAS 5 #define expOfTwoOfMaxBLASElementsInTLAS 5 const float PI = 3.14159265359; const float infinity = 1. / 0.; const mat4 identityTransform = mat4(1); // This gets adjusted on each call by the OpenGL core layout(local_size_x = COMP_SIZE, local_size_y = COMP_SIZE, local_size_z = 1) in; layout(rgba32f, binding = 0) uniform image2D img_output; uniform uint width; // Render target surface width uniform uint height; // Render target surface height struct Geometry { vec4 signature; vec4 vertex1_or_sphere_center; vec4 vertex2_or_sphere_radius; vec4 vertex3_or_nothing; }; struct GeometryCollection { Geometry geometry[1 << expOfTwoOfMaxGeometryElementsInCollection]; // geometry slots }; struct AABB { vec4 position; // this is the point of the aabb with min x, y and z components vec4 dimensions; // this is the amount to be added to aabbVertex to obtain the point with max x, y and z in the aabb }; struct NodeData { AABB aabb; uint left; // this can also be the content if right is zero uint right; // this is zero if the node is a leaf }; struct BLAS { mat4 ModelMatrix; GeometryCollection collection[1 << expOfTwoOfMaxCollectionElementsInBLAS]; NodeData tree[((1 << expOfTwoOfMaxCollectionElementsInBLAS) * 2) - 1]; }; struct TLAS { mat4 ViewMatrix; NodeData tree[((1 << expOfTwoOfMaxBLASElementsInTLAS) * 2) - 1]; BLAS blas[1 << expOfTwoOfMaxBLASElementsInTLAS]; }; // Output buffer object to be written layout(std430, binding = 3) buffer scene { TLAS tlas[]; }; uniform uint shadingAlgorithm; uniform vec3 mOrigin; uniform vec3 mLowerLeftCorner; uniform vec3 mHorizontal; uniform vec3 mVertical; /************************************************************************************************************************* * Algorithms * *************************************************************************************************************************/ struct RayGeometryIntersection { float dist; vec4 point; vec4 normal; }; struct Ray { vec4 origin; vec4 direction; }; // Empty geometry is a sphere with radius equals to 0.0 such geometry CANNOT be intersected const Geometry emptyGeometry = Geometry( vec4(0, 0, 0, 0), vec4(0, 0, 0, 0), vec4(0, 0, 0, 0), vec4(0, 0, 0, 0) ); const RayGeometryIntersection miss = RayGeometryIntersection(infinity, vec4(0, 0, 0, 1), vec4(0, 0, 0, 0)); bool isLeaf(NodeData node) { return node.right == 0; } bool hasMissed(RayGeometryIntersection test) { return isinf(test.dist) || isnan(test.dist); } vec3 rayPointAt(Ray ray, float coeff) { return vec3(ray.origin) + coeff * vec3(ray.direction); } vec3 applyShading(RayGeometryIntersection isect, uint algorithm) { if (algorithm == 0) { // hit or miss return hasMissed(isect) ? vec3(0,0,0) : vec3(1,1,1); } else if (algorithm == 1) { // Here the camera position is vec3(0, 0, 0) because all of this is in camera space return vec3(max(0, dot(isect.normal, normalize(/*mPosition*/ -isect.point)))); } return vec3(0.5, 0.5, 0.5); // this is to get attention as this should never verify } vec4 getInvDirection(Ray ray) { return vec4(1.0/ray.direction.x, 1.0/ray.direction.y, 1.0/ray.direction.z, 0); } AABB transformAABB(AABB starting, mat4 transformMatrix) { vec4 v[8] = { vec4(starting.position.x, starting.position.y, starting.position.z, 1), vec4(starting.position.x, starting.position.y, starting.position.z, 1), vec4(starting.position.x, starting.position.y, starting.position.z, 1), vec4(starting.position.x, starting.position.y, starting.position.z, 1), vec4(starting.position.x, starting.position.y, starting.position.z, 1), vec4(starting.position.x, starting.position.y, starting.position.z, 1), vec4(starting.position.x, starting.position.y, starting.position.z, 1), vec4(starting.position.x, starting.position.y, starting.position.z, 1), }; float maxX = 1.0/0.0, maxY = 1.0/0.0, maxZ=1.0/0.0, minX=-1.0/0.0, minY=-1.0/0.0, minZ=-1.0/0.0; for (uint i = 0; i < 8; ++i) { maxX = (v[i].x > maxX) ? v[i].x : maxX; maxY = (v[i].y > maxY) ? v[i].y : maxY; maxZ = (v[i].z > maxZ) ? v[i].z : maxZ; minX = (v[i].x < minX) ? v[i].x : minX; minY = (v[i].y < minY) ? v[i].y : minY; minZ = (v[i].z < minZ) ? v[i].z : minZ; } return AABB(vec4(minX, minY, minZ, 1), vec4(maxX-minX, maxY-minY, maxZ-minZ, 0)); } struct raySigns { bool[3] signs; }; bool intersectAABB(Ray ray, AABB aabb, mat4 transformMatrix) { AABB transformedAABB = /*(transformMatrix != identityTransform) ? */transformAABB(aabb, transformMatrix) /*: *this*/; float tmin, tmax, tymin, tymax, tzmin, tzmax; vec3 orig = ray.origin.xyz; vec3 invdir = getInvDirection(ray).xyz; vec3 bounds[2] = { transformedAABB.position.xyz, (transformedAABB.position.xyz) + (transformedAABB.dimensions.xyz) }; const int[3] raySigns = int[3]( (invdir.x < 0) ? 1 : 0, (invdir.y < 0) ? 1 : 0, (invdir.z < 0) ? 1 : 0 ); tmin = (bounds[raySigns[0]].x - orig.x) * invdir.x; tmax = (bounds[1 - raySigns[0]].x - orig.x) * invdir.x; tymin = (bounds[raySigns[1]].y - orig.y) * invdir.y; tymax = (bounds[1 - raySigns[1]].y - orig.y) * invdir.y; if ((tmin > tymax) || (tymin > tmax)) return false; if (tymin > tmin) tmin = tymin; if (tymax < tmax) tmax = tymax; tzmin = (bounds[raySigns[2]].z - orig.z) * invdir.z; tzmax = (bounds[1 - raySigns[2]].z - orig.z) * invdir.z; if ((tmin > tzmax) || (tzmin > tmax)) return false; if (tzmin > tmin) tmin = tzmin; if (tzmax < tmax) tmax = tzmax; return true; } RayGeometryIntersection intersect(Ray ray, Geometry geometry, mat4 transformMatrix, float minDistance, float maxDistance) { if (geometry.signature.w == 0.0) { // test ray-sphere intersection const vec3 center = vec3(transformMatrix * geometry.vertex1_or_sphere_center); const float radius = geometry.vertex2_or_sphere_radius.x; if (radius == 0) return miss; const vec3 origin = vec3(ray.origin); const vec3 direction = vec3(ray.direction); vec3 oc = origin - center; const float a = dot(direction, direction); const float b = dot(oc, direction); const float c = dot(oc, oc) - radius * radius; const float discriminant = b * b - a * c; if (discriminant > 0.0) { // if delta > 0 then we have two intersections (one for each side of the sphere) const float squareRoot = sqrt(discriminant); // x0 and x1 are the distances to the origin of the ray float x0 = (-b - squareRoot) / a, x1 = (-b + squareRoot) / a; // Use x0 and x1 to calculate intersection points vec3 point_x0 = rayPointAt(ray, x0), point_x1 = rayPointAt(ray, x1); // so if I use that distance as a coefficient I obtain the intersection point // Use intersecting points to calculate the surface normal at that point vec3 normal_x0 = (point_x0 - center) / radius, normal_x1 = (point_x1 - center) / radius; // and the normal for a point p is (point - center)/radius // No valid intersection? return a miss. if (((x0 <= minDistance) || (x0 >= maxDistance)) && ((x1 <= minDistance) || (x1 >= maxDistance))) { return miss; } // Choose the closest intersection point return (((x0 > minDistance) && (x0 < maxDistance)) && (x0 <= x1)) || ((x1 <= minDistance) || (x1 >= maxDistance)) ? RayGeometryIntersection(x0, vec4(point_x0, 1), vec4(normal_x0, 0)) : RayGeometryIntersection(x1, vec4(point_x1, 1), vec4(normal_x1, 0)); } } else if (geometry.signature.w == 1.0) { // test ray-triangle intersection } return miss; } RayGeometryIntersection intersect(Ray ray, GeometryCollection collection, mat4 transformMatrix, float minDistance, float maxDistance) { RayGeometryIntersection bestHit = miss; for (uint i = 0; i < collection.geometry.length(); ++i) { // Execute the ray-geometry intersection algorithm const RayGeometryIntersection currentIntersectionInfo = intersect(ray, collection.geometry[i], transformMatrix, minDistance, maxDistance); // Check if this is a better hit than the former one if (currentIntersectionInfo.dist < bestHit.dist) { bestHit = currentIntersectionInfo; } } return bestHit; } RayGeometryIntersection intersect(Ray ray, BLAS blas, mat4 transformMatrix, float minDistance, float maxDistance) { // Adjust transformation matrix to consider the BLAS model matrix transformMatrix = transformMatrix * blas.ModelMatrix; RayGeometryIntersection bestHit = miss; int currentDepth = 0; uint currentPath = 0; while ((currentDepth < 32) && (currentDepth >= 0)) { uint currentNodeIndex = 0; // This for cycles is used to reach the interesting node for (uint i = 0; i < currentDepth; ++i) { currentNodeIndex = ((currentPath & (1 << currentDepth)) == 0) ? blas.tree[currentNodeIndex].left : blas.tree[currentNodeIndex].left; } if ((!isLeaf(blas.tree[currentNodeIndex])) && (intersectAABB(ray, blas.tree[currentNodeIndex].aabb, transformMatrix))) { // Go one level deeper currentDepth += 1; } else { // This is a leaf that should be tested... if ((isLeaf(blas.tree[currentNodeIndex])) && (intersectAABB(ray, blas.tree[currentNodeIndex].aabb, transformMatrix))) { RayGeometryIntersection currentHit = intersect(ray, blas.collection[blas.tree[currentNodeIndex].left], transformMatrix, minDistance, maxDistance); if (currentHit.dist < bestHit.dist) bestHit = currentHit; } // in either case: subtree discarded or visited leaf the algorithm should reduce the depth and change the path currentDepth -= 1; while (((currentPath & (1 << currentDepth)) == 1) && (currentDepth >= 0)) { //set the bit at currentDepth position to 0 currentPath &= ~(1 << currentDepth); // and decrease currentDepth currentDepth -= 1; } if (currentDepth >= 0) { // set the bit at currentDepth position to 1 (the next visit will choose another path) currentPath |= (1 << currentDepth); } else { // the visited node was the very last one (the right-most) and the iteration should come to an end break; } } } return bestHit; } RayGeometryIntersection intersect(Ray ray, TLAS tlas, mat4 transformMatrix, float minDistance, float maxDistance) { // Adjust transformation matrix to consider the BLAS model matrix transformMatrix = transformMatrix * tlas.ViewMatrix; RayGeometryIntersection bestHit = miss; int currentDepth = 0; uint currentPath = 0; while ((currentDepth < 32) && (currentDepth >= 0)) { uint currentNodeIndex = 0; // This for cycles is used to reach the interesting node for (uint i = 0; i < currentDepth; ++i) { currentNodeIndex = ((currentPath & (1 << currentDepth)) == 0) ? tlas.tree[currentNodeIndex].left : tlas.tree[currentNodeIndex].left; } if ((!isLeaf(tlas.tree[currentNodeIndex])) && (intersectAABB(ray, tlas.tree[currentNodeIndex].aabb, transformMatrix))) { // Go one level deeper currentDepth += 1; } else { // This is a leaf that should be tested... if ((isLeaf(tlas.tree[currentNodeIndex])) && (intersectAABB(ray, tlas.tree[currentNodeIndex].aabb, transformMatrix))) { RayGeometryIntersection currentHit = intersect(ray, tlas.blas[tlas.tree[currentNodeIndex].left], transformMatrix, minDistance, maxDistance); if (currentHit.dist < bestHit.dist) bestHit = currentHit; } // in either case: subtree discarded or visited leaf the algorithm should reduce the depth and change the path currentDepth -= 1; while (((currentPath & (1 << currentDepth)) == 1) && (currentDepth >= 0)) { //set the bit at currentDepth position to 0 currentPath &= ~(1 << currentDepth); // and decrease currentDepth currentDepth -= 1; } if (currentDepth >= 0) { // set the bit at currentDepth position to 1 (the next visit will choose another path) currentPath |= (1 << currentDepth); } else { // the visited node was the very last one (the right-most) and the iteration should come to an end break; } } } return bestHit; } Ray generateCameraRay(float s, float t) { return Ray(vec4(mOrigin, 1), vec4(mLowerLeftCorner + s * mHorizontal + t * mVertical - mOrigin, 0)); } void main() { // base pixel colour for image vec4 pixel = vec4(0, 0, 0, 0); // get index in global work group i.e x,y position ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); // Avoid calculating useless pixels if ((gl_GlobalInvocationID.x >= width) || (gl_GlobalInvocationID.y >= height)) { return; } const float u = float(gl_GlobalInvocationID.x) / float(width); const float v = float(gl_GlobalInvocationID.y) / float(height); // Generate camera ray Ray cameraRay = generateCameraRay(u, v); pixel = vec4(applyShading(intersect(cameraRay, tlas[0], identityTransform, 0.001, 1000.0), shadingAlgorithm), 1.0); // output to a specific pixel in the image imageStore(img_output, pixel_coords, pixel); }