コード例 #1
0
def ray_color(r: RayList, world: HittableList, depth: int) \
        -> Tuple[Optional[RayList], Optional[Vec3List], Vec3List]:
    length = len(r)
    if not r.direction().e.any():
        return None, None, Vec3List.new_zero(length)

    # Calculate object hits
    rec_list: HitRecordList = world.hit(r, 0.001, cp.inf)

    # Useful empty arrays
    empty_vec3list = Vec3List.new_zero(length)
    empty_array_float = cp.zeros(length, cp.float32)
    empty_array_bool = cp.zeros(length, cp.bool)
    empty_array_int = cp.zeros(length, cp.int32)

    # Background / Sky
    unit_direction = r.direction().unit_vector()
    sky_condition = Vec3List.from_array((unit_direction.length() > 0)
                                        & (rec_list.material == 0))
    t = (unit_direction.y() + 1) * 0.5
    blue_bg = (Vec3List.from_vec3(Color(1, 1, 1), length).mul_ndarray(1 - t) +
               Vec3List.from_vec3(Color(0.5, 0.7, 1), length).mul_ndarray(t))
    result_bg = Vec3List(cp.where(sky_condition.e, blue_bg.e,
                                  empty_vec3list.e))
    if depth <= 1:
        return None, None, result_bg

    # Material scatter calculations
    materials: Dict[int, Material] = world.get_materials()
    scattered_list = RayList.new_zero(length)
    attenuation_list = Vec3List.new_zero(length)
    for mat_idx in materials:
        mat_condition = (rec_list.material == mat_idx)
        mat_condition_3 = Vec3List.from_array(mat_condition)
        if not mat_condition.any():
            continue

        ray = RayList(
            Vec3List(cp.where(mat_condition_3.e, r.orig.e, empty_vec3list.e)),
            Vec3List(cp.where(mat_condition_3.e, r.dir.e, empty_vec3list.e)))
        rec = HitRecordList(
            Vec3List(
                cp.where(mat_condition_3.e, rec_list.p.e, empty_vec3list.e)),
            cp.where(mat_condition, rec_list.t, empty_array_float),
            cp.where(mat_condition, rec_list.material, empty_array_int),
            Vec3List(
                cp.where(mat_condition_3.e, rec_list.normal.e,
                         empty_vec3list.e)),
            cp.where(mat_condition, rec_list.front_face, empty_array_bool))
        ray, rec, idx_list = compress(ray, rec)

        scattered, attenuation = materials[mat_idx].scatter(ray, rec)
        scattered, attenuation = decompress(scattered, attenuation, idx_list,
                                            length)
        scattered_list += scattered
        attenuation_list += attenuation

    return scattered_list, attenuation_list, result_bg
コード例 #2
0
def ray_color(r, world, depth):
    length = len(r)
    if not r.direction().e.any():
        return None, None, Vec3List.new_zero(length)

    rec_list = world.hit(r, 0.001, cp.inf)

    empty_vec3list = Vec3List.new_zero(length)
    empty_array_float = cp.zeros(length, cp.float32)
    empty_array_bool = cp.zeros(length, cp.bool)
    empty_array_int = cp.zeros(length, cp.int32)

    unit_direction = r.direction().unit_vector()
    sky_condition = Vec3List.from_array((unit_direction.length() > 0)
                                        & (rec_list.material == 0))
    t = (unit_direction.y() + 1) * 0.5
    blue_bg = (Vec3List.from_vec3(Color(1, 1, 1), length).mul_ndarray(1 - t) +
               Vec3List.from_vec3(Color(0.5, 0.7, 1), length).mul_ndarray(t))
    result_bg = Vec3List(cp.where(sky_condition.e, blue_bg.e,
                                  empty_vec3list.e))
    if depth <= 1:
        return None, None, result_bg

    materials = world.get_materials()
    scattered_list = RayList.new_zero(length)
    attenuation_list = Vec3List.new_zero(length)
    for mat_idx in materials:
        mat_condition = (rec_list.material == mat_idx)
        mat_condition_3 = Vec3List.from_array(mat_condition)
        if not mat_condition.any():
            continue

        ray = RayList(
            Vec3List(
                cp.where(mat_condition_3.e,
                         r.origin().e, empty_vec3list.e)),
            Vec3List(
                cp.where(mat_condition_3.e,
                         r.direction().e, empty_vec3list.e)))
        rec = HitRecordList(
            Vec3List(
                cp.where(mat_condition_3.e, rec_list.p.e, empty_vec3list.e)),
            cp.where(mat_condition, rec_list.t, empty_array_float),
            cp.where(mat_condition, rec_list.material, empty_array_int),
            Vec3List(
                cp.where(mat_condition_3.e, rec_list.normal.e,
                         empty_vec3list.e)),
            cp.where(mat_condition, rec_list.front_face, empty_array_bool))
        ray, rec, idx_list = compress(ray, rec)

        scattered, attenuation = materials[mat_idx].scatter(ray, rec)
        scattered, attenuation = decompress(scattered, attenuation, idx_list,
                                            length)
        scattered_list += scattered
        attenuation_list += attenuation

    return scattered_list, attenuation_list, result_bg
