rustracer/src/main.rs

550 lines
15 KiB
Rust

use std::sync::{Arc, Mutex};
use std::time::Instant;
use crate::camera::Camera;
use crate::hittable::{Sphere, HittableList, HittableObject};
use crate::output::{Output, PNG};
use crate::ray::Ray;
use crate::vec3::{Color, Point3, Vec3};
use hittable::MovableSphere;
use rand::Rng;
use rand::distributions::{Distribution, Uniform};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::material::{Dielectric, DiffuseLight, Lambertian, Material, Metal};
use crate::aabb::Aabb;
use crate::cuboid::Cuboid;
use crate::image_texture::ImageTexture;
use crate::noise::NoiseTexture;
use crate::perlin::Perlin;
use crate::texture::CheckerTexture;
use crate::rect::{Plane, Rect2D};
use crate::rotate_y::RotateY;
use crate::translate::Translate;
mod vec3;
mod ray;
mod hittable;
mod material;
mod camera;
mod output;
mod aabb;
mod bvh;
mod texture;
mod perlin;
mod noise;
mod image_texture;
mod rect;
mod cuboid;
mod translate;
mod rotate_y;
// Image
const DEFAULT_ASPECT_RATIO: f64 = 3.0 / 2.0;
const IMAGE_WIDTH: usize = 400;
const SAMPLES_PER_PIXEL: i32 = 100;
const MAX_DEPTH: i32 = 50;
struct Scene {
pub world: HittableList,
pub cam: Camera,
pub background: Color
}
fn cornell_box() -> Scene {
let mut world:HittableList = Vec::new();
let red = Material::Lambertian(
Lambertian::from(Color::new(0.65, 0.05, 0.05)));
let white = Arc::new(Material::Lambertian(
Lambertian::from(Color::new(0.73, 0.73, 0.73))));
let green = Material::Lambertian(
Lambertian::from(Color::new(0.12, 0.45, 0.15)));
let light = Material::DiffuseLight(
DiffuseLight::from(Color::new(15.0, 15.0, 15.0)));
// Walls
world.push(Arc::new(Rect2D::new(
Plane::YZ,
0.0,
555.0,
0.0,
555.0,
555.0,
Arc::new(green)
)));
world.push(Arc::new(Rect2D::new(
Plane::YZ,
0.0,
555.0,
0.0,
555.0,
0.0,
Arc::new(red)
)));
world.push(Arc::new(Rect2D::new(
Plane::XZ,
213.0,
343.0,
227.0,
332.0,
554.0,
Arc::new(light)
)));
world.push(Arc::new(Rect2D::new(
Plane::XZ,
0.0,
555.0,
0.0,
555.0,
0.0,
white.clone()
)));
world.push(Arc::new(Rect2D::new(
Plane::XZ,
0.0,
555.0,
0.0,
555.0,
555.0,
white.clone()
)));
world.push(Arc::new(Rect2D::new(
Plane::XY,
0.0,
555.0,
0.0,
555.0,
555.0,
white.clone()
)));
// Boxes
let rotated1 = RotateY::new(
Cuboid::new(
Point3::new(0.0, 0.0, 0.0),
Point3::new(165.0, 330.0, 165.0),
white.clone()
),
15.0
);
let box1 = Translate::new(
rotated1,
Vec3::new(265.0, 0.0, 295.0)
);
world.push(Arc::new(box1));
let rotated2 = RotateY::new(
Cuboid::new(
Point3::new(0.0, 0.0, 0.0),
Point3::new(165.0, 165.0, 165.0),
white.clone()
),
-18.0
);
let box2 = Translate::new(
rotated2,
Vec3::new(130.0,0.0,65.0)
);
world.push(Arc::new(box2));
let look_from = Point3::new(278.0, 278.0, -800.0);
let look_at = Point3::new(278.0, 278.0, 0.0);
let focus_dist = 2.0;
let cam = Camera::new(
look_from,
look_at,
Vec3::new(0.0, 1.0, 0.0),
1.0,
40.0,
0.0,
focus_dist,
0.0,
1.0);
Scene {
world,
cam,
background: Color::default()
}
}
fn simple_light() -> Scene {
let mut world:HittableList = Vec::new();
let noise = NoiseTexture { noise: Perlin::new(), scale: 4.0 };
let noise_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(noise))));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, -1000.0, 0.0),
radius: 1000.0,
material: Arc::clone(&noise_material)
}));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, 2.0, 0.0),
radius: 2.0,
material: noise_material
}));
let difflight = Material::DiffuseLight(
// Brighter than 1.0/1.0/1.0 so it can light things
DiffuseLight::from(Color::new(4.0, 4.0, 4.0)));
world.push(Arc::new(Rect2D::new(
Plane::XY,
3.0,
5.0,
1.0,
3.0,
-2.0,
Arc::new(difflight)
)));
let look_from = Point3::new(26.0, 3.0, 6.0);
let look_at = Point3::new(0.0, 2.0, 0.0);
let focus_dist = 2.0;
let cam = Camera::new(
look_from,
look_at,
Vec3::new(0.0, 1.0, 0.0),
DEFAULT_ASPECT_RATIO,
20.0,
0.0,
focus_dist,
0.0,
1.0);
Scene {
world,
cam,
background: Color::default()
}
}
fn sun() -> Scene {
let mut world:HittableList = Vec::new();
let earth_material = Arc::new(
Material::DiffuseLight(DiffuseLight::from(Color::new(1.0, 1.0, 0.0))));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, 0.0, 0.0),
radius: 2.0,
material: earth_material
}));
let look_from = Point3::new(13.0, 2.0, 3.0);
let look_at = Point3::new(0.0, 0.0, 0.0);
let focus_dist = 2.0;
let cam = Camera::new(
look_from,
look_at,
Vec3::new(0.0, 1.0, 0.0),
DEFAULT_ASPECT_RATIO,
20.0,
0.0,
focus_dist,
0.0,
1.0);
Scene {
world,
cam,
background: Color::default()
}
}
fn earth() -> Scene {
let mut world:HittableList = Vec::new();
let earth_texture = ImageTexture::new("textures/earthmap.jpg");
let earth_material = Arc::new(
Material::Lambertian(Lambertian::textured(Arc::new(earth_texture))));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, 2.0, 1.0),
radius: 2.0,
material: earth_material
}));
let glass = Arc::new(
Material::Dielectric(Dielectric::new(1.5)));
world.push(Arc::new(Sphere {
center: Point3::new(-0.5,1.0, -1.5),
radius: 1.0,
material: glass
}));
let noise = NoiseTexture { noise: Perlin::new(), scale: 4.0 };
let noise_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(noise))));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, -1000.0, 0.0),
radius: 1000.0,
material: noise_material
}));
let look_from = Point3::new(10.0, 6.0, 3.0);
let look_at = Point3::new(0.0, 0.0, 0.0);
let focus_dist = 2.0;
let cam = Camera::new(
look_from,
look_at,
Vec3::new(0.0, 1.0, 0.0),
DEFAULT_ASPECT_RATIO,
40.0,
0.0,
focus_dist,
0.0,
1.0);
Scene {
world,
cam,
background: Color::new(0.70, 0.80, 1.00)
}
}
fn two_spheres() -> Scene {
let mut world:HittableList = Vec::new();
let checker = CheckerTexture::colored(
Color::new(0.2, 0.3, 0.1),
Color::new(0.9, 0.9, 0.9));
let checker_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(checker))));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, -10.0, 0.0),
radius: 10.0,
material: Arc::clone(&checker_material)
}));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, 10.0, 0.0),
radius: 10.0,
material: checker_material
}));
let look_from = Point3::new(13.0, 2.0, 3.0);
let look_at = Point3::new(0.0, 0.0, 0.0);
let focus_dist = 2.0;
let cam = Camera::new(
look_from,
look_at,
Vec3::new(0.0, 1.0, 0.0),
DEFAULT_ASPECT_RATIO,
20.0,
0.0,
focus_dist,
0.0,
1.0);
Scene {
world,
cam,
background: Color::new(0.70, 0.80, 1.00)
}
}
fn two_perlin_spheres() -> Scene {
let mut world:HittableList = Vec::new();
let noise = NoiseTexture { noise: Perlin::new(), scale: 4.0 };
let noise_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(noise))));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, -1000.0, 0.0),
radius: 1000.0,
material: Arc::clone(&noise_material)
}));
world.push(Arc::new(Sphere {
center: Point3::new(1.0, 2.0, 1.0),
radius: 2.0,
material: noise_material
}));
let look_from = Point3::new(13.0, 2.0, 3.0);
let look_at = Point3::new(0.0, 0.0, 0.0);
let focus_dist = 2.0;
let cam = Camera::new(
look_from,
look_at,
Vec3::new(0.0, 1.0, 0.0),
DEFAULT_ASPECT_RATIO,
20.0,
0.0,
focus_dist,
0.0,
1.0);
Scene {
world,
cam,
background: Color::new(0.70, 0.80, 1.00)
}
}
fn random_scene() -> Scene {
let mut world: HittableList = Vec::new();
let checker = CheckerTexture::colored(
Color::new(0.2, 0.3, 0.1),
Color::new(0.9, 0.9, 0.9));
let material_ground = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(checker))));
let ground = Sphere {
center: Point3::new(0.0, -1000.0, 0.0),
radius: 1000.0,
material: material_ground
};
world.push(Arc::new(ground));
let unit_range = Uniform::from(0.0..1.0);
let fuzz_range = Uniform::from(0.0..0.5);
let mut rng = rand::thread_rng();
let p = Point3::new(4.0, 0.2, 0.0);
for a in -1..11 {
for b in -11..11 {
let choose_material = unit_range.sample(&mut rng);
let center = Point3::new(
(a as f64) + 0.9*unit_range.sample(&mut rng),
0.2,
(b as f64) + 0.9*unit_range.sample(&mut rng));
if (center - p).length() < 0.9 {
continue;
}
let material = match choose_material {
_ if choose_material < 0.8 => Arc::new(Material::Lambertian(Lambertian::from(
Color::random(0.0, 1.0) * Color::random(0.0, 1.0)))),
_ if choose_material < 0.95 => Arc::new(Material::Metal(Metal::new(
Color::random(0.5, 1.0),
fuzz_range.sample(&mut rng)))),
_ => Arc::new(Material::Dielectric(Dielectric::new(1.5))),
};
let sphere: HittableObject = match rng.gen_bool(1.0 / 3.0) {
true => {
let center1 = center + Vec3::new(0.0, fuzz_range.sample(&mut rng) / 2.0, 0.0);
Arc::new(MovableSphere {
center0: center,
center1,
radius: 0.2,
material,
time0: 0.0,
time1: 1.0
})
}
false => Arc::new(Sphere {
center,
radius: 0.2,
material
})
};
world.push(sphere);
}
}
let material1 = Arc::new(Material::Dielectric(Dielectric::new(1.5)));
world.push(Arc::new(Sphere {
center: Point3::new(0.0, 1.0, 0.0),
radius: 1.0,
material: material1
}));
let material2 = Arc::new(Material::Lambertian(Lambertian::from(Color::new(0.4, 0.2, 0.1))));
world.push(Arc::new(Sphere {
center: Point3::new(-4.0, 1.0, 0.0),
radius: 1.0,
material: material2
}));
let material3 = Arc::new(Material::Metal(Metal::new(Color::new(0.7, 0.6, 0.5), 0.0)));
world.push(Arc::new(Sphere {
center: Point3::new(4.0, 1.0, 0.0),
radius: 1.0,
material: material3
}));
let look_from = Point3::new(13.0, 2.0, 3.0);
let look_at = Point3::new(0.0, 0.0, 0.0);
let focus_dist = 2.0;
let cam = Camera::new(
look_from,
look_at,
Vec3::new(0.0, 1.0, 0.0),
DEFAULT_ASPECT_RATIO,
20.0,
0.0,
focus_dist,
0.0,
1.0);
Scene {
world,
cam,
background: Color::new(0.70, 0.80, 1.00)
}
}
fn main() {
// World
let scene: u8 = 5;
let scene_setup = match scene {
0 => two_spheres(),
1 => two_perlin_spheres(),
2 => earth(),
3 => sun(),
4 => simple_light(),
5 => cornell_box(),
_ => random_scene(),
};
let between = Uniform::from(0.0..1.0);
let start = Instant::now();
let image_height: usize = (IMAGE_WIDTH as f64 / scene_setup.cam.aspect_ratio()) as usize;
let mut pixels = vec![0; IMAGE_WIDTH * image_height * 3];
let bands: Vec<(usize, &mut [u8])> = pixels.chunks_mut(3).enumerate().collect();
//rayon::ThreadPoolBuilder::new().num_threads(3).build_global().unwrap(); // Enable, to reduce load
let count = Mutex::new(0);
bands.into_par_iter().for_each(|(i, pixel)| {
let row = image_height - (i / IMAGE_WIDTH) - 1;
let col = i % IMAGE_WIDTH;
let mut rng = rand::thread_rng();
let mut color = Color::default();
(0..SAMPLES_PER_PIXEL).for_each(|_s| {
let random_number = between.sample(&mut rng);
let u = (col as f64 + random_number) / (IMAGE_WIDTH - 1) as f64;
let v = (row as f64 + random_number) / (image_height - 1) as f64;
let ray = scene_setup.cam.get_ray(u, v);
color += ray.pixel_color(scene_setup.background, &scene_setup.world, MAX_DEPTH);
});
let bytes = color.into_bytes(SAMPLES_PER_PIXEL);
pixel[0] = bytes[0];
pixel[1] = bytes[1];
pixel[2] = bytes[2];
if i % 100 == 0 {
let mut rem = count.lock().unwrap();
let percent_done_before = 100 * *rem / (IMAGE_WIDTH * image_height);
*rem += 100;
let percent_done_after = 100 * *rem / (IMAGE_WIDTH * image_height);
if percent_done_before != percent_done_after {
eprint!("\rProgress: {}% ", percent_done_after);
}
}
});
PNG::write("imc.png", &pixels, IMAGE_WIDTH, image_height).expect("Error writing image: {}");
eprintln!("\nDone. Time: {}ms", start.elapsed().as_millis());
}
fn world_bounding_box(time0: f64, time1: f64, world: &HittableList) -> Option<Aabb> {
if world.is_empty() {
return None;
}
let mut is_first = true;
let mut output_box: Aabb = Aabb {
minimum: Point3::default(),
maximum: Point3::default()
};
for object in world {
if let Some(bb) = object.bounding_box(time0, time1) {
output_box = match is_first {
true => bb,
false => output_box.surrounding(&bb)
};
is_first = false;
} else {
return None;
}
}
Some(output_box)
}