def scatter(self, ray_in: Ray, hit_record: hit_record): outward_normal = Vector3(1, 1, 1) reflected = ray_in.direction().reflect(hit_record.normal) ni_over_nt = 0 attenuation = Vector3(1, 1, 1) scattered = Vector3(0, 0, 0) reflect_prob = 0 cosine = 0 if (ray_in.direction().dot(hit_record.normal) > 0): outward_normal = Vector3(0, 0, 0) - hit_record.normal ni_over_nt = self.ref_idx cosine = self.ref_idx * ray_in.direction().dot( hit_record.normal) / ray_in.direction().length() else: outward_normal = hit_record.normal ni_over_nt = 1 / self.ref_idx cosine = -ray_in.direction().dot( hit_record.normal) / ray_in.direction().length() is_scattered, refracted = refract(ray_in.direction(), outward_normal, ni_over_nt) if (is_scattered): reflect_prob = schlick(cosine, self.ref_idx) else: scattered = Ray(hit_record.p, reflected) reflect_prob = 1.0 if (random.uniform(0, 1) < reflect_prob): scattered = Ray(hit_record.p, reflected) else: scattered = Ray(hit_record.p, refracted) return (True, scattered, attenuation)
def scatter(self, r_in: Ray, hit_rec): if r_in.direction.dot(hit_rec.normal) > 0: outward_normal = -hit_rec.normal n1_n2 = self.ref_idx cosine = self.ref_idx * r_in.direction.dot( hit_rec.normal) / r_in.direction.norm() else: outward_normal = hit_rec.normal n1_n2 = self.inv_ref_idx cosine = -r_in.direction.dot( hit_rec.normal) / r_in.direction.norm() ret_val, refracted = refract(r_in.direction, outward_normal, n1_n2) if ret_val: reflect_prob = schlick(cosine, self.ref_idx) else: reflect_prob = 1.0 if random.random() < reflect_prob: reflected = reflect(unit_vector(r_in.direction), hit_rec.normal) scattered_ray = Ray(hit_rec.p, reflected) else: scattered_ray = Ray(hit_rec.p, refracted) return True, scattered_ray, self.attenuation
def scatter(self, ray, rec, srec): srec.is_specular = True srec.pdf_ptr = 0 srec.attenuation = Vec3(1.0, 1.0, 1.0) outward_normal = Vec3() reflected = ray.dir.reflect(rec.normal) refracted = Vec3() if ray.dir.dot(rec.normal) > 0: outward_normal = -rec.normal ni_over_nt = self.ref_idx cosine = self.ref_idx * ray.dir.dot(rec.normal) / ray.dir.length() else: outward_normal = rec.normal ni_over_nt = 1.0 / self.ref_idx cosine = -ray.dir.dot(rec.normal) / ray.dir.length() if self.refract(ray.dir, outward_normal, ni_over_nt, refracted): reflect_prob = self.schlick(cosine, self.ref_idx) else: reflect_prob = 1.0 if random() < reflect_prob: srec.specular_ray = Ray(rec.p, reflected) else: srec.specular_ray = Ray(rec.p, refracted) return True
def testTransformation(self): plane = Plane(transformation=rotation_y(angle_deg=90.0)) ray1 = Ray(origin=Point(1, 0, 0), dir=-VEC_X) intersection1 = plane.ray_intersection(ray1) assert intersection1 assert HitRecord( world_point=Point(0.0, 0.0, 0.0), normal=Normal(1.0, 0.0, 0.0), surface_point=Vec2d(0.0, 0.0), t=1.0, ray=ray1, material=plane.material, ).is_close(intersection1) ray2 = Ray(origin=Point(0, 0, 1), dir=VEC_Z) intersection2 = plane.ray_intersection(ray2) assert not intersection2 ray3 = Ray(origin=Point(0, 0, 1), dir=VEC_X) intersection3 = plane.ray_intersection(ray3) assert not intersection3 ray4 = Ray(origin=Point(0, 0, 1), dir=VEC_Y) intersection4 = plane.ray_intersection(ray4) assert not intersection4
def getColor(ray, depth): result = np.array([0, 0, 0], float) if depth == 0: return result hit = getClose(ray) if hit is None: return result # Phong for l in lights: p = hit.p + hit.n / 1000 if getShadow(Ray(p, l.pos - p)): continue lm = (l.pos - hit.p).normalized() kl = lm.dot(hit.n) if kl > 0: result += kl * hit.m.d * l.m.d * hit.c kv = -ray.v.dot(2 * kl * hit.n - lm) if kv > 0: result += (kv**hit.m.sh) * hit.m.s * l.m.s result += ia * hit.m.a * hit.c if hit.m.r > 0: reflect = -2 * ray.v.dot(hit.n) * hit.n + ray.v result += hit.m.r * getColor(Ray(hit.p + hit.n / 1000, reflect), depth - 1) return result
def scatter(self, r_in, rec): outward_normal = Vec3() reflected = Vec3.reflect(r_in.direction, rec["normal"]) ni_over_nt = 0.0 attenuation = Vec3(1.0, 1.0, 1.0) cosine = 0.0 if Vec3.dot(r_in.direction, rec["normal"]) > 0: outward_normal = -rec["normal"] ni_over_nt = self.ref_idx cosine = self.ref_idx * Vec3.dot( r_in.direction, rec["normal"]) / r_in.direction.length() else: outward_normal = rec["normal"] ni_over_nt = 1.0 / self.ref_idx cosine = -Vec3.dot(r_in.direction, rec["normal"]) / r_in.direction.length() scattered = Ray() reflect_prob = 0.0 refracted = Vec3.refract(r_in.direction, outward_normal, ni_over_nt) if refracted is not None: reflect_prob = schlick(cosine, self.ref_idx) else: reflect_prob = 1.0 if random() < reflect_prob: scattered = Ray(rec["p"], reflected) else: scattered = Ray(rec["p"], refracted) return attenuation, scattered
def trace_non_diffuse(self, ray, scene): self.__scene = scene hit_object, hit_point, hit_normal = self.__intersect(ray) traced_color = Vector3() reflection_ray = Ray(Vector3(), Vector3(), 0) refraction_ray = Ray(Vector3(), Vector3(), 0) fresnel = 0 if hit_object is None: return reflection_ray, refraction_ray, fresnel if not hit_object.material.is_diffuse: inside = ray.direction.dot(hit_normal) > 0 if inside: hit_normal = -hit_normal facing_ratio = -ray.direction.dot(hit_normal) fresnel = self.__mix((1 - facing_ratio)**2, 1, 0.1) reflection_ray = Ray(hit_point + self.__bias * hit_normal, ray.direction.reflect(hit_normal).normalize()) refraction = Vector3() # transparent? if hit_object.material.transparency > 0: from_ior = ray.current_ior if inside else hit_object.material.ior to_ior = hit_object.material.ior if inside else ray.current_ior refraction_ray = Ray( hit_point - self.__bias * hit_normal, ray.direction.refract(from_ior, to_ior, hit_normal).normalize()) # mix according to fresnel return reflection_ray, refraction_ray, fresnel
def __trace_non_diffuse(self, ray, hit_object, hit_point, hit_normal, depth): """Traces color of an object with refractive/reflective material""" inside = ray.direction.dot(hit_normal) > 0 if inside: hit_normal = -hit_normal # corret the normal vector in case it points inside of the sphere facing_ratio = -ray.direction.dot(hit_normal) fresnel = self.__mix((1 - facing_ratio)**2, 1, 0.1) reflection_ray = Ray(hit_point + self.__bias * hit_normal, ray.direction.reflect(hit_normal).normalize()) reflection = self.__trace_recursively(reflection_ray, depth + 1) refraction = Vector3() # transparent? if hit_object.material.transparency > 0: from_ior = ray.current_ior if inside else hit_object.material.ior to_ior = hit_object.material.ior if inside else ray.current_ior refraction_ray = Ray( hit_point - self.__bias * hit_normal, ray.direction.refract(from_ior, to_ior, hit_normal).normalize()) refraction = self.__trace_recursively(refraction_ray, depth + 1) # mix according to fresnel return ((reflection * fresnel + refraction * (1 - fresnel) * hit_object.material.transparency).mul_comp( hit_object.material.surface_color))
def shadow_ray_pass(ray, rng, indirectPass=False): r = ray L = Vector3() F = Vector3(1.0, 1.0, 1.0) while True: hit, id = intersect(r) if (not hit): return L shape = spheres[id] p = r(r.tmax) n = (p - shape.p).normalize() if indirectPass: if shape.reflection_t == Sphere.Reflection_t.DIFFUSE: F *= (shape.f / math.pi) else: F *= shape.f if shape.name == "light": return shape.e # Bounce another ray if the surface is reflective or refractive if shape.reflection_t == Sphere.Reflection_t.SPECULAR: d = ideal_specular_reflect(r.d, n) r = Ray(p, d, tmin=Sphere.EPSILON, depth=r.depth + 1) continue elif shape.reflection_t == Sphere.Reflection_t.REFRACTIVE: d, pr = ideal_specular_transmit(r.d, n, REFRACTIVE_INDEX_OUT, REFRACTIVE_INDEX_IN, rng) F *= pr r = Ray(p, d, tmin=Sphere.EPSILON, depth=r.depth + 1) continue light_list = lights() for light in light_list: #get the spherical angle of the light projected from the point on the surface light_vec = light.p - p cos_projected_angle = math.sqrt(1 - (pow(light.r, 2) / light_vec.norm2_squared())) projected_angle = math.acos(cos_projected_angle) shadow_ray_dir = unit_hemisphere_vector( light_vec, rng.uniform_float() * projected_angle, rng.uniform_range_float(0, 2 * math.pi)) shadow_ray_vec = Ray(p, shadow_ray_dir, tmin=Sphere.EPSILON, depth=r.depth + 1) inv_shadow_ray_pdf = 2 * math.pi * (1 - cos_projected_angle) hit, id = intersect(shadow_ray_vec) if spheres[id] != light: continue else: cos_incident_angle_surface = shadow_ray_vec.d.dot(n) L += F * light.e * cos_incident_angle_surface * inv_shadow_ray_pdf * math.pi return L
def scatter(self, ray, hitrec): reflected = reflect(ray.direction, hitrec.normal) if (np.dot(ray.direction, hitrec.normal) > 0.0): outward_normal = -hitrec.normal ni_over_nt = self.ref_idx cosine = self.ref_idx * np.dot(ray.direction, hitrec.normal) / ( math.sqrt(np.sum(ray.direction * ray.direction))) else: outward_normal = hitrec.normal ni_over_nt = 1.0 / self.ref_idx cosine = -np.dot(ray.direction, hitrec.normal) / (math.sqrt( np.sum(ray.direction * ray.direction))) is_refract, refracted = refract(ray.direction, outward_normal, ni_over_nt) if (is_refract): reflect_prob = schlick(cosine, self.ref_idx) else: reflect_prob = 1.0 if (random.uniform(0.0, 1.0) < reflect_prob): scattered = Ray(hitrec.p, reflected) else: scattered = Ray(hitrec.p, refracted) return True, scattered, np.array([1.0, 1.0, 1.0])
def scatter(self, ray_in, rec): attenuation = Vec3(1.0, 1.0, 1.0) if rec.frontFacing: etai_over_etat = 1 / self.refractive_index else: etai_over_etat = self.refractive_index unit_direction = ray_in.direction.norm() cos_theta = min(dot(rec.normal, -unit_direction), 1.0) sin_theta = math.sqrt(1.0 - cos_theta * cos_theta) if etai_over_etat * sin_theta > 1.0: reflected = reflect(unit_direction, rec.normal) scattered = Ray(rec.position, reflected) return True, scattered, attenuation reflection_probability = schlick(cos_theta, etai_over_etat) if random.random() < reflection_probability: reflected = reflect(unit_direction, rec.normal) scattered = Ray(rec.position, reflected) return True, scattered, attenuation refracted = refract(unit_direction, rec.normal, etai_over_etat) scattered = Ray(rec.position, refracted) return True, scattered, attenuation
def albedo_pass(ray, rng): r = ray L = Vector3() F = Vector3(1.0, 1.0, 1.0) while True: hit, id = intersect(r) if (not hit): return L shape = spheres[id] p = r(r.tmax) n = (p - shape.p).normalize() if shape.reflection_t == Sphere.Reflection_t.DIFFUSE: F *= (shape.f / math.pi) else: F *= shape.f if shape.reflection_t == Sphere.Reflection_t.SPECULAR: d = ideal_specular_reflect(r.d, n) r = Ray(p, d, tmin=Sphere.EPSILON, depth=r.depth + 1) continue elif shape.reflection_t == Sphere.Reflection_t.REFRACTIVE: d, pr = ideal_specular_transmit(r.d, n, REFRACTIVE_INDEX_OUT, REFRACTIVE_INDEX_IN, rng) F *= pr r = Ray(p, d, tmin=Sphere.EPSILON, depth=r.depth + 1) continue elif shape.name == "light": L = shape.e return L else: L = F return L
def scatter(self, r_in: Ray, rec: hit_record): reflected: np.ndarray = reflect(r_in.direction(), rec.normal) attenuation = np.array((1, 1, 1), float) if np.dot(r_in.direction(), rec.normal) > 0: outward_normal = -rec.normal ni_over_nt = self.ref_idx cosine = self.ref_idx * np.dot(r_in.direction(), rec.normal) / np.linalg.norm( r_in.direction()) else: outward_normal = rec.normal ni_over_nt = 1.0 / self.ref_idx cosine = float(-np.dot(r_in.direction(), rec.normal) / np.linalg.norm(r_in.direction())) has_refracted, refracted = refract(r_in.direction(), outward_normal, ni_over_nt) if has_refracted: reflect_prob = schlick(cosine, self.ref_idx) else: reflect_prob = 1 if random() < reflect_prob: scattered = Ray(rec.p, reflected, r_in.time()) else: scattered = Ray(rec.p, refracted, r_in.time()) return True, attenuation, scattered
def testTransformation(self): sphere = Sphere(transformation=translation(Vec(10.0, 0.0, 0.0))) ray1 = Ray(origin=Point(10, 0, 2), dir=-VEC_Z) intersection1 = sphere.ray_intersection(ray1) assert intersection1 assert HitRecord( world_point=Point(10.0, 0.0, 1.0), normal=Normal(0.0, 0.0, 1.0), surface_point=Vec2d(0.0, 0.0), t=1.0, ray=ray1, material=sphere.material, ).is_close(intersection1) ray2 = Ray(origin=Point(13, 0, 0), dir=-VEC_X) intersection2 = sphere.ray_intersection(ray2) assert intersection2 assert HitRecord( world_point=Point(11.0, 0.0, 0.0), normal=Normal(1.0, 0.0, 0.0), surface_point=Vec2d(0.0, 0.5), t=2.0, ray=ray2, material=sphere.material, ).is_close(intersection2) # Check if the sphere failed to move by trying to hit the untransformed shape assert not sphere.ray_intersection( Ray(origin=Point(0, 0, 2), dir=-VEC_Z)) # Check if the *inverse* transformation was wrongly applied assert not sphere.ray_intersection( Ray(origin=Point(-10, 0, 0), dir=-VEC_Z))
def testHit(self): sphere = Sphere() ray1 = Ray(origin=Point(0, 0, 2), dir=-VEC_Z) intersection1 = sphere.ray_intersection(ray1) assert intersection1 assert HitRecord( world_point=Point(0.0, 0.0, 1.0), normal=Normal(0.0, 0.0, 1.0), surface_point=Vec2d(0.0, 0.0), t=1.0, ray=ray1, material=sphere.material, ).is_close(intersection1) ray2 = Ray(origin=Point(3, 0, 0), dir=-VEC_X) intersection2 = sphere.ray_intersection(ray2) assert intersection2 assert HitRecord( world_point=Point(1.0, 0.0, 0.0), normal=Normal(1.0, 0.0, 0.0), surface_point=Vec2d(0.0, 0.5), t=2.0, ray=ray2, material=sphere.material, ).is_close(intersection2) assert not sphere.ray_intersection( Ray(origin=Point(0, 10, 2), dir=-VEC_Z))
def test_is_close(self): ray1 = Ray(origin=Point(1.0, 2.0, 3.0), dir=Vec(5.0, 4.0, -1.0)) ray2 = Ray(origin=Point(1.0, 2.0, 3.0), dir=Vec(5.0, 4.0, -1.0)) ray3 = Ray(origin=Point(5.0, 1.0, 4.0), dir=Vec(3.0, 9.0, 4.0)) assert ray1.is_close(ray2) assert not ray1.is_close(ray3)
def to_3d_point(p1, p2): x1 = p1[0] y1 = p1[1] x2 = p2[0] y2 = p2[1] u1 = c1toGlobal.dot([(x1 - cx) / fx, (y1 - cy) / fy, 1, 0])[0:3] u2 = c2toGlobal.dot([(x2 - cx) / fx, (y2 - cy) / fy, 1, 0])[0:3] ray1 = Ray(c1Pos, u1) ray2 = Ray(c2Pos, u2) return ray1.find_closest_point(ray2)
def test_ray_misses_cylinder(self): cyl = Cylinder() rays = [ Ray(Point(1, 0, 0), Vector(0, 1, 0)), Ray(Point(0, 0, 0), Vector(0, 1, 0)), Ray(Point(0, 0, -5), Vector(1, 1, 1)) ] for ray in rays: direction = Vector.normalize(ray.direction) r = Ray(ray.origin, direction) xs = cyl.local_intersect(r) self.assertEqual(len(xs), 0)
def testUVCoordinates(self): plane = Plane() ray1 = Ray(origin=Point(0, 0, 1), dir=-VEC_Z) intersection1 = plane.ray_intersection(ray1) assert intersection1.surface_point.is_close(Vec2d(0.0, 0.0)) ray2 = Ray(origin=Point(0.25, 0.75, 1), dir=-VEC_Z) intersection2 = plane.ray_intersection(ray2) assert intersection2.surface_point.is_close(Vec2d(0.25, 0.75)) ray3 = Ray(origin=Point(4.25, 7.75, 1), dir=-VEC_Z) intersection3 = plane.ray_intersection(ray3) assert intersection3.surface_point.is_close(Vec2d(0.25, 0.75))
def scatter(self, r_in, p, n): v = r_in.direction() if v.dot(n) > 0: outward_normal = -n refrac_ratio = self.refrac_index else: outward_normal = n refrac_ratio = 1 / self.refrac_index v_refrac = refract(v, outward_normal, refrac_ratio) if v_refrac: return Ray(p, v_refrac), Vec3(1, 1, 1) else: return Ray(p, reflect(v, n)), Vec3(1, 1, 1)
def test_intersect3(self): origin = Point(0, 2, -5) direction = Vector(0, 0, 1) r = Ray(origin, direction) s = Sphere() xs = Intersections([]) self.assertTrue(s.intersect(r) == xs)
def ray_color(ray, background, world, lights, depth): '''Determine the color of a ray based on the objects in a scene (recursive with a max depth)''' rec = HitRecord() # Don't exceed ray bounce limit if depth <= 0: return Vec3(0, 0, 0) # If the ray hits nothing, return background color if not world.hit(ray, 0.001, float('inf'), rec): return background # Determine if the object emits light or has a specular material srec = ScatterRecord() emitted = rec.mat.emitted(ray, rec, rec.u, rec.v, rec.p) if not rec.mat.scatter(ray, rec, srec): return emitted if srec.is_specular: return srec.attenuation * ray_color(srec.specular_ray, background, world, lights, depth - 1) # Use PDFs to determine the next ray and call ray_color() again light_ptr = HittablePDF(lights, rec.p) p = MixturePDF(light_ptr, srec.pdf_ptr) scattered = Ray(rec.p, p.generate(), ray.time) pdf_val = p.value(scattered.dir) del srec.pdf_ptr return emitted + srec.attenuation * rec.mat.scattering_pdf( ray, rec, scattered) * ray_color(scattered, background, world, lights, depth - 1) / pdf_val
def test_ray_originate_inside_sphere(self): r = Ray(Point(0, 0, 0), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, -1.0) self.assertEqual(xs[1].t, 1.0)
def test_sphere_behind_ray(self): r = Ray(Point(0, 0, 5), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, -6.0) self.assertEqual(xs[1].t, -4.0)
def hard_shadow(ph, objects, l, dist_l): """ Determines if this point should have a shadow for the light in pl. Args: ph: 3D Point of hit objects([Object]): list of objects that can be between the point and the light l(numpy.array): unit vector pointing to the light dist_l(float): distance to the light Returns: numpy.array: The calculated color for this hard shadow (RGB) """ # Case outside of cone in SpotLight if np.array_equal(l, np.zeros(3)): return np.zeros(3) shadow_coef = 0 r = Ray(ph, l) for obj in objects: # Cast ray from ph to the object with n = l and shadow if t < dist_l t = r.intersect(obj) if 0 < t < dist_l: shadow_coef = 1 break shadow_color = np.zeros(3) # Use SHADOW_STRENGTH = 0 for no shadows and 1 for hard shadows shadow_coef *= max(0.0, min(SHADOW_STRENGTH, 1.0)) color = COLOR_FOR_LIGHT * (1 - shadow_coef) + shadow_color * shadow_coef return color
def ray_trace(self, ray, scene, depth=0): color = Color(0, 0, 0) #Find the nearest object hited by the ray in the scene dist_hit, obj_hit = self.find_nearest(ray, scene) if obj_hit is None: return color hit_pos = ray.origin + ray.direction * dist_hit #We calculate the normal at the hit position hit_normal = obj_hit.normal(hit_pos) color += self.color_at(obj_hit, hit_pos, hit_normal, scene) """ Making the reflections of the objects Finding the reflected rays In order to make the algorithm more effient We use Monte Carlo for this """ if probability(): if depth < self.MAX_DEPTH: new_ray_pos = hit_pos + hit_normal * self.MIN_DISPLACE new_ray_dir = ray.direction - 2 * ray.direction.dot_product( hit_normal) * hit_normal new_ray = Ray(new_ray_pos, new_ray_dir) # Attenuate the reflected ray by the reflection coefficient # calls the funtion of ray tracing again color += self.ray_trace( new_ray, scene, depth + 1) * obj_hit.material.reflection return color
def test_intersect(self): r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].object, s) self.assertEqual(xs[1].object, s)
def test_intersect_with_transform(self): # intersect a scaled sphere r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() s.transform = Matrix.scale(2, 2, 2) xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, 3) self.assertEqual(xs[1].t, 7) # intersect a translated sphere r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() s.transform = Matrix.translate(5, 0, 0) xs = s.intersect(r) self.assertEqual(len(xs), 0)
def test_group_hit_bounding_box(self): child = Shape.test_shape() shape = Group() shape.add_child(child) r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) xs = shape.intersect(r) self.assertIsNotNone(child.saved_ray)
def render(self, scene, hmin, hmax, part_file, rows_done): width = scene.width height = scene.height aspect_ratio = float(width) / height x0 = -1.0 x1 = +1.0 xstep = (x1 - x0) / (width - 1) y0 = -1.0 / aspect_ratio y1 = +1.0 / aspect_ratio ystep = (y1 - y0) / (height - 1) camera = scene.camera pixels = Image(width, hmax - hmin) for j in range(hmin, hmax): y = y0 + j * ystep for i in range(width): x = x0 + i * xstep ray = Ray(camera, Point(x, y) - camera) pixels.set_pixel(i, j - hmin, self.ray_trace(ray, scene)) # Update progress bar if rows_done: with rows_done.get_lock(): rows_done.value += 1 print( "{:3.0f}%".format(float(rows_done.value) / float(height) * 100), end="\r", ) with open(part_file, "w") as part_fileobj: pixels.write_ppm_raw(part_fileobj)