-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathengine.py
139 lines (127 loc) · 4.92 KB
/
engine.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import shutil
import tempfile
from multiprocessing import Process, Value
from pathlib import Path
from color import Color
from image import Image
from point import Point
from ray import Ray
class RenderEngine:
"""Renders 3D objects into 2D objects using ray tracing"""
MAX_DEPTH = 5
MIN_DISPLACE = 0.0001
def render_multiprocess(self, scene, process_count, img_fileobj):
def split_range(count, parts):
d, r = divmod(count, parts)
return [
(i * d + min(i, r), (i + 1) * d + min(i + 1, r)) for i in range(parts)
]
width = scene.width
height = scene.height
ranges = split_range(height, process_count)
temp_dir = Path(tempfile.mkdtemp())
temp_file_tmpl = "puray-part-{}.temp"
processes = []
try:
rows_done = Value("i", 0)
for hmin, hmax in ranges:
part_file = temp_dir / temp_file_tmpl.format(hmin)
processes.append(
Process(
target=self.render,
args=(scene, hmin, hmax, part_file, rows_done),
)
)
# Start all the processes
for process in processes:
process.start()
# Wait for all the processes to finish
for process in processes:
process.join()
# Construct the image by joining all the parts
Image.write_ppm_header(img_fileobj, height=height, width=width)
for hmin, _ in ranges:
part_file = temp_dir / temp_file_tmpl.format(hmin)
img_fileobj.write(open(part_file, "r").read())
finally:
shutil.rmtree(temp_dir)
def render(self, scene, hmin, hmax, part_file, rows_done):
width = scene.width
height = scene.height
aspect_ratio = float(width) / height
x0 = -1.0
x1 = +1.0
xstep = (x1 - x0) / (width - 1)
y0 = -1.0 / aspect_ratio
y1 = +1.0 / aspect_ratio
ystep = (y1 - y0) / (height - 1)
camera = scene.camera
pixels = Image(width, hmax - hmin)
for j in range(hmin, hmax):
y = y0 + j * ystep
for i in range(width):
x = x0 + i * xstep
ray = Ray(camera, Point(x, y) - camera)
pixels.set_pixel(i, j - hmin, self.ray_trace(ray, scene))
# Update progress bar
if rows_done:
with rows_done.get_lock():
rows_done.value += 1
print(
"{:3.0f}%".format(float(rows_done.value) / float(height) * 100),
end="\r",
)
with open(part_file, "w") as part_fileobj:
pixels.write_ppm_raw(part_fileobj)
def ray_trace(self, ray, scene, depth=0):
color = Color(0, 0, 0)
# Find the nearest object hit by the ray in the scene
dist_hit, obj_hit = self.find_nearest(ray, scene)
if obj_hit is None:
return color
hit_pos = ray.origin + ray.direction * dist_hit
hit_normal = obj_hit.normal(hit_pos)
color += self.color_at(obj_hit, hit_pos, hit_normal, scene)
if depth < self.MAX_DEPTH:
new_ray_pos = hit_pos + hit_normal * self.MIN_DISPLACE
new_ray_dir = (
ray.direction - 2 * ray.direction.dot_product(hit_normal) * hit_normal
)
new_ray = Ray(new_ray_pos, new_ray_dir)
# Attenuate the reflected ray by the reflection coefficient
color += (
self.ray_trace(new_ray, scene, depth + 1) * obj_hit.material.reflection
)
return color
def find_nearest(self, ray, scene):
dist_min = None
obj_hit = None
for obj in scene.objects:
dist = obj.intersects(ray)
if dist is not None and (obj_hit is None or dist < dist_min):
dist_min = dist
obj_hit = obj
return (dist_min, obj_hit)
def color_at(self, obj_hit, hit_pos, normal, scene):
material = obj_hit.material
obj_color = material.color_at(hit_pos)
to_cam = scene.camera - hit_pos
specular_k = 50
color = material.ambient * Color.from_hex("#FFFFFF")
# Light calculations
for light in scene.lights:
to_light = Ray(hit_pos, light.position - hit_pos)
# Diffuse shading (Lambert)
color += (
obj_color
* material.diffuse
* max(normal.dot_product(to_light.direction), 0)
)
# Specular shading (Blinn-Phong)
half_vector = (to_light.direction + to_cam).normalize()
color += (
light.color
* material.specular
* max(normal.dot_product(half_vector), 0) ** specular_k
)
return color