// https://www.shadertoy.com/view/7ts3z8 // 4x supersampling antialiasing. #define SUPERSAMPLE // The minimum distance between two points before they are considered intersecting. #define MIN_DISTANCE 0.001 // The maximum distance from the camera objects are visible. #define MAX_DISTANCE 15.000 // The maximum number of steps we're allowed to take during ray marching. #define MAX_STEPS 150 // The width of the field of view. (180 degrees / FOV) // Larger numbers suffer from less distortion at the edges of the image, // but make the scene appear more zoomed-in. #define FOV 2. #define CAMERA_ORIGIN vec3(0., 0., -2.) // The minimum lighting intensity for objects which would otherwise // be in total darkness. #define MIN_LIGHT 0.01 #define INF 1./0. #define NaN sqrt(-1.) struct NearPoint { vec3 point; vec3 normal; }; struct NearObject { NearPoint near; int material; }; NearObject nearestNearObject(NearObject a, NearObject b, vec3 pos) { if (distance(a.near.point, pos) < distance(b.near.point, pos)) { return a; } return b; } NearPoint nearPoint(vec3 pPos, vec3 pos) { vec3 normal = normalize(pos - pPos); return NearPoint(pPos, normal); } NearPoint nearSegment(vec3 pPos, vec3 pVec, vec3 pos) { float len = length(pVec); float h = clamp(dot(normalize(pVec), pos - pPos), 0., len); vec3 point = pPos + h * normalize(pVec); return NearPoint(point, normalize(pos - point)); } NearPoint nearPlane(float height, vec3 pos) { vec3 normal = vec3(0., 1., 0.); vec3 point = vec3(pos.x, height, pos.z); return NearPoint(point, normal); } NearPoint inflate(float radius, NearPoint near) { return NearPoint(near.normal * radius + near.point, near.normal); } NearPoint nearSphere(vec3 sPos, float sRadius, vec3 pos) { return inflate(sRadius, nearPoint(sPos, pos)); } NearPoint nearPill(vec3 pPos, vec3 pVec, float pRadius, vec3 pos) { return inflate(pRadius, nearSegment(pPos, pVec, pos)); } NearObject nearestObject(vec3 pos) { NearObject ground = NearObject(nearPlane(-1., pos), 1); NearObject sphere1 = NearObject(nearSphere( 5. * vec3(sin(iTime), 0., cos(iTime)), 1., pos ), 2); NearObject pill1 = NearObject(nearPill( vec3(-0.7, -0.8, 2.), vec3(1.3, 0.1, -0.1), 0.1, pos ), 3); return nearestNearObject(nearestNearObject(ground, sphere1, pos), pill1, pos); } vec3 srgb2linear(vec3 srgb) { // approximation return vec3(pow(srgb.x, 1./2.2), pow(srgb.y, 1./2.2), pow(srgb.z, 1./2.2)); } vec3 materialColor(int material) { if (material == 1) { return vec3(1.); } if (material == 2) { return srgb2linear(vec3(0., 0.19, 0.56)); } if (material == 3) { return srgb2linear(vec3(0.50, 1.00, 0.40)); } if (material == 4) { return srgb2linear(vec3(0.83, 0.69, 0.22)); } // missing material placeholder color return vec3(1., 0., 1.); } struct Ray { vec3 origin; vec3 direction; }; NearObject raymarch(Ray ray) { vec3 pos = ray.origin; NearObject nanObject = NearObject(NearPoint(vec3(INF), vec3(NaN)), -1); NearObject object = nanObject; for (int i = 0; i < MAX_STEPS && distance(pos, ray.origin) < MAX_DISTANCE; i++) { object = nearestObject(pos); float delta = distance(pos, object.near.point); if (delta < MIN_DISTANCE) { return object; } pos += ray.direction * delta; } return nanObject; } Ray projectSphere(vec2 uv) { vec3 direction = vec3(uv, sqrt(1. - uv.x*uv.x - uv.y*uv.y)); return Ray(CAMERA_ORIGIN + direction, direction); } float light(vec3 sPos, NearPoint near) { vec3 sDirection = normalize(sPos - near.point); float intensity = dot(sDirection, near.normal); float marchDist = distance(raymarch(Ray(sPos, -sDirection)).near.point, sPos); if (marchDist < distance(sPos, near.point) - 10. * MIN_DISTANCE) { // The object is in the shadow. intensity *= 0.1; } if (intensity < MIN_LIGHT && length(near.point) < INF) { return MIN_LIGHT; } return intensity; } vec3 rayColor(Ray ray) { NearObject object = raymarch(ray); if (object.material == -1) { return vec3(0.); } vec3 color = materialColor(object.material); float lightIntensity = light(vec3(1.5, 1.5, 6.), object.near); return color * lightIntensity; } vec3 linear2srgb(vec3 linear_rgb) { // copied from somewhere on the internet bvec3 cutoff = lessThan(linear_rgb, vec3(0.0031308)); vec3 higher = vec3(1.055)*pow(linear_rgb, vec3(1.0/2.4)) - vec3(0.055); vec3 lower = linear_rgb * vec3(12.92); return mix(higher, lower, cutoff); } vec3 uvColor(vec2 uv) { return rayColor(projectSphere(uv)); } vec3 uvSupersample(vec2 uv) { vec2 off = vec2(0.5, -0.5) / (FOV * iResolution.x); return ( uvColor(uv + off.xx) + uvColor(uv + off.xy) + uvColor(uv + off.yx) + uvColor(uv + off.yy) ) / 4.; } void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = (fragCoord / iResolution.xy - 0.5) * 2.; // Cut off the part of the scene which doesn't fit due to the aspect ratio. if (iResolution.x > iResolution.y) { uv = vec2(uv.x, uv.y * iResolution.y / iResolution.x); } else { uv = vec2(uv.x * iResolution.x / iResolution.y, uv.y); } uv /= FOV; #ifndef SUPERSAMPLE vec3 color = uvColor(uv); #else vec3 color = uvSupersample(uv); #endif //vec3 color = raymarch(projectSphere(uv)).near.normal / 2. + 0.5; fragColor = vec4(linear2srgb(color), 0.); }