├── requirements.txt
├── .gitignore
├── img
├── b_p.png
├── color_only.png
├── lambertian.png
├── whitted_style.png
├── b_p_with_shadow.png
├── path_tracing_black_background.png
└── path_tracing_sample_on_sphere_surface.png
├── README.md
├── 1_0_color_only.py
├── 2_0_lambertian_reflection.py
├── 2_1_blinn_phong_model.py
├── 3_1_blinn_phong_with_shadow.py
├── 4_0_path_tracing.py
├── ray_tracing_models.py
└── 3_2_whitted_style_ray_tracing.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | taichi
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.pyc
3 |
--------------------------------------------------------------------------------
/img/b_p.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichiCourse01/taichi_ray_tracing/HEAD/img/b_p.png
--------------------------------------------------------------------------------
/img/color_only.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichiCourse01/taichi_ray_tracing/HEAD/img/color_only.png
--------------------------------------------------------------------------------
/img/lambertian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichiCourse01/taichi_ray_tracing/HEAD/img/lambertian.png
--------------------------------------------------------------------------------
/img/whitted_style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichiCourse01/taichi_ray_tracing/HEAD/img/whitted_style.png
--------------------------------------------------------------------------------
/img/b_p_with_shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichiCourse01/taichi_ray_tracing/HEAD/img/b_p_with_shadow.png
--------------------------------------------------------------------------------
/img/path_tracing_black_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichiCourse01/taichi_ray_tracing/HEAD/img/path_tracing_black_background.png
--------------------------------------------------------------------------------
/img/path_tracing_sample_on_sphere_surface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taichiCourse01/taichi_ray_tracing/HEAD/img/path_tracing_sample_on_sphere_surface.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 太极图形课S1-Ray Tracing示例程序
2 |
3 | ## 背景简介
4 | 该repo实现了一些Rendering的方法, Path tracing的具体实现参考了[Ray Tracing in One Weekend](https://raytracing.github.io/)
5 |
6 |
7 | ## 效果展示
8 | |Color only |Lambertian reflection | Blinn-Phong model |
9 | |:-------------------------:|:-------------------------:|:-------------------------:|
10 | |
|
|
|
11 | |Blinn-Phong model with shadow| Whitted style ray tracing|Path tracing|
12 | |
|
|
|
13 |
14 |
15 | ## 运行环境
16 |
17 | ```
18 | [Taichi] version 0.8.3, llvm 10.0.0, commit 2680dabd, linux, python 3.8.10
19 | ```
20 |
21 | ## 运行方式
22 | 确保ray_tracing_models.py可以访问的情况下,可以直接运行:`python3 [*].py`
23 |
--------------------------------------------------------------------------------
/1_0_color_only.py:
--------------------------------------------------------------------------------
1 | import taichi as ti
2 | import numpy as np
3 | import argparse
4 | from ray_tracing_models import Ray, Camera, Hittable_list, Sphere, PI
5 |
6 | ti.init(arch=ti.gpu)
7 | PI = 3.14159265
8 |
9 | # Canvas
10 | aspect_ratio = 1.0
11 | image_width = 800
12 | image_height = int(image_width / aspect_ratio)
13 | canvas = ti.Vector.field(3, dtype=ti.f32, shape=(image_width, image_height))
14 |
15 | # Rendering parameters
16 | samples_per_pixel = 4
17 | max_depth = 10
18 |
19 | @ti.kernel
20 | def render():
21 | for i, j in canvas:
22 | u = (i + ti.random()) / image_width
23 | v = (j + ti.random()) / image_height
24 | color = ti.Vector([0.0, 0.0, 0.0])
25 | for n in range(samples_per_pixel):
26 | ray = camera.get_ray(u, v)
27 | color += ray_color(ray)
28 | color /= samples_per_pixel
29 | canvas[i, j] += color
30 |
31 | @ti.func
32 | def ray_color(ray):
33 | default_color = ti.Vector([1.0, 1.0, 1.0])
34 | scattered_origin = ray.origin
35 | scattered_direction = ray.direction
36 | is_hit, hit_point, hit_point_normal, front_face, material, color = scene.hit(Ray(scattered_origin, scattered_direction))
37 | if is_hit:
38 | default_color = color
39 | return default_color
40 |
41 | if __name__ == "__main__":
42 | parser = argparse.ArgumentParser(description='Naive Ray Tracing')
43 | parser.add_argument(
44 | '--max_depth', type=int, default=1, help='max depth (default: 10)')
45 | parser.add_argument(
46 | '--samples_per_pixel', type=int, default=4, help='samples_per_pixel (default: 4)')
47 | args = parser.parse_args()
48 |
49 | max_depth = args.max_depth
50 | assert max_depth == 1
51 | samples_per_pixel = args.samples_per_pixel
52 |
53 | scene = Hittable_list()
54 |
55 | # Light source
56 | scene.add(Sphere(center=ti.Vector([0, 5.4, -1]), radius=3.0, material=0, color=ti.Vector([10.0, 10.0, 10.0])))
57 | # Ground
58 | scene.add(Sphere(center=ti.Vector([0, -100.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
59 | # ceiling
60 | scene.add(Sphere(center=ti.Vector([0, 102.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
61 | # back wall
62 | scene.add(Sphere(center=ti.Vector([0, 1, 101]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
63 | # right wall
64 | scene.add(Sphere(center=ti.Vector([-101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.6, 0.0, 0.0])))
65 | # left wall
66 | scene.add(Sphere(center=ti.Vector([101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.0, 0.6, 0.0])))
67 |
68 | # Diffuse ball
69 | scene.add(Sphere(center=ti.Vector([0, -0.2, -1.5]), radius=0.3, material=1, color=ti.Vector([0.8, 0.3, 0.3])))
70 | # Metal ball
71 | scene.add(Sphere(center=ti.Vector([-0.8, 0.2, -1]), radius=0.7, material=2, color=ti.Vector([0.6, 0.8, 0.8])))
72 | # Glass ball
73 | scene.add(Sphere(center=ti.Vector([0.7, 0, -0.5]), radius=0.5, material=3, color=ti.Vector([1.0, 1.0, 1.0])))
74 | # Metal ball-2
75 | scene.add(Sphere(center=ti.Vector([0.6, -0.3, -2.0]), radius=0.2, material=2, color=ti.Vector([0.8, 0.6, 0.2])))
76 |
77 | camera = Camera()
78 | gui = ti.GUI("Ray Tracing - Color Only", res=(image_width, image_height))
79 | canvas.fill(0)
80 | cnt = 0
81 | while gui.running:
82 | render()
83 | cnt += 1
84 | gui.set_image(np.sqrt(canvas.to_numpy() / cnt))
85 | gui.show()
--------------------------------------------------------------------------------
/2_0_lambertian_reflection.py:
--------------------------------------------------------------------------------
1 | import taichi as ti
2 | import numpy as np
3 | import argparse
4 | from ray_tracing_models import Ray, Camera, Hittable_list, Sphere, PI
5 | ti.init(arch=ti.gpu)
6 |
7 | PI = 3.14159265
8 |
9 | # Canvas
10 | aspect_ratio = 1.0
11 | image_width = 800
12 | image_height = int(image_width / aspect_ratio)
13 | canvas = ti.Vector.field(3, dtype=ti.f32, shape=(image_width, image_height))
14 |
15 | # Rendering parameters
16 | samples_per_pixel = 4
17 | max_depth = 10
18 |
19 | @ti.kernel
20 | def render():
21 | for i, j in canvas:
22 | u = (i + ti.random()) / image_width
23 | v = (j + ti.random()) / image_height
24 | color = ti.Vector([0.0, 0.0, 0.0])
25 | for n in range(samples_per_pixel):
26 | ray = camera.get_ray(u, v)
27 | color += ray_color(ray)
28 | color /= samples_per_pixel
29 | canvas[i, j] += color
30 |
31 |
32 | @ti.func
33 | def to_light_source(hit_point, light_source):
34 | return light_source - hit_point
35 |
36 | # Lambertian reflection model
37 | @ti.func
38 | def ray_color(ray):
39 | default_color = ti.Vector([1.0, 1.0, 1.0])
40 | scattered_origin = ray.origin
41 | scattered_direction = ray.direction
42 | is_hit, hit_point, hit_point_normal, front_face, material, color = scene.hit(Ray(scattered_origin, scattered_direction))
43 | if is_hit:
44 | if material == 0:
45 | default_color = color
46 | else:
47 | hit_point_to_source = to_light_source(hit_point, ti.Vector([0, 5.4 - 3.0, -1]))
48 | default_color = color * ti.max(hit_point_to_source.dot(hit_point_normal) / (hit_point_to_source.norm() * hit_point_normal.norm()), 0.0)
49 | return default_color
50 |
51 | if __name__ == "__main__":
52 | parser = argparse.ArgumentParser(description='Naive Ray Tracing')
53 | parser.add_argument(
54 | '--max_depth', type=int, default=10, help='max depth (default: 10)')
55 | parser.add_argument(
56 | '--samples_per_pixel', type=int, default=4, help='samples_per_pixel (default: 4)')
57 | args = parser.parse_args()
58 |
59 | max_depth = args.max_depth
60 | samples_per_pixel = args.samples_per_pixel
61 | scene = Hittable_list()
62 |
63 | # Light source
64 | scene.add(Sphere(center=ti.Vector([0, 5.4, -1]), radius=3.0, material=0, color=ti.Vector([10.0, 10.0, 10.0])))
65 | # Ground
66 | scene.add(Sphere(center=ti.Vector([0, -100.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
67 | # ceiling
68 | scene.add(Sphere(center=ti.Vector([0, 102.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
69 | # back wall
70 | scene.add(Sphere(center=ti.Vector([0, 1, 101]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
71 | # right wall
72 | scene.add(Sphere(center=ti.Vector([-101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.6, 0.0, 0.0])))
73 | # left wall
74 | scene.add(Sphere(center=ti.Vector([101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.0, 0.6, 0.0])))
75 |
76 | # Diffuse ball
77 | scene.add(Sphere(center=ti.Vector([0, -0.2, -1.5]), radius=0.3, material=1, color=ti.Vector([0.8, 0.3, 0.3])))
78 | # Metal ball
79 | scene.add(Sphere(center=ti.Vector([-0.8, 0.2, -1]), radius=0.7, material=2, color=ti.Vector([0.6, 0.8, 0.8])))
80 | # Glass ball
81 | scene.add(Sphere(center=ti.Vector([0.7, 0, -0.5]), radius=0.5, material=3, color=ti.Vector([1.0, 1.0, 1.0])))
82 | # Metal ball-2
83 | scene.add(Sphere(center=ti.Vector([0.6, -0.3, -2.0]), radius=0.2, material=2, color=ti.Vector([0.8, 0.6, 0.2])))
84 |
85 | camera = Camera()
86 | gui = ti.GUI("Ray Tracing", res=(image_width, image_height))
87 | canvas.fill(0)
88 | cnt = 0
89 | while gui.running:
90 | render()
91 | cnt += 1
92 | gui.set_image(np.sqrt(canvas.to_numpy() / cnt))
93 | gui.show()
--------------------------------------------------------------------------------
/2_1_blinn_phong_model.py:
--------------------------------------------------------------------------------
1 | import taichi as ti
2 | import numpy as np
3 | import argparse
4 | from ray_tracing_models import Ray, Camera, Hittable_list, Sphere, PI
5 | ti.init(arch=ti.gpu)
6 |
7 | PI = 3.14159265
8 |
9 | # Canvas
10 | aspect_ratio = 1.0
11 | image_width = 800
12 | image_height = int(image_width / aspect_ratio)
13 | canvas = ti.Vector.field(3, dtype=ti.f32, shape=(image_width, image_height))
14 | light_source = ti.Vector([0, 5.4 - 3.0, -1])
15 |
16 | # Rendering parameters
17 | samples_per_pixel = 4
18 | max_depth = 10
19 |
20 | @ti.kernel
21 | def render():
22 | for i, j in canvas:
23 | u = (i + ti.random()) / image_width
24 | v = (j + ti.random()) / image_height
25 | color = ti.Vector([0.0, 0.0, 0.0])
26 | for n in range(samples_per_pixel):
27 | ray = camera.get_ray(u, v)
28 | color += ray_color(ray)
29 | color /= samples_per_pixel
30 | canvas[i, j] += color
31 |
32 | @ti.func
33 | def to_light_source(hit_point, light_source):
34 | return light_source - hit_point
35 |
36 |
37 | @ti.func
38 | def blinn_phong(ray_direction, hit_point, hit_point_normal, color, material):
39 | # Compute the local color use Blinn-Phong model
40 | hit_point_to_source = to_light_source(hit_point, light_source)
41 | # Diffuse light
42 | diffuse_color = color * ti.max(
43 | hit_point_to_source.dot(hit_point_normal) / (
44 | hit_point_to_source.norm() * hit_point_normal.norm()),
45 | 0.0)
46 | specular_color = ti.Vector([0.0, 0.0, 0.0])
47 | diffuse_weight = 1.0
48 | specular_weight = 1.0
49 | if material != 1:
50 | # Specular light
51 | H = (-(ray_direction.normalized()) + hit_point_to_source.normalized()).normalized()
52 | N_dot_H = ti.max(H.dot(hit_point_normal.normalized()), 0.0)
53 | intensity = ti.pow(N_dot_H, 10)
54 | specular_color = intensity * color
55 |
56 | # Fuzz metal ball
57 | if material == 4:
58 | diffuse_weight = 0.5
59 | specular_weight = 0.5
60 |
61 | return diffuse_weight * diffuse_color + specular_weight * specular_color
62 |
63 | # Blinn–Phong reflection model
64 | @ti.func
65 | def ray_color(ray):
66 | color_buffer = ti.Vector([1.0, 1.0, 1.0])
67 | curr_origin = ray.origin
68 | curr_direction = ray.direction
69 | is_hit, hit_point, hit_point_normal, front_face, material, color = scene.hit(Ray(curr_origin, curr_direction))
70 | if is_hit:
71 | if material == 0:
72 | color_buffer = color
73 | else:
74 | color_buffer = blinn_phong(curr_direction, hit_point, hit_point_normal, color, material)
75 | return color_buffer
76 |
77 | if __name__ == "__main__":
78 | parser = argparse.ArgumentParser(description='Naive Ray Tracing')
79 | parser.add_argument(
80 | '--max_depth', type=int, default=10, help='max depth (default: 10)')
81 | parser.add_argument(
82 | '--samples_per_pixel', type=int, default=4, help='samples_per_pixel (default: 4)')
83 | args = parser.parse_args()
84 |
85 | max_depth = args.max_depth
86 | samples_per_pixel = args.samples_per_pixel
87 | scene = Hittable_list()
88 |
89 | # Light source
90 | scene.add(Sphere(center=ti.Vector([0, 5.4, -1]), radius=3.0, material=0, color=ti.Vector([10.0, 10.0, 10.0])))
91 | # Ground
92 | scene.add(Sphere(center=ti.Vector([0, -100.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
93 | # ceiling
94 | scene.add(Sphere(center=ti.Vector([0, 102.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
95 | # back wall
96 | scene.add(Sphere(center=ti.Vector([0, 1, 101]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
97 | # right wall
98 | scene.add(Sphere(center=ti.Vector([-101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.6, 0.0, 0.0])))
99 | # left wall
100 | scene.add(Sphere(center=ti.Vector([101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.0, 0.6, 0.0])))
101 |
102 | # Diffuse ball
103 | scene.add(Sphere(center=ti.Vector([0, -0.2, -1.5]), radius=0.3, material=1, color=ti.Vector([0.8, 0.3, 0.3])))
104 | # Metal ball
105 | scene.add(Sphere(center=ti.Vector([-0.8, 0.2, -1]), radius=0.7, material=2, color=ti.Vector([0.6, 0.8, 0.8])))
106 | # Glass ball
107 | scene.add(Sphere(center=ti.Vector([0.7, 0, -0.5]), radius=0.5, material=3, color=ti.Vector([1.0, 1.0, 1.0])))
108 | # Metal ball-2, here 4 indicates a fuzz metal ball
109 | scene.add(Sphere(center=ti.Vector([0.6, -0.3, -2.0]), radius=0.2, material=4, color=ti.Vector([0.8, 0.6, 0.2])))
110 |
111 | camera = Camera()
112 | gui = ti.GUI("Ray Tracing", res=(image_width, image_height))
113 | canvas.fill(0)
114 | cnt = 0
115 | while gui.running:
116 | render()
117 | cnt += 1
118 | gui.set_image(np.sqrt(canvas.to_numpy() / cnt))
119 | gui.show()
--------------------------------------------------------------------------------
/3_1_blinn_phong_with_shadow.py:
--------------------------------------------------------------------------------
1 | import taichi as ti
2 | import numpy as np
3 | import argparse
4 | from ray_tracing_models import Ray, Camera, Hittable_list, Sphere, PI
5 |
6 | ti.init(arch=ti.gpu)
7 |
8 | # Canvas
9 | aspect_ratio = 1.0
10 | image_width = 800
11 | image_height = int(image_width / aspect_ratio)
12 | canvas = ti.Vector.field(3, dtype=ti.f32, shape=(image_width, image_height))
13 | light_source = ti.Vector([0, 5.4 - 3.0, -1])
14 |
15 | # Rendering parameters
16 | samples_per_pixel = 4
17 |
18 | @ti.func
19 | def to_light_source(hit_point, light_source):
20 | return light_source - hit_point
21 |
22 | @ti.kernel
23 | def render():
24 | for i, j in canvas:
25 | u = (i + ti.random()) / image_width
26 | v = (j + ti.random()) / image_height
27 | color = ti.Vector([0.0, 0.0, 0.0])
28 | for n in range(samples_per_pixel):
29 | ray = camera.get_ray(u, v)
30 | color += ray_color(ray)
31 | color /= samples_per_pixel
32 | canvas[i, j] += color
33 |
34 | @ti.func
35 | def blinn_phong(ray_direction, hit_point, hit_point_normal, color, material):
36 | # Compute the local color use Blinn-Phong model
37 | hit_point_to_source = to_light_source(hit_point, light_source)
38 | # Diffuse light
39 | diffuse_color = color * ti.max(
40 | hit_point_to_source.dot(hit_point_normal) / (
41 | hit_point_to_source.norm() * hit_point_normal.norm()),
42 | 0.0)
43 | specular_color = ti.Vector([0.0, 0.0, 0.0])
44 | diffuse_weight = 1.0
45 | specular_weight = 1.0
46 | if material != 1:
47 | # Specular light
48 | H = (-(ray_direction.normalized()) + hit_point_to_source.normalized()).normalized()
49 | N_dot_H = ti.max(H.dot(hit_point_normal.normalized()), 0.0)
50 | intensity = ti.pow(N_dot_H, 10)
51 | specular_color = intensity * color
52 |
53 | # Fuzz metal ball
54 | if material == 4:
55 | diffuse_weight = 0.5
56 | specular_weight = 0.5
57 |
58 | # Add shadow
59 | is_hit_source, hitted_dielectric_num, is_hitted_non_dielectric = scene.hit_shadow(
60 | Ray(hit_point, hit_point_to_source))
61 | shadow_weight = 1.0
62 | if not is_hit_source:
63 | if is_hitted_non_dielectric:
64 | # Add hard shadow
65 | shadow_weight = 0
66 | elif hitted_dielectric_num > 0:
67 | # Add soft shadow if the obstacles are dielectric
68 | shadow_weight = ti.pow(0.5, hitted_dielectric_num)
69 | return (diffuse_weight * diffuse_color + specular_weight * specular_color) * shadow_weight
70 |
71 |
72 | # Blinn–Phong reflection model with shadow
73 | @ti.func
74 | def ray_color(ray):
75 | color_buffer = ti.Vector([1.0, 1.0, 1.0])
76 | curr_origin = ray.origin
77 | curr_direction = ray.direction
78 | is_hit, hit_point, hit_point_normal, front_face, material, color = scene.hit(Ray(curr_origin, curr_direction))
79 | if is_hit:
80 | if material == 0:
81 | color_buffer = color
82 | else:
83 | color_buffer = blinn_phong(curr_direction, hit_point, hit_point_normal, color, material)
84 | return color_buffer
85 |
86 | if __name__ == "__main__":
87 | parser = argparse.ArgumentParser(description='Naive Ray Tracing')
88 | parser.add_argument(
89 | '--max_depth', type=int, default=10, help='max depth (default: 10)')
90 | parser.add_argument(
91 | '--samples_per_pixel', type=int, default=4, help='samples_per_pixel (default: 4)')
92 | args = parser.parse_args()
93 |
94 | max_depth = args.max_depth
95 | samples_per_pixel = args.samples_per_pixel
96 | scene = Hittable_list()
97 |
98 | # Light source
99 | scene.add(Sphere(center=ti.Vector([0, 5.4, -1]), radius=3.0, material=0, color=ti.Vector([10.0, 10.0, 10.0])))
100 | # Ground
101 | scene.add(Sphere(center=ti.Vector([0, -100.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
102 | # ceiling
103 | scene.add(Sphere(center=ti.Vector([0, 102.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
104 | # back wall
105 | scene.add(Sphere(center=ti.Vector([0, 1, 101]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
106 | # right wall
107 | scene.add(Sphere(center=ti.Vector([-101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.6, 0.0, 0.0])))
108 | # left wall
109 | scene.add(Sphere(center=ti.Vector([101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.0, 0.6, 0.0])))
110 |
111 | # Diffuse ball
112 | scene.add(Sphere(center=ti.Vector([0, -0.2, -1.5]), radius=0.3, material=1, color=ti.Vector([0.8, 0.3, 0.3])))
113 | # Metal ball
114 | scene.add(Sphere(center=ti.Vector([-0.8, 0.2, -1]), radius=0.7, material=2, color=ti.Vector([0.6, 0.8, 0.8])))
115 | # Glass ball
116 | scene.add(Sphere(center=ti.Vector([0.7, 0, -0.5]), radius=0.5, material=3, color=ti.Vector([1.0, 1.0, 1.0])))
117 | # Metal ball-2, here 4 indicates a fuzz metal ball
118 | scene.add(Sphere(center=ti.Vector([0.6, -0.3, -2.0]), radius=0.2, material=4, color=ti.Vector([0.8, 0.6, 0.2])))
119 |
120 | camera = Camera()
121 | gui = ti.GUI("Ray Tracing", res=(image_width, image_height))
122 | canvas.fill(0)
123 | cnt = 0
124 | while gui.running:
125 | render()
126 | cnt += 1
127 | gui.set_image(np.sqrt(canvas.to_numpy() / cnt))
128 | gui.show()
--------------------------------------------------------------------------------
/4_0_path_tracing.py:
--------------------------------------------------------------------------------
1 | import taichi as ti
2 | import numpy as np
3 | import argparse
4 | from ray_tracing_models import Ray, Camera, Hittable_list, Sphere, PI, random_in_unit_sphere, refract, reflect, reflectance, random_unit_vector
5 | ti.init(arch=ti.gpu)
6 |
7 | # Canvas
8 | aspect_ratio = 1.0
9 | image_width = 800
10 | image_height = int(image_width / aspect_ratio)
11 | canvas = ti.Vector.field(3, dtype=ti.f32, shape=(image_width, image_height))
12 |
13 | # Rendering parameters
14 | samples_per_pixel = 4
15 | max_depth = 10
16 | sample_on_unit_sphere_surface = True
17 |
18 |
19 | @ti.kernel
20 | def render():
21 | for i, j in canvas:
22 | u = (i + ti.random()) / image_width
23 | v = (j + ti.random()) / image_height
24 | color = ti.Vector([0.0, 0.0, 0.0])
25 | for n in range(samples_per_pixel):
26 | ray = camera.get_ray(u, v)
27 | color += ray_color(ray)
28 | color /= samples_per_pixel
29 | canvas[i, j] += color
30 |
31 | # Path tracing
32 | @ti.func
33 | def ray_color(ray):
34 | color_buffer = ti.Vector([0.0, 0.0, 0.0])
35 | brightness = ti.Vector([1.0, 1.0, 1.0])
36 | scattered_origin = ray.origin
37 | scattered_direction = ray.direction
38 | p_RR = 0.8
39 | for n in range(max_depth):
40 | if ti.random() > p_RR:
41 | break
42 | is_hit, hit_point, hit_point_normal, front_face, material, color = scene.hit(Ray(scattered_origin, scattered_direction))
43 | if is_hit:
44 | if material == 0:
45 | color_buffer = color * brightness
46 | break
47 | else:
48 | # Diffuse
49 | if material == 1:
50 | target = hit_point + hit_point_normal
51 | if sample_on_unit_sphere_surface:
52 | target += random_unit_vector()
53 | else:
54 | target += random_in_unit_sphere()
55 | scattered_direction = target - hit_point
56 | scattered_origin = hit_point
57 | brightness *= color
58 | # Metal and Fuzz Metal
59 | elif material == 2 or material == 4:
60 | fuzz = 0.0
61 | if material == 4:
62 | fuzz = 0.4
63 | scattered_direction = reflect(scattered_direction.normalized(),
64 | hit_point_normal)
65 | if sample_on_unit_sphere_surface:
66 | scattered_direction += fuzz * random_unit_vector()
67 | else:
68 | scattered_direction += fuzz * random_in_unit_sphere()
69 | scattered_origin = hit_point
70 | if scattered_direction.dot(hit_point_normal) < 0:
71 | break
72 | else:
73 | brightness *= color
74 | # Dielectric
75 | elif material == 3:
76 | refraction_ratio = 1.5
77 | if front_face:
78 | refraction_ratio = 1 / refraction_ratio
79 | cos_theta = min(-scattered_direction.normalized().dot(hit_point_normal), 1.0)
80 | sin_theta = ti.sqrt(1 - cos_theta * cos_theta)
81 | # total internal reflection
82 | if refraction_ratio * sin_theta > 1.0 or reflectance(cos_theta, refraction_ratio) > ti.random():
83 | scattered_direction = reflect(scattered_direction.normalized(), hit_point_normal)
84 | else:
85 | scattered_direction = refract(scattered_direction.normalized(), hit_point_normal, refraction_ratio)
86 | scattered_origin = hit_point
87 | brightness *= color
88 | brightness /= p_RR
89 | return color_buffer
90 |
91 |
92 | if __name__ == "__main__":
93 | parser = argparse.ArgumentParser(description='Naive Ray Tracing')
94 | parser.add_argument(
95 | '--max_depth', type=int, default=10, help='max depth (default: 10)')
96 | parser.add_argument(
97 | '--samples_per_pixel', type=int, default=4, help='samples_per_pixel (default: 4)')
98 | parser.add_argument(
99 | '--samples_in_unit_sphere', action='store_true', help='whether sample in a unit sphere')
100 | args = parser.parse_args()
101 |
102 | max_depth = args.max_depth
103 | samples_per_pixel = args.samples_per_pixel
104 | sample_on_unit_sphere_surface = not args.samples_in_unit_sphere
105 | scene = Hittable_list()
106 |
107 | # Light source
108 | scene.add(Sphere(center=ti.Vector([0, 5.4, -1]), radius=3.0, material=0, color=ti.Vector([10.0, 10.0, 10.0])))
109 | # Ground
110 | scene.add(Sphere(center=ti.Vector([0, -100.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
111 | # ceiling
112 | scene.add(Sphere(center=ti.Vector([0, 102.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
113 | # back wall
114 | scene.add(Sphere(center=ti.Vector([0, 1, 101]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
115 | # right wall
116 | scene.add(Sphere(center=ti.Vector([-101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.6, 0.0, 0.0])))
117 | # left wall
118 | scene.add(Sphere(center=ti.Vector([101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.0, 0.6, 0.0])))
119 |
120 | # Diffuse ball
121 | scene.add(Sphere(center=ti.Vector([0, -0.2, -1.5]), radius=0.3, material=1, color=ti.Vector([0.8, 0.3, 0.3])))
122 | # Metal ball
123 | scene.add(Sphere(center=ti.Vector([-0.8, 0.2, -1]), radius=0.7, material=2, color=ti.Vector([0.6, 0.8, 0.8])))
124 | # Glass ball
125 | scene.add(Sphere(center=ti.Vector([0.7, 0, -0.5]), radius=0.5, material=3, color=ti.Vector([1.0, 1.0, 1.0])))
126 | # Metal ball-2
127 | scene.add(Sphere(center=ti.Vector([0.6, -0.3, -2.0]), radius=0.2, material=4, color=ti.Vector([0.8, 0.6, 0.2])))
128 |
129 | camera = Camera()
130 | gui = ti.GUI("Ray Tracing", res=(image_width, image_height))
131 | canvas.fill(0)
132 | cnt = 0
133 | while gui.running:
134 | render()
135 | cnt += 1
136 | gui.set_image(np.sqrt(canvas.to_numpy() / cnt))
137 | gui.show()
138 |
--------------------------------------------------------------------------------
/ray_tracing_models.py:
--------------------------------------------------------------------------------
1 | import taichi as ti
2 |
3 | PI = 3.14159265
4 |
5 | @ti.func
6 | def rand3():
7 | return ti.Vector([ti.random(), ti.random(), ti.random()])
8 |
9 | @ti.func
10 | def random_in_unit_sphere():
11 | p = 2.0 * rand3() - ti.Vector([1, 1, 1])
12 | while p.norm() >= 1.0:
13 | p = 2.0 * rand3() - ti.Vector([1, 1, 1])
14 | return p
15 |
16 | @ti.func
17 | def random_unit_vector():
18 | return random_in_unit_sphere().normalized()
19 |
20 | @ti.func
21 | def to_light_source(hit_point, light_source):
22 | return light_source - hit_point
23 |
24 | @ti.func
25 | def reflect(v, normal):
26 | return v - 2 * v.dot(normal) * normal
27 |
28 | @ti.func
29 | def refract(uv, n, etai_over_etat):
30 | cos_theta = min(n.dot(-uv), 1.0)
31 | r_out_perp = etai_over_etat * (uv + cos_theta * n)
32 | r_out_parallel = -ti.sqrt(abs(1.0 - r_out_perp.dot(r_out_perp))) * n
33 | return r_out_perp + r_out_parallel
34 |
35 | @ti.func
36 | def reflectance(cosine, ref_idx):
37 | # Use Schlick's approximation for reflectance.
38 | r0 = (1 - ref_idx) / (1 + ref_idx)
39 | r0 = r0 * r0
40 | return r0 + (1 - r0) * pow((1 - cosine), 5)
41 |
42 | @ti.data_oriented
43 | class Ray:
44 | def __init__(self, origin, direction):
45 | self.origin = origin
46 | self.direction = direction
47 | def at(self, t):
48 | return self.origin + t * self.direction
49 |
50 | @ti.data_oriented
51 | class Sphere:
52 | def __init__(self, center, radius, material, color):
53 | self.center = center
54 | self.radius = radius
55 | self.material = material
56 | self.color = color
57 |
58 | @ti.func
59 | def hit(self, ray, t_min=0.001, t_max=10e8):
60 | oc = ray.origin - self.center
61 | a = ray.direction.dot(ray.direction)
62 | b = 2.0 * oc.dot(ray.direction)
63 | c = oc.dot(oc) - self.radius * self.radius
64 | discriminant = b * b - 4 * a * c
65 | is_hit = False
66 | front_face = False
67 | root = 0.0
68 | hit_point = ti.Vector([0.0, 0.0, 0.0])
69 | hit_point_normal = ti.Vector([0.0, 0.0, 0.0])
70 | if discriminant > 0:
71 | sqrtd = ti.sqrt(discriminant)
72 | root = (-b - sqrtd) / (2 * a)
73 | if root < t_min or root > t_max:
74 | root = (-b + sqrtd) / (2 * a)
75 | if root >= t_min and root <= t_max:
76 | is_hit = True
77 | else:
78 | is_hit = True
79 | if is_hit:
80 | hit_point = ray.at(root)
81 | hit_point_normal = (hit_point - self.center) / self.radius
82 | # Check which side does the ray hit, we set the hit point normals always point outward from the surface
83 | if ray.direction.dot(hit_point_normal) < 0:
84 | front_face = True
85 | else:
86 | hit_point_normal = -hit_point_normal
87 | return is_hit, root, hit_point, hit_point_normal, front_face, self.material, self.color
88 |
89 | @ti.data_oriented
90 | class Hittable_list:
91 | def __init__(self):
92 | self.objects = []
93 | def add(self, obj):
94 | self.objects.append(obj)
95 | def clear(self):
96 | self.objects = []
97 |
98 | @ti.func
99 | def hit(self, ray, t_min=0.001, t_max=10e8):
100 | closest_t = t_max
101 | is_hit = False
102 | front_face = False
103 | hit_point = ti.Vector([0.0, 0.0, 0.0])
104 | hit_point_normal = ti.Vector([0.0, 0.0, 0.0])
105 | color = ti.Vector([0.0, 0.0, 0.0])
106 | material = 1
107 | for index in ti.static(range(len(self.objects))):
108 | is_hit_tmp, root_tmp, hit_point_tmp, hit_point_normal_tmp, front_face_tmp, material_tmp, color_tmp = self.objects[index].hit(ray, t_min, closest_t)
109 | if is_hit_tmp:
110 | closest_t = root_tmp
111 | is_hit = is_hit_tmp
112 | hit_point = hit_point_tmp
113 | hit_point_normal = hit_point_normal_tmp
114 | front_face = front_face_tmp
115 | material = material_tmp
116 | color = color_tmp
117 | return is_hit, hit_point, hit_point_normal, front_face, material, color
118 |
119 | @ti.func
120 | def hit_shadow(self, ray, t_min=0.001, t_max=10e8):
121 | is_hit_source = False
122 | is_hit_source_temp = False
123 | hitted_dielectric_num = 0
124 | is_hitted_non_dielectric = False
125 | # Compute the t_max to light source
126 | is_hit_tmp, root_light_source, hit_point_tmp, hit_point_normal_tmp, front_face_tmp, material_tmp, color_tmp = \
127 | self.objects[0].hit(ray, t_min)
128 | for index in ti.static(range(len(self.objects))):
129 | is_hit_tmp, root_tmp, hit_point_tmp, hit_point_normal_tmp, front_face_tmp, material_tmp, color_tmp = self.objects[index].hit(ray, t_min, root_light_source)
130 | if is_hit_tmp:
131 | if material_tmp != 3 and material_tmp != 0:
132 | is_hitted_non_dielectric = True
133 | if material_tmp == 3:
134 | hitted_dielectric_num += 1
135 | if material_tmp == 0:
136 | is_hit_source_temp = True
137 | if is_hit_source_temp and (not is_hitted_non_dielectric) and hitted_dielectric_num == 0:
138 | is_hit_source = True
139 | return is_hit_source, hitted_dielectric_num, is_hitted_non_dielectric
140 |
141 |
142 | @ti.data_oriented
143 | class Camera:
144 | def __init__(self, fov=60, aspect_ratio=1.0):
145 | # Camera parameters
146 | self.lookfrom = ti.Vector.field(3, dtype=ti.f32, shape=())
147 | self.lookat = ti.Vector.field(3, dtype=ti.f32, shape=())
148 | self.vup = ti.Vector.field(3, dtype=ti.f32, shape=())
149 | self.fov = fov
150 | self.aspect_ratio = aspect_ratio
151 |
152 | self.cam_lower_left_corner = ti.Vector.field(3, dtype=ti.f32, shape=())
153 | self.cam_horizontal = ti.Vector.field(3, dtype=ti.f32, shape=())
154 | self.cam_vertical = ti.Vector.field(3, dtype=ti.f32, shape=())
155 | self.cam_origin = ti.Vector.field(3, dtype=ti.f32, shape=())
156 | self.reset()
157 |
158 | @ti.kernel
159 | def reset(self):
160 | self.lookfrom[None] = [0.0, 1.0, -5.0]
161 | self.lookat[None] = [0.0, 1.0, -1.0]
162 | self.vup[None] = [0.0, 1.0, 0.0]
163 | theta = self.fov * (PI / 180.0)
164 | half_height = ti.tan(theta / 2.0)
165 | half_width = self.aspect_ratio * half_height
166 | self.cam_origin[None] = self.lookfrom[None]
167 | w = (self.lookfrom[None] - self.lookat[None]).normalized()
168 | u = (self.vup[None].cross(w)).normalized()
169 | v = w.cross(u)
170 | self.cam_lower_left_corner[None] = ti.Vector([-half_width, -half_height, -1.0])
171 | self.cam_lower_left_corner[
172 | None] = self.cam_origin[None] - half_width * u - half_height * v - w
173 | self.cam_horizontal[None] = 2 * half_width * u
174 | self.cam_vertical[None] = 2 * half_height * v
175 |
176 | @ti.func
177 | def get_ray(self, u, v):
178 | return Ray(self.cam_origin[None], self.cam_lower_left_corner[None] + u * self.cam_horizontal[None] + v * self.cam_vertical[None] - self.cam_origin[None])
--------------------------------------------------------------------------------
/3_2_whitted_style_ray_tracing.py:
--------------------------------------------------------------------------------
1 | import taichi as ti
2 | import numpy as np
3 | import argparse
4 | from ray_tracing_models import Ray, Camera, Hittable_list, Sphere, PI, random_in_unit_sphere, refract, reflect, reflectance, to_light_source
5 | ti.init(arch=ti.gpu)
6 |
7 | PI = 3.14159265
8 | # Canvas
9 | aspect_ratio = 1.0
10 | image_width = 800
11 | image_height = int(image_width / aspect_ratio)
12 | canvas = ti.Vector.field(3, dtype=ti.f32, shape=(image_width, image_height))
13 | light_source = ti.Vector([0, 5.4 - 3.0, -1])
14 |
15 | # Rendering parameters
16 | samples_per_pixel = 4
17 | stack_depth = 10
18 |
19 | @ti.kernel
20 | def render():
21 | for i, j in canvas:
22 | u = (i + ti.random()) / image_width
23 | v = (j + ti.random()) / image_height
24 | color = ti.Vector([0.0, 0.0, 0.0])
25 | for n in range(samples_per_pixel):
26 | ray = camera.get_ray(u, v)
27 | color += ray_color(ray, i, j)
28 | color /= samples_per_pixel
29 | canvas[i, j] += color
30 |
31 | @ti.func
32 | def to_light_source(hit_point, light_source):
33 | return light_source - hit_point
34 |
35 |
36 | @ti.func
37 | def blinn_phong(ray_direction, hit_point, hit_point_normal, color, material):
38 | # Compute the local color use Blinn-Phong model
39 | hit_point_to_source = to_light_source(hit_point, light_source)
40 | # Diffuse light
41 | diffuse_color = color * ti.max(
42 | hit_point_to_source.dot(hit_point_normal) / (
43 | hit_point_to_source.norm() * hit_point_normal.norm()),
44 | 0.0)
45 | specular_color = ti.Vector([0.0, 0.0, 0.0])
46 | diffuse_weight = 1.0
47 | specular_weight = 1.0
48 | if material != 1:
49 | # Specular light
50 | H = (-(ray_direction.normalized()) + hit_point_to_source.normalized()).normalized()
51 | N_dot_H = ti.max(H.dot(hit_point_normal.normalized()), 0.0)
52 | intensity = ti.pow(N_dot_H, 10)
53 | specular_color = intensity * color
54 |
55 | # Dieletric
56 | if material == 3:
57 | diffuse_weight = 0.1
58 | # Fuzz metal ball
59 | if material == 4:
60 | diffuse_weight = 0.5
61 | specular_weight = 0.5
62 |
63 | # Add shadow
64 | is_hit_source, hitted_dielectric_num, is_hitted_non_dielectric = scene.hit_shadow(
65 | Ray(hit_point, hit_point_to_source))
66 | shadow_weight = 1.0
67 | if not is_hit_source:
68 | if is_hitted_non_dielectric:
69 | # Add hard shadow
70 | shadow_weight = 0
71 | elif hitted_dielectric_num > 0:
72 | # Add soft shadow if the obstacles are dielectric
73 | shadow_weight = ti.pow(0.5, hitted_dielectric_num)
74 |
75 | return (diffuse_weight * diffuse_color + specular_weight * specular_color) * shadow_weight
76 |
77 |
78 | origin_stack = ti.Vector.field(3, dtype=float, shape=(image_width, image_height, stack_depth))
79 | origin_stack_pointer = ti.field(dtype=int, shape=(image_width, image_height))
80 |
81 | direction_stack = ti.Vector.field(3, dtype=float, shape=(image_width, image_height, stack_depth))
82 | direction_stack_pointer = ti.field(dtype=int, shape=(image_width, image_height))
83 |
84 | reflect_refract_stack = ti.Vector.field(2, dtype=int, shape=(image_width, image_height, stack_depth))
85 | reflect_refract_stack_pointer = ti.field(dtype=int, shape=(image_width, image_height))
86 |
87 | color_weight_stack = ti.field(dtype=float, shape=(image_width, image_height, stack_depth))
88 | color_weight_stack_pointer = ti.field(dtype=int, shape=(image_width, image_height))
89 |
90 | @ti.func
91 | def stack_clear(i, j):
92 | origin_stack_pointer[i, j] = -1
93 | direction_stack_pointer[i, j] = -1
94 | reflect_refract_stack_pointer[i, j] = -1
95 | color_weight_stack_pointer[i, j] = -1
96 |
97 | @ti.func
98 | def stack_push(i, j, hit_point, new_direction, color_weight):
99 | origin_stack_pointer[i, j] += 1
100 | direction_stack_pointer[i, j] += 1
101 | reflect_refract_stack_pointer[i, j] += 1
102 | color_weight_stack_pointer[i, j] += 1
103 |
104 | origin_stack[i, j, origin_stack_pointer[i, j]] = hit_point
105 | direction_stack[i, j, direction_stack_pointer[i, j]] = new_direction
106 | reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]] = ti.Vector([0, 0])
107 | color_weight_stack[i, j, color_weight_stack_pointer[i, j]] = color_weight
108 |
109 | @ti.func
110 | def stack_pop(i, j):
111 | origin_stack_pointer[i, j] -= 1
112 | direction_stack_pointer[i, j] -= 1
113 | reflect_refract_stack_pointer[i, j] -= 1
114 | color_weight_stack_pointer[i, j] -= 1
115 |
116 | @ti.func
117 | def stack_top(i, j):
118 | return origin_stack[i, j, origin_stack_pointer[i, j]], \
119 | direction_stack[i, j, direction_stack_pointer[i, j]], \
120 | reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]], \
121 | color_weight_stack[i, j, color_weight_stack_pointer[i, j]]
122 |
123 |
124 | # Whitted-style ray tracing
125 | @ti.func
126 | def ray_color(ray, i, j):
127 | color_buffer = ti.Vector([0.0, 0.0, 0.0])
128 | color_buffer_temp = ti.Vector([0.0, 0.0, 0.0])
129 | stack_clear(i, j)
130 | stack_push(i, j, ray.origin, ray.direction, 1.0)
131 | while origin_stack_pointer[i, j] >= 0 and origin_stack_pointer[i, j] < stack_depth:
132 | # Fetch a ray
133 | curr_origin, curr_direction, curr_relect_refract, color_weight = stack_top(i, j)
134 | is_hit, hit_point, hit_point_normal, front_face, material, color = scene.hit(Ray(curr_origin, curr_direction))
135 | if is_hit:
136 | # Light source
137 | if material == 0:
138 | color_buffer = color * color_weight
139 | # Pop
140 | stack_pop(i, j)
141 | # Diffuse
142 | elif material == 1:
143 | local_color = blinn_phong(curr_direction, hit_point, hit_point_normal, color, material)
144 | color_buffer = local_color * color_weight
145 | # Pop
146 | stack_pop(i, j)
147 | # Metal
148 | elif material == 2 or material == 4:
149 | fuzz = 0.0
150 | if material == 4:
151 | fuzz = 0.4
152 | refected = reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]][0]
153 | if not refected:
154 | # Reflect
155 | reflected_direction = reflect(curr_direction.normalized(), hit_point_normal) + fuzz * random_in_unit_sphere()
156 | reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]][0] = 1
157 | if reflected_direction.dot(hit_point_normal) > 0:
158 | stack_push(i, j, hit_point, reflected_direction, 1.0)
159 | else:
160 | local_color = blinn_phong(curr_direction, hit_point, hit_point_normal, color, material)
161 | color_buffer += local_color
162 | stack_pop(i, j)
163 | # Dieletric
164 | elif material == 3:
165 | refraction_ratio = 1.5
166 | if front_face:
167 | refraction_ratio = 1 / refraction_ratio
168 | cos_theta = min(-curr_direction.normalized().dot(hit_point_normal), 1.0)
169 | sin_theta = ti.sqrt(1 - cos_theta * cos_theta)
170 | reflect_weight = reflectance(cos_theta, refraction_ratio)
171 | refract_weight = 1- reflect_weight
172 |
173 | refected = reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]][0]
174 | refracted = reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]][1]
175 | if not refected:
176 | # Reflect
177 | reflected_direction = reflect(curr_direction.normalized(), hit_point_normal) # + fuzz * random_in_unit_sphere()
178 | reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]][0] = 1
179 | stack_push(i, j, hit_point, reflected_direction, reflect_weight)
180 | else:
181 | local_color = blinn_phong(curr_direction, hit_point, hit_point_normal, color, material)
182 | color_buffer = color_buffer + 0.1 * local_color
183 | stack_pop(i, j)
184 |
185 | if not refracted:
186 | # Check total internal reflection
187 | if refraction_ratio * sin_theta <= 1.0:
188 | # Refract
189 | refracted_direction = refract(curr_direction.normalized(), hit_point_normal, refraction_ratio)
190 | reflect_refract_stack[i, j, reflect_refract_stack_pointer[i, j]][1] = 1
191 | stack_push(i, j, hit_point, refracted_direction, refract_weight)
192 | else:
193 | local_color = blinn_phong(curr_direction, hit_point, hit_point_normal, color, material)
194 | color_buffer = color_buffer + 0.1 * local_color
195 | stack_pop(i, j)
196 | else:
197 | stack_pop(i, j)
198 | else:
199 | stack_pop(i, j)
200 |
201 | return color_buffer
202 |
203 | if __name__ == "__main__":
204 | parser = argparse.ArgumentParser(description='Naive Ray Tracing')
205 | parser.add_argument(
206 | '--max_depth', type=int, default=10, help='max depth (default: 10)')
207 | parser.add_argument(
208 | '--samples_per_pixel', type=int, default=4, help='samples_per_pixel (default: 4)')
209 | args = parser.parse_args()
210 |
211 | max_depth = args.max_depth
212 | samples_per_pixel = args.samples_per_pixel
213 | scene = Hittable_list()
214 |
215 | # Light source
216 | scene.add(Sphere(center=ti.Vector([0, 5.4, -1]), radius=3.0, material=0, color=ti.Vector([10.0, 10.0, 10.0])))
217 | # Ground
218 | scene.add(Sphere(center=ti.Vector([0, -100.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
219 | # ceiling
220 | scene.add(Sphere(center=ti.Vector([0, 102.5, -1]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
221 | # back wall
222 | scene.add(Sphere(center=ti.Vector([0, 1, 101]), radius=100.0, material=1, color=ti.Vector([0.8, 0.8, 0.8])))
223 | # right wall
224 | scene.add(Sphere(center=ti.Vector([-101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.6, 0.0, 0.0])))
225 | # left wall
226 | scene.add(Sphere(center=ti.Vector([101.5, 0, -1]), radius=100.0, material=1, color=ti.Vector([0.0, 0.6, 0.0])))
227 |
228 | # Diffuse ball
229 | scene.add(Sphere(center=ti.Vector([0, -0.2, -1.5]), radius=0.3, material=1, color=ti.Vector([0.8, 0.3, 0.3])))
230 | # Metal ball
231 | scene.add(Sphere(center=ti.Vector([-0.8, 0.2, -1]), radius=0.7, material=2, color=ti.Vector([0.6, 0.8, 0.8])))
232 | # Glass ball
233 | scene.add(Sphere(center=ti.Vector([0.7, 0, -0.5]), radius=0.5, material=3, color=ti.Vector([1.0, 1.0, 1.0])))
234 | # Metal ball-2
235 | scene.add(Sphere(center=ti.Vector([0.6, -0.3, -2.0]), radius=0.2, material=4, color=ti.Vector([0.8, 0.6, 0.2])))
236 |
237 | camera = Camera()
238 | gui = ti.GUI("Ray Tracing", res=(image_width, image_height))
239 | canvas.fill(0)
240 | cnt = 0
241 | while gui.running:
242 | render()
243 | cnt += 1
244 | gui.set_image(np.sqrt(canvas.to_numpy() / cnt))
245 | gui.show()
--------------------------------------------------------------------------------