Advertisement
here2share

# np_tinyraytracer.py

Mar 12th, 2021
1,066
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.07 KB | None | 0 0
  1. # np_tinyraytracer.py
  2.  
  3. from math import sqrt, tan
  4. import numpy as np
  5. import sys
  6.    
  7. class Light:
  8.     position = np.array([None] * 3)
  9.     intensity = 0.0
  10.    
  11.     def __init__(self, p, i):
  12.         self.position = p
  13.         self.intensity = i
  14.  
  15. class Material:
  16.     refractive_index = 0.0
  17.     albedo = np.array([None] * 4)
  18.     diffuse_color = np.array([None] * 3)
  19.     specular_exponent = 0.0
  20.    
  21.     def __init__(self,r = 1, a = np.array([1, 0, 0, 0]), color = np.array([None] * 3), spec = 0.0):
  22.         self.refractive_index = r
  23.         self.albedo = a
  24.         self.diffuse_color = color
  25.         self.specular_exponent = spec
  26.  
  27. class Sphere:
  28.     center = np.array([None] * 3)
  29.     radius = 0.0
  30.     material = Material()
  31.    
  32.     def __init__(self, c, r, m):
  33.         self.center = c
  34.         self.radius = r
  35.         self.material = m
  36.  
  37.     def ray_intersect(self, orig, dir):
  38.         L = self.center - orig
  39.         tca = sum(L*dir)
  40.         d2 = (sum(L*L) - tca*tca)
  41.         if (d2 > self.radius * self.radius): return False, 0
  42.         thc = sqrt(self.radius * self.radius - d2)
  43.         t0 = tca - thc
  44.         t1 = tca + thc
  45.         if (t0 < 0): t0 = t1
  46.         if (t0 < 0): return False, 0
  47.         return True, t0
  48.  
  49. def reflect(I, N):
  50.     return I - N * 2.0 * sum(I * N)
  51.  
  52. def refract(I, N, refractive_index): # Snell's law
  53.     cosi = -max(-1.0, min(1.0, sum(I * N))) #float
  54.     etai, etat = 1, refractive_index       #float
  55.     n = N                                   #Vec3f
  56.     if cosi < 0: # if the ray is inside the object, swap the indices and invert the normal to get the correct result
  57.         cosi = -cosi
  58.         (etai, etat) = (etat, etai)         #swap
  59.         n = -N
  60.     eta = etai / etat                       #float
  61.     k = 1 - eta * eta * (1 - cosi * cosi)   #float
  62.     return (np.array([0,0,0]), n) if k < 0 else (I * eta + n * (eta * cosi - sqrt(k)), n)
  63.    
  64. def scene_intersect(orig, dir, spheres):
  65.     material = Material(color = np.array([0.2, 0.7, 0.8]))
  66.     N, hit = np.array([None] * 3), np.array([None] * 3)
  67.     spheres_dist = sys.float_info.max
  68.     for i in range(len(spheres)):
  69.         intersect, dist_i = spheres[i].ray_intersect(orig, dir)
  70.         if ( intersect and dist_i < spheres_dist):
  71.             spheres_dist = dist_i
  72.             hit = orig + dir*dist_i
  73.             N = (hit - spheres[i].center)/np.linalg.norm((hit - spheres[i].center))
  74.             material = spheres[i].material
  75.     checkerboard_dist = sys.float_info.max
  76.     if (abs(dir[1]) > 1e-3):
  77.         d = -(orig[1]+4)/dir[1] # the checkerboard plane has equation y = -4
  78.         pt = orig + dir * d
  79.         if (d > 0 and abs(pt[0]) < 10 and pt[2] < -10 and pt[2] > -30 and d < spheres_dist):
  80.             checkerboard_dist = d
  81.             hit = pt
  82.             N = np.array([0,1,0])
  83.             fifa = (int(0.5*hit[0]+1000) + int(0.5*hit[2])) & 1
  84.             material.diffuse_color = np.array([1,1,1]) if fifa else np.array([1, 0.7, 0.3])
  85.             material.diffuse_color = material.diffuse_color * 0.3
  86.     return min(spheres_dist, checkerboard_dist) < 1000, material, N, hit
  87.  
  88. def cast_ray(orig, dir, spheres, lights, depth=0):
  89.     intersect, material, N, point = scene_intersect(orig, dir, spheres)
  90.     if not intersect or depth > 4:
  91.         return material.diffuse_color #background color
  92.    
  93.     ray = reflect(dir, N)
  94.     reflect_dir = ray/np.linalg.norm(ray)                           #VECTOR
  95.     ray, N = refract(dir, N, material.refractive_index)
  96.     refract_dir = ray/np.linalg.norm(ray) #VECTOR
  97.     reflect_orig  = sum(point - N) * 1e-3 if sum(reflect_dir * N) < 0 else point + N * 1e-3  #VECTOR  offset the original point to avoid occlusion by the object itself
  98.     refract_orig  = point - N * 1e-3 if sum(refract_dir * N) < 0 else point + N * 1e-3       #VECTOR
  99.     reflect_color = cast_ray(reflect_orig, reflect_dir, spheres, lights, depth + 1)          #VECTOR
  100.     refract_color = cast_ray(refract_orig, refract_dir, spheres, lights, depth + 1)          #VECTOR
  101.  
  102.     diffuse_light_intensity = 0
  103.     specular_light_intensity = 0
  104.     for i in range(len(lights)):
  105.         light_dir = (lights[i].position - point)/np.linalg.norm(lights[i].position - point)
  106.         light_distance = np.linalg.norm(lights[i].position - point)
  107.  
  108.         shadow_orig = point - N * 1e-3 if sum(light_dir * N) < 0 else point + N * 1e-3 # checking if the point lies in the shadow of the lights[i]
  109.        
  110.         intersect, tmpmaterial, shadow_N, shadow_pt = scene_intersect(shadow_orig, light_dir, spheres)
  111.         if (intersect and np.linalg.norm(shadow_pt - shadow_orig) < light_distance):
  112.             continue
  113.         diffuse_light_intensity += lights[i].intensity * max(0.0, sum(light_dir * N))
  114.         specular_light_intensity += pow(max(0.0, sum(-reflect(-light_dir, N) * dir)), material.specular_exponent) * lights[i].intensity
  115.     return material.diffuse_color * diffuse_light_intensity * material.albedo[0] + np.array([1.0, 1.0, 1.0]) * specular_light_intensity * material.albedo[1] + reflect_color * material.albedo[2] + refract_color * material.albedo[3]
  116.    
  117. def render(spheres, lights, width, height):
  118.     fov = int(np.pi/2.0)
  119.     framebuffer = [None] * (width*height)
  120.  
  121.     for j in range(height):
  122.         for i in range(width):
  123.             x =  (2*(i + 0.5)/float(width)  - 1)*tan(fov/2.)*width/float(height)    #float
  124.             y = -(2*(j + 0.5)/float(height) - 1)*tan(fov/2.)                        #float
  125.             dir = np.array([x, y, -1])/np.linalg.norm(np.array([x, y, -1]))         #SAME
  126.             framebuffer[i+j*width] = cast_ray(np.array([0,0,0]), dir, spheres, lights)
  127.        
  128.     with open('out.ppm', 'w', encoding="utf-8") as ofs:
  129.         ofs.write(f"P3\n{width} {height}\n255\n")
  130.         for i in range(height*width):
  131.             c = framebuffer[i]
  132.             maximum = max(c[0], max(c[1], c[2]))
  133.             if (maximum > 1): framebuffer[i] = c * (1.0 / maximum)
  134.             for j in range(3):
  135.                 char = int((255 * max(0, min(1, framebuffer[i][j]))))
  136.                 ofs.write(str(char)+' ')
  137.             ofs.write('\n')
  138.  
  139. if __name__ == "__main__":
  140.    
  141.     ivory =         Material(1.0, np.array([0.6,  0.3, 0.1, 0.0]), np.array([0.4, 0.4, 0.3]), 50.0)
  142.     red_rubber =    Material(1.0, np.array([0.9,  0.1, 0.0, 0.0]), np.array([0.3, 0.1, 0.1]), 10.0)
  143.     mirror =        Material(1.0, np.array([0.0, 10.0, 0.8, 0.0]), np.array([1.0, 1.0, 1.0]), 1425.0)
  144.     glass =         Material(1.5, np.array([0.0,  0.5, 0.1, 0.8]), np.array([0.6, 0.7, 0.8]), 125.0)
  145.    
  146.     spheres = []
  147.     spheres.append(Sphere(np.array([  -3,    0, -16]), 2, ivory))
  148.     spheres.append(Sphere(np.array([-1.0, -1.5, -12]), 2, glass))
  149.     spheres.append(Sphere(np.array([ 1.5, -0.5, -18]), 3, red_rubber))
  150.     spheres.append(Sphere(np.array([   7,    5, -18]), 4, mirror))
  151.  
  152.     lights = []
  153.     lights.append(Light(np.array([-20, 20,  20]), 1.5))
  154.     lights.append(Light(np.array([ 30, 50, -25]), 1.8))
  155.     lights.append(Light(np.array([ 30, 20,  30]), 1.7))
  156.     if len(sys.argv) < 3: print("Not enough arguments");exit()
  157.     render(spheres, lights, int(sys.argv[1]), int(sys.argv[2]))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement