def random_scene(aspect_ratio: float, time0: float, time1: float) \ -> Tuple[BVHNode, Camera]: world = HittableList() # ground_material = Lambertian(SolidColor(0.5, 0.5, 0.5)) ground_material = Lambertian( CheckerTexture(SolidColor(0.2, 0.3, 0.1), SolidColor(0.9, 0.9, 0.9))) world.add(Sphere(Point3(0, -1000, 0), 1000, ground_material)) for a in range(-11, 11): for b in range(-11, 11): choose_mat = random_float() center = Point3(a + 0.9 * random_float(), 0.2, b + 0.9 * random_float()) if (center - Vec3(4, 0.2, 0)).length() > 0.9: if choose_mat < 0.6: # Diffuse albedo = Color.random() * Color.random() sphere_material_diffuse = Lambertian(SolidColor(albedo)) center2 = center + Vec3(0, random_float(0, 0.5), 0) world.add( MovingSphere(center, center2, 0, 1, 0.2, sphere_material_diffuse)) elif choose_mat < 0.8: # Metal albedo = Color.random(0.5, 1) fuzz = random_float(0, 0.5) sphere_material_metal = Metal(albedo, fuzz) world.add(Sphere(center, 0.2, sphere_material_metal)) else: # Glass sphere_material_glass = Dielectric(1.5) world.add(Sphere(center, 0.2, sphere_material_glass)) material_1 = Dielectric(1.5) world.add(Sphere(Point3(0, 1, 0), 1, material_1)) material_2 = Lambertian(SolidColor(0.4, 0.2, 0.1)) world.add(Sphere(Point3(-4, 1, 0), 1, material_2)) material_3 = Metal(Color(0.7, 0.6, 0.5), 0) world.add(Sphere(Point3(4, 1, 0), 1, material_3)) world_bvh = BVHNode(world.objects, time0, time1) lookfrom = Point3(13, 2, 3) lookat = Point3(0, 0, 0) vup = Vec3(0, 1, 0) vfov = 20 dist_to_focus: float = 10 aperture: float = 0.1 cam = Camera(lookfrom, lookat, vup, vfov, aspect_ratio, aperture, dist_to_focus, time0, time1) return world_bvh, cam
def scan_line(j: int, background: Color, world: BVHNode, cam: Camera, image_width: int, image_height: int, samples_per_pixel: int, max_depth: int) -> Img: img = Img(image_width, 1) for i in range(image_width): pixel_color = Color(0, 0, 0) for s in range(samples_per_pixel): u: float = (i + random_float()) / (image_width - 1) v: float = (j + random_float()) / (image_height - 1) r: Ray = cam.get_ray(u, v) pixel_color += ray_color(r, background, world, max_depth) img.write_pixel(i, 0, pixel_color, samples_per_pixel) print(f"Scanlines remaining: {j} ", end="\r") return img
def hit(self, r: Ray, t_min: float, t_max: float) -> Optional[HitRecord]: rec1 = self.boundary.hit(r, -np.inf, np.inf) if rec1 is None: return None rec2 = self.boundary.hit(r, rec1.t + 0.0001, np.inf) if rec2 is None: return None if rec1.t < t_min: rec1.t = t_min if rec1.t < 0: rec1.t = 0 if rec2.t > t_max: rec2.t = t_max if rec1.t >= rec2.t: return None ray_length = r.direction().length() distance_inside_boundary = (rec2.t - rec1.t) * ray_length hit_distance = self.neg_inv_density * np.log(random_float()) if hit_distance > distance_inside_boundary: return None t = rec1.t + hit_distance / ray_length p = r.at(t) rec = HitRecord(p, t, self.phase_function) rec.normal = Vec3(1, 0, 0) rec.front_face = True return rec
def scatter(self, r_in: Ray, rec: HitRecord) \ -> Optional[Tuple[Ray, Color]]: if rec.front_face: etai_over_etat = 1 / self.ref_idx else: etai_over_etat = self.ref_idx unit_direction: Vec3 = r_in.direction().unit_vector() cos_theta: float = min(-unit_direction @ rec.normal, 1) sin_theta: float = np.sqrt(1 - cos_theta**2) reflect_prob: float = self.schlick(cos_theta, etai_over_etat) if etai_over_etat * sin_theta > 1 or random_float() < reflect_prob: # total internal reflection reflected: Vec3 = unit_direction.reflect(rec.normal) scattered = Ray(rec.p, reflected) else: # refraction refracted: Vec3 = unit_direction.refract( rec.normal, etai_over_etat ) scattered = Ray(rec.p, refracted) attenuation = Color(1, 1, 1) return scattered, attenuation
def random_in_unit_sphere() -> Vec3: """ This method is modified from: https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/#better-choice-of-spherical-coordinates """ u = random_float() v = random_float() theta = u * 2 * cp.pi phi = cp.arccos(2 * v - 1) r = cp.cbrt(random_float()) sinTheta = cp.sin(theta) cosTheta = cp.cos(theta) sinPhi = cp.sin(phi) cosPhi = cp.cos(phi) x = r * sinPhi * cosTheta y = r * sinPhi * sinTheta z = r * cosPhi return Vec3(x, y, z)
def get_ray(self, s: float, t: float) -> Ray: rd: Vec3 = Vec3.random_in_unit_disk() * self.lens_radius offset: Vec3 = self.u * rd.x() + self.v * rd.y() return Ray( self.origin + offset, (self.lower_left_corner + self.horizontal*s + self.vertical*t - self.origin - offset), random_float(self.time0, self.time1) )
def random_scene() -> HittableList: world = HittableList() ground_material = Lambertian(Color(0.5, 0.5, 0.5), 1) world.add(Sphere(Point3(0, -1000, 0), 1000, ground_material)) sphere_material_glass = Dielectric(1.5, 2) for a in range(-11, 11): for b in range(-11, 11): choose_mat = random_float() center = Point3(a + 0.9 * random_float(), 0.2, b + 0.9 * random_float()) if (center - Vec3(4, 0.2, 0)).length() > 0.9: idx = (a * 22 + b) + (11 * 22 + 11) + 6 if choose_mat < 0.6: # Diffuse albedo = Color.random() * Color.random() sphere_material_diffuse = Lambertian(albedo, idx) world.add(Sphere(center, 0.2, sphere_material_diffuse)) elif choose_mat < 0.8: # Metal albedo = Color.random(0.5, 1) fuzz = random_float(0, 0.5) sphere_material_metal = Metal(albedo, fuzz, idx) world.add(Sphere(center, 0.2, sphere_material_metal)) else: # Glass world.add(Sphere(center, 0.2, sphere_material_glass)) material_1 = Dielectric(1.5, 3) world.add(Sphere(Point3(0, 1, 0), 1, material_1)) material_2 = Lambertian(Color(0.4, 0.2, 0.1), 4) world.add(Sphere(Point3(-4, 1, 0), 1, material_2)) material_3 = Metal(Color(0.7, 0.6, 0.5), 0, 5) world.add(Sphere(Point3(4, 1, 0), 1, material_3)) return world
def random_in_unit_disk(): while True: p = Vec3(random_float(-1, 1), random_float(-1, 1), 0) if p.length_squared() >= 1: continue return p
def random_unit_vector() -> Vec3: a: float = random_float(0, 2 * np.pi) z: float = random_float(-1, 1) r: float = np.sqrt(1 - z**2) return Vec3(r * np.cos(a), r * np.sin(a), z)
def final_scene(aspect_ratio: float, time0: float, time1: float) \ -> Tuple[BVHNode, Camera]: world = HittableList() # Ground boxes1 = HittableList() ground = Lambertian(SolidColor(0.48, 0.83, 0.53)) boxes_per_side = 20 for i in range(boxes_per_side): for j in range(boxes_per_side): w = 100 x0 = -1000 + i * w z0 = -1000 + j * w y0 = 0 x1 = x0 + w y1 = random_float(1, 101) z1 = z0 + w boxes1.add(Box(Point3(x0, y0, x0), Point3(x1, y1, z1), ground)) world.add(BVHNode(boxes1.objects, time0, time1)) # Light light = DiffuseLight(SolidColor(7, 7, 7)) world.add(XZRect(123, 423, 147, 412, 554, light)) # Moving sphere center1 = Point3(400, 400, 200) center2 = center1 + Vec3(30, 0, 0) moving_sphere_material = Lambertian(SolidColor(0.7, 0.3, 0.1)) world.add(MovingSphere(center1, center2, 0, 1, 50, moving_sphere_material)) # Dielectric & metal balls world.add(Sphere(Point3(260, 150, 45), 50, Dielectric(1.5))) world.add( Sphere(Point3(0, 150, 145), 50, Metal(Color(0.8, 0.8, 0.9), 10.0))) # The subsurface reflection sphere boundary = Sphere(Point3(360, 150, 145), 70, Dielectric(1.5)) world.add(boundary) world.add(ConstantMedium(boundary, 0.2, SolidColor(0.2, 0.4, 0.9))) # Big thin mist mist = Sphere(Point3(0, 0, 0), 5000, Dielectric(1.5)) world.add(ConstantMedium(mist, 0.0001, SolidColor(1, 1, 1))) # Earth and marble ball emat = Lambertian(ImageTexture("earthmap.jpg")) world.add(Sphere(Point3(400, 200, 400), 100, emat)) pertext = NoiseTexture(0.1) world.add(Sphere(Point3(220, 280, 300), 80, Lambertian(pertext))) # Foam boxes2 = HittableList() white = Lambertian(SolidColor(0.73, 0.73, 0.73)) ns = 1000 for j in range(ns): boxes2.add(Sphere(Point3.random(0, 165), 10, white)) world.add( Translate(RotateY(BVHNode(boxes2.objects, time0, time1), 15), Vec3(-100, 270, 395))) world_bvh = BVHNode(world.objects, time0, time1) lookfrom = Point3(478, 278, -600) lookat = Point3(278, 278, 0) vup = Vec3(0, 1, 0) vfov = 40 dist_to_focus: float = 10 aperture: float = 0 cam = Camera(lookfrom, lookat, vup, vfov, aspect_ratio, aperture, dist_to_focus, time0, time1) return world_bvh, cam