#version 430 core #define PI 3.1415926538f layout(local_size_x = 8, local_size_y = 8) in; layout(rgba32f) uniform image2D img_output; uniform sampler2D u_skybox; uniform sampler2D u_tex1; uniform sampler2D u_tex2; uniform sampler2D u_tex3; uniform sampler2D u_tex4; uniform sampler2D u_tex5; precision highp float; struct SceneObject{ vec4 position; vec4 size; vec4 orientation; vec4 albedo; vec4 specular; vec4 emission; ivec4 texture_id; float refractive_index; float transparency; float smoothness; int type; int offset; int vert_num; int obj_id; int padding; }; struct Ray{ vec3 origin; vec3 direction; vec3 color; }; layout(std140, binding=2) uniform shader_data{ vec4 _camera_position; // 4 4 vec4 _camera_rotation; // 4 8 vec2 _pixel_offset; // 2 10 ivec2 _screen_size; // 2 12 int _iterations; // 1 13 float _seed; // 1 14 int _object_count; // 1 15 int _sample; // 1 16 int _samples_per_frame; // 1 17 int _fov; // 1 18 vec2 padding; // 2 20 }; layout (std140, binding = 3) uniform object_data{ SceneObject objects[1]; }; layout (std430, binding = 4) buffer index_buffer{ int faces[]; }; layout (std430, binding = 5) buffer vert_buffer{ float vertices[]; }; layout (std430, binding = 6) buffer texture_vert_buffer{ float t_vertices[]; }; uniform float u_gamma; float distlimit = 10000.0f; float seed = _seed; vec2 pixel_id; vec3 DegToRad(vec3 vect){ return vect * float(PI)/180.0f; } float saturate(float value){ return clamp(value,0.0,1.0); } float sdot(vec3 x, vec3 y){ float f = 1.0f; return saturate(dot(x, y) * f); } float sdot(vec3 x, vec3 y, float f){ return saturate(dot(x, y) * f); } // A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm. uint hash( uint x ) { x += ( x << 10u ); x ^= ( x >> 6u ); x += ( x << 3u ); x ^= ( x >> 11u ); x += ( x << 15u ); return x; } uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ); } // Construct a float with half-open range [0:1] using low 23 bits. // All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0. float floatConstruct( uint m ) { const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32 m &= ieeeMantissa; // Keep only mantissa bits (fractional part) m |= ieeeOne; // Add fractional part to 1.0 float f = uintBitsToFloat( m ); // Range [1:2] return f - 1.0; // Range [0:1] } // Pseudo-random value in half-open range [0:1]. float Rand() { seed += 1.0f; return floatConstruct(hash(floatBitsToUint(vec3(pixel_id, seed)))); } float SmoothnessToPhongAlpha(float s){ return pow(1000.0f, s * s); } mat3x3 GetTangentSpace(vec3 n){ // Choose a helper vector for the cross product vec3 helper = vec3(1, 0, 0); if (abs(n.x) > 0.99f) helper = vec3(0, 0, 1); // Generate vectors vec3 t = normalize(cross(n, helper)); vec3 b = normalize(cross(n, t)); return mat3x3( t.x, b.x, n.x, t.y, b.y, n.y, t.z, b.z, n.z ); } vec3 SampleHemisphere(vec3 normal, float alpha){ // Sample the hemisphere, where alpha determines the kind of the sampling float cosTheta = pow(Rand(), 1.0f / (alpha + 1.0f)); float sinTheta = sqrt(1.0f - cosTheta * cosTheta); float phi = 2 * PI * Rand(); vec3 tangentSpaceDir = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); // Transform direction to world space return normalize(tangentSpaceDir * GetTangentSpace(normal)); } vec3 RefractRay(vec3 direction, vec3 n, float n1, float n2){ vec3 new_direction; direction = normalize(direction); n = normalize(n); float dotp = dot(n, -direction); float ratio = n1/n2; vec3 e1 = (ratio * direction); // n1/n2 * i float e2 = (ratio * dotp); // n1/n2 * cosB float e3 = (ratio * ratio) * (1.0f - (dotp * dotp)); // (n1/n2)^2 * (1 - cosB^2) new_direction = e1 + ((e2 - sqrt(1.0f - e3)) * n); // n1/n2 * i + (n1/n2 * cosB - /(1 - sinT^2) * n return normalize(new_direction); } float GetReflectance(vec3 direction, vec3 normal, float n1, float n2){ float raycast; float cosI = dot(-direction, normal); float ratio = n1/n2; float sin2T = (ratio * ratio) * (1.0f - (cosI * cosI)); if(sin2T <= 1.0f){ // Reflection or Transmission float cosT = sqrt(1.0f - sin2T); float R1 = pow((n1 * cosI - n2 * cosT)/(n1 * cosI + n2 * cosT), 2); float R2 = pow((n2 * cosI - n1 * cosT)/(n2 * cosI + n1 * cosT), 2); float Ravg = (R1 + R2) / 2.0f; raycast = Ravg; }else{ // Total Internal Reflection raycast = 1.0f; } return raycast; } vec3 RotateVector(vec3 vect, vec3 rotation){ rotation = DegToRad(rotation); float xr = rotation.x; float yr = rotation.y; float zr = rotation.z; mat3x3 Rx = mat3x3( 1, 0, 0, 0, cos(xr), -sin(xr), 0, sin(xr), cos(xr) ); mat3x3 Ry = mat3x3( cos(yr), 0, sin(yr), 0, 1, 0, -sin(yr), 0, cos(yr) ); mat3x3 Rz = mat3x3( cos(zr), -sin(zr), 0, sin(zr), cos(zr), 0, 0, 0, 1 ); return (((Ry*Rx)*Rz)*vect); } Ray CreateRay(vec3 origin, vec3 direction){ Ray ray; ray.origin = origin; ray.direction = normalize(direction); return ray; } Ray CreateCameraRay(float x, float y){ float n = 0.1f; float fov_v = (float(_fov)/2.0f) * (PI/180.0f); float ratio = (float(_screen_size.x)/float(_screen_size.y)); float fov_h = atan( tan(fov_v) * ratio ); x = (x - 0.5f) * 2; y = (y - 0.5f) * 2; vec3 plane = vec3(x * tan(fov_h) * n,y * tan(fov_v) * n,n); vec3 direction = RotateVector(normalize(plane),_camera_rotation.xyz); return CreateRay(_camera_position.xyz,direction); } vec4 CheckIntersectionWithSphere(vec3 r1, vec3 r2, int i){ vec3 oc = r1 - objects[i].position.xyz; float a = dot(r2,r2); float b = 2.0f * dot(oc, r2); float c = dot(oc, oc) - objects[i].size.x * objects[i].size.x; float p = c/a; float q = -b/a; float d = b * b - (4 * a * c); if(p < 0){ //inside float val = (-b + sqrt(d)) / (2.0f*a); vec3 hit = r1 + r2 * val; return vec4(r1 + r2 * val, val); }else{ if(q > 0){ //hit float val = (-b - sqrt(d)) / (2.0f*a); vec3 hit = r1 + r2 * val; return vec4(r1 + r2 * val, val); }else{ //not hit return vec4(vec3(0,0,0),-1.0f); } } } vec4 CheckIntersectionWithPlane(vec3 r1, vec3 r2, int i){ vec3 normal = normalize(RotateVector(vec3(0,1,0), objects[i].orientation.xyz)); float dotP = dot(normal, r2); vec3 pos = objects[i].position.xyz; int ans = int(abs(dotP) >= 0.001f); float d = dot((pos - r1),normal)/dotP; return vec4(r1 + r2 * d, ans * (d + 1) - 1); if(abs(dotP) >= 0.001f){ float d = dot((pos - r1),normal)/dotP; if(d < 0) return vec4(vec3(0,0,0),-1.0f); return vec4(r1 + r2 * d, d); } return vec4(vec3(0,0,0),-1.0f); } vec3 GetTriangleVertex(int id, int object_id){ vec3 pos = objects[object_id].position.xyz; vec3 size = objects[object_id].size.xyz; vec3 rotation = objects[object_id].orientation.xyz; id = id * 3; vec3 vertex_from_array = vec3(vertices[id], vertices[id+1], vertices[id+2]); vec3 vert = RotateVector(vertex_from_array * size, rotation); return vert + pos; } vec2 GetTriangleUV(int id){ id = id * 2; vec2 vertex_from_array = vec2(t_vertices[id], t_vertices[id+1]); return vertex_from_array; } vec4 CheckIntersectionWithTriangleUV(vec3 r1, vec3 r2, int face_id, int object_id, inout vec2 uv, inout vec3 normal){ const float EPSILON = 0.0000001; vec3 pos = objects[object_id].position.xyz; vec3 size = objects[object_id].size.xyz; vec3 vertex0 = GetTriangleVertex(faces[(face_id * 6) + 0], object_id); vec3 vertex1 = GetTriangleVertex(faces[(face_id * 6) + 1], object_id); vec3 vertex2 = GetTriangleVertex(faces[(face_id * 6) + 2], object_id); vec3 edge1, edge2, h, s, q; float a,f; edge1 = vertex1 - vertex0; edge2 = vertex2 - vertex0; h = cross(r2, edge2); vec3 N = cross(edge1,edge2); // N bool reverse = dot(N, r2) > 0; normal = (reverse ? -1 : 1) * normalize(N); a = dot(edge1, h); if (a > -EPSILON && a < EPSILON) return vec4(0.0f, 0.0f, 0.0f, -1.0f); // This ray is parallel to this triangle. f = 1.0/a; s = r1 - vertex0; uv.x = f * dot(s, h); if (uv.x < 0.0 || uv.x > 1.0) return vec4(0.0f, 0.0f, 0.0f, -1.0f); // This ray is parallel to this triangle. q = cross(s, edge1); uv.y = f * dot(r2, q); if (uv.y < 0.0 || uv.x + uv.y > 1.0) return vec4(0.0f, 0.0f, 0.0f, -1.0f); // This ray is parallel to this triangle. // At this stage we can compute t to find out where the intersection point is on the line. float t = f * dot(edge2, q); if (t > EPSILON) // ray intersection { // map uv float u = uv.x; float v = uv.y; float w = 1.0 - uv.x - uv.y; // A(v0) - w // B(v1) - u // C(v2) - v vec2 uv0 = GetTriangleUV(faces[(face_id * 6) + 3]); vec2 uv1 = GetTriangleUV(faces[(face_id * 6) + 4]); vec2 uv2 = GetTriangleUV(faces[(face_id * 6) + 5]); uv = uv0 * w + uv1 * u + uv2 * v; return vec4(r1 + r2 * t, t); // This ray is parallel to this triangle. } else // This means that there is a line intersection but not a ray intersection. return vec4(0.0f, 0.0f, 0.0f, -1.0f); // This ray is parallel to this triangle. } vec4 CheckIntersectionWithMesh(vec3 r1, vec3 r2, int object_id, inout vec3 normal, inout vec2 uv){ vec4 raycast; float val = 100000.0f; vec3 hit_point; vec3 temp_normal; vec2 temp_uv; int vert_num = objects[object_id].vert_num; int offset = objects[object_id].offset; for(int i = 0; i < vert_num; i++){ raycast = CheckIntersectionWithTriangleUV(r1, r2, offset + i, object_id, temp_uv, temp_normal); if (raycast.w >= 0 && raycast.w < val){ hit_point = raycast.xyz; val = raycast.w; normal = temp_normal; uv = temp_uv; } } return vec4(hit_point, val); } float GetEnergy(vec3 color){ return dot(color, vec3(1.0f / 3.0f)); } vec3 GetTextureColor(vec2 uv, int object_id, int map){ int id = objects[object_id].texture_id[map]; switch(id){ case 0: return vec3(1.0f,1.0f,1.0f); case 1: return texture(u_tex1, uv).xyz; case 2: return texture(u_tex2, uv).xyz; case 3: return texture(u_tex3, uv).xyz; case 4: return texture(u_tex4, uv).xyz; case 5: return texture(u_tex5, uv).xyz; default: return vec3(1.0f,1.0f,1.0f); } } vec3 GetNormal_old(vec3 pos, int object_id){ vec3 normal = vec3(0,1,0); if(objects[object_id].type == 0) //sphere normal = normalize(pos - objects[object_id].position.xyz); else if(objects[object_id].type == 1) //plane normal = normalize(RotateVector(vec3(0,1,0), objects[object_id].orientation.xyz)); else if(objects[object_id].type == 2){ //mesh int v_offset = objects[object_id].offset; vec3 v0 = GetTriangleVertex(faces[(v_offset * 6) + 0], object_id); vec3 v1 = GetTriangleVertex(faces[(v_offset * 6) + 1], object_id); vec3 v2 = GetTriangleVertex(faces[(v_offset * 6) + 2], object_id); vec3 A = v1 - v0; // edge 0 vec3 B = v2 - v0; // edge 1 normal = normalize(cross(A, B)); } return normal; } vec3 GetNormal(vec3 pos, vec3 dir, int object_id){ vec3 normal = vec3(0,1,0); if(objects[object_id].type == 0) //sphere normal = normalize(pos - objects[object_id].position.xyz); else if(objects[object_id].type == 1) //plane normal = normalize(RotateVector(vec3(0,1,0), objects[object_id].orientation.xyz)); else if(objects[object_id].type == 2){ //mesh int v_offset = objects[object_id].offset; vec3 v0 = GetTriangleVertex(faces[(v_offset * 6) + 0], object_id); vec3 v1 = GetTriangleVertex(faces[(v_offset * 6) + 1], object_id); vec3 v2 = GetTriangleVertex(faces[(v_offset * 6) + 2], object_id); vec3 A = v1 - v0; // edge 0 vec3 B = v2 - v0; // edge 1 normal = normalize(cross(A, B)); } bool reverse = dot(normal, dir) > 0; normal = (reverse ? -1 : 1) * normalize(normal); return normal; } vec3 GetNormal(vec3 pos, vec3 dir, int object_id, inout bool reversed){ vec3 normal = vec3(0,1,0); if(objects[object_id].type == 0) //sphere normal = normalize(pos - objects[object_id].position.xyz); else if(objects[object_id].type == 1) //plane normal = normalize(RotateVector(vec3(0,1,0), objects[object_id].orientation.xyz)); else if(objects[object_id].type == 2){ //mesh int v_offset = objects[object_id].offset; vec3 v0 = GetTriangleVertex(faces[(v_offset * 6) + 0], object_id); vec3 v1 = GetTriangleVertex(faces[(v_offset * 6) + 1], object_id); vec3 v2 = GetTriangleVertex(faces[(v_offset * 6) + 2], object_id); vec3 A = v1 - v0; // edge 0 vec3 B = v2 - v0; // edge 1 normal = normalize(cross(A, B)); } reversed = dot(normal, dir) > 0; normal = (reversed ? -1 : 1) * normalize(normal); return normal; } vec4 CastRay(Ray ray, inout vec3 normal, inout vec2 uv){ int object_id = -1; vec4 raycast; float val = distlimit; vec3 hit_point; vec3 temp_normal; vec2 temp_uv; for(int i = 0; i < _object_count; i++){ vec3 r1 = ray.origin; vec3 r2 = ray.direction; vec3 op = objects[i].position.xyz; float r = objects[i].size.x; if(objects[i].type == 0){ raycast = CheckIntersectionWithSphere(r1,r2,i); if(raycast.w > 0){ bool reversed; temp_normal = GetNormal(raycast.xyz,r2,i,reversed); float theta = 1.0f - (acos((reversed ? -1 : 1) * temp_normal.y) / PI); float phi = atan((reversed ? -1 : 1) * temp_normal.z, (reversed ? -1 : 1) * temp_normal.x) / (2 * PI) + 0.5f; temp_uv.x = mod((phi + (objects[i].orientation[1] / (2.0 * PI))), 1); temp_uv.y = theta; } } else if(objects[i].type == 1){ raycast = CheckIntersectionWithPlane(r1,r2,i); if(raycast.w > 0) temp_normal = GetNormal(raycast.xyz,r2, i); } else if(objects[i].type == 2){ raycast = CheckIntersectionWithMesh(r1,r2,i, temp_normal, temp_uv); } else raycast = vec4(0,0,0,distlimit); if (raycast.w >= 0 && raycast.w < val){ hit_point = raycast.xyz; val = raycast.w; object_id = i; normal = temp_normal; uv = temp_uv; } } return vec4(hit_point,object_id); } vec3 GetSkyColor(vec3 direction){ float theta = 1.0f - (acos(direction.y) / PI); float phi = atan(direction.x, direction.z) / (2 * PI) + 0.5f; return texture(u_skybox, vec2(phi, theta)).xyz; } void main(){ ivec2 id = ivec2(gl_GlobalInvocationID.xy); vec4 pixel = vec4(0.0f, 0.0f, 0.0f, 1.0f); vec4 raycast; vec4 raycast_n; vec3 color; vec3 reflection; vec3 normal; vec3 albedo; vec3 specular; vec3 energy; vec2 offset = _pixel_offset; vec2 uv; Ray reflection_ray; Ray camera_ray; float x, y; float diffuse_chance; float specular_chance; float refraction_chance; float sum; float roulette; float f; float alpha; float dist; float air_transparency = 1.0f; int face_id; int object_id; bool inside_object[100]; float refractive_indices_queue[100]; refractive_indices_queue[0] = air_transparency; int refractive_indices_count = 1; for(int i = 0; i < 100; i++){ inside_object[i] = false; } pixel_id = id; for(int s = 0; s < _samples_per_frame; s++){ // map x,y to [0,1] x = (((float(id.x) - 0.5f + offset.x)/_screen_size.x)); y = (((float(id.y) - 0.5f + offset.y)/_screen_size.y)); // new random offset offset = vec2(1.0f * (abs(Rand()) - 0.5f) ,1.0f * (abs(Rand()) - 0.5f)); color = vec3(0.0f, 0.0f, 0.0f); // get pixel for random generator pixel_id = vec2(id) + offset; // create a ray and cast it camera_ray = CreateCameraRay(x,y); raycast = CastRay(camera_ray, normal, uv); object_id = int(raycast.w); // if hit any object if(object_id >= 0){ // calculate distance between rays dist = length(_camera_position.xyz - raycast.xyz); // initial energy values energy = vec3(1.0f, 1.0f, 1.0f); // add emmission from map color += objects[object_id].emission.xyz * GetTextureColor(uv, object_id, 3); //color += normal; reflection_ray = camera_ray; // recast ray multiple times for(int i = 0; i < _iterations; i++){ roulette = Rand(); // get specular and albedo specular = objects[object_id].specular.xyz * GetTextureColor(uv, object_id, 1); albedo = min(1.0f - specular, objects[object_id].albedo.xyz * GetTextureColor(uv, object_id, 0)); // check if object is translucent bool translucent = (objects[object_id].transparency > 0.01f); if(translucent){ // refractive indicies float n1; float n2; // 0 -> 1 if(!inside_object[object_id]){ // n1 is set to last index n1 = refractive_indices_queue[refractive_indices_count-1]; // n2 is set to new object n2 = objects[object_id].refractive_index; } else{ // 1 -> 0 // n1 is set to last index n1 = refractive_indices_queue[refractive_indices_count-1]; // n2 is set to the one before that n2 = refractive_indices_queue[refractive_indices_count-2]; } f = 1.0f; if(objects[object_id].smoothness <= 0.9999f){ alpha = SmoothnessToPhongAlpha(objects[object_id].smoothness); normal = SampleHemisphere(normal, alpha); f = (alpha + 2) / (alpha + 1); } // calculate reflection and transmission float val_R = GetReflectance(reflection_ray.direction.xyz, normal, n1, n2); float val_T = 1.0f - val_R; bool refract = (roulette < val_T); bool entering = !inside_object[object_id]; // update queue if(refract){ inside_object[object_id] = !inside_object[object_id]; reflection = RefractRay(reflection_ray.direction, normal, n1, n2); if(entering){ // add n2 to queue refractive_indices_count++; refractive_indices_queue[refractive_indices_count - 1] = n2; }else{ // leaving (1 -> 0) refractive_indices_count--; } } else{ reflection = reflect(reflection_ray.direction, normal); // no change to queue } vec3 ray_normal; if(refract) ray_normal = normal * -1; else ray_normal = normal; // create and cast reflection ray reflection_ray = CreateRay(raycast.xyz + ray_normal * 0.001f, reflection); // calculate loss due to partial transparency float transmittance = 1; if(!entering) transmittance = pow(10, -1 * (1.0f - objects[object_id].transparency) * dist); // update energy energy *= specular * transmittance; } else{ // calculate chances diffuse_chance = GetEnergy(albedo.xyz) + 0.0001f; specular_chance = GetEnergy(specular.xyz) + 0.0001f; sum = diffuse_chance + specular_chance; diffuse_chance /= sum; specular_chance /= sum; if(roulette < diffuse_chance){ //diffuse reflection = SampleHemisphere(normal, 1.0f); // update energy energy *= albedo * 2 * sdot(normal, reflection); }else{ // specular reflection = normalize(reflection_ray.direction - 2 * dot(reflection_ray.direction, normal) * normal); f = 1.0f; if(objects[object_id].smoothness <= 0.9999f){ alpha = SmoothnessToPhongAlpha(objects[object_id].smoothness); reflection = SampleHemisphere(reflection, alpha); f = (alpha + 2) / (alpha + 1); } // update energy energy *= specular * 2 * sdot(normal, reflection); } //create and cast reflected ray reflection_ray = CreateRay(raycast.xyz + normal * 0.001f, reflection); } // cast new ray raycast_n = CastRay(reflection_ray, normal, uv); object_id = int(raycast_n.w); //if hit sky, break and get color * energy if(object_id == -1){ color += energy * GetSkyColor(reflection_ray.direction); break; }else{ color += energy * objects[object_id].emission.xyz * GetTextureColor(uv, object_id, 3); dist = length(raycast_n.xyz - raycast.xyz); } // if energy is low, no need to keep going if((energy.x + energy.y + energy.z <= 0.01f && i > 1)){ break; } raycast = raycast_n; } } else{ // sample skybox color = GetSkyColor(camera_ray.direction); } // add sample pixel += vec4(color.xyz, 0.0f); } // get average over samples pixel = vec4(pixel.xyz/float(_samples_per_frame), 1.0f); // update the new average vec4 old = imageLoad(img_output, id); pixel = old + (vec4(pixel) - old)/_sample; // update pixel in texture imageStore(img_output, id, pixel); }