Fix the weird other reflection
This commit is contained in:
parent
79b8faa00f
commit
75d4e6266f
8 changed files with 238 additions and 112 deletions
39
assignment-1d/Cargo.lock
generated
39
assignment-1d/Cargo.lock
generated
|
@ -52,6 +52,7 @@ dependencies = [
|
|||
"rand",
|
||||
"rayon",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
|
@ -785,6 +786,33 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
|
@ -797,6 +825,17 @@ dependencies = [
|
|||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.23"
|
||||
|
|
|
@ -32,4 +32,5 @@ ordered-float = "3.4.0"
|
|||
rand = "0.8.5"
|
||||
rayon = "1.6.1"
|
||||
tracing = "0.1.37"
|
||||
tracing-appender = "0.2.2"
|
||||
tracing-subscriber = { version = "0.3.16", features = ["json"] }
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs::File, str::FromStr};
|
||||
|
||||
use anyhow::Result;
|
||||
use assignment_1d::{image::Image, ray::Ray, scene::Scene};
|
||||
|
@ -11,6 +13,7 @@ use clap::{ArgAction, Parser};
|
|||
|
||||
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::{
|
||||
fmt::Layer, prelude::__tracing_subscriber_SubscriberExt,
|
||||
util::SubscriberInitExt,
|
||||
|
@ -29,6 +32,14 @@ struct Opt {
|
|||
#[clap(short = 'o', long = "output")]
|
||||
output_path: Option<PathBuf>,
|
||||
|
||||
/// Log output in json
|
||||
#[clap(long = "json")]
|
||||
use_json: bool,
|
||||
|
||||
/// Which file to send logs to (stderr by default)
|
||||
#[clap(long = "log-output")]
|
||||
log_output: Option<PathBuf>,
|
||||
|
||||
/// Force parallel projection to be used
|
||||
#[clap(long = "parallel")]
|
||||
force_parallel: bool,
|
||||
|
@ -40,30 +51,16 @@ struct Opt {
|
|||
/// Verbosity
|
||||
#[clap(short, long, action = ArgAction::Count)]
|
||||
verbosity: u8,
|
||||
|
||||
/// Evaluate at a single pixel
|
||||
#[clap(short, long = "render-pixel")]
|
||||
render_pixel: Option<RenderPixel>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opt = Opt::parse();
|
||||
|
||||
let level_filter = match opt.verbosity {
|
||||
0 => LevelFilter::ERROR,
|
||||
1 => LevelFilter::WARN,
|
||||
2 => LevelFilter::INFO,
|
||||
3 => LevelFilter::DEBUG,
|
||||
_ => LevelFilter::TRACE,
|
||||
};
|
||||
|
||||
// Set up logging
|
||||
let layer = Layer::default()
|
||||
.json()
|
||||
.with_target(false)
|
||||
.with_timer(tracing_subscriber::fmt::time::uptime())
|
||||
.with_level(true);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(layer)
|
||||
.with(level_filter)
|
||||
.init();
|
||||
let _guard = setup_logging(&opt);
|
||||
|
||||
// Rename the output file if it's not provided
|
||||
let out_file = opt
|
||||
|
@ -81,6 +78,37 @@ fn main() -> Result<()> {
|
|||
// Translate image pixels to real-world 3d coords
|
||||
let translate_pixel = scene.pixel_translation_function(distance);
|
||||
|
||||
let evaluate_at_pixel = |px, py| {
|
||||
let span = trace_span!("main_loop", px = px, py = py);
|
||||
let _enter = span.enter();
|
||||
|
||||
let pixel_in_space = translate_pixel(px, py);
|
||||
|
||||
let ray_start = if scene.parallel_projection {
|
||||
// For a parallel projection, we'll just take the view direction and
|
||||
// subtract it from the target point. This means every single
|
||||
// ray will be viewed from a point at infinity, rather than a single eye
|
||||
// position.
|
||||
let n = scene.view_dir.normalize();
|
||||
let view_dir = n * distance;
|
||||
pixel_in_space - view_dir
|
||||
} else {
|
||||
scene.eye_pos
|
||||
};
|
||||
|
||||
let ray = Ray::from_endpoints(ray_start, pixel_in_space);
|
||||
|
||||
// let res= rayon::spawn(|| scene.trace_single_ray(ray, 0));
|
||||
scene.trace_single_ray(scene.eye_pos, ray, 0)
|
||||
};
|
||||
|
||||
// For debugging purposes!
|
||||
if let Some(RenderPixel(px, py)) = opt.render_pixel {
|
||||
let pixel_color = evaluate_at_pixel(px, py)?;
|
||||
println!("Pixel color: {pixel_color}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Generate a parallel iterator for pixels
|
||||
// The iterator preserves order and uses row-major order
|
||||
let pixels_iter = (0..scene.image_height)
|
||||
|
@ -89,29 +117,7 @@ fn main() -> Result<()> {
|
|||
|
||||
// Loop through every single pixel of the output file
|
||||
let pixels = pixels_iter
|
||||
.map(|(px, py)| {
|
||||
let span = trace_span!("main_loop", px = px, py = py);
|
||||
let _enter = span.enter();
|
||||
|
||||
let pixel_in_space = translate_pixel(px, py);
|
||||
|
||||
let ray_start = if scene.parallel_projection {
|
||||
// For a parallel projection, we'll just take the view direction and
|
||||
// subtract it from the target point. This means every single
|
||||
// ray will be viewed from a point at infinity, rather than a single eye
|
||||
// position.
|
||||
let n = scene.view_dir.normalize();
|
||||
let view_dir = n * distance;
|
||||
pixel_in_space - view_dir
|
||||
} else {
|
||||
scene.eye_pos
|
||||
};
|
||||
|
||||
let ray = Ray::from_endpoints(ray_start, pixel_in_space);
|
||||
|
||||
// let res= rayon::spawn(|| scene.trace_single_ray(ray, 0));
|
||||
scene.trace_single_ray(scene.eye_pos, ray, 0)
|
||||
})
|
||||
.map(|(px, py)| evaluate_at_pixel(px, py))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
// Construct and emit image
|
||||
|
@ -128,3 +134,71 @@ fn main() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RenderPixel(usize, usize);
|
||||
|
||||
impl FromStr for RenderPixel {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts = s.split(",").collect::<Vec<_>>();
|
||||
ensure!(parts.len() == 2, "must be a pair");
|
||||
|
||||
let x = parts[0].parse::<usize>()?;
|
||||
let y = parts[1].parse::<usize>()?;
|
||||
|
||||
Ok(RenderPixel(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
/// A little bit of engineering to make it easy to write conditional builders
|
||||
/// for logging setup because the tracing-subscriber crate for some reason
|
||||
/// decided it would be a good idea to have all of its builders be polymorphic?
|
||||
macro_rules! logsetup_if {
|
||||
($ident:ident , $cond:expr , $iftrue:expr , $iffalse:expr => { $($body:tt)* }) => {
|
||||
if ($cond) {
|
||||
let $ident = $iftrue;
|
||||
$($body)*
|
||||
} else {
|
||||
let $ident = $iffalse;
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn setup_logging(opt: &Opt) -> Option<WorkerGuard> {
|
||||
let mut result = None;
|
||||
|
||||
let level_filter = match opt.verbosity {
|
||||
0 => LevelFilter::ERROR,
|
||||
1 => LevelFilter::WARN,
|
||||
2 => LevelFilter::INFO,
|
||||
3 => LevelFilter::DEBUG,
|
||||
_ => LevelFilter::TRACE,
|
||||
};
|
||||
|
||||
let layer = Layer::default();
|
||||
|
||||
logsetup_if! (layer, opt.use_json, layer.json(), layer => {
|
||||
let layer = layer
|
||||
.with_target(false)
|
||||
.with_timer(tracing_subscriber::fmt::time::uptime())
|
||||
.with_level(true);
|
||||
|
||||
logsetup_if! (layer, opt.log_output.is_some(), {
|
||||
let log_output = opt.log_output.clone().unwrap();
|
||||
let file_appender = tracing_appender::rolling::never(".", log_output);
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
||||
result = Some(guard);
|
||||
layer.with_writer(non_blocking)
|
||||
}, layer => {
|
||||
tracing_subscriber::registry()
|
||||
.with(layer)
|
||||
.with(level_filter)
|
||||
.init();
|
||||
});
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
@ -1,15 +1,34 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::{Point, Vector};
|
||||
|
||||
/// A normalized parametric Ray of the form (origin + direction * time)
|
||||
///
|
||||
/// That means at any time t: f64, the point represented by origin + direction *
|
||||
/// time occurs on the ray.
|
||||
#[derive(Debug)]
|
||||
/// time occurs on the ray. This is pretty much a (time -> point) function.
|
||||
pub struct Ray {
|
||||
/// The point in space where the ray started
|
||||
pub origin: Point,
|
||||
|
||||
/// The direction the ray is headed
|
||||
pub direction: Vector,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Ray {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"({:.2}, {:.2}, {:.2}) + t * ({:.2}, {:.2}, {:.2})",
|
||||
self.origin.x,
|
||||
self.origin.y,
|
||||
self.origin.z,
|
||||
self.direction.x,
|
||||
self.direction.y,
|
||||
self.direction.z,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new(origin: Point, direction: Vector) -> Self {
|
||||
Ray { origin, direction }
|
||||
|
|
|
@ -11,7 +11,9 @@ use rayon::prelude::{
|
|||
use crate::{
|
||||
image::Color,
|
||||
ray::Ray,
|
||||
utils::{compute_refraction_lengths, dot, RefractionResult},
|
||||
utils::{
|
||||
compute_reflection_ray, compute_refraction_lengths, dot, RefractionResult,
|
||||
},
|
||||
Point, Vector,
|
||||
};
|
||||
|
||||
|
@ -48,8 +50,7 @@ impl Scene {
|
|||
intersection_context: IntersectionContext,
|
||||
depth: usize,
|
||||
) -> Result<Color> {
|
||||
let span =
|
||||
trace_span!("compute_pixel_color", intersection = ?intersection_context);
|
||||
let span = trace_span!("compute_pixel_color", intersection = ?intersection_context, incident_ray=?incident_ray);
|
||||
let _enter = span.enter();
|
||||
|
||||
let material = match self.materials.get(object.material_idx) {
|
||||
|
@ -83,17 +84,7 @@ impl Scene {
|
|||
// The vector pointing in the direction of the light
|
||||
let light_direction = light.direction_from(intersection_context.point);
|
||||
|
||||
let normal = {
|
||||
let mut normal = intersection_context.normal.normalize();
|
||||
|
||||
// If we're exiting the material, the normal should face the other direction since that's
|
||||
// how the reflection works
|
||||
if intersection_context.exiting {
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
normal
|
||||
};
|
||||
let normal = intersection_context.normal.normalize(); // reflection_normal();
|
||||
|
||||
// Viewer direction is no longer towards the eye, but to the last origin point, so that
|
||||
// transmitted rays reflect properly
|
||||
|
@ -147,30 +138,6 @@ impl Scene {
|
|||
})
|
||||
.sum();
|
||||
|
||||
/*
|
||||
let specular_reflection: Color = {
|
||||
let reflection_ray = self.compute_reflection_ray(
|
||||
incident_ray.direction,
|
||||
intersection_context.normal,
|
||||
);
|
||||
|
||||
let fresnel_coefficient = self.compute_fresnel_coefficient(
|
||||
&material,
|
||||
incident_ray.direction,
|
||||
intersection_context.normal,
|
||||
);
|
||||
|
||||
// Jitter a bit to reduce acne
|
||||
let origin = intersection_context.point;
|
||||
let origin = origin + JITTER_CONST * reflection_ray;
|
||||
|
||||
let ray = Ray::new(origin, reflection_ray);
|
||||
let r_lambda = self.trace_single_ray(origin, ray, depth + 1)?;
|
||||
|
||||
fresnel_coefficient * r_lambda
|
||||
};
|
||||
*/
|
||||
|
||||
let (eta_i, eta_t) = match intersection_context.exiting {
|
||||
// true => (material.eta, 1.0),
|
||||
_ => (1.0, material.eta),
|
||||
|
@ -186,7 +153,7 @@ impl Scene {
|
|||
)?
|
||||
};
|
||||
|
||||
let transparency_component = if eta_t < 1.0 {
|
||||
let transparency_component = if eta_t < 1.0 || material.alpha == 1.0 {
|
||||
ZERO_COLOR
|
||||
} else {
|
||||
self.compute_transparency(
|
||||
|
@ -212,6 +179,14 @@ impl Scene {
|
|||
* (1.0 - material.alpha)
|
||||
* transparency_component
|
||||
};
|
||||
debug!(
|
||||
last_time_component = ?(ambient_component + diffuse_and_specular),
|
||||
?specular_reflection_component,
|
||||
?transparency_component,
|
||||
?fresnel_coefficient,
|
||||
?color,
|
||||
"color result"
|
||||
);
|
||||
|
||||
// Apply depth cueing to the result
|
||||
let a_dc = {
|
||||
|
@ -412,30 +387,15 @@ impl Scene {
|
|||
fr
|
||||
}
|
||||
|
||||
fn compute_reflection_ray(
|
||||
&self,
|
||||
incident_ray: Vector,
|
||||
normal: Vector,
|
||||
) -> Vector {
|
||||
let opposite_incident_ray = (-incident_ray).normalize();
|
||||
let unit_normal = normal.normalize();
|
||||
|
||||
let a = dot(unit_normal, opposite_incident_ray);
|
||||
let r = 2.0 * (a * unit_normal) - opposite_incident_ray;
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
fn compute_specular_reflection(
|
||||
&self,
|
||||
intersection_context: &IntersectionContext,
|
||||
incident_ray: &Ray,
|
||||
depth: usize,
|
||||
) -> Result<Color> {
|
||||
// Specular reflection
|
||||
let reflection_ray = self.compute_reflection_ray(
|
||||
let reflection_ray = compute_reflection_ray(
|
||||
incident_ray.direction.clone(),
|
||||
intersection_context.normal,
|
||||
intersection_context.reflection_normal().normalize(),
|
||||
);
|
||||
|
||||
let origin = intersection_context.point;
|
||||
|
@ -458,14 +418,7 @@ impl Scene {
|
|||
let _enter = span.enter();
|
||||
|
||||
// Fix the normal direction to account for exiting a material
|
||||
let normal = {
|
||||
let mut n = intersection_context.normal.normalize();
|
||||
if intersection_context.exiting {
|
||||
n = -n;
|
||||
}
|
||||
|
||||
n
|
||||
};
|
||||
let normal = intersection_context.reflection_normal().normalize();
|
||||
|
||||
let i = incident_ray.direction.normalize();
|
||||
|
||||
|
@ -484,7 +437,7 @@ impl Scene {
|
|||
match compute_refraction_lengths(normal, &incident_ray, eta_i, eta_t) {
|
||||
Some(RefractionResult {
|
||||
cos_theta_i,
|
||||
sin_theta_i,
|
||||
sin_theta_i: _,
|
||||
sin_theta_t,
|
||||
cos_theta_t,
|
||||
}) => {
|
||||
|
@ -493,7 +446,7 @@ impl Scene {
|
|||
// new direction.
|
||||
|
||||
// Calculate refraction direction
|
||||
let a = normal.normalize() * cos_theta_t;
|
||||
let a = normal * cos_theta_t;
|
||||
let s_direction = cos_theta_i * normal - i;
|
||||
let m_unit = s_direction.normalize();
|
||||
let b = m_unit * sin_theta_t;
|
||||
|
@ -550,4 +503,13 @@ pub struct IntersectionContext {
|
|||
|
||||
impl Eq for IntersectionContext {}
|
||||
|
||||
impl IntersectionContext {}
|
||||
impl IntersectionContext {
|
||||
// If we're exiting the material, the normal should face the other direction
|
||||
// since that's how the reflection works
|
||||
pub fn reflection_normal(&self) -> Vector {
|
||||
match self.exiting {
|
||||
true => -self.normal,
|
||||
false => self.normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ impl Scene {
|
|||
let earliest_intersection =
|
||||
intersections.into_iter().min_by_key(|(_, t, _)| t.time);
|
||||
|
||||
info!("Ray {ray:?} intersected at: {earliest_intersection:?}");
|
||||
|
||||
Ok(match earliest_intersection {
|
||||
// Take the object's material color
|
||||
Some((obj_idx, intersection_context, object)) => self
|
||||
|
|
|
@ -61,6 +61,11 @@ impl Triangle {
|
|||
-(a * x0 + b * y0 + c * z0 + d) / denom
|
||||
};
|
||||
|
||||
// Intersected the plane behind where the ray started
|
||||
if time < 0.0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let time = NotNan::new(time)?;
|
||||
let point = ray.eval(*time);
|
||||
|
||||
|
|
|
@ -134,3 +134,27 @@ pub fn compute_refraction_lengths(
|
|||
cos_theta_t,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn compute_reflection_ray(incident_ray: Vector, normal: Vector) -> Vector {
|
||||
let I = (-incident_ray).normalize();
|
||||
let N = normal.normalize();
|
||||
|
||||
2.0 * dot(N, I) * N - I
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{utils::compute_reflection_ray, Vector};
|
||||
|
||||
#[test]
|
||||
fn test_reflection_ray() {
|
||||
let incident_ray = Vector::new(2.0, -1.0, 2.0);
|
||||
let normal = Vector::new(0.0, 1.0, 0.0);
|
||||
|
||||
assert_eq!(
|
||||
compute_reflection_ray(incident_ray, normal),
|
||||
Vector::new(2.0, 1.0, 2.0).normalize()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue