summaryrefslogtreecommitdiff
path: root/racer-tracer
diff options
context:
space:
mode:
Diffstat (limited to 'racer-tracer')
-rw-r--r--racer-tracer/src/geometry.rs21
-rw-r--r--racer-tracer/src/geometry/sphere.rs8
-rw-r--r--racer-tracer/src/main.rs176
-rw-r--r--racer-tracer/src/material.rs10
-rw-r--r--racer-tracer/src/material/lambertian.rs33
-rw-r--r--racer-tracer/src/material/metal.rs33
-rw-r--r--racer-tracer/src/util.rs3
-rw-r--r--racer-tracer/src/vec3.rs58
8 files changed, 285 insertions, 57 deletions
diff --git a/racer-tracer/src/geometry.rs b/racer-tracer/src/geometry.rs
index 8d86353..1cc7e28 100644
--- a/racer-tracer/src/geometry.rs
+++ b/racer-tracer/src/geometry.rs
@@ -1,5 +1,8 @@
pub mod sphere;
+use std::sync::Arc;
+
+use crate::material::Material;
use crate::ray::Ray;
use crate::vec3::Vec3;
@@ -8,17 +11,17 @@ pub struct HitRecord {
pub normal: Vec3,
pub t: f64,
pub front_face: bool,
- pub color: Vec3,
+ pub material: Arc<Box<dyn Material + Send + Sync>>,
}
impl HitRecord {
- fn new(point: Vec3, t: f64, color: Vec3) -> Self {
+ fn new(point: Vec3, t: f64, material: Arc<Box<dyn Material + Send + Sync>>) -> Self {
Self {
point,
normal: Vec3::default(),
t,
front_face: true,
- color,
+ material,
}
}
@@ -32,18 +35,6 @@ impl HitRecord {
}
}
-impl Default for HitRecord {
- fn default() -> Self {
- HitRecord {
- point: Vec3::default(),
- normal: Vec3::default(),
- t: 0.0,
- front_face: true,
- color: Vec3::default(),
- }
- }
-}
-
pub trait Hittable {
//pub trait Hittable {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
diff --git a/racer-tracer/src/geometry/sphere.rs b/racer-tracer/src/geometry/sphere.rs
index e974c44..256abec 100644
--- a/racer-tracer/src/geometry/sphere.rs
+++ b/racer-tracer/src/geometry/sphere.rs
@@ -1,17 +1,19 @@
use std::option::Option;
+use std::sync::Arc;
use crate::geometry::{HitRecord, Hittable};
+use crate::material::Material;
use crate::ray::Ray;
use crate::vec3::Vec3;
pub struct Sphere {
pos: Vec3,
radius: f64,
- material: Vec3, // Just a color for now.
+ material: Arc<Box<dyn Material + Sync + Send>>, // Just a color for now.
}
impl Sphere {
- pub fn new(pos: Vec3, radius: f64, material: Vec3) -> Self {
+ pub fn new(pos: Vec3, radius: f64, material: Arc<Box<dyn Material + Sync + Send>>) -> Self {
Self {
pos,
radius,
@@ -47,7 +49,7 @@ impl Hittable for Sphere {
let point = ray.at(root);
let outward_normal = (point - self.pos) / self.radius;
- let mut hit_record = HitRecord::new(point, root, self.material);
+ let mut hit_record = HitRecord::new(point, root, Arc::clone(&self.material));
hit_record.set_face_normal(ray, outward_normal);
Some(hit_record)
}
diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs
index c9ed169..508c22a 100644
--- a/racer-tracer/src/main.rs
+++ b/racer-tracer/src/main.rs
@@ -3,6 +3,7 @@ mod error;
mod camera;
mod geometry;
mod image;
+mod material;
mod ray;
mod scene;
mod util;
@@ -11,26 +12,43 @@ mod vec3;
use std::{
borrow::Borrow,
sync::{Arc, Mutex, RwLock},
+ time::{Duration, Instant},
vec::Vec,
};
-use geometry::Hittable;
+use material::{lambertian::Lambertian, metal::Metal, Material};
use minifb::{Key, Window, WindowOptions};
use rayon::prelude::*;
+use vec3::Color;
-use crate::camera::Camera;
-use crate::error::TracerError;
-use crate::geometry::sphere::Sphere;
-use crate::image::Image;
-use crate::ray::Ray;
-use crate::scene::Scene;
-use crate::util::random_double;
-use crate::vec3::Vec3;
-
-fn ray_color(scene: &dyn Hittable, ray: &Ray) -> Vec3 {
- if let Some(hit_record) = scene.hit(ray, 0.0, std::f64::INFINITY) {
+use crate::{
+ camera::Camera,
+ error::TracerError,
+ geometry::sphere::Sphere,
+ geometry::Hittable,
+ image::Image,
+ ray::Ray,
+ scene::Scene,
+ util::random_double,
+ vec3::Vec3,
+ vec3::{random_in_hemisphere, random_unit_vector},
+};
+
+fn ray_color(scene: &dyn Hittable, ray: &Ray, depth: usize) -> Vec3 {
+ if depth == 0 {
+ return Vec3::default();
+ }
+
+ if let Some(rec) = scene.hit(ray, 0.001, std::f64::INFINITY) {
+ if let Some((scattered, attenuation)) = rec.material.scatter(ray, &rec) {
+ return attenuation * ray_color(scene, &scattered, depth - 1);
+ }
+ return Color::default();
+ //let target = rec.point + random_in_hemisphere(&rec.normal);
+ //let target = rec.point + rec.normal + random_unit_vector();
+ //return 0.5 * ray_color(scene, &Ray::new(rec.point, target - rec.point), depth - 1);
//return hit_record.color;
- return 0.5 * (hit_record.normal + Vec3::new(1.0, 1.0, 1.0));
+ //return 0.5 * (hit_record.normal + Vec3::new(1.0, 1.0, 1.0));
}
// TODO: make sky part of scene.
@@ -40,23 +58,70 @@ fn ray_color(scene: &dyn Hittable, ray: &Ray) -> Vec3 {
(1.0 - t) * Vec3::new(1.0, 1.0, 1.0) + t * Vec3::new(0.5, 0.7, 1.0)
}
-fn raytrace(scene: &dyn Hittable, camera: &Camera, image: &Image, row: usize) -> Vec<u32> {
+fn flush(
+ row: usize,
+ width: usize,
+ start: usize,
+ count: usize,
+ samples: usize,
+ source: &[Vec3],
+ dest: &RwLock<Vec<u32>>,
+) {
+ let mut buf = dest
+ .write()
+ .expect("Failed to get write guard when flushing data.");
+
+ for i in 0..count {
+ buf[row * width + start + i] = source[start + i].scale_sqrt(samples).as_color();
+ }
+}
+
+fn raytrace(
+ buffer: &RwLock<Vec<u32>>,
+ scene: &dyn Hittable,
+ camera: &Camera,
+ image: &Image,
+ row: usize,
+ max_depth: usize,
+) {
+ let mut now = Instant::now();
+ let flush_delay = Duration::from_millis(50 + ((random_double() * 2000.0) as u64));
+ let mut flush_start: usize = 0;
let mut colors: Vec<Vec3> = vec![Vec3::default(); image.width as usize];
- colors.iter_mut().enumerate().for_each(|(i, col)| {
+
+ for i in 0..colors.len() {
for _ in 0..image.samples_per_pixel {
let u: f64 = (i as f64 + random_double()) / (image.width - 1) as f64;
let v: f64 = (row as f64 + random_double()) / (image.height - 1) as f64;
- *col += ray_color(scene, &camera.get_ray(u, v));
+ colors[i] += ray_color(scene, &camera.get_ray(u, v), max_depth);
}
- });
- // TODO: Could do rolling average
- let mut buffer: Vec<u32> = vec![0; image.width as usize];
- for i in 0..image.width {
- buffer[i] = (colors[i] / image.samples_per_pixel as f64).as_color();
+ // Update the screen buffer every now and again.
+ if now.elapsed() > flush_delay {
+ now = Instant::now();
+ flush(
+ row,
+ image.width,
+ flush_start,
+ i - flush_start,
+ image.samples_per_pixel,
+ colors.as_slice(),
+ buffer,
+ );
+ flush_start = i;
+ }
}
- buffer
+ // Flush last part of the buffer.
+ flush(
+ row,
+ image.width,
+ flush_start,
+ colors.len() - flush_start,
+ image.samples_per_pixel,
+ colors.as_slice(),
+ buffer,
+ );
}
type Data = (Arc<RwLock<Vec<u32>>>, Arc<Camera>, Arc<Image>, usize);
@@ -66,9 +131,9 @@ fn render(
camera: Arc<Camera>,
image: Arc<Image>,
scene: Box<dyn Hittable + std::marker::Sync>,
+ max_depth: usize,
) {
let scene: &(dyn Hittable + Sync) = scene.borrow();
-
let v: Vec<Data> = (0..image.height)
.map(|row| {
(
@@ -81,28 +146,65 @@ fn render(
.collect();
v.par_iter().for_each(|(buf, camera, image, row)| {
- let start = row * image.width;
- let end = start + image.width;
- let col_buf = raytrace(scene.borrow(), camera, image, *row);
- let mut buf = buf.write().expect("Failed to get screen buffer lock."); // TODO: No except
- buf[start..end].copy_from_slice(col_buf.as_slice());
+ raytrace(buf, scene, camera, image, *row, max_depth);
});
}
+type SharedMaterial = Arc<Box<dyn Material + Send + Sync>>;
fn create_scene() -> Scene {
let mut scene = Scene::new();
- let sphere1 = Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5, Vec3::new(0.0, 1.0, 0.0));
- let sphere2 = Sphere::new(
+ let material_ground: SharedMaterial =
+ Arc::new(Box::new(Lambertian::new(Color::new(0.8, 0.8, 0.0))));
+ let material_center: SharedMaterial =
+ Arc::new(Box::new(Lambertian::new(Color::new(0.7, 0.3, 0.3))));
+ let material_left: SharedMaterial =
+ Arc::new(Box::new(Metal::new(Color::new(0.8, 0.8, 0.8), 0.3)));
+ let material_right: SharedMaterial =
+ Arc::new(Box::new(Metal::new(Color::new(0.8, 0.6, 0.2), 0.1)));
+
+ scene.add(Box::new(Sphere::new(
Vec3::new(0.0, -100.5, -1.0),
100.0,
- Vec3::new(0.0, 1.0, 0.0),
- );
+ Arc::clone(&material_ground),
+ )));
+ scene.add(Box::new(Sphere::new(
+ Vec3::new(0.0, 0.0, -1.0),
+ 0.5,
+ Arc::clone(&material_center),
+ )));
+ scene.add(Box::new(Sphere::new(
+ Vec3::new(-1.0, 0.0, -1.0),
+ 0.5,
+ Arc::clone(&material_left),
+ )));
+ scene.add(Box::new(Sphere::new(
+ Vec3::new(1.0, 0.0, -1.0),
+ 0.5,
+ Arc::clone(&material_right),
+ )));
+ scene
+ // Materials
+ /* let red: Arc<Box<dyn Material + Send + Sync>> =
+ Arc::new(Box::new(Lambertian::new(Color::new(1.0, 0.0, 0.0))));
+ let green: Arc<Box<dyn Material + Send + Sync>> =
+ Arc::new(Box::new(Lambertian::new(Color::new(0.0, 1.0, 0.0))));
+ let metal_red: Arc<Box<dyn Material + Send + Sync>> =
+ Arc::new(Box::new(Metal::new(Color::new(1.0, 0.0, 0.0))));
+
+ // Geometry
+ let sphere1 = Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5, Arc::clone(&metal_red));
+ let sphere2 = Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0, Arc::clone(&green));
scene.add(Box::new(sphere1));
scene.add(Box::new(sphere2));
- scene
+ scene*/
}
-fn run(aspect_ratio: f64, screen_width: usize, samples: usize) -> Result<(), TracerError> {
+fn run(
+ aspect_ratio: f64,
+ screen_width: usize,
+ samples: usize,
+ max_depth: usize,
+) -> Result<(), TracerError> {
let image = Arc::new(image::Image::new(aspect_ratio, screen_width, samples));
let camera = Arc::new(camera::Camera::new(&image, 2.0, 1.0));
let scene: Box<dyn Hittable + Sync + Send> = Box::new(create_scene());
@@ -118,6 +220,7 @@ fn run(aspect_ratio: f64, screen_width: usize, samples: usize) -> Result<(), Tra
camera,
Arc::clone(&image),
scene,
+ max_depth,
)
});
s.spawn(|_| {
@@ -133,10 +236,9 @@ fn run(aspect_ratio: f64, screen_width: usize, samples: usize) -> Result<(), Tra
window
})
.and_then(|mut window| {
- // TODO: Only re-render window then buffer is changed
while window.is_open() && !window.is_key_down(Key::Escape) {
// Sleep a bit to not hog the lock on the buffer all the time.
- std::thread::sleep(std::time::Duration::from_secs(1));
+ std::thread::sleep(std::time::Duration::from_millis(100));
screen_buffer
.read()
@@ -162,7 +264,7 @@ fn run(aspect_ratio: f64, screen_width: usize, samples: usize) -> Result<(), Tra
}
fn main() {
- if let Err(e) = run(16.0 / 9.0, 1200, 10) {
+ if let Err(e) = run(16.0 / 9.0, 1200, 1000, 50) {
eprintln!("{}", e);
std::process::exit(e.into())
}
diff --git a/racer-tracer/src/material.rs b/racer-tracer/src/material.rs
new file mode 100644
index 0000000..13c29d7
--- /dev/null
+++ b/racer-tracer/src/material.rs
@@ -0,0 +1,10 @@
+pub mod lambertian;
+pub mod metal;
+
+use crate::geometry::HitRecord;
+use crate::ray::Ray;
+use crate::vec3::Color;
+
+pub trait Material {
+ fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Ray, Color)>;
+}
diff --git a/racer-tracer/src/material/lambertian.rs b/racer-tracer/src/material/lambertian.rs
new file mode 100644
index 0000000..8356685
--- /dev/null
+++ b/racer-tracer/src/material/lambertian.rs
@@ -0,0 +1,33 @@
+use crate::{
+ material::Material,
+ ray::Ray,
+ vec3::{random_unit_vector, Color, Vec3},
+};
+
+pub struct Lambertian {
+ color: Color,
+}
+
+impl Lambertian {
+ pub fn new(color: Color) -> Self {
+ Self { color }
+ }
+}
+
+impl Material for Lambertian {
+ fn scatter(
+ &self,
+ ray: &crate::ray::Ray,
+ rec: &crate::geometry::HitRecord,
+ ) -> Option<(Ray, Color)> {
+ let mut scatter_direction = rec.normal + random_unit_vector();
+
+ // Catch bad scatter direction
+ if scatter_direction.near_zero() {
+ scatter_direction = rec.normal;
+ }
+
+ let scattered = Ray::new(rec.point, scatter_direction);
+ Some((scattered, self.color))
+ }
+}
diff --git a/racer-tracer/src/material/metal.rs b/racer-tracer/src/material/metal.rs
new file mode 100644
index 0000000..162342d
--- /dev/null
+++ b/racer-tracer/src/material/metal.rs
@@ -0,0 +1,33 @@
+use crate::{
+ material::Material,
+ ray::Ray,
+ vec3::{random_in_unit_sphere, reflect, Color},
+};
+
+pub struct Metal {
+ color: Color,
+ fuzz: f64,
+}
+
+impl Metal {
+ pub fn new(color: Color, fuzz: f64) -> Self {
+ Self { color, fuzz }
+ }
+}
+
+impl Material for Metal {
+ fn scatter(
+ &self,
+ ray: &crate::ray::Ray,
+ rec: &crate::geometry::HitRecord,
+ ) -> Option<(Ray, Color)> {
+ let reflected = reflect(&ray.direction().unit_vector(), &rec.normal);
+ let scattered = Ray::new(rec.point, reflected + self.fuzz * random_in_unit_sphere());
+
+ if scattered.direction().dot(&rec.normal) < 0.0 {
+ None
+ } else {
+ Some((scattered, self.color))
+ }
+ }
+}
diff --git a/racer-tracer/src/util.rs b/racer-tracer/src/util.rs
index 196b82d..dc39e1c 100644
--- a/racer-tracer/src/util.rs
+++ b/racer-tracer/src/util.rs
@@ -10,9 +10,8 @@ pub fn random_double() -> f64 {
rng.gen::<f64>()
}
-/*
// For later use
pub fn random_double_range(min: f64, max: f64) -> f64 {
let mut rng = rand::thread_rng();
rng.gen_range(min..max)
-}*/
+}
diff --git a/racer-tracer/src/vec3.rs b/racer-tracer/src/vec3.rs
index 10f3bfa..e092754 100644
--- a/racer-tracer/src/vec3.rs
+++ b/racer-tracer/src/vec3.rs
@@ -1,10 +1,14 @@
use std::{fmt, ops};
+use crate::util::{random_double, random_double_range};
+
#[derive(Default, Clone, Copy)]
pub struct Vec3 {
data: [f64; 3],
}
+pub type Color = Vec3;
+
impl Vec3 {
pub fn new(x: f64, y: f64, z: f64) -> Vec3 {
Vec3 { data: [x, y, z] }
@@ -48,6 +52,60 @@ impl Vec3 {
let blue: u32 = (self.data[2] * 255.0) as u32;
((red as u32) << 16) | ((green as u32) << 8) | blue as u32
}
+
+ pub fn random() -> Self {
+ Vec3 {
+ data: [random_double(), random_double(), random_double()],
+ }
+ }
+
+ pub fn random_range(min: f64, max: f64) -> Self {
+ Vec3 {
+ data: [
+ random_double_range(min, max),
+ random_double_range(min, max),
+ random_double_range(min, max),
+ ],
+ }
+ }
+
+ pub fn scale_sqrt(mut self, samples: usize) -> Vec3 {
+ let scale = 1.0 / samples as f64;
+ self.data[0] = (scale * self.data[0]).sqrt();
+ self.data[1] = (scale * self.data[1]).sqrt();
+ self.data[2] = (scale * self.data[2]).sqrt();
+ self
+ }
+
+ pub fn near_zero(&self) -> bool {
+ let s = 1e-8;
+ self.data[0].abs() < s && self.data[1].abs() < s && self.data[2].abs() < s
+ }
+}
+
+pub fn reflect(v1: &Vec3, v2: &Vec3) -> Vec3 {
+ v1 - 2.0 * v1.dot(v2) * v2
+}
+
+pub fn random_in_unit_sphere() -> Vec3 {
+ let mut v = Vec3::random_range(-1.0, 1.0);
+ while v.length_squared() >= 1.0 {
+ v = Vec3::random_range(-1.0, 1.0);
+ }
+ v
+}
+
+pub fn random_in_hemisphere(normal: &Vec3) -> Vec3 {
+ let unit_sphere = random_in_unit_sphere();
+ if unit_sphere.dot(normal) > 0.0 {
+ unit_sphere
+ } else {
+ -unit_sphere
+ }
+}
+
+pub fn random_unit_vector() -> Vec3 {
+ random_in_unit_sphere().unit_vector()
}
pub fn dot(v1: &Vec3, v2: &Vec3) -> f64 {