├── README.md ├── .cargo └── config ├── .gitignore ├── Cargo.toml ├── src ├── ray.rs ├── rnd.rs ├── ppm.rs ├── hit.rs ├── sph.rs ├── cam.rs ├── vec.rs ├── mat.rs └── main.rs ├── LICENSE ├── .github └── workflows │ └── ci.yml └── Cargo.lock /README.md: -------------------------------------------------------------------------------- 1 | # ray-tracing 2 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-Ctarget-cpu=haswell"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ray-tracing" 3 | version = "0.1.0" 4 | authors = [] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rand = { version = "0.8", features = ["small_rng"] } 9 | 10 | [profile.release] 11 | panic = "abort" 12 | lto = true 13 | codegen-units = 1 14 | -------------------------------------------------------------------------------- /src/ray.rs: -------------------------------------------------------------------------------- 1 | use crate::vec::Vec3; 2 | 3 | #[derive(Debug)] 4 | pub struct Ray { 5 | pub origin: Vec3, 6 | pub direction: Vec3, 7 | } 8 | 9 | impl Ray { 10 | pub fn point_at(&self, t: f32) -> Vec3 { 11 | self.origin + t * self.direction 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/rnd.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::Standard; 2 | use rand::Rng; 3 | 4 | use crate::vec::Vec3; 5 | 6 | pub fn random_in_unit_sphere(rng: &mut impl Rng) -> Vec3 { 7 | loop { 8 | let p = 9 | 2.0 * Vec3( 10 | rng.sample(Standard), 11 | rng.sample(Standard), 12 | rng.sample(Standard), 13 | ) - Vec3(1., 1., 1.); 14 | if p.squared_length() < 1. { 15 | return p; 16 | } 17 | } 18 | } 19 | 20 | pub fn random_in_unit_disk(rng: &mut impl Rng) -> Vec3 { 21 | loop { 22 | let p = 2.0 * Vec3(rng.sample(Standard), rng.sample(Standard), 0.) - Vec3(1., 1., 0.); 23 | if p.squared_length() < 1. { 24 | return p; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ppm.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | pub struct Writer { 4 | writer: W, 5 | written: u64, 6 | expected: u64, 7 | } 8 | 9 | impl Writer { 10 | pub fn new(mut writer: W, x: u64, y: u64) -> Result { 11 | writeln!(writer, "P3")?; // colors are in ASCII 12 | writeln!(writer, "{} {}", x, y)?; // columns and rows 13 | writeln!(writer, "255")?; // max color 14 | Ok(Self { 15 | writer, 16 | written: 0, 17 | expected: x * y, 18 | }) 19 | } 20 | 21 | pub fn write_pixel(&mut self, r: u8, g: u8, b: u8) -> Result<(), io::Error> { 22 | writeln!(self.writer, "{} {} {}", r, g, b)?; 23 | self.written += 1; 24 | assert!( 25 | self.written <= self.expected, 26 | "too many pixels were written" 27 | ); 28 | Ok(()) 29 | } 30 | } 31 | 32 | impl Drop for Writer { 33 | fn drop(&mut self) { 34 | assert_eq!( 35 | self.written, self.expected, 36 | "correct number of pixels were written" 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/hit.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use crate::mat::Material; 4 | use crate::ray::Ray; 5 | use crate::vec::Vec3; 6 | 7 | pub struct HitRecord<'a> { 8 | pub t: f32, 9 | pub p: Vec3, 10 | pub normal: Vec3, 11 | pub material: &'a Material, 12 | } 13 | 14 | pub trait Hittable { 15 | fn hit(&self, r: &Ray, t_range: Range) -> Option>; 16 | } 17 | 18 | impl<'a, T: Hittable> Hittable for &'a T { 19 | fn hit(&self, r: &Ray, t_range: Range) -> Option> { 20 | T::hit(self, r, t_range) 21 | } 22 | } 23 | 24 | impl<'a, T: Hittable> Hittable for &'a mut T { 25 | fn hit(&self, r: &Ray, t_range: Range) -> Option> { 26 | T::hit(self, r, t_range) 27 | } 28 | } 29 | 30 | impl Hittable for Vec { 31 | fn hit(&self, r: &Ray, t_range: Range) -> Option> { 32 | let mut closest_so_far = t_range.end; 33 | let mut hit_rec = None; 34 | for h in self { 35 | if let Some(rec) = h.hit(r, t_range.start..closest_so_far) { 36 | closest_so_far = rec.t; 37 | hit_rec = Some(rec); 38 | } 39 | } 40 | hit_rec 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/sph.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use crate::hit::{HitRecord, Hittable}; 4 | use crate::mat::Material; 5 | use crate::ray::Ray; 6 | use crate::vec::{dot, Vec3}; 7 | 8 | pub struct Sphere { 9 | pub center: Vec3, 10 | pub radius: f32, 11 | pub material: Material, 12 | } 13 | 14 | impl Hittable for Sphere { 15 | fn hit(&self, r: &Ray, t_range: Range) -> Option> { 16 | let oc = r.origin - self.center; 17 | let a = dot(r.direction, r.direction); 18 | let b = dot(oc, r.direction); 19 | let c = dot(oc, oc) - (self.radius * self.radius); 20 | let discr = (b * b) - (a * c); 21 | if discr > 0. { 22 | let temp = (-b - (b * b - a * c).sqrt()) / a; 23 | if t_range.start < temp && temp < t_range.end { 24 | let t = temp; 25 | let p = r.point_at(t); 26 | let normal = (p - self.center) / self.radius; 27 | return Some(HitRecord { 28 | t, 29 | p, 30 | normal, 31 | material: &self.material, 32 | }); 33 | } 34 | let temp = (-b + (b * b - a * c).sqrt()) / a; 35 | if t_range.start < temp && temp < t_range.end { 36 | let t = temp; 37 | let p = r.point_at(t); 38 | let normal = (p - self.center) / self.radius; 39 | return Some(HitRecord { 40 | t, 41 | p, 42 | normal, 43 | material: &self.material, 44 | }); 45 | } 46 | } 47 | None 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | fmt: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | - run: rustup component add rustfmt 18 | - run: cargo fmt --all -- --check 19 | 20 | clippy: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | - run: rustup component add clippy 28 | - run: RUSTFLAGS="-D warnings" cargo clippy 29 | 30 | test: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: stable 37 | - run: cargo test 38 | 39 | build-linux: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: stable 46 | target: x86_64-unknown-linux-musl 47 | - run: cargo build --release --target=x86_64-unknown-linux-musl 48 | - run: strip target/x86_64-unknown-linux-musl/release/ray-tracing 49 | - run: ls -lh target/x86_64-unknown-linux-musl/release/ray-tracing 50 | 51 | build-windows: 52 | runs-on: windows-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | - uses: actions-rs/toolchain@v1 56 | with: 57 | toolchain: stable 58 | - run: cargo build --release 59 | env: 60 | RUSTFLAGS: -Ctarget-feature=+crt-static 61 | - run: dir target/release/ray-tracing.exe 62 | -------------------------------------------------------------------------------- /src/cam.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use rand::Rng; 4 | 5 | use crate::ray::Ray; 6 | use crate::rnd::random_in_unit_disk; 7 | use crate::vec::{cross, Vec3}; 8 | 9 | pub struct Camera { 10 | origin: Vec3, 11 | lower_left_corner: Vec3, 12 | horizontal: Vec3, 13 | vertical: Vec3, 14 | u: Vec3, 15 | v: Vec3, 16 | lens_radius: f32, 17 | } 18 | 19 | impl Camera { 20 | pub fn new( 21 | origin: Vec3, 22 | look_at: Vec3, 23 | vup: Vec3, 24 | vfov: f32, /* deg */ 25 | aspect: f32, 26 | aperture: f32, 27 | focus_dist: f32, 28 | ) -> Self { 29 | let theta = vfov * PI / 180.; 30 | let half_height = (theta / 2.).tan(); 31 | let half_width = aspect * half_height; 32 | let w = (origin - look_at).unit_vector(); 33 | let u = cross(vup, w).unit_vector(); 34 | let v = cross(w, u); 35 | Self { 36 | origin, 37 | lower_left_corner: origin 38 | - half_width * focus_dist * u 39 | - half_height * focus_dist * v 40 | - focus_dist * w, 41 | horizontal: 2. * half_width * focus_dist * u, 42 | vertical: 2. * half_height * focus_dist * v, 43 | u, 44 | v, 45 | lens_radius: aperture / 2., 46 | } 47 | } 48 | 49 | pub fn get_ray(&self, rng: &mut impl Rng, s: f32, t: f32) -> Ray { 50 | let rd = self.lens_radius * random_in_unit_disk(rng); 51 | let offset = rd.x() * self.u + rd.y() * self.v; 52 | Ray { 53 | origin: self.origin + offset, 54 | direction: self.lower_left_corner + (s * self.horizontal) + (t * self.vertical) 55 | - self.origin 56 | - offset, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cfg-if" 7 | version = "1.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 10 | 11 | [[package]] 12 | name = "getrandom" 13 | version = "0.2.6" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 16 | dependencies = [ 17 | "cfg-if", 18 | "libc", 19 | "wasi", 20 | ] 21 | 22 | [[package]] 23 | name = "libc" 24 | version = "0.2.125" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" 27 | 28 | [[package]] 29 | name = "ppv-lite86" 30 | version = "0.2.16" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 33 | 34 | [[package]] 35 | name = "rand" 36 | version = "0.8.5" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 39 | dependencies = [ 40 | "libc", 41 | "rand_chacha", 42 | "rand_core", 43 | ] 44 | 45 | [[package]] 46 | name = "rand_chacha" 47 | version = "0.3.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 50 | dependencies = [ 51 | "ppv-lite86", 52 | "rand_core", 53 | ] 54 | 55 | [[package]] 56 | name = "rand_core" 57 | version = "0.6.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 60 | dependencies = [ 61 | "getrandom", 62 | ] 63 | 64 | [[package]] 65 | name = "ray-tracing" 66 | version = "0.1.0" 67 | dependencies = [ 68 | "rand", 69 | ] 70 | 71 | [[package]] 72 | name = "wasi" 73 | version = "0.10.2+wasi-snapshot-preview1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 76 | -------------------------------------------------------------------------------- /src/vec.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct Vec3(pub f32, pub f32, pub f32); 5 | 6 | impl Neg for Vec3 { 7 | type Output = Self; 8 | fn neg(self) -> Self { 9 | Vec3(-self.0, -self.1, -self.2) 10 | } 11 | } 12 | 13 | impl Add for Vec3 { 14 | type Output = Self; 15 | fn add(self, rhs: Self) -> Self { 16 | Vec3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) 17 | } 18 | } 19 | 20 | impl AddAssign for Vec3 { 21 | fn add_assign(&mut self, rhs: Self) { 22 | *self = *self + rhs; 23 | } 24 | } 25 | 26 | impl Sub for Vec3 { 27 | type Output = Self; 28 | fn sub(self, rhs: Self) -> Self { 29 | Vec3(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2) 30 | } 31 | } 32 | 33 | impl SubAssign for Vec3 { 34 | fn sub_assign(&mut self, rhs: Self) { 35 | *self = *self - rhs; 36 | } 37 | } 38 | 39 | impl Mul for Vec3 { 40 | type Output = Self; 41 | fn mul(self, rhs: Self) -> Self { 42 | Vec3(self.0 * rhs.0, self.1 * rhs.1, self.2 * rhs.2) 43 | } 44 | } 45 | 46 | impl MulAssign for Vec3 { 47 | fn mul_assign(&mut self, rhs: Self) { 48 | *self = *self * rhs; 49 | } 50 | } 51 | 52 | impl Div for Vec3 { 53 | type Output = Self; 54 | fn div(self, rhs: Self) -> Self { 55 | Vec3(self.0 / rhs.0, self.1 / rhs.1, self.2 / rhs.2) 56 | } 57 | } 58 | 59 | impl DivAssign for Vec3 { 60 | fn div_assign(&mut self, rhs: Self) { 61 | *self = *self / rhs 62 | } 63 | } 64 | 65 | impl Mul for Vec3 { 66 | type Output = Vec3; 67 | fn mul(self, rhs: f32) -> Vec3 { 68 | Vec3(self.0 * rhs, self.1 * rhs, self.2 * rhs) 69 | } 70 | } 71 | 72 | impl Mul for f32 { 73 | type Output = Vec3; 74 | fn mul(self, rhs: Vec3) -> Vec3 { 75 | rhs * self 76 | } 77 | } 78 | 79 | impl MulAssign for Vec3 { 80 | fn mul_assign(&mut self, rhs: f32) { 81 | *self = *self * rhs; 82 | } 83 | } 84 | 85 | impl Div for Vec3 { 86 | type Output = Self; 87 | fn div(self, rhs: f32) -> Self { 88 | let k = 1. / rhs; 89 | Vec3(self.0 * k, self.1 * k, self.2 * k) 90 | } 91 | } 92 | 93 | impl DivAssign for Vec3 { 94 | fn div_assign(&mut self, rhs: f32) { 95 | *self = *self / rhs; 96 | } 97 | } 98 | 99 | impl Vec3 { 100 | pub fn x(&self) -> f32 { 101 | self.0 102 | } 103 | 104 | pub fn y(&self) -> f32 { 105 | self.1 106 | } 107 | 108 | pub fn z(&self) -> f32 { 109 | self.2 110 | } 111 | 112 | pub fn length(&self) -> f32 { 113 | self.squared_length().sqrt() 114 | } 115 | 116 | pub fn squared_length(&self) -> f32 { 117 | self.0 * self.0 + self.1 * self.1 + self.2 * self.2 118 | } 119 | 120 | pub fn unit_vector(&self) -> Self { 121 | let k = 1. / self.length(); 122 | Vec3(self.0 * k, self.1 * k, self.2 * k) 123 | } 124 | } 125 | 126 | pub fn dot(a: Vec3, b: Vec3) -> f32 { 127 | a.0 * b.0 + a.1 * b.1 + a.2 * b.2 128 | } 129 | 130 | pub fn cross(a: Vec3, b: Vec3) -> Vec3 { 131 | Vec3( 132 | a.1 * b.2 - a.2 * b.1, 133 | -(a.0 * b.2 - a.2 * b.0), 134 | a.0 * b.1 - a.1 * b.0, 135 | ) 136 | } 137 | -------------------------------------------------------------------------------- /src/mat.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::Standard; 2 | use rand::Rng; 3 | 4 | use crate::hit::HitRecord; 5 | use crate::ray::Ray; 6 | use crate::rnd::random_in_unit_sphere; 7 | use crate::vec::{dot, Vec3}; 8 | 9 | pub enum Material { 10 | Lambertian { 11 | albedo: Vec3, 12 | }, 13 | Metal { 14 | albedo: Vec3, 15 | fuzz: f32, /* 0..1 */ 16 | }, 17 | Dielectric { 18 | ref_idx: f32, 19 | }, 20 | } 21 | 22 | pub struct Scatter { 23 | pub attenuation: Vec3, 24 | pub scattered: Ray, 25 | } 26 | 27 | impl Material { 28 | pub fn scatter(&self, rng: &mut impl Rng, r_in: &Ray, rec: &HitRecord) -> Option { 29 | match self { 30 | Material::Lambertian { albedo } => { 31 | let target = rec.p + rec.normal + random_in_unit_sphere(rng); 32 | let scattered = Ray { 33 | origin: rec.p, 34 | direction: target - rec.p, 35 | }; 36 | Some(Scatter { 37 | scattered, 38 | attenuation: *albedo, 39 | }) 40 | } 41 | Material::Metal { albedo, fuzz } => { 42 | let reflected = reflect(r_in.direction.unit_vector(), rec.normal); 43 | let scattered = Ray { 44 | origin: rec.p, 45 | direction: reflected + *fuzz * random_in_unit_sphere(rng), 46 | }; 47 | if dot(scattered.direction, rec.normal) > 0. { 48 | Some(Scatter { 49 | scattered, 50 | attenuation: *albedo, 51 | }) 52 | } else { 53 | None 54 | } 55 | } 56 | Material::Dielectric { ref_idx } => { 57 | let reflected = reflect(r_in.direction, rec.normal); 58 | let outward_normal; 59 | let ni_over_nt; 60 | let cosine; 61 | if dot(r_in.direction, rec.normal) > 0. { 62 | outward_normal = -rec.normal; 63 | ni_over_nt = *ref_idx; 64 | cosine = ref_idx * dot(r_in.direction, rec.normal) / r_in.direction.length(); 65 | } else { 66 | outward_normal = rec.normal; 67 | ni_over_nt = 1. / ref_idx; 68 | cosine = -dot(r_in.direction, rec.normal) / r_in.direction.length(); 69 | }; 70 | let direction = match refract(r_in.direction, outward_normal, ni_over_nt) { 71 | Some(refracted) => { 72 | let reflect_prob = schlick(cosine, *ref_idx); 73 | if rng.sample::(Standard) < reflect_prob { 74 | reflected 75 | } else { 76 | refracted 77 | } 78 | } 79 | None => reflected, 80 | }; 81 | let scattered = Ray { 82 | origin: rec.p, 83 | direction, 84 | }; 85 | Some(Scatter { 86 | scattered, 87 | attenuation: Vec3(1., 1., 1.), 88 | }) 89 | } 90 | } 91 | } 92 | } 93 | 94 | fn reflect(v: Vec3, n: Vec3) -> Vec3 { 95 | v - 2. * dot(v, n) * n 96 | } 97 | 98 | fn refract(v: Vec3, n: Vec3, ni_over_nt: f32) -> Option { 99 | let uv = v.unit_vector(); 100 | let dt = dot(uv, n); 101 | let discriminant = 1. - ni_over_nt * ni_over_nt * (1. - dt * dt); 102 | if discriminant > 0. { 103 | Some(ni_over_nt * (uv - dt * n) - discriminant.sqrt() * n) 104 | } else { 105 | None 106 | } 107 | } 108 | 109 | fn schlick(cosine: f32, ref_idx: f32) -> f32 { 110 | let r0 = ((1. - ref_idx) / (1. + ref_idx)).powi(2); 111 | r0 + (1. - r0) * (1. - cosine).powf(5.) 112 | } 113 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::many_single_char_names)] 2 | 3 | use std::f32; 4 | use std::io::{self, stdout}; 5 | use std::time::Instant; 6 | 7 | use rand::distributions::Standard; 8 | use rand::rngs::SmallRng; 9 | use rand::{Rng, SeedableRng}; 10 | 11 | use crate::cam::Camera; 12 | use crate::hit::Hittable; 13 | use crate::mat::{Material, Scatter}; 14 | use crate::ray::Ray; 15 | use crate::sph::Sphere; 16 | use crate::vec::Vec3; 17 | 18 | mod cam; 19 | mod hit; 20 | mod mat; 21 | mod ppm; 22 | mod ray; 23 | mod rnd; 24 | mod sph; 25 | mod vec; 26 | 27 | fn color(rng: &mut impl Rng, r: &Ray, world: &impl Hittable, depth: u32) -> Vec3 { 28 | match world.hit(r, 0.001..f32::MAX) { 29 | Some(rec) => match rec.material.scatter(rng, r, &rec) { 30 | Some(Scatter { 31 | attenuation, 32 | ref scattered, 33 | }) if depth < 50 => attenuation * color(rng, scattered, world, depth + 1), 34 | _ => Vec3(0., 0., 0.), 35 | }, 36 | None => { 37 | let unit_direction = r.direction.unit_vector(); 38 | let t = 0.5 * (unit_direction.y() + 1.); 39 | (1. - t) * Vec3(1., 1., 1.) + t * Vec3(0.5, 0.7, 1.) 40 | } 41 | } 42 | } 43 | 44 | fn main() -> Result<(), io::Error> { 45 | let start = Instant::now(); 46 | 47 | const SEED: [u8; 32] = { 48 | let mut arr = [0; 32]; 49 | let mut i = 0; 50 | while i < arr.len() { 51 | arr[i] = i as _; 52 | i += 1; 53 | } 54 | arr 55 | }; 56 | 57 | let mut rng = SmallRng::from_seed(SEED); 58 | 59 | let nx = 400; 60 | let ny = 200; 61 | let ns = 100; 62 | 63 | let mut ppm = ppm::Writer::new(stdout(), nx, ny)?; 64 | 65 | let world = random_scene(&mut rng); 66 | 67 | let origin = Vec3(5., 1.5, 3.); 68 | let look_at = Vec3(0., -1., 0.); 69 | let dist_to_focus = (origin - look_at).length(); 70 | let aperture = 0.1; 71 | let cam = Camera::new( 72 | origin, 73 | look_at, 74 | Vec3(0., 1., 0.), 75 | 70., 76 | nx as f32 / ny as f32, 77 | aperture, 78 | dist_to_focus, 79 | ); 80 | 81 | for j in (0..ny).rev() { 82 | for i in 0..nx { 83 | let mut col = Vec3(0., 0., 0.); 84 | for _ in 0..ns { 85 | let s = (i as f32 + rng.sample::(Standard)) / nx as f32; 86 | let t = (j as f32 + rng.sample::(Standard)) / ny as f32; 87 | let r = cam.get_ray(&mut rng, s, t); 88 | col += color(&mut rng, &r, &world, 0); 89 | } 90 | let col = col / ns as f32; 91 | 92 | let col = Vec3(col.0.sqrt(), col.1.sqrt(), col.2.sqrt()); 93 | let ir = (255.99 * col.0) as u8; 94 | let ig = (255.99 * col.1) as u8; 95 | let ib = (255.99 * col.2) as u8; 96 | 97 | ppm.write_pixel(ir, ig, ib)?; 98 | } 99 | } 100 | 101 | let elapsed = start.elapsed(); 102 | eprintln!("{}.{:0>3}s", elapsed.as_secs(), elapsed.subsec_millis()); 103 | 104 | Ok(()) 105 | } 106 | 107 | fn random_scene(rng: &mut impl Rng) -> impl Hittable { 108 | let mut spheres = vec![ 109 | Sphere { 110 | center: Vec3(0., -1000., 0.), 111 | radius: 1000., 112 | material: Material::Lambertian { 113 | albedo: Vec3(0.5, 0.5, 0.5), 114 | }, 115 | }, 116 | Sphere { 117 | center: Vec3(0., 1., 0.), 118 | radius: 1., 119 | material: Material::Dielectric { ref_idx: 1.5 }, 120 | }, 121 | Sphere { 122 | center: Vec3(-4., 1., 0.), 123 | radius: 1., 124 | material: Material::Lambertian { 125 | albedo: Vec3(0.4, 0.2, 0.1), 126 | }, 127 | }, 128 | Sphere { 129 | center: Vec3(4., 1., 0.), 130 | radius: 1., 131 | material: Material::Metal { 132 | albedo: Vec3(0.7, 0.6, 0.5), 133 | fuzz: 0., 134 | }, 135 | }, 136 | ]; 137 | for a in -11..11 { 138 | for b in -11..11 { 139 | let choose_mat = rng.sample::(Standard); 140 | let center = Vec3( 141 | a as f32 + 0.9 * rng.sample::(Standard), 142 | 0.2, 143 | b as f32 + 0.9 * rng.sample::(Standard), 144 | ); 145 | if (center - Vec3(4., 0.2, 0.)).length() > 0.9 { 146 | let material = if choose_mat < 0.8 { 147 | Material::Lambertian { 148 | albedo: Vec3( 149 | rng.sample::(Standard) * rng.sample::(Standard), 150 | rng.sample::(Standard) * rng.sample::(Standard), 151 | rng.sample::(Standard) * rng.sample::(Standard), 152 | ), 153 | } 154 | } else if choose_mat < 0.95 { 155 | Material::Metal { 156 | albedo: Vec3( 157 | 0.5 * (1. + rng.sample::(Standard)), 158 | 0.5 * (1. + rng.sample::(Standard)), 159 | 0.5 * (1. + rng.sample::(Standard)), 160 | ), 161 | fuzz: 0.5 * rng.sample::(Standard), 162 | } 163 | } else { 164 | Material::Dielectric { ref_idx: 1.5 } 165 | }; 166 | spheres.push(Sphere { 167 | center, 168 | radius: 0.2, 169 | material, 170 | }); 171 | } 172 | } 173 | } 174 | spheres 175 | } 176 | --------------------------------------------------------------------------------