rtiow: break project into multiple workspaces.
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
30
rtiow/renderer/Cargo.toml
Normal file
30
rtiow/renderer/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "renderer"
|
||||
version = "0.1.0"
|
||||
authors = ["Bill Thiede <git@xinu.tv>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[[bench]]
|
||||
harness = false
|
||||
name = "spheres"
|
||||
|
||||
[dependencies]
|
||||
chrono = "*"
|
||||
core_affinity = "0.5"
|
||||
cpuprofiler = { version = "0.0.3", optional = true }
|
||||
image = "0.19.0"
|
||||
lazy_static = "1.1.0"
|
||||
log = "0.4.5"
|
||||
num_cpus = "1.8.0"
|
||||
rand = "0.5.5"
|
||||
serde = "1.0.79"
|
||||
serde_derive = "1.0.79"
|
||||
serde_json = "1.0.41"
|
||||
structopt = "0.2.10"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.2"
|
||||
|
||||
[features]
|
||||
profile = ["cpuprofiler"]
|
||||
37
rtiow/renderer/benches/spheres.rs
Normal file
37
rtiow/renderer/benches/spheres.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
#[macro_use]
|
||||
extern crate criterion;
|
||||
|
||||
use criterion::Criterion;
|
||||
use criterion::ParameterizedBenchmark;
|
||||
|
||||
use rtiow::hitable::Hit;
|
||||
use rtiow::material::Lambertian;
|
||||
use rtiow::ray::Ray;
|
||||
use rtiow::sphere::Sphere;
|
||||
use rtiow::texture::ConstantTexture;
|
||||
use rtiow::vec3::Vec3;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let sphere = Sphere::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
1.,
|
||||
Lambertian::new(ConstantTexture::new([1., 0., 0.])),
|
||||
);
|
||||
let rays = vec![
|
||||
// Hit
|
||||
Ray::new([0., 0., -2.], [0., 0., 1.], 0.),
|
||||
// Miss
|
||||
Ray::new([0., 0., -2.], [0., 0., -1.], 0.),
|
||||
];
|
||||
c.bench(
|
||||
"sphere",
|
||||
ParameterizedBenchmark::new(
|
||||
"Sphere",
|
||||
move |b, r| b.iter(|| sphere.hit(*r, 0., 1.)),
|
||||
rays,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
BIN
rtiow/renderer/images/envmap.jpg
Normal file
BIN
rtiow/renderer/images/envmap.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 MiB |
BIN
rtiow/renderer/images/world.jpg
Normal file
BIN
rtiow/renderer/images/world.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 295 KiB |
172
rtiow/renderer/src/aabb.rs
Normal file
172
rtiow/renderer/src/aabb.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AABB {
|
||||
bounds: [Vec3; 2],
|
||||
}
|
||||
|
||||
impl fmt::Display for AABB {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({})-({})", self.bounds[0], self.bounds[1])
|
||||
}
|
||||
}
|
||||
|
||||
fn min(x: f32, y: f32) -> f32 {
|
||||
if x < y {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
|
||||
fn max(x: f32, y: f32) -> f32 {
|
||||
if x > y {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
|
||||
impl AABB {
|
||||
pub fn new(min: Vec3, max: Vec3) -> AABB {
|
||||
AABB { bounds: [min, max] }
|
||||
}
|
||||
|
||||
pub fn volume(&self) -> f32 {
|
||||
(self.min().x - self.max().x).abs()
|
||||
* (self.min().y - self.max().y).abs()
|
||||
* (self.min().z - self.max().z).abs()
|
||||
}
|
||||
|
||||
pub fn longest_axis(&self) -> usize {
|
||||
let xd = (self.min().x - self.max().x).abs();
|
||||
let yd = (self.min().y - self.max().y).abs();
|
||||
let zd = (self.min().z - self.max().z).abs();
|
||||
|
||||
if xd >= yd && xd >= zd {
|
||||
return 0;
|
||||
}
|
||||
if yd >= xd && yd >= zd {
|
||||
return 1;
|
||||
}
|
||||
2
|
||||
}
|
||||
|
||||
pub fn mid_point(&self) -> Vec3 {
|
||||
self.bounds[0] + (self.bounds[1] - self.bounds[0]) / 2.
|
||||
}
|
||||
|
||||
pub fn min(&self) -> Vec3 {
|
||||
self.bounds[0]
|
||||
}
|
||||
pub fn max(&self) -> Vec3 {
|
||||
self.bounds[1]
|
||||
}
|
||||
|
||||
pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||
let mut t_min = t_min;
|
||||
let mut t_max = t_max;
|
||||
for axis in 0..3 {
|
||||
let d_inv = 1.0 / r.direction[axis];
|
||||
let t0 = min(
|
||||
(self.min()[axis] - r.origin[axis]) * d_inv,
|
||||
(self.max()[axis] - r.origin[axis]) * d_inv,
|
||||
);
|
||||
let t1 = max(
|
||||
(self.min()[axis] - r.origin[axis]) * d_inv,
|
||||
(self.max()[axis] - r.origin[axis]) * d_inv,
|
||||
);
|
||||
t_min = max(t0, t_min);
|
||||
t_max = min(t1, t_max);
|
||||
if t_max <= t_min {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn hit_precompute(&self, r: Ray, t0: f32, t1: f32) -> bool {
|
||||
// TODO(wathiede): this has bugs.
|
||||
let mut t_min = (self.bounds[r.sign[0]].x - r.origin.x) * r.inv_direction.x;
|
||||
let mut t_max = (self.bounds[1 - r.sign[0]].x - r.origin.x) * r.inv_direction.x;
|
||||
let t_y_min = (self.bounds[r.sign[0]].y - r.origin.y) * r.inv_direction.y;
|
||||
let t_y_max = (self.bounds[1 - r.sign[0]].y - r.origin.y) * r.inv_direction.y;
|
||||
|
||||
if t_min > t_y_max || t_y_min > t_max {
|
||||
return false;
|
||||
}
|
||||
|
||||
if t_y_min > t_min {
|
||||
t_min = t_y_min;
|
||||
}
|
||||
if t_y_max < t_max {
|
||||
t_max = t_y_max;
|
||||
}
|
||||
|
||||
let t_z_min = (self.bounds[r.sign[2]].z - r.origin.z) * r.inv_direction.z;
|
||||
let t_z_max = (self.bounds[1 - r.sign[2]].z - r.origin.z) * r.inv_direction.z;
|
||||
if t_min > t_z_max || t_z_min > t_max {
|
||||
return false;
|
||||
}
|
||||
if t_z_min > t_min {
|
||||
t_min = t_z_min;
|
||||
}
|
||||
if t_z_max < t_max {
|
||||
t_max = t_z_max;
|
||||
}
|
||||
t_min < t1 && t_max > t0
|
||||
}
|
||||
|
||||
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||
let mut t_min = t_min;
|
||||
let mut t_max = t_max;
|
||||
for axis in 0..3 {
|
||||
let t0 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
||||
.min((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
||||
let t1 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
||||
.max((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
||||
t_min = t0.max(t_min);
|
||||
t_max = t1.min(t_max);
|
||||
if t_max <= t_min {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> bool {
|
||||
let b = self;
|
||||
let mut t1 = (b.min()[0] - r.origin[0]) * 1. / r.direction[0];
|
||||
let mut t2 = (b.max()[0] - r.origin[0]) * 1. / r.direction[0];
|
||||
|
||||
let mut tmin = t1.min(t2);
|
||||
let mut tmax = t1.max(t2);
|
||||
|
||||
for i in 1..3 {
|
||||
t1 = (b.min()[i] - r.origin[i]) * 1. / r.direction[i];
|
||||
t2 = (b.max()[i] - r.origin[i]) * 1. / r.direction[i];
|
||||
|
||||
tmin = tmin.max(t1.min(t2));
|
||||
tmax = tmax.min(t1.max(t2));
|
||||
}
|
||||
|
||||
tmax > tmin.max(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn surrounding_box(box0: &AABB, box1: &AABB) -> AABB {
|
||||
let min = Vec3::new(
|
||||
box0.min().x.min(box1.min().x),
|
||||
box0.min().y.min(box1.min().y),
|
||||
box0.min().z.min(box1.min().z),
|
||||
);
|
||||
let max = Vec3::new(
|
||||
box0.max().x.max(box1.max().x),
|
||||
box0.max().y.max(box1.max().y),
|
||||
box0.max().z.max(box1.max().z),
|
||||
);
|
||||
AABB::new(min, max)
|
||||
}
|
||||
5
rtiow/renderer/src/build.rs
Normal file
5
rtiow/renderer/src/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
extern crate askama;
|
||||
|
||||
fn main() {
|
||||
askama::rerun_if_templates_changed();
|
||||
}
|
||||
300
rtiow/renderer/src/bvh.rs
Normal file
300
rtiow/renderer/src/bvh.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
use std;
|
||||
use std::fmt;
|
||||
use std::time::Instant;
|
||||
|
||||
use log::info;
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::aabb::surrounding_box;
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::ray::Ray;
|
||||
|
||||
enum BVHNode {
|
||||
Leaf(Box<dyn Hit>),
|
||||
Branch {
|
||||
left: Box<BVHNode>,
|
||||
right: Box<BVHNode>,
|
||||
bbox: Option<AABB>,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for BVHNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
BVHNode::Leaf(ref hit) => write!(
|
||||
f,
|
||||
"Leaf: {}",
|
||||
hit.bounding_box(0., 0.)
|
||||
.map_or("NO BBOX".to_owned(), |bb| bb.to_string())
|
||||
),
|
||||
BVHNode::Branch { bbox, .. } => write!(
|
||||
f,
|
||||
"Branch: {}",
|
||||
// TODO(wathiede): removing this .clone() results in a complaint about moving out
|
||||
// of a borrow.
|
||||
bbox.clone()
|
||||
.map_or("NO BBOX".to_owned(), |bb| bb.to_string())
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lint is wrong when dealing with Box<Trait>
|
||||
#[allow(clippy::borrowed_box)]
|
||||
fn box_x_compare(ah: &Box<dyn Hit>, bh: &Box<dyn Hit>) -> std::cmp::Ordering {
|
||||
match (ah.bounding_box(0., 0.), bh.bounding_box(0., 0.)) {
|
||||
(Some(box_left), Some(box_right)) => {
|
||||
box_left.min().x.partial_cmp(&box_right.min().x).unwrap()
|
||||
}
|
||||
_ => panic!("hit missing bounding box"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
fn box_y_compare(ah: &Box<dyn Hit>, bh: &Box<dyn Hit>) -> std::cmp::Ordering {
|
||||
match (ah.bounding_box(0., 0.), bh.bounding_box(0., 0.)) {
|
||||
(Some(box_left), Some(box_right)) => {
|
||||
box_left.min().y.partial_cmp(&box_right.min().y).unwrap()
|
||||
}
|
||||
_ => panic!("hit missing bounding box"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
fn box_z_compare(ah: &Box<dyn Hit>, bh: &Box<dyn Hit>) -> std::cmp::Ordering {
|
||||
match (ah.bounding_box(0., 0.), bh.bounding_box(0., 0.)) {
|
||||
(Some(box_left), Some(box_right)) => {
|
||||
box_left.min().z.partial_cmp(&box_right.min().z).unwrap()
|
||||
}
|
||||
_ => panic!("hit missing bounding box"),
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first element from the vector, which should be the only element.
|
||||
// TODO(wathiede): we really want a .first_into() (.first() doesn't work because it
|
||||
// returns a reference.)
|
||||
fn vec_first_into<T>(v: Vec<T>) -> T {
|
||||
if v.len() != 1 {
|
||||
panic!(format!(
|
||||
"vec_first_into called for vector length != 1, length {}",
|
||||
v.len()
|
||||
));
|
||||
}
|
||||
v.into_iter().next().unwrap()
|
||||
}
|
||||
|
||||
fn vec_split_into<T>(v: Vec<T>, offset: usize) -> (Vec<T>, Vec<T>) {
|
||||
let mut left_half = Vec::new();
|
||||
let mut right_half = Vec::new();
|
||||
v.into_iter().enumerate().for_each(|(i, h)| {
|
||||
if i < offset {
|
||||
left_half.push(h);
|
||||
} else {
|
||||
right_half.push(h);
|
||||
}
|
||||
});
|
||||
(left_half, right_half)
|
||||
}
|
||||
|
||||
impl BVHNode {
|
||||
fn new(l: Vec<Box<dyn Hit>>, t_min: f32, t_max: f32) -> BVHNode {
|
||||
if l.len() == 1 {
|
||||
BVHNode::Leaf(vec_first_into(l))
|
||||
} else {
|
||||
let mut l: Vec<Box<dyn Hit>> = l.into_iter().collect();
|
||||
let mut rng = rand::thread_rng();
|
||||
match rng.gen_range(0, 3) {
|
||||
0 => l.sort_by(box_x_compare),
|
||||
1 => l.sort_by(box_y_compare),
|
||||
2 => l.sort_by(box_z_compare),
|
||||
val => panic!("unknown axis {}", val),
|
||||
}
|
||||
|
||||
let half = l.len() / 2;
|
||||
let (left_half, right_half) = vec_split_into(l, half);
|
||||
let left = Box::new(BVHNode::new(left_half, t_min, t_max));
|
||||
let right = Box::new(BVHNode::new(right_half, t_min, t_max));
|
||||
let bbox = BVHNode::surrounding_box(left.as_ref(), right.as_ref(), t_min, t_max);
|
||||
BVHNode::Branch { left, right, bbox }
|
||||
}
|
||||
}
|
||||
|
||||
fn surrounding_box(left: &dyn Hit, right: &dyn Hit, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
match (
|
||||
left.bounding_box(t_min, t_max),
|
||||
right.bounding_box(t_min, t_max),
|
||||
) {
|
||||
(Some(left_bbox), Some(right_bbox)) => Some(surrounding_box(&left_bbox, &right_bbox)),
|
||||
(None, Some(right_bbox)) => Some(right_bbox),
|
||||
(Some(left_bbox), None) => Some(left_bbox),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn volume(&self) -> f32 {
|
||||
let bbox = self.bounding_box(0., 0.).unwrap();
|
||||
(bbox.min().x - bbox.max().x).abs()
|
||||
* (bbox.min().y - bbox.max().y).abs()
|
||||
* (bbox.min().z - bbox.max().z).abs()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hit for BVHNode {
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
match self {
|
||||
BVHNode::Leaf(ref hit) => hit.hit(r, t_min, t_max),
|
||||
BVHNode::Branch {
|
||||
ref left,
|
||||
ref right,
|
||||
ref bbox,
|
||||
} => match bbox {
|
||||
Some(ref bbox) => {
|
||||
if bbox.hit(r, t_min, t_max) {
|
||||
match (left.hit(r, t_min, t_max), right.hit(r, t_min, t_max)) {
|
||||
(Some(hit_left), Some(hit_right)) => {
|
||||
if hit_left.t < hit_right.t {
|
||||
Some(hit_left)
|
||||
} else {
|
||||
Some(hit_right)
|
||||
}
|
||||
}
|
||||
(Some(hit_left), None) => Some(hit_left),
|
||||
(None, Some(hit_right)) => Some(hit_right),
|
||||
(None, None) => None,
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
match self {
|
||||
BVHNode::Leaf(ref hit) => hit.bounding_box(t_min, t_max),
|
||||
BVHNode::Branch { ref bbox, .. } => bbox.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BVH {
|
||||
root: BVHNode,
|
||||
}
|
||||
|
||||
impl BVH {
|
||||
pub fn new(l: Vec<Box<dyn Hit>>, t_min: f32, t_max: f32) -> BVH {
|
||||
let count = l.len();
|
||||
let start = Instant::now();
|
||||
let bvh = BVH {
|
||||
root: BVHNode::new(l, t_min, t_max),
|
||||
};
|
||||
let runtime = start.elapsed();
|
||||
info!(
|
||||
"BVH build time {}.{} seconds for {} hitables",
|
||||
runtime.as_secs(),
|
||||
runtime.subsec_millis(),
|
||||
count
|
||||
);
|
||||
bvh
|
||||
}
|
||||
}
|
||||
|
||||
fn print_tree(f: &mut fmt::Formatter, depth: usize, bvhn: &BVHNode) -> fmt::Result {
|
||||
let vol = bvhn.volume();
|
||||
writeln!(f, "{:.*}{}{}", 2, vol, " ".repeat(depth * 2), bvhn)?;
|
||||
if let BVHNode::Branch { left, right, .. } = bvhn {
|
||||
print_tree(f, depth + 1, left)?;
|
||||
print_tree(f, depth + 1, right)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl fmt::Display for BVH {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Root")?;
|
||||
print_tree(f, 1, &self.root)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hit for BVH {
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
self.root.hit(r, t_min, t_max)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
self.root.bounding_box(t_min, t_max)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::aabb::AABB;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Metal;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bbox_two_spheres() {
|
||||
let two_spheres_bvh = BVH::new(
|
||||
vec![
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
0.5,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.1, 0.2, 0.5))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(1., 0., 0.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.6, 0.6, 0.6), 0.2),
|
||||
)),
|
||||
],
|
||||
0.,
|
||||
1.,
|
||||
);
|
||||
assert_eq!(
|
||||
AABB::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(1.5, 0.5, 0.5)),
|
||||
two_spheres_bvh.bounding_box(0., 1.).unwrap(),
|
||||
"BVH:\n{}",
|
||||
two_spheres_bvh
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bbox_three_spheres() {
|
||||
let three_spheres_bvh = BVH::new(
|
||||
vec![
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
0.5,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.1, 0.2, 0.5))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(1., 0., 0.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.6, 0.6, 0.6), 0.2),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 1., 0.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.6, 0.6, 0.6), 0.2),
|
||||
)),
|
||||
],
|
||||
0.,
|
||||
1.,
|
||||
);
|
||||
assert_eq!(
|
||||
AABB::new(Vec3::new(-0.5, -0.5, -0.5), Vec3::new(1.5, 1.5, 0.5)),
|
||||
three_spheres_bvh.bounding_box(0., 1.).unwrap(),
|
||||
"BVH:\n{}",
|
||||
three_spheres_bvh
|
||||
);
|
||||
}
|
||||
}
|
||||
82
rtiow/renderer/src/camera.rs
Normal file
82
rtiow/renderer/src/camera.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::cross;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
fn random_in_unit_disk() -> Vec3 {
|
||||
let mut rng = rand::thread_rng();
|
||||
let v = Vec3::new(1., 1., 0.);
|
||||
loop {
|
||||
let p = 2. * Vec3::new(rng.gen_range(0., 1.), rng.gen_range(0., 1.), 0.) - v;
|
||||
if p.squared_length() < 1. {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Camera {
|
||||
origin: Vec3,
|
||||
lower_left_corner: Vec3,
|
||||
horizontal: Vec3,
|
||||
vertical: Vec3,
|
||||
u: Vec3,
|
||||
v: Vec3,
|
||||
lens_radius: f32,
|
||||
time_min: f32,
|
||||
time_max: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
// Parameters match the example C++ code.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
// vfov is top to bottom in degrees
|
||||
pub fn new(
|
||||
lookfrom: Vec3,
|
||||
lookat: Vec3,
|
||||
vup: Vec3,
|
||||
vfov: f32,
|
||||
aspect: f32,
|
||||
aperture: f32,
|
||||
focus_dist: f32,
|
||||
time_min: f32,
|
||||
time_max: f32,
|
||||
) -> Camera {
|
||||
let theta = vfov * PI / 180.;
|
||||
let half_height = (theta / 2.).tan();
|
||||
let half_width = aspect * half_height;
|
||||
let origin = lookfrom;
|
||||
let w = (lookfrom - lookat).unit_vector();
|
||||
let u = cross(vup, w).unit_vector();
|
||||
let v = cross(w, u);
|
||||
Camera {
|
||||
lower_left_corner: origin
|
||||
- half_width * focus_dist * u
|
||||
- half_height * focus_dist * v
|
||||
- focus_dist * w,
|
||||
horizontal: 2. * half_width * focus_dist * u,
|
||||
vertical: 2. * half_height * focus_dist * v,
|
||||
origin,
|
||||
u: cross(vup, w).unit_vector(),
|
||||
v: cross(w, u),
|
||||
lens_radius: aperture / 2.,
|
||||
time_min,
|
||||
time_max,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ray(&self, u: f32, v: f32) -> Ray {
|
||||
let mut rng = rand::thread_rng();
|
||||
let rd = self.lens_radius * random_in_unit_disk();
|
||||
let offset = self.u * rd.x + self.v * rd.y;
|
||||
let time = self.time_min + rng.gen_range(0., 1.) * (self.time_max - self.time_min);
|
||||
Ray::new(
|
||||
self.origin + offset,
|
||||
self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin - offset,
|
||||
time,
|
||||
)
|
||||
}
|
||||
}
|
||||
88
rtiow/renderer/src/constant_medium.rs
Normal file
88
rtiow/renderer/src/constant_medium.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std;
|
||||
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::material::Isotropic;
|
||||
use crate::ray::Ray;
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct ConstantMedium<H, T>
|
||||
where
|
||||
H: Hit,
|
||||
T: Texture,
|
||||
{
|
||||
density: f32,
|
||||
material: Isotropic<T>,
|
||||
hitable: H,
|
||||
}
|
||||
|
||||
impl<H, T> ConstantMedium<H, T>
|
||||
where
|
||||
H: Hit,
|
||||
T: Texture,
|
||||
{
|
||||
pub fn new(hitable: H, density: f32, texture: T) -> ConstantMedium<H, T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
ConstantMedium {
|
||||
density,
|
||||
material: Isotropic::new(texture),
|
||||
hitable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T> Hit for ConstantMedium<H, T>
|
||||
where
|
||||
H: Hit,
|
||||
T: Texture,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let mut rng = rand::thread_rng();
|
||||
if let Some(mut rec1) = self.hitable.hit(r, std::f32::MIN, std::f32::MAX) {
|
||||
if let Some(mut rec2) = self.hitable.hit(r, rec1.t + 0.001, std::f32::MAX) {
|
||||
if rec1.t < t_min {
|
||||
rec1.t = t_min;
|
||||
}
|
||||
if rec2.t > t_max {
|
||||
rec2.t = t_max;
|
||||
}
|
||||
if rec1.t >= rec2.t {
|
||||
return None;
|
||||
}
|
||||
if rec1.t < 0. {
|
||||
rec1.t = 0.;
|
||||
}
|
||||
let distance_inside_boundary = (rec2.t - rec1.t) * r.direction.length();
|
||||
let hit_distance = -(1. / self.density) * rng.gen_range::<f32>(0., 1.).ln();
|
||||
if hit_distance < distance_inside_boundary {
|
||||
let t = rec1.t + hit_distance / r.direction.length();
|
||||
let normal = Vec3::new(
|
||||
rng.gen_range(0., 1.),
|
||||
rng.gen_range(0., 1.),
|
||||
rng.gen_range(0., 1.),
|
||||
)
|
||||
.unit_vector();
|
||||
return Some(HitRecord {
|
||||
t,
|
||||
p: r.point_at_parameter(t),
|
||||
normal,
|
||||
material: &self.material,
|
||||
uv: (0., 0.),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
self.hitable.bounding_box(t_min, t_max)
|
||||
}
|
||||
}
|
||||
90
rtiow/renderer/src/cuboid.rs
Normal file
90
rtiow/renderer/src/cuboid.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::aabb::AABB;
|
||||
use crate::flip_normals::FlipNormals;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::material::Material;
|
||||
use crate::ray::Ray;
|
||||
use crate::rect::XYRect;
|
||||
use crate::rect::XZRect;
|
||||
use crate::rect::YZRect;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct Cuboid {
|
||||
p_min: Vec3,
|
||||
p_max: Vec3,
|
||||
walls: HitableList,
|
||||
}
|
||||
|
||||
impl Cuboid {
|
||||
// This clippy doesn't work right with Arc.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn new(p_min: Vec3, p_max: Vec3, material: Arc<dyn Material>) -> Cuboid {
|
||||
Cuboid {
|
||||
p_min,
|
||||
p_max,
|
||||
walls: HitableList::new(vec![
|
||||
Box::new(XYRect::new(
|
||||
p_min.x,
|
||||
p_max.x,
|
||||
p_min.y,
|
||||
p_max.y,
|
||||
p_max.z,
|
||||
Arc::clone(&material),
|
||||
)),
|
||||
Box::new(FlipNormals::new(XYRect::new(
|
||||
p_min.x,
|
||||
p_max.x,
|
||||
p_min.y,
|
||||
p_max.y,
|
||||
p_min.z,
|
||||
Arc::clone(&material),
|
||||
))),
|
||||
Box::new(XZRect::new(
|
||||
p_min.x,
|
||||
p_max.x,
|
||||
p_min.z,
|
||||
p_max.z,
|
||||
p_max.y,
|
||||
Arc::clone(&material),
|
||||
)),
|
||||
Box::new(FlipNormals::new(XZRect::new(
|
||||
p_min.x,
|
||||
p_max.x,
|
||||
p_min.z,
|
||||
p_max.z,
|
||||
p_min.y,
|
||||
Arc::clone(&material),
|
||||
))),
|
||||
Box::new(YZRect::new(
|
||||
p_min.y,
|
||||
p_max.y,
|
||||
p_min.z,
|
||||
p_max.z,
|
||||
p_max.x,
|
||||
Arc::clone(&material),
|
||||
)),
|
||||
Box::new(FlipNormals::new(YZRect::new(
|
||||
p_min.y,
|
||||
p_max.y,
|
||||
p_min.z,
|
||||
p_max.z,
|
||||
p_min.x,
|
||||
Arc::clone(&material),
|
||||
))),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hit for Cuboid {
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
self.walls.hit(r, t_min, t_max)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
Some(AABB::new(self.p_min, self.p_max))
|
||||
}
|
||||
}
|
||||
39
rtiow/renderer/src/flip_normals.rs
Normal file
39
rtiow/renderer/src/flip_normals.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::ray::Ray;
|
||||
|
||||
pub struct FlipNormals<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
hitable: H,
|
||||
}
|
||||
|
||||
impl<H> FlipNormals<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
pub fn new(hitable: H) -> FlipNormals<H> {
|
||||
FlipNormals { hitable }
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Hit for FlipNormals<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
if let Some(rec) = self.hitable.hit(r, t_min, t_max) {
|
||||
return Some(HitRecord {
|
||||
normal: -rec.normal,
|
||||
..rec
|
||||
});
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
self.hitable.bounding_box(t_min, t_max)
|
||||
}
|
||||
}
|
||||
28
rtiow/renderer/src/hitable.rs
Normal file
28
rtiow/renderer/src/hitable.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::aabb::AABB;
|
||||
use crate::material::Material;
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct HitRecord<'m> {
|
||||
pub t: f32,
|
||||
pub uv: (f32, f32),
|
||||
pub p: Vec3,
|
||||
pub normal: Vec3,
|
||||
pub material: &'m dyn Material,
|
||||
}
|
||||
|
||||
pub trait Hit: Send + Sync {
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord>;
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB>;
|
||||
}
|
||||
|
||||
impl Hit for Arc<dyn Hit> {
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
(**self).hit(r, t_min, t_max)
|
||||
}
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
(**self).bounding_box(t_min, t_max)
|
||||
}
|
||||
}
|
||||
48
rtiow/renderer/src/hitable_list.rs
Normal file
48
rtiow/renderer/src/hitable_list.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std;
|
||||
|
||||
use crate::aabb::surrounding_box;
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HitableList {
|
||||
list: Vec<Box<dyn Hit>>,
|
||||
}
|
||||
|
||||
impl HitableList {
|
||||
pub fn new(list: Vec<Box<dyn Hit>>) -> HitableList {
|
||||
HitableList { list }
|
||||
}
|
||||
}
|
||||
|
||||
impl Hit for HitableList {
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let mut min_hit = None;
|
||||
let mut closest_so_far = t_max;
|
||||
for hitable in &self.list {
|
||||
if let Some(h) = hitable.hit(r, t_min, closest_so_far) {
|
||||
closest_so_far = h.t;
|
||||
min_hit = Some(h);
|
||||
}
|
||||
}
|
||||
min_hit
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
// TODO(wathiede): I don't understand the version from the book, this implementation
|
||||
// returns a superset of all bounding boxes.
|
||||
let super_box = AABB::new(
|
||||
Vec3::new(std::f32::MAX, std::f32::MAX, std::f32::MAX),
|
||||
Vec3::new(std::f32::MIN, std::f32::MIN, std::f32::MIN),
|
||||
);
|
||||
Some(self.list.iter().fold(super_box, |acc, hitable| {
|
||||
match hitable.bounding_box(t_min, t_max) {
|
||||
Some(bbox) => surrounding_box(&acc, &bbox),
|
||||
None => acc,
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
227
rtiow/renderer/src/human.rs
Normal file
227
rtiow/renderer/src/human.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
#![allow(dead_code)]
|
||||
//! From https://raw.githubusercontent.com/BobGneu/human-format-rs/master/src/lib.rs
|
||||
//! `human` provides facilitates creating a formatted string, converting between numbers that are beyond typical
|
||||
//! needs for humans into a simpler string that conveys the gist of the meaning of the number.
|
||||
//!
|
||||
//! Print some human readable strings
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rtiow::human;
|
||||
//!
|
||||
//! // "1.00 k"
|
||||
//! let tmpStr = human::Formatter::new()
|
||||
//! .format(1000.0);
|
||||
//! # assert_eq!(tmpStr, "1.00 k");
|
||||
//!
|
||||
//! // "1.00 M"
|
||||
//! let tmpStr2 = human::Formatter::new()
|
||||
//! .format(1000000.0);
|
||||
//! # assert_eq!(tmpStr2, "1.00 M");
|
||||
//!
|
||||
//! // "1.00 B"
|
||||
//! let tmpStr3 = human::Formatter::new()
|
||||
//! .format(1000000000.0);
|
||||
//! # assert_eq!(tmpStr3, "1.00 B");
|
||||
//! ```
|
||||
//!
|
||||
//! If you are so inspired you can even try playing with units and customizing your `Scales`
|
||||
//!
|
||||
//! For more examples you should review the examples on github: [tests/demo.rs](https://github.com/BobGneu/human-format-rs/blob/master/tests/demo.rs)
|
||||
//!
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ScaledValue {
|
||||
value: f32,
|
||||
suffix: String,
|
||||
}
|
||||
|
||||
/// Entry point to the lib. Use this to handle your formatting needs.
|
||||
#[derive(Debug)]
|
||||
pub struct Formatter {
|
||||
decimals: usize,
|
||||
separator: String,
|
||||
scales: Scales,
|
||||
forced_units: String,
|
||||
forced_suffix: String,
|
||||
}
|
||||
|
||||
/// Provide a customized scaling scheme for your own modeling.
|
||||
#[derive(Debug)]
|
||||
pub struct Scales {
|
||||
base: u32,
|
||||
suffixes: Vec<String>,
|
||||
}
|
||||
|
||||
impl Formatter {
|
||||
/// Initializes a new `Formatter` with default values.
|
||||
pub fn new() -> Self {
|
||||
Formatter {
|
||||
decimals: 2,
|
||||
separator: " ".to_owned(),
|
||||
scales: Scales::si(),
|
||||
forced_units: "".to_owned(),
|
||||
forced_suffix: "".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the decimals value for formatting the string.
|
||||
pub fn with_decimals(&mut self, decimals: usize) -> &mut Self {
|
||||
self.decimals = decimals;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the separator value for formatting the string.
|
||||
pub fn with_separator(&mut self, separator: &str) -> &mut Self {
|
||||
self.separator = separator.to_owned();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the scales value.
|
||||
pub fn with_scales(&mut self, scales: Scales) -> &mut Self {
|
||||
self.scales = scales;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the units value.
|
||||
pub fn with_units(&mut self, units: &str) -> &mut Self {
|
||||
self.forced_units = units.to_owned();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the expected suffix value.
|
||||
pub fn with_suffix(&mut self, suffix: &str) -> &mut Self {
|
||||
self.forced_suffix = suffix.to_owned();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Formats the number into a string
|
||||
pub fn format(&self, value: f64) -> String {
|
||||
if value < 0.0 {
|
||||
return format!("-{}", self.format(value * -1.0));
|
||||
}
|
||||
|
||||
let scaled_value = self.scales.to_scaled_value(value);
|
||||
|
||||
format!(
|
||||
"{:.width$}{}{}{}",
|
||||
scaled_value.value,
|
||||
self.separator,
|
||||
scaled_value.suffix,
|
||||
self.forced_units,
|
||||
width = self.decimals
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a string back into a float value.
|
||||
pub fn parse(&self, value: &str) -> f64 {
|
||||
let v: Vec<&str> = value.split(&self.separator).collect();
|
||||
|
||||
let result = v.get(0).unwrap().parse::<f64>().unwrap();
|
||||
|
||||
let mut suffix = v.get(1).unwrap().to_string();
|
||||
let new_len = suffix.len() - self.forced_units.len();
|
||||
|
||||
suffix.truncate(new_len);
|
||||
|
||||
let magnitude_multiplier = self.scales.get_magnitude_multipler(&suffix);
|
||||
|
||||
(result * magnitude_multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
impl Scales {
|
||||
/// Instantiates a new `Scales` with SI keys
|
||||
pub fn new() -> Self {
|
||||
Scales::si()
|
||||
}
|
||||
|
||||
/// Instantiates a new `Scales` with SI keys
|
||||
pub fn si() -> Self {
|
||||
Scales {
|
||||
base: 1000,
|
||||
suffixes: vec![
|
||||
"".to_owned(),
|
||||
"k".to_owned(),
|
||||
"M".to_owned(),
|
||||
"B".to_owned(),
|
||||
"T".to_owned(),
|
||||
"P".to_owned(),
|
||||
"E".to_owned(),
|
||||
"Z".to_owned(),
|
||||
"Y".to_owned(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiates a new `Scales` with Binary keys
|
||||
pub fn binary() -> Self {
|
||||
Scales {
|
||||
base: 1000,
|
||||
suffixes: vec![
|
||||
"".to_owned(),
|
||||
"ki".to_owned(),
|
||||
"Mi".to_owned(),
|
||||
"Gi".to_owned(),
|
||||
"Ti".to_owned(),
|
||||
"Pi".to_owned(),
|
||||
"Ei".to_owned(),
|
||||
"Zi".to_owned(),
|
||||
"Yi".to_owned(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the base for the `Scales`
|
||||
pub fn with_base(&mut self, base: u32) -> &mut Self {
|
||||
self.base = base;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the suffixes listing appropriately
|
||||
pub fn with_suffixes(&mut self, suffixes: Vec<&str>) -> &mut Self {
|
||||
self.suffixes = Vec::new();
|
||||
|
||||
for suffix in suffixes {
|
||||
// This should be to_owned to be clear about intent.
|
||||
// https://users.rust-lang.org/t/to-string-vs-to-owned-for-string-literals/1441/6
|
||||
self.suffixes.push(suffix.to_owned());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn get_magnitude_multipler(&self, value: &str) -> f64 {
|
||||
for ndx in 0..self.suffixes.len() {
|
||||
if value == self.suffixes[ndx] {
|
||||
return self.base.pow(ndx as u32) as f64;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
fn to_scaled_value(&self, value: f64) -> ScaledValue {
|
||||
let mut index: usize = 0;
|
||||
let mut _value: f64 = value;
|
||||
|
||||
loop {
|
||||
if _value < (self.base as f64) {
|
||||
break;
|
||||
}
|
||||
|
||||
_value /= self.base as f64;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
ScaledValue {
|
||||
value: (value / self.base.pow((index) as u32) as f64) as f32,
|
||||
suffix: self.suffixes[index].to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
191
rtiow/renderer/src/kdtree.rs
Normal file
191
rtiow/renderer/src/kdtree.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use std::fmt;
|
||||
|
||||
use log::info;
|
||||
|
||||
use crate::aabb::surrounding_box;
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::ray::Ray;
|
||||
|
||||
pub enum KDTree {
|
||||
Leaf(Box<dyn Hit>),
|
||||
Branch {
|
||||
left: Box<KDTree>,
|
||||
right: Box<KDTree>,
|
||||
bbox: Option<AABB>,
|
||||
},
|
||||
}
|
||||
|
||||
// Return the first element from the vector, which should be the only element.
|
||||
// TODO(wathiede): we really want a .first_into() (.first() doesn't work because it
|
||||
// returns a reference.)
|
||||
fn vec_first_into<T>(v: Vec<T>) -> T {
|
||||
if v.len() != 1 {
|
||||
panic!(format!(
|
||||
"vec_first_into called for vector length != 1, length {}",
|
||||
v.len()
|
||||
));
|
||||
}
|
||||
v.into_iter().next().unwrap()
|
||||
}
|
||||
|
||||
fn vec_split_into<T>(v: Vec<T>, offset: usize) -> (Vec<T>, Vec<T>) {
|
||||
let mut left_half = Vec::new();
|
||||
let mut right_half = Vec::new();
|
||||
v.into_iter().enumerate().for_each(|(i, h)| {
|
||||
if i < offset {
|
||||
left_half.push(h);
|
||||
} else {
|
||||
right_half.push(h);
|
||||
}
|
||||
});
|
||||
(left_half, right_half)
|
||||
}
|
||||
|
||||
impl KDTree {
|
||||
pub fn new(l: Vec<Box<dyn Hit>>, t_min: f32, t_max: f32) -> KDTree {
|
||||
if l.is_empty() {
|
||||
panic!("Attempt to build k-d tree with no Hit objects");
|
||||
}
|
||||
|
||||
if l.len() == 1 {
|
||||
return KDTree::Leaf(vec_first_into(l));
|
||||
}
|
||||
|
||||
if l.len() == 2 {
|
||||
let (left_vec, right_vec) = vec_split_into(l, 1);
|
||||
let left = vec_first_into(left_vec);
|
||||
let right = vec_first_into(right_vec);
|
||||
let bbox = surrounding_box(
|
||||
&left.bounding_box(t_min, t_max).unwrap(),
|
||||
&right.bounding_box(t_min, t_max).unwrap(),
|
||||
);
|
||||
return KDTree::Branch {
|
||||
left: Box::new(KDTree::Leaf(left)),
|
||||
right: Box::new(KDTree::Leaf(right)),
|
||||
bbox: Some(bbox),
|
||||
};
|
||||
}
|
||||
let bbox = l[0].bounding_box(t_min, t_max).unwrap();
|
||||
let mut mid_point = bbox.mid_point();
|
||||
let bbox = l[1..].iter().fold(bbox, |acc, h| {
|
||||
let new_bbox = &h.bounding_box(t_min, t_max).unwrap();
|
||||
mid_point = mid_point + new_bbox.mid_point();
|
||||
surrounding_box(&acc, new_bbox)
|
||||
});
|
||||
mid_point = mid_point / l.len() as f32;
|
||||
let axis = bbox.longest_axis();
|
||||
let mut left_half = Vec::new();
|
||||
let mut right_half = Vec::new();
|
||||
match axis {
|
||||
0 => l.into_iter().for_each(|h| {
|
||||
if h.bounding_box(t_min, t_max).unwrap().mid_point().x < mid_point.x {
|
||||
left_half.push(h);
|
||||
} else {
|
||||
right_half.push(h);
|
||||
}
|
||||
}),
|
||||
1 => l.into_iter().for_each(|h| {
|
||||
if h.bounding_box(t_min, t_max).unwrap().mid_point().y < mid_point.y {
|
||||
left_half.push(h);
|
||||
} else {
|
||||
right_half.push(h);
|
||||
}
|
||||
}),
|
||||
2 => l.into_iter().for_each(|h| {
|
||||
if h.bounding_box(t_min, t_max).unwrap().mid_point().z < mid_point.z {
|
||||
left_half.push(h);
|
||||
} else {
|
||||
right_half.push(h);
|
||||
}
|
||||
}),
|
||||
_ => panic!("Unreachable"),
|
||||
};
|
||||
KDTree::Branch {
|
||||
left: Box::new(KDTree::new(left_half, t_min, t_max)),
|
||||
right: Box::new(KDTree::new(right_half, t_min, t_max)),
|
||||
bbox: Some(bbox),
|
||||
}
|
||||
}
|
||||
|
||||
fn volume(&self) -> f32 {
|
||||
let bbox = self.bounding_box(0., 0.).unwrap();
|
||||
(bbox.min().x - bbox.max().x).abs()
|
||||
* (bbox.min().y - bbox.max().y).abs()
|
||||
* (bbox.min().z - bbox.max().z).abs()
|
||||
}
|
||||
}
|
||||
|
||||
fn print_tree(f: &mut fmt::Formatter, depth: usize, kdt: &KDTree) -> fmt::Result {
|
||||
let vol = kdt.volume();
|
||||
write!(f, "{:8.2}{}", vol, " ".repeat(depth * 2))?;
|
||||
match kdt {
|
||||
KDTree::Leaf(ref hit) => writeln!(
|
||||
f,
|
||||
"Leaf: {}",
|
||||
hit.bounding_box(0., 0.)
|
||||
.map_or("NO BBOX".to_owned(), |bb| bb.to_string())
|
||||
)?,
|
||||
KDTree::Branch { bbox, .. } => writeln!(
|
||||
f,
|
||||
"Branch: {}",
|
||||
// TODO(wathiede): removing this .clone() results in a complaint about moving out
|
||||
// of a borrow.
|
||||
bbox.clone()
|
||||
.map_or("NO BBOX".to_owned(), |bb| bb.to_string())
|
||||
)?,
|
||||
}
|
||||
if let KDTree::Branch { left, right, .. } = kdt {
|
||||
print_tree(f, depth + 1, left)?;
|
||||
print_tree(f, depth + 1, right)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl fmt::Display for KDTree {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "kd-tree")?;
|
||||
print_tree(f, 1, &self)
|
||||
}
|
||||
}
|
||||
impl Hit for KDTree {
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
match self.bounding_box(t_min, t_max) {
|
||||
Some(bbox) => {
|
||||
if !bbox.hit(r, t_min, t_max) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
info!("KDTree::hit no bbox: {:?}", r);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
match self {
|
||||
KDTree::Leaf(ref hit) => hit.hit(r, t_min, t_max),
|
||||
KDTree::Branch { left, right, bbox } => match bbox {
|
||||
None => None,
|
||||
Some(_bbox) => match (left.hit(r, t_min, t_max), right.hit(r, t_min, t_max)) {
|
||||
(Some(hit_left), Some(hit_right)) => {
|
||||
if hit_left.t < hit_right.t {
|
||||
Some(hit_left)
|
||||
} else {
|
||||
Some(hit_right)
|
||||
}
|
||||
}
|
||||
(Some(hit_left), None) => Some(hit_left),
|
||||
(None, Some(hit_right)) => Some(hit_right),
|
||||
(None, None) => None,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
match self {
|
||||
KDTree::Leaf(ref hit) => hit.bounding_box(t_min, t_max),
|
||||
KDTree::Branch { bbox, .. } => bbox.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
23
rtiow/renderer/src/lib.rs
Normal file
23
rtiow/renderer/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
pub mod aabb;
|
||||
pub mod bvh;
|
||||
pub mod camera;
|
||||
pub mod constant_medium;
|
||||
pub mod cuboid;
|
||||
pub mod flip_normals;
|
||||
pub mod hitable;
|
||||
pub mod hitable_list;
|
||||
pub mod human;
|
||||
pub mod kdtree;
|
||||
pub mod material;
|
||||
pub mod moving_sphere;
|
||||
pub mod noise;
|
||||
pub mod output;
|
||||
pub mod ray;
|
||||
pub mod rect;
|
||||
pub mod renderer;
|
||||
pub mod rotate;
|
||||
pub mod scenes;
|
||||
pub mod sphere;
|
||||
pub mod texture;
|
||||
pub mod translate;
|
||||
pub mod vec3;
|
||||
274
rtiow/renderer/src/material.rs
Normal file
274
rtiow/renderer/src/material.rs
Normal file
@@ -0,0 +1,274 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::ray::Ray;
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::dot;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
fn random_in_unit_sphere() -> Vec3 {
|
||||
let mut rng = rand::thread_rng();
|
||||
let v = Vec3::new(1., 1., 1.);
|
||||
loop {
|
||||
let p =
|
||||
2. * Vec3::new(
|
||||
rng.gen_range(0., 1.),
|
||||
rng.gen_range(0., 1.),
|
||||
rng.gen_range(0., 1.),
|
||||
) - v;
|
||||
if p.squared_length() < 1. {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ScatterResponse {
|
||||
pub scattered: Ray,
|
||||
pub attenutation: Vec3,
|
||||
pub reflected: bool,
|
||||
}
|
||||
|
||||
pub trait Material: Send + Sync {
|
||||
fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse;
|
||||
fn emitted(&self, _u: f32, _v: f32, _p: Vec3) -> Vec3 {
|
||||
Vec3::new(0., 0., 0.)
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Arc<dyn Material> {
|
||||
fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse {
|
||||
(**self).scatter(r_in, rec)
|
||||
}
|
||||
fn emitted(&self, u: f32, v: f32, p: Vec3) -> Vec3 {
|
||||
(**self).emitted(u, v, p)
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Box<dyn Material> {
|
||||
fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse {
|
||||
(**self).scatter(r_in, rec)
|
||||
}
|
||||
fn emitted(&self, u: f32, v: f32, p: Vec3) -> Vec3 {
|
||||
(**self).emitted(u, v, p)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Isotropic<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
albedo: T,
|
||||
}
|
||||
|
||||
impl<T> Isotropic<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
pub fn new(texture: T) -> Isotropic<T> {
|
||||
Isotropic { albedo: texture }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Material for Isotropic<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
fn scatter(&self, _r_in: &Ray, rec: &HitRecord) -> ScatterResponse {
|
||||
let (u, v) = rec.uv;
|
||||
ScatterResponse {
|
||||
attenutation: self.albedo.value(u, v, rec.p),
|
||||
scattered: Ray::new(rec.p, random_in_unit_sphere(), 0.),
|
||||
reflected: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lambertian<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
albedo: T,
|
||||
}
|
||||
|
||||
impl<T> Lambertian<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
pub fn new(texture: T) -> Lambertian<T> {
|
||||
Lambertian { albedo: texture }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Material for Lambertian<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse {
|
||||
let target = rec.p + rec.normal + random_in_unit_sphere();
|
||||
let (u, v) = rec.uv;
|
||||
ScatterResponse {
|
||||
attenutation: self.albedo.value(u, v, rec.p),
|
||||
scattered: Ray::new(rec.p, target - rec.p, r_in.time),
|
||||
reflected: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Metal {
|
||||
albedo: Vec3,
|
||||
fuzzy: f32,
|
||||
}
|
||||
|
||||
fn reflect(v: Vec3, n: Vec3) -> Vec3 {
|
||||
v - 2. * dot(v, n) * n
|
||||
}
|
||||
|
||||
impl Metal {
|
||||
pub fn new<V>(albedo: V, fuzzy: f32) -> Metal
|
||||
where
|
||||
V: Into<Vec3>,
|
||||
{
|
||||
let fuzzy = fuzzy.min(1.);
|
||||
Metal {
|
||||
albedo: albedo.into(),
|
||||
fuzzy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Metal {
|
||||
fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse {
|
||||
let reflected = reflect(r_in.direction.unit_vector(), rec.normal);
|
||||
let scattered = Ray::new(
|
||||
rec.p,
|
||||
reflected + self.fuzzy * random_in_unit_sphere(),
|
||||
r_in.time,
|
||||
);
|
||||
ScatterResponse {
|
||||
scattered,
|
||||
attenutation: self.albedo,
|
||||
reflected: dot(scattered.direction, rec.normal) > 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refract(v: Vec3, n: Vec3, ni_over_nt: f32) -> Option<Vec3> {
|
||||
let uv = v.unit_vector();
|
||||
let dt = dot(uv, n);
|
||||
let discriminant = 1. - ni_over_nt * ni_over_nt * (1. - dt * dt);
|
||||
if discriminant > 0. {
|
||||
return Some(ni_over_nt * (uv - n * dt) - n * discriminant.sqrt());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn schlick(cosine: f32, ref_idx: f32) -> f32 {
|
||||
let mut r0 = (1. - ref_idx) / (1. + ref_idx);
|
||||
r0 = r0 * r0;
|
||||
r0 + (1. - r0) * (1. - cosine).powf(5.)
|
||||
}
|
||||
|
||||
pub struct Dielectric {
|
||||
ref_idx: f32,
|
||||
}
|
||||
|
||||
impl Dielectric {
|
||||
pub fn new(ref_idx: f32) -> Dielectric {
|
||||
Dielectric { ref_idx }
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Dielectric {
|
||||
fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse {
|
||||
let reflected = reflect(r_in.direction, rec.normal);
|
||||
let (outward_normal, ni_over_nt, cosine) = if dot(r_in.direction, rec.normal) > 0. {
|
||||
(
|
||||
-rec.normal,
|
||||
self.ref_idx,
|
||||
self.ref_idx * dot(r_in.direction, rec.normal) / r_in.direction.length(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
rec.normal,
|
||||
1. / self.ref_idx,
|
||||
-dot(r_in.direction, rec.normal) / r_in.direction.length(),
|
||||
)
|
||||
};
|
||||
let scattered = if let Some(refracted) = refract(r_in.direction, outward_normal, ni_over_nt)
|
||||
{
|
||||
let mut rng = rand::thread_rng();
|
||||
if rng.gen_range(0., 1.) < schlick(cosine, self.ref_idx) {
|
||||
Ray::new(rec.p, reflected, r_in.time)
|
||||
} else {
|
||||
Ray::new(rec.p, refracted, r_in.time)
|
||||
}
|
||||
} else {
|
||||
Ray::new(rec.p, reflected, r_in.time)
|
||||
};
|
||||
|
||||
ScatterResponse {
|
||||
attenutation: Vec3::new(1., 1., 1.),
|
||||
scattered,
|
||||
reflected: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiffuseLight<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
emit: T,
|
||||
}
|
||||
|
||||
impl<T> DiffuseLight<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
pub fn new(emit: T) -> DiffuseLight<T> {
|
||||
DiffuseLight { emit }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Material for DiffuseLight<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
fn scatter(&self, _r_in: &Ray, _rec: &HitRecord) -> ScatterResponse {
|
||||
ScatterResponse {
|
||||
scattered: Default::default(),
|
||||
attenutation: Default::default(),
|
||||
reflected: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn emitted(&self, u: f32, v: f32, p: Vec3) -> Vec3 {
|
||||
self.emit.value(u, v, p)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::texture::ConstantTexture;
|
||||
|
||||
#[test]
|
||||
fn arc_material() {
|
||||
let white: Arc<dyn Material> =
|
||||
Arc::new(Lambertian::new(ConstantTexture::new([0.73, 0.73, 0.73])));
|
||||
|
||||
fn material_fn<M>(m: M)
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
let _ = m;
|
||||
}
|
||||
|
||||
material_fn(Arc::clone(&white));
|
||||
material_fn(Arc::clone(&white));
|
||||
}
|
||||
}
|
||||
101
rtiow/renderer/src/moving_sphere.rs
Normal file
101
rtiow/renderer/src/moving_sphere.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use crate::aabb::surrounding_box;
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::material::Material;
|
||||
use crate::ray::Ray;
|
||||
use crate::sphere::get_sphere_uv;
|
||||
use crate::vec3::dot;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct MovingSphere<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
center0: Vec3,
|
||||
center1: Vec3,
|
||||
radius: f32,
|
||||
material: M,
|
||||
time0: f32,
|
||||
time1: f32,
|
||||
}
|
||||
|
||||
impl<M> MovingSphere<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
pub fn new(
|
||||
center0: Vec3,
|
||||
center1: Vec3,
|
||||
radius: f32,
|
||||
time0: f32,
|
||||
time1: f32,
|
||||
material: M,
|
||||
) -> MovingSphere<M> {
|
||||
MovingSphere {
|
||||
center0,
|
||||
center1,
|
||||
radius,
|
||||
material,
|
||||
time0,
|
||||
time1,
|
||||
}
|
||||
}
|
||||
fn center(&self, time: f32) -> Vec3 {
|
||||
self.center0
|
||||
+ ((time - self.time0) / (self.time1 - self.time0)) * (self.center1 - self.center0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Hit for MovingSphere<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
fn hit(&self, r: Ray, t0: f32, t1: f32) -> Option<HitRecord> {
|
||||
let oc = r.origin - self.center(r.time);
|
||||
let a = dot(r.direction, r.direction);
|
||||
let b = dot(oc, r.direction);
|
||||
let c = dot(oc, oc) - self.radius * self.radius;
|
||||
let discriminant = b * b - a * c;
|
||||
if discriminant > 0. {
|
||||
let temp = (-b - (b * b - a * c).sqrt()) / a;
|
||||
if temp < t1 && temp > t0 {
|
||||
let point = r.point_at_parameter(temp);
|
||||
let uv = get_sphere_uv((point - self.center(r.time)) / self.radius);
|
||||
return Some(HitRecord {
|
||||
t: temp,
|
||||
uv,
|
||||
p: point,
|
||||
normal: (point - self.center(r.time)) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
let temp = (-b + (b * b - a * c).sqrt()) / a;
|
||||
if temp < t1 && temp > t0 {
|
||||
let point = r.point_at_parameter(temp);
|
||||
let uv = get_sphere_uv((point - self.center(r.time)) / self.radius);
|
||||
return Some(HitRecord {
|
||||
t: temp,
|
||||
uv,
|
||||
p: point,
|
||||
normal: (point - self.center(r.time)) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
let t_min_bb = AABB::new(
|
||||
self.center0 - Vec3::new(self.radius, self.radius, self.radius),
|
||||
self.center0 + Vec3::new(self.radius, self.radius, self.radius),
|
||||
);
|
||||
|
||||
let t_max_bb = AABB::new(
|
||||
self.center1 - Vec3::new(self.radius, self.radius, self.radius),
|
||||
self.center1 + Vec3::new(self.radius, self.radius, self.radius),
|
||||
);
|
||||
Some(surrounding_box(&t_min_bb, &t_max_bb))
|
||||
}
|
||||
}
|
||||
68
rtiow/renderer/src/noise/lode.rs
Normal file
68
rtiow/renderer/src/noise/lode.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use log::trace;
|
||||
/// Implements the concepts from https://lodev.org/cgtutor/randomnoise.html
|
||||
use rand;
|
||||
|
||||
use crate::noise::NoiseSource;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
const NOISE_SIZE: usize = 128;
|
||||
pub struct Lode {
|
||||
// Using fixed array causes stack overflow.
|
||||
noise: Vec<Vec<Vec<f32>>>, //[[[f32; NOISE_SIZE]; NOISE_SIZE]; NOISE_SIZE],
|
||||
}
|
||||
|
||||
impl Lode {
|
||||
pub fn new<R>(rng: &mut R) -> Lode
|
||||
where
|
||||
R: rand::Rng,
|
||||
{
|
||||
let mut noise = vec![vec![vec![0.; NOISE_SIZE]; NOISE_SIZE]; NOISE_SIZE];
|
||||
// Squelch warning about only being used to index, as this way reads more consistently.
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for x in 0..NOISE_SIZE {
|
||||
for y in 0..NOISE_SIZE {
|
||||
for z in 0..NOISE_SIZE {
|
||||
noise[x][y][z] = rng.gen_range(0., 1.);
|
||||
}
|
||||
}
|
||||
}
|
||||
Lode { noise }
|
||||
}
|
||||
}
|
||||
|
||||
impl NoiseSource for Lode {
|
||||
fn value(&self, p: Vec3) -> f32 {
|
||||
// Fractional part of vector components
|
||||
let x = p.x - p.x.floor();
|
||||
let y = p.y - p.y.floor();
|
||||
let z = p.z - p.z.floor();
|
||||
|
||||
// Wrap around the arrays
|
||||
let x1 = (p.x.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
|
||||
let y1 = (p.y.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
|
||||
let z1 = (p.z.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
|
||||
|
||||
// Neighbor values
|
||||
let x2 = (x1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
|
||||
let y2 = (y1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
|
||||
let z2 = (z1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
|
||||
|
||||
trace!(target: "noise", "p {}", p);
|
||||
trace!(target: "noise", "x {:.2} y {:.2} z {:.2}", x, y, z);
|
||||
trace!(target: "noise", "x1 {:.2} y1 {:.2} z1 {:.2}", x1, y1, z1);
|
||||
trace!(target: "noise", "x2 {:.2} y2 {:.2} z2 {:.2}", x2, y2, z2);
|
||||
let mut value = 0.;
|
||||
// Smooth the noise with bilinear interpolation. Completely untested
|
||||
value += x * y * z * self.noise[x1][y1][z1];
|
||||
value += (1. - x) * y * z * self.noise[x2][y1][z1];
|
||||
value += x * (1. - y) * z * self.noise[x1][y2][z1];
|
||||
value += x * y * (1. - z) * self.noise[x1][y1][z2];
|
||||
value += (1. - x) * (1. - y) * z * self.noise[x2][y2][z1];
|
||||
value += x * (1. - y) * (1. - z) * self.noise[x1][y2][z2];
|
||||
value += (1. - x) * y * (1. - z) * self.noise[x2][y1][z2];
|
||||
value += (1. - x) * (1. - y) * (1. - z) * self.noise[x2][y2][z2];
|
||||
|
||||
trace!(target: "noise", "luma {}", value);
|
||||
value
|
||||
}
|
||||
}
|
||||
96
rtiow/renderer/src/noise/mod.rs
Normal file
96
rtiow/renderer/src/noise/mod.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
pub mod lode;
|
||||
pub mod perlin;
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub trait NoiseSource: Send + Sync {
|
||||
/// value returns noise on the interval [0., 1.).
|
||||
fn value(&self, p: Vec3) -> f32;
|
||||
|
||||
fn marble(&self, p: Vec3, period: Vec3, power: f32, size: usize, scale: f32) -> f32 {
|
||||
let p = p / scale;
|
||||
// TODO(wathiede): can't understand why 255 works for perlin and lode, maybe it's near 360
|
||||
// degrees and interacts with the sine function?
|
||||
let xyz_value = p.x * period.x / 255.
|
||||
+ p.y * period.y / 255.
|
||||
+ p.z * period.z / 255.
|
||||
+ power * self.turbulence(p, size);
|
||||
(xyz_value * PI).sin().abs()
|
||||
}
|
||||
|
||||
fn turbulence(&self, p: Vec3, factor: usize) -> f32 {
|
||||
let mut value = 0.;
|
||||
let initial_factor = factor;
|
||||
let mut factor = factor;
|
||||
|
||||
while factor >= 1 {
|
||||
value += self.value(p / factor as f32) * factor as f32;
|
||||
factor /= 2;
|
||||
}
|
||||
0.5 * value / initial_factor as f32
|
||||
}
|
||||
|
||||
fn scaled(&self, p: Vec3, scale: f32) -> f32 {
|
||||
let p = p / scale;
|
||||
self.value(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl NoiseSource for Box<dyn NoiseSource> {
|
||||
fn value(&self, p: Vec3) -> f32 {
|
||||
(**self).value(p)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NoiseType {
|
||||
Scale(f32),
|
||||
Turbulence(usize),
|
||||
Marble {
|
||||
period: Vec3,
|
||||
power: f32,
|
||||
size: usize,
|
||||
scale: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl NoiseType {
|
||||
pub fn to_url(&self) -> String {
|
||||
match &self {
|
||||
NoiseType::Scale(scale) => format!("scale/{}", scale),
|
||||
NoiseType::Turbulence(turbulence) => format!("turbulence/{}", turbulence),
|
||||
NoiseType::Marble {
|
||||
period,
|
||||
power,
|
||||
size,
|
||||
scale,
|
||||
} => format!(
|
||||
"marble/period/{},{},{}/power/{}/size/{}/scale/{}",
|
||||
period.x, period.y, period.z, power, size, scale
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parameters(&self) -> Vec<(&str, String)> {
|
||||
match &self {
|
||||
NoiseType::Scale(scale) => vec![("Scale", scale.to_string())],
|
||||
NoiseType::Turbulence(turbulence) => vec![("Turbulence", turbulence.to_string())],
|
||||
NoiseType::Marble {
|
||||
period,
|
||||
power,
|
||||
size,
|
||||
scale,
|
||||
} => vec![
|
||||
("Period", period.to_string()),
|
||||
("Power", power.to_string()),
|
||||
("Size", size.to_string()),
|
||||
("Scale", scale.to_string()),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
152
rtiow/renderer/src/noise/perlin.rs
Normal file
152
rtiow/renderer/src/noise/perlin.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
// There are many math functions in this file, so we allow single letter variable names.
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
use log::trace;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::noise::NoiseSource;
|
||||
use crate::vec3::dot;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct Perlin {
|
||||
ran_vec: Vec<Vec3>,
|
||||
perm_x: Vec<usize>,
|
||||
perm_y: Vec<usize>,
|
||||
perm_z: Vec<usize>,
|
||||
}
|
||||
|
||||
fn perlin_generate<R>(rng: &mut R) -> Vec<Vec3>
|
||||
where
|
||||
R: Rng,
|
||||
{
|
||||
(0..256)
|
||||
.map(|_| {
|
||||
Vec3::new(
|
||||
rng.gen_range(-1., 1.),
|
||||
rng.gen_range(-1., 1.),
|
||||
rng.gen_range(-1., 1.),
|
||||
)
|
||||
.unit_vector()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn perlin_generate_perm<R>(rng: &mut R) -> Vec<usize>
|
||||
where
|
||||
R: Rng,
|
||||
{
|
||||
let mut p: Vec<usize> = (0..256).map(|i| i).collect();
|
||||
rng.shuffle(&mut p);
|
||||
p
|
||||
}
|
||||
|
||||
fn perlin_interp(c: [[[Vec3; 2]; 2]; 2], u: f32, v: f32, w: f32) -> f32 {
|
||||
// Hermite cubic to round off interpolation and attempt to address 'mach bands'.
|
||||
let uu = u * u * (3. - 2. * u);
|
||||
let vv = v * v * (3. - 2. * v);
|
||||
let ww = w * w * (3. - 2. * w);
|
||||
|
||||
let mut accum = 0.;
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
for k in 0..2 {
|
||||
let weight_v = Vec3::new(u - i as f32, v - j as f32, w - k as f32);
|
||||
|
||||
let i = i as f32;
|
||||
let j = j as f32;
|
||||
let k = k as f32;
|
||||
accum += (i * uu + (1. - i) * (1. - uu))
|
||||
* (j * vv + (1. - j) * (1. - vv))
|
||||
* (k * ww + (1. - k) * (1. - ww))
|
||||
* dot(c[i as usize][j as usize][k as usize], weight_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
//info!("u {} v {} accum {}", u, v, accum);
|
||||
accum
|
||||
}
|
||||
|
||||
impl Perlin {
|
||||
pub fn new<R>(rng: &mut R) -> Perlin
|
||||
where
|
||||
R: Rng,
|
||||
{
|
||||
let p = Perlin {
|
||||
ran_vec: perlin_generate(rng),
|
||||
perm_x: perlin_generate_perm(rng),
|
||||
perm_y: perlin_generate_perm(rng),
|
||||
perm_z: perlin_generate_perm(rng),
|
||||
};
|
||||
trace!(target: "perlin",
|
||||
"ran_vec: {}",
|
||||
p.ran_vec
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
trace!(target: "perlin",
|
||||
"perm_x: {}",
|
||||
p.perm_x
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
trace!(target: "perlin",
|
||||
"perm_y: {}",
|
||||
p.perm_y
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
trace!(target: "perlin",
|
||||
"perm_z: {}",
|
||||
p.perm_z
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
p
|
||||
}
|
||||
|
||||
pub fn turb(&self, p: Vec3, depth: usize) -> f32 {
|
||||
let mut accum = 0.;
|
||||
let mut temp_p = p;
|
||||
let mut weight = 1.;
|
||||
for _ in 0..depth {
|
||||
accum += weight * self.value(temp_p);
|
||||
weight *= 0.5;
|
||||
temp_p = temp_p * 0.2;
|
||||
}
|
||||
accum.abs()
|
||||
}
|
||||
}
|
||||
|
||||
impl NoiseSource for Perlin {
|
||||
fn value(&self, p: Vec3) -> f32 {
|
||||
let u = p.x - p.x.floor();
|
||||
let v = p.y - p.y.floor();
|
||||
let w = p.z - p.z.floor();
|
||||
|
||||
let i = p.x.floor() as usize;
|
||||
let j = p.y.floor() as usize;
|
||||
let k = p.z.floor() as usize;
|
||||
|
||||
let mut c: [[[Vec3; 2]; 2]; 2] = Default::default();
|
||||
|
||||
// Squelch warning about di only being used to index, as this way reads more consistently.
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for di in 0..2 {
|
||||
for dj in 0..2 {
|
||||
for dk in 0..2 {
|
||||
c[di][dj][dk] = self.ran_vec[self.perm_x[(i + di) & 255]
|
||||
^ self.perm_y[(j + dj) & 255]
|
||||
^ self.perm_z[(k + dk) & 255]]
|
||||
}
|
||||
}
|
||||
}
|
||||
(1. + perlin_interp(c, u, v, w)) * 0.5
|
||||
}
|
||||
}
|
||||
222
rtiow/renderer/src/output.rs
Normal file
222
rtiow/renderer/src/output.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time;
|
||||
|
||||
use chrono::Local;
|
||||
use image;
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use serde_derive::Serialize;
|
||||
|
||||
use crate::renderer::Scene;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
// Main RGB image output from rendering the scene.
|
||||
pub const MAIN_IMAGE: &str = "@final";
|
||||
// Debug image for adaptive pixels subsampling.
|
||||
// Red indicates recursion hit maximum depth splitting the pixel.
|
||||
// Green indicates no subdivision necessary
|
||||
pub const ADAPTIVE_DEPTH: &str = "adaptive_depth";
|
||||
// Grey scale showing rays cast per pixel.
|
||||
pub const RAYS_PER_PIXEL: &str = "rays_per_pixel";
|
||||
|
||||
lazy_static! {
|
||||
static ref DEBUGGER: Arc<Mutex<Debugger>> = Arc::new(Mutex::new(Debugger::new()));
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ImageMetadata {
|
||||
name: String,
|
||||
image: String,
|
||||
binary: String,
|
||||
ratio: f32,
|
||||
size: (usize, usize),
|
||||
format: ImageType,
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Data<'s> {
|
||||
timestamp: i64,
|
||||
render_time_seconds: f32,
|
||||
scene: &'s Scene,
|
||||
image_metadata: Vec<ImageMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub enum ImageType {
|
||||
RGB01,
|
||||
Grey01,
|
||||
GreyNormalized,
|
||||
}
|
||||
|
||||
struct Image {
|
||||
w: usize,
|
||||
h: usize,
|
||||
pix: Vec<Vec3>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn new(w: usize, h: usize) -> Image {
|
||||
Image {
|
||||
w,
|
||||
h,
|
||||
pix: (0..w * h).map(|_| [0., 0., 0.].into()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn put_pixel(&mut self, x: usize, y: usize, p: Vec3) {
|
||||
let offset = x + y * self.w;
|
||||
self.pix[offset] = p;
|
||||
}
|
||||
}
|
||||
|
||||
struct Debugger {
|
||||
images: HashMap<String, (ImageType, Image)>,
|
||||
}
|
||||
|
||||
impl Debugger {
|
||||
fn new() -> Debugger {
|
||||
Debugger {
|
||||
images: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_image(name: String, dimensions: (usize, usize), it: ImageType) {
|
||||
let mut debugger = DEBUGGER.lock().unwrap();
|
||||
debugger
|
||||
.images
|
||||
.insert(name, (it, Image::new(dimensions.0, dimensions.1)));
|
||||
}
|
||||
|
||||
pub fn set_pixel(name: &str, x: usize, y: usize, pixel: Vec3) {
|
||||
let mut debugger = DEBUGGER.lock().unwrap();
|
||||
let (_it, img) = debugger
|
||||
.images
|
||||
.get_mut(name)
|
||||
.expect(&format!("couldn't find image named '{}'", name));
|
||||
let y_inv = img.h - y - 1;
|
||||
img.put_pixel(x, y_inv, pixel);
|
||||
}
|
||||
|
||||
pub fn set_pixel_grey(name: &str, x: usize, y: usize, grey: f32) {
|
||||
let mut debugger = DEBUGGER.lock().unwrap();
|
||||
let (_it, img) = debugger
|
||||
.images
|
||||
.get_mut(name)
|
||||
.expect(&format!("couldn't find image named '{}'", name));
|
||||
let y_inv = img.h - y - 1;
|
||||
img.put_pixel(x, y_inv, [grey, grey, grey].into());
|
||||
}
|
||||
|
||||
trait ImageSaver {
|
||||
fn save<Q>(&self, path: Q) -> std::io::Result<()>
|
||||
where
|
||||
Q: AsRef<Path> + Sized;
|
||||
}
|
||||
|
||||
pub fn write_images<P: AsRef<Path>>(
|
||||
scene: &Scene,
|
||||
render_time: time::Duration,
|
||||
output_dir: P,
|
||||
) -> std::io::Result<()> {
|
||||
let output_dir: &Path = output_dir.as_ref();
|
||||
let debugger = DEBUGGER.lock().unwrap();
|
||||
let now = Local::now();
|
||||
// Write out images in consistent order.
|
||||
let mut names = debugger.images.keys().collect::<Vec<_>>();
|
||||
names.sort();
|
||||
let mut image_metadata = Vec::new();
|
||||
for name in &names {
|
||||
let (it, img) = debugger.images.get(*name).unwrap();
|
||||
let image = format!("{}.png", name);
|
||||
let binary = format!("{}.json", name);
|
||||
let ratio = img.w as f32 / img.h as f32;
|
||||
let size = (img.w, img.h);
|
||||
let image_path = output_dir.join(&image);
|
||||
let binary_path = output_dir.join(&binary);
|
||||
image_metadata.push(ImageMetadata {
|
||||
name: name.to_string(),
|
||||
image,
|
||||
binary,
|
||||
ratio,
|
||||
size,
|
||||
format: *it,
|
||||
});
|
||||
info!("Saving {}", image_path.to_string_lossy());
|
||||
match it {
|
||||
ImageType::RGB01 => {
|
||||
let mut out_img = image::RgbImage::new(img.w as u32, img.h as u32);
|
||||
out_img
|
||||
.enumerate_pixels_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, (_x, _y, p))| {
|
||||
let pixel = img.pix[i];
|
||||
*p = image::Rgb([
|
||||
(pixel[0] * 255.).min(255.) as u8,
|
||||
(pixel[1] * 255.).min(255.) as u8,
|
||||
(pixel[2] * 255.).min(255.) as u8,
|
||||
])
|
||||
});
|
||||
out_img.save(image_path)?;
|
||||
}
|
||||
ImageType::Grey01 => {
|
||||
let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32);
|
||||
out_img
|
||||
.enumerate_pixels_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, (_x, _y, p))| {
|
||||
let pixel = img.pix[i];
|
||||
*p = image::Luma([(pixel[0] * 255.).min(255.) as u8])
|
||||
});
|
||||
out_img.save(image_path)?;
|
||||
}
|
||||
ImageType::GreyNormalized => {
|
||||
let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32);
|
||||
|
||||
let max_val = img.pix.iter().map(|v| v.x).fold(0., f32::max);
|
||||
out_img
|
||||
.enumerate_pixels_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, (_x, _y, p))| {
|
||||
let pixel = img.pix[i];
|
||||
*p = image::Luma([(pixel[0] / max_val * 255.).min(255.) as u8])
|
||||
});
|
||||
out_img.save(image_path)?;
|
||||
}
|
||||
};
|
||||
info!("Saving {}", binary_path.to_string_lossy());
|
||||
let f = File::create(output_dir.join(binary_path))?;
|
||||
let f = BufWriter::new(f);
|
||||
match it {
|
||||
ImageType::RGB01 => {
|
||||
serde_json::ser::to_writer(
|
||||
f,
|
||||
&img.pix
|
||||
.iter()
|
||||
.map(|v| [v.x, v.y, v.z])
|
||||
.collect::<Vec<[f32; 3]>>(),
|
||||
)?;
|
||||
}
|
||||
ImageType::Grey01 | ImageType::GreyNormalized => {
|
||||
serde_json::ser::to_writer(f, &img.pix.iter().map(|v| v.x).collect::<Vec<f32>>())?;
|
||||
}
|
||||
};
|
||||
}
|
||||
let f = File::create(output_dir.join("data.json"))?;
|
||||
let f = BufWriter::new(f);
|
||||
serde_json::ser::to_writer(
|
||||
f,
|
||||
&Data {
|
||||
timestamp: now.timestamp(),
|
||||
render_time_seconds: render_time.as_secs_f32(),
|
||||
scene,
|
||||
image_metadata,
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
35
rtiow/renderer/src/ray.rs
Normal file
35
rtiow/renderer/src/ray.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Ray {
|
||||
pub origin: Vec3,
|
||||
pub direction: Vec3,
|
||||
pub time: f32,
|
||||
pub inv_direction: Vec3,
|
||||
pub sign: [usize; 3],
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new<V>(origin: V, direction: V, time: f32) -> Ray
|
||||
where
|
||||
V: Into<Vec3>,
|
||||
{
|
||||
let direction = direction.into();
|
||||
let origin = origin.into();
|
||||
let inv = 1. / direction;
|
||||
Ray {
|
||||
origin,
|
||||
direction,
|
||||
time,
|
||||
inv_direction: inv,
|
||||
sign: [
|
||||
(inv.x < 0.) as usize,
|
||||
(inv.y < 0.) as usize,
|
||||
(inv.z < 0.) as usize,
|
||||
],
|
||||
}
|
||||
}
|
||||
pub fn point_at_parameter(self, t: f32) -> Vec3 {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
}
|
||||
191
rtiow/renderer/src/rect.rs
Normal file
191
rtiow/renderer/src/rect.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
// There are many math functions in this file, so we allow single letter variable names.
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::material::Material;
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct XYRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
x0: f32,
|
||||
x1: f32,
|
||||
y0: f32,
|
||||
y1: f32,
|
||||
k: f32,
|
||||
material: M,
|
||||
}
|
||||
|
||||
impl<M> XYRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
pub fn new(x0: f32, x1: f32, y0: f32, y1: f32, k: f32, material: M) -> XYRect<M> {
|
||||
XYRect {
|
||||
x0,
|
||||
x1,
|
||||
y0,
|
||||
y1,
|
||||
k,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Hit for XYRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let t = (self.k - r.origin.z) / r.direction.z;
|
||||
if t < t_min || t > t_max {
|
||||
return None;
|
||||
}
|
||||
let x = r.origin.x + t * r.direction.x;
|
||||
let y = r.origin.y + t * r.direction.y;
|
||||
if x < self.x0 || x > self.x1 || y < self.y0 || y > self.y1 {
|
||||
return None;
|
||||
}
|
||||
let u = (x - self.x0) / (self.x1 - self.x0);
|
||||
let v = (y - self.y0) / (self.y1 - self.y0);
|
||||
Some(HitRecord {
|
||||
t,
|
||||
uv: (u, v),
|
||||
p: r.point_at_parameter(t),
|
||||
normal: Vec3::new(0., 0., 1.),
|
||||
material: &self.material,
|
||||
})
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
Some(AABB::new(
|
||||
Vec3::new(self.x0, self.y0, self.k - 0.0001),
|
||||
Vec3::new(self.x1, self.y1, self.k + 0.0001),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct XZRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
x0: f32,
|
||||
x1: f32,
|
||||
z0: f32,
|
||||
z1: f32,
|
||||
k: f32,
|
||||
material: M,
|
||||
}
|
||||
|
||||
impl<M> XZRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
pub fn new(x0: f32, x1: f32, z0: f32, z1: f32, k: f32, material: M) -> XZRect<M> {
|
||||
XZRect {
|
||||
x0,
|
||||
x1,
|
||||
z0,
|
||||
z1,
|
||||
k,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Hit for XZRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let t = (self.k - r.origin.y) / r.direction.y;
|
||||
if t < t_min || t > t_max {
|
||||
return None;
|
||||
}
|
||||
let x = r.origin.x + t * r.direction.x;
|
||||
let z = r.origin.z + t * r.direction.z;
|
||||
if x < self.x0 || x > self.x1 || z < self.z0 || z > self.z1 {
|
||||
return None;
|
||||
}
|
||||
let u = (x - self.x0) / (self.x1 - self.x0);
|
||||
let v = (z - self.z0) / (self.z1 - self.z0);
|
||||
Some(HitRecord {
|
||||
t,
|
||||
uv: (u, v),
|
||||
p: r.point_at_parameter(t),
|
||||
normal: Vec3::new(0., 1., 0.),
|
||||
material: &self.material,
|
||||
})
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
Some(AABB::new(
|
||||
Vec3::new(self.x0, self.k - 0.0001, self.z0),
|
||||
Vec3::new(self.x1, self.k + 0.0001, self.z1),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct YZRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
y0: f32,
|
||||
y1: f32,
|
||||
k: f32,
|
||||
z0: f32,
|
||||
z1: f32,
|
||||
material: M,
|
||||
}
|
||||
|
||||
impl<M> YZRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
pub fn new(y0: f32, y1: f32, z0: f32, z1: f32, k: f32, material: M) -> YZRect<M> {
|
||||
YZRect {
|
||||
y0,
|
||||
y1,
|
||||
z0,
|
||||
z1,
|
||||
k,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Hit for YZRect<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let t = (self.k - r.origin.x) / r.direction.x;
|
||||
if t < t_min || t > t_max {
|
||||
return None;
|
||||
}
|
||||
let y = r.origin.y + t * r.direction.y;
|
||||
let z = r.origin.z + t * r.direction.z;
|
||||
if y < self.y0 || y > self.y1 || z < self.z0 || z > self.z1 {
|
||||
return None;
|
||||
}
|
||||
let u = (y - self.y0) / (self.y1 - self.y0);
|
||||
let v = (z - self.z0) / (self.z1 - self.z0);
|
||||
Some(HitRecord {
|
||||
t,
|
||||
uv: (u, v),
|
||||
p: r.point_at_parameter(t),
|
||||
normal: Vec3::new(1., 0., 0.),
|
||||
material: &self.material,
|
||||
})
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
Some(AABB::new(
|
||||
Vec3::new(self.k - 0.0001, self.y0, self.z0),
|
||||
Vec3::new(self.k + 0.0001, self.y1, self.z1),
|
||||
))
|
||||
}
|
||||
}
|
||||
649
rtiow/renderer/src/renderer.rs
Normal file
649
rtiow/renderer/src/renderer.rs
Normal file
@@ -0,0 +1,649 @@
|
||||
use std::fmt;
|
||||
use std::ops::AddAssign;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
use std::sync;
|
||||
use std::sync::mpsc::sync_channel;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::SyncSender;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
use core_affinity;
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use num_cpus;
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
use serde_derive::Serialize;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::human;
|
||||
use crate::material::Lambertian;
|
||||
use crate::output;
|
||||
use crate::ray::Ray;
|
||||
use crate::scenes;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::texture::EnvMap;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Model {
|
||||
BVH,
|
||||
Bench,
|
||||
Book,
|
||||
CornellBox,
|
||||
CornellSmoke,
|
||||
Final,
|
||||
Mandelbrot,
|
||||
PerlinDebug,
|
||||
Spheramid,
|
||||
Test,
|
||||
Tutorial,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn scene(&self, opt: &Opt) -> Scene {
|
||||
match self {
|
||||
Model::BVH => scenes::bvh::new(&opt),
|
||||
Model::Bench => scenes::bench::new(&opt),
|
||||
Model::Book => scenes::book::new(&opt),
|
||||
Model::CornellBox => scenes::cornell_box::new(&opt),
|
||||
Model::CornellSmoke => scenes::cornell_smoke::new(&opt),
|
||||
Model::Final => scenes::final_scene::new(&opt),
|
||||
Model::Mandelbrot => scenes::mandelbrot::new(&opt),
|
||||
Model::PerlinDebug => scenes::perlin_debug::new(&opt),
|
||||
Model::Spheramid => scenes::spheramid::new(&opt),
|
||||
Model::Test => scenes::test::new(&opt),
|
||||
Model::Tutorial => scenes::tutorial::new(&opt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModelParseError(String);
|
||||
|
||||
impl fmt::Display for ModelParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "unknown model enum type '{}'", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Model {
|
||||
type Err = ModelParseError;
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s {
|
||||
"bench" => Ok(Model::Bench),
|
||||
"book" => Ok(Model::Book),
|
||||
"bvh" => Ok(Model::BVH),
|
||||
"cornell_box" => Ok(Model::CornellBox),
|
||||
"cornell_smoke" => Ok(Model::CornellSmoke),
|
||||
"final" => Ok(Model::Final),
|
||||
"mandelbrot" => Ok(Model::Mandelbrot),
|
||||
"perlin_debug" => Ok(Model::PerlinDebug),
|
||||
"spheramid" => Ok(Model::Spheramid),
|
||||
"test" => Ok(Model::Test),
|
||||
"tutorial" => Ok(Model::Tutorial),
|
||||
_ => Err(ModelParseError(s.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::string::ToString for Model {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Model::BVH => "bvh".to_string(),
|
||||
Model::Bench => "bench".to_string(),
|
||||
Model::Book => "book".to_string(),
|
||||
Model::CornellBox => "cornell_box".to_string(),
|
||||
Model::CornellSmoke => "cornell_smoke".to_string(),
|
||||
Model::Final => "final".to_string(),
|
||||
Model::Mandelbrot => "mandelbrot".to_string(),
|
||||
Model::PerlinDebug => "perlin_debug".to_string(),
|
||||
Model::Spheramid => "spheramid".to_string(),
|
||||
Model::Test => "test".to_string(),
|
||||
Model::Tutorial => "tutorial".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "tracer", about = "An experimental ray tracer.")]
|
||||
pub struct Opt {
|
||||
/// Image width
|
||||
#[structopt(short = "w", long = "width", default_value = "512")]
|
||||
pub width: usize,
|
||||
/// Image height
|
||||
#[structopt(short = "h", long = "height", default_value = "512")]
|
||||
pub height: usize,
|
||||
/// Number of threads
|
||||
#[structopt(short = "t", long = "num_threads")]
|
||||
pub num_threads: Option<usize>,
|
||||
/// Sub-samples per pixel
|
||||
#[structopt(short = "s", long = "subsample", default_value = "8")]
|
||||
pub subsamples: usize,
|
||||
/// Select scene to render, one of: "bench", "book", "tutorial", "bvh", "test", "cornell_box",
|
||||
/// "cornell_smoke", "perlin_debug", "final"
|
||||
#[structopt(long = "model", default_value = "book")]
|
||||
pub model: Model,
|
||||
/// Path to store pprof profile data, i.e. /tmp/cpuprofile.pprof
|
||||
#[structopt(long = "pprof", parse(from_os_str))]
|
||||
pub pprof: Option<PathBuf>,
|
||||
/// Use acceleration data structure, may be BVH or kd-tree depending on scene.
|
||||
#[structopt(long = "use_accel")]
|
||||
pub use_accel: bool,
|
||||
|
||||
/// Output directory
|
||||
#[structopt(parse(from_os_str), default_value = "/tmp/tracer")]
|
||||
pub output: PathBuf,
|
||||
}
|
||||
|
||||
pub fn opt_hash(opt: &Opt) -> String {
|
||||
// TODO(wathiede): add threads.
|
||||
format!(
|
||||
"w:{}-h:{}-s:{}-pprof:{}-model:{}-use_accel:{}-{}",
|
||||
opt.width,
|
||||
opt.height,
|
||||
opt.subsamples,
|
||||
opt.pprof.is_some(),
|
||||
opt.model.to_string(),
|
||||
opt.use_accel,
|
||||
opt.output.display().to_string().replace("/", "_")
|
||||
)
|
||||
}
|
||||
|
||||
// TODO(wathiede): implement the skips and then the renderer could use json as an input file type.
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Scene {
|
||||
#[serde(skip)]
|
||||
pub world: Box<dyn Hit>,
|
||||
#[serde(skip)]
|
||||
pub camera: Camera,
|
||||
pub subsamples: usize,
|
||||
/// overrides subsamples setting.
|
||||
pub adaptive_subsampling: Option<f32>,
|
||||
pub num_threads: Option<usize>,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub global_illumination: bool,
|
||||
#[serde(skip)]
|
||||
pub env_map: Option<EnvMap>,
|
||||
}
|
||||
|
||||
impl Default for Scene {
|
||||
fn default() -> Scene {
|
||||
let lookfrom = Vec3::new(20., 20., 20.);
|
||||
let lookat = Vec3::new(0., 0., 0.);
|
||||
let dist_to_focus = (lookfrom - lookat).length();
|
||||
let aperture = 0.1;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
70.,
|
||||
1.,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
Scene {
|
||||
world: Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
1.0,
|
||||
Lambertian::new(ConstantTexture::new([0., 1., 0.])),
|
||||
)),
|
||||
camera,
|
||||
subsamples: 0,
|
||||
adaptive_subsampling: None,
|
||||
num_threads: None,
|
||||
width: 0,
|
||||
height: 0,
|
||||
global_illumination: false,
|
||||
env_map: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// color will trace ray up to 50 bounces deep accumulating color as it goes. If
|
||||
// global_illumination is true, a default light background color is assumed and will light the
|
||||
// world. If false, it is expected the scene has emissive light sources.
|
||||
fn color(
|
||||
r: Ray,
|
||||
world: &dyn Hit,
|
||||
depth: usize,
|
||||
global_illumination: bool,
|
||||
env_map: &Option<EnvMap>,
|
||||
) -> (Vec3, usize) {
|
||||
if let Some(rec) = world.hit(r, 0.001, std::f32::MAX) {
|
||||
let (u, v) = rec.uv;
|
||||
let emitted = rec.material.emitted(u, v, rec.p);
|
||||
let scatter_response = rec.material.scatter(&r, &rec);
|
||||
if depth < 50 && scatter_response.reflected {
|
||||
let (c, rays) = color(
|
||||
scatter_response.scattered,
|
||||
world,
|
||||
depth + 1,
|
||||
global_illumination,
|
||||
env_map,
|
||||
);
|
||||
return (emitted + scatter_response.attenutation * c, rays + 1);
|
||||
} else {
|
||||
return (emitted, 1);
|
||||
}
|
||||
}
|
||||
if global_illumination {
|
||||
return match env_map {
|
||||
Some(env_map) => (env_map.color(r.direction.unit_vector()), 1),
|
||||
None => {
|
||||
let unit_direction = r.direction.unit_vector();
|
||||
// No hit, choose color from background.
|
||||
let t = 0.5 * (unit_direction.y + 1.);
|
||||
(
|
||||
Vec3::new(1., 1., 1.) * (1. - t) + Vec3::new(0.5, 0.7, 1.) * t,
|
||||
1,
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
// No global illumination, so background is black.
|
||||
(Vec3::new(0., 0., 0.), 1)
|
||||
}
|
||||
|
||||
const MAX_ADAPTIVE_DEPTH: usize = 10;
|
||||
fn trace_pixel_adaptive(
|
||||
depth: usize,
|
||||
threshold: f32,
|
||||
x: usize,
|
||||
y: usize,
|
||||
x_range: Range<f32>,
|
||||
y_range: Range<f32>,
|
||||
scene: &Scene,
|
||||
) -> (Vec3, usize) {
|
||||
let w = scene.width as f32;
|
||||
let h = scene.height as f32;
|
||||
let x_mid = x_range.start + ((x_range.end - x_range.start) / 2.);
|
||||
let y_mid = y_range.start + ((y_range.end - y_range.start) / 2.);
|
||||
let mc = ((x_mid + x as f32) / w, (y_mid + y as f32) / h);
|
||||
let (center, rays) = color(
|
||||
scene.camera.get_ray(mc.0, mc.1),
|
||||
scene.world.as_ref(),
|
||||
0,
|
||||
scene.global_illumination,
|
||||
&scene.env_map,
|
||||
);
|
||||
if depth == 0 {
|
||||
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [1., 0., 0.].into());
|
||||
return (center, rays);
|
||||
}
|
||||
// t = top
|
||||
// m = middle
|
||||
// b = bottom
|
||||
// l = left
|
||||
// c = center
|
||||
// r = right
|
||||
let tl = (
|
||||
(x_range.start + x as f32) / w,
|
||||
(y_range.start + y as f32) / h,
|
||||
);
|
||||
let tr = ((x_range.end + x as f32) / w, (y_range.start + y as f32) / h);
|
||||
let bl = ((x_range.start + x as f32) / w, (y_range.end + y as f32) / h);
|
||||
let br = ((x_range.end + x as f32) / w, (y_range.end + y as f32) / h);
|
||||
|
||||
let (corners, rays) = [tl, tr, mc, bl, br]
|
||||
.iter()
|
||||
.map(|(u, v)| {
|
||||
color(
|
||||
scene.camera.get_ray(*u, *v),
|
||||
scene.world.as_ref(),
|
||||
0,
|
||||
scene.global_illumination,
|
||||
&scene.env_map,
|
||||
)
|
||||
})
|
||||
.fold(
|
||||
([0., 0., 0.].into(), 0),
|
||||
|(p1, r1): (Vec3, usize), (p2, r2): (Vec3, usize)| ((p1 + p2), (r1 + r2)),
|
||||
);
|
||||
let corners = corners / 5.;
|
||||
if (corners - center).length() > threshold {
|
||||
let tl = trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_range.start..x_mid,
|
||||
y_range.start..y_mid,
|
||||
scene,
|
||||
);
|
||||
let tr = trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_mid..x_range.end,
|
||||
y_range.start..y_mid,
|
||||
scene,
|
||||
);
|
||||
let bl = trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_range.start..x_mid,
|
||||
y_mid..y_range.end,
|
||||
scene,
|
||||
);
|
||||
let br = trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_mid..x_range.end,
|
||||
y_mid..y_range.end,
|
||||
scene,
|
||||
);
|
||||
let pixel = (tl.0 + tr.0 + bl.0 + br.0) / 4.;
|
||||
let rays = tl.1 + tr.1 + bl.1 + br.1;
|
||||
(pixel, rays)
|
||||
} else {
|
||||
if depth == MAX_ADAPTIVE_DEPTH {
|
||||
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [0., 1., 0.].into());
|
||||
}
|
||||
(corners, rays)
|
||||
}
|
||||
}
|
||||
|
||||
fn trace_pixel_random(x: usize, y: usize, scene: &Scene) -> (Vec3, usize) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let u = (rng.gen_range(0., 1.) + x as f32) / scene.width as f32;
|
||||
let v = (rng.gen_range(0., 1.) + y as f32) / scene.height as f32;
|
||||
let ray = scene.camera.get_ray(u, v);
|
||||
color(
|
||||
ray,
|
||||
scene.world.as_ref(),
|
||||
0,
|
||||
scene.global_illumination,
|
||||
&scene.env_map,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct RenderStats {
|
||||
rays: usize,
|
||||
pixels: usize,
|
||||
}
|
||||
|
||||
impl AddAssign for RenderStats {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
*self = Self {
|
||||
rays: self.rays + other.rays,
|
||||
pixels: self.pixels + other.pixels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn progress(
|
||||
last_stat: &RenderStats,
|
||||
current_stat: &RenderStats,
|
||||
time_diff: time::Duration,
|
||||
pixel_total: usize,
|
||||
) -> String {
|
||||
let human = human::Formatter::new();
|
||||
let pixel_diff = current_stat.pixels - last_stat.pixels;
|
||||
let ray_diff = current_stat.rays - last_stat.rays;
|
||||
format!(
|
||||
"{:7} / {:7}pixels ({:2}%) {:7}pixels/s {:7}rays/s",
|
||||
human.format(current_stat.pixels as f64),
|
||||
human.format(pixel_total as f64),
|
||||
100 * current_stat.pixels / pixel_total,
|
||||
human.format(pixel_diff as f64 / time_diff.as_secs_f64()),
|
||||
human.format(ray_diff as f64 / time_diff.as_secs_f64())
|
||||
)
|
||||
}
|
||||
|
||||
impl Default for RenderStats {
|
||||
fn default() -> Self {
|
||||
RenderStats { rays: 0, pixels: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
enum Request {
|
||||
Pixel { x: usize, y: usize },
|
||||
Line { width: usize, y: usize },
|
||||
}
|
||||
|
||||
enum Response {
|
||||
Pixel {
|
||||
x: usize,
|
||||
y: usize,
|
||||
pixel: Vec3,
|
||||
rs: RenderStats,
|
||||
},
|
||||
Line {
|
||||
y: usize,
|
||||
pixels: Vec<Vec3>,
|
||||
rs: RenderStats,
|
||||
},
|
||||
}
|
||||
|
||||
fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) {
|
||||
let (pixel, rays) = if let Some(threshold) = scene.adaptive_subsampling {
|
||||
trace_pixel_adaptive(
|
||||
MAX_ADAPTIVE_DEPTH,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
0.0..1.0,
|
||||
0.0..1.0,
|
||||
scene,
|
||||
)
|
||||
} else {
|
||||
let (pixel, rays) = (0..scene.subsamples)
|
||||
.map(|_| trace_pixel_random(x, y, scene))
|
||||
.fold(
|
||||
([0., 0., 0.].into(), 0),
|
||||
|(p1, r1): (Vec3, usize), (p2, r2): (Vec3, usize)| ((p1 + p2), (r1 + r2)),
|
||||
);
|
||||
output::set_pixel_grey(output::RAYS_PER_PIXEL, x, y, rays as f32);
|
||||
(pixel / scene.subsamples as f32, rays)
|
||||
};
|
||||
// Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 or
|
||||
// sqrt.
|
||||
(
|
||||
Vec3::new(pixel[0].sqrt(), pixel[1].sqrt(), pixel[2].sqrt()),
|
||||
rays,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_worker(
|
||||
tid: usize,
|
||||
scene: &Scene,
|
||||
input_chan: Arc<Mutex<Receiver<Request>>>,
|
||||
output_chan: &SyncSender<Response>,
|
||||
) {
|
||||
loop {
|
||||
let job = { input_chan.lock().unwrap().recv() };
|
||||
match job {
|
||||
Err(err) => {
|
||||
trace!("Shutting down render_worker {}: {}", tid, err);
|
||||
return;
|
||||
}
|
||||
Ok(req) => match req {
|
||||
Request::Line { width, y } => {
|
||||
trace!("tid {} width {} y {}", tid, width, y);
|
||||
let batch = false;
|
||||
if batch {
|
||||
let (pixels, rays): (Vec<Vec3>, Vec<usize>) = (0..width)
|
||||
.map(|x| render_pixel(scene, x, y))
|
||||
.collect::<Vec<(_, _)>>()
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let rays = rays.iter().sum();
|
||||
output_chan
|
||||
.send(Response::Line {
|
||||
y,
|
||||
pixels,
|
||||
rs: RenderStats {
|
||||
rays,
|
||||
pixels: width,
|
||||
},
|
||||
})
|
||||
.expect("failed to send pixel response");
|
||||
} else {
|
||||
(0..width).for_each(|x| {
|
||||
let (pixel, rays) = render_pixel(scene, x, y);
|
||||
output_chan
|
||||
.send(Response::Pixel {
|
||||
x,
|
||||
y,
|
||||
pixel,
|
||||
rs: RenderStats { rays, pixels: 1 },
|
||||
})
|
||||
.expect("failed to send pixel response");
|
||||
});
|
||||
}
|
||||
}
|
||||
Request::Pixel { x, y } => {
|
||||
trace!("tid {} x {} y {}", tid, x, y);
|
||||
let (pixel, rays) = render_pixel(scene, x, y);
|
||||
output_chan
|
||||
.send(Response::Pixel {
|
||||
x,
|
||||
y,
|
||||
pixel,
|
||||
rs: RenderStats { rays, pixels: 1 },
|
||||
})
|
||||
.expect("failed to send line response");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::io::Error> {
|
||||
let num_threads = scene.num_threads.unwrap_or_else(num_cpus::get);
|
||||
let (pixel_req_tx, pixel_req_rx) = sync_channel(2 * num_threads);
|
||||
let (pixel_resp_tx, pixel_resp_rx) = sync_channel(2 * num_threads);
|
||||
|
||||
let scene = Arc::new(scene);
|
||||
let pixel_req_rx = Arc::new(Mutex::new(pixel_req_rx));
|
||||
info!("Adaptive subsampling: {:?}", scene.adaptive_subsampling);
|
||||
// Retrieve the IDs of all active CPU cores.
|
||||
let core_ids = core_affinity::get_core_ids().unwrap();
|
||||
let core_ids = if core_ids.len() > num_threads {
|
||||
core_ids[..num_threads].to_vec()
|
||||
} else {
|
||||
core_ids
|
||||
};
|
||||
info!("Creating {} render threads", core_ids.len());
|
||||
output::register_image(
|
||||
output::MAIN_IMAGE.to_string(),
|
||||
(scene.width, scene.height),
|
||||
output::ImageType::RGB01,
|
||||
);
|
||||
if scene.adaptive_subsampling.is_some() {
|
||||
output::register_image(
|
||||
output::ADAPTIVE_DEPTH.to_string(),
|
||||
(scene.width, scene.height),
|
||||
output::ImageType::RGB01,
|
||||
);
|
||||
}
|
||||
output::register_image(
|
||||
output::RAYS_PER_PIXEL.to_string(),
|
||||
(scene.width, scene.height),
|
||||
output::ImageType::GreyNormalized,
|
||||
);
|
||||
// Create a thread for each active CPU core.
|
||||
let mut handles = core_ids
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _id)| *i < num_threads)
|
||||
.map(|(i, id)| {
|
||||
let s = sync::Arc::clone(&scene);
|
||||
let pixel_req_rx = pixel_req_rx.clone();
|
||||
let pixel_resp_tx = pixel_resp_tx.clone();
|
||||
thread::spawn(move || {
|
||||
core_affinity::set_for_current(id);
|
||||
render_worker(i, &s, pixel_req_rx, &pixel_resp_tx);
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
drop(pixel_req_rx);
|
||||
drop(pixel_resp_tx);
|
||||
|
||||
let start_time = time::Instant::now();
|
||||
let (w, h) = (scene.width, scene.height);
|
||||
handles.push(thread::spawn(move || {
|
||||
let batch_line_requests = true;
|
||||
if batch_line_requests {
|
||||
for y in 0..h {
|
||||
pixel_req_tx
|
||||
.send(Request::Line { width: w, y })
|
||||
.expect("failed to send line request");
|
||||
}
|
||||
} else {
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
pixel_req_tx
|
||||
.send(Request::Pixel { x, y })
|
||||
.expect("failed to send pixel request");
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(pixel_req_tx);
|
||||
}));
|
||||
|
||||
info!("Rendering with {} subsamples", scene.subsamples);
|
||||
|
||||
let pixel_total = scene.width * scene.height;
|
||||
let mut last_time = time::Instant::now();
|
||||
let mut last_stat: RenderStats = Default::default();
|
||||
let mut current_stat: RenderStats = Default::default();
|
||||
for resp in pixel_resp_rx {
|
||||
match resp {
|
||||
Response::Pixel { x, y, pixel, rs } => {
|
||||
current_stat += rs;
|
||||
output::set_pixel(output::MAIN_IMAGE, x, y, pixel);
|
||||
}
|
||||
Response::Line { y, pixels, rs } => {
|
||||
current_stat += rs;
|
||||
for (x, pixel) in pixels.iter().enumerate() {
|
||||
output::set_pixel(output::MAIN_IMAGE, x, y, *pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let now = time::Instant::now();
|
||||
let time_diff = now - last_time;
|
||||
if time_diff > time::Duration::from_secs(1) {
|
||||
info!(
|
||||
"{}",
|
||||
progress(&last_stat, ¤t_stat, time_diff, pixel_total)
|
||||
);
|
||||
last_stat = current_stat;
|
||||
last_time = now;
|
||||
}
|
||||
}
|
||||
for thr in handles {
|
||||
thr.join().expect("thread join");
|
||||
}
|
||||
let time_diff = time::Instant::now() - start_time;
|
||||
info!(
|
||||
"Runtime {} seconds {}",
|
||||
time_diff.as_secs_f32(),
|
||||
progress(&Default::default(), ¤t_stat, time_diff, pixel_total)
|
||||
);
|
||||
|
||||
output::write_images(&scene, time_diff, output_dir)
|
||||
}
|
||||
99
rtiow/renderer/src/rotate.rs
Normal file
99
rtiow/renderer/src/rotate.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::f32::consts::PI;
|
||||
use std::f32::MAX;
|
||||
use std::f32::MIN;
|
||||
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct RotateY<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
hitable: H,
|
||||
sin_theta: f32,
|
||||
cos_theta: f32,
|
||||
bbox: Option<AABB>,
|
||||
}
|
||||
|
||||
impl<H> RotateY<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
pub fn new(hitable: H, angle: f32) -> RotateY<H> {
|
||||
let radians = PI / 180. * angle;
|
||||
let sin_theta = radians.sin();
|
||||
let cos_theta = radians.cos();
|
||||
let mut min = vec![MAX, MAX, MAX];
|
||||
let mut max = vec![MIN, MIN, MIN];
|
||||
let bbox = hitable.bounding_box(0., 1.).unwrap();
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
for k in 0..2 {
|
||||
let x = i as f32 * bbox.max().x + (1 - i) as f32 * bbox.min().x;
|
||||
let y = j as f32 * bbox.max().y + (1 - j) as f32 * bbox.min().y;
|
||||
let z = k as f32 * bbox.max().z + (1 - k) as f32 * bbox.min().z;
|
||||
let new_x = cos_theta * x + sin_theta * z;
|
||||
let new_z = -sin_theta * x + cos_theta * z;
|
||||
let tester = Vec3::new(new_x, y, new_z);
|
||||
for c in 0..3 {
|
||||
if tester[c] > max[c] {
|
||||
max[c] = tester[c];
|
||||
}
|
||||
if tester[c] < min[c] {
|
||||
min[c] = tester[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RotateY {
|
||||
hitable,
|
||||
sin_theta,
|
||||
cos_theta,
|
||||
bbox: Some(AABB::new(
|
||||
Vec3::new(min[0], min[1], min[2]),
|
||||
Vec3::new(max[0], max[1], max[2]),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Hit for RotateY<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let origin = Vec3::new(
|
||||
self.cos_theta * r.origin[0] - self.sin_theta * r.origin[2],
|
||||
r.origin.y,
|
||||
self.sin_theta * r.origin[0] + self.cos_theta * r.origin[2],
|
||||
);
|
||||
let direction = Vec3::new(
|
||||
self.cos_theta * r.direction[0] - self.sin_theta * r.direction[2],
|
||||
r.direction.y,
|
||||
self.sin_theta * r.direction[0] + self.cos_theta * r.direction[2],
|
||||
);
|
||||
let rotated_r = Ray::new(origin, direction, r.time);
|
||||
if let Some(rec) = self.hitable.hit(rotated_r, t_min, t_max) {
|
||||
let p = Vec3::new(
|
||||
self.cos_theta * rec.p[0] + self.sin_theta * rec.p[2],
|
||||
rec.p[1],
|
||||
-self.sin_theta * rec.p[0] + self.cos_theta * rec.p[2],
|
||||
);
|
||||
let normal = Vec3::new(
|
||||
self.cos_theta * rec.normal[0] + self.sin_theta * rec.normal[2],
|
||||
rec.normal[1],
|
||||
-self.sin_theta * rec.normal[0] + self.cos_theta * rec.normal[2],
|
||||
);
|
||||
return Some(HitRecord { p, normal, ..rec });
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
self.bbox.clone()
|
||||
}
|
||||
}
|
||||
78
rtiow/renderer/src/scenes/bench.rs
Normal file
78
rtiow/renderer/src/scenes/bench.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use log::trace;
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::bvh::BVH;
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::Lambertian;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(20., 20., 20.);
|
||||
let lookat = Vec3::new(0., 0., 0.);
|
||||
let dist_to_focus = (lookfrom - lookat).length();
|
||||
let aperture = 0.1;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
70.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut grid: Vec<Box<dyn Hit>> = Vec::new();
|
||||
let len = 1000;
|
||||
for x in 0..len {
|
||||
for z in 0..len {
|
||||
let r = rng.gen_range(0., 1.);
|
||||
let g = rng.gen_range(0., 1.);
|
||||
let b = rng.gen_range(0., 1.);
|
||||
|
||||
let x_pos = (x - len / 2) as f32 + rng.gen_range(-0.1, 0.1);
|
||||
let z_pos = (z - len / 2) as f32 + rng.gen_range(-0.1, 0.1);
|
||||
|
||||
grid.push(Box::new(Sphere::new(
|
||||
Vec3::new(x_pos, 0., z_pos),
|
||||
0.5,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(r, g, b))),
|
||||
)));
|
||||
}
|
||||
}
|
||||
let world: Box<dyn Hit>;
|
||||
if opt.use_accel {
|
||||
let use_kd = true;
|
||||
if use_kd {
|
||||
let kdtree = KDTree::new(grid, time_min, time_max);
|
||||
trace!("k-d tree world {}", kdtree);
|
||||
world = Box::new(kdtree);
|
||||
} else {
|
||||
let bvh = BVH::new(grid, time_min, time_max);
|
||||
trace!("BVH world {}", bvh);
|
||||
world = Box::new(bvh);
|
||||
}
|
||||
} else {
|
||||
world = Box::new(HitableList::new(grid));
|
||||
}
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
144
rtiow/renderer/src/scenes/book.rs
Normal file
144
rtiow/renderer/src/scenes/book.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::Dielectric;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Material;
|
||||
use crate::material::Metal;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::CheckerTexture;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::texture::EnvMap;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(13., 2., 3.);
|
||||
let lookat = Vec3::new(0., 0., 0.);
|
||||
let dist_to_focus = 10.;
|
||||
let aperture = 0.1;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
20.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let ground_color = if opt.use_accel {
|
||||
[1.0, 0.4, 0.4]
|
||||
} else {
|
||||
[0.4, 1.0, 0.4]
|
||||
};
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(random_scene(ground_color), time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(random_scene(ground_color)))
|
||||
};
|
||||
let skybox_bytes = include_bytes!("../../images/envmap.jpg");
|
||||
let skybox = image::load_from_memory(skybox_bytes).unwrap().to_rgb();
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
adaptive_subsampling: Some(0.5),
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
global_illumination: true,
|
||||
env_map: Some(EnvMap::new(skybox)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn random_scene<V>(ground_color: V) -> Vec<Box<dyn Hit>>
|
||||
where
|
||||
V: Into<Vec3>,
|
||||
{
|
||||
let mut rng = rand::thread_rng();
|
||||
let checker = true;
|
||||
let ground_material: Box<dyn Material> = if checker {
|
||||
Box::new(Lambertian::new(CheckerTexture::new(
|
||||
ConstantTexture::new([0., 0., 0.]),
|
||||
ConstantTexture::new(ground_color),
|
||||
)))
|
||||
} else {
|
||||
Box::new(Lambertian::new(ConstantTexture::new(ground_color)))
|
||||
};
|
||||
|
||||
let mut objects: Vec<Box<dyn Hit>> = vec![Box::new(Sphere::new(
|
||||
[0., -1000., 0.],
|
||||
1000.,
|
||||
ground_material,
|
||||
))];
|
||||
let mut random = || rng.gen_range(0., 1.);
|
||||
|
||||
for a in -11..11 {
|
||||
for b in -11..11 {
|
||||
let choose_mat = random();
|
||||
let center = Vec3::new(a as f32 + 0.9 * random(), 0.2, b as f32 + 0.9 * random());
|
||||
if (center - Vec3::new(4., 0.2, 0.)).length() > 0.9 {
|
||||
let sphere: Box<dyn Hit> = if choose_mat < 0.8 {
|
||||
// diffuse
|
||||
Box::new(Sphere::new(
|
||||
center,
|
||||
0.2,
|
||||
Lambertian::new(ConstantTexture::new([
|
||||
random() * random(),
|
||||
random() * random(),
|
||||
random() * random(),
|
||||
])),
|
||||
))
|
||||
} else if choose_mat < 0.95 {
|
||||
// metal
|
||||
Box::new(Sphere::new(
|
||||
center,
|
||||
0.2,
|
||||
Metal::new(
|
||||
Vec3::new(
|
||||
0.5 * (1. + random()),
|
||||
0.5 * (1. + random()),
|
||||
0.5 * (1. + random()),
|
||||
),
|
||||
0.5 * random(),
|
||||
),
|
||||
))
|
||||
} else {
|
||||
// glass
|
||||
Box::new(Sphere::new(center, 0.2, Dielectric::new(1.5)))
|
||||
};
|
||||
objects.push(sphere);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let more: Vec<Box<dyn Hit>> = vec![
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 1., 0.),
|
||||
1.0,
|
||||
Dielectric::new(1.5),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(-4., 1., 0.),
|
||||
1.0,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.4, 0.2, 0.1))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(4., 1., 0.),
|
||||
1.0,
|
||||
Metal::new(Vec3::new(0.7, 0.6, 0.5), 0.0),
|
||||
)),
|
||||
];
|
||||
objects.extend(more);
|
||||
objects
|
||||
}
|
||||
73
rtiow/renderer/src/scenes/bvh.rs
Normal file
73
rtiow/renderer/src/scenes/bvh.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use log::trace;
|
||||
|
||||
use crate::bvh::BVH;
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Metal;
|
||||
use crate::moving_sphere::MovingSphere;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(3., 3., 2.);
|
||||
let lookat = Vec3::new(0., 0., -1.);
|
||||
let dist_to_focus = (lookfrom - lookat).length();
|
||||
let aperture = 0.1;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
45.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let b = BVH::new(
|
||||
vec![
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., -1.),
|
||||
0.5,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.1, 0.2, 0.5))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., -100.5, -1.),
|
||||
100.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.8, 0.8, 0.8))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(1., 0., -1.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.6, 0.6, 0.6), 0.2),
|
||||
)),
|
||||
Box::new(MovingSphere::new(
|
||||
Vec3::new(-1., 0., -1.25),
|
||||
Vec3::new(-1., 0., -0.75),
|
||||
0.5,
|
||||
time_min,
|
||||
time_max,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.2, 0.8, 0.2))),
|
||||
)),
|
||||
],
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
trace!(target: "bvh", "World {}", b);
|
||||
let world: Box<dyn Hit> = Box::new(b);
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
139
rtiow/renderer/src/scenes/cornell_box.rs
Normal file
139
rtiow/renderer/src/scenes/cornell_box.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::cuboid::Cuboid;
|
||||
use crate::flip_normals::FlipNormals;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::DiffuseLight;
|
||||
use crate::material::Lambertian;
|
||||
use crate::rect::XYRect;
|
||||
use crate::rect::XZRect;
|
||||
use crate::rect::YZRect;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::rotate::RotateY;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::translate::Translate;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(278., 278., -800.);
|
||||
let lookat = Vec3::new(278., 278., 0.);
|
||||
let dist_to_focus = 10.0;
|
||||
let aperture = 0.0;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
40.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
// Box1
|
||||
Box::new(Translate::new(
|
||||
RotateY::new(
|
||||
Cuboid::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
Vec3::new(165., 165., 165.),
|
||||
Arc::new(Lambertian::new(ConstantTexture::new(Vec3::new(
|
||||
0.73, 0.73, 0.73,
|
||||
)))),
|
||||
),
|
||||
-18.,
|
||||
),
|
||||
Vec3::new(100., 0., 0.),
|
||||
)),
|
||||
// Box2
|
||||
Box::new(Translate::new(
|
||||
RotateY::new(
|
||||
Cuboid::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
Vec3::new(165., 330., 165.),
|
||||
Arc::new(Lambertian::new(ConstantTexture::new(Vec3::new(
|
||||
0.73, 0.73, 0.73,
|
||||
)))),
|
||||
),
|
||||
15.,
|
||||
),
|
||||
Vec3::new(265., 0., 295.),
|
||||
)),
|
||||
// Green wall left
|
||||
Box::new(FlipNormals::new(YZRect::new(
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
555.,
|
||||
555.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.12, 0.45, 0.15))),
|
||||
))),
|
||||
// Red floor right
|
||||
Box::new(YZRect::new(
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.65, 0.05, 0.05))),
|
||||
)),
|
||||
// Light in ceiling
|
||||
Box::new(XZRect::new(
|
||||
213.,
|
||||
343.,
|
||||
227.,
|
||||
332.,
|
||||
554.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(15., 15., 15.))),
|
||||
)),
|
||||
// Grey ceiling
|
||||
Box::new(FlipNormals::new(XZRect::new(
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
555.,
|
||||
555.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.73, 0.73, 0.73))),
|
||||
))),
|
||||
// Grey floor
|
||||
Box::new(XZRect::new(
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.73, 0.73, 0.73))),
|
||||
)),
|
||||
// Grey back wall
|
||||
Box::new(FlipNormals::new(XYRect::new(
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
555.,
|
||||
555.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.73, 0.73, 0.73))),
|
||||
))),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(objects))
|
||||
};
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
global_illumination: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
124
rtiow/renderer/src/scenes/cornell_smoke.rs
Normal file
124
rtiow/renderer/src/scenes/cornell_smoke.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::constant_medium::ConstantMedium;
|
||||
use crate::cuboid::Cuboid;
|
||||
use crate::flip_normals::FlipNormals;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::DiffuseLight;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Material;
|
||||
use crate::rect::XYRect;
|
||||
use crate::rect::XZRect;
|
||||
use crate::rect::YZRect;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::rotate::RotateY;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::translate::Translate;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(278., 278., -800.);
|
||||
let lookat = Vec3::new(278., 278., 0.);
|
||||
let dist_to_focus = 10.0;
|
||||
let aperture = 0.0;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
40.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
|
||||
let red = Lambertian::new(ConstantTexture::new([0.65, 0.05, 0.05]));
|
||||
let white: Arc<dyn Material> =
|
||||
Arc::new(Lambertian::new(ConstantTexture::new([0.73, 0.73, 0.73])));
|
||||
let green = Lambertian::new(ConstantTexture::new([0.12, 0.45, 0.15]));
|
||||
let light = DiffuseLight::new(ConstantTexture::new([7., 7., 7.]));
|
||||
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
// White smoke box on the right
|
||||
Box::new(ConstantMedium::new(
|
||||
Translate::new(
|
||||
RotateY::new(
|
||||
Cuboid::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
Vec3::new(165., 165., 165.),
|
||||
Arc::clone(&white),
|
||||
),
|
||||
-18.,
|
||||
),
|
||||
Vec3::new(130., 0., 65.),
|
||||
),
|
||||
0.01,
|
||||
ConstantTexture::new([1., 1., 1.]),
|
||||
)),
|
||||
// Black smoke box on the left
|
||||
Box::new(ConstantMedium::new(
|
||||
Translate::new(
|
||||
RotateY::new(
|
||||
Cuboid::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
Vec3::new(165., 330., 165.),
|
||||
Arc::clone(&white),
|
||||
),
|
||||
15.,
|
||||
),
|
||||
Vec3::new(265., 0., 295.),
|
||||
),
|
||||
0.01,
|
||||
ConstantTexture::new([0., 0., 0.]),
|
||||
)),
|
||||
// Green wall left
|
||||
Box::new(FlipNormals::new(YZRect::new(
|
||||
0., 555., 0., 555., 555., green,
|
||||
))),
|
||||
// Red floor right
|
||||
Box::new(YZRect::new(0., 555., 0., 555., 0., red)),
|
||||
// Light in ceiling
|
||||
Box::new(XZRect::new(113., 443., 127., 432., 554., light)),
|
||||
// Grey ceiling
|
||||
Box::new(FlipNormals::new(XZRect::new(
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
555.,
|
||||
555.,
|
||||
Arc::clone(&white),
|
||||
))),
|
||||
// Grey floor
|
||||
Box::new(XZRect::new(0., 555., 0., 555., 0., Arc::clone(&white))),
|
||||
// Grey back wall
|
||||
Box::new(FlipNormals::new(XYRect::new(
|
||||
0.,
|
||||
555.,
|
||||
0.,
|
||||
555.,
|
||||
555.,
|
||||
Arc::clone(&white),
|
||||
))),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(objects))
|
||||
};
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
177
rtiow/renderer/src/scenes/final_scene.rs
Normal file
177
rtiow/renderer/src/scenes/final_scene.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use image;
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::constant_medium::ConstantMedium;
|
||||
use crate::cuboid::Cuboid;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::Dielectric;
|
||||
use crate::material::DiffuseLight;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Material;
|
||||
use crate::material::Metal;
|
||||
use crate::moving_sphere::MovingSphere;
|
||||
use crate::noise::perlin::Perlin;
|
||||
use crate::noise::NoiseType;
|
||||
use crate::rect::XZRect;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::rotate::RotateY;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::texture::ImageTexture;
|
||||
use crate::texture::NoiseTexture;
|
||||
use crate::translate::Translate;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(478., 278., -600.);
|
||||
let lookat = Vec3::new(278., 278., 0.);
|
||||
let dist_to_focus = 10.;
|
||||
let aperture = 0.0;
|
||||
let t_min = 0.;
|
||||
let t_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
40.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
t_min,
|
||||
t_max,
|
||||
);
|
||||
|
||||
let nb = 20;
|
||||
|
||||
let rng = &mut rand::thread_rng();
|
||||
let mut boxlist: Vec<Box<dyn Hit>> = Vec::with_capacity(10000);
|
||||
let mut list: Vec<Box<dyn Hit>> = Vec::new();
|
||||
let white: Arc<dyn Material> =
|
||||
Arc::new(Lambertian::new(ConstantTexture::new([0.73, 0.73, 0.73])));
|
||||
let ground: Arc<dyn Material> =
|
||||
Arc::new(Lambertian::new(ConstantTexture::new([0.48, 0.83, 0.53])));
|
||||
for i in 0..nb {
|
||||
for j in 0..nb {
|
||||
let w = 100.;
|
||||
let x0 = -1000. + i as f32 * w;
|
||||
let z0 = -1000. + j as f32 * w;
|
||||
let y0 = 0.;
|
||||
let x1 = x0 + w;
|
||||
let y1 = 100. * (rng.gen_range(0., 1.) + 0.01);
|
||||
let z1 = z0 + w;
|
||||
boxlist.push(Box::new(Cuboid::new(
|
||||
Vec3::new(x0, y0, z0),
|
||||
Vec3::new(x1, y1, z1),
|
||||
Arc::clone(&ground),
|
||||
)));
|
||||
}
|
||||
}
|
||||
list.push(Box::new(KDTree::new(boxlist, t_min, t_max)));
|
||||
|
||||
let light = DiffuseLight::new(ConstantTexture::new([7., 7., 7.]));
|
||||
// Light in ceiling
|
||||
list.push(Box::new(XZRect::new(123., 423., 147., 412., 554., light)));
|
||||
|
||||
let center = Vec3::new(400., 400., 200.);
|
||||
// Moving brownish ball
|
||||
list.push(Box::new(MovingSphere::new(
|
||||
center,
|
||||
center + Vec3::new(30., 0., 0.),
|
||||
50.,
|
||||
t_min,
|
||||
t_max,
|
||||
Lambertian::new(ConstantTexture::new([0.7, 0.3, 0.1])),
|
||||
)));
|
||||
|
||||
// Glass ball, lower-left corner
|
||||
list.push(Box::new(Sphere::new(
|
||||
[260., 150., 45.],
|
||||
50.,
|
||||
Dielectric::new(1.5),
|
||||
)));
|
||||
// Metal ball lower-right corner
|
||||
list.push(Box::new(Sphere::new(
|
||||
[0., 150., 145.],
|
||||
50.,
|
||||
Metal::new([0.8, 0.8, 0.9], 10.0),
|
||||
)));
|
||||
|
||||
// Blue smokey glass ball lower-left
|
||||
let boundary: Arc<dyn Hit> =
|
||||
Arc::new(Sphere::new([360., 150., 145.], 70., Dielectric::new(1.5)));
|
||||
list.push(Box::new(Arc::clone(&boundary)));
|
||||
list.push(Box::new(ConstantMedium::new(
|
||||
Arc::clone(&boundary),
|
||||
0.2,
|
||||
ConstantTexture::new([0.2, 0.4, 0.9]),
|
||||
)));
|
||||
|
||||
// General white mist over whole scene
|
||||
let boundary: Arc<dyn Hit> = Arc::new(Sphere::new([0., 0., 0.], 5000., Dielectric::new(1.5)));
|
||||
list.push(Box::new(Arc::clone(&boundary)));
|
||||
list.push(Box::new(ConstantMedium::new(
|
||||
Arc::clone(&boundary),
|
||||
0.0001,
|
||||
ConstantTexture::new([1.0, 1.0, 1.0]),
|
||||
)));
|
||||
|
||||
// Earth sphere
|
||||
let world_image_bytes = include_bytes!("../../images/world.jpg");
|
||||
let world = ImageTexture::new(image::load_from_memory(world_image_bytes).unwrap().to_rgb());
|
||||
list.push(Box::new(Sphere::new(
|
||||
Vec3::new(400., 200., 400.),
|
||||
100.,
|
||||
Lambertian::new(world),
|
||||
)));
|
||||
|
||||
// Perlin noise sphere
|
||||
let noise_source = Perlin::new(rng);
|
||||
let noise_type = NoiseType::Marble {
|
||||
period: Vec3::new(0., 1., 0.),
|
||||
power: 4.,
|
||||
size: 32,
|
||||
scale: 0.5,
|
||||
};
|
||||
let pertext = NoiseTexture::new(noise_source, noise_type);
|
||||
list.push(Box::new(Sphere::new(
|
||||
[220., 280., 300.],
|
||||
80.,
|
||||
Lambertian::new(pertext),
|
||||
)));
|
||||
|
||||
// White 'cube' made of 1000 spheres
|
||||
let ns = 1000;
|
||||
let mut boxlist: Vec<Box<dyn Hit>> = Vec::with_capacity(1000);
|
||||
for _ in 0..ns {
|
||||
boxlist.push(Box::new(Sphere::new(
|
||||
[
|
||||
165. * rng.gen_range(0., 1.),
|
||||
165. * rng.gen_range(0., 1.),
|
||||
165. * rng.gen_range(0., 1.),
|
||||
],
|
||||
10.,
|
||||
Arc::clone(&white),
|
||||
)));
|
||||
}
|
||||
list.push(Box::new(Translate::new(
|
||||
RotateY::new(KDTree::new(boxlist, t_min, t_max), 15.),
|
||||
[-100., 270., 395.],
|
||||
)));
|
||||
|
||||
Scene {
|
||||
camera,
|
||||
world: Box::new(HitableList::new(list)),
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
157
rtiow/renderer/src/scenes/mandelbrot.rs
Normal file
157
rtiow/renderer/src/scenes/mandelbrot.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use rand;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::DiffuseLight;
|
||||
use crate::material::Lambertian;
|
||||
use crate::noise::perlin::Perlin;
|
||||
use crate::noise::NoiseType;
|
||||
use crate::rect::XYRect;
|
||||
use crate::rect::XZRect;
|
||||
use crate::rect::YZRect;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::texture::ImageTexture;
|
||||
use crate::texture::Mandelbrot;
|
||||
use crate::texture::NoiseTexture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(20., 20., 20.);
|
||||
let lookat = Vec3::new(0., 1., 0.);
|
||||
let dist_to_focus = 10.0;
|
||||
let aperture = 0.0;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
20.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let rng = &mut rand::thread_rng();
|
||||
let _ground_color = if opt.use_accel {
|
||||
Box::new(ConstantTexture::new(Vec3::new(1.0, 0.4, 0.4)))
|
||||
} else {
|
||||
Box::new(ConstantTexture::new(Vec3::new(0.4, 1.0, 0.4)))
|
||||
};
|
||||
|
||||
let world_image_bytes = include_bytes!("../../images/world.jpg");
|
||||
let it = ImageTexture::new(image::load_from_memory(world_image_bytes).unwrap().to_rgb());
|
||||
let noise_source = Perlin::new(rng);
|
||||
let noise_type = NoiseType::Scale(10.);
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
// Textured globe
|
||||
// Box::new(Sphere::new(Vec3::new(0., 2., 0.), 2.0, Lambertian::new(it))),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 2., 0.),
|
||||
2.0,
|
||||
DiffuseLight::new(it),
|
||||
)),
|
||||
// Ground
|
||||
Box::new(XZRect::new(
|
||||
-100.,
|
||||
100.,
|
||||
-100.,
|
||||
1000.,
|
||||
-60.,
|
||||
DiffuseLight::new(Mandelbrot::default()),
|
||||
)),
|
||||
Box::new(XZRect::new(
|
||||
-100.,
|
||||
100.,
|
||||
-100.,
|
||||
200.,
|
||||
60.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(1., 1., 1.))),
|
||||
)),
|
||||
Box::new(YZRect::new(
|
||||
1.,
|
||||
3.,
|
||||
-1.,
|
||||
1.,
|
||||
4.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(4., 0., 4.))),
|
||||
)),
|
||||
Box::new(YZRect::new(
|
||||
1.,
|
||||
3.,
|
||||
-1.,
|
||||
1.,
|
||||
-4.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(0., 4., 0.))),
|
||||
)),
|
||||
Box::new(XZRect::new(
|
||||
-1.,
|
||||
1.,
|
||||
-1.,
|
||||
1.,
|
||||
6.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(4., 4., 0.))),
|
||||
)),
|
||||
Box::new(XYRect::new(
|
||||
-1.,
|
||||
1.,
|
||||
1.,
|
||||
3.,
|
||||
-4.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(0., 0., 4.))),
|
||||
)),
|
||||
Box::new(XYRect::new(
|
||||
-1.75,
|
||||
1.75,
|
||||
1.,
|
||||
3.,
|
||||
4.,
|
||||
Lambertian::new(NoiseTexture::new(noise_source, noise_type)),
|
||||
)),
|
||||
/*
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
0.5,
|
||||
Box::new(Lambertian::new(ConstantTexture::new(Vec3::new(
|
||||
0.1, 0.2, 0.5,
|
||||
)))),
|
||||
)),
|
||||
// Shiny sphere
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(1., 0., 0.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||
)),
|
||||
Box::new(MovingSphere::new(
|
||||
Vec3::new(-1., 0., -0.25),
|
||||
Vec3::new(-1., 0., 0.25),
|
||||
0.5,
|
||||
0.,
|
||||
1.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(
|
||||
0.2, 0.8, 0.2,
|
||||
))),
|
||||
)),
|
||||
*/
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(objects))
|
||||
};
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
11
rtiow/renderer/src/scenes/mod.rs
Normal file
11
rtiow/renderer/src/scenes/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
pub mod bench;
|
||||
pub mod book;
|
||||
pub mod bvh;
|
||||
pub mod cornell_box;
|
||||
pub mod cornell_smoke;
|
||||
pub mod final_scene;
|
||||
pub mod mandelbrot;
|
||||
pub mod perlin_debug;
|
||||
pub mod spheramid;
|
||||
pub mod test;
|
||||
pub mod tutorial;
|
||||
74
rtiow/renderer/src/scenes/perlin_debug.rs
Normal file
74
rtiow/renderer/src/scenes/perlin_debug.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::Lambertian;
|
||||
use crate::noise::perlin::Perlin;
|
||||
use crate::noise::NoiseType;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::NoiseTexture;
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(13., 2., 3.);
|
||||
let lookat = Vec3::new(0., 0., 0.);
|
||||
let dist_to_focus = 10.0;
|
||||
let aperture = 0.0;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
20.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let rng = &mut rand::thread_rng();
|
||||
let noise_source = Perlin::new(rng);
|
||||
let noise_type = NoiseType::Marble {
|
||||
period: Vec3::new(0., 1., 0.),
|
||||
power: 4.,
|
||||
size: 32,
|
||||
scale: 0.05,
|
||||
};
|
||||
let pertext: Arc<dyn Texture> = Arc::new(NoiseTexture::new(noise_source, noise_type));
|
||||
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., -1000., 0.),
|
||||
1000.,
|
||||
Lambertian::new(Arc::clone(&pertext)),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 2., 0.),
|
||||
2.0,
|
||||
Lambertian::new(Arc::clone(&pertext)),
|
||||
)),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(objects))
|
||||
};
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
global_illumination: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
88
rtiow/renderer/src/scenes/spheramid.rs
Normal file
88
rtiow/renderer/src/scenes/spheramid.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::Dielectric;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Metal;
|
||||
use crate::moving_sphere::MovingSphere;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::CheckerTexture;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(3., 4., 2.);
|
||||
let lookat = Vec3::new(0., 0., -1.);
|
||||
let dist_to_focus = (lookfrom - lookat).length();
|
||||
let aperture = 0.1;
|
||||
let time_min = 0.;
|
||||
let time_max = 0.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
45.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let ground_color: Box<dyn Texture> = if opt.use_accel {
|
||||
Box::new(CheckerTexture::new(
|
||||
ConstantTexture::new([0., 0., 0.]),
|
||||
ConstantTexture::new([1.0, 0.4, 0.4]),
|
||||
))
|
||||
} else {
|
||||
Box::new(ConstantTexture::new(Vec3::new(0.4, 1.0, 0.4)))
|
||||
};
|
||||
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
//let world: Box<Hit> = Box::new(HitableList::new(vec![
|
||||
Box::new(Sphere::new([1., 0.5, 1.], 0.5, Dielectric::new(1.5))),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., -1.),
|
||||
0.5,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.1, 0.2, 0.5))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., -1000.5, -1.),
|
||||
1000.,
|
||||
Lambertian::new(ground_color),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(1., 0., -1.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.2),
|
||||
)),
|
||||
Box::new(MovingSphere::new(
|
||||
Vec3::new(-1., 0., -1.25),
|
||||
Vec3::new(-1., 0., -0.75),
|
||||
0.5,
|
||||
0.,
|
||||
1.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.2, 0.8, 0.2))),
|
||||
)),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(objects))
|
||||
};
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
adaptive_subsampling: None,
|
||||
// adaptive_subsampling: Some(0.5),
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
global_illumination: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
150
rtiow/renderer/src/scenes/test.rs
Normal file
150
rtiow/renderer/src/scenes/test.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use image;
|
||||
use rand;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::DiffuseLight;
|
||||
use crate::material::Lambertian;
|
||||
use crate::noise::perlin::Perlin;
|
||||
use crate::noise::NoiseType;
|
||||
use crate::rect::XYRect;
|
||||
use crate::rect::XZRect;
|
||||
use crate::rect::YZRect;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::texture::ImageTexture;
|
||||
use crate::texture::NoiseTexture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(20., 20., 20.);
|
||||
let lookat = Vec3::new(0., 1., 0.);
|
||||
let dist_to_focus = 10.0;
|
||||
let aperture = 0.0;
|
||||
let time_min = 0.;
|
||||
let time_max = 1.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
20.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let rng = &mut rand::thread_rng();
|
||||
let _ground_color = if opt.use_accel {
|
||||
Box::new(ConstantTexture::new(Vec3::new(1.0, 0.4, 0.4)))
|
||||
} else {
|
||||
Box::new(ConstantTexture::new(Vec3::new(0.4, 1.0, 0.4)))
|
||||
};
|
||||
|
||||
let world_image_bytes = include_bytes!("../../images/world.jpg");
|
||||
let it = ImageTexture::new(image::load_from_memory(world_image_bytes).unwrap().to_rgb());
|
||||
let noise_source = Perlin::new(rng);
|
||||
let noise_type = NoiseType::Scale(10.);
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
// Big sphere
|
||||
Box::new(Sphere::new(Vec3::new(0., 2., 0.), 2.0, Lambertian::new(it))),
|
||||
// Earth sized sphere
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., -1000., 0.),
|
||||
1000.,
|
||||
// Box::new(Lambertian::new(ground_color)),
|
||||
Lambertian::new(NoiseTexture::new(noise_source, noise_type)),
|
||||
)),
|
||||
Box::new(XZRect::new(
|
||||
-100.,
|
||||
100.,
|
||||
-100.,
|
||||
1000.,
|
||||
60.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(1., 1., 1.))),
|
||||
)),
|
||||
Box::new(YZRect::new(
|
||||
1.,
|
||||
3.,
|
||||
-1.,
|
||||
1.,
|
||||
4.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(4., 0., 4.))),
|
||||
)),
|
||||
Box::new(YZRect::new(
|
||||
1.,
|
||||
3.,
|
||||
-1.,
|
||||
1.,
|
||||
-4.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(0., 4., 0.))),
|
||||
)),
|
||||
Box::new(XZRect::new(
|
||||
-1.,
|
||||
1.,
|
||||
-1.,
|
||||
1.,
|
||||
6.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(4., 4., 0.))),
|
||||
)),
|
||||
Box::new(XYRect::new(
|
||||
-1.,
|
||||
1.,
|
||||
1.,
|
||||
3.,
|
||||
-4.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(0., 0., 4.))),
|
||||
)),
|
||||
Box::new(XYRect::new(
|
||||
-1.,
|
||||
1.,
|
||||
1.,
|
||||
3.,
|
||||
4.,
|
||||
DiffuseLight::new(ConstantTexture::new(Vec3::new(0., 4., 4.))),
|
||||
)),
|
||||
/*
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
0.5,
|
||||
Box::new(Lambertian::new(ConstantTexture::new(Vec3::new(
|
||||
0.1, 0.2, 0.5,
|
||||
)))),
|
||||
)),
|
||||
// Shiny sphere
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(1., 0., 0.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||
)),
|
||||
Box::new(MovingSphere::new(
|
||||
Vec3::new(-1., 0., -0.25),
|
||||
Vec3::new(-1., 0., 0.25),
|
||||
0.5,
|
||||
0.,
|
||||
1.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(
|
||||
0.2, 0.8, 0.2,
|
||||
))),
|
||||
)),
|
||||
*/
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(objects))
|
||||
};
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
88
rtiow/renderer/src/scenes/tutorial.rs
Normal file
88
rtiow/renderer/src/scenes/tutorial.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::Dielectric;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Metal;
|
||||
use crate::moving_sphere::MovingSphere;
|
||||
use crate::renderer::Opt;
|
||||
use crate::renderer::Scene;
|
||||
use crate::sphere::Sphere;
|
||||
use crate::texture::CheckerTexture;
|
||||
use crate::texture::ConstantTexture;
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(3., 4., 2.);
|
||||
let lookat = Vec3::new(0., 0., -1.);
|
||||
let dist_to_focus = (lookfrom - lookat).length();
|
||||
let aperture = 0.1;
|
||||
let time_min = 0.;
|
||||
let time_max = 0.;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0., 1., 0.),
|
||||
45.,
|
||||
opt.width as f32 / opt.height as f32,
|
||||
aperture,
|
||||
dist_to_focus,
|
||||
time_min,
|
||||
time_max,
|
||||
);
|
||||
let ground_color: Box<dyn Texture> = if opt.use_accel {
|
||||
Box::new(CheckerTexture::new(
|
||||
ConstantTexture::new([0., 0., 0.]),
|
||||
ConstantTexture::new([1.0, 0.4, 0.4]),
|
||||
))
|
||||
} else {
|
||||
Box::new(ConstantTexture::new(Vec3::new(0.4, 1.0, 0.4)))
|
||||
};
|
||||
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
//let world: Box<Hit> = Box::new(HitableList::new(vec![
|
||||
Box::new(Sphere::new([1., 0.5, 1.], 0.5, Dielectric::new(1.5))),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., -1.),
|
||||
0.5,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.1, 0.2, 0.5))),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., -1000.5, -1.),
|
||||
1000.,
|
||||
Lambertian::new(ground_color),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(1., 0., -1.),
|
||||
0.5,
|
||||
Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.2),
|
||||
)),
|
||||
Box::new(MovingSphere::new(
|
||||
Vec3::new(-1., 0., -1.25),
|
||||
Vec3::new(-1., 0., -0.75),
|
||||
0.5,
|
||||
0.,
|
||||
1.,
|
||||
Lambertian::new(ConstantTexture::new(Vec3::new(0.2, 0.8, 0.2))),
|
||||
)),
|
||||
];
|
||||
let world: Box<dyn Hit> = if opt.use_accel {
|
||||
Box::new(KDTree::new(objects, time_min, time_max))
|
||||
} else {
|
||||
Box::new(HitableList::new(objects))
|
||||
};
|
||||
Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
adaptive_subsampling: None,
|
||||
// adaptive_subsampling: Some(0.5),
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
global_illumination: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
89
rtiow/renderer/src/sphere.rs
Normal file
89
rtiow/renderer/src/sphere.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::material::Material;
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::dot;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct Sphere<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
center: Vec3,
|
||||
radius: f32,
|
||||
material: M,
|
||||
}
|
||||
|
||||
pub fn get_sphere_uv(p: Vec3) -> (f32, f32) {
|
||||
let phi = p.z.atan2(p.x);
|
||||
let theta = p.y.asin();
|
||||
let u = 1. - (phi + PI) / (2. * PI);
|
||||
let v = (theta + PI / 2.) / PI;
|
||||
(u, v)
|
||||
}
|
||||
|
||||
impl<M> Sphere<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
pub fn new<V>(center: V, radius: f32, material: M) -> Sphere<M>
|
||||
where
|
||||
V: Into<Vec3>,
|
||||
{
|
||||
Sphere {
|
||||
center: center.into(),
|
||||
radius,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Hit for Sphere<M>
|
||||
where
|
||||
M: Material,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let oc = r.origin - self.center;
|
||||
let a = dot(r.direction, r.direction);
|
||||
let b = dot(oc, r.direction);
|
||||
let c = dot(oc, oc) - self.radius * self.radius;
|
||||
let discriminant = b * b - a * c;
|
||||
if discriminant > 0. {
|
||||
let temp = (-b - (b * b - a * c).sqrt()) / a;
|
||||
if temp < t_max && temp > t_min {
|
||||
let point = r.point_at_parameter(temp);
|
||||
let uv = get_sphere_uv((point - self.center) / self.radius);
|
||||
return Some(HitRecord {
|
||||
t: temp,
|
||||
uv,
|
||||
p: point,
|
||||
normal: (point - self.center) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
let temp = (-b + (b * b - a * c).sqrt()) / a;
|
||||
if temp < t_max && temp > t_min {
|
||||
let point = r.point_at_parameter(temp);
|
||||
let uv = get_sphere_uv((point - self.center) / self.radius);
|
||||
return Some(HitRecord {
|
||||
t: temp,
|
||||
uv,
|
||||
p: point,
|
||||
normal: (point - self.center) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||
Some(AABB::new(
|
||||
self.center - Vec3::new(self.radius, self.radius, self.radius),
|
||||
self.center + Vec3::new(self.radius, self.radius, self.radius),
|
||||
))
|
||||
}
|
||||
}
|
||||
33
rtiow/renderer/src/texture/checker.rs
Normal file
33
rtiow/renderer/src/texture/checker.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct CheckerTexture<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
odd: T,
|
||||
even: T,
|
||||
}
|
||||
|
||||
impl<T> CheckerTexture<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
pub fn new(odd: T, even: T) -> CheckerTexture<T> {
|
||||
CheckerTexture { odd, even }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Texture for CheckerTexture<T>
|
||||
where
|
||||
T: Texture,
|
||||
{
|
||||
fn value(&self, u: f32, v: f32, p: Vec3) -> Vec3 {
|
||||
let sines = (10. * p.x).sin() * (10. * p.y).sin() * (10. * p.z).sin();
|
||||
if sines < 0. {
|
||||
self.odd.value(u, v, p)
|
||||
} else {
|
||||
self.even.value(u, v, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
24
rtiow/renderer/src/texture/constant.rs
Normal file
24
rtiow/renderer/src/texture/constant.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ConstantTexture {
|
||||
color: Vec3,
|
||||
}
|
||||
|
||||
impl ConstantTexture {
|
||||
pub fn new<V>(color: V) -> ConstantTexture
|
||||
where
|
||||
V: Into<Vec3>,
|
||||
{
|
||||
ConstantTexture {
|
||||
color: color.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for ConstantTexture {
|
||||
fn value(&self, _u: f32, _v: f32, _p: Vec3) -> Vec3 {
|
||||
self.color
|
||||
}
|
||||
}
|
||||
26
rtiow/renderer/src/texture/envmap.rs
Normal file
26
rtiow/renderer/src/texture/envmap.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::f32;
|
||||
|
||||
use image::RgbImage;
|
||||
|
||||
use crate::texture::ImageTexture;
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnvMap {
|
||||
img_tex: ImageTexture,
|
||||
}
|
||||
|
||||
impl EnvMap {
|
||||
pub fn new(img: RgbImage) -> EnvMap {
|
||||
EnvMap {
|
||||
img_tex: ImageTexture::new(img),
|
||||
}
|
||||
}
|
||||
pub fn color(&self, ray: Vec3) -> Vec3 {
|
||||
let zero = Vec3::new(0., 0., 0.);
|
||||
let u = ray.x.atan2(ray.z) / f32::consts::PI /2.0 + 0.5;
|
||||
let v = ray.y / 2.0 + 0.5;
|
||||
self.img_tex.value(u, v, zero)
|
||||
}
|
||||
}
|
||||
48
rtiow/renderer/src/texture/image.rs
Normal file
48
rtiow/renderer/src/texture/image.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use image::RgbImage;
|
||||
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImageTexture {
|
||||
img: RgbImage,
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
impl ImageTexture {
|
||||
pub fn new(img: RgbImage) -> ImageTexture {
|
||||
let (w, h) = img.dimensions();
|
||||
ImageTexture {
|
||||
img,
|
||||
width: f32::from(w.min(64000) as u16),
|
||||
height: f32::from(h.min(64000) as u16),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for ImageTexture {
|
||||
fn value(&self, u: f32, v: f32, _p: Vec3) -> Vec3 {
|
||||
// Wrap texcoords by default.
|
||||
let x = (u % 1. * (self.width - 1.)) as u32;
|
||||
let y = ((1. - v % 1.) * (self.height - 1.)) as u32;
|
||||
if x >= self.width as u32 {
|
||||
panic!(format!(
|
||||
"u {} v {} x {} y {} w {} h {}",
|
||||
u, v, x, y, self.width, self.height
|
||||
));
|
||||
}
|
||||
if y >= self.height as u32 {
|
||||
panic!(format!(
|
||||
"u {} v {} x {} y {} w {} h {}",
|
||||
u, v, x, y, self.width, self.height
|
||||
));
|
||||
}
|
||||
let pixel = self.img.get_pixel(x, y);
|
||||
Vec3::new(
|
||||
f32::from(pixel[0]) / 255.,
|
||||
f32::from(pixel[1]) / 255.,
|
||||
f32::from(pixel[2]) / 255.,
|
||||
)
|
||||
}
|
||||
}
|
||||
79
rtiow/renderer/src/texture/mandelbrot.rs
Normal file
79
rtiow/renderer/src/texture/mandelbrot.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mandelbrot {
|
||||
scale: f32,
|
||||
palette: Vec<Vec3>,
|
||||
}
|
||||
|
||||
// HSV values in [0..1]
|
||||
// returns [r, g, b] values from 0 to 255
|
||||
//From https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
|
||||
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Vec3 {
|
||||
let h_i = (h * 6.) as i32;
|
||||
let f = h * 6. - h_i as f32;
|
||||
let p = v * (1. - s);
|
||||
let q = v * (1. - f * s);
|
||||
let t = v * (1. - (1. - f) * s);
|
||||
match h_i {
|
||||
0 => Vec3::new(v, t, p),
|
||||
1 => Vec3::new(q, v, p),
|
||||
2 => Vec3::new(p, v, t),
|
||||
3 => Vec3::new(p, q, v),
|
||||
4 => Vec3::new(t, p, v),
|
||||
5 => Vec3::new(v, p, q),
|
||||
_ => panic!(format!("Unknown H value {}", h_i)),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_palette(num: usize) -> Vec<Vec3> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut random = || rng.gen_range(0.0, 0.1);
|
||||
// use golden ratio
|
||||
let golden_ratio_conjugate = 0.618_034;
|
||||
let mut h = random();
|
||||
(0..num)
|
||||
.map(|_| {
|
||||
h += golden_ratio_conjugate;
|
||||
h %= 1.0;
|
||||
hsv_to_rgb(h, 0.99, 0.99)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Default for Mandelbrot {
|
||||
fn default() -> Self {
|
||||
Mandelbrot {
|
||||
scale: 2.0,
|
||||
palette: generate_palette(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for Mandelbrot {
|
||||
fn value(&self, u: f32, v: f32, _p: Vec3) -> Vec3 {
|
||||
// scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2.5, 1))
|
||||
let x0 = u * 3.5 - 2.5;
|
||||
// scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1, 1))
|
||||
let y0 = v * 2.0 - 1.0;
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
let mut iteration = 0;
|
||||
let max_iteration = 1000;
|
||||
while (x * x + y * y) <= 2. * 2. && iteration < max_iteration {
|
||||
let xtemp = x * x - y * y + x0;
|
||||
y = 2. * x * y + y0;
|
||||
x = xtemp;
|
||||
iteration += 1;
|
||||
}
|
||||
if iteration == max_iteration {
|
||||
return Vec3::default();
|
||||
}
|
||||
self.palette[iteration % self.palette.len()]
|
||||
}
|
||||
}
|
||||
45
rtiow/renderer/src/texture/mod.rs
Normal file
45
rtiow/renderer/src/texture/mod.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
mod checker;
|
||||
mod constant;
|
||||
mod envmap;
|
||||
mod image;
|
||||
mod mandelbrot;
|
||||
mod noise;
|
||||
pub use crate::texture::checker::CheckerTexture;
|
||||
pub use crate::texture::constant::ConstantTexture;
|
||||
pub use crate::texture::envmap::EnvMap;
|
||||
pub use crate::texture::image::ImageTexture;
|
||||
pub use crate::texture::mandelbrot::Mandelbrot;
|
||||
pub use crate::texture::noise::NoiseTexture;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub trait Texture: Send + Sync {
|
||||
fn value(&self, u: f32, v: f32, p: Vec3) -> Vec3;
|
||||
}
|
||||
|
||||
impl Texture for Arc<dyn Texture> {
|
||||
fn value(&self, u: f32, v: f32, p: Vec3) -> Vec3 {
|
||||
(**self).value(u, v, p)
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture for Box<dyn Texture> {
|
||||
fn value(&self, u: f32, v: f32, p: Vec3) -> Vec3 {
|
||||
(**self).value(u, v, p)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn constant_texture_from_array() {
|
||||
assert_eq!(
|
||||
ConstantTexture::new(Vec3::new(1., 2., 3.)),
|
||||
ConstantTexture::new([1., 2., 3.])
|
||||
);
|
||||
}
|
||||
}
|
||||
46
rtiow/renderer/src/texture/noise.rs
Normal file
46
rtiow/renderer/src/texture/noise.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::noise::NoiseSource;
|
||||
use crate::noise::NoiseType;
|
||||
use crate::texture::Texture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct NoiseTexture<N>
|
||||
where
|
||||
N: NoiseSource,
|
||||
{
|
||||
noise_source: N,
|
||||
noise_type: NoiseType,
|
||||
}
|
||||
|
||||
impl<N> NoiseTexture<N>
|
||||
where
|
||||
N: NoiseSource,
|
||||
{
|
||||
pub fn new(noise_source: N, noise_type: NoiseType) -> NoiseTexture<N> {
|
||||
NoiseTexture {
|
||||
noise_source,
|
||||
noise_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N> Texture for NoiseTexture<N>
|
||||
where
|
||||
N: NoiseSource,
|
||||
{
|
||||
fn value(&self, _u: f32, _v: f32, p: Vec3) -> Vec3 {
|
||||
let v = match self.noise_type {
|
||||
NoiseType::Scale(scale) => self.noise_source.scaled(p, scale),
|
||||
|
||||
NoiseType::Turbulence(turbulence) => self.noise_source.turbulence(p, turbulence),
|
||||
NoiseType::Marble {
|
||||
period,
|
||||
power,
|
||||
size,
|
||||
scale,
|
||||
} => self.noise_source.marble(p, period, power, size, scale),
|
||||
};
|
||||
debug_assert!(v >= 0., "Cold pixel @ {}: {}", p, v);
|
||||
debug_assert!(v <= 1., "Hot pixel @ {}: {}", p, v);
|
||||
Vec3::new(1., 1., 1.) * v
|
||||
}
|
||||
}
|
||||
54
rtiow/renderer/src/translate.rs
Normal file
54
rtiow/renderer/src/translate.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::aabb::AABB;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable::HitRecord;
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub struct Translate<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
hitable: H,
|
||||
offset: Vec3,
|
||||
}
|
||||
|
||||
impl<H> Translate<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
pub fn new<V>(hitable: H, offset: V) -> Translate<H>
|
||||
where
|
||||
V: Into<Vec3>,
|
||||
{
|
||||
Translate {
|
||||
hitable,
|
||||
offset: offset.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Hit for Translate<H>
|
||||
where
|
||||
H: Hit,
|
||||
{
|
||||
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let moved_r = Ray::new(r.origin - self.offset, r.direction, r.time);
|
||||
if let Some(rec) = self.hitable.hit(moved_r, t_min, t_max) {
|
||||
return Some(HitRecord {
|
||||
p: rec.p + self.offset,
|
||||
..rec
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||
if let Some(bbox) = self.hitable.bounding_box(t_min, t_max) {
|
||||
return Some(AABB::new(
|
||||
bbox.min() + self.offset,
|
||||
bbox.max() + self.offset,
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
279
rtiow/renderer/src/vec3.rs
Normal file
279
rtiow/renderer/src/vec3.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
use std::num::ParseFloatError;
|
||||
use std::ops::Add;
|
||||
use std::ops::Div;
|
||||
use std::ops::Index;
|
||||
use std::ops::Mul;
|
||||
use std::ops::Neg;
|
||||
use std::ops::Sub;
|
||||
use std::str;
|
||||
|
||||
use serde_derive::Deserialize;
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
|
||||
pub struct Vec3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: v1.y * v2.z - v1.z * v2.y,
|
||||
y: v1.z * v2.x - v1.x * v2.z,
|
||||
z: v1.x * v2.y - v1.y * v2.x,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dot(v1: Vec3, v2: Vec3) -> f32 {
|
||||
v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
|
||||
}
|
||||
|
||||
impl Vec3 {
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
|
||||
Vec3 { x, y, z }
|
||||
}
|
||||
|
||||
pub fn min(self) -> f32 {
|
||||
self.x.min(self.y).min(self.z)
|
||||
}
|
||||
|
||||
pub fn max(self) -> f32 {
|
||||
self.x.max(self.y).max(self.z)
|
||||
}
|
||||
|
||||
pub fn length(self) -> f32 {
|
||||
self.squared_length().sqrt()
|
||||
}
|
||||
|
||||
pub fn squared_length(self) -> f32 {
|
||||
self.x * self.x + self.y * self.y + self.z * self.z
|
||||
}
|
||||
|
||||
pub fn unit_vector(self) -> Vec3 {
|
||||
self / self.length()
|
||||
}
|
||||
|
||||
pub fn make_unit_vector(&mut self) {
|
||||
*self = self.unit_vector();
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 3]> for Vec3 {
|
||||
fn from(v: [f32; 3]) -> Self {
|
||||
Vec3 {
|
||||
x: v[0],
|
||||
y: v[1],
|
||||
z: v[2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Vec3 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {} {}", self.x, self.y, self.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Vec3 {
|
||||
type Err = ParseFloatError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let coords: Vec<&str> = s.split(' ').collect();
|
||||
Ok(Vec3 {
|
||||
x: coords[0].parse::<f32>()?,
|
||||
y: coords[1].parse::<f32>()?,
|
||||
z: coords[2].parse::<f32>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<f32> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn add(self, r: f32) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x + r,
|
||||
y: self.y + r,
|
||||
z: self.z + r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn add(self, r: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x + r.x,
|
||||
y: self.y + r.y,
|
||||
z: self.z + r.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Vec3> for f32 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, r: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self / r.x,
|
||||
y: self / r.y,
|
||||
z: self / r.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, r: f32) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x / r,
|
||||
y: self.y / r,
|
||||
z: self.z / r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Vec3 {
|
||||
type Output = f32;
|
||||
fn index(&self, idx: usize) -> &f32 {
|
||||
match idx {
|
||||
0 => &self.x,
|
||||
1 => &self.y,
|
||||
2 => &self.z,
|
||||
_ => panic!(format!("idx {} out of range for vec3", idx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn mul(self, r: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x * r.x,
|
||||
y: self.y * r.y,
|
||||
z: self.z * r.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3> for f32 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn mul(self, v: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: v.x * self,
|
||||
y: v.y * self,
|
||||
z: v.z * self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn mul(self, r: f32) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x * r,
|
||||
y: self.y * r,
|
||||
z: self.z * r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn neg(self) -> Vec3 {
|
||||
-1. * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn sub(self, r: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x - r.x,
|
||||
y: self.y - r.y,
|
||||
z: self.z - r.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::f32::consts::PI;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn vec_add() {
|
||||
let v0 = Vec3::new(1., 2., 3.);
|
||||
let v1 = Vec3::new(1., 1., 1.);
|
||||
assert_eq!(v0 + v1, Vec3::new(2., 3., 4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_div() {
|
||||
let v0 = Vec3::new(1., 2., 4.);
|
||||
assert_eq!(v0 / 2., Vec3::new(0.5, 1., 2.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_mul() {
|
||||
let v0 = Vec3::new(1., 2., 4.);
|
||||
assert_eq!(v0 * 0.5, Vec3::new(0.5, 1., 2.));
|
||||
assert_eq!(v0 * v0, Vec3::new(1., 4., 16.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_sub() {
|
||||
let v0 = Vec3::new(1., 2., 3.);
|
||||
let v1 = Vec3::new(1., 1., 1.);
|
||||
assert_eq!(v0 - v1, Vec3::new(0., 1., 2.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_idx() {
|
||||
let v0 = Vec3::new(1., 2., 3.);
|
||||
assert_eq!(v0[2], 3.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_display() {
|
||||
let v0 = Vec3::new(1., 2., 3.);
|
||||
assert_eq!(format!("{}", v0), "1 2 3".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_from_str() {
|
||||
assert_eq!(Vec3::from_str("1. 2. 3.").unwrap(), Vec3::new(1., 2., 3.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_str_roundtrip() {
|
||||
let v = Vec3::from_str("1 2 3").unwrap();
|
||||
let s = format!("{}", v);
|
||||
assert_eq!(v, Vec3::from_str(&s).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_dot() {
|
||||
let v0 = Vec3::new(1., 0., 0.);
|
||||
let v1 = Vec3::new(-1., 0., 0.);
|
||||
assert_eq!(dot(v0, v1), v0.length() * v1.length() * PI.cos());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_cross() {
|
||||
let v0 = Vec3::new(1., 0., 0.);
|
||||
let v1 = Vec3::new(0., 1., 0.);
|
||||
assert_eq!(cross(v0, v1), Vec3::new(0., 0., 1.));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user