├── 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() --------------------------------------------------------------------------------