コード例 #1
0
def test_one_plane():
    scene_str = """
surfaces:

  s1:
    color: [0,0,253]

objects:
  ground:
    type: plane
    point: [0, 0, 0]
    normal: [0, 1, 0]
    surface: s1

lights: {}
camera:
    position: [0, 0, 0]
    direction: [1,-0.45,0]
    up: [1,1,0]
"""
    scene, _ = parse_scene(scene_str)
    assert len(scene.objects) == 1
    assert isinstance(scene.objects[0], Plane)
    assert scene.objects[0].point == Vector3(0, 0, 0)
    assert scene.objects[0].normal == Vector3(0, 1, 0)
    assert scene.objects[0].surface.color == Vector3(0, 0, 253)
コード例 #2
0
def test_4_spheres():
    surface = Surface(color=RED)
    s1 = Sphere(position=(10, -4, -4), radius=5, surface=surface)
    s2 = Sphere(position=(10, -4, 4), radius=5, surface=surface)
    s3 = Sphere(position=(10, 4, -4), radius=5, surface=surface)
    s4 = Sphere(position=(10, 4, 4), radius=5, surface=surface)

    scene = Scene(objects=[s1, s2, s3, s4], background=BLACK)

    # Center ray, miss all spheres
    ray = Ray(origin=Vector3(0, 0, 0), direction=Vector3(1, 0, 0))
    d, obtained = scene.find_intersect(ray)
    assert obtained is None

    ray = Ray(origin=Vector3(0, 0, 0), direction=Vector3(1, 0.2, 0.2))
    d, intersected = scene.find_intersect(ray)
    assert intersected == s4
    assert d == approx(6.967, rel=1e-3)

    ray = Ray(origin=Vector3(0, 0, 0), direction=Vector3(1, -0.2, 0.2))
    d, intersected = scene.find_intersect(ray)
    assert intersected == s2
    assert d == approx(6.967, rel=1e-3)

    ray = Ray(origin=Vector3(0, 0, 0), direction=Vector3(1, -0.2, -0.2))
    d, intersected = scene.find_intersect(ray)
    assert intersected == s1
    assert d == approx(6.967, rel=1e-3)

    ray = Ray(origin=Vector3(0, 0, 0), direction=Vector3(1, 0.2, -0.2))
    d, intersected = scene.find_intersect(ray)
    assert intersected == s3
    assert d == approx(6.967, rel=1e-3)
コード例 #3
0
def test_scalar_subtraction():
    v1 = Vector3(1, 6, -1)
    obtained = v1 - 1
    assert obtained == Vector3(0, 5, -2)

    obtained = 1 - v1
    assert obtained == Vector3(0, -5, 2)
コード例 #4
0
def test_center_pixel_position():
    camera = Camera(
        Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0), screen_distance=5
    )

    screen1 = Screen(40, 30)
    camera.set_screen(screen1)
    print(f" n :{camera.n} u : {camera.u} v: {camera.v}")

    pixel_0_0 = camera.pixel_pos(0, 0)

    assert pixel_0_0 == camera.screen_corner
    print(f"0,0: {pixel_0_0}")

    pixel_40_0 = camera.pixel_pos(30, 0)
    print(f"40,0: {pixel_40_0}")

    pixel_0_30 = camera.pixel_pos(0, 40)
    print(f"0,30: {pixel_0_30}")

    pixel_40_30 = camera.pixel_pos(30, 40)
    print(f"40,30: {pixel_40_30}")

    # check screen is centered
    assert pixel_0_0.y == approx(-pixel_40_30.y)
    assert pixel_0_0.z == approx(-pixel_40_30.z)

    pixel_center = camera.pixel_pos(15, 20)

    assert pixel_center.as_tuple() == approx(Vector3(5, 0, 0).as_tuple())
コード例 #5
0
def test_compute_camera_origin():

    camera = Camera((0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0))

    assert camera.n == Vector3(-1, 0, 0)
    assert camera.v == Vector3(0, 1, 0)
    assert camera.u == Vector3(0, 0, 1)
コード例 #6
0
def test_scalar_addition():
    v1 = Vector3(1, 6, -1)

    obtained = v1 + 2
    assert obtained == Vector3(3, 8, 1)

    obtained = 2 + v1
    assert obtained == Vector3(3, 8, 1)
コード例 #7
0
def test_element_wise_addition():
    v1 = Vector3(1, 6, -1)
    v2 = Vector3(0, 1, 2)

    obtained = v1 + v2
    assert obtained == Vector3(1, 7, 1)

    obtained = v2 + v1
    assert obtained == Vector3(1, 7, 1)
コード例 #8
0
def test_intersect_single_sphere():
    surface = Surface(color=RED)
    s = Sphere(position=(10, 0, 0), radius=5, surface=surface)

    scene = Scene(objects=[s], background=BLACK)

    ray = Ray(origin=Vector3(0, 0, 0), direction=Vector3(1, 0, 0))
    obtained = scene.find_intersect(ray)

    assert obtained == (5, s)
コード例 #9
0
 def __init__(self,
              ambient_light=None,
              background=None,
              objects=None,
              light_sources=None):
     self.objects = [] if objects is None else objects
     self.light_sources = [] if light_sources is None else light_sources
     self.ambient_light = (Vector3(0.6, 0.6, 0.6) if ambient_light is None
                           else Vector3(*ambient_light))
     self.background = (Vector3(0, 0, 0) if background is None else Vector3(
         *background))
コード例 #10
0
def test_cast_on_single_sphere():

    surface = Surface(color=RED)
    s = Sphere(position=(10, 0, 0), radius=5, surface=surface)

    scene = Scene(objects=[s], background=BLACK)

    ray = Ray(origin=Vector3(0, 0, 0), direction=Vector3(1, 0, 0))
    obtained = scene.cast_ray(ray)

    assert obtained.x != 0
    assert obtained.y == 0
    assert obtained.z == 0
コード例 #11
0
def test_cast_miss_single_sphere():

    surface = Surface(color=RED)
    s = Sphere(position=(10, 0, 0), radius=5, surface=surface)

    scene = Scene(objects=[s], background=BLACK)

    # Ray is above the sphere and should miss it
    ray = Ray(origin=Vector3(0, 6, 0), direction=Vector3(1, 0, 0))
    obtained = scene.cast_ray(ray)

    assert obtained.x == 0
    assert obtained.y == 0
    assert obtained.z == 0
コード例 #12
0
    def __init__(
        self,
        diffuse=True,
        color=None,
        ka=None,
        kd=None,
        ks=None,
        alpha: int = 16,
        mirror_reflection=None,
        kr: float = None,
    ):
        """

        Parameters
        ----------

        diffuse: Boolean
            If True, the surface is shaded using the Phong model
        color: Vector3
            Base color of the surface, given as RGB (0-255)
        ka: Vector3
            Ambient reflection light coefficient (Phong model)
        kd: Vector3
            Diffuse reflection coefficient (Phong model)
        ks: Vector3
            Specular reflection coefficient (Phong model)
        alpha: int
            Shininess for specular reflexion (Phong model), large alpha produces
            small specular highlights , i.e. mirror like (=64)

        mirror_reflection: Vector3
            coefficient for reflection, used when `mirror` is `True`.
            One [0-1] coefficient must be given for each RGB component.
            If given, the surface acts as a mirror and reflects light.

        kr: float
            Refractive index, used for computing refraction using Snell's Law.
            We assume the objects are placed in air, which has kr = 1.
            If given, the surface is transparent and lighting is made of refracted and
            reflected light, according to kr.


        """
        self.diffuse = diffuse

        # Surface properties for Phong reflection model
        self.color = Vector3(*color) if color is not None else Vector3(0, 0, 0)
        self.ka = Vector3(*ka) if ka is not None else Vector3(0.9, 0.9, 0.9)
        self.kd = Vector3(*kd) if kd is not None else Vector3(0.8, 0.8, 0.8)
        self.ks = Vector3(*ks) if ks is not None else Vector3(1.2, 1.2, 1.2)
        self.alpha = alpha

        self.mirror_reflection = (Vector3(
            *mirror_reflection) if mirror_reflection is not None else None)
        self.kr = kr
