Advertisement
Guest User

Julius Bier Kirkegaard Simple Raytracer

a guest
Nov 13th, 2013
2,440
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.59 KB | None | 0 0
  1. import numpy as np
  2. import Image
  3. import math
  4.  
  5. epsilon = 1e-8
  6.  
  7. class Scene:
  8.     def __init__(self, camera_position, camera_look_at, field_of_view=10.0, gamma=0.05,
  9.                     focus=3.8, focal=7.5):
  10.         self.objects = list()
  11.         self.light_sources = list()
  12.         self.ambient = RGB([100, 100, 100])
  13.         self.gamma = gamma
  14.         self.focus = focus
  15.         self.focal = focal # High focal = better focus
  16.  
  17.         self.camera_position = np.array(camera_position)
  18.         self.camera_look_at = np.array(camera_look_at)
  19.         self.field_of_view = field_of_view
  20.  
  21.         self.camera_direction = self.camera_look_at - self.camera_position
  22.         self.camera_direction = self.camera_direction / np.linalg.norm(self.camera_direction)
  23.  
  24.         ### Temp: should be rotated not projected ###
  25.         self.camera_up = np.cross(self.camera_direction, np.cross(np.array([0,0,1]), self.camera_direction))
  26.         self.camera_up = self.camera_up / np.linalg.norm(self.camera_up)
  27.         self.camera_right = np.cross(self.camera_direction, self.camera_up)
  28.         #####
  29.  
  30.     def add_object(self, object):
  31.         self.objects.append(object)
  32.  
  33.     def add_light(self, light):
  34.         self.light_sources.append(light)
  35.  
  36.     def reflected_index(self, i, j, width, height):
  37.         if i<0:
  38.             i = -i
  39.         if j<0:
  40.             j = -j
  41.         if i >= width:
  42.             i = 2 * width - i - 1
  43.         if j >= height:
  44.             j = 2 * height - j - 1
  45.         return (i,j)
  46.  
  47.     def DOF(self, pixels, Z, N=15, offset=5):
  48.         inf = Z == -1
  49.         m = np.max(Z)
  50.         Z[inf] = m
  51.         Z -= self.focus
  52.         Z = 1.2 * N * np.abs(np.tanh(Z/self.focal))
  53.  
  54.         base = np.zeros((N,N))
  55.         for x in xrange(N):
  56.             for y in xrange(N):
  57.                 base[x,y] = (x-N/2)**2 + (y-N/2)**2
  58.  
  59.         width, height = Z.shape
  60.         for x in xrange(width):
  61.             print 'Depth of Field :', x/float(width) * 100, '%'
  62.             for y in xrange(height):
  63.                 if Z[x,y] < offset:
  64.                     continue
  65.  
  66.                 gauss = np.exp(-base/(Z[x,y]-offset))
  67.                 gauss /= np.sum(gauss)
  68.                 temp = [0]*3   
  69.  
  70.  
  71.                 for i in range(N):
  72.                     for j in xrange(N):
  73.                         for c in xrange(3):
  74.                             l, m = self.reflected_index(x + i - N/2, y + j - N/2, width, height)
  75.                             temp[c] += gauss[i,j] * pixels[l][m][c]
  76.  
  77.                 pixels[x][y] = tuple(map(int,temp))
  78.  
  79.         return pixels
  80.        
  81.  
  82.     def render(self, width=128, height=128, max_depth=2, DOF=True):
  83.         pixels = [[0]*height for _ in xrange(width)]
  84.         Z = np.zeros((width, height))
  85.         for x in xrange(width):
  86.             print 'Raytracing :', x/float(width) * 100, '%'
  87.             for y in xrange(height):
  88.                 x_shift = (x - width/2.0)/width * self.field_of_view * self.camera_right
  89.                 y_shift = (y - height/2.0)/width * self.field_of_view * self.camera_up # normalized by width
  90.                 ray = Ray(origin=self.camera_position, direction=self.camera_look_at+x_shift+y_shift - self.camera_position)
  91.                 color, dist = self.trace(ray, max_depth=max_depth)
  92.                 pixels[x][y] = color.tuple
  93.                 Z[x][y] = dist
  94.  
  95.         # Depth of Field:
  96.         if DOF:
  97.             pixels = self.DOF(pixels, Z)
  98.  
  99.         img = Image.new("RGB",(width,height))
  100.         for x in xrange(width):
  101.             for y in xrange(height):
  102.                 img.putpixel((width - x - 1,height - y - 1), pixels[x][y])
  103.         return img
  104.  
  105.     def find_ray_intersection(self, ray):
  106.         smallest_distance = float('inf')
  107.         first_intersection = None
  108.         for obj in self.objects:
  109.             intersection = obj.find_intersection(ray)
  110.             if intersection.true_intersection and epsilon < intersection.distance < smallest_distance:
  111.                 first_intersection = intersection
  112.                 smallest_distance = intersection.distance
  113.         return first_intersection
  114.  
  115.     def diffuse_to_light_sources(self, point, normal, obj):
  116.         brightness = 0.0
  117.         for light in self.light_sources:
  118.             to_light = light.position - point
  119.             ray = Ray(origin=point + epsilon*normal, direction=to_light)
  120.             if self.find_ray_intersection(ray) == None:
  121.                 cosine = max([0, to_light.dot(normal) / (np.linalg.norm(to_light) * np.linalg.norm(normal))])
  122.                 intensity = 100 * light.intensity / sum(to_light**2)
  123.  
  124.                 # Shading:
  125.                 brightness +=  intensity * cosine
  126.  
  127.                 # Specular reflections:
  128.                 if obj.specular_level > 0:
  129.                     brightness += intensity * obj.specular_level * pow(cosine, obj.specular_falloff)
  130.  
  131.         return brightness + self.gamma
  132.  
  133.     def trace(self, ray, max_depth=2, depth=0):
  134.         color = RGB([0.0, 0.0, 0.0])
  135.         intersection = self.find_ray_intersection(ray)
  136.         if intersection == None:
  137.             return self.ambient, -1
  138.  
  139.         obj = intersection.object
  140.         if depth == max_depth:
  141.             return obj.color, 0
  142.  
  143.         refl_color = RGB([0, 0, 0])
  144.         if obj.reflectivity > 0.0:
  145.             reflected_ray_direction = ray.direction - \
  146.                     2 * intersection.normal * ray.direction.dot(intersection.normal)
  147.             reflected_ray = Ray(intersection.point, reflected_ray_direction)
  148.             refl_color, dummy = self.trace(reflected_ray, max_depth, depth+1)
  149.  
  150.         brightness = self.diffuse_to_light_sources(intersection.point, intersection.normal, intersection.object)
  151.         return (refl_color * obj.reflectivity + obj.color * (1.0 - obj.reflectivity)) * brightness, intersection.distance
  152.  
  153. class RGB:
  154.     def __init__(self, color):
  155.         self.color = [0,0,0]
  156.         for i in range(3):
  157.             self.__setitem__(i, color[i])
  158.  
  159.     def __setitem__(self, i, value):
  160.         self.color[i] = int(value)
  161.         if self.color[i] < 0:
  162.             self.color[i] = 0
  163.         elif self.color[i] > 255:
  164.             self.color[i] = 255
  165.  
  166.     def __getitem__(self, i):
  167.         return self.color[i]
  168.  
  169.     def __add__(self, other):
  170.         out = RGB([0,0,0])
  171.         for i in range(3):
  172.             out[i] = self[i] + other[i]
  173.         return out
  174.  
  175.     def __mul__(self, other):
  176.         out = RGB([0,0,0])
  177.         for i in range(3):
  178.             out[i] = other * self[i]
  179.         return out
  180.  
  181.     def norm(self):
  182.         return np.array(self.color) / np.linalg.norm(self.color)
  183.  
  184.     @property
  185.     def tuple(self):
  186.         return tuple(self.color)
  187.  
  188. class Plane:
  189.     def __init__(self, center, normal, color):
  190.         self.center = np.array(center)
  191.         self.normal = np.array(normal)
  192.         self.color = RGB(color)
  193.         self.reflectivity = 0.3
  194.         self.name = 'plane'
  195.         self.specular_level = 0
  196.         self.specular_falloff = 1
  197.  
  198.     def find_intersection(self, ray):
  199.         dot = self.normal.dot(ray.direction)
  200.         if dot == 0.0:
  201.             return Intersection()
  202.         else:
  203.             inter = Intersection()
  204.             inter.distance = (self.center - ray.origin).dot(self.normal)/dot
  205.             if inter.distance < 0:
  206.                 return inter
  207.             inter.point = ray.origin + inter.distance * ray.direction
  208.             inter.normal = self.normal
  209.             inter.object = self
  210.             inter.true_intersection = True
  211.             return inter
  212.  
  213. class Sphere:
  214.     def __init__(self, center, radius, color):
  215.         self.center = np.array(center)
  216.         self.radius = radius
  217.         self.color = RGB(color)
  218.         self.reflectivity = 0.5
  219.         self.name = 'sphere'
  220.         self.specular_level = 2
  221.         self.specular_falloff = 500
  222.  
  223.     def find_intersection(self, ray):
  224.         l = ray.direction
  225.         o = ray.origin
  226.         c = self.center
  227.         pre = l.dot(o - c)
  228.         discriminant = pre**2 - l.dot(l) * ( (o-c).dot(o-c) - self.radius**2 )
  229.         if discriminant <= 0: # fuck the edge
  230.             return Intersection()
  231.         else:
  232.             discriminant = np.sqrt(discriminant)
  233.             dist1 = -pre + discriminant
  234.             dist2 = -pre - discriminant
  235.             inter = Intersection()
  236.  
  237.             if dist1 <= 0:
  238.                 if dist2 <=0:
  239.                     return inter
  240.                 else:
  241.                     dist = dist2
  242.             elif dist2 <= 0:
  243.                 dist = dist1
  244.             else:
  245.                 dist = min([dist1, dist2])
  246.  
  247.             inter.object = self
  248.             inter.distance = dist / l.dot(l)
  249.             inter.point = o + l*inter.distance
  250.             inter.normal = inter.point - c
  251.             inter.normal = inter.normal / np.linalg.norm(inter.normal)
  252.             inter.true_intersection = True
  253.             return inter
  254.  
  255. class OmniLight:
  256.     def __init__(self, position, intensity):
  257.         self.position = position
  258.         self.intensity = intensity
  259.  
  260. class Ray:
  261.     def __init__(self, origin, direction):
  262.         self.origin = np.array(origin)
  263.         self.direction = np.array(direction / np.linalg.norm(direction))
  264.  
  265. class Intersection( object ):
  266.     def __init__(self):
  267.         self.point = np.array([0.0, 0.0, 0.0])
  268.         self.distace = 0
  269.         self.normal = np.array([0.0, 0.0, 0.0])
  270.         self.object = None
  271.         self.true_intersection = False
  272.  
  273. if __name__ == "__main__":
  274.     scene = Scene((0.0, 0.0, 1.0), (1.0, 0.0, 1.0), field_of_view=1.0,
  275.                     gamma=0.65, focus=9.5, focal=2.0)
  276.     scene.ambient = RGB((105, 205, 255))
  277.  
  278.     floor = Plane((0.0, 0.0, 0.0), (0.0, 0.0, 1.0), color=(200, 200, 175))
  279.     scene.add_object(floor)
  280.  
  281.  
  282.     scene.add_object(Sphere((10, -0.5, 1.5), 1.5, color=(50, 190, 25)))
  283.    
  284.     scene.add_object(Sphere((12, -4.8, 2.15), 2.15, color=(255,128,0)))
  285.     scene.add_object(Sphere((5.5, -2.6, 0.83), 0.83, color=(255,128,0)))
  286.     scene.add_object(Sphere((6.6, 1, 0.5), 0.5, color=(255,128,0)))
  287.     scene.add_object(Sphere((8.5, 2.2, 0.5), 0.5, color=(255,128,0)))
  288.     scene.add_object(Sphere((4.6, 2.15, 0.9), 0.9, color=(255,128,0)))
  289.  
  290.  
  291.     scene.add_light( OmniLight((6.5, -10.0, 5.0), 1.6) )
  292.  
  293.     img = scene.render(1024, 768, max_depth=3, DOF=True) # choose width, height (ie. quality) here
  294.     img.save("rendered.bmp")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement