diff --git a/Cargo.toml b/Cargo.toml index 5417d38..4f4780b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rand = "0.7.0" +rand = "0.10.0" rayon = "1.8" image = "0.25.9" -indicatif = "0.18.3" \ No newline at end of file +indicatif = "0.18.4" \ No newline at end of file diff --git a/example.png b/example.png index 979ba45..a3fff5d 100644 Binary files a/example.png and b/example.png differ diff --git a/src/camera.rs b/src/camera.rs index bf7d1b8..50e3ab6 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,3 +1,4 @@ +use crate::hittable::Hittable; use crate::ray::Ray; use crate::rtweekend::degrees_to_radians; use crate::vec3::*; @@ -9,8 +10,7 @@ pub struct Camera { vertical: Vec3, u: Vec3, v: Vec3, - w: Vec3, - lens_radius: f64, + lens_radius: f64, } impl Camera { @@ -18,13 +18,15 @@ impl Camera { lookfrom: Point3, lookat: Point3, vup: Vec3, - vfov: f64, + focal_length_mm: f64, aspect_ratio: f64, - apeture: f64, + defocus_angle: f64, focus_dist: f64, ) -> Self { - let theta = degrees_to_radians(vfov); - let h = f64::tan(theta/2.0); + let sensor_height = 24.0; // in mm + let theta = 2.0 * f64::atan(sensor_height / (2.0 * focal_length_mm)); + + let h = f64::tan(theta / 2.0); let viewport_height = 2.0 * h; let viewport_width = aspect_ratio * viewport_height; @@ -35,20 +37,57 @@ impl Camera { let origin = lookfrom; let horizontal = focus_dist * viewport_width * u; let vertical = focus_dist * viewport_height * v; - let lower_left_corner = origin - horizontal/2.0 - vertical/2.0 - focus_dist * w; + let lower_left_corner = origin - horizontal / 2.0 - vertical / 2.0 - focus_dist * w; - let lens_radius = apeture / 2.0; + let defocus_theta = degrees_to_radians(defocus_angle); + let lens_radius = focus_dist * f64::tan(defocus_theta / 2.0); - Camera { origin, lower_left_corner, horizontal, vertical, u, v, w, lens_radius} + Camera { + origin, + lower_left_corner, + horizontal, + vertical, + u, + v, + lens_radius, + } } pub fn get_ray(&self, s: f64, t: f64) -> Ray { let rd = self.lens_radius * random_in_unit_disk(); let offset = self.u * rd.x() + self.v * rd.y(); + let ray_origin = self.origin + offset; + Ray { - origin: self.origin, - direction: self.lower_left_corner + s*self.horizontal + t*self.vertical - self.origin - offset + origin: ray_origin, + direction: self.lower_left_corner + s * self.horizontal + t * self.vertical + - ray_origin, } } -} \ No newline at end of file + + pub fn ray_color(&self, r: Ray, world: &dyn Hittable, depth: i32) -> Color { + // Limit the bounces + if depth <= 0 { + return Color::new(0.0, 0.0, 0.0); + } + + if let Some(rec) = world.hit(&r, 0.001, f64::INFINITY) { + let mut scattered = Ray::new_empty(); + let mut attenuation = Color::new_empty(); + + if rec + .material + .scatter(r, rec, &mut attenuation, &mut scattered) + { + return attenuation * Self::ray_color(&self, scattered, world, depth - 1); + } + return Color::new_empty(); + } + + let unit_direction: Vec3 = r.direction().unit_vector(); + let t = 0.5 * (unit_direction.y() + 1.0); + + ((1.0 - t) * Color::new(1.0, 1.0, 1.0)) + (t * Color::new(0.5, 0.7, 1.0)) + } +} diff --git a/src/hittable.rs b/src/hittable.rs index 8314654..008422e 100644 --- a/src/hittable.rs +++ b/src/hittable.rs @@ -6,14 +6,14 @@ use crate::vec3::*; pub struct HitRecord<'a> { pub p: Point3, pub normal: Vec3, - pub mat_ptr: &'a dyn Material, + pub material: &'a dyn Material, pub t: f64, pub front_face: bool, } impl HitRecord<'_> { pub fn new_empty(material: &'static T) -> Self { - HitRecord { p: Point3::new(0.0, 0.0, 0.0), normal: Vec3::new(0.0, 0.0, 0.0), t: 0.0, front_face: false, mat_ptr: material } + HitRecord { p: Point3::new(0.0, 0.0, 0.0), normal: Vec3::new(0.0, 0.0, 0.0), t: 0.0, front_face: false, material: material } } pub fn set_face_normal(&mut self, r: &Ray, outward_normal: Vec3) { diff --git a/src/main.rs b/src/main.rs index 06f663b..231b339 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,6 @@ use rayon::prelude::*; use crate::{ camera::*, color::*, - hittable::*, hittable_list::*, material::*, material::{Dielectric, Lambertian}, @@ -27,7 +26,8 @@ use crate::{ vec3::*, }; -fn random_scene() -> HittableList { +#[allow(dead_code)] +fn random_scene(aspect_ratio: f64) -> (Camera, HittableList) { let mut world = HittableList::new_empty(); let ground_material: Lambertian = Lambertian::new(Color::new(0.5, 0.5, 0.5)); @@ -47,16 +47,28 @@ fn random_scene() -> HittableList { ); if (center - Point3::new(4.0, 0.2, 0.0)).length() > 0.9 { - if choose_mat < 0.8 { + if choose_mat < 0.3 { //let albedo = Color::new(0.3, 0.8, 0.4); let sphere_material: Lambertian = Lambertian::new(Color::random()); - world.add(Box::new(Sphere::new(center, 0.2, Box::new(sphere_material)))); - } else if choose_mat < 0.95 { + world.add(Box::new(Sphere::new( + center, + 0.2, + Box::new(sphere_material), + ))); + } else if choose_mat < 0.7 { let sphere_material: Metal = Metal::new(Color::random(), 0.2); - world.add(Box::new(Sphere::new(center, 0.2, Box::new(sphere_material)))); + world.add(Box::new(Sphere::new( + center, + 0.2, + Box::new(sphere_material), + ))); } else { let sphere_material: Dielectric = Dielectric::new(1.5); - world.add(Box::new(Sphere::new(center, 0.2, Box::new(sphere_material)))); + world.add(Box::new(Sphere::new( + center, + 0.2, + Box::new(sphere_material), + ))); } } } @@ -83,32 +95,85 @@ fn random_scene() -> HittableList { Box::new(material3), ))); - world + // Camera + let lookfrom = Point3::new(13.0, 2.0, 3.0); + let lookat = Point3::new(0.0, 0.0, 0.0); + let vup = Vec3::new(0.0, 1.0, 0.0); + let dist_to_focus = (lookfrom - lookat).length(); + let focal_length = 50.0; + let defocus_angle = 1.; + + let cam = Camera::new( + lookfrom, + lookat, + vup, + focal_length, + aspect_ratio, + defocus_angle, + dist_to_focus, + ); + + (cam, world) } -fn ray_color(r: Ray, world: &dyn Hittable, depth: i32) -> Color { - // Limit the bounces - if depth <= 0 { - return Color::new(0.0, 0.0, 0.0); - } +#[allow(dead_code)] +fn three_balls_scene(aspect_ratio: f64) -> (Camera, HittableList) { + let mut world = HittableList::new_empty(); - if let Some(rec) = world.hit(&r, 0.001, INFINITY) { - let mut scattered = Ray::new_empty(); - let mut attenuation = Color::new_empty(); + let material_ground: Lambertian = Lambertian::new(Color::new(0.8, 0.8, 0.0)); + world.add(Box::new(Sphere::new( + Point3::new(0.0, -100.5, -1.0), + 100.0, + Box::new(material_ground), + ))); - if rec - .mat_ptr - .scatter(r, rec, &mut attenuation, &mut scattered) - { - return attenuation * ray_color(scattered, world, depth - 1); - } - return Color::new_empty(); - } + let material_center = Lambertian::new(Color::new(0.1, 0.2, 0.5)); + world.add(Box::new(Sphere::new( + Point3::new(0.0, 0.0, -1.2), + 0.5, + Box::new(material_center), + ))); - let unit_direction: Vec3 = r.direction().unit_vector(); - let t = 0.5 * (unit_direction.y() + 1.0); + let material_left = Dielectric::new(1.5); + world.add(Box::new(Sphere::new( + Point3::new(-1.0, 0.0, -1.0), + 0.5, + Box::new(material_left), + ))); - ((1.0 - t) * Color::new(1.0, 1.0, 1.0)) + (t * Color::new(0.5, 0.7, 1.0)) + let material_bubble = Dielectric::new(1.0 / 1.5); + world.add(Box::new(Sphere::new( + Point3::new(-1.0, 0.0, -1.0), + 0.4, + Box::new(material_bubble), + ))); + + let material_right = Metal::new(Color::new(0.8, 0.6, 0.2), 1.0); + world.add(Box::new(Sphere::new( + Point3::new(1.0, 0.0, -1.0), + 0.5, + Box::new(material_right), + ))); + + // Camera + let lookfrom = Point3::new(-2.0, 2.0, 1.0); + let lookat = Point3::new(0.0, 0.0, -1.0); + let vup = Vec3::new(0.0, 1.0, 0.0); + let dist_to_focus = 10.0; //(lookfrom - lookat).length_squared(); + let vfov = 20.0; + let defocus_angle = 0.0; + + let cam = Camera::new( + lookfrom, + lookat, + vup, + vfov, + aspect_ratio, + defocus_angle, + dist_to_focus, + ); + + (cam, world) } fn main() { @@ -117,26 +182,10 @@ fn main() { let image_width: i32 = 1920; let image_height: i32 = (image_width as f64 / aspect_ratio as f64) as i32; let samples_per_pixel: i32 = 200; - let max_depth: i32 = 5; + let max_depth: i32 = 10; - // World - let world = random_scene(); - - // Camera - let lookfrom = Point3::new(13.0, 2.0, 3.0); - let lookat = Point3::new(0.0, 0.0, 0.0); - let vup = Vec3::new(0.0, 1.0, 0.0); - let dist_to_focus = 10.0; //(lookfrom-lookat).length_squared(); - - let cam = Camera::new( - lookfrom, - lookat, - vup, - 30.0, - aspect_ratio, - 0.0, - dist_to_focus, - ); + // Camera and World + let (cam, world) = random_scene(aspect_ratio); let num_threads: usize = match available_parallelism() { Ok(threads) => threads.into(), @@ -174,7 +223,7 @@ fn main() { let u = (x as f64 + random_f64()) / (image_width - 1) as f64; let v = (reversed_y as f64 + random_f64()) / (image_height - 1) as f64; let r = cam.get_ray(u, v); - pixel_color += ray_color(r, &world, max_depth); + pixel_color += cam.ray_color(r, &world, max_depth); } let (ir, ig, ib) = get_scaled_color_components(pixel_color, samples_per_pixel); diff --git a/src/material.rs b/src/material.rs index 8749f3e..32ad529 100644 --- a/src/material.rs +++ b/src/material.rs @@ -1,7 +1,7 @@ use crate::hittable::HitRecord; use crate::ray::Ray; use crate::rtweekend::random_f64; -use crate::vec3::{Color, random_unit_vector, reflect, refract}; +use crate::vec3::{random_unit_vector, reflect, refract, Color}; //More rusty /* @@ -15,9 +15,14 @@ use crate::vec3::{Color, random_unit_vector, reflect, refract}; } */ - pub trait Material: Send + Sync { - fn scatter(&self, r_in: Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool; + fn scatter( + &self, + r_in: Ray, + rec: HitRecord, + attenuation: &mut Color, + scattered: &mut Ray, + ) -> bool; } pub struct Lambertian { @@ -31,7 +36,13 @@ impl Lambertian { } impl Material for Lambertian { - fn scatter(&self, _r_in: Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { + fn scatter( + &self, + _r_in: Ray, + rec: HitRecord, + attenuation: &mut Color, + scattered: &mut Ray, + ) -> bool { let mut scatter_direction = rec.normal + random_unit_vector(); if scatter_direction.near_zero() { @@ -56,9 +67,16 @@ impl Metal { } impl Material for Metal { - fn scatter(&self, r_in: Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { - let reflected = reflect(&r_in.direction(), &rec.normal); - *scattered = Ray::new(rec.p, reflected + self.fuzz*random_unit_vector()); + fn scatter( + &self, + r_in: Ray, + rec: HitRecord, + attenuation: &mut Color, + scattered: &mut Ray, + ) -> bool { + let mut reflected = reflect(&r_in.direction(), &rec.normal); + reflected = reflected.unit_vector() + (self.fuzz * random_unit_vector()); + *scattered = Ray::new(rec.p, reflected + self.fuzz * random_unit_vector()); *attenuation = self.albedo; scattered.direction().dot(&rec.normal) > 0.0 @@ -76,29 +94,37 @@ impl Dielectric { fn reflectance(&self, cosine: f64, ref_idx: f64) -> f64 { // Schlick - let mut r0 = (1.0-ref_idx) / (1.0+ref_idx); - r0 = r0*r0; + let mut r0 = (1.0 - ref_idx) / (1.0 + ref_idx); + r0 = r0 * r0; - return r0 + (1.0-r0)*f64::powf(1.0 - cosine, 5.0); + return r0 + (1.0 - r0) * f64::powf(1.0 - cosine, 5.0); } } impl Material for Dielectric { - fn scatter(&self, r_in: Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { + fn scatter( + &self, + r_in: Ray, + rec: HitRecord, + attenuation: &mut Color, + scattered: &mut Ray, + ) -> bool { *attenuation = Color::new(1.0, 1.0, 1.0); let refraction_ratio: f64 = if rec.front_face { - 1.0/self.ir + 1.0 / self.ir } else { self.ir }; let unit_direction = r_in.direction().unit_vector(); let cos_theta = f64::min(-unit_direction.dot(&rec.normal), 1.0); - let sin_theta = f64::sqrt(1.0 - cos_theta*cos_theta); + let sin_theta = f64::sqrt(1.0 - cos_theta * cos_theta); - let cannot_refract = (refraction_ratio * sin_theta )> 1.0; + let cannot_refract = (refraction_ratio * sin_theta) > 1.0; - let direction = if cannot_refract || Dielectric::reflectance(self, cos_theta, refraction_ratio) > random_f64() { + let direction = if cannot_refract + || Dielectric::reflectance(self, cos_theta, refraction_ratio) > random_f64() + { reflect(&unit_direction, &rec.normal) } else { refract(&unit_direction, &rec.normal, refraction_ratio) @@ -107,4 +133,4 @@ impl Material for Dielectric { *scattered = Ray::new(rec.p, direction); true } -} \ No newline at end of file +} diff --git a/src/rtweekend.rs b/src/rtweekend.rs index 0dbf732..bac8296 100644 --- a/src/rtweekend.rs +++ b/src/rtweekend.rs @@ -1,26 +1,22 @@ -use rand::Rng; - -// Constants -pub const INFINITY: f64 = std::f64::INFINITY; -pub const PI: f64 = std::f64::consts::PI; +use rand::RngExt; // Utility Functions pub fn degrees_to_radians(degrees: f64) -> f64 { degrees * std::f64::consts::PI / 180.0 } -// PLEASE CHANGE pub fn random_f64() -> f64 { - rand::thread_rng().gen() + rand::rng().random() } -// PLEASE CHANGE pub fn random_range_f64(min: f64, max: f64) -> f64 { - rand::thread_rng().gen_range(min, max) + rand::rng().random_range(min..max) } pub fn clamp(x: f64, min: f64, max: f64) -> f64 { if x < min { return min; } if x > max { return max; } x -} \ No newline at end of file +} + +// TODO: für jeden thread einen rng() erstellen und danach droppen, anstatt für jede execution rand::rng() \ No newline at end of file diff --git a/src/sphere.rs b/src/sphere.rs index 6c9e9e3..b79ba9a 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -8,12 +8,12 @@ use crate::Ray; pub struct Sphere { center: Point3, radius: f64, - mat_ptr: Box, + material: Box, } impl Sphere { - pub fn new(center: Point3, radius: f64, mat_ptr: Box) -> Self { - Sphere {center, radius, mat_ptr} + pub fn new(center: Point3, radius: f64, material: Box) -> Self { + Sphere {center, radius, material} } } @@ -54,7 +54,7 @@ impl Hittable for Sphere { p: p, normal: outward_normal, front_face, - mat_ptr: self.mat_ptr.deref(), + material: self.material.deref(), }) } } \ No newline at end of file