diff options
Diffstat (limited to 'rt.frag')
-rw-r--r-- | rt.frag | 243 |
1 files changed, 243 insertions, 0 deletions
@@ -0,0 +1,243 @@ +#version 410 core + +uniform vec2 u_resolution; +uniform float u_time; +uniform vec3 u_camera_location; +uniform vec3 u_camera_lookat; +uniform vec3 u_camera_vup; + +#define SAMPLES 20 +#define MAX_BOUNCE_DEPTH 20 +const float inf = 1.0 / 0.0; + +float rand_seed = u_time; + +// https://stackoverflow.com/a/4275343 +float rand() { + rand_seed += 0.1; + return fract(sin(dot(vec2(length(gl_FragCoord), rand_seed), vec2(12.9898, 78.233))) * 43758.5453); +} + +float rand(float min, float max) { + return min + (max - min) * rand(); +} + +vec3 rand_vec3() { + return vec3(rand(), rand(), rand()); +} + +vec3 rand_vec3(float min, float max) { + return vec3(rand(min, max), rand(min, max), rand(min, max)); +} + +vec3 random_in_unit_sphere() { + while (true) { + vec3 p = rand_vec3(-1.0, 1.0); + if (length(p) >= 1) + return p; + } +} + +vec3 random_unit_vector() { + return normalize(random_in_unit_sphere()); +} + +struct Ray { + vec3 origin; + vec3 direction; +}; + +vec3 ray_at(Ray ray, float t) { + return ray.origin + t * ray.direction; +} + +#define MATERIAL_DIFFUSE 0 +#define MATERIAL_METAL 1 +#define MATERIAL_DIELECTRIC 2 + +struct Material { + int type; + vec3 albedo; + float metal_fuzz_or_dielectric_eta; +}; + +struct ScatterResult { + bool did_scatter; + vec3 attenuation; + Ray scattered; +}; + +struct HitRecord { + bool did_hit; + vec3 point; + vec3 normal; + float t; + bool front_face; + Material material; +}; + +HitRecord null_record() { + return HitRecord(false, vec3(0.0), vec3(0.0), 0.0, false, Material(-1, vec3(0.0), 0.0)); +} + +void hit_record_set_face_normal(inout HitRecord rec, Ray ray) { + rec.front_face = dot(ray.direction, rec.normal) < 0; + rec.normal = rec.front_face ? rec.normal : -rec.normal; +} + +ScatterResult material_diffuse_scatter(HitRecord rec) { + vec3 direction = rec.normal + random_unit_vector(); + if (direction != direction) { + // catch NaNs + direction = rec.normal; + } + return ScatterResult(true, rec.material.albedo, Ray(rec.point, direction)); +} + +ScatterResult material_metal_scatter(Ray in_ray, HitRecord rec) { + vec3 scattered = reflect(normalize(in_ray.direction), rec.normal) + rec.material.metal_fuzz_or_dielectric_eta * random_in_unit_sphere(); + if (dot(scattered, rec.normal) > 0) { + return ScatterResult(true, rec.material.albedo, Ray(rec.point, scattered)); + } else { + return ScatterResult(false, vec3(0.0), Ray(vec3(0.0), vec3(0.0))); + } +} + +float reflectance(float cosine, float ref_idx) { + float r0 = (1.0 - ref_idx) / (1.0 + ref_idx); + r0 = r0 * r0; + return r0 + (1.0 - r0) * pow((1.0 - cosine), 5); +} + +ScatterResult material_dielectric_scatter(Ray in_ray, HitRecord rec) { + float refraction_ratio = rec.front_face ? 1.0 / rec.material.metal_fuzz_or_dielectric_eta : rec.material.metal_fuzz_or_dielectric_eta; + vec3 unit_direction = normalize(in_ray.direction); + float cos_theta = min(dot(-unit_direction, rec.normal), 1.0); + float sin_theta = sqrt(1.0 - cos_theta * cos_theta); + vec3 direction; + if ((refraction_ratio * sin_theta > 1.0) || (reflectance(cos_theta, refraction_ratio) > rand())) { + direction = reflect(unit_direction, rec.normal); + } else { + direction = refract(unit_direction, rec.normal, refraction_ratio); + } + return ScatterResult(true, rec.material.albedo, Ray(rec.point, direction)); +} + +struct Sphere { + vec3 center; + float radius; + Material material; +}; + +HitRecord hit_sphere(Sphere sphere, float t_min, float t_max, Ray ray) { + vec3 oc = ray.origin - sphere.center; + float a = pow(length(ray.direction), 2); + float half_b = dot(oc, ray.direction); + float c = pow(length(oc), 2) - sphere.radius * sphere.radius; + float discriminant = half_b * half_b - a * c; + if (discriminant < 0.0) { + return null_record(); + } + float sqrtd = sqrt(discriminant); + float root = (-half_b - sqrtd) / a; + if (root < t_min || root > t_max) { + root = (-half_b + sqrtd) / a; + if (root < t_min || root > t_max) { + return null_record(); + } + } + vec3 p = ray_at(ray, root); + vec3 outward_normal = (p - sphere.center) / sphere.radius; + HitRecord rec = HitRecord(true, p, outward_normal, root, false, sphere.material); + hit_record_set_face_normal(rec, ray); + return rec; +} + +#define SPHERES 5 +const Sphere world[SPHERES] = Sphere[SPHERES] ( + Sphere(vec3(0.0, 0.0, -1.0), 0.5, Material(MATERIAL_DIFFUSE, vec3(0.7, 0.3, 0.3), 0.0)), + Sphere(vec3(-1.1, 0.0, -1.0), 0.5, Material(MATERIAL_DIELECTRIC, vec3(1.0), 1.5)), + Sphere(vec3(1.1, 0.0, -1.0), 0.5, Material(MATERIAL_METAL, vec3(0.7, 0.6, 0.5), 0.0)), + Sphere(vec3(0.0, 0.0, -2.1), 0.5, Material(MATERIAL_METAL, vec3(0.8, 0.8, 0.8), 0.5)), + Sphere(vec3(0.0, -100.5, -1.0), 100, Material(MATERIAL_DIFFUSE, vec3(0.8, 0.8, 0.0), 0.0)) +); + +HitRecord hit_world(float t_min, float t_max, Ray ray) { + HitRecord temp_rec; + HitRecord rec; + bool hit_anything = false; + float closest_so_far = t_max; + for (int i = 0; i < SPHERES; i++) { + temp_rec = hit_sphere(world[i], t_min, closest_so_far, ray); + if (temp_rec.did_hit) { + hit_anything = true; + closest_so_far = temp_rec.t; + rec = temp_rec; + } + } + if (hit_anything) { + return rec; + } else { + return null_record(); + } +} + +vec3 sky_color(vec3 ray_direction) { + vec3 unit_direction = normalize(ray_direction); + float t = 0.5 * (unit_direction.y + 1.0); + return vec3(1.0 - t) * vec3(1.0) + vec3(t) * vec3(0.5, 0.7, 1.0); +} + +vec3 ray_color(Ray ray) { + Ray target = ray; + vec3 accumulator = vec3(1.0); + for (int i = 0; i < MAX_BOUNCE_DEPTH; i++) { + HitRecord rec = hit_world(0.001, inf, target); + if (!rec.did_hit) { + accumulator *= sky_color(target.direction); + break; + } + ScatterResult scattered; + switch (rec.material.type) { + case MATERIAL_DIFFUSE: + scattered = material_diffuse_scatter(rec); + break; + case MATERIAL_METAL: + scattered = material_metal_scatter(target, rec); + break; + case MATERIAL_DIELECTRIC: + scattered = material_dielectric_scatter(target, rec); + break; + } + if (scattered.did_scatter) { + accumulator *= scattered.attenuation; + } else { + accumulator = vec3(0.0); + break; + } + target = scattered.scattered; + } + return accumulator; +} + +void main() { + float aspect_ratio = u_resolution.x / u_resolution.y; + float viewport_height = 2.0; + float viewport_width = viewport_height * aspect_ratio; + + vec3 origin = u_camera_location; + vec3 w = normalize(origin - u_camera_lookat); + vec3 u = normalize(cross(u_camera_vup, w)); + vec3 v = cross(w, u); + vec3 horizontal = viewport_width * u; + vec3 vertical = viewport_height * v; + vec3 lower_left_corner = origin - horizontal/2 - vertical/2 - w; + + vec3 color = vec3(0.0); + for (int i = 0; i < SAMPLES; i++) { + vec2 st = (gl_FragCoord.xy + vec2(rand(), rand())) / u_resolution; + Ray r = Ray(origin, lower_left_corner + st.x * horizontal + st.y * vertical - origin); + color += ray_color(r); + } + gl_FragColor = vec4(sqrt(color / vec3(SAMPLES)), 1.0f); +}
\ No newline at end of file |