wip: parallelization
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1240,6 +1240,7 @@ dependencies = [
|
|||||||
"ops",
|
"ops",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rand 0.10.1",
|
"rand 0.10.1",
|
||||||
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
image = "0.25.10"
|
image = {version = "0.25.10", features = ["png"]}
|
||||||
is_close = "0.1.3"
|
is_close = "0.1.3"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
ops = "0.6.0"
|
ops = "0.6.0"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
|
rayon = "1.12.0"
|
||||||
serde = {version = "1.0.228", features = ["derive"]}
|
serde = {version = "1.0.228", features = ["derive"]}
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"camera": {
|
|
||||||
"image_width": 1920,
|
|
||||||
"image_height": 1080,
|
|
||||||
"anti_alias_rate": 1,
|
|
||||||
"max_depth": 10,
|
|
||||||
"fov": 90.0,
|
|
||||||
"look_from": { "x": -10, "y": 5, "z": 10 },
|
|
||||||
"look_at": { "x": 0.0, "y": 0.0, "z": -1.0 },
|
|
||||||
"vup": { "x": 0.0, "y": 1.0, "z": 0.0 }
|
|
||||||
},
|
|
||||||
"materials": [
|
|
||||||
{ "type": "metal", "albedo": { "x": 0.2, "y": 0.4, "z": 0.8 }, "prob": 1.0, "fuzz": 0.1 }
|
|
||||||
|
|
||||||
],
|
|
||||||
"objects": [
|
|
||||||
{ "type": "sphere", "center": { "x": 0, "y": 0.7, "z": -0.4 }, "radius": 0.2, "material": 1}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"filename": "output.png",
|
||||||
|
"image_width": 1920,
|
||||||
|
"image_height": 1080,
|
||||||
|
"max_depth": 50,
|
||||||
"camera": {
|
"camera": {
|
||||||
"image_width": 1920,
|
"anti_alias_rate": 23,
|
||||||
"image_height": 1080,
|
"fov": 70.0,
|
||||||
"anti_alias_rate": 2,
|
"look_from": [-10, 4, 15],
|
||||||
"max_depth": 50,
|
"look_at": [-11.0, 0.0, 0.0],
|
||||||
"fov": 90.0,
|
|
||||||
"look_from": [15, 4, 15],
|
|
||||||
"look_at": [0.0, 0.0, 0.0],
|
|
||||||
"vup": [0.0, 1.0, 0.0],
|
"vup": [0.0, 1.0, 0.0],
|
||||||
"defocus_angle": 0,
|
"defocus_angle": 0,
|
||||||
"focus_dist": 15.68
|
"focus_dist": 15.68
|
||||||
@@ -28,7 +29,8 @@
|
|||||||
{ "type": "quad", "p1": [-20, -1, 20], "p2": [-20, -1, -20], "p3": [-20, 20, -20], "p4": [-20, 20, 20], "material": 0},
|
{ "type": "quad", "p1": [-20, -1, 20], "p2": [-20, -1, -20], "p3": [-20, 20, -20], "p4": [-20, 20, 20], "material": 0},
|
||||||
{ "type": "quad", "p1": [-20, -1, 20], "p2": [20, -1, 20], "p3": [20, -1, -20], "p4": [-20, -1, -20], "material": 0},
|
{ "type": "quad", "p1": [-20, -1, 20], "p2": [20, -1, 20], "p3": [20, -1, -20], "p4": [-20, -1, -20], "material": 0},
|
||||||
{ "type": "quad", "p1": [20, -1, 20], "p2": [20, -1, -20], "p3": [20, 20, -20], "p4": [20, 20, 20], "material": 0},
|
{ "type": "quad", "p1": [20, -1, 20], "p2": [20, -1, -20], "p3": [20, 20, -20], "p4": [20, 20, 20], "material": 0},
|
||||||
{ "type": "cube", "p1": [8, 0, 2], "p2": [12, 0, 2], "p3": [12, 4, 2], "p4": [8, 4, 2], "p5": [8, 0, -2], "p6": [12, 0, -2], "p7": [12, 4, -2], "p8": [8, 4, -2], "material": 3}
|
{ "type": "cube", "p1": [8, 0, 2], "p2": [12, 0, 2], "p3": [12, 4, 2], "p4": [8, 4, 2], "p5": [8, 0, -2], "p6": [12, 0, -2], "p7": [12, 4, -2], "p8": [8, 4, -2], "material": 3},
|
||||||
|
{ "type": "circle", "center": [-9, 3, 0], "radius": 3, "normal": [0, 1, 0.5], "material": {"type": "metal", "albedo": [0.9, 0.9, 0.9], "prob": 1.0, "fuzz": 0.3}}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
142
src/camera.rs
142
src/camera.rs
@@ -1,34 +1,23 @@
|
|||||||
use std::{f32::consts::PI, sync::Arc};
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use log::info;
|
use crate::{ray::Ray, vec3::Vec3};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
objects::{hit::Hit, traits::Hittable},
|
|
||||||
ray::Ray,
|
|
||||||
vec3::{Colour, Vec3},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
// output
|
|
||||||
image_width: u32,
|
|
||||||
image_height: u32,
|
|
||||||
|
|
||||||
// raytracing
|
// raytracing
|
||||||
anti_alias_rate: u32,
|
pub anti_alias_rate: u32,
|
||||||
max_depth: u32,
|
pub pixel00_loc: Vec3,
|
||||||
pixel00_loc: Vec3,
|
pub pixel_delta_u: Vec3,
|
||||||
pixel_delta_u: Vec3,
|
pub pixel_delta_v: Vec3,
|
||||||
pixel_delta_v: Vec3,
|
|
||||||
|
|
||||||
// camera
|
// camera
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
fov: f32,
|
pub fov: f32,
|
||||||
look_from: Vec3,
|
pub look_from: Vec3,
|
||||||
look_at: Vec3,
|
pub look_at: Vec3,
|
||||||
vup: Vec3,
|
vup: Vec3,
|
||||||
defocus_angle: f32,
|
pub defocus_angle: f32,
|
||||||
focus_dist: f32,
|
pub focus_dist: f32,
|
||||||
|
|
||||||
// camera helpers
|
// camera helpers
|
||||||
u: Vec3,
|
u: Vec3,
|
||||||
@@ -44,12 +33,9 @@ fn deg_to_rad(deg: f32) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Camera {
|
impl Camera {
|
||||||
pub fn new(image_width: u32, image_height: u32) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
image_width,
|
|
||||||
image_height,
|
|
||||||
anti_alias_rate: 1,
|
anti_alias_rate: 1,
|
||||||
max_depth: 10,
|
|
||||||
dirty: true,
|
dirty: true,
|
||||||
fov: 60.,
|
fov: 60.,
|
||||||
pixel00_loc: Vec3::default(),
|
pixel00_loc: Vec3::default(),
|
||||||
@@ -69,10 +55,7 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_full(
|
pub fn new_full(
|
||||||
image_width: u32,
|
|
||||||
image_height: u32,
|
|
||||||
anti_alias_rate: u32,
|
anti_alias_rate: u32,
|
||||||
max_depth: u32,
|
|
||||||
fov: f32,
|
fov: f32,
|
||||||
look_from: Vec3,
|
look_from: Vec3,
|
||||||
look_at: Vec3,
|
look_at: Vec3,
|
||||||
@@ -81,10 +64,7 @@ impl Camera {
|
|||||||
focus_dist: f32,
|
focus_dist: f32,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
image_width,
|
|
||||||
image_height,
|
|
||||||
anti_alias_rate,
|
anti_alias_rate,
|
||||||
max_depth,
|
|
||||||
pixel00_loc: Vec3::default(),
|
pixel00_loc: Vec3::default(),
|
||||||
pixel_delta_u: Vec3::default(),
|
pixel_delta_u: Vec3::default(),
|
||||||
pixel_delta_v: Vec3::default(),
|
pixel_delta_v: Vec3::default(),
|
||||||
@@ -103,7 +83,11 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self) {
|
pub fn init(&mut self, w: f32, hi: f32) {
|
||||||
|
if !self.dirty {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// camera
|
// camera
|
||||||
let theta = deg_to_rad(self.fov);
|
let theta = deg_to_rad(self.fov);
|
||||||
let h = (theta / 2.).tan();
|
let h = (theta / 2.).tan();
|
||||||
@@ -113,13 +97,13 @@ impl Camera {
|
|||||||
|
|
||||||
// viewport
|
// viewport
|
||||||
let viewport_height = 2. * h * self.focus_dist;
|
let viewport_height = 2. * h * self.focus_dist;
|
||||||
let viewport_width = viewport_height * (self.image_width as f32 / self.image_height as f32);
|
let viewport_width = viewport_height * (w / hi);
|
||||||
let viewport_u = viewport_width * self.u;
|
let viewport_u = viewport_width * self.u;
|
||||||
let viewport_v = viewport_height * -self.v;
|
let viewport_v = viewport_height * -self.v;
|
||||||
|
|
||||||
// variables
|
// variables
|
||||||
self.pixel_delta_u = viewport_u / self.image_width as f32;
|
self.pixel_delta_u = viewport_u / w;
|
||||||
self.pixel_delta_v = viewport_v / self.image_height as f32;
|
self.pixel_delta_v = viewport_v / hi;
|
||||||
self.pixel00_loc =
|
self.pixel00_loc =
|
||||||
self.look_from - (self.focus_dist * self.w) - viewport_u / 2. - viewport_v / 2.;
|
self.look_from - (self.focus_dist * self.w) - viewport_u / 2. - viewport_v / 2.;
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
@@ -131,6 +115,19 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ray(&self, pixel_tl: Vec3, x: u32, y: u32) -> Ray {
|
||||||
|
let pixel_loc = pixel_tl
|
||||||
|
+ (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32)
|
||||||
|
+ (y * self.pixel_delta_v / (self.anti_alias_rate + 1) as f32);
|
||||||
|
let ray_orig = if self.defocus_angle > 0. {
|
||||||
|
self.defocus_disk_sample()
|
||||||
|
} else {
|
||||||
|
self.look_from
|
||||||
|
};
|
||||||
|
let ray_dir = pixel_loc - ray_orig;
|
||||||
|
Ray::new(ray_orig, ray_dir)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_fov(&mut self, fov: f32) {
|
pub fn set_fov(&mut self, fov: f32) {
|
||||||
if self.fov != fov {
|
if self.fov != fov {
|
||||||
self.fov = fov;
|
self.fov = fov;
|
||||||
@@ -138,13 +135,6 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_max_depth(&mut self, depth: u32) {
|
|
||||||
if self.max_depth != depth {
|
|
||||||
self.max_depth = depth;
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_anti_alias_rate(&mut self, rate: u32) {
|
pub fn set_anti_alias_rate(&mut self, rate: u32) {
|
||||||
if self.anti_alias_rate != rate {
|
if self.anti_alias_rate != rate {
|
||||||
self.anti_alias_rate = rate;
|
self.anti_alias_rate = rate;
|
||||||
@@ -181,72 +171,8 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn defocus_disk_sample(&self) -> Vec3 {
|
pub fn defocus_disk_sample(&self) -> Vec3 {
|
||||||
let p = Vec3::random_in_unit_disk();
|
let p = Vec3::random_in_unit_disk();
|
||||||
self.look_from + (p.x() * self.defocus_disk_u) + (p.y() * self.defocus_disk_v)
|
self.look_from + (p.x() * self.defocus_disk_u) + (p.y() * self.defocus_disk_v)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, hittables: &Vec<Arc<dyn Hittable>>) {
|
|
||||||
if self.dirty {
|
|
||||||
self.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut imgbuf = image::ImageBuffer::new(self.image_width, self.image_height);
|
|
||||||
|
|
||||||
// render
|
|
||||||
for j in 0..self.image_height {
|
|
||||||
info!("{}\tScanlines remaining.", (self.image_height - j));
|
|
||||||
for i in 0..self.image_width {
|
|
||||||
let pixel_tl =
|
|
||||||
self.pixel00_loc + (i * self.pixel_delta_u) + (j * self.pixel_delta_v);
|
|
||||||
|
|
||||||
let mut pixel_colour = Colour::default();
|
|
||||||
for y in 1..(self.anti_alias_rate + 1) {
|
|
||||||
for x in 1..(self.anti_alias_rate + 1) {
|
|
||||||
let pixel_loc = pixel_tl
|
|
||||||
+ (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32)
|
|
||||||
+ (y * self.pixel_delta_v / (self.anti_alias_rate + 1) as f32);
|
|
||||||
let ray_orig = if self.defocus_angle > 0. {
|
|
||||||
self.defocus_disk_sample()
|
|
||||||
} else {
|
|
||||||
self.look_from
|
|
||||||
};
|
|
||||||
let ray_dir = pixel_loc - ray_orig;
|
|
||||||
let r = Ray::new(ray_orig, ray_dir);
|
|
||||||
pixel_colour += self.ray_colour(hittables, &r, self.max_depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pixel = imgbuf.get_pixel_mut(i, j);
|
|
||||||
*pixel =
|
|
||||||
(pixel_colour / (self.anti_alias_rate * self.anti_alias_rate) as f32).output();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Writing image file...");
|
|
||||||
imgbuf.save("output.png").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ray_colour(&self, hittables: &Vec<Arc<dyn Hittable>>, r: &Ray, depth: u32) -> Colour {
|
|
||||||
if depth == 0 {
|
|
||||||
return Colour::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
let closest = Hit::hit_list(hittables, r);
|
|
||||||
if let Some(hit) = closest {
|
|
||||||
if let Some((scattered, att)) = hit.mat().scatter(&hit, r) {
|
|
||||||
if let Some(ray) = scattered {
|
|
||||||
return att * self.ray_colour(hittables, &ray, depth - 1);
|
|
||||||
}
|
|
||||||
return att;
|
|
||||||
}
|
|
||||||
return Colour::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
// background
|
|
||||||
let unit_dir = r.dir().get_unit();
|
|
||||||
let a = 0.5 * (unit_dir.y() + 1.);
|
|
||||||
|
|
||||||
(1.0 - a) * Colour::new(1., 1., 1.) + a * Colour::new(0.5, 0.7, 1.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main.rs
10
src/main.rs
@@ -3,6 +3,7 @@
|
|||||||
mod camera;
|
mod camera;
|
||||||
mod objects;
|
mod objects;
|
||||||
mod ray;
|
mod ray;
|
||||||
|
mod raytracer;
|
||||||
mod scenes;
|
mod scenes;
|
||||||
mod vec3;
|
mod vec3;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ use crate::objects::materials::lambertian::{Lambertian, Metal};
|
|||||||
use crate::objects::sphere::Sphere;
|
use crate::objects::sphere::Sphere;
|
||||||
use crate::objects::traits::Hittable;
|
use crate::objects::traits::Hittable;
|
||||||
use crate::ray::Ray;
|
use crate::ray::Ray;
|
||||||
|
use crate::raytracer::render;
|
||||||
use crate::scenes::scene::Scene;
|
use crate::scenes::scene::Scene;
|
||||||
use crate::vec3::Vec3;
|
use crate::vec3::Vec3;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
@@ -29,7 +31,7 @@ fn main() {
|
|||||||
let json_file = "./scenes/scene.json";
|
let json_file = "./scenes/scene.json";
|
||||||
let json_str = fs::read_to_string(json_file).expect("Reading specified scene file failed!");
|
let json_str = fs::read_to_string(json_file).expect("Reading specified scene file failed!");
|
||||||
let mut scene: Scene = serde_json::from_str(&json_str).unwrap();
|
let mut scene: Scene = serde_json::from_str(&json_str).unwrap();
|
||||||
scene.render();
|
render(&mut scene);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// random spheres code; thought: make this available as cli flag?
|
// random spheres code; thought: make this available as cli flag?
|
||||||
@@ -108,14 +110,14 @@ fn main() {
|
|||||||
Arc::new(Metal::rgb(0.7, 0.6, 0.5, 1., 0.)),
|
Arc::new(Metal::rgb(0.7, 0.6, 0.5, 1., 0.)),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
let mut c = Camera::new(1920, 1080);
|
let mut c = Camera::new();
|
||||||
c.set_fov(20.);
|
c.set_fov(20.);
|
||||||
c.set_anti_alias_rate(23);
|
c.set_anti_alias_rate(23);
|
||||||
c.set_max_depth(50);
|
|
||||||
c.set_vup(Vec3::new(0., 1., 0.));
|
c.set_vup(Vec3::new(0., 1., 0.));
|
||||||
c.set_look_from(Vec3::new(13., 2., 3.));
|
c.set_look_from(Vec3::new(13., 2., 3.));
|
||||||
c.set_look_at(Vec3::new(0., 0., 0.));
|
c.set_look_at(Vec3::new(0., 0., 0.));
|
||||||
c.add_defocus_blur(0.6, 10.);
|
c.add_defocus_blur(0.6, 10.);
|
||||||
|
|
||||||
c.render(&world);
|
let mut s = Scene::new(c, world, "output.png".to_string(), 1920, 1080, 50);
|
||||||
|
render(&mut s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
pub mod circle;
|
||||||
|
pub mod cube;
|
||||||
|
pub mod cylinder;
|
||||||
pub mod hit;
|
pub mod hit;
|
||||||
pub mod materials;
|
pub mod materials;
|
||||||
pub mod sphere;
|
pub mod mesh;
|
||||||
pub mod triangle;
|
|
||||||
pub mod quad;
|
pub mod quad;
|
||||||
pub mod cube;
|
pub mod sphere;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
pub mod triangle;
|
||||||
|
|||||||
52
src/objects/circle.rs
Normal file
52
src/objects/circle.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
objects::{materials::traits::Material, traits::Hittable},
|
||||||
|
ray::Ray,
|
||||||
|
vec3::Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::hit::Hit;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Circle {
|
||||||
|
radius: f32,
|
||||||
|
center: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
material: Arc<dyn Material>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Circle {
|
||||||
|
pub fn new(radius: f32, center: Vec3, normal: Vec3, material: Arc<dyn Material>) -> Self {
|
||||||
|
Self {
|
||||||
|
radius,
|
||||||
|
center,
|
||||||
|
normal: normal.get_unit(),
|
||||||
|
material,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hittable for Circle {
|
||||||
|
fn hit(&self, r: &Ray) -> Option<Hit> {
|
||||||
|
// check if ray parallel to plane
|
||||||
|
let dot = self.normal.dot(r.dir());
|
||||||
|
if dot == 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hitpoint on plane
|
||||||
|
let t = self.normal.dot(&(self.center - r.origin())) / dot;
|
||||||
|
|
||||||
|
// hits behind camera
|
||||||
|
if t < 0. {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let p = r.at(t);
|
||||||
|
if (p - self.center).length() < self.radius {
|
||||||
|
return Some(Hit::new(t, p, self.normal, self.material.clone(), self.normal.dot(&r.dir()) < 0.));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,9 +41,4 @@ impl Hittable for Cube {
|
|||||||
fn hit(&self, r: &Ray) -> Option<Hit> {
|
fn hit(&self, r: &Ray) -> Option<Hit> {
|
||||||
Hit::hit_list(&self.faces, r)
|
Hit::hit_list(&self.faces, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normal_at(&self, _p: &Vec3) -> Vec3 {
|
|
||||||
// TODO: normal calc for cube
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/objects/cylinder.rs
Normal file
26
src/objects/cylinder.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use crate::{objects::traits::Hittable, vec3::Vec3};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cylinder {
|
||||||
|
radius: f32,
|
||||||
|
length: f32,
|
||||||
|
up: Vec3,
|
||||||
|
bottom_center: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cylinder {
|
||||||
|
pub fn new(radius: f32, length: f32, up: Vec3, bottom_center: Vec3) -> Self {
|
||||||
|
Self {
|
||||||
|
radius,
|
||||||
|
length,
|
||||||
|
up,
|
||||||
|
bottom_center,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hittable for Cylinder {
|
||||||
|
fn hit(&self, r: &crate::ray::Ray) -> Option<super::hit::Hit> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,7 @@ pub struct Dielectric {
|
|||||||
|
|
||||||
impl Dielectric {
|
impl Dielectric {
|
||||||
pub fn new(refraction_index: f32) -> Self {
|
pub fn new(refraction_index: f32) -> Self {
|
||||||
Self {
|
Self { refraction_index }
|
||||||
refraction_index,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reflectance(cos: f32, refraction_index: f32) -> f32 {
|
fn reflectance(cos: f32, refraction_index: f32) -> f32 {
|
||||||
@@ -46,7 +44,8 @@ impl Material for Dielectric {
|
|||||||
let cannot_refract = ri * sin_theta > 1.;
|
let cannot_refract = ri * sin_theta > 1.;
|
||||||
|
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
let dir = if cannot_refract || Dielectric::reflectance(cos_theta, ri) > rng.random::<f32>() {
|
let dir = if cannot_refract || Dielectric::reflectance(cos_theta, ri) > rng.random::<f32>()
|
||||||
|
{
|
||||||
unit.reflect(hit.n())
|
unit.reflect(hit.n())
|
||||||
} else {
|
} else {
|
||||||
unit.refract(hit.n(), ri)
|
unit.refract(hit.n(), ri)
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ pub struct Lambertian {
|
|||||||
|
|
||||||
impl Lambertian {
|
impl Lambertian {
|
||||||
pub fn new(albedo: Colour, prob: f32) -> Self {
|
pub fn new(albedo: Colour, prob: f32) -> Self {
|
||||||
Self {
|
Self { albedo, prob }
|
||||||
albedo,
|
|
||||||
prob,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rgb(r: f32, g: f32, b: f32, prob: f32) -> Self {
|
pub fn rgb(r: f32, g: f32, b: f32, prob: f32) -> Self {
|
||||||
@@ -53,11 +50,7 @@ pub struct Metal {
|
|||||||
|
|
||||||
impl Metal {
|
impl Metal {
|
||||||
pub fn new(albedo: Colour, prob: f32, fuzz: f32) -> Self {
|
pub fn new(albedo: Colour, prob: f32, fuzz: f32) -> Self {
|
||||||
Self {
|
Self { albedo, prob, fuzz }
|
||||||
albedo,
|
|
||||||
prob,
|
|
||||||
fuzz,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rgb(r: f32, g: f32, b: f32, prob: f32, fuzz: f32) -> Self {
|
pub fn rgb(r: f32, g: f32, b: f32, prob: f32, fuzz: f32) -> Self {
|
||||||
|
|||||||
0
src/objects/mesh.rs
Normal file
0
src/objects/mesh.rs
Normal file
@@ -69,9 +69,4 @@ impl Hittable for Quad {
|
|||||||
r,
|
r,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normal_at(&self, _p: &Vec3) -> Vec3 {
|
|
||||||
// FIXME: might cause ownership issues
|
|
||||||
self.normal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ use core::f32::math::sqrt;
|
|||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::Vec3;
|
||||||
use crate::objects::hit::Hit;
|
use crate::objects::hit::Hit;
|
||||||
use crate::objects::materials::traits::Material;
|
use crate::objects::materials::traits::Material;
|
||||||
use crate::objects::traits::Hittable;
|
use crate::objects::traits::Hittable;
|
||||||
use crate::ray::Ray;
|
use crate::ray::Ray;
|
||||||
use crate::Vec3;
|
|
||||||
|
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
center: Vec3,
|
center: Vec3,
|
||||||
@@ -63,14 +62,10 @@ impl Hittable for Sphere {
|
|||||||
Some(Hit::new(
|
Some(Hit::new(
|
||||||
t,
|
t,
|
||||||
p,
|
p,
|
||||||
self.normal_at(&p),
|
(p - self.center).get_unit(),
|
||||||
self.material.clone(),
|
self.material.clone(),
|
||||||
out_n.dot(r.dir()) < 0.,
|
out_n.dot(r.dir()) < 0.,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normal_at(&self, p: &Vec3) -> Vec3 {
|
|
||||||
(*p - self.center).get_unit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::objects::hit::Hit;
|
|
||||||
use crate::Ray;
|
use crate::Ray;
|
||||||
use crate::Vec3;
|
use crate::objects::hit::Hit;
|
||||||
|
|
||||||
pub trait Hittable: Debug + Send + Sync {
|
pub trait Hittable: Debug + Send + Sync {
|
||||||
fn hit(&self, r: &Ray) -> Option<Hit>;
|
fn hit(&self, r: &Ray) -> Option<Hit>;
|
||||||
fn normal_at(&self, p: &Vec3) -> Vec3;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ impl Triangle {
|
|||||||
|
|
||||||
let diff = (a4 - a1 - a2 - a3).abs();
|
let diff = (a4 - a1 - a2 - a3).abs();
|
||||||
if diff < 0.001 {
|
if diff < 0.001 {
|
||||||
Some(Hit::new(t, p, normal, material, normal.dot(&-r.dir()) > 0.))
|
Some(Hit::new(t, p, normal, material, normal.dot(&r.dir()) < 0.))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -83,9 +83,4 @@ impl Hittable for Triangle {
|
|||||||
r,
|
r,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normal_at(&self, _p: &Vec3) -> Vec3 {
|
|
||||||
// FIXME: might cause ownership issues
|
|
||||||
self.normal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ pub struct Ray {
|
|||||||
|
|
||||||
impl Ray {
|
impl Ray {
|
||||||
pub fn new(origin: Vec3, dir: Vec3) -> Self {
|
pub fn new(origin: Vec3, dir: Vec3) -> Self {
|
||||||
Self {
|
Self { origin, dir }
|
||||||
origin,
|
|
||||||
dir,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn at(&self, t: f32) -> Vec3 {
|
pub fn at(&self, t: f32) -> Vec3 {
|
||||||
|
|||||||
96
src/raytracer.rs
Normal file
96
src/raytracer.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
|
||||||
|
use log::{error, info};
|
||||||
|
use rayon::iter::IntoParallelIterator;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::{fs::File, sync::Arc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
objects::{hit::Hit, traits::Hittable},
|
||||||
|
ray::Ray,
|
||||||
|
scenes::scene::Scene,
|
||||||
|
vec3::Colour,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn write_image(filename: &str, pixels: &[u8], width: u32, height: u32) {
|
||||||
|
match File::create(filename) {
|
||||||
|
Ok(out) => {
|
||||||
|
let enc = PngEncoder::new(out);
|
||||||
|
if let Err(e) = enc.write_image(pixels, width, height, ExtendedColorType::Rgb8) {
|
||||||
|
error!("Failed writing image: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => error!("Failed to create image file: {} ", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(scene: &mut Scene) {
|
||||||
|
let camera = &mut scene.camera;
|
||||||
|
camera.init(scene.image_width as f32, scene.image_height as f32);
|
||||||
|
|
||||||
|
// TODO: currently splits per vertical line, but could be more granular (per chunk)
|
||||||
|
let mut pixels = vec![0 as u8; (scene.image_width * scene.image_height * 3) as usize];
|
||||||
|
let scanlines: Vec<(usize, &mut [u8])> = pixels
|
||||||
|
.chunks_mut(scene.image_width as usize * 3)
|
||||||
|
.enumerate()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
scanlines.into_par_iter().for_each(|(i, chunk)| {
|
||||||
|
render_chunk(chunk, scene, i as u32);
|
||||||
|
});
|
||||||
|
|
||||||
|
info!("Writing image file...");
|
||||||
|
write_image(
|
||||||
|
&scene.filename,
|
||||||
|
&pixels,
|
||||||
|
scene.image_width,
|
||||||
|
scene.image_height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_chunk(chunk: &mut [u8], scene: &Scene, y: u32) {
|
||||||
|
let camera = &scene.camera;
|
||||||
|
|
||||||
|
for i in 0..scene.image_width {
|
||||||
|
let pixel_tl = camera.pixel00_loc + (i * camera.pixel_delta_u) + (y * camera.pixel_delta_v);
|
||||||
|
// THOUGHT: the above seems more efficient (esp for larger aa rates) but can test for a
|
||||||
|
// get_ray_no_tl() function and compare performance.
|
||||||
|
|
||||||
|
let mut pixel_colour = Colour::default();
|
||||||
|
for y in 1..(camera.anti_alias_rate + 1) {
|
||||||
|
for x in 1..(camera.anti_alias_rate + 1) {
|
||||||
|
let r = camera.get_ray(pixel_tl, x, y);
|
||||||
|
pixel_colour += ray_colour(&scene.objects, &r, scene.max_depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (r, g, b) =
|
||||||
|
(pixel_colour / (camera.anti_alias_rate * camera.anti_alias_rate) as f32).output();
|
||||||
|
chunk[(i * 3) as usize] = r;
|
||||||
|
chunk[(i * 3) as usize + 1] = g;
|
||||||
|
chunk[(i * 3) as usize + 2] = b;
|
||||||
|
}
|
||||||
|
info!("Line {} finished.", y);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ray_colour(hittables: &Vec<Arc<dyn Hittable>>, r: &Ray, depth: u32) -> Colour {
|
||||||
|
if depth == 0 {
|
||||||
|
return Colour::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let closest = Hit::hit_list(hittables, r);
|
||||||
|
if let Some(hit) = closest {
|
||||||
|
if let Some((scattered, att)) = hit.mat().scatter(&hit, r) {
|
||||||
|
if let Some(ray) = scattered {
|
||||||
|
return att * ray_colour(hittables, &ray, depth - 1);
|
||||||
|
}
|
||||||
|
return att;
|
||||||
|
}
|
||||||
|
return Colour::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
// background
|
||||||
|
let unit_dir = r.dir().get_unit();
|
||||||
|
let a = 0.5 * (unit_dir.y() + 1.);
|
||||||
|
|
||||||
|
(1.0 - a) * Colour::new(1., 1., 1.) + a * Colour::new(0.5, 0.7, 1.0)
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ use serde::Deserialize;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::{
|
objects::{
|
||||||
cube::Cube, materials::traits::Material, quad::Quad, sphere::Sphere, traits::Hittable,
|
circle::Circle, cube::Cube, materials::traits::Material, quad::Quad, sphere::Sphere,
|
||||||
triangle::Triangle,
|
traits::Hittable, triangle::Triangle,
|
||||||
},
|
},
|
||||||
scenes::material_def::MaterialDef,
|
scenes::material_def::MaterialDef,
|
||||||
vec3::Vec3,
|
vec3::Vec3,
|
||||||
@@ -19,11 +19,11 @@ pub(crate) enum HittableDef {
|
|||||||
Triangle(RawTriangle),
|
Triangle(RawTriangle),
|
||||||
Quad(RawQuad),
|
Quad(RawQuad),
|
||||||
Cube(RawCube),
|
Cube(RawCube),
|
||||||
|
Circle(RawCircle),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HittableDef {
|
impl HittableDef {
|
||||||
pub(crate) fn into_arc(self, materials: &Vec<Arc<dyn Material>>) -> Option<Arc<dyn Hittable>> {
|
pub(crate) fn into_arc(self, materials: &Vec<Arc<dyn Material>>) -> Option<Arc<dyn Hittable>> {
|
||||||
// THOUGHT: i think this can be done better; in the map/filter call in scnene.deserialize?
|
|
||||||
match self {
|
match self {
|
||||||
HittableDef::Sphere(s) => {
|
HittableDef::Sphere(s) => {
|
||||||
let material = s.material.into_arc(materials);
|
let material = s.material.into_arc(materials);
|
||||||
@@ -55,6 +55,13 @@ impl HittableDef {
|
|||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HittableDef::Circle(c) => {
|
||||||
|
let material = c.material.into_arc(materials);
|
||||||
|
match material {
|
||||||
|
Some(m) => Some(Arc::new(Circle::new(c.radius, c.center, c.normal, m))),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,6 +103,14 @@ pub(crate) struct RawCube {
|
|||||||
pub material: RawMaterial,
|
pub material: RawMaterial,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub(crate) struct RawCircle {
|
||||||
|
radius: f32,
|
||||||
|
center: Vec3,
|
||||||
|
normal: Vec3,
|
||||||
|
material: RawMaterial,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub(crate) enum RawMaterial {
|
pub(crate) enum RawMaterial {
|
||||||
|
|||||||
@@ -4,15 +4,9 @@ use crate::{camera::Camera, vec3::Vec3};
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct RawCamera {
|
pub struct RawCamera {
|
||||||
// output
|
|
||||||
image_width: u32,
|
|
||||||
image_height: u32,
|
|
||||||
|
|
||||||
// raytracing
|
// raytracing
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
anti_alias_rate: u32,
|
anti_alias_rate: u32,
|
||||||
#[serde(default)]
|
|
||||||
max_depth: u32,
|
|
||||||
|
|
||||||
// camera
|
// camera
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -32,10 +26,7 @@ pub struct RawCamera {
|
|||||||
impl Default for RawCamera {
|
impl Default for RawCamera {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
image_width: 400,
|
|
||||||
image_height: 300,
|
|
||||||
anti_alias_rate: 1,
|
anti_alias_rate: 1,
|
||||||
max_depth: 10,
|
|
||||||
fov: 70.,
|
fov: 70.,
|
||||||
look_from: Vec3::new(0., 0., 0.),
|
look_from: Vec3::new(0., 0., 0.),
|
||||||
look_at: Vec3::new(0., 0., -1.),
|
look_at: Vec3::new(0., 0., -1.),
|
||||||
@@ -49,10 +40,7 @@ impl Default for RawCamera {
|
|||||||
impl From<RawCamera> for Camera {
|
impl From<RawCamera> for Camera {
|
||||||
fn from(raw: RawCamera) -> Self {
|
fn from(raw: RawCamera) -> Self {
|
||||||
Camera::new_full(
|
Camera::new_full(
|
||||||
raw.image_width,
|
|
||||||
raw.image_height,
|
|
||||||
raw.anti_alias_rate,
|
raw.anti_alias_rate,
|
||||||
raw.max_depth,
|
|
||||||
raw.fov,
|
raw.fov,
|
||||||
raw.look_from,
|
raw.look_from,
|
||||||
raw.look_at,
|
raw.look_at,
|
||||||
|
|||||||
@@ -10,14 +10,33 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
pub camera: Camera,
|
pub camera: Camera, // FIXME: should not be public
|
||||||
pub _materials: Vec<Arc<dyn Material>>,
|
|
||||||
pub objects: Vec<Arc<dyn Hittable>>,
|
pub objects: Vec<Arc<dyn Hittable>>,
|
||||||
|
// image
|
||||||
|
pub filename: String,
|
||||||
|
pub image_width: u32,
|
||||||
|
pub image_height: u32,
|
||||||
|
// raytracing // TODO: think about organisation of these vars, also in Camera
|
||||||
|
pub max_depth: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn render(&mut self) {
|
pub fn new(
|
||||||
self.camera.render(&self.objects);
|
camera: Camera,
|
||||||
|
objects: Vec<Arc<dyn Hittable>>,
|
||||||
|
filename: String,
|
||||||
|
image_width: u32,
|
||||||
|
image_height: u32,
|
||||||
|
max_depth: u32,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
camera,
|
||||||
|
objects,
|
||||||
|
filename,
|
||||||
|
image_width,
|
||||||
|
image_height,
|
||||||
|
max_depth,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +45,10 @@ struct SceneDef {
|
|||||||
pub camera: RawCamera,
|
pub camera: RawCamera,
|
||||||
pub materials: Vec<MaterialDef>,
|
pub materials: Vec<MaterialDef>,
|
||||||
pub objects: Vec<HittableDef>,
|
pub objects: Vec<HittableDef>,
|
||||||
|
pub filename: String,
|
||||||
|
pub image_width: u32,
|
||||||
|
pub image_height: u32,
|
||||||
|
pub max_depth: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Scene {
|
impl<'de> Deserialize<'de> for Scene {
|
||||||
@@ -46,8 +69,11 @@ impl<'de> Deserialize<'de> for Scene {
|
|||||||
.collect();
|
.collect();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
camera: Camera::from(conc.camera),
|
camera: Camera::from(conc.camera),
|
||||||
_materials: mats,
|
|
||||||
objects: objs,
|
objects: objs,
|
||||||
|
filename: conc.filename,
|
||||||
|
image_width: conc.image_width,
|
||||||
|
image_height: conc.image_height,
|
||||||
|
max_depth: conc.max_depth,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ impl Vec3 {
|
|||||||
r_out_perp + r_out_parr
|
r_out_perp + r_out_parr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn output(self) -> image::Rgb<u8> {
|
pub fn output(self) -> (u8, u8, u8) {
|
||||||
// gamma correction
|
// gamma correction
|
||||||
let r = if self.x > 0. {
|
let r = if self.x > 0. {
|
||||||
sqrt(self.x).clamp(0., 1.)
|
sqrt(self.x).clamp(0., 1.)
|
||||||
@@ -144,7 +144,7 @@ impl Vec3 {
|
|||||||
let ig = (255.599 * g) as u8;
|
let ig = (255.599 * g) as u8;
|
||||||
let ib = (255.599 * b) as u8;
|
let ib = (255.599 * b) as u8;
|
||||||
|
|
||||||
image::Rgb([ir, ig, ib])
|
(ir, ig, ib)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone(&self) -> Self {
|
pub fn clone(&self) -> Self {
|
||||||
|
|||||||
Reference in New Issue
Block a user