2019年5月13日月曜日

開発環境

The Ray Tracer Challenge: A Test-Driven Guide to Your First 3D Renderer (Jamis Buck(著)、Pragmatic Bookshelf)、Chapter 7(Making a Scnene)のPut It Together(105)を取り組んでみる。

コード

Python 3

intersections_test.py

#!/usr/bin/env python3
from unittest import TestCase, main
from tuples import Point, Vector
from spheres import Sphere
from rays import Ray
from intersections import Intersection, Intersections


class IntersectionTest(TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_intersection(self):
        s = Sphere()
        i = Intersection(3.5, s)
        self.assertEqual(i.t, 3.5)
        self.assertEqual(i.obj, s)


class IntersectionsTest(TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_intersection(self):
        s = Sphere()
        i1 = Intersection(1, s)
        i2 = Intersection(2, s)
        xs = Intersections(i1, i2)
        for a, b in [(len(xs), 2), (xs[0].t, 1), (xs[1].t, 2)]:
            self.assertEqual(a, b)

    def test_hit(self):
        s = Sphere()
        i1 = Intersection(1, s)
        i2 = Intersection(2, s)
        xs = Intersections(i2, i1)
        self.assertEqual(xs.hit(), i1)

    def test_hit_positive_and_negative(self):
        s = Sphere()
        i1 = Intersection(-1, s)
        i2 = Intersection(1, s)
        xs = Intersections(i2, i1)
        self.assertEqual(xs.hit(), i2)

    def test_hit_none(self):
        s = Sphere()
        i1 = Intersection(-2, s)
        i2 = Intersection(-1, s)
        xs = Intersections(i2, i1)
        self.assertIsNone(xs.hit())

    def test_hit_nonnegative(self):
        s = Sphere()
        intersections = [Intersection(t, s) for t in [5, 7, -3, 2]]
        xs = Intersections(*intersections)
        self.assertEqual(xs.hit(), intersections[-1])

    def test_prepare_computations(self):
        r = Ray(Point(0, 0, -5), Vector(0, 0, 1))
        shape = Sphere()
        i = Intersection(4, shape)
        comps = i.prepare_computations(r)
        for a, b in [(comps.t, i.t),
                     (comps.obj, i.obj),
                     (comps.point, Point(0, 0, -1)),
                     (comps.eye_vector, Vector(0, 0, -1)),
                     (comps.normal_vector, Vector(0, 0, -1))]:
            self.assertEqual(a, b)

    def test_hit_intersection_outside(self):
        r = Ray(Point(0, 0, -5), Vector(0, 0, 1))
        shape = Sphere()
        i = Intersection(4, shape)
        comps = i.prepare_computations(r)
        self.assertFalse(comps.inside)

    def test_hit_intersection_inside(self):
        r = Ray(Point(0, 0, 0), Vector(0, 0, 1))
        shape = Sphere()
        i = Intersection(1, shape)
        comps = i.prepare_computations(r)
        for a, b in [(comps.point, Point(0, 0, 1)),
                     (comps.eye_vector, Vector(0, 0, -1)),
                     (comps.normal_vector, Vector(0, 0, -1))]:
            self.assertEqual(a, b)
        self.assertTrue(comps.inside)


if __name__ == '__main__':
    main()

intersections.py

from tuples import Point
from rays import Ray


class Intersection:
    def __init__(self, t: float, obj):
        self.t = t
        self.obj = obj

    def __repr__(self):
        return f'Intersection({self.t},{self.obj})'

    def prepare_computations(self, ray: Ray):
        point = ray.position(self.t)
        eye_vector = -ray.direction
        normal_vector = self.obj.normal_at(point)
        return Computations(t=self.t,
                            obj=self.obj,
                            point=point,
                            eye_vector=eye_vector,
                            normal_vector=normal_vector)


class Intersections:
    def __init__(self, *args):
        self.xs = list(args)
        self.xs.sort(key=lambda o: o.t)

    def __getitem__(self, i: int):
        return self.xs[i]

    def __len__(self):
        return len(self.xs)

    def __repr__(self):
        return f'Inersections({self.xs})'

    def hit(self):
        for i in self.xs:
            if i.t > 0:
                return i
        return None


class Computations:
    def __init__(self, t, obj, point, eye_vector, normal_vector):
        self.t = t
        self.obj = obj
        self.point = point
        self.eye_vector = eye_vector
        self.normal_vector = normal_vector
        if normal_vector.dot(eye_vector) < 0:
            self.inside = True
            self.normal_vector = -normal_vector
        else:
            self.inside = False

    def __repr__(self):
        return f'Computations({self.t},{self.obj},{self.point},' +\
            f'{self.eye_vector},{self.normal_vector})'

world_test.py

#!/usr/bin/env python3
from unittest import TestCase, main
from tuples import Point, Vector, Color
from lights import Light
from spheres import Sphere
from materials import Material
from transformations import scaling
from rays import Ray
from intersections import Intersection
from world import World


class WorldTest(TestCase):
    def setUp(self):
        self.light = Light(Point(-10, 10, -10), Color(1, 1, 1))
        self.s1 = Sphere(material=Material(color=Color(0.8, 1.0, 0.6),
                                           diffuse=0.7,
                                           specular=0.2))
        self.s2 = Sphere(transform=scaling(0.5, 0.5, 0.5))
        self.w = World(objs=[self.s1, self.s2], light=self.light)

    def tearDown(self):
        pass

    def test_world(self):
        w = World()
        self.assertEqual(len(w), 0)
        self.assertIsNone(w.light)

    def test_default_world(self):
        self.assertEqual(self.w.light, self.light)
        for s in [self.s1, self.s2]:
            self.assertIn(s, self.w)

    def test_intersect_ray(self):
        r = Ray(Point(0, 0, -5), Vector(0, 0, 1))
        xs = self.w.intersect(r)
        self.assertEqual(len(xs), 4)
        for i, t in enumerate([4, 4.5, 5.5, 6]):
            self.assertEqual(xs[i].t, t)

    def test_shade_hit(self):
        r = Ray(Point(0, 0, -5), Vector(0, 0, 1))
        shape = self.w[0]
        i = Intersection(4, shape)
        comps = i.prepare_computations(r)
        c = self.w.shade_hit(comps)
        self.assertEqual(c, Color(0.38066, 0.47583, 0.2855))

    def test_shade_hit_from_inside(self):
        self.w.light = Light(Point(0, 0.25, 0), Color(1, 1, 1))
        r = Ray(Point(0, 0, 0), Vector(0, 0, 1))
        shape = self.w[1]
        i = Intersection(0.5, shape)
        comps = i.prepare_computations(r)
        c = self.w.shade_hit(comps)
        self.assertEqual(c, Color(0.90498, 0.90498, 0.90498))

    def test_color_ray_misses(self):
        r = Ray(Point(0, 0, -5), Vector(0, 1, 0))
        c = self.w.color_at(r)
        self.assertEqual(c, Color(0, 0, 0))

    def test_color_ray_hits(self):
        r = Ray(Point(0, 0, -5), Vector(0, 0, 1))
        c = self.w.color_at(r)
        self.assertEqual(c, Color(0.38066, 0.47583, 0.2855))

    def test_color_intersection_behind_the_ray(self):
        outer = self.w[0]
        inner = self.w[1]
        outer.material.ambient = 1
        inner.material.ambient = 1
        r = Ray(Point(0, 0, 0.75), Vector(0, 0, -1))
        c = self.w.color_at(r)
        # self.assertNotEqual(c, outer.material.color)
        # self.assertEqual(c, inner.material.color)


if __name__ == '__main__':
    main()

world.py

#!/usr/bin/env python3
from intersections import Intersections
from tuples import Color


class World:
    def __init__(self, objs=None, light=None):
        if objs is None:
            self.objs = []
        else:
            self.objs = objs
        self.light = light

    def __getitem__(self, y):
        return self.objs[y]

    def __cointains__(self, key):
        return key in self.objs

    def __len__(self):
        return len(self.objs)

    def __repr__(self):
        return f'World({self.objs}, {self.light})'

    def intersect(self, ray):
        intersections = []
        for obj in self.objs:
            intersections += obj.intersect(ray)
        return Intersections(*intersections)

    def shade_hit(self, comps):
        return comps.obj.material.lighting(
            self.light, comps.point, comps.eye_vector, comps.normal_vector)

    def color_at(self, r):
        intersections = self.intersect(r)
        hit = intersections.hit()
        if hit is None:
            return Color(0, 0, 0)
        comps = hit.prepare_computations(r)
        return self.shade_hit(comps)

transformations_test.py

#!//usr/bin/env python3
from unittest import TestCase, main
from tuples import Point, Vector
from transformations import translation, scaling
from transformations import rotation_x, rotation_y, rotation_z
from transformations import view_transform
from transformations import shearing
from matrices import Matrix
import math


class TransformationsTest(TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_translation(self):
        transform = translation(5, -3, 2)
        inv = transform.inverse()
        p = Point(-3, 4, 5)
        self.assertEqual(transform * p, Point(2, 1, 7))

    def test_translation_vector(self):
        transform = translation(5, -3, 2)
        v = Vector(-3, 4, 5)
        self.assertEqual(transform * v, v)

    def test_scaling_point(self):
        transform = scaling(2, 3, 4)
        p = Point(-4, 6, 8)
        self.assertEqual(transform * p, Point(-8, 18, 32))

    def test_scaling_vector(self):
        transform = scaling(2, 3, 4)
        v = Vector(-4, 6, 8)
        self.assertEqual(transform * v, Vector(-8, 18, 32))

    def test_scaling_vector_inv(self):
        transform = scaling(2, 3, 4)
        v = inv = transform.inverse()
        v = Vector(-4, 6, 8)
        self.assertEqual(inv * v, Vector(-2, 2, 2))

    def test_scaling_negative(self):
        transform = scaling(-1, 1, 1)
        p = Point(2, 3, 4)
        self.assertEqual(transform * p, Point(-2, 3, 4))

    def test_rotation_x(self):
        p = Point(0, 1, 0)
        half_quarter = rotation_x(math.pi / 4)
        full_quarter = rotation_x(math.pi / 2)
        self.assertEqual(half_quarter * p,
                         Point(0, math.sqrt(2) / 2, math.sqrt(2) / 2))
        self.assertEqual(full_quarter * p,
                         Point(0, 0, 1))

    def test_rotation_x_opposite(self):
        p = Point(0, 1, 0)
        half_quarter = rotation_x(math.pi / 4)
        inv = half_quarter.inverse()
        self.assertEqual(inv * p,
                         Point(0, math.sqrt(2) / 2, -math.sqrt(2) / 2))

    def test_rotation_y(self):
        p = Point(0, 0, 1)
        half_quarter = rotation_y(math.pi / 4)
        full_quarter = rotation_y(math.pi / 2)
        self.assertEqual(half_quarter * p,
                         Point(math.sqrt(2) / 2, 0, math.sqrt(2) / 2))
        self.assertEqual(full_quarter * p,
                         Point(1, 0, 0))

    def test_rotation_z(self):
        p = Point(0, 1, 0)
        half_quarter = rotation_z(math.pi / 4)
        full_quarter = rotation_z(math.pi / 2)
        self.assertEqual(half_quarter * p,
                         Point(-math.sqrt(2) / 2, math.sqrt(2) / 2, 0))
        self.assertEqual(full_quarter * p,
                         Point(-1, 0, 0))

    def test_shearing_x_to_y(self):
        transform = shearing(1, 0, 0, 0, 0, 0)
        p = Point(2, 3, 4)
        self.assertEqual(transform * p, Point(5, 3, 4))

    def test_shearing_x_z(self):
        transform = shearing(0, 1, 0, 0, 0, 0)
        p = Point(2, 3, 4)
        self.assertEqual(transform * p, Point(6, 3, 4))

    def test_shearing_y_x(self):
        transform = shearing(0, 0, 1, 0, 0, 0)
        p = Point(2, 3, 4)
        self.assertEqual(transform * p, Point(2, 5, 4))

    def test_shearing_y_z(self):
        transform = shearing(0, 0, 0, 1, 0, 0)
        p = Point(2, 3, 4)
        self.assertEqual(transform * p, Point(2, 7, 4))

    def test_shearing_z_x(self):
        transform = shearing(0, 0, 0, 0, 1, 0)
        p = Point(2, 3, 4)
        self.assertEqual(transform * p, Point(2, 3, 6))

    def test_shearing_z_y(self):
        transform = shearing(0, 0, 0, 0, 0, 1)
        p = Point(2, 3, 4)
        self.assertEqual(transform * p, Point(2, 3, 7))

    def test_transformation_sequence(self):
        p = Point(1, 0, 1)
        A = rotation_x(math.pi / 2)
        B = scaling(5, 5, 5)
        C = translation(10, 5, 7)
        p2 = A * p
        p3 = B * p2
        p4 = C * p3
        for a, b in zip([p2, p3, p4], [Point(1, -1, 0),
                                       Point(5, -5, 0),
                                       Point(15, 0, 7)]):
            self.assertEqual(a, b)

    def test_transformation_chain(self):
        p = Point(1, 0, 1)
        A = rotation_x(math.pi / 2)
        B = scaling(5, 5, 5)
        C = translation(10, 5, 7)
        T = C * B * A
        self.assertEqual(T * p, Point(15, 0, 7))


class ViewTransformTest(TestCase):
    def setUp(self):
        self.from_ = Point(0, 0, 0)
        self.up = Vector(0, 1, 0)

    def tearDown(self):
        pass

    def test_default_orientation(self):
        to = Point(0, 0, -1)
        self.assertEqual(view_transform(self.from_, to, self.up),
                         Matrix([[1, 0, 0, 0],
                                 [0, 1, 0, 0],
                                 [0, 0, 1, 0],
                                 [0, 0, 0, 1]]))

    def test_looking_positive_z_direction(self):
        to = Point(0, 0, 1)
        t = view_transform(self.from_, to, self.up)
        self.assertEqual(t, scaling(-1, 1, -1))

    def test_movews_world(self):
        from_ = Point(0, 0, 8)
        to = Point(0, 0, 0)
        self.assertEqual(view_transform(from_, to, self.up),
                         translation(0, 0, -8))

    def test_arbitary(self):
        from_ = Point(1, 3, 2)
        to = Point(4, -2, 8)
        up = Vector(1, 1, 0)
        self.assertEqual(view_transform(from_, to, up),
                         Matrix([[-0.50709, 0.50709, 0.67612, -2.36643],
                                 [0.76772, 0.60609, 0.12122, -2.82843],
                                 [-0.35857, 0.59761, -0.71714, 0],
                                 [0, 0, 0, 1]]))


if __name__ == '__main__':
    main()

transformations.py

from matrices import Matrix
import math


def translation(x: float, y: float, z: float) -> Matrix:
    return Matrix([[1, 0, 0, x],
                   [0, 1, 0, y],
                   [0, 0, 1, z],
                   [0, 0, 0, 1]])


def scaling(x: float, y: float, z: float) -> Matrix:
    return Matrix([[x, 0, 0, 0],
                   [0, y, 0, 0],
                   [0, 0, z, 0],
                   [0, 0, 0, 1]])


def rotation_x(r: float) -> Matrix:
    return Matrix(((1, 0, 0, 0),
                   (0, math.cos(r), -math.sin(r), 0),
                   (0, math.sin(r), math.cos(r), 0),
                   (0, 0, 0, 1)))


def rotation_y(r: float) -> Matrix:
    return Matrix(((math.cos(r), 0, math.sin(r), 0),
                   (0, 1, 0, 0),
                   (-math.sin(r), 0, math.cos(r), 0),
                   (0, 0, 0, 1)))


def rotation_z(r: float) -> Matrix:
    return Matrix(((math.cos(r), -math.sin(r), 0, 0),
                   (math.sin(r), math.cos(r), 0, 0),
                   (0, 0, 1, 0),
                   (0, 0, 0, 1)))


def shearing(xy: float, xz: float,
             yx: float, yz: float,
             zx: float, zy: float) -> Matrix:
    return Matrix(((1, xy, xz, 0),
                   (yx, 1, yz, 0),
                   (zx, zy, 1, 0),
                   (0, 0, 0, 1)))


def view_transform(from_, to, up):
    forward = (to - from_).normalize()
    left = forward.cross(up.normalize())
    true_up = left.cross(forward)
    orientation = Matrix([[left.x, left.y, left.z, 0],
                          [true_up.x, true_up.y, true_up.z, 0],
                          [-forward.x, -forward.y, -forward.z, 0],
                          [0, 0, 0, 1]])
    return orientation * translation(-from_.x, -from_.y, -from_.z)

camera_test.py

#!//usr/bin/env python3
from unittest import TestCase, main
from camera import Camera
from matrices import Matrix
from tuples import is_equal, Point, Vector, Color
from transformations import rotation_y, translation, scaling
from transformations import view_transform
from lights import Light
from spheres import Sphere
from materials import Material
from world import World
from canvas import Canvas
import math


class CameraTest(TestCase):
    def setUp(self):
        self.c = Camera(201, 101, math.pi / 2)
        self.light = Light(Point(-10, 10, -10), Color(1, 1, 1))
        self.s1 = Sphere()
        self.s1.material = Material(
            Color(0.8, 1.0, 0.6), diffuse=0.7, specular=0.2)
        self.s2 = Sphere()
        self.s2.transform = scaling(0.5, 0.5, 0.5)
        self.w = World([self.s1, self.s2], self.light)

    def tearDown(self):
        pass

    def test_camera(self):
        horizontal_size = 160
        vertical_size = 120
        field_of_view = math.pi / 2
        c = Camera(horizontal_size, vertical_size, field_of_view)
        for a, b in [(c.horizontal_size, horizontal_size),
                     (c.vertical_size, vertical_size),
                     (c.field_of_view, math.pi / 2),
                     (c.transform, Matrix([[1, 0, 0, 0],
                                           [0, 1, 0, 0],
                                           [0, 0, 1, 0],
                                           [0, 0, 0, 1]]))]:
            self.assertEqual(a, b)

    def test_pixel_size_horizontal_canvas(self):
        c = Camera(200, 125, math.pi / 2)
        self.assertTrue(is_equal(c.pixel_size, 0.01))

    def test_pixel_size_vertcial_canvas(self):
        c = Camera(125, 200, math.pi / 2)
        self.assertTrue(is_equal(c.pixel_size, 0.01))

    def test_ray_for_pixel_center_canvas(self):
        r = self.c.ray_for_pixel(100, 50)
        for a, b in [(r.origin, Point(0, 0, 0)),
                     (r.direction, Vector(0, 0, -1))]:
            self.assertEqual(a, b)

    def test_ray_for_pixel_corner_canvas(self):
        r = self.c.ray_for_pixel(0, 0)
        for a, b in [(r.origin, Point(0, 0, 0)),
                     (r.direction, Vector(0.66519, 0.33259, -0.66851))]:
            self.assertEqual(a, b)

    def test_ray_for_pixel_camera_is_transformed(self):
        self.c.transform = rotation_y(math.pi / 4) * translation(0, -2, 5)
        r = self.c.ray_for_pixel(100, 50)
        for a, b in [(r.origin, Point(0, 2, -5)),
                     (r.direction,
                      Vector(1 / math.sqrt(2), 0, -1 / math.sqrt(2)))]:
            self.assertEqual(a, b)

    def test_rendering_world_camera(self):
        c = Camera(11, 11, math.pi / 2)
        from_ = Point(0, 0, -5)
        to = Point(0, 0, 0)
        up = Vector(0, 1, 0)
        c.transform = view_transform(from_, to, up)
        image = c.render(self.w)
        self.assertEqual(image.pixel_at(5, 5),
                         Color(0.38066, 0.47583, 0.2855))


if __name__ == '__main__':
    main()

camera.py

from matrices import Matrix
from tuples import Point
from rays import Ray
from canvas import Canvas
import math


class Camera:
    def __init__(self, horizontal_size, vertical_size, field_of_view,
                 transform=Matrix([[1, 0, 0, 0],
                                   [0, 1, 0, 0],
                                   [0, 0, 1, 0],
                                   [0, 0, 0, 1]])):
        self.horizontal_size = horizontal_size
        self.vertical_size = vertical_size
        self.field_of_view = field_of_view
        self.transform = transform
        half_view = math.tan(field_of_view / 2)
        aspect = horizontal_size / vertical_size
        if aspect >= 1:
            self.half_width = half_view
            self.half_height = half_view / aspect
        else:
            self.half_width = half_view * aspect
            self.half_height = half_view
        self.pixel_size = (self.half_width * 2) / horizontal_size

    def ray_for_pixel(self, x, y):
        x_offset = (x + 0.5) * self.pixel_size
        y_offset = (y + 0.5) * self.pixel_size

        world_x = self.half_width - x_offset
        world_y = self.half_height - y_offset
        pixel = self.transform.inverse() * Point(world_x, world_y, -1)
        origin = self.transform.inverse() * Point(0, 0, 0)
        direction = (pixel - origin).normalize()
        return Ray(origin, direction)

    def render(self, world):
        image = Canvas(self.horizontal_size, self.vertical_size)
        for y in range(self.vertical_size):
            for x in range(self.horizontal_size):
                ray = self.ray_for_pixel(x, y)
                color = world.color_at(ray)
                image.write_pixel(x, y, color)
        return image

sample.py

#!/usr/bin/env python3
import math
from tuples import Point, Vector, Color
from spheres import Sphere
from transformations import scaling, translation, rotation_x, rotation_y
from transformations import view_transform
from materials import Material
from camera import Camera
from world import World
from lights import Light
import time

floor = Sphere()
floor.transform = scaling(10, 0.01, 10)
floor.maerial = Material(color=Color(1, 0.9, 0.9), specular=0)

left_wall = Sphere(translation(0, 0, 5) *
                   rotation_y(-math.pi / 4) *
                   rotation_x(math.pi / 2) *
                   scaling(10, 0.01, 10),
                   floor.material)
right_wall = Sphere(translation(0, 0, 5) *
                    rotation_y(math.pi / 4) *
                    rotation_x(math.pi / 2) *
                    scaling(10, 0.01, 10),
                    floor.material)
camera = Camera(100, 50, math.pi / 3,
                transform=view_transform(Point(0, 1.5, -5),
                                         Point(0, 1, 0),
                                         Vector(0, 1, 0)))
middle = Sphere(translation(-0.5, 1, 0.5),
                Material(Color(0.1, 1, 0.5),
                         diffuse=0.7,
                         specular=0.3))
right = Sphere(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5),
               Material(Color(0.5, 1, 0.1),
                        diffuse=0.7,
                        specular=0.3))
left = Sphere(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33),
              Material(Color(1, 0.8, 0.1),
                       diffuse=0.7,
                       specular=0.3))

world = World([floor, left_wall, right_wall, middle, right, left],
              Light(Point(-10, 10, -10), Color(1, 1, 1)))

start = time.time()
canvas = camera.render(world)
seconds_renader = time.time() - start
start = time.time()
with open('sample.ppm', 'w') as f:
    canvas.to_ppm(f)
seconds_to_ppm = time.time() - start
print(f'sample.ppm, render time: {seconds_renader}, to_ppm time: ' +
      f'{seconds_to_ppm}')

colors = [Color(1, 0, 0), Color(0, 1, 0), Color(0, 0, 1), Color(0.5, 0.5, 0)]
for i, color in enumerate(colors, 1):
    camera = Camera(100, 50, math.pi / (1 + i),
                    transform=view_transform(Point(0, 1.5, -5),
                                             Point(0, 1, 0),
                                             Vector(0, 1, 0)))
    other = Sphere(translation(0, 0.6, -0.5) * scaling(0.6, 0.6, 0.6),
                   Material(color, diffuse=0.7, specular=0.3))
    world.objs.append(other)
    start = time.time()
    canvas = camera.render(world)
    seconds_renader = time.time() - start
    start = time.time()
    with open(f'sample{i}.ppm', 'w') as f:
        canvas.to_ppm(f)
    seconds_to_ppm = time.time() - start
    print(f'sample{i}.ppm {color}, ' +
          f'render time: {seconds_renader}, to_ppm time: {seconds_to_ppm}')
    world.objs.pop()

入出力結果(cmd(コマンドプロンプト)、Terminal、Bash、Jupyter(IPython))

C:\Users\...>py intersections_test.py
.........
----------------------------------------------------------------------
Ran 9 tests in 0.004s

OK

C:\Users\...>py world_test.py
........
----------------------------------------------------------------------
Ran 8 tests in 0.010s

OK

C:\Users\...>py transformations_test.py
......................
----------------------------------------------------------------------
Ran 22 tests in 0.004s

OK

C:\Users\...>py camera_test.py
.......
----------------------------------------------------------------------
Ran 7 tests in 0.282s

OK

C:\Users\...>py sample.py
sample.ppm, render time: 28.784709930419922, to_ppm time: 0.010057926177978516
sample1.ppm Color(1,0,0,0), render time: 31.43201994895935, to_ppm time: 0.01018214225769043
sample2.ppm Color(0,1,0,0), render time: 34.474550008773804, to_ppm time: 0.010061979293823242
sample3.ppm Color(0,0,1,0), render time: 32.88658618927002, to_ppm time: 0.010567903518676758
sample4.ppm Color(0.5,0.5,0,0), render time: 35.47628879547119, to_ppm time: 0.01112222671508789

C:\Users\...>

0 コメント:

コメントを投稿