コード例 #13
0
    def phong(self, point, normal, ray, scene):

        # ambient light
        ambient_coef = self.ka * scene.ambient_light

        # For each light source, diffuse and specular reflexion
        lights_coef = Vector3(0, 0, 0)
        for light in scene.light_sources:

            # Direction and distance to light
            light_segment = light.position - point
            light_dir = light_segment.normalize()
            light_power = light.power / (math.pi *
                                         light_segment.dot(light_segment))

            # check if there is an object between the light source and the point
            outer_point = point + normal * NUDGE
            _, obj = scene.find_intersect(Ray(outer_point, light_dir),
                                          exclude=[self])
            if obj:
                continue

            # Diffuse lightning:
            lights_coef += self.diffuse_lightning(normal, light_dir,
                                                  light_power)
            # Specular reflexion lightning
            lights_coef += self.specular_reflexion(ray, normal, light_dir,
                                                   light_power)

        return self.color * (ambient_coef + lights_coef)
コード例 #14
0
 def specular_reflexion(self, ray: Ray, normal: Vector3, light_dir: Vector3,
                        light_power):
     spec_reflexion_dir = 2 * (light_dir.dot(normal)) * normal - light_dir
     view_dir = ray.direction * -1
     spec_coef = view_dir.dot(spec_reflexion_dir)
     if spec_coef > 0:
         return (self.ks * math.pow(spec_coef, self.alpha)) * light_power
     return Vector3(0, 0, 0)
コード例 #15
0
    def __init__(
        self,
        position: Vector3,
        direction: Vector3,
        up: Vector3,
        field_of_view=math.pi * 0.4,
        screen_distance=10,
    ):
        self.position = Vector3(*position)
        self.direction = Vector3(*direction)  # In which we are looking !
        self.up = Vector3(*up)  # viewing orientation
        self.field_of_view = field_of_view  # angle,
        self.screen_distance = screen_distance

        # Compute basis vector at camera position:
        # need : position, coi and v_up
        self.n = -1 * self.direction.normalize()
        self.u = (self.up.cross(self.n)).normalize()
        self.v = self.n.cross(self.u)
        self.screen_3d_width, self.screen_3d_height = 0, 0
コード例 #16
0
def test_intersect():

    s = Sphere(Vector3(10, 0, 0), 5, surface=None)

    # Ray is straight to the sphere, we must have two intersection on the x axis:
    intersections = s.intersect(Ray(Vector3(0, 0, 0), Vector3(10, 0, 0)))
    assert intersections is not None
    assert intersections == 5

    # Throw ray on y, while the sphere is on x
    assert not s.intersect(Ray(Vector3(0, 0, 0), Vector3(0, 1, 0)))

    # Throw ray on z, while the sphere is on x
    assert not s.intersect(Ray(Vector3(0, 0, 0), Vector3(0, 0, 1)))

    # More subtle
    assert s.intersect(Ray(Vector3(0, 0, 0), Vector3(1, 0.2, 0.1)))
コード例 #17
0
def test_scene_one_light_source():
    scene_str = """
surfaces: {}

objects: {}

lights:
  light1:
    position: [20,50,30]
    power: [1000, 1000, 1000]
camera:
    position: [0, 0, 0]
    direction: [1,-0.45,0]
    up: [1,1,0]
"""
    scene, _ = parse_scene(scene_str)

    assert len(scene.light_sources) == 1
    assert isinstance(scene.light_sources[0], LightSource)
    assert scene.light_sources[0].position == Vector3(20, 50, 30)
    assert scene.light_sources[0].power == Vector3(1000, 1000, 1000)