コード例 #3
0
def three_ball_scene() -> HittableList:
    world = HittableList()
    world.add(Sphere(Point3(0, 0, -1), 0.5, Lambertian(Color(0.1, 0.2, 0.5))))
    world.add(
        Sphere(Point3(0, -100.5, -1), 100, Lambertian(Color(0.8, 0.8, 0))))
    world.add(Sphere(Point3(1, 0, -1), 0.5, Metal(Color(0.8, 0.6, 0.2), 0.3)))
    world.add(Sphere(Point3(-1, 0, -1), 0.5, Dielectric(1.5)))
    world.add(Sphere(Point3(-1, 0, -1), -0.45, Dielectric(1.5)))
    return world
コード例 #4
0
ファイル: scenes.py プロジェクト: SteveHawk/RayTracing-Py
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
コード例 #5
0
def three_ball_scene():
    world = HittableList()
    world.add(
        Sphere(Point3(0, 0, -1), 0.5, Lambertian(Color(0.1, 0.2, 0.5), 1)))
    world.add(
        Sphere(Point3(0, -100.5, -1), 100, Lambertian(Color(0.8, 0.8, 0), 2)))
    world.add(
        Sphere(Point3(1, 0, -1), 0.5, Metal(Color(0.8, 0.6, 0.2), 0.3, 3)))
    material_dielectric = Dielectric(1.5, 4)
    world.add(Sphere(Point3(-1, 0, -1), 0.5, material_dielectric))
    world.add(Sphere(Point3(-1, 0, -1), -0.45, material_dielectric))
    return world
コード例 #6
0
def ray_color(r: Ray, world: HittableList, depth: int) -> Color:
    if depth <= 0:
        return Color(0, 0, 0)

    rec: Optional[HitRecord] = world.hit(r, 0.001, np.inf)
    if rec is not None:
        scatter_result = rec.material.scatter(r, rec)
        if scatter_result is not None:
            scattered, attenuation = scatter_result
            return attenuation * ray_color(scattered, world, depth - 1)
        return Color(0, 0, 0)

    unit_direction: Vec3 = r.direction().unit_vector()
    t = (unit_direction.y() + 1) * 0.5
    return Color(1, 1, 1) * (1 - t) + Color(0.5, 0.7, 1) * t
