Fix the weird other reflection

This commit is contained in:
Michael Zhang 2023-04-05 01:22:54 -05:00
parent 79b8faa00f
commit 75d4e6266f
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
8 changed files with 238 additions and 112 deletions

View file

@ -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"

View file

@ -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"] }

View file

@ -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
}

View file

@ -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 }

View file

@ -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,
}
}
}

View file

@ -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

View file

@ -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);

View file

@ -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()
);
}
}