Browse Source

refactor + using faster thread local rng

Inderjit Gill 5 months ago
parent
commit
c3384ff161
8 changed files with 343 additions and 139 deletions
  1. 2
    0
      .gitignore
  2. 5
    5
      src/camera.rs
  3. 98
    118
      src/main.rs
  4. 30
    10
      src/material.rs
  5. 6
    0
      src/picture.rs
  6. 0
    6
      src/rnd.rs
  7. 21
    0
      src/rng.rs
  8. 181
    0
      src/scene.rs

+ 2
- 0
.gitignore View File

@@ -1,3 +1,5 @@
1 1
 /target
2 2
 **/*.rs.bk
3 3
 /research
4
+
5
+auto.png

+ 5
- 5
src/camera.rs View File

@@ -1,7 +1,7 @@
1 1
 use cgmath::prelude::*;
2 2
 
3 3
 use ray::*;
4
-use rnd::*;
4
+use rng::ThreadLocalRng;
5 5
 use types::*;
6 6
 
7 7
 pub struct Camera {
@@ -48,8 +48,8 @@ impl Camera {
48 48
         }
49 49
     }
50 50
 
51
-    pub fn get_ray(&self, s: S, t: S) -> Ray {
52
-        let rd = self.lens_radius * random_in_unit_disk();
51
+    pub fn get_ray(&self, rng: &mut ThreadLocalRng, s: S, t: S) -> Ray {
52
+        let rd = self.lens_radius * random_in_unit_disk(rng);
53 53
         let offset = self.u * rd.x + self.v * rd.y;
54 54
         Ray::new(
55 55
             self.origin + offset,
@@ -58,11 +58,11 @@ impl Camera {
58 58
     }
59 59
 }
60 60
 
61
-fn random_in_unit_disk() -> V3 {
61
+fn random_in_unit_disk(rng: &mut ThreadLocalRng) -> V3 {
62 62
     let mut p = V3::new(10.0, 10.0, 10.0);
63 63
 
64 64
     while p.dot(p) >= 1.0 {
65
-        p = 2.0 * V3::new(rand(), rand(), 0.0) - V3::new(1.0, 1.0, 0.0);
65
+        p = 2.0 * V3::new(rng.gen(), rng.gen(), 0.0) - V3::new(1.0, 1.0, 0.0);
66 66
     }
67 67
 
68 68
     p

+ 98
- 118
src/main.rs View File

@@ -15,8 +15,10 @@ extern crate rand;
15 15
 mod camera;
16 16
 mod error;
17 17
 mod material;
18
+mod picture;
18 19
 mod ray;
19
-mod rnd;
20
+mod rng;
21
+mod scene;
20 22
 mod shape;
21 23
 mod types;
22 24
 
@@ -25,11 +27,14 @@ use clap::{App, Arg};
25 27
 use image::{ImageBuffer, Rgba, RgbaImage};
26 28
 use std::time::SystemTime;
27 29
 
28
-use camera::*;
30
+// use rand::prelude::*;
31
+// use rand::distributions::{Distribution, Uniform};
32
+
29 33
 use error::*;
30
-use material::*;
34
+use picture::*;
31 35
 use ray::*;
32
-use rnd::*;
36
+use rng::ThreadLocalRng;
37
+use scene::*;
33 38
 use shape::*;
34 39
 use types::*;
35 40
 
@@ -53,6 +58,12 @@ fn main() -> Result<()> {
53 58
                 .help("The number of images along the y-axis")
54 59
                 .takes_value(true),
55 60
         ).arg(
61
+            Arg::with_name("samples")
62
+                .short("s")
63
+                .long("samples")
64
+                .help("The number of samples per pixel")
65
+                .takes_value(true),
66
+        ).arg(
56 67
             Arg::with_name("output")
57 68
                 .short("o")
58 69
                 .long("output")
@@ -66,18 +77,77 @@ fn main() -> Result<()> {
66 77
     if let Some(o) = matches.value_of("output") {
67 78
         output = o;
68 79
     } else {
80
+        let now = SystemTime::now();
81
+        let mut sum: S = 0.0;
82
+
83
+        let mut tl_rng = ThreadLocalRng::new();
84
+
85
+        // let range = Uniform::new(0.0, 1.0);
86
+        // let mut rng = rand::thread_rng();
87
+
88
+        // thread_rng + gen test: 16.572s (sum=-2225.2817)
89
+        // thread_rng + gen test: 16.629s (sum=4477.5664)
90
+        // thread_rng + gen test: 16.648s (sum=3463.85)
91
+        //
92
+        // for i in 0..100_000_000 {
93
+        //     if i % 2 == 0 {
94
+        //         sum += rng.gen::<S>();
95
+        //     } else {
96
+        //         sum -= rng.gen::<S>();
97
+        //     }
98
+        // }
99
+
100
+        // thread_rng + range test: 15.964s (sum=9989.141)
101
+        // thread_rng + range test: 15.995s (sum=-350.22742)
102
+        // thread_rng + range test: 15.969s (sum=-1650.2385)
103
+        //
104
+        for i in 0..100_000_000 {
105
+            if i % 2 == 0 {
106
+                sum += tl_rng.gen(); // rngcall(); // range.sample(&mut rng);
107
+            } else {
108
+                sum -= tl_rng.gen(); // rngcall(); // range.sample(&mut rng);
109
+            }
110
+        }
111
+
112
+        // old test: 31.003s (sum=-682.41144)
113
+        // old test: 30.997s (sum=-41.44916)
114
+        // old test: 31.039s (sum=2203.6802)
115
+        //
116
+        // for i in 0..100_000_000 {
117
+        //     if i % 2 == 0 {
118
+        //         sum += rand();
119
+        //     } else {
120
+        //         sum -= rand();
121
+        //     }
122
+        // }
123
+        let elapsed = now.elapsed()?;
124
+        let ftime = (elapsed.as_secs() as f32) + ((elapsed.subsec_millis() as f32) / 1000.0);
125
+        println!("ThreadLocalRNG test: {}s (sum={})", ftime, sum);
126
+
69 127
         // assume that we have no command line arguments, just generate a default image
70
-        let img = ray_trace_main(100, 50)?;
128
+        let img = ray_trace_main(
129
+            ScenePreset::Basic,
130
+            Picture {
131
+                width: 200,
132
+                height: 100,
133
+                samples: 10,
134
+            },
135
+        )?;
71 136
         img.save("auto.png")?;
72 137
         return Ok(());
73 138
     }
74 139
 
75
-    let width = value_t!(matches, "width", u32).unwrap_or(1);
76
-    let height = value_t!(matches, "height", u32).unwrap_or(1);
140
+    let width = value_t!(matches, "width", u32).unwrap_or(100);
141
+    let height = value_t!(matches, "height", u32).unwrap_or(50);
142
+    let samples = value_t!(matches, "samples", u32).unwrap_or(1);
77 143
 
78
-    // cargo run -- -w 100 -h 120 -o foo.png
144
+    let picture = Picture {
145
+        width,
146
+        height,
147
+        samples,
148
+    };
79 149
 
80
-    let img = ray_trace_main(width, height)?;
150
+    let img = ray_trace_main(ScenePreset::Basic, picture)?;
81 151
 
82 152
     // save to disk
83 153
     img.save(output)?;
@@ -85,132 +155,42 @@ fn main() -> Result<()> {
85 155
     Ok(())
86 156
 }
87 157
 
88
-fn random_scene() -> HitableList {
89
-    let mut world = HitableList::new();
90
-
91
-    world.add_sphere(Sphere {
92
-        center: V3::new(0.0, -1000.0, 0.0),
93
-        radius: 1000.0,
94
-        material: Box::new(Lambertian {
95
-            albedo: V3::new(0.5, 0.5, 0.5),
96
-        }),
97
-    });
98
-
99
-    for a in -11..11 {
100
-        for b in -11..11 {
101
-            let choose_mat = rand();
102
-            let center = V3::new(a as S + 0.9 * rand(), 0.2, b as S + 0.9 * rand());
103
-
104
-            if (center - V3::new(4.0, 0.2, 0.0)).magnitude() > 0.9 {
105
-                if choose_mat < 0.8 {
106
-                    world.add_sphere(Sphere {
107
-                        center,
108
-                        radius: 0.2,
109
-                        material: Box::new(Lambertian {
110
-                            albedo: V3::new(rand() * rand(), rand() * rand(), rand() * rand()),
111
-                        }),
112
-                    });
113
-                } else if choose_mat < 0.95 {
114
-                    world.add_sphere(Sphere {
115
-                        center,
116
-                        radius: 0.2,
117
-                        material: Box::new(Metal {
118
-                            albedo: V3::new(
119
-                                0.5 * (1.0 + rand()),
120
-                                0.5 * (1.0 + rand()),
121
-                                0.5 * (1.0 + rand()),
122
-                            ),
123
-                            fuzz: 0.5 * rand(),
124
-                        }),
125
-                    });
126
-                } else {
127
-                    world.add_sphere(Sphere {
128
-                        center,
129
-                        radius: 0.2,
130
-                        material: Box::new(Dielectric { ref_idx: 1.5 }),
131
-                    });
132
-                }
133
-            }
134
-        }
135
-    }
136
-
137
-    world.add_sphere(Sphere {
138
-        center: V3::new(0.0, 1.0, 0.0),
139
-        radius: 1.0,
140
-        material: Box::new(Dielectric { ref_idx: 1.5 }),
141
-    });
142
-
143
-    world.add_sphere(Sphere {
144
-        center: V3::new(-4.0, 1.0, 0.0),
145
-        radius: 1.0,
146
-        material: Box::new(Lambertian {
147
-            albedo: V3::new(0.4, 0.2, 0.1),
148
-        }),
149
-    });
150
-
151
-    world.add_sphere(Sphere {
152
-        center: V3::new(4.0, 1.0, 0.0),
153
-        radius: 1.0,
154
-        material: Box::new(Metal {
155
-            albedo: V3::new(0.7, 0.6, 0.5),
156
-            fuzz: 0.0,
157
-        }),
158
-    });
159
-
160
-    world
161
-}
162
-
163
-fn ray_trace_main(width: u32, height: u32) -> Result<(RgbaImage)> {
158
+fn ray_trace_main(scene_preset: ScenePreset, picture: Picture) -> Result<(RgbaImage)> {
164 159
     // create the combined image
165
-    let mut img: RgbaImage = ImageBuffer::new(width, height);
160
+    let mut img: RgbaImage = ImageBuffer::new(picture.width, picture.height);
166 161
 
167 162
     let now = SystemTime::now();
168 163
 
169
-    let ns = 100;
170
-
171
-    let world = random_scene();
164
+    let mut rng = ThreadLocalRng::new();
172 165
 
173
-    let look_from = V3::new(13.0, 2.0, 3.0);
174
-    let look_at = V3::new(0.0, 0.0, 0.0);
175
-    let dist_to_focus = (look_from - look_at).magnitude();
176
-    let aperture = 0.05;
177
-
178
-    let cam = Camera::new(
179
-        look_from,
180
-        look_at,
181
-        V3::new(0.0, 1.0, 0.0),
182
-        18.0,
183
-        width as S / height as S,
184
-        aperture,
185
-        dist_to_focus,
186
-    );
166
+    let scene = get_scene(scene_preset, picture, &mut rng);
187 167
 
188
-    for y in 0..height {
189
-        if y % (height / 10) == 0 {
190
-            println!("{}% complete", (100.0 / height as f32) * y as f32);
168
+    for y in 0..picture.height {
169
+        if y % (picture.height / 10) == 0 {
170
+            println!("{}% complete", (100.0 / picture.height as f32) * y as f32);
191 171
         }
192
-        for x in 0..width {
172
+        for x in 0..picture.width {
193 173
             let mut col = Col::new(0.0, 0.0, 0.0);
194 174
 
195
-            for _ in 0..ns {
196
-                let u = ((x as S) + rand()) / width as S;
197
-                let v = ((y as S) + rand()) / height as S;
175
+            for _ in 0..picture.samples {
176
+                let u = ((x as S) + rng.gen()) / picture.width as S;
177
+                let v = ((y as S) + rng.gen()) / picture.height as S;
198 178
 
199
-                let r = cam.get_ray(u, v);
200
-                col += colour(&r, &world, 0);
179
+                let r = scene.camera.get_ray(&mut rng, u, v);
180
+                col += colour(&mut rng, &r, &scene.hitables, 0);
201 181
             }
202 182
 
203
-            col /= ns as S;
183
+            col /= picture.samples as S;
204 184
 
205 185
             col = Col::new(col.x.sqrt(), col.y.sqrt(), col.z.sqrt());
206 186
 
207 187
             // ray tracing origin is in lower left, image buffer origin is in top left
208 188
             // so vertically flip image
209
-            img.put_pixel(x, height - y - 1, rgba_from_col(col))
189
+            img.put_pixel(x, picture.height - y - 1, rgba_from_col(col))
210 190
         }
211 191
     }
212 192
 
213
-    let primary_rays = (width * height * ns) as f32;
193
+    let primary_rays = (picture.width * picture.height * picture.samples) as f32;
214 194
 
215 195
     let elapsed = now.elapsed()?;
216 196
     let ftime = (elapsed.as_secs() as f32) + ((elapsed.subsec_millis() as f32) / 1000.0);
@@ -240,12 +220,12 @@ fn mult_col(a: Col, b: Col) -> Col {
240 220
     Col::new(a.x * b.x, a.y * b.y, a.z * b.z)
241 221
 }
242 222
 
243
-fn colour(ray: &Ray, world: &Hitable, depth: i32) -> Col {
223
+fn colour(rng: &mut ThreadLocalRng, ray: &Ray, world: &Hitable, depth: i32) -> Col {
244 224
     if let Some(hit_record) = world.hit(ray, 0.001, S_MAX) {
245 225
         if depth < 50 {
246
-            if let Some(scatter_info) = hit_record.material.scatter(ray, &hit_record) {
226
+            if let Some(scatter_info) = hit_record.material.scatter(rng, ray, &hit_record) {
247 227
                 mult_col(
248
-                    colour(&scatter_info.scattered, world, depth + 1),
228
+                    colour(rng, &scatter_info.scattered, world, depth + 1),
249 229
                     scatter_info.attenuation,
250 230
                 )
251 231
             } else {

+ 30
- 10
src/material.rs View File

@@ -1,7 +1,7 @@
1 1
 use cgmath::prelude::*;
2 2
 
3 3
 use ray::*;
4
-use rnd::*;
4
+use rng::ThreadLocalRng;
5 5
 use types::*;
6 6
 
7 7
 #[derive(Clone, Copy)]
@@ -18,7 +18,12 @@ pub struct MaterialScatterInfo {
18 18
 }
19 19
 
20 20
 pub trait Material {
21
-    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<MaterialScatterInfo>;
21
+    fn scatter(
22
+        &self,
23
+        rng: &mut ThreadLocalRng,
24
+        r_in: &Ray,
25
+        rec: &HitRecord,
26
+    ) -> Option<MaterialScatterInfo>;
22 27
 }
23 28
 
24 29
 #[derive(Debug, Clone, Copy)]
@@ -27,8 +32,13 @@ pub struct Lambertian {
27 32
 }
28 33
 
29 34
 impl Material for Lambertian {
30
-    fn scatter(&self, _r_in: &Ray, rec: &HitRecord) -> Option<MaterialScatterInfo> {
31
-        let target = rec.p + rec.normal + random_in_unit_sphere();
35
+    fn scatter(
36
+        &self,
37
+        rng: &mut ThreadLocalRng,
38
+        _r_in: &Ray,
39
+        rec: &HitRecord,
40
+    ) -> Option<MaterialScatterInfo> {
41
+        let target = rec.p + rec.normal + random_in_unit_sphere(rng);
32 42
         Some(MaterialScatterInfo {
33 43
             scattered: Ray::new(rec.p, target - rec.p),
34 44
             attenuation: self.albedo,
@@ -43,9 +53,14 @@ pub struct Metal {
43 53
 }
44 54
 
45 55
 impl Material for Metal {
46
-    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<MaterialScatterInfo> {
56
+    fn scatter(
57
+        &self,
58
+        rng: &mut ThreadLocalRng,
59
+        r_in: &Ray,
60
+        rec: &HitRecord,
61
+    ) -> Option<MaterialScatterInfo> {
47 62
         let reflected = reflect(r_in.direction().normalize(), rec.normal);
48
-        let scattered = Ray::new(rec.p, reflected + self.fuzz * random_in_unit_sphere());
63
+        let scattered = Ray::new(rec.p, reflected + self.fuzz * random_in_unit_sphere(rng));
49 64
 
50 65
         if scattered.direction().dot(rec.normal) > 0.0 {
51 66
             Some(MaterialScatterInfo {
@@ -64,7 +79,12 @@ pub struct Dielectric {
64 79
 }
65 80
 
66 81
 impl Material for Dielectric {
67
-    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<MaterialScatterInfo> {
82
+    fn scatter(
83
+        &self,
84
+        rng: &mut ThreadLocalRng,
85
+        r_in: &Ray,
86
+        rec: &HitRecord,
87
+    ) -> Option<MaterialScatterInfo> {
68 88
         let outward_normal: V3;
69 89
         let reflected = reflect(r_in.direction(), rec.normal);
70 90
         let ni_over_nt: S;
@@ -83,7 +103,7 @@ impl Material for Dielectric {
83 103
         if let Some(refracted) = refract(r_in.direction(), outward_normal, ni_over_nt) {
84 104
             let reflect_prob = schlick(cosine, self.ref_idx);
85 105
 
86
-            if rand() < reflect_prob {
106
+            if rng.gen() < reflect_prob {
87 107
                 Some(MaterialScatterInfo {
88 108
                     scattered: Ray::new(rec.p, reflected),
89 109
                     attenuation,
@@ -126,12 +146,12 @@ fn refract(v: V3, n: V3, ni_over_nt: S) -> Option<V3> {
126 146
     }
127 147
 }
128 148
 
129
-fn random_in_unit_sphere() -> V3 {
149
+fn random_in_unit_sphere(rng: &mut ThreadLocalRng) -> V3 {
130 150
     let v1 = V3::new(1.0, 1.0, 1.0);
131 151
     let mut p = V3::new(100.0, 100.0, 100.0);
132 152
 
133 153
     while p.magnitude2() >= 1.0 {
134
-        p = 2.0 * V3::new(rand(), rand(), rand()) - v1;
154
+        p = 2.0 * V3::new(rng.gen(), rng.gen(), rng.gen()) - v1;
135 155
     }
136 156
 
137 157
     p

+ 6
- 0
src/picture.rs View File

@@ -0,0 +1,6 @@
1
+#[derive(Clone, Copy)]
2
+pub struct Picture {
3
+    pub width: u32,
4
+    pub height: u32,
5
+    pub samples: u32,
6
+}

+ 0
- 6
src/rnd.rs View File

@@ -1,6 +0,0 @@
1
-use rand::prelude::*;
2
-use types::*;
3
-
4
-pub fn rand() -> S {
5
-    random::<S>()
6
-}

+ 21
- 0
src/rng.rs View File

@@ -0,0 +1,21 @@
1
+use rand::distributions::{Distribution, Uniform};
2
+use rand::prelude::*;
3
+use types::*;
4
+
5
+pub struct ThreadLocalRng {
6
+    pub range: Uniform<S>,
7
+    pub rng: ThreadRng,
8
+}
9
+
10
+impl ThreadLocalRng {
11
+    pub fn new() -> Self {
12
+        ThreadLocalRng {
13
+            range: Uniform::new(0.0, 1.0),
14
+            rng: thread_rng(),
15
+        }
16
+    }
17
+
18
+    pub fn gen(&mut self) -> S {
19
+        self.range.sample(&mut self.rng)
20
+    }
21
+}

+ 181
- 0
src/scene.rs View File

@@ -0,0 +1,181 @@
1
+use camera::*;
2
+use cgmath::prelude::*;
3
+use material::*;
4
+use picture::*;
5
+use rng::ThreadLocalRng;
6
+use shape::*;
7
+use types::*;
8
+
9
+// short-term hack
10
+pub enum ScenePreset {
11
+    Basic,
12
+    LoadOfBalls,
13
+}
14
+
15
+pub struct Scene {
16
+    pub camera: Camera,
17
+    pub hitables: HitableList,
18
+    // lights etc
19
+}
20
+
21
+pub fn get_scene(scene_preset: ScenePreset, picture: Picture, rng: &mut ThreadLocalRng) -> Scene {
22
+    match scene_preset {
23
+        ScenePreset::Basic => basic(picture),
24
+        ScenePreset::LoadOfBalls => load_of_balls(picture, rng),
25
+    }
26
+}
27
+
28
+fn basic(picture: Picture) -> Scene {
29
+    let mut world = HitableList::new();
30
+
31
+    world.add_sphere(Sphere {
32
+        center: V3::new(0.0, 0.0, -1.0),
33
+        radius: 0.5,
34
+        material: Box::new(Lambertian {
35
+            albedo: V3::new(0.8, 0.3, 0.3),
36
+        }),
37
+    });
38
+
39
+    world.add_sphere(Sphere {
40
+        center: V3::new(0.0, -100.5, -1.0),
41
+        radius: 100.0,
42
+        material: Box::new(Lambertian {
43
+            albedo: V3::new(0.8, 0.8, 0.0),
44
+        }),
45
+    });
46
+
47
+    world.add_sphere(Sphere {
48
+        center: V3::new(1.0, 0.0, -1.0),
49
+        radius: 0.5,
50
+        material: Box::new(Metal {
51
+            albedo: V3::new(0.8, 0.6, 0.2),
52
+            fuzz: 1.0,
53
+        }),
54
+    });
55
+
56
+    world.add_sphere(Sphere {
57
+        center: V3::new(-1.0, 0.0, -1.0),
58
+        radius: 0.5,
59
+        material: Box::new(Metal {
60
+            albedo: V3::new(0.8, 0.8, 0.8),
61
+            fuzz: 0.3,
62
+        }),
63
+    });
64
+
65
+    let look_from = V3::new(0.0, 0.0, 0.0);
66
+    let look_at = V3::new(0.0, 0.0, -1.0);
67
+    let dist_to_focus = (look_from - look_at).magnitude();
68
+    let aperture = 0.0;
69
+
70
+    let camera = Camera::new(
71
+        look_from,
72
+        look_at,
73
+        V3::new(0.0, 1.0, 0.0),
74
+        90.0,
75
+        picture.width as S / picture.height as S,
76
+        aperture,
77
+        dist_to_focus,
78
+    );
79
+
80
+    Scene {
81
+        camera,
82
+        hitables: world,
83
+    }
84
+}
85
+
86
+fn load_of_balls(picture: Picture, rng: &mut ThreadLocalRng) -> Scene {
87
+    let mut world = HitableList::new();
88
+
89
+    world.add_sphere(Sphere {
90
+        center: V3::new(0.0, -1000.0, 0.0),
91
+        radius: 1000.0,
92
+        material: Box::new(Lambertian {
93
+            albedo: V3::new(0.5, 0.5, 0.5),
94
+        }),
95
+    });
96
+
97
+    for a in -11..11 {
98
+        for b in -11..11 {
99
+            let choose_mat = rng.gen();
100
+            let center = V3::new(a as S + 0.9 * rng.gen(), 0.2, b as S + 0.9 * rng.gen());
101
+
102
+            if (center - V3::new(4.0, 0.2, 0.0)).magnitude() > 0.9 {
103
+                if choose_mat < 0.8 {
104
+                    world.add_sphere(Sphere {
105
+                        center,
106
+                        radius: 0.2,
107
+                        material: Box::new(Lambertian {
108
+                            albedo: V3::new(
109
+                                rng.gen() * rng.gen(),
110
+                                rng.gen() * rng.gen(),
111
+                                rng.gen() * rng.gen(),
112
+                            ),
113
+                        }),
114
+                    });
115
+                } else if choose_mat < 0.95 {
116
+                    world.add_sphere(Sphere {
117
+                        center,
118
+                        radius: 0.2,
119
+                        material: Box::new(Metal {
120
+                            albedo: V3::new(
121
+                                0.5 * (1.0 + rng.gen()),
122
+                                0.5 * (1.0 + rng.gen()),
123
+                                0.5 * (1.0 + rng.gen()),
124
+                            ),
125
+                            fuzz: 0.5 * rng.gen(),
126
+                        }),
127
+                    });
128
+                } else {
129
+                    world.add_sphere(Sphere {
130
+                        center,
131
+                        radius: 0.2,
132
+                        material: Box::new(Dielectric { ref_idx: 1.5 }),
133
+                    });
134
+                }
135
+            }
136
+        }
137
+    }
138
+
139
+    world.add_sphere(Sphere {
140
+        center: V3::new(0.0, 1.0, 0.0),
141
+        radius: 1.0,
142
+        material: Box::new(Dielectric { ref_idx: 1.5 }),
143
+    });
144
+
145
+    world.add_sphere(Sphere {
146
+        center: V3::new(-4.0, 1.0, 0.0),
147
+        radius: 1.0,
148
+        material: Box::new(Lambertian {
149
+            albedo: V3::new(0.4, 0.2, 0.1),
150
+        }),
151
+    });
152
+
153
+    world.add_sphere(Sphere {
154
+        center: V3::new(4.0, 1.0, 0.0),
155
+        radius: 1.0,
156
+        material: Box::new(Metal {
157
+            albedo: V3::new(0.7, 0.6, 0.5),
158
+            fuzz: 0.0,
159
+        }),
160
+    });
161
+
162
+    let look_from = V3::new(13.0, 2.0, 3.0);
163
+    let look_at = V3::new(0.0, 0.0, 0.0);
164
+    let dist_to_focus = (look_from - look_at).magnitude();
165
+    let aperture = 0.05;
166
+
167
+    let camera = Camera::new(
168
+        look_from,
169
+        look_at,
170
+        V3::new(0.0, 1.0, 0.0),
171
+        18.0,
172
+        picture.width as S / picture.height as S,
173
+        aperture,
174
+        dist_to_focus,
175
+    );
176
+
177
+    Scene {
178
+        camera,
179
+        hitables: world,
180
+    }
181
+}