aboutsummaryrefslogtreecommitdiff
path: root/rt.frag
diff options
context:
space:
mode:
Diffstat (limited to 'rt.frag')
-rw-r--r--rt.frag243
1 files changed, 243 insertions, 0 deletions
diff --git a/rt.frag b/rt.frag
new file mode 100644
index 0000000..8490804
--- /dev/null
+++ b/rt.frag
@@ -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