コード例 #18
0
def test_camera():
    scene_str = """
surfaces: {}

objects: {}

lights: {}

camera:
    position: [0, 0, 0]
    direction: [1,-0.45,0]
    up: [1,1,0]
    field_of_view: 2.3
    screen_distance: 11
"""
    _, camera = parse_scene(scene_str)

    assert camera.position == Vector3(0, 0, 0)
    assert camera.direction == Vector3(1, -0.45, 0)
    assert camera.up == Vector3(1, 1, 0)
    assert camera.field_of_view == 2.3
    assert camera.screen_distance == 11
コード例 #19
0
def test_screen_size_and_position_do_not_depend_on_resolution():
    camera = Camera(
        Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0), screen_distance=5
    )

    screen1 = Screen(40, 30)
    camera.set_screen(screen1)
    screen1_width = camera.screen_3d_width
    screen1_height = camera.screen_3d_height
    screen1_corner = camera.screen_corner

    screen2 = Screen(400, 300)
    camera.set_screen(screen2)
    screen2_width = camera.screen_3d_width
    screen2_height = camera.screen_3d_height
    screen2_corner = camera.screen_corner

    assert screen1_width == screen2_width
    assert screen1_height == screen2_height
    assert screen1_corner == screen2_corner

    print(f" {camera.screen_3d_width} {camera.screen_3d_height} {camera.screen_corner}")
コード例 #20
0
def test_scene_one_sphere():
    scene_str = """
surfaces:

  s1:
    color: [0,0,253]
    ka: [0.1, 0.2, 0.3]
    kd: [0.4, 0.5, 0.6]
    ks: [0.7, 0.8, 0.9]

objects:
  sphere1:
    type: sphere
    position: [10, 10, 10]
    radius: 5
    surface: s1

lights: {}
camera:
    position: [0, 0, 0]
    direction: [1,-0.45,0]
    up: [1,1,0]

"""
    scene, _ = parse_scene(scene_str)

    assert len(scene.objects) == 1
    assert isinstance(scene.objects[0], Sphere)
    assert scene.objects[0].radius == 5
    assert scene.objects[0].position == Vector3(10, 10, 10)

    assert scene.objects[0].surface.diffuse
    assert not scene.objects[0].surface.mirror_reflection
    assert not scene.objects[0].surface.kr
    assert scene.objects[0].surface.ka == Vector3(0.1, 0.2, 0.3)
    assert scene.objects[0].surface.kd == Vector3(0.4, 0.5, 0.6)
    assert scene.objects[0].surface.ks == Vector3(0.7, 0.8, 0.9)
    assert scene.objects[0].surface.color == Vector3(0, 0, 253)
コード例 #21
0
    def refraction_at(self, point, ray, normal, scene, depth):
        cos_out = normal.dot(ray.direction)
        if cos_out > 0:
            # getting out of the object: invert refraction coefficients
            n1 = self.kr
            n2 = 1
        else:
            # Entering the object
            n1 = 1
            n2 = self.kr
            cos_out = -cos_out

        n12 = n1 / n2

        # Refraction + Reflexion
        # Assume we are moving from air (n= 1) to another material with nt
        # Ratio of reflected light, use Fresnel and Schilck approximation
        r0 = math.pow((n2 - 1) / (n2 + 1), 2)
        r = r0 + (1 - r0) * math.pow(1 - cos_out, 5)

        # Reflexion
        reflexion_dir = ray.direction - 2 * (
            ray.direction.dot(normal)) * normal
        reflexion_ray = Ray(point + normal * NUDGE, reflexion_dir)
        reflexion_color = scene.cast_ray(reflexion_ray, depth - 1)

        # Refraction
        refraction_color = Vector3(255, 0, 0)
        dis = 1 - n12 * n12 * (1 - cos_out * cos_out)
        if dis > 0:
            # otherwise, no refraction, all is reflected
            refraction_dir = n12 * (ray.direction -
                                    normal * cos_out) - normal * math.sqrt(dis)

            refraction_ray = Ray(point - normal * NUDGE, refraction_dir)

            # Cast a refraction (aka transparency) ray:
            refraction_color = scene.cast_ray(refraction_ray, depth - 1)
        else:
            r = 1

        color = r * reflexion_color + (1 - r) * refraction_color

        return color
