開発環境
- macOS Mojave - Apple (OS)
- Emacs (Text Editor)
- Windows 10 Pro (OS)
- Visual Studio Code (Text Editor)
- Python 3.7 (プログラミング言語)
- GIMP (ビットマップ画像編集・加工ソフトウェア、PPM形式(portable pixmap)の画像用)
The Ray Tracer Challenge: A Test-Driven Guide to Your First 3D Renderer (Jamis Buck(著)、Pragmatic Bookshelf)、Chapter 10(Patterns)のPut It Together(138)を取り組んでみる。
コード
Python 3
materials_test.py
#!/usr/bin/env python3 from unittest import TestCase, main from materials import Material from tuples import Point, Vector, Color from lights import Light from patterns import Stripe from spheres import Sphere import math class MaterialTest(TestCase): def setUp(self): self.m = Material() self.position = Point(0, 0, 0) self.obj = Sphere() def tearDown(self): pass def test_marial(self): m = Material() tests = [(m.color, Color(1, 1, 1)), (m.ambient, 0.1), (m.diffuse, 0.9), (m.specular, 0.9), (m.shininess, 200)] for a, b in tests: self.assertEqual(a, b) def test_lighting_with_eye_between_light_surface(self): eye_vecotr = Vector(0, 0, -1) normal_vector = Vector(0, 0, -1) light = Light(Point(0, 0, -10), Color(1, 1, 1)) result = self.m.lighting( self.obj, light, self.position, eye_vecotr, normal_vector) self.assertEqual(result, Color(1.9, 1.9, 1.9)) def test_lighting_with_eye_between_light_surface_eye45(self): eye_vector = Vector(0, 1 / math.sqrt(2), -1 / math.sqrt(2)) normal_vector = Vector(0, 0, -1) light = Light(Point(0, 0, -10), Color(1, 1, 1)) self.assertEqual( self.m.lighting(self.obj, light, self.position, eye_vector, normal_vector), Color(1.0, 1.0, 1.0)) def test_lighting_with_eye_opposite_surface_light45(self): eye_vector = Vector(0, 0, -1) normal_vector = Vector(0, 0, -1) light = Light(Point(0, 10, -10), Color(1, 1, 1)) self.assertEqual( self.m.lighting(self.obj, light, self.position, eye_vector, normal_vector), Color(0.7364, 0.7364, 0.7364)) def test_lighting_eye_reflection(self): eye_vector = Vector(0, -1 / math.sqrt(2), -1 / math.sqrt(2)) normal_vector = Vector(0, 0, -1) light = Light(Point(0, 10, -10), Color(1, 1, 1)) self.assertEqual( self.m.lighting(self.obj, light, self.position, eye_vector, normal_vector), Color(1.6364, 1.6364, 1.6364)) def test_lighting_behind_surface(self): eye_vector = Vector(0, 0, -1) normal_vector = Vector(0, 0, -1) light = Light(Point(0, 0, 10), Color(1, 1, 1)) self.assertEqual( self.m.lighting(self.obj, light, self.position, eye_vector, normal_vector), Color(0.1, 0.1, 0.1)) def test_lighting_with_surface_in_shadow(self): eye_vercotr = Vector(0, 0, -1) normal_vector = Vector(0, 0, -1) light = Light(Point(0, 0, -10), Color(1, 1, 1)) in_shadow = True result = self.m.lighting(self.obj, light, self.position, eye_vercotr, normal_vector, in_shadow) self.assertEqual(result, Color(0.1, 0.1, 0.1)) def test_lighting_with_stripe_aplied(self): self.m.pattern = Stripe(Color(1, 1, 1), Color(0, 0, 0)) self.m.ambient = 1 self.m.diffuse = 0 self.m.specular = 0 eye_vector = Vector(0, 0, -1) normal_vector = Vector(0, 0, -1) light = Light(Point(0, 0, -10), Color(1, 1, 1)) for xyz, color in [((0.9, 0, 0), Color(1, 1, 1)), ((1.1, 0, 0), Color(0, 0, 0))]: self.assertEqual( self.m.lighting(self.obj, light, Point( *xyz), eye_vector, normal_vector), color) if __name__ == '__main__': main()
materials.py
from tuples import Color, is_equal class Material: def __init__(self, color=Color(1, 1, 1), ambient=0.1, diffuse=0.9, specular=0.9, shininess=200, pattern=None): self.color = color self.ambient = ambient self.diffuse = diffuse self.specular = specular self.shininess = shininess self.pattern = pattern def __repr__(self): return f'Material({self.color},{self.ambient},{self.diffuse},' +\ f'{self.specular},{self.shininess})' def __eq__(self, other): if self.color != other.color: return False tests = [(self.ambient, other.ambient), (self.diffuse, other.diffuse), (self.specular, other.specular), (self.shininess, other.shininess)] for a, b in tests: if not is_equal(a, b): return False return True def lighting(self, obj, light, point, eye_vector, normal_vector, in_shadow=False) -> Color: if self.pattern is None: color = self.color else: color = self.pattern.at_shape(obj, point) effective_color = color * light.intensity light_vector = (light.position - point).normalize() ambient = effective_color * self.ambient if in_shadow: return ambient light_dot_normal = light_vector.dot(normal_vector) if light_dot_normal < 0: diffuse = Color(0, 0, 0) specular = Color(0, 0, 0) else: diffuse = effective_color * self.diffuse * light_dot_normal reflect_vector = -light_vector.reflect(normal_vector) reflect_dot_eye = reflect_vector.dot(eye_vector) if reflect_dot_eye <= 0: specular = Color(0, 0, 0) else: factor = reflect_dot_eye ** self.shininess specular = light.intensity * self.specular * factor return ambient + diffuse + specular
patterns_test.py
#!//usr/bin/env python3 from unittest import TestCase, main from patterns import Pattern, Stripe, Gradient, Ring, Checkers from tuples import Point, Color from spheres import Sphere from transformations import scaling, translation from matrices import IDENTITY_MATRIX class PatternTest(TestCase): def setUp(self): self.pattern = Pattern() def tearDown(self): pass def test_transformation(self): self.assertEqual(self.pattern.transform, IDENTITY_MATRIX) def test_assign_transformation(self): self.pattern.transform = translation(1, 2, 3) self.assertEqual(self.pattern.transform, translation(1, 2, 3)) def test_with_obj_transformation(self): shape = Sphere(scaling(2, 2, 2)) c = self.pattern.at_shape(shape, Point(2, 3, 4)) self.assertEqual(c, Color(1, 1.5, 2)) def test_with_pattern_transformation(self): shape = Sphere() self.pattern.transform = scaling(2, 2, 2) c = self.pattern.at_shape(shape, Point(2, 3, 4)) self.assertEqual(c, Color(1, 1.5, 2)) class StripeTest(TestCase): def setUp(self): self.black = Color(0, 0, 0) self.white = Color(1, 1, 1) def tearDown(self): pass def test_stripe(self): pattern = Stripe(self.white, self.black) for a, b in [(pattern.a, self.white), (pattern.b, self.black)]: self.assertEqual(a, b) def test_constant_y(self): pattern = Stripe(self.white, self.black) for point in [Point(0, 0, 0), Point(0, 1, 0), Point(0, 2, 0)]: self.assertEqual(pattern.at(point), self.white) def test_constant_z(self): pattern = Stripe(self.white, self.black) for point in [Point(0, 0, 0), Point(0, 0, 1), Point(0, 0, 2)]: self.assertEqual(pattern.at(point), self.white) def test_alternates_x(self): pattern = Stripe(self.white, self.black) for xyz, color in [((0, 0, 0), self.white), ((0.9, 0, 0), self.white), ((1, 0, 0), self.black), ((-0.1, 0, 0), self.black), ((-1, 0, 0), self.black), ((-1.1, 0, 0), self.white)]: self.assertEqual(pattern.at(Point(*xyz)), color) def test_with_obj_transformation(self): obj = Sphere(transform=scaling(2, 2, 2)) pattern = Stripe(self.white, self.black) self.assertEqual(pattern.at_shape(obj, Point(1.5, 0, 0)), self.white) def test_with_pattern_transformation(self): obj = Sphere() pattern = Stripe(self.white, self.black, transform=scaling(2, 2, 2)) self.assertEqual(pattern.at_shape(obj, Point(1.5, 0, 0)), self.white) def test_with_both_obj_pattern_transformation(self): obj = Sphere(scaling(2, 2, 2)) pattern = Stripe(self.white, self.black, translation(0.5, 0, 0)) self.assertEqual(pattern.at_shape(obj, Point(2.5, 0, 0)), self.white) class GradientTest(TestCase): def setUp(self): pass def tearDown(self): pass def test_linearly_interpolates_between_colors(self): pattern = Gradient(Color(1, 1, 1), Color(0, 0, 0)) for point_xyz, color_xyz in [((0, 0, 0), (1, 1, 1)), ((0.25, 0, 0), (0.75, 0.75, 0.75)), ((0.5, 0, 0), (0.5, 0.5, 0.5)), ((0.75, 0, 0), (0.25, 0.25, 0.25))]: self.assertEqual(pattern.at(Point(*point_xyz)), Color(*color_xyz)) class RingTest(TestCase): def setUp(self): pass def tearDown(self): pass def test(self): ring = Ring(Color(1, 1, 1), Color(0, 0, 0)) for point, color in [((0, 0, 0), (1, 1, 1)), ((1, 0, 0), (0, 0, 0)), ((0, 0, 1), (0, 0, 0)), ((0.708, 0, 0.708), (0, 0, 0))]: self.assertEqual(ring.at(Point(*point)), Color(*color)) class CheckersTest(TestCase): def setUp(self): self.white = Color(1, 1, 1) self.black = Color(0, 0, 0) def tearDown(self): pass def test_repeate_x(self): checkers = Checkers(self.white, self.black) for point, color in [((0, 0, 0), self.white), ((0.99, 0, 0), self.white), ((1.01, 0, 0), self.black)]: self.assertEqual(checkers.at(Point(*point)), color) def test_repeate_y(self): checkers = Checkers(self.white, self.black) for point, color in [((0, 0, 0), self.white), ((0, 0.99, 0), self.white), ((0, 1.01, 0), self.black)]: self.assertEqual(checkers.at(Point(*point)), color) def test_repeate_z(self): checkers = Checkers(self.white, self.black) for point, color in [((0, 0, 0), self.white), ((0, 0, 0.99), self.white), ((0, 0, 1.01), self.black)]: self.assertEqual(checkers.at(Point(*point)), color) if __name__ == '__main__': main()
patterns.py
import math from matrices import IDENTITY_MATRIX from tuples import Color class Pattern: def __init__(self, transform=IDENTITY_MATRIX): self.transform = transform def at(self, point): return Color(point.x, point.y, point.z) def at_shape(self, shape, world_point): obj_point = shape.transform.inverse() * world_point pattern_point = self.transform.inverse() * obj_point return self.at(pattern_point) class Stripe(Pattern): def __init__(self, color1, color2, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = color1 self.b = color2 def at(self, point): if math.floor(point.x) % 2 == 0: return self.a return self.b class Gradient(Pattern): def __init__(self, color1, color2, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = color1 self.b = color2 def at(self, point): distance = self.b - self.a fraction = point.x - math.floor(point.x) return self.a + distance * fraction class Ring(Pattern): def __init__(self, color1, color2, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = color1 self.b = color2 def at(self, point): if math.floor(math.sqrt(point.x ** 2 + point.z ** 2)) % 2 == 0: return self.a return self.b class Checkers(Pattern): def __init__(self, color1, color2, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = color1 self.b = color2 def at(self, point): if sum([math.floor(p) for p in [point.x, point.y, point.z]]) % 2 == 0: return self.a return self.b class RadialGradient(Pattern): def __init__(self, color1, color2, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = color1 self.b = color2 def at(self, point): color_distance = self.b - self.a distance = math.sqrt(point.x ** 2 + point.z ** 2) fraction = distance - math.floor(distance) return self.a + color_distance * fraction
sample1.py
#!/usr/bin/env python3 import math import time from tuples import Point, Vector, Color from planes import Plane from materials import Material from patterns import Ring, Gradient, RadialGradient from camera import Camera from lights import Light from world import World from transformations import scaling, view_transform, translation, rotation_x print('ファイル名, rendering time(秒)') width = 250 height = 125 patterns = [Ring(Color(1, 0, 0), Color(0, 1, 0)), Gradient(Color(1, 0, 0), Color(0, 1, 0)), RadialGradient(Color(1, 0, 0), Color(0, 1, 0))] material = Material(specular=0) # material = Material(specular=0, pattern=pattern) shape = Plane(material=material, transform=rotation_x(math.pi / 2)) camera = Camera(width, height, math.pi / 3, transform=view_transform(Point(0, 0, -5), Point(0, 0, 0), Vector(0, 1, 0))) world = World([shape], Light(Point(-10, 10, -10), Color(1, 1, 1))) for i, pattern in enumerate(patterns, 1): material.pattern = pattern start = time.time() canvas = camera.render(world) s = time.time() - start with open(f'sample{i}.ppm', 'w') as f: canvas.to_ppm(f) print(f'sample{i}.ppm,{s}')
入出力結果(cmd(コマンドプロンプト)、Terminal、Bash、Jupyter(IPython))
C:\Users\...>py materials_test.py ........ ---------------------------------------------------------------------- Ran 8 tests in 0.003s OK C:\Users\...>py patterns_test.py ................ ---------------------------------------------------------------------- Ran 16 tests in 0.007s OK C:\Users\...>py sample1.py ファイル名, rendering time(秒) sample1.ppm,130.32527017593384 sample2.ppm,131.36756920814514 sample3.ppm,139.62577509880066 C:\Users\...>
0 コメント:
コメントを投稿