From 5aeed6a21c20a57122db5e82e915bb1864434346 Mon Sep 17 00:00:00 2001 From: Yann Herklotz Date: Thu, 30 Apr 2020 02:04:52 +0100 Subject: Add better camera and more materials --- src/camera.rs | 37 +++++++++++++++++++------- src/main.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++------------ src/material.rs | 64 +++++++++++++++++++++++++++++++++++++++++++-- src/render.rs | 21 +++++++-------- 4 files changed, 165 insertions(+), 38 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index 8090bde..7564c12 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,25 +1,44 @@ +use cgmath::prelude::*; use cgmath::{Vector3, vec3}; +use rand::prelude::*; use crate::ray::Ray; +use crate::utils::random_in_unit_disk; pub struct Camera { origin: Vector3, lower_left_corner: Vector3, horizontal: Vector3, - vertical: Vector3 + vertical: Vector3, + u: Vector3, + v: Vector3, + w: Vector3, + lens_radius: f64 } impl Camera { - pub fn new() -> Camera { + pub fn new(lookfrom: &Vector3, lookat: &Vector3, vup: &Vector3, + vfov: f64, aspect: f64, aperture: f64, focus_dist: f64) -> Camera { + let lens_radius = aperture / 2.0; + let half_height = (vfov / 2.0).tan(); + let half_width = aspect * half_height; + let w = (lookfrom - lookat).normalize(); + let u = vup.cross(w).normalize(); + let v = w.cross(u); Camera { - origin: vec3(0.0, 0.0, 0.0), - lower_left_corner: vec3(-2.0, -1.0, -1.0), - horizontal: vec3(4.0, 0.0, 0.0), - vertical: vec3(0.0, 2.0, 0.0) + origin: *lookfrom, + lower_left_corner: lookfrom - half_width * focus_dist * u + - half_height * focus_dist * v - focus_dist * w, + horizontal: 2.0 * half_width * focus_dist * u, + vertical: 2.0 * half_height * focus_dist * v, + lens_radius, + u, v, w } } - pub fn get_ray(&self, u: f64, v: f64) -> Ray { - Ray::new(self.origin, - self.lower_left_corner + u * self.horizontal + v * self.vertical) + pub fn get_ray(&self, rng: &mut ThreadRng, s: f64, t: f64) -> Ray { + let rd = self.lens_radius * random_in_unit_disk(rng); + let offset = self.u * rd.x + self.v * rd.y; + Ray::new(self.origin + offset, + self.lower_left_corner + s * self.horizontal + t * self.vertical - self.origin - offset) } } diff --git a/src/main.rs b/src/main.rs index 4abc7c9..1a0b001 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,84 @@ -use cgmath::vec3; +use cgmath::prelude::*; +use cgmath::{Vector3, vec3}; use rand::prelude::*; +use std::f64::consts::PI; mod camera; -mod colour; mod hittable; mod material; -mod random; mod ray; mod render; mod scene; mod sphere; +mod utils; -fn main() { - let image_width = 200; - let image_height = 100; - let samples = 50; - let max_depth = 25; +pub fn random_scene(rng: &mut ThreadRng) -> scene::Scene { + let mut scene = scene::Scene::new(); - println!("P3\n{} {}\n255", image_width, image_height); + scene.add(Box::new(sphere::Sphere::new( + vec3(0.0, -1000.0, 0.0), 1000.0, Box::new(material::Lambertian::new(vec3(0.5, 0.5, 0.5)))))); + + for a in -11 .. 11 { + for b in -11 .. 11 { + let choose_mat: f64 = rng.gen(); + let center = vec3(a as f64 + 0.9*rng.gen::(), 0.2, b as f64 + 0.9*rng.gen::()); + if (center - vec3(4.0, 0.2, 0.0)).magnitude() > 0.9 { + if choose_mat < 0.8 { + // diffuse + let albedo = utils::random_vector3(rng, 0.0, 1.0).mul_element_wise(utils::random_vector3(rng, 0.0, 1.0)); + scene.add( + Box::new(sphere::Sphere::new(center, 0.2, Box::new(material::Lambertian::new(albedo))))); + } else if choose_mat < 0.95 { + // metal + let albedo = utils::random_vector3(rng, 0.5, 1.0); + let fuzz = rng.gen_range(0.0, 0.5); + scene.add( + Box::new(sphere::Sphere::new( + center, 0.2, Box::new(material::Metal::new(albedo, fuzz))))); + } else { + // glass + scene.add(Box::new(sphere::Sphere::new( + center, 0.2, Box::new(material::Dielectric::new(1.5))))); + } + } + } + } - let mut world = scene::Scene::new(); - world.add(Box::new(sphere::Sphere::new( - vec3(0.0, 0.0, -1.0), 0.5, Box::new(material::Lambertian::new(vec3(1.0, 1.0, 1.0)))))); - world.add(Box::new(sphere::Sphere::new( - vec3(0.0, -100.5, -1.0), 100.0, Box::new(material::Lambertian::new(vec3(1.0, 1.0, 1.0)))))); + scene.add(Box::new(sphere::Sphere::new( + vec3(0.0, 1.0, 0.0), 1.0, Box::new(material::Dielectric::new(1.5))))); - let camera = camera::Camera::new(); + scene.add( + Box::new(sphere::Sphere::new(vec3(-4.0, 1.0, 0.0), 1.0, + Box::new(material::Lambertian::new(vec3(0.4, 0.2, 0.1)))))); + + scene.add( + Box::new(sphere::Sphere::new( + vec3(4.0, 1.0, 0.0), 1.0, Box::new(material::Metal::new(vec3(0.7, 0.6, 0.5), 0.0))))); + + scene +} +fn main() { + let image_width = 1200; + let image_height = 800; + let samples = 100; + let max_depth = 50; + let aspect_ratio = image_width as f64 / image_height as f64; + + println!("P3\n{} {}\n255", image_width, image_height); let mut rng = thread_rng(); - render::render(&world, camera, image_height, image_width, samples, max_depth, &mut rng); + let scene = random_scene(&mut rng); + + let lookfrom = vec3(13.0, 2.0, 3.0); + let lookat = vec3(0.0, 0.0, 0.0); + let vup = vec3(0.0, 1.0, 0.0); + let dist_to_focus = 10.0; + let aperture = 0.1; + let camera = camera::Camera::new(&lookfrom, &lookat, &vup, + 20.0 * PI / 180.0, aspect_ratio, aperture, dist_to_focus); + + render::render(&scene, camera, image_height, image_width, samples, max_depth, &mut rng); eprintln!("\nDone") } diff --git a/src/material.rs b/src/material.rs index 3cbbb4a..42e8d0c 100644 --- a/src/material.rs +++ b/src/material.rs @@ -1,8 +1,9 @@ -use cgmath::Vector3; +use cgmath::prelude::*; +use cgmath::{Vector3, vec3, dot}; use rand::prelude::*; use crate::ray::{Ray, NextRay}; use crate::hittable::Hit; -use crate::random::random_unit_vector; +use crate::utils::{random_unit_vector, random_in_unit_sphere, reflect, refract, schlick}; pub trait Material { fn scatter(&self, rng: &mut ThreadRng, ray: &Ray, hit: &Hit) -> Option; @@ -24,3 +25,62 @@ impl Material for Lambertian { Some(NextRay::new(self.albedo, Ray::new(hit.p, scatter_direction))) } } + +pub struct Metal { + pub albedo: Vector3, + pub fuzz: f64 +} + +impl Metal { + pub fn new(albedo: Vector3, f: f64) -> Metal { + let fuzz = if f < 1.0 { f } else { 1.0 }; + Metal { albedo, fuzz } + } +} + +impl Material for Metal { + fn scatter(&self, rng: &mut ThreadRng, ray: &Ray, hit: &Hit) -> Option { + let reflected = reflect(&ray.dir.normalize(), &hit.normal); + let scattered = Ray::new(hit.p, reflected + self.fuzz * random_in_unit_sphere(rng)); + if dot(scattered.dir, hit.normal) > 0.0 { + Some(NextRay::new(self.albedo, scattered)) + } else { + None + } + } +} + +pub struct Dielectric { + pub ref_idx: f64 +} + +impl Dielectric { + pub fn new(ref_idx: f64) -> Dielectric { + Dielectric { ref_idx } + } +} + +impl Material for Dielectric { + fn scatter(&self, rng: &mut ThreadRng, ray: &Ray, hit: &Hit) -> Option { + let attenuation = vec3(1.0, 1.0, 1.0); + let etai_over_etat = if hit.front_face { 1.0 / self.ref_idx } else { self.ref_idx }; + + let unit_direction = ray.dir.normalize(); + let cos_theta = f64::min(dot(-unit_direction, hit.normal), 1.0); + let sin_theta = (1.0 - cos_theta*cos_theta).sqrt(); + + if etai_over_etat * sin_theta > 1.0 { + let reflected = reflect(&unit_direction, &hit.normal); + return Some(NextRay::new(attenuation, Ray::new(hit.p, reflected))) + } + + let reflect_prob = schlick(cos_theta, etai_over_etat); + if rng.gen::() < reflect_prob { + let reflected = reflect(&unit_direction, &hit.normal); + return Some(NextRay::new(attenuation, Ray::new(hit.p, reflected))) + } + + let refracted = refract(&unit_direction, &hit.normal, etai_over_etat); + Some(NextRay::new(attenuation, Ray::new(hit.p, refracted))) + } +} diff --git a/src/render.rs b/src/render.rs index 6e2abf8..740c3eb 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,24 +3,23 @@ use cgmath::{Vector3, vec3}; use rand::prelude::*; use std::f64::INFINITY; use crate::camera::Camera; -use crate::colour::print_colour; use crate::scene::Scene; -use crate::random::random_in_unit_sphere; +use crate::utils::{print_colour}; use crate::ray::Ray; use crate::hittable::Hittable; fn ray_colour(rng: &mut ThreadRng, ray: &Ray, scene: &Scene, depth: i32) -> Vector3 { if depth <= 0 { return vec3(0.0, 0.0, 0.0) } - match scene.is_hit(ray, 0.001, INFINITY) { - None => { - let t = 0.5 * (ray.dir.normalize().y + 1.0); - (1.0 - t) * vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0) - } - Some(t) => { - let target = t.p + t.normal + random_in_unit_sphere(rng); - 0.5 * ray_colour(rng, &Ray::new(t.p, target - t.p), scene, depth - 1) + if let Some(t) = scene.is_hit(ray, 0.001, INFINITY) { + if let Some(nray) = t.material.scatter(rng, ray, &t) { + ray_colour(rng, &nray.ray, scene, depth-1).mul_element_wise(nray.attenuation) + } else { + vec3(0.0, 0.0, 0.0) } + } else { + let t = 0.5 * (ray.dir.normalize().y + 1.0); + (1.0 - t) * vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0) } } @@ -35,7 +34,7 @@ pub fn render(world: &Scene, camera: Camera, image_height: i32, image_width: i32 let rv: f64 = rng.gen(); let u = (i as f64 + ru) / image_width as f64; let v = ((image_height - 1 - j) as f64 + rv) / image_height as f64; - let ray = camera.get_ray(u, v); + let ray = camera.get_ray(rng, u, v); colour += ray_colour(rng, &ray, &world, max_depth); } print_colour(&colour, samples); -- cgit