├── .gitignore ├── Cargo.toml ├── README.md ├── screenshots └── yjsnpi.png └── src ├── camera.rs ├── defocus_camera.rs ├── dielectric.rs ├── hitable.rs ├── hitable_list.rs ├── lambertian.rs ├── main.rs ├── material.rs ├── metal.rs ├── playground_scene.rs ├── random_scene.rs ├── ray.rs ├── scene.rs ├── sphere.rs ├── standard_camera.rs ├── surface.rs ├── tracer.rs ├── utils.rs ├── vec3.rs ├── window.rs └── yjsnpi_scene.rs /.gitignore: -------------------------------------------------------------------------------- 1 | ## IDEA 2 | .idea/ 3 | 4 | ## macOS 5 | .DS_Store 6 | 7 | ## Rust 8 | target/ 9 | Cargo.lock 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReitoreSenpai" 3 | version = "0.1.0" 4 | authors = ["xqq "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | png = "0.14.0" 9 | rand = "0.6.5" 10 | sdl2 = "0.32.2" 11 | rand_xorshift = "0.1.1" 12 | scoped_threadpool = "0.1.9" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | レイトレ先輩 2 | ====== 3 | ReitoreSenpai 4 | 5 | Rustで光線追跡によりヤジュウ先輩を描く。 6 | 7 | ## Build 8 | ```bash 9 | cargo build --release 10 | ``` 11 | 12 | ## Run 13 | ```bash 14 | ./target/release/ReitoreSenpai 15 | ``` 16 | 17 | ## Senpai 18 | ```bash 19 | out.png 20 | ``` 21 | ![yjsnpi.png](screenshots/yjsnpi.png) 22 | 23 | ## Reference 24 | - Ray Tracing in One Weekend 25 | -------------------------------------------------------------------------------- /screenshots/yjsnpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xqq/ReitoreSenpai/2dd350433ac4c3c152fe22962d1c4d76c6407f9a/screenshots/yjsnpi.png -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | use crate::ray::Ray; 2 | 3 | pub trait Camera { 4 | 5 | fn get_ray(&self, s: f32, t: f32) -> Ray; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/defocus_camera.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use crate::vec3::Vec3; 3 | use crate::ray::Ray; 4 | use crate::utils::*; 5 | use crate::camera::Camera; 6 | 7 | pub struct DefocusCamera { 8 | origin: Vec3, 9 | lower_left_corner: Vec3, 10 | horizontal: Vec3, 11 | vertical: Vec3, 12 | u: Vec3, 13 | v: Vec3, 14 | lens_radius: f32 15 | } 16 | 17 | impl DefocusCamera { 18 | 19 | pub fn new(look_from: Vec3, look_at: Vec3, vup: Vec3, vfov: f32, aspect: f32, aperture: f32, focus_dist: f32) -> DefocusCamera { 20 | let theta = vfov * f32::consts::PI / 180.0; 21 | let half_height = (theta / 2.0).tan(); 22 | let half_width = aspect * half_height; 23 | 24 | let w = (look_from - look_at).to_unit_vector(); 25 | let u = Vec3::cross(&vup, &w).to_unit_vector(); 26 | let v = Vec3::cross(&w, &u).to_unit_vector(); 27 | 28 | DefocusCamera { 29 | origin: look_from, 30 | lower_left_corner: look_from - half_width * focus_dist * u - half_height * focus_dist * v - focus_dist * w, 31 | horizontal: 2.0 * half_width * focus_dist * u, 32 | vertical: 2.0 * half_height * focus_dist * v, 33 | u, 34 | v, 35 | lens_radius: aperture / 2.0 36 | } 37 | } 38 | 39 | } 40 | 41 | impl Camera for DefocusCamera { 42 | 43 | fn get_ray(&self, s: f32, t: f32) -> Ray { 44 | let rd = self.lens_radius * random_in_unit_disk(); 45 | let offset = self.u * rd.x() + self.v * rd.y(); 46 | 47 | Ray::from_a_b( 48 | self.origin + offset, 49 | self.lower_left_corner + s * self.horizontal + t * self.vertical - self.origin - offset 50 | ) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/dielectric.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | use crate::vec3::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hitable::HitRecord; 5 | use crate::material::Material; 6 | use crate::material::ScatterResult; 7 | use crate::utils::*; 8 | 9 | pub struct Dielectric { 10 | ref_idx: f32 11 | } 12 | 13 | impl Dielectric { 14 | 15 | pub fn new(ref_idx: f32) -> Box { 16 | Box::new(Dielectric { 17 | ref_idx 18 | }) 19 | } 20 | 21 | } 22 | 23 | impl Material for Dielectric { 24 | 25 | fn scatter(&self, r_in: &Ray, record: &HitRecord) -> (bool, Option) { 26 | let mut refracted = Vec3::default(); 27 | let outward_normal: Vec3; 28 | let ni_over_nt; 29 | let reflect_prob: f32; 30 | let cosine: f32; 31 | 32 | if Vec3::dot(&r_in.direction(), &record.normal) > 0.0 { 33 | outward_normal = -record.normal; 34 | ni_over_nt = self.ref_idx; 35 | cosine = self.ref_idx * Vec3::dot(&r_in.direction(), &record.normal) / r_in.direction().length(); 36 | } else { 37 | outward_normal = record.normal; 38 | ni_over_nt = 1.0 / self.ref_idx; 39 | cosine = -Vec3::dot(&r_in.direction(), &record.normal) / r_in.direction().length(); 40 | } 41 | 42 | if let Some(refract) = refract(&r_in.direction(), &outward_normal, ni_over_nt) { 43 | refracted = refract; 44 | reflect_prob = schlick(cosine, self.ref_idx); 45 | } else { 46 | reflect_prob = 1.0; 47 | } 48 | 49 | let rng = fast_thread_rng(); 50 | 51 | if rng.gen::() < reflect_prob { 52 | let reflected = reflect(&r_in.direction(), &record.normal); 53 | (true, Some(ScatterResult { 54 | attenuation: Vec3(1.0, 1.0, 1.0), 55 | scattered: Ray::from_a_b(record.hit_point, reflected) 56 | })) 57 | } else { 58 | (true, Some(ScatterResult { 59 | attenuation: Vec3(1.0, 1.0, 1.0), 60 | scattered: Ray::from_a_b(record.hit_point, refracted) 61 | })) 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/hitable.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::ray::Ray; 3 | use crate::material::Material; 4 | 5 | pub struct HitRecord<'a> { 6 | pub t: f32, 7 | pub hit_point: Vec3, 8 | pub normal: Vec3, 9 | pub material: &'a dyn Material 10 | } 11 | 12 | pub trait Hitable : Send + Sync { 13 | 14 | fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> (bool, Option); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/hitable_list.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::ops::DerefMut; 3 | use crate::ray::Ray; 4 | use crate::hitable::*; 5 | 6 | #[derive(Default)] 7 | pub struct HitableList { 8 | list: Vec> 9 | } 10 | 11 | impl Hitable for HitableList { 12 | 13 | fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> (bool, Option) { 14 | let hit_record = std::mem::MaybeUninit::::uninit(); 15 | let mut hit_record = unsafe { hit_record.assume_init() }; 16 | 17 | let mut hit_anything = false; 18 | let mut closest_so_far = t_max; 19 | 20 | for object in &self.list { 21 | if let (true, Some(record)) = object.hit(ray, t_min, closest_so_far) { 22 | hit_anything = true; 23 | closest_so_far = record.t; 24 | hit_record = record; 25 | } 26 | } 27 | 28 | if hit_anything { 29 | (true, Some(hit_record)) 30 | } else { 31 | (false, None) 32 | } 33 | } 34 | 35 | } 36 | 37 | impl Deref for HitableList { 38 | type Target = Vec>; 39 | 40 | #[inline(always)] 41 | fn deref(&self) -> &Vec> { 42 | &self.list 43 | } 44 | } 45 | 46 | impl DerefMut for HitableList { 47 | 48 | #[inline(always)] 49 | fn deref_mut(&mut self) -> &mut Vec> { 50 | &mut self.list 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/lambertian.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::ray::Ray; 3 | use crate::hitable::HitRecord; 4 | use crate::material::*; 5 | use crate::utils::*; 6 | 7 | // diffuse 8 | pub struct Lambertian { 9 | albedo: Vec3 10 | } 11 | 12 | impl Lambertian { 13 | 14 | pub fn new(albedo: Vec3) -> Box { 15 | Box::new(Lambertian { 16 | albedo 17 | }) 18 | } 19 | 20 | } 21 | 22 | impl Material for Lambertian { 23 | 24 | fn scatter(&self, _r_in: &Ray, record: &HitRecord) -> (bool, Option) { 25 | let target = record.hit_point + record.normal + random_in_unit_sphere(); 26 | 27 | (true, Some(ScatterResult { 28 | attenuation: self.albedo, 29 | scattered: Ray::from_a_b(record.hit_point, target - record.hit_point) 30 | })) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | #![allow(unused)] 3 | #![feature(maybe_uninit)] 4 | #![feature(duration_float)] 5 | #![feature(range_contains)] 6 | #![feature(nll)] 7 | 8 | extern crate rand; 9 | extern crate sdl2; 10 | extern crate rand_xorshift; 11 | extern crate scoped_threadpool; 12 | 13 | mod window; 14 | mod vec3; 15 | mod ray; 16 | mod hitable; 17 | mod sphere; 18 | mod surface; 19 | mod tracer; 20 | mod hitable_list; 21 | mod camera; 22 | mod standard_camera; 23 | mod defocus_camera; 24 | mod material; 25 | mod metal; 26 | mod lambertian; 27 | mod dielectric; 28 | mod utils; 29 | mod scene; 30 | mod random_scene; 31 | mod playground_scene; 32 | mod yjsnpi_scene; 33 | 34 | fn main() { 35 | window::window_main_loop() 36 | } 37 | -------------------------------------------------------------------------------- /src/material.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::ray::Ray; 3 | use crate::hitable::HitRecord; 4 | 5 | pub struct ScatterResult { 6 | pub attenuation: Vec3, 7 | pub scattered: Ray 8 | } 9 | 10 | pub trait Material : Send + Sync { 11 | 12 | fn scatter(&self, r_in: &Ray, record: &HitRecord) -> (bool, Option); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/metal.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::ray::Ray; 3 | use crate::hitable::HitRecord; 4 | use crate::material::Material; 5 | use crate::material::ScatterResult; 6 | use crate::utils::*; 7 | 8 | pub struct Metal { 9 | albedo: Vec3, 10 | fuzz: f32 11 | } 12 | 13 | impl Metal { 14 | 15 | pub fn new(albedo: Vec3, f: f32) -> Box { 16 | let fuzz = if f < 1.0 { 17 | f 18 | } else { 19 | 1.0 20 | }; 21 | 22 | Box::new(Metal { 23 | albedo, 24 | fuzz 25 | }) 26 | } 27 | 28 | } 29 | 30 | impl Material for Metal { 31 | 32 | fn scatter(&self, r_in: &Ray, record: &HitRecord) -> (bool, Option) { 33 | let reflected = reflect(&r_in.direction().to_unit_vector(), &record.normal); 34 | let scattered = Ray::from_a_b(record.hit_point, reflected + self.fuzz * random_in_unit_sphere()); 35 | let attenuation = self.albedo; 36 | 37 | let has_reflect = Vec3::dot(&scattered.direction(), &record.normal) > 0.0; 38 | 39 | (has_reflect, Some(ScatterResult { attenuation, scattered })) 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/playground_scene.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::hitable_list::HitableList; 3 | use crate::sphere::Sphere; 4 | use crate::lambertian::Lambertian; 5 | use crate::dielectric::Dielectric; 6 | use crate::metal::Metal; 7 | use crate::scene::Scene; 8 | 9 | pub struct PlaygroundScene {} 10 | 11 | impl Scene for PlaygroundScene { 12 | 13 | fn generate(&self) -> HitableList { 14 | let mut world = HitableList::default(); 15 | 16 | world.push(Sphere::new( 17 | Vec3(0.0, 0.0, -1.0), 18 | 0.5, 19 | Lambertian::new(Vec3(0.1, 0.2, 0.5)) 20 | )); 21 | world.push(Sphere::new( 22 | Vec3(0.0, -100.5, -1.0), 23 | 100.0, 24 | Lambertian::new(Vec3(0.8, 0.8, 0.0)) 25 | )); 26 | world.push(Sphere::new( 27 | Vec3(1.0, 0.0, -1.0), 28 | 0.5, 29 | Metal::new(Vec3(0.8, 0.6, 0.2), 1.0) 30 | )); 31 | world.push(Sphere::new( 32 | Vec3(-1.0, 0.0, -1.0), 33 | 0.5, 34 | Dielectric::new(1.5) 35 | )); 36 | world.push(Sphere::new( 37 | Vec3(-1.0, 0.0, -1.0), 38 | -0.45, 39 | Dielectric::new(1.5) 40 | )); 41 | 42 | world 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/random_scene.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::hitable_list::HitableList; 3 | use rand::Rng; 4 | use crate::sphere::Sphere; 5 | use crate::lambertian::Lambertian; 6 | use crate::dielectric::Dielectric; 7 | use crate::metal::Metal; 8 | use crate::scene::Scene; 9 | use crate::utils::*; 10 | 11 | pub struct RandomScene { 12 | 13 | } 14 | 15 | impl Scene for RandomScene { 16 | 17 | fn generate(&self) -> HitableList { 18 | let n: usize = 500; 19 | let mut list = HitableList::default(); 20 | list.reserve(n + 1); 21 | list.push(Sphere::new( 22 | Vec3(0.0, -1000.0, 0.0), 23 | 1000.0, 24 | Lambertian::new( 25 | Vec3(0.5, 0.5, 0.5) 26 | ) 27 | )); 28 | 29 | let rng = fast_thread_rng(); 30 | 31 | for a in -11..11 { 32 | for b in -11..11 { 33 | let choose_mat: f32 = rng.gen(); 34 | let center = Vec3( 35 | a as f32 + 0.9 * rng.gen::(), 36 | 0.2, 37 | b as f32 + 0.9 * rng.gen::() 38 | ); 39 | 40 | if (center - Vec3(4.0, 0.2, 0.0)).length() > 0.9 { 41 | if choose_mat < 0.8 { // diffuse 42 | list.push(Sphere::new( 43 | center, 44 | 0.2, 45 | Lambertian::new( 46 | Vec3( 47 | rng.gen::() * rng.gen::(), 48 | rng.gen::() * rng.gen::(), 49 | rng.gen::() * rng.gen::() 50 | ) 51 | ) 52 | )); 53 | } else if choose_mat < 0.95 { // metal 54 | list.push(Sphere::new( 55 | center, 56 | 0.2, 57 | Metal::new( 58 | Vec3( 59 | 0.5 * (1.0 + rng.gen::()), 60 | 0.5 * (1.0 + rng.gen::()), 61 | 0.5 * (1.0 + rng.gen::()) 62 | ), 63 | 0.5 * rng.gen::() 64 | ) 65 | )); 66 | } else { // glass 67 | list.push(Sphere::new( 68 | center, 69 | 0.2, 70 | Dielectric::new( 71 | 1.5 72 | ) 73 | )); 74 | } 75 | } 76 | } 77 | } 78 | 79 | list.push(Sphere::new( 80 | Vec3(0.0, 1.0, 0.0), 81 | 1.0, 82 | Dielectric::new( 83 | 1.5 84 | ) 85 | )); 86 | list.push(Sphere::new( 87 | Vec3(-4.0, 1.0, 0.0), 88 | 1.0, 89 | Lambertian::new( 90 | Vec3(0.4, 0.2, 0.1) 91 | ) 92 | )); 93 | list.push(Sphere::new( 94 | Vec3(4.0, 1.0, 0.0), 95 | 1.0, 96 | Metal::new( 97 | Vec3(0.7, 0.6, 0.5), 98 | 0.0 99 | ) 100 | )); 101 | 102 | list 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/ray.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use crate::vec3::Vec3; 4 | 5 | #[derive(Copy, Clone, Default, Debug, PartialEq)] 6 | pub struct Ray { 7 | a: Vec3, 8 | b: Vec3 9 | } 10 | 11 | impl Ray { 12 | 13 | pub fn from_a_b(a: Vec3, b: Vec3) -> Ray { 14 | Ray { 15 | a, 16 | b 17 | } 18 | } 19 | 20 | #[inline] 21 | pub fn origin(&self) -> Vec3 { 22 | self.a 23 | } 24 | 25 | #[inline] 26 | pub fn direction(&self) -> Vec3 { 27 | self.b 28 | } 29 | 30 | #[inline] 31 | pub fn point_at_parameter(&self, t: f32) -> Vec3 { 32 | self.a + t * self.b 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/scene.rs: -------------------------------------------------------------------------------- 1 | use crate::hitable_list::HitableList; 2 | 3 | pub trait Scene { 4 | 5 | fn generate(&self) -> HitableList; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/sphere.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::ray::Ray; 3 | use crate::hitable::*; 4 | use crate::material::Material; 5 | 6 | pub struct Sphere { 7 | center: Vec3, 8 | radius: f32, 9 | material: Box 10 | } 11 | 12 | impl Sphere { 13 | 14 | pub fn new(center: Vec3, radius: f32, material: Box) -> Box { 15 | Box::new(Sphere { 16 | center, 17 | radius, 18 | material 19 | }) 20 | } 21 | 22 | } 23 | 24 | impl Hitable for Sphere { 25 | 26 | fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> (bool, Option) { 27 | let oc = ray.origin() - self.center; 28 | 29 | let a = Vec3::dot(&ray.direction(), &ray.direction()); 30 | let b = Vec3::dot(&oc, &ray.direction()); 31 | let c = Vec3::dot(&oc, &oc) - self.radius * self.radius; 32 | 33 | let discriminant = b * b - a * c; 34 | 35 | if discriminant > 0.0 { 36 | let temp = (-b - discriminant.sqrt()) / a; 37 | if temp > t_min && temp < t_max { 38 | let hit_point = ray.point_at_parameter(temp); 39 | return (true, Some(HitRecord { 40 | t: temp, 41 | hit_point, 42 | normal: (hit_point - self.center) / self.radius, 43 | material: self.material.as_ref() 44 | })) 45 | } 46 | 47 | let temp = (-b + discriminant.sqrt()) / a; 48 | if temp > t_min && temp < t_max { 49 | let hit_point = ray.point_at_parameter(temp); 50 | return (true, Some(HitRecord { 51 | t: temp, 52 | hit_point, 53 | normal: (hit_point - self.center) / self.radius, 54 | material: self.material.as_ref() 55 | })) 56 | } 57 | } 58 | 59 | (false, None) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/standard_camera.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use crate::vec3::Vec3; 3 | use crate::ray::Ray; 4 | use crate::camera::Camera; 5 | 6 | pub struct StandardCamera { 7 | origin: Vec3, 8 | lower_left_corner: Vec3, 9 | horizontal: Vec3, 10 | vertical: Vec3, 11 | } 12 | 13 | impl StandardCamera { 14 | 15 | pub fn new(look_from: Vec3, look_at: Vec3, vup: Vec3, vfov: f32, aspect: f32) -> StandardCamera { 16 | let theta = vfov * f32::consts::PI / 180.0; 17 | let half_height = (theta / 2.0).tan(); 18 | let half_width = aspect * half_height; 19 | 20 | let w = (look_from - look_at).to_unit_vector(); 21 | let u = Vec3::cross(&vup, &w).to_unit_vector(); 22 | let v = Vec3::cross(&w, &u).to_unit_vector(); 23 | 24 | StandardCamera { 25 | origin: look_from, 26 | lower_left_corner: look_from - half_width * u - half_height * v - w, 27 | horizontal: 2.0 * half_width * u, 28 | vertical: 2.0 * half_height * v 29 | } 30 | } 31 | 32 | } 33 | 34 | impl Camera for StandardCamera { 35 | 36 | fn get_ray(&self, s: f32, t: f32) -> Ray { 37 | Ray::from_a_b( 38 | self.origin, 39 | self.lower_left_corner + s * self.horizontal + t * self.vertical - self.origin 40 | ) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/surface.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | use crate::vec3::Vec3; 3 | use crate::ray::Ray; 4 | use crate::hitable::*; 5 | use crate::material::Material; 6 | use crate::utils::nearly_equal; 7 | 8 | pub struct Surface { 9 | a: f32, 10 | b: f32, 11 | c: f32, 12 | d: f32, 13 | normal: Vec3, 14 | xrange: RangeInclusive, 15 | yrange: RangeInclusive, 16 | zrange: RangeInclusive, 17 | material: Box 18 | } 19 | 20 | impl Surface { 21 | 22 | // ax + by + c - d = 0 23 | pub fn new(a: f32, b: f32, c: f32, d: f32, 24 | xrange: RangeInclusive, yrange: RangeInclusive, zrange: RangeInclusive, 25 | material: Box) -> Box { 26 | Box::new(Surface { 27 | a, 28 | b, 29 | c, 30 | d, 31 | normal: Vec3(a, b, c).to_unit_vector(), 32 | xrange, 33 | yrange, 34 | zrange, 35 | material 36 | }) 37 | } 38 | 39 | } 40 | 41 | impl Hitable for Surface { 42 | 43 | fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> (bool, Option) { 44 | let normal_dot_direction = Vec3::dot(&self.normal, &ray.direction()); 45 | if nearly_equal(normal_dot_direction, 0.0) { 46 | return (false, None); 47 | } 48 | 49 | let normal_dot_origin = Vec3::dot(&self.normal, &ray.origin()); 50 | 51 | let t = -(self.d + normal_dot_origin) / (normal_dot_direction); 52 | if t <= t_min || t >= t_max { 53 | return (false, None); 54 | } 55 | 56 | let hit_point = ray.point_at_parameter(t); 57 | 58 | if self.xrange.contains(&hit_point.x()) && 59 | self.yrange.contains(&hit_point.y()) && 60 | self.zrange.contains(&hit_point.z()) { 61 | 62 | let normal = if normal_dot_direction < 0.0 { 63 | // cos(theta) < 0, 90 < theta < 180 64 | self.normal 65 | } else { 66 | // cos(theta) > 0, 0 < theta < 90 67 | -self.normal 68 | }; 69 | 70 | (true, Some(HitRecord { 71 | t, 72 | hit_point, 73 | normal, 74 | material: self.material.as_ref() 75 | })) 76 | } else { 77 | (false, None) 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/tracer.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | use scoped_threadpool::Pool; 3 | use std::time::{Duration, Instant}; 4 | use crate::vec3::Vec3; 5 | use crate::ray::Ray; 6 | use crate::hitable::*; 7 | use crate::scene::Scene; 8 | use crate::playground_scene::PlaygroundScene; 9 | use crate::random_scene::RandomScene; 10 | use crate::camera::Camera; 11 | use crate::defocus_camera::DefocusCamera; 12 | use crate::standard_camera::StandardCamera; 13 | use crate::utils::as_u32_slice_mut; 14 | use crate::yjsnpi_scene::YjsnpiScene; 15 | use crate::utils::*; 16 | 17 | fn color(r: &Ray, world: &T, depth: i32) -> Vec3 { 18 | if let (true, Some(record)) = world.hit(r, 0.001, std::f32::MAX) { 19 | if depth < 50 { 20 | if let (true, Some(scatter)) = record.material.scatter(r, &record) { 21 | scatter.attenuation * color(&scatter.scattered, world, depth + 1) 22 | } else { 23 | Vec3::default() 24 | } 25 | } else { 26 | Vec3::default() 27 | } 28 | } else { 29 | // let unit_direction = r.direction().to_unit_vector(); 30 | // let t = 0.5 * (unit_direction.y() + 1.0); 31 | // (1.0 - t) * Vec3(1.0, 1.0, 1.0) + t * Vec3(0.5, 0.7, 1.0) 32 | Vec3(1.0, 1.0, 1.0) 33 | } 34 | } 35 | 36 | pub fn trace(buffer: &mut [u8], pitch: usize, width: u32, height: u32) -> (u32, Duration) { 37 | let nx = width; 38 | let ny = height; 39 | let ns = 100; 40 | let threads: u32 = 8; 41 | 42 | let look_from = Vec3(13.0, 2.0, 3.0); 43 | let look_at = Vec3(0.0, 0.0, 0.0); 44 | let dist_to_focus: f32 = 10.0; 45 | let aperture: f32 = 0.1; 46 | 47 | // let camera = DefocusCamera::new( 48 | // look_from, 49 | // look_at, 50 | // Vec3(0.0, 1.0, 0.0), 51 | // 20.0, 52 | // width as f32 / height as f32, 53 | // aperture, 54 | // dist_to_focus 55 | // ); 56 | 57 | // BasicCamera 58 | let camera = StandardCamera::new( 59 | Vec3(12.0, 12.0, 12.0), 60 | Vec3(3.0, 2.0, 3.0), 61 | Vec3(0.0, 1.0, 0.0), 62 | 45.0, 63 | width as f32 / height as f32 64 | ); 65 | 66 | let scene = YjsnpiScene {}; 67 | let world = scene.generate(); 68 | 69 | let mut pool = Pool::new(threads); 70 | 71 | let now = Instant::now(); 72 | 73 | pool.scoped(|scope| { 74 | let camera = &camera; 75 | let world = &world; 76 | let mut chunks = buffer.chunks_mut(pitch); 77 | 78 | for j in (0..ny).rev() { 79 | let y = j; 80 | let pixel_line = as_u32_slice_mut(chunks.next().unwrap()); 81 | 82 | scope.execute(move || { 83 | let rng = fast_thread_rng(); 84 | 85 | for x in 0..nx { 86 | let mut col = Vec3(0.0, 0.0, 0.0); 87 | 88 | for _s in 0..ns { 89 | let u = (x as f32 + rng.gen::()) / nx as f32; 90 | let v = (y as f32 + rng.gen::()) / ny as f32; 91 | let r = camera.get_ray(u, v); 92 | col += color(&r, world, 0); 93 | } 94 | 95 | col /= ns as f32; 96 | col = Vec3(col.r().sqrt(), col.g().sqrt(), col.b().sqrt()); 97 | 98 | let ir = (255.99 * col.0) as u32; 99 | let ig = (255.99 * col.1) as u32; 100 | let ib = (255.99 * col.2) as u32; 101 | 102 | let value: u32 = ir | ig << 8 | ib << 16 | 255 << 24; 103 | pixel_line[x as usize] = value; 104 | } 105 | }); 106 | } 107 | }); 108 | 109 | (threads, now.elapsed()) 110 | } 111 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use rand::Rng; 4 | use rand::SeedableRng; 5 | use rand_xorshift::XorShiftRng; 6 | use png::HasParameters; 7 | use std::cell::UnsafeCell; 8 | use std::slice; 9 | use std::path::Path; 10 | use std::fs::File; 11 | use std::io::BufWriter; 12 | use crate::vec3::Vec3; 13 | 14 | thread_local!( 15 | static THREAD_XORSHIFT_RNG: UnsafeCell = { 16 | let mut rng = XorShiftRng::from_rng(rand::thread_rng()).unwrap(); 17 | 18 | UnsafeCell::new(rng) 19 | } 20 | ); 21 | 22 | pub fn fast_thread_rng() -> &'static mut XorShiftRng { 23 | let rng = THREAD_XORSHIFT_RNG.with(|t| { 24 | unsafe { 25 | &mut *t.get() 26 | } 27 | }); 28 | rng 29 | } 30 | 31 | pub fn random_in_unit_sphere() -> Vec3 { 32 | let rng = fast_thread_rng(); 33 | let mut p; 34 | 35 | loop { 36 | p = 2.0 * Vec3(rng.gen(), rng.gen(), rng.gen()); 37 | p -= Vec3(1.0, 1.0, 1.0); 38 | 39 | if p.squared_length() < 1.0 { 40 | break; 41 | } 42 | } 43 | 44 | p 45 | } 46 | 47 | pub fn random_in_unit_disk() -> Vec3 { 48 | let rng = fast_thread_rng(); 49 | let mut p; 50 | 51 | loop { 52 | p = 2.0 * Vec3(rng.gen(), rng.gen(), 0.0) - Vec3(1.0, 1.0, 0.0); 53 | 54 | if Vec3::dot(&p, &p) < 1.0 { 55 | break; 56 | } 57 | } 58 | 59 | p 60 | } 61 | 62 | pub fn reflect(v: &Vec3, normal: &Vec3) -> Vec3 { 63 | *v - 2.0 * Vec3::dot(v, normal) * normal 64 | } 65 | 66 | pub fn refract(v: &Vec3, normal: &Vec3, ni_over_nt: f32) -> Option { 67 | let uv = v.to_unit_vector(); 68 | let dt = Vec3::dot(&uv, normal); 69 | let discriminant = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt); 70 | 71 | if discriminant > 0.0 { 72 | Some(ni_over_nt * (uv - *normal * dt) - *normal * discriminant.sqrt()) 73 | } else { 74 | None 75 | } 76 | } 77 | 78 | pub fn schlick(cosine: f32, ref_idx: f32) -> f32 { 79 | let mut r0 = (1.0 - ref_idx) / (1.0 + ref_idx); 80 | r0 = r0 * r0; 81 | r0 + (1.0 - r0) * (1.0 - cosine).powi(5) 82 | } 83 | 84 | pub fn nearly_equal(a: f32, b: f32) -> bool { 85 | const EPSILON: f32 = 1e-6; 86 | (a - EPSILON <= b) && (b <= a + EPSILON) 87 | } 88 | 89 | pub fn split_chunk_from_mut(slice: &[T], from: usize, length: usize) -> &mut [T] { 90 | let ptr = slice.as_ptr() as *mut T; 91 | 92 | unsafe { 93 | assert!(from + length <= slice.len()); 94 | 95 | slice::from_raw_parts_mut(ptr.add(from), length) 96 | } 97 | } 98 | 99 | pub fn as_u32_slice_mut(slice: &mut [u8]) -> &mut [u32] { 100 | unsafe { 101 | std::slice::from_raw_parts_mut( 102 | slice.as_mut_ptr() as *mut u32, 103 | slice.len() / 4 104 | ) 105 | } 106 | } 107 | 108 | pub fn write_png_file(file_path: String, width: u32, height: u32, buffer: &[u8]) { 109 | let path = Path::new(&file_path); 110 | let file = File::create(path).unwrap(); 111 | let ref mut w = BufWriter::new(file); 112 | 113 | let mut encoder = png::Encoder::new(w, width, height); 114 | encoder 115 | .set(png::ColorType::RGBA) 116 | .set(png::BitDepth::Eight) 117 | .set(png::Compression::Best); 118 | let mut writer = encoder.write_header().unwrap(); 119 | 120 | writer.write_image_data(&buffer).unwrap(); 121 | } 122 | -------------------------------------------------------------------------------- /src/vec3.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::fmt; 4 | use std::ops::Add; 5 | use std::ops::Sub; 6 | use std::ops::Mul; 7 | use std::ops::Div; 8 | use std::ops::Neg; 9 | use std::ops::AddAssign; 10 | use std::ops::SubAssign; 11 | use std::ops::MulAssign; 12 | use std::ops::DivAssign; 13 | 14 | #[derive(Copy, Clone, Default, Debug, PartialEq)] 15 | pub struct Vec3(pub f32, pub f32, pub f32); 16 | 17 | impl Vec3 { 18 | 19 | #[inline] 20 | pub fn length(&self) -> f32 { 21 | (self.0 * self.0 + self.1 * self.1 + self.2 * self.2).sqrt() 22 | } 23 | 24 | #[inline] 25 | pub fn squared_length(&self) -> f32 { 26 | self.0 * self.0 + self.1 * self.1 + self.2 * self.2 27 | } 28 | 29 | #[inline] 30 | pub fn to_unit_vector(&self) -> Vec3 { 31 | *self / self.length() 32 | } 33 | 34 | #[inline] 35 | pub fn make_unit_vector(&mut self) { 36 | let k: f32 = 1.0 / self.squared_length(); 37 | self.0 *= k; 38 | self.1 *= k; 39 | self.2 *= k; 40 | } 41 | 42 | #[inline] 43 | pub fn cross(lhs: &Vec3, rhs: &Vec3) -> Vec3 { 44 | Vec3( 45 | lhs.1 * rhs.2 - lhs.2 * rhs.1, 46 | lhs.2 * rhs.0 - lhs.0 * rhs.2, 47 | lhs.0 * rhs.1 - lhs.1 * rhs.0 48 | ) 49 | } 50 | 51 | #[inline] 52 | pub fn dot(lhs: &Vec3, rhs: &Vec3) -> f32 { 53 | lhs.0 * rhs.0 + lhs.1 * rhs.1 + lhs.2 * rhs.2 54 | } 55 | 56 | #[inline] 57 | pub fn x(&self) -> f32 { 58 | self.0 59 | } 60 | 61 | #[inline] 62 | pub fn y(&self) -> f32 { 63 | self.1 64 | } 65 | 66 | #[inline] 67 | pub fn z(&self) -> f32 { 68 | self.2 69 | } 70 | 71 | #[inline] 72 | pub fn r(&self) -> f32 { 73 | self.0 74 | } 75 | 76 | #[inline] 77 | pub fn g(&self) -> f32 { 78 | self.1 79 | } 80 | 81 | #[inline] 82 | pub fn b(&self) -> f32 { 83 | self.2 84 | } 85 | 86 | } 87 | 88 | impl fmt::Display for Vec3 { 89 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 90 | write!(f, "({}, {}, {})", self.0, self.1, self.2) 91 | } 92 | } 93 | 94 | impl Neg for Vec3 { 95 | type Output = Vec3; 96 | 97 | #[inline] 98 | fn neg(self) -> Vec3 { 99 | Vec3( 100 | -self.0, 101 | -self.1, 102 | -self.2 103 | ) 104 | } 105 | } 106 | 107 | impl Add for Vec3 { 108 | type Output = Vec3; 109 | 110 | #[inline] 111 | fn add(self, rhs: Vec3) -> Vec3 { 112 | Vec3( 113 | self.0 + rhs.0, 114 | self.1 + rhs.1, 115 | self.2 + rhs.2 116 | ) 117 | } 118 | } 119 | 120 | impl AddAssign for Vec3 { 121 | #[inline] 122 | fn add_assign(&mut self, rhs: Vec3) { 123 | self.0 += rhs.0; 124 | self.1 += rhs.1; 125 | self.2 += rhs.2; 126 | } 127 | } 128 | 129 | impl Sub for Vec3 { 130 | type Output = Vec3; 131 | 132 | #[inline] 133 | fn sub(self, rhs: Vec3) -> Vec3 { 134 | Vec3( 135 | self.0 - rhs.0, 136 | self.1 - rhs.1, 137 | self.2 - rhs.2 138 | ) 139 | } 140 | } 141 | 142 | impl SubAssign for Vec3 { 143 | #[inline] 144 | fn sub_assign(&mut self, rhs: Vec3) { 145 | self.0 -= rhs.0; 146 | self.1 -= rhs.1; 147 | self.2 -= rhs.2; 148 | } 149 | } 150 | 151 | impl Mul for Vec3 { 152 | type Output = Vec3; 153 | 154 | #[inline] 155 | fn mul(self, rhs: Vec3) -> Vec3 { 156 | Vec3( 157 | self.0 * rhs.0, 158 | self.1 * rhs.1, 159 | self.2 * rhs.2 160 | ) 161 | } 162 | } 163 | 164 | impl MulAssign for Vec3 { 165 | #[inline] 166 | fn mul_assign(&mut self, rhs: Vec3) { 167 | self.0 *= rhs.0; 168 | self.1 *= rhs.1; 169 | self.2 *= rhs.2; 170 | } 171 | } 172 | 173 | impl Mul for Vec3 { 174 | type Output = Vec3; 175 | 176 | #[inline] 177 | fn mul(self, rhs: f32) -> Vec3 { 178 | Vec3( 179 | self.0 * rhs, 180 | self.1 * rhs, 181 | self.2 * rhs 182 | ) 183 | } 184 | } 185 | 186 | impl MulAssign for Vec3 { 187 | #[inline] 188 | fn mul_assign(&mut self, rhs: f32) { 189 | self.0 *= rhs; 190 | self.1 *= rhs; 191 | self.2 *= rhs; 192 | } 193 | } 194 | 195 | impl Mul for f32 { 196 | type Output = Vec3; 197 | 198 | #[inline] 199 | fn mul(self, rhs: Vec3) -> Vec3 { 200 | Vec3( 201 | self * rhs.0, 202 | self * rhs.1, 203 | self * rhs.2 204 | ) 205 | } 206 | } 207 | 208 | impl Mul<&Vec3> for f32 { 209 | type Output = Vec3; 210 | 211 | #[inline] 212 | fn mul(self, rhs: &Vec3) -> Vec3 { 213 | Vec3( 214 | self * rhs.0, 215 | self * rhs.1, 216 | self * rhs.2 217 | ) 218 | } 219 | } 220 | 221 | impl Div for Vec3 { 222 | type Output = Vec3; 223 | 224 | #[inline] 225 | fn div(self, rhs: Vec3) -> Vec3 { 226 | Vec3( 227 | self.0 / rhs.0, 228 | self.1 / rhs.1, 229 | self.2 / rhs.2 230 | ) 231 | } 232 | } 233 | 234 | impl DivAssign for Vec3 { 235 | #[inline] 236 | fn div_assign(&mut self, rhs: Vec3) { 237 | self.0 /= rhs.0; 238 | self.1 /= rhs.1; 239 | self.2 /= rhs.2; 240 | } 241 | } 242 | 243 | impl Div for Vec3 { 244 | type Output = Vec3; 245 | 246 | #[inline] 247 | fn div(self, rhs: f32) -> Vec3 { 248 | Vec3( 249 | self.0 / rhs, 250 | self.1 / rhs, 251 | self.2 / rhs 252 | ) 253 | } 254 | } 255 | 256 | impl DivAssign for Vec3 { 257 | #[inline] 258 | fn div_assign(&mut self, rhs: f32) { 259 | self.0 /= rhs; 260 | self.1 /= rhs; 261 | self.2 /= rhs; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use sdl2::rect::Rect; 2 | use sdl2::event::Event; 3 | use sdl2::keyboard::Keycode; 4 | use sdl2::pixels::PixelFormatEnum; 5 | use crate::tracer::trace; 6 | use crate::utils::write_png_file; 7 | 8 | pub fn window_main_loop() { 9 | let texture_width: u32 = 1920; 10 | let texture_height: u32 = 1080; 11 | 12 | let sdl_context = sdl2::init().unwrap(); 13 | let video_subsystem = sdl_context.video().unwrap(); 14 | 15 | let window = video_subsystem.window("ReitoreSenpai", texture_width, texture_height) 16 | .position_centered() 17 | .opengl() 18 | .build() 19 | .unwrap(); 20 | 21 | let mut canvas = window.into_canvas().present_vsync().build().unwrap(); 22 | let texture_creator = canvas.texture_creator(); 23 | 24 | let mut texture = texture_creator.create_texture_streaming(PixelFormatEnum::ABGR8888, texture_width, texture_height) 25 | .unwrap(); 26 | 27 | canvas.clear(); 28 | canvas.present(); 29 | 30 | println!("Tracing......"); 31 | 32 | texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { 33 | let (threads, duration) = trace(buffer, pitch, texture_width, texture_height); 34 | let window = canvas.window_mut(); 35 | window.set_title(format!("ReitoreSenpai threads: {}, elapsed: {}", threads, duration.as_secs_f32()).as_ref()).unwrap(); 36 | write_png_file(r"out.png".to_string(), texture_width, texture_height, buffer); 37 | }).unwrap(); 38 | 39 | println!("Compelete! Written to out.png"); 40 | 41 | canvas.clear(); 42 | canvas.copy_ex(&texture, None, Some(Rect::new(0, 0, texture_width, texture_height)), 43 | 0.0, None, false, false).unwrap(); 44 | canvas.present(); 45 | 46 | let mut event_pump = sdl_context.event_pump().unwrap(); 47 | 48 | 'running: loop { 49 | for event in event_pump.poll_iter() { 50 | match event { 51 | Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { 52 | break 'running; 53 | }, 54 | _ => {} 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/yjsnpi_scene.rs: -------------------------------------------------------------------------------- 1 | use crate::vec3::Vec3; 2 | use crate::hitable_list::HitableList; 3 | use crate::sphere::Sphere; 4 | use crate::surface::Surface; 5 | use crate::lambertian::Lambertian; 6 | use crate::dielectric::Dielectric; 7 | use crate::metal::Metal; 8 | use crate::scene::Scene; 9 | 10 | pub struct YjsnpiScene {} 11 | 12 | impl Scene for YjsnpiScene { 13 | 14 | fn generate(&self) -> HitableList { 15 | let mut world = HitableList::default(); 16 | 17 | // x-y plane 18 | world.push(Surface::new( 19 | 0.0, 20 | 0.0, 21 | 1.0, 22 | 0.0, 23 | -0.5..=15.0, 24 | -0.5..=15.0, 25 | -0.5..=15.0, 26 | Lambertian::new(Vec3(0.615, 0.555, 0.504)) 27 | )); 28 | 29 | // y-z plane 30 | world.push(Surface::new( 31 | 1.0, 32 | 0.0, 33 | 0.0, 34 | 0.0, 35 | -0.5..=15.0, 36 | -0.5..=15.0, 37 | -0.5..=15.0, 38 | Lambertian::new(Vec3(0.346, 0.389, 0.365)) 39 | )); 40 | 41 | // x-z plane 42 | world.push(Surface::new( 43 | 0.0, 44 | 1.0, 45 | 0.0, 46 | 0.0, 47 | -0.5..=15.0, 48 | -0.5..=15.0, 49 | -0.5..=15.0, 50 | Metal::new(Vec3(0.061, 0.04, 0.063), 0.0) 51 | )); 52 | 53 | // senpai ball 54 | world.push(Sphere::new( 55 | Vec3(4.0, 3.0, 4.0), 56 | 3.0, 57 | Lambertian::new( 58 | Vec3(0.398, 0.200, 0.173), 59 | ) 60 | )); 61 | 62 | world 63 | } 64 | 65 | } 66 | --------------------------------------------------------------------------------