コード例 #22
0
def test_two_spheres():

    scene = Scene()
    light2 = LightSource(Vector3(0, -20, -10))
    surface = Surface(color=Vector3(100, 0, 0))
    sphere1 = Sphere(Vector3(40, 4, 0), 3, surface)
    sphere2 = Sphere(Vector3(30, -4, 0), 3, surface)

    scene.objects.append(sphere1)
    scene.objects.append(sphere2)
    scene.light_sources.append(light2)

    camera = Camera(Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0))
    screen = PngScreen("test_two_spheres.png", 400, 400)
    camera.set_screen(screen)

    camera.ray_for_pixel(50, 50)

    camera.take_picture(scene)
コード例 #23
0
    def color_at(self, point, ray, hit_normal, scene, depth):
        # Compute the color for a ray touching this surface,
        # using phong, reflection or transparent (reflexion + refraction + fresnel)

        color = Vector3(0, 0, 0)
        if self.diffuse:
            # Phong model for ambient, diffuse and specular reflexion light
            color += self.phong(point, hit_normal, ray, scene)

        if depth < 0:
            return color

        if self.mirror_reflection:
            # Reflexion only, mirror like
            color += self.mirror_reflection * self.reflexion_at(
                point, ray, hit_normal, scene, depth)

            return color

        elif self.kr:
            # Refraction
            color += self.refraction_at(point, ray, hit_normal, scene, depth)
        return color
コード例 #24
0
def test_sphere_position():

    scene = Scene()
    light2 = LightSource(Vector3(0, -20, -20))
    surface = Surface(color=Vector3(100, 0, 0))
    sphere1 = Sphere(Vector3(25, -3, -5), 3, surface)
    scene.objects.append(sphere1)
    scene.light_sources.append(light2)

    camera = Camera(Vector3(0, 0, 0),
                    Vector3(1, 0, 0),
                    Vector3(1, 1, 0),
                    screen_distance=10)
    screen = PngScreen("test_sphere_position.png", 600, 400)
    camera.set_screen(screen)

    camera.ray_for_pixel(50, 50)

    camera.take_picture(scene)
コード例 #25
0
def test_element_wise_division():
    v1 = Vector3(1, 6, -1)
    v2 = Vector3(1, 2, -0.5)

    obtained = v1 / v2
    assert obtained == Vector3(1, 3, 2)
コード例 #26
0
def test_scalar_division():
    v1 = Vector3(1, 6, -1)
    obtained = v1 / 2
    assert obtained == Vector3(0.5, 3, -0.5)
コード例 #27
0
 def __init__(self, position, power=Vector3(1, 1, 1)):
     self.position = Vector3(*position)
     # Power of the light source, not restricted to [0-1]
     # The value depends on the scale of the scene you are using
     self.power = Vector3(*power)
コード例 #28
0
 def diffuse_lightning(self, normal: Vector3, light_dir: Vector3,
                       light_power):
     dot_p = normal.dot(light_dir)
     if dot_p > 0:
         return (self.kd * dot_p) * light_power
     return Vector3(0, 0, 0)
コード例 #29
0
 def __init__(self, position: Vector3, radius: float, surface):
     super().__init__(surface)
     self.radius = radius
     self.position = Vector3(*position)
コード例 #30
0
 def __init__(self, point: Vector3, normal: Vector3, surface: Surface):
     super().__init__(surface)
     self.point = Vector3(*point)
     self.normal = Vector3(*normal).normalize()