fix up reflection + refraction
This commit is contained in:
parent
e8a5758103
commit
79b8faa00f
5 changed files with 213 additions and 90 deletions
18
assignment-1d/examples/sample-1-but-only-1-sphere.txt
Normal file
18
assignment-1d/examples/sample-1-but-only-1-sphere.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
eye 0 5 0
|
||||
viewdir 0 0 1
|
||||
updir 0 1 0
|
||||
hfov 45
|
||||
imsize 1080 1080
|
||||
bkgcolor 0.5 0.7 0.9 1
|
||||
light 0 -1 0 0 1 1 1
|
||||
mtlcolor 1 1 1 1 1 1 0.2 0.4 0.6 60 1 0
|
||||
sphere -1.5 4 15 1
|
||||
|
||||
mtlcolor 1 1 1 1 1 1 0.2 0.8 0 20 1 0
|
||||
v 10 0 5
|
||||
v -10 0 5
|
||||
v -10 0 25
|
||||
v 10 0 25
|
||||
|
||||
f 1 2 3
|
||||
f 1 3 4
|
|
@ -110,7 +110,7 @@ fn main() -> Result<()> {
|
|||
let ray = Ray::from_endpoints(ray_start, pixel_in_space);
|
||||
|
||||
// let res= rayon::spawn(|| scene.trace_single_ray(ray, 0));
|
||||
scene.trace_single_ray(ray, 0)
|
||||
scene.trace_single_ray(scene.eye_pos, ray, 0)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
|
|
|
@ -8,7 +8,12 @@ use rayon::prelude::{
|
|||
ParallelIterator,
|
||||
};
|
||||
|
||||
use crate::{image::Color, ray::Ray, utils::dot, Point, Vector};
|
||||
use crate::{
|
||||
image::Color,
|
||||
ray::Ray,
|
||||
utils::{compute_refraction_lengths, dot, RefractionResult},
|
||||
Point, Vector,
|
||||
};
|
||||
|
||||
use super::{
|
||||
data::{DepthCueing, Light, LightKind, Material},
|
||||
|
@ -38,6 +43,7 @@ impl Scene {
|
|||
&self,
|
||||
obj_idx: usize,
|
||||
object: &Object,
|
||||
origin: Point,
|
||||
incident_ray: Ray,
|
||||
intersection_context: IntersectionContext,
|
||||
depth: usize,
|
||||
|
@ -77,10 +83,25 @@ impl Scene {
|
|||
// The vector pointing in the direction of the light
|
||||
let light_direction = light.direction_from(intersection_context.point);
|
||||
|
||||
let normal = intersection_context.normal.normalize();
|
||||
let viewer_direction = self.eye_pos - 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
|
||||
};
|
||||
|
||||
// Viewer direction is no longer towards the eye, but to the last origin point, so that
|
||||
// transmitted rays reflect properly
|
||||
// let viewer_direction = self.eye_pos - intersection_context.point;
|
||||
let incoming_ray_direction = (intersection_context.point - origin).normalize();
|
||||
|
||||
let halfway_direction =
|
||||
((light_direction + viewer_direction) / 2.0).normalize();
|
||||
((light_direction + incoming_ray_direction) / 2.0).normalize();
|
||||
|
||||
let diffuse_component = material.k_d
|
||||
* diffuse_color
|
||||
|
@ -126,6 +147,7 @@ impl Scene {
|
|||
})
|
||||
.sum();
|
||||
|
||||
/*
|
||||
let specular_reflection: Color = {
|
||||
let reflection_ray = self.compute_reflection_ray(
|
||||
incident_ray.direction,
|
||||
|
@ -143,34 +165,53 @@ impl Scene {
|
|||
let origin = origin + JITTER_CONST * reflection_ray;
|
||||
|
||||
let ray = Ray::new(origin, reflection_ray);
|
||||
let r_lambda = self.trace_single_ray(ray, depth + 1)?;
|
||||
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),
|
||||
};
|
||||
|
||||
let transparency = if eta_t < 1.0 {
|
||||
let specular_reflection_component = if material.k_s == 0.0 {
|
||||
ZERO_COLOR
|
||||
} else {
|
||||
self.compute_specular_reflection(
|
||||
&intersection_context,
|
||||
&incident_ray,
|
||||
depth,
|
||||
)?
|
||||
};
|
||||
|
||||
let transparency_component = if eta_t < 1.0 {
|
||||
ZERO_COLOR
|
||||
} else {
|
||||
self.compute_transparency(
|
||||
&intersection_context,
|
||||
incident_ray,
|
||||
material,
|
||||
&incident_ray,
|
||||
eta_i,
|
||||
eta_t,
|
||||
depth,
|
||||
)?
|
||||
};
|
||||
|
||||
let fresnel_coefficient = self.compute_fresnel_coefficient(
|
||||
material,
|
||||
&incident_ray.direction,
|
||||
intersection_context.normal,
|
||||
);
|
||||
|
||||
// This is the result of the Phong illumination equation.
|
||||
let color = ambient_component
|
||||
+ diffuse_and_specular
|
||||
+ specular_reflection
|
||||
+ transparency;
|
||||
let color = ambient_component + diffuse_and_specular + {
|
||||
// This part is all the transparency + reflection stuff
|
||||
fresnel_coefficient * specular_reflection_component
|
||||
+ (1.0 - fresnel_coefficient)
|
||||
* (1.0 - material.alpha)
|
||||
* transparency_component
|
||||
};
|
||||
|
||||
// Apply depth cueing to the result
|
||||
let a_dc = {
|
||||
|
@ -226,7 +267,7 @@ impl Scene {
|
|||
#[derive(Clone, Copy)]
|
||||
struct ShadowResult {
|
||||
transparent_coefficient: f64,
|
||||
opacity: f64,
|
||||
shadow_opacity: f64,
|
||||
}
|
||||
|
||||
// Get the list of intersections with all the other objects in the scene
|
||||
|
@ -241,7 +282,9 @@ impl Scene {
|
|||
None
|
||||
}
|
||||
}?;
|
||||
|
||||
let intersection_time = *intersection_context.time;
|
||||
let material = &self.materials[object.material_idx];
|
||||
|
||||
match light.kind {
|
||||
// In the case of point lights, we must check to see if both t > 0 and
|
||||
|
@ -252,27 +295,27 @@ impl Scene {
|
|||
if intersection_time <= 0.0 || intersection_time >= light_time {
|
||||
None
|
||||
} else {
|
||||
let soft_shadow_coefficient =
|
||||
self.compute_soft_shadow_coefficient(location, point, object);
|
||||
|
||||
let material = &self.materials[object.material_idx];
|
||||
|
||||
Some(ShadowResult {
|
||||
transparent_coefficient: 1.0 - material.alpha,
|
||||
opacity: soft_shadow_coefficient,
|
||||
transparent_coefficient: material.alpha,
|
||||
shadow_opacity: self
|
||||
.compute_soft_shadow_coefficient(location, point, object),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// In the case of directional lights, only t > 0 needs to be checked,
|
||||
// otherwise
|
||||
// In the case of directional lights, only t > 0 needs to be checked
|
||||
LightKind::Directional { .. } => {
|
||||
if intersection_time <= 0.0 {
|
||||
None
|
||||
} else {
|
||||
// The object obstructed the directional light, which means (1 -
|
||||
// alpha) amount of light passes through
|
||||
Some(ShadowResult {
|
||||
transparent_coefficient: 1.0,
|
||||
opacity: 0.0,
|
||||
transparent_coefficient: material.alpha,
|
||||
|
||||
// Opacity is 0 because there's no jitter from an infinitely far
|
||||
// away light source
|
||||
shadow_opacity: 0.0,
|
||||
}) // complete obstruction
|
||||
}
|
||||
}
|
||||
|
@ -283,12 +326,14 @@ impl Scene {
|
|||
match intersections.is_empty() {
|
||||
true => 1.0,
|
||||
false => {
|
||||
let average = intersections.iter().map(|s| s.opacity).sum::<f64>()
|
||||
/ intersections.len() as f64;
|
||||
let average =
|
||||
intersections.iter().map(|s| s.shadow_opacity).sum::<f64>()
|
||||
/ intersections.len() as f64;
|
||||
|
||||
// S * (1 - a_0) * (1 - a_1) * (...)
|
||||
let transparency = intersections
|
||||
.iter()
|
||||
.map(|s| s.transparent_coefficient)
|
||||
.map(|s| 1.0 - s.transparent_coefficient)
|
||||
.product::<f64>();
|
||||
|
||||
average * transparency
|
||||
|
@ -345,13 +390,13 @@ impl Scene {
|
|||
fn compute_fresnel_coefficient(
|
||||
&self,
|
||||
material: &Material,
|
||||
incident_ray: Vector,
|
||||
incident_ray: &Vector,
|
||||
normal: Vector,
|
||||
) -> f64 {
|
||||
let mut cos_theta_i = dot(incident_ray, normal);
|
||||
let mut cos_theta_i = dot(*incident_ray, normal);
|
||||
|
||||
if cos_theta_i < 0.0 {
|
||||
cos_theta_i = dot(incident_ray, -normal);
|
||||
cos_theta_i = dot(*incident_ray, -normal);
|
||||
}
|
||||
|
||||
let f0 = ((material.eta - 1.0) / (material.eta + 1.0)).powi(2);
|
||||
|
@ -381,11 +426,29 @@ impl Scene {
|
|||
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(
|
||||
incident_ray.direction.clone(),
|
||||
intersection_context.normal,
|
||||
);
|
||||
|
||||
let origin = intersection_context.point;
|
||||
let origin = origin + JITTER_CONST * reflection_ray;
|
||||
let ray = Ray::new(origin, reflection_ray);
|
||||
|
||||
self.trace_single_ray(origin, ray, depth + 1)
|
||||
}
|
||||
|
||||
fn compute_transparency(
|
||||
&self,
|
||||
intersection_context: &IntersectionContext,
|
||||
incident_ray: Ray,
|
||||
material: &Material,
|
||||
incident_ray: &Ray,
|
||||
eta_i: f64,
|
||||
eta_t: f64,
|
||||
depth: usize,
|
||||
|
@ -394,72 +457,67 @@ impl Scene {
|
|||
trace_span!("compute_transparency", eta_i = eta_i, eta_t = eta_t);
|
||||
let _enter = span.enter();
|
||||
|
||||
let mut n = intersection_context.normal.normalize();
|
||||
if intersection_context.exiting {
|
||||
n = -n;
|
||||
}
|
||||
// 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 i = incident_ray.direction.normalize();
|
||||
|
||||
assert!(eta_t != 0.0, "wtf eta_t is 0");
|
||||
|
||||
// Slide 69
|
||||
let mut cos_theta_i = dot(i, n);
|
||||
// This comes in two parts: one is reflection and one is refraction. The
|
||||
// refraction component will only occur if the angle remains below the
|
||||
// critical angle. The reflection amount is added in proportion to the
|
||||
// Fresnel coefficient.
|
||||
|
||||
if cos_theta_i > 1.0 {
|
||||
// warn!("[{depth}] cos_theta_i = {cos_theta_i}");
|
||||
cos_theta_i = 1.0;
|
||||
}
|
||||
// First, calculate whether or not refraction is happening. If total
|
||||
// internal reflection occurs, then there's no refraction since there's
|
||||
// no ray escaping the medium.
|
||||
|
||||
let sin_theta_i = (1.0 - cos_theta_i.powi(2)).sqrt();
|
||||
let value =
|
||||
match compute_refraction_lengths(normal, &incident_ray, eta_i, eta_t) {
|
||||
Some(RefractionResult {
|
||||
cos_theta_i,
|
||||
sin_theta_i,
|
||||
sin_theta_t,
|
||||
cos_theta_t,
|
||||
}) => {
|
||||
// Now that we identified that there is refraction happening, transmit
|
||||
// a ray through the material at the scene behind it in the
|
||||
// new direction.
|
||||
|
||||
// Total internal reflection check
|
||||
if sin_theta_i * eta_i > eta_t {
|
||||
warn!(
|
||||
sin_theta_i,
|
||||
eta_i, eta_t, "Total internal reflection check failed."
|
||||
);
|
||||
return Ok(ZERO_COLOR);
|
||||
}
|
||||
// Calculate refraction direction
|
||||
let a = normal.normalize() * cos_theta_t;
|
||||
let s_direction = cos_theta_i * normal - i;
|
||||
let m_unit = s_direction.normalize();
|
||||
let b = m_unit * sin_theta_t;
|
||||
let t = a + b;
|
||||
|
||||
let sin_theta_t = (eta_i / eta_t) * sin_theta_i;
|
||||
let cos_theta_t = (1.0 - sin_theta_t.powi(2)).sqrt();
|
||||
// Jitter a bit to reduce acne
|
||||
// TODO: Is this a good constant?
|
||||
let origin = intersection_context.point;
|
||||
let origin = origin + JITTER_CONST * t;
|
||||
let ray = Ray::new(origin, t);
|
||||
|
||||
let fresnel_coefficient = self.compute_fresnel_coefficient(&material, i, n);
|
||||
self.trace_single_ray(origin, ray, depth + 1)?
|
||||
}
|
||||
|
||||
// Calculate refraction direction
|
||||
let a = (-n).normalize() * cos_theta_t;
|
||||
let s_direction = cos_theta_i * n - i;
|
||||
let m_unit = s_direction.normalize();
|
||||
let b = m_unit * sin_theta_t;
|
||||
let t = a + b;
|
||||
// No extra color from the transmitted ray, since it's completely
|
||||
// reflected
|
||||
None => ZERO_COLOR,
|
||||
};
|
||||
|
||||
// Jitter a bit to reduce acne
|
||||
// TODO: Is this a good constant?
|
||||
let origin = intersection_context.point;
|
||||
let origin = origin + JITTER_CONST * t;
|
||||
|
||||
let ray = Ray::new(origin, t);
|
||||
if ray.has_nan() {
|
||||
warn!("WTF");
|
||||
}
|
||||
/* assert!(
|
||||
!ray.has_nan(),
|
||||
"ray is nan WTF {cos_theta_i} {sin_theta_i} ({}) {sin_theta_t} {cos_theta_t} | normal: {:?}",
|
||||
eta_i / eta_t,
|
||||
intersection_context.normal,
|
||||
); */
|
||||
let t_lambda = self.trace_single_ray(ray, depth + 1)?;
|
||||
|
||||
let value = (1.0 - fresnel_coefficient) * (1.0 - material.alpha) * t_lambda;
|
||||
/* debug!(
|
||||
depth,
|
||||
fresnel_coefficient,
|
||||
alpha = material.alpha,
|
||||
?t_lambda,
|
||||
?value,
|
||||
"computing final value"
|
||||
); */
|
||||
// Calculate reflection
|
||||
// let sin_theta_t = (eta_i / eta_t) * sin_theta_i;
|
||||
// let cos_theta_t = (1.0 - sin_theta_t.powi(2)).sqrt();
|
||||
// let fresnel_coefficient = self.compute_fresnel_coefficient(&material, i,
|
||||
// n);
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::{image::Color, ray::Ray};
|
||||
use crate::{image::Color, ray::Ray, Point};
|
||||
|
||||
use super::Scene;
|
||||
|
||||
const MAX_RECURSION_DEPTH: usize = 10_usize;
|
||||
|
||||
impl Scene {
|
||||
pub fn trace_single_ray(&self, ray: Ray, depth: usize) -> Result<Color> {
|
||||
pub fn trace_single_ray(
|
||||
&self,
|
||||
origin: Point,
|
||||
ray: Ray,
|
||||
depth: usize,
|
||||
) -> Result<Color> {
|
||||
if depth > MAX_RECURSION_DEPTH {
|
||||
return Ok(Color::new(0.0, 0.0, 0.0));
|
||||
}
|
||||
|
@ -45,6 +50,7 @@ impl Scene {
|
|||
.compute_pixel_color(
|
||||
obj_idx,
|
||||
object,
|
||||
origin,
|
||||
ray,
|
||||
intersection_context,
|
||||
depth,
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use nalgebra::{Matrix3, Vector3};
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use crate::{Vector};
|
||||
use crate::{ray::Ray, Vector};
|
||||
|
||||
/// Finds the minimum of an iterator of f64s, ignoring any NaN values
|
||||
#[inline]
|
||||
|
@ -93,3 +93,44 @@ pub fn compute_rotation_matrix(
|
|||
|
||||
Ok(f_inverse * g * f)
|
||||
}
|
||||
|
||||
pub struct RefractionResult {
|
||||
pub cos_theta_i: f64,
|
||||
pub sin_theta_i: f64,
|
||||
pub sin_theta_t: f64,
|
||||
pub cos_theta_t: f64,
|
||||
}
|
||||
|
||||
/// This function computes the 4 values:
|
||||
///
|
||||
/// - cos_theta_i
|
||||
/// - sin_theta_i
|
||||
/// - sin_theta_t
|
||||
/// - cos_theta_t
|
||||
///
|
||||
/// If total internal reflection occurs, return None instead.
|
||||
pub fn compute_refraction_lengths(
|
||||
normal: Vector,
|
||||
incident_ray: &Ray,
|
||||
eta_i: f64,
|
||||
eta_t: f64,
|
||||
) -> Option<RefractionResult> {
|
||||
let i = incident_ray.direction.normalize();
|
||||
let cos_theta_i = dot(i, normal);
|
||||
let sin_theta_i = (1.0 - cos_theta_i.powi(2)).sqrt();
|
||||
|
||||
if sin_theta_i * eta_i > eta_t {
|
||||
info!("Total internal reflection encountered.");
|
||||
return None;
|
||||
}
|
||||
|
||||
let sin_theta_t = (eta_i / eta_t) * sin_theta_i;
|
||||
let cos_theta_t = (1.0 - sin_theta_t.powi(2)).sqrt();
|
||||
|
||||
Some(RefractionResult {
|
||||
cos_theta_i,
|
||||
sin_theta_i,
|
||||
sin_theta_t,
|
||||
cos_theta_t,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue