aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYann Herklotz <git@yannherklotz.com>2020-04-30 02:04:52 +0100
committerYann Herklotz <git@yannherklotz.com>2020-04-30 02:05:00 +0100
commit5aeed6a21c20a57122db5e82e915bb1864434346 (patch)
treeced09e7be6502978faa5cba47ef7c68ef0fb6420
parentcd63b41fd6fe7586a099364d21b69199f8361031 (diff)
downloadleela-5aeed6a21c20a57122db5e82e915bb1864434346.tar.gz
leela-5aeed6a21c20a57122db5e82e915bb1864434346.zip
Add better camera and more materials
-rw-r--r--src/camera.rs37
-rw-r--r--src/main.rs81
-rw-r--r--src/material.rs64
-rw-r--r--src/render.rs21
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<f64>,
lower_left_corner: Vector3<f64>,
horizontal: Vector3<f64>,
- vertical: Vector3<f64>
+ vertical: Vector3<f64>,
+ u: Vector3<f64>,
+ v: Vector3<f64>,
+ w: Vector3<f64>,
+ lens_radius: f64
}
impl Camera {
- pub fn new() -> Camera {
+ pub fn new(lookfrom: &Vector3<f64>, lookat: &Vector3<f64>, vup: &Vector3<f64>,
+ 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::<f64>(), 0.2, b as f64 + 0.9*rng.gen::<f64>());
+ 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<NextRay>;
@@ -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<f64>,
+ pub fuzz: f64
+}
+
+impl Metal {
+ pub fn new(albedo: Vector3<f64>, 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<NextRay> {
+ 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<NextRay> {
+ 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::<f64>() < 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<f64> {
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);