コード例 #7
0
ファイル: scenes.py プロジェクト: SteveHawk/RayTracing-Py
def three_ball_scene(aspect_ratio: float, time0: float, time1: float) \
        -> Tuple[BVHNode, Camera]:
    world = HittableList()
    world.add(
        Sphere(Point3(0, 0, -1), 0.5, Lambertian(SolidColor(0.1, 0.2, 0.5))))
    world.add(
        Sphere(Point3(0, -100.5, -1), 100, Lambertian(SolidColor(0.8, 0.8,
                                                                 0))))
    world.add(Sphere(Point3(1, 0, -1), 0.5, Metal(Color(0.8, 0.6, 0.2), 0.3)))
    world.add(Sphere(Point3(-1, 0, -1), 0.5, Dielectric(1.5)))
    world.add(Sphere(Point3(-1, 0, -1), -0.45, Dielectric(1.5)))
    world_bvh = BVHNode(world.objects, time0, time1)

    lookfrom = Point3(3, 3, 2)
    lookat = Point3(0, 0, -1)
    vup = Vec3(0, 1, 0)
    vfov = 20
    dist_to_focus: float = (lookfrom - lookat).length()
    aperture: float = 0
    cam = Camera(lookfrom, lookat, vup, vfov, aspect_ratio, aperture,
                 dist_to_focus, 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
コード例 #8
0
ファイル: material.py プロジェクト: SteveHawk/RayTracing-Py
    def scatter(self, r_in: RayList, rec: HitRecordList) \
            -> Tuple[RayList, Vec3List]:
        etai_over_etat = cp.where(
            rec.front_face, 1 / self.ref_idx, self.ref_idx
        )

        unit_direction = r_in.direction().unit_vector()
        cos_theta = -unit_direction @ rec.normal
        cos_theta = cp.where(cos_theta > 1, 1, cos_theta)
        sin_theta = cp.sqrt(1 - cos_theta**2)
        reflect_prob = self.schlick(cos_theta, etai_over_etat)

        reflect_condition = (
            (etai_over_etat * sin_theta > 1)
            | (random_float_list(len(r_in)) < reflect_prob)
        )
        # total internal reflection
        reflected = (unit_direction.mul_ndarray(reflect_condition)).reflect(
            rec.normal.mul_ndarray(reflect_condition)
        )
        # refraction
        refracted = (unit_direction.mul_ndarray(~reflect_condition)).refract(
            rec.normal.mul_ndarray(~reflect_condition), etai_over_etat
        )
        direction = reflected + refracted

        condition = rec.t > 0
        scattered = RayList(
            rec.p.mul_ndarray(condition),
            direction.mul_ndarray(condition)
        )
        attenuation = Vec3List.from_array(condition) * Color(1, 1, 1)
        return scattered, attenuation
コード例 #9
0
    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
コード例 #10
0
ファイル: main.py プロジェクト: SteveHawk/RayTracing-Py
def main() -> None:
    aspect_ratio = 1
    image_width = 256
    image_height = int(image_width / aspect_ratio)
    samples_per_pixel = 20
    max_depth = 10
    time0 = 0
    time1 = 1

    world, cam = scenes.final_scene(aspect_ratio, time0, time1)
    background = Color(0, 0, 0)

    print("Start rendering.")
    start_time = time.time()

    n_processer = multiprocessing.cpu_count()
    img_list: List[Img] = Parallel(n_jobs=n_processer, verbose=10)(
        delayed(scan_line)(
            j, background, world, cam,
            image_width, image_height,
            samples_per_pixel, max_depth
        ) for j in range(image_height-1, -1, -1)
    )

    final_img = Img(image_width, image_height)
    final_img.set_array(
        np.concatenate([img.frame for img in img_list])
    )

    end_time = time.time()
    print(f"\nDone. Total time: {round(end_time - start_time, 1)} s.")
    final_img.save("./output.png", True)
コード例 #11
0
ファイル: texture.py プロジェクト: SteveHawk/RayTracing-Py
 def __init__(self,
              r: Union[float, Color],
              g: float = None,
              b: float = None) -> None:
     if isinstance(r, Color):
         self.color_value = r
     elif g is not None and b is not None:
         self.color_value = Color(r, g, b)
     else:
         raise ValueError
コード例 #12
0
ファイル: texture.py プロジェクト: SteveHawk/RayTracing-Py
    def value(self, u: float, v: float, p: Vec3) -> Color:
        u = np.clip(u, 0, 1)
        v = 1 - np.clip(v, 0, 1)

        i = int(u * self.width)
        j = int(v * self.height)
        if i >= self.width:
            i = self.width - 1
        if j >= self.height:
            j = self.height - 1

        pixel = self.data[j][i] / 255
        return Color(*pixel)
コード例 #13
0
ファイル: main.py プロジェクト: SteveHawk/RayTracing-Py
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
コード例 #14
0
ファイル: main.py プロジェクト: SteveHawk/RayTracing-Py
def scan_line(j: int, world: HittableList, cam: Camera, image_width: int,
              image_height: int, samples_per_pixel: int,
              max_depth: int) -> Img:
    img = Img(image_width, 1)
    row_pixel_color = Vec3List.from_vec3(Color(), image_width)

    for s in range(samples_per_pixel):
        u: np.ndarray = (random_float_list(image_width) +
                         np.arange(image_width)) / (image_width - 1)
        v: np.ndarray = (random_float_list(image_width) + j) / (image_height -
                                                                1)
        r: RayList = cam.get_ray(u, v)
        row_pixel_color += ray_color(r, world, max_depth)

    img.write_pixel_list(0, row_pixel_color, samples_per_pixel)
    return img
コード例 #15
0
ファイル: main.py プロジェクト: SteveHawk/RayTracing-Py
def ray_color(r: Ray, background: Color, world: BVHNode, depth: int) -> Color:
    # Bounce limit
    if depth <= 0:
        return Color(0, 0, 0)

    rec: Optional[HitRecord] = world.hit(r, 0.001, np.inf)

    # Ray hits nothing
    if rec is None:
        return background

    emitted = rec.material.emitted(rec.u, rec.v, rec.p)
    scatter_result = rec.material.scatter(r, rec)

    # No scattered ray (could be emissive material)
    if scatter_result is None:
        return emitted

    scattered, attenuation = scatter_result
    return (emitted + (
        attenuation * ray_color(scattered, background, world, depth-1)
    ))
コード例 #16
0
ファイル: main.py プロジェクト: SteveHawk/RayTracing-Py
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
コード例 #17
0
 def emitted(self, u: float, v: float, p: Point3) -> Color:
     return Color(0, 0, 0)
コード例 #18
0
ファイル: main.py プロジェクト: SteveHawk/RayTracing-Py
def ray_color(r: RayList, world: HittableList, depth: int) -> Vec3List:
    length = len(r)
    if not r.direction().e.any():
        return Vec3List.new_zero(length)

    # Calculate object hits
    rec_list: HitRecordList = world.hit(r, 0.001, np.inf)

    # Useful empty arrays
    empty_vec3list = Vec3List.new_zero(length)
    empty_array_float = np.zeros(length, np.float32)
    empty_array_bool = np.zeros(length, np.bool)
    empty_array_int = np.zeros(length, np.int32)

    # Background / Sky
    unit_direction = r.direction().unit_vector()
    sky_condition = Vec3List.from_array((unit_direction.length() > 0)
                                        & (rec_list.material == 0))
    t = (unit_direction.y() + 1) * 0.5
    blue_bg = (Vec3List.from_vec3(Color(1, 1, 1), length).mul_ndarray(1 - t) +
               Vec3List.from_vec3(Color(0.5, 0.7, 1), length).mul_ndarray(t))
    result_bg = Vec3List(np.where(sky_condition.e, blue_bg.e,
                                  empty_vec3list.e))
    if depth <= 1:
        return result_bg

    # Per-material preparations
    materials: Dict[int, Material] = world.get_materials()
    material_dict: Dict[int, Tuple[RayList, HitRecordList]] = dict()
    for mat_idx in materials:
        mat_condition = (rec_list.material == mat_idx)
        mat_condition_3 = Vec3List.from_array(mat_condition)
        if not mat_condition.any():
            continue
        raylist_temp = RayList(
            Vec3List(np.where(mat_condition_3.e, r.orig.e, empty_vec3list.e)),
            Vec3List(np.where(mat_condition_3.e, r.dir.e, empty_vec3list.e)))
        reclist_temp = HitRecordList(
            Vec3List(
                np.where(mat_condition_3.e, rec_list.p.e, empty_vec3list.e)),
            np.where(mat_condition, rec_list.t, empty_array_float),
            np.where(mat_condition, rec_list.material, empty_array_int),
            Vec3List(
                np.where(mat_condition_3.e, rec_list.normal.e,
                         empty_vec3list.e)),
            np.where(mat_condition, rec_list.front_face, empty_array_bool))
        material_dict[mat_idx] = raylist_temp, reclist_temp

    # Material scatter calculations
    scattered_list = RayList.new_zero(length)
    attenuation_list = Vec3List.new_zero(length)
    for key in material_dict:
        ray, rec = material_dict[key]
        ray, rec, idx_list = compress(ray, rec)

        scattered, attenuation = materials[key].scatter(ray, rec)
        scattered, attenuation = decompress(scattered, attenuation, idx_list,
                                            length)
        scattered_list += scattered
        attenuation_list += attenuation
    result_hittable = (attenuation_list *
                       ray_color(scattered_list, world, depth - 1))

    return result_hittable + result_bg
コード例 #19
0
ファイル: texture.py プロジェクト: SteveHawk/RayTracing-Py
 def value(self, u: float, v: float, p: Point3) -> Color:
     # return Color(1, 1, 1) * 0.5 * (1 + self.noise.noise(self.scale * p))
     # return Color(1, 1, 1) * self.noise.turb(self.scale * p)
     return (Color(1, 1, 1) * 0.5 *
             (1 + np.sin(self.scale * p.z() + 10 * self.noise.turb(p))))
コード例 #20
0
ファイル: scenes.py プロジェクト: SteveHawk/RayTracing-Py
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