struct Hit { vec3 pos; vec3 normal; vec3 albedo; bool hit; }; float aabbIntersection(vec3 ro, vec3 rd, vec3 bMin, vec3 bMax) { // Ray AABB intersection from // https://tavianator.com/2011/ray_box.html vec3 t1 = (bMin - ro) / rd; vec3 t2 = (bMax - ro) / rd; vec3 t3 = min(t1, t2); vec3 t4 = max(t1, t2); float tMin = max(t3.x, max(t3.y, t3.z)); float tMax = min(t4.x, max(t4.y, t4.z)); return tMax >= tMin ? tMin : -1.0; } vec3 cylinderIntersection (vec3 ro, vec3 rd, float r, float h, out vec3 normal){ float distToTopPlane = (h - ro.y) / rd.y; vec3 topPlaneHitPos = ro + rd * distToTopPlane; if(dot(topPlaneHitPos.xz, topPlaneHitPos.xz) < r*r) { normal = vec3(0.0, 1.0, 0.0); return topPlaneHitPos; } float len = length(rd.xz); float distToClosestPoint = -dot(ro.xz, rd.xz/len); vec2 closestPoint = ro.xz + rd.xz/len * distToClosestPoint; float distToCenterSq = dot(closestPoint, closestPoint); if(distToCenterSq < r * r && distToClosestPoint > 0.0) { float offset = sqrt(r * r - distToCenterSq); vec3 pos = ro + (rd/len) * (distToClosestPoint - offset); if(pos.y > h || pos.y < -h) return vec3(0.0); vec2 n = normalize(pos.xz); normal = vec3(n.x, 0.0, n.y); return pos; } return vec3(0.0); } vec3 roundPlateIntersection(vec3 ro, vec3 rd, out vec3 normal) { vec3 stub = cylinderIntersection(ro - vec3(0.0, 0.3, 0.0), rd, 0.3, 0.1, normal); if(stub != vec3(0.0)) return stub; vec3 bigCylinder = cylinderIntersection(ro - vec3(0.0, 0.15, 0.0), rd, 0.48, 0.06, normal); if(bigCylinder != vec3(0.0)) return bigCylinder; vec3 smallCylinder = cylinderIntersection(ro - vec3(0.0, 0.12, 0.0), rd, 0.384, 0.24, normal); if(smallCylinder != vec3(0.0)) return smallCylinder; } float brickHeight(float h) { return round(h * 2.4) / 2.4; } float wave(float x) { return 1.0 - sqrt(sin(x) * 0.5 + 0.5); } bool hitsVoxel(vec2 gridPos, vec3 ro, vec3 rd, vec2 mask, out vec3 normal, out vec3 albedo) { normal = vec3(0.0); albedo = vec3(0.0, 0.2, 0.5); if(min(gridPos.x, gridPos.y) < 0.0 || max(gridPos.x, gridPos.y) >= 40.0 || ro.y < 0.0) return false; vec2 noiseOffset = vec2(texture(iChannel0, gridPos / 200.0).x, texture(iChannel0, (gridPos / 200.0) + 0.5).x) * 30.0; vec2 noisePos = gridPos + noiseOffset; float w1 = wave(noisePos.x * 0.10 + noisePos.y * 0.05 + iTime * 1.5) * 8.0; float w2 = wave(noisePos.x * -0.10 + noisePos.y * 0.05 + iTime * 1.0) * 8.0; float w3 = wave(noisePos.x * -0.20 + noisePos.y * -0.44 + iTime * 3.0) * 1.0; float w4 = wave(noisePos.x * 0.10 + noisePos.y * -0.20 + iTime * 6.0) * 3.0; float w5 = wave(noisePos.x * -0.03 + noisePos.y * 0.08 + iTime * 1.0) * 3.0; float w = w1 + w2 + w3 + w4 + w5; w = pow(w, 1.5) * 0.15; float height = brickHeight(w) + 0.41666; noiseOffset.x; if(ro.y < height) { normal = vec3(mask.x, 0.0, mask.y) * -sign(rd); albedo = vec3(0.0, 0.2, 0.5) * (min(ro.y*0.05, 0.6)+0.4); return true; } float t = min((floor(max(max(w1, w2), max(w4, w5)) / 2.0) * 2.0) / 4.0, 1.0); vec3 albedoCol = vec3(0.0, 0.2, 0.5) * t + vec3(0.1, 0.35, 0.5) * (1.0 - t); vec3 pos; if(max(w1, w2) > 7.2 || w5 > 2.9) { vec3 offset = vec3(gridPos.x + 0.5, height, gridPos.y + 0.5); pos = roundPlateIntersection(ro - rd - offset, rd, normal); albedo = vec3(0.9, 0.9, 1.0); } else { vec3 offset = vec3(gridPos.x + 0.5, height + 0.1, gridPos.y + 0.5); pos = cylinderIntersection(ro - rd - offset, rd, 0.3, 0.1, normal); albedo = albedoCol; } if(pos != vec3(0.0)) return true; albedo = albedoCol; float dist = (height - ro.y) / rd.y; vec3 hitPos = ro + rd * dist; if(hitPos.x < gridPos.x + 1.0 && hitPos.z < gridPos.y + 1.0 && hitPos.x > gridPos.x && hitPos.z > gridPos.y) { normal = vec3(0.0, 1.0, 0.0); return true; } return false; } Hit rayMiss(vec3 ro, vec3 rd) { float distToGround = -ro.y / rd.y; if(distToGround > 1.0) { Hit hit; hit.pos = ro + rd * distToGround; vec2 d = abs(hit.pos.xz-20.0) - vec2(20.0); float ao = length(max(d, 0.0)) + min(max(d.x,d.y), 0.0); hit.albedo = vec3(min(ao * 1.2, 0.6)) + 0.4; hit.normal = vec3(0.0, 1.0, 0.0); hit.hit = true; return hit; } Hit hit; hit.pos = vec3(0.0); hit.albedo = vec3(0.0); hit.normal = vec3(0.0); hit.hit = false; return hit; } Hit castRay(vec3 ro3, vec3 rd3) { vec2 ro = ro3.xz; vec2 rd = rd3.xz; float boundingBoxIntersect = aabbIntersection(ro3, rd3, vec3(0.0), vec3(40.0)); if(boundingBoxIntersect == -1.0) { return rayMiss(ro3, rd3); } if(boundingBoxIntersect < 0.0) boundingBoxIntersect = 0.01; // Grid traversal from // https://www.shadertoy.com/view/4dX3zl vec2 gridPos = floor(ro + rd * (boundingBoxIntersect - 0.01)); vec2 sideDist = abs(length(rd) / rd); vec2 toSide = (sign(rd) * (gridPos - ro) + (sign(rd) * 0.5) + 0.5) * sideDist; vec3 pos = ro3; vec2 mask; bool hits = false; vec3 normal = vec3(0.0); vec3 albedo = vec3(0.0); for(int i = 0; i < 80; ++i) { if(hitsVoxel(gridPos, pos, rd3, mask, normal, albedo)) { hits = true; break; } mask = vec2(lessThanEqual(toSide.xy, toSide.yx)); toSide += sideDist * mask; gridPos += mask * sign(rd); float dist = dot(mask * ((gridPos + vec2(lessThan(rd, vec2(0.0))) - ro) / rd), vec2(1.0)); pos = ro3 + rd3 * dist; } if(hits) { Hit hit; hit.pos = pos; hit.albedo = albedo; hit.normal = normal; hit.hit = true; return hit; }else{ return rayMiss(ro3, rd3); } } vec3 calculateColor(vec3 albedo, vec3 normal, vec3 pos, vec3 viewDir, vec2 seed) { const vec3 lDir = normalize(vec3(1.0, 1.0, -0.2)); float shadow = 0.0; for(int i = 0; i < 12; i++) { vec3 rand = textureLod(iChannel1, seed, 0.0).xyz; seed = fract(rand.xy * 100.0); vec3 rayDirection = normalize(lDir + (rand - 0.5) * 0.4); shadow += castRay(pos + rayDirection * 0.001, rayDirection).hit ? 0.0 : 1.0; } shadow /= 12.0; float diffuse = max(dot(normal, lDir), 0.0) * shadow; diffuse = diffuse * 0.7 + 0.45; float r0 = 0.1; float fresnel = r0 + (1.0 - r0) * pow(1.0 - dot(-viewDir, normal), 5.0); float specular = max(dot(reflect(viewDir, normal), lDir), 0.0) * fresnel * (shadow * 0.9 + 0.1); return diffuse * albedo + specular + fresnel * 0.1; } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 screenPos = (fragCoord - iResolution.xy * 0.5) / iResolution.y; vec3 ro = vec3(-25.0, 62.0, -25.0); float angle = sin(iTime) * 0.5; vec3 rd = normalize(vec3(screenPos, 1.8)); float rx = 30.0; float ry = 45.0; rd *= mat3( 0.7071, 0.5, 0.5, 0.0, 0.7071, -0.7071, -0.7071, 0.5, 0.5 ); Hit mainRay = castRay(ro, rd); if(mainRay.hit) { vec3 col = calculateColor(mainRay.albedo, mainRay.normal, mainRay.pos, rd, fragCoord * 113.14235); fragColor = vec4(pow(col, vec3(1.0 / 2.2)), 1.0); } else { fragColor = vec4(0.1, 0.1, 0.1, 1.0); } }