author Emil Mikulic Mon, 5 Sep 2016 06:17:18 +0000 (16:17 +1000) committer Emil Mikulic Mon, 5 Sep 2016 06:17:18 +0000 (16:17 +1000)
 tf.py [new file with mode: 0755] patch | blob

diff --git a/tf.py b/tf.py
new file mode 100755 (executable)
index 0000000..2bc8365
--- /dev/null
+++ b/tf.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+"""
+The worst raytracer.
+Emil Mikulic <emikulic@gmail.com> was here 2012.
+"""
+import numpy as np
+from accidental_complexity import show
+# this file contains mostly essential complexity
+
+def vec(x, y, z):
+  return np.array((x, y, z), dtype=np.float)
+
+def color(r, g, b):
+  return np.reshape(vec(r,g,b), (1,1,3))
+
+def dot(a, b):
+  return (a * b).sum(axis=2)
+
+def mag(a):
+  return np.sqrt(np.square(a).sum(axis=2))
+
+def depthwise(a):
+  return np.expand_dims(a, axis=2)
+
+def norm(a):
+  return a / depthwise(mag(a))
+
+def reflect(i, n):
+  c = n * depthwise(dot(n, -i))
+  return 2*c + i
+
+def diffuse(n, l):
+  return depthwise(dot(n, l).clip(0, 1))
+
+def specular(r, l, power):
+  return pow(diffuse(r, l), power)
+
+def lerp(ar, a, b, u, v):
+  "[u,v] is mapped to [a,b]"
+  return a + (b-a) * (ar.clip(u,v) - u) / (v-u)
+
+class Scene:
+  def __init__(self):
+    self.light1 = vec(20,10,-10)
+    self.light1_col = color(1,.5,.3)
+    self.light2 = vec(-5,5,5)
+    self.light2_col = color(1,1,1) - self.light1_col
+    self.sky_col = color(.2, .3, .6)
+    self.sph_center = vec(-.5,0,0)
+    self.sph_col = color(1,1,1)
+    self.camera = vec(0,0,-5)
+    self.ground_col1 = color(.4,.4,.4)
+    self.ground_col2 = color(.8,.8,.8)
+    self.plane_n = vec(0,1,0)
+    self.plane_amb = 0.02
+    self.plane_dif = 0.9
+    self.plane_height = -0.5
+    self.fog_near = 0
+    self.fog_far = 40
+
+  def trace_bg(self, camera, ray):
+    # background
+    plane_depth = (self.plane_height - camera[:,:,1]) / ray[:,:,1]
+    plane_p = camera + depthwise(plane_depth) * ray
+    plane_l1 = norm(self.light1 - plane_p)
+    plane_l2 = norm(self.light2 - plane_p)
+    plane_xpos = plane_p[:,:,0] - np.floor(plane_p[:,:,0])
+    plane_zpos = plane_p[:,:,2] - np.floor(plane_p[:,:,2])
+    checkers = depthwise(np.logical_xor(plane_xpos < 0.5, plane_zpos < 0.5))
+
+    # trace shadows (light 1)
+    ec = plane_p - self.sph_center
+    b = 2 * dot(plane_l1, ec)
+    c = dot(ec, ec) - np.square(self.sph_rad)
+    det = b*b - 4*c
+    depth = (-b - np.sqrt(det)) / 2
+    illum_l1 = np.where(depthwise(depth > 0), 0, self.light1_col)
+
+    # light 2
+    b = 2 * dot(plane_l2, ec)
+    det = b*b - 4*c
+    depth = (-b - np.sqrt(det)) / 2
+    illum_l2 = np.where(depthwise(depth > 0), 0, self.light2_col)
+
+    plane_col = np.where(checkers, self.ground_col1, self.ground_col2) * (
+        self.plane_amb + self.plane_dif * (
+          diffuse(self.plane_n, plane_l1) * illum_l1 +
+          diffuse(self.plane_n, plane_l2) * illum_l2))
+
+    fog_dist = mag(plane_p - vec(0, self.plane_height, 0))
+    fog = lerp(fog_dist, 1, 0, self.fog_near, self.fog_far)
+    plane_col *= depthwise(np.square(fog))
+
+    sky = self.sky_col * depthwise(ray[:,:,1])
+    return np.where(depthwise(plane_depth > 0), plane_col, sky)
+
+  def render(self, w, h):
+    # set up scene
+    sph_amb = 0.02
+    sph_diff = 0.05
+    sph_spec = 1.1
+    sph_refl = 0.5
+    sph_shine = 20
+
+    # points on the screen plane (z = 0)
+    aspect = float(w) / float(h)
+    scr_x = np.linspace(-1 + 1./w, 1 - 1./w, w) * aspect
+    scr_y = np.linspace(-1 + 1./h, 1 - 1./h, h) * -1
+    scr = np.zeros((h,w,3), dtype=np.float)
+    scr[:,:,0] = np.reshape(scr_x, (1, w))
+    scr[:,:,1] = np.reshape(scr_y, (h, 1))
+
+    ray = norm(scr - self.camera)
+
+    # sphere
+    ec = self.camera - self.sph_center
+    b = 2 * dot(ray, ec)
+    c = ec.dot(ec) - np.square(self.sph_rad)
+    det = b*b - 4*c
+    sph_depth = (-b - np.sqrt(det)) / 2
+
+    # sphere color
+    sph_p = self.camera + depthwise(sph_depth) * ray
+    sph_l1 = norm(self.light1 - sph_p)
+    sph_l2 = norm(self.light2 - sph_p)
+    sph_n = (sph_p - self.sph_center) / self.sph_rad
+    sph_r = norm(reflect(ray, sph_n))
+    r_col = self.trace_bg(sph_p, sph_r)
+
+    sph = self.sph_col * (sph_amb +
+        sph_diff * diffuse(sph_n, sph_l1) * self.light1_col +
+        sph_diff * diffuse(sph_n, sph_l2) * self.light2_col +
+        sph_spec * specular(sph_r, sph_l1, sph_shine) * self.light1_col +
+        sph_spec * specular(sph_r, sph_l2, sph_shine) * self.light2_col +
+        r_col * sph_refl)
+
+    bg = self.trace_bg(np.reshape(self.camera, (1,1,3)), ray)
+    return np.where(depthwise(sph_depth > 0), sph, bg)
+
+show(Scene().render(800, 600))
+# vim:set ts=2 sw=2 et: