implement shadows
This commit is contained in:
parent
1885fe59a1
commit
43b85ae5d8
12 changed files with 213 additions and 61 deletions
39
assignment-1b/Cargo.lock
generated
39
assignment-1b/Cargo.lock
generated
|
@ -28,6 +28,7 @@ dependencies = [
|
|||
"num",
|
||||
"ordered-float",
|
||||
"rayon",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -388,6 +389,12 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
|
@ -532,6 +539,38 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
|
|
|
@ -16,3 +16,4 @@ nalgebra = "0.32.1"
|
|||
num = { version = "0.4.0", features = ["serde"] }
|
||||
ordered-float = "3.4.0"
|
||||
rayon = "1.6.1"
|
||||
tracing = "0.1.37"
|
||||
|
|
|
@ -5,7 +5,7 @@ hfov 60
|
|||
updir 0 1 0
|
||||
bkgcolor 0.1 0.1 0.1
|
||||
|
||||
depthcueing 0.1 0.1 0.1 1 0.2 100 5
|
||||
depthcueing 0.1 0.1 0.1 1 0.2 100 0
|
||||
|
||||
light -10 10 -3 0 0.8 0.8 0.8
|
||||
light -10 10 -3 1 0.8 0.8 0.8
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate derivative;
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod image;
|
||||
pub mod ray;
|
||||
|
|
|
@ -98,12 +98,13 @@ fn main() -> Result<()> {
|
|||
let intersections = scene
|
||||
.objects
|
||||
.iter()
|
||||
.filter_map(|object| {
|
||||
.enumerate()
|
||||
.filter_map(|(i, object)| {
|
||||
match object.kind.intersects_ray_at(&ray) {
|
||||
Ok(Some(t)) => {
|
||||
// Return both the t and the sphere, because we want to sort on
|
||||
// the t but later retrieve attributes from the sphere
|
||||
Some(Ok((t, object)))
|
||||
Some(Ok((i, t, object)))
|
||||
}
|
||||
Ok(None) => None,
|
||||
Err(e) => Some(Err(e)),
|
||||
|
@ -113,13 +114,12 @@ fn main() -> Result<()> {
|
|||
|
||||
// Sort the list of intersection times by the lowest one.
|
||||
let earliest_intersection =
|
||||
intersections.into_iter().min_by_key(|(t, _)| t.time);
|
||||
intersections.into_iter().min_by_key(|(_, t, _)| t.time);
|
||||
|
||||
Ok(match earliest_intersection {
|
||||
// Take the object's material color
|
||||
Some((intersection_context, object)) => {
|
||||
scene.compute_pixel_color(object.material, intersection_context)
|
||||
}
|
||||
Some((obj_idx, intersection_context, object)) => scene
|
||||
.compute_pixel_color(obj_idx, object.material, intersection_context),
|
||||
|
||||
// There was no intersection, so this should default to the scene's
|
||||
// background color
|
||||
|
|
|
@ -15,12 +15,12 @@ pub struct Cylinder {
|
|||
pub length: f64,
|
||||
}
|
||||
|
||||
impl ObjectKind for Cylinder {
|
||||
impl Cylinder {
|
||||
/// Given a cylinder, returns the first time at which this ray intersects the
|
||||
/// cylinder.
|
||||
///
|
||||
/// If there is no intersection point, returns None.
|
||||
fn intersects_ray_at(
|
||||
pub fn intersects_ray_at(
|
||||
&self,
|
||||
ray: &Ray,
|
||||
) -> Result<Option<IntersectionContext>> {
|
||||
|
|
|
@ -6,23 +6,38 @@ use nalgebra::Vector3;
|
|||
use crate::image::Color;
|
||||
use crate::ray::Ray;
|
||||
|
||||
use super::cylinder::Cylinder;
|
||||
use super::illumination::IntersectionContext;
|
||||
use super::sphere::Sphere;
|
||||
use super::Scene;
|
||||
|
||||
pub trait ObjectKind: Debug + Send + Sync {
|
||||
#[derive(Debug)]
|
||||
pub enum ObjectKind {
|
||||
Sphere(Sphere),
|
||||
Cylinder(Cylinder),
|
||||
}
|
||||
|
||||
impl ObjectKind {
|
||||
/// Determine where the ray intersects this object, returning the earliest
|
||||
/// time this happens. Returns None if no intersection occurs.
|
||||
///
|
||||
/// Also known as Trace_Ray in the slides, except not the part where it calls
|
||||
/// Shade_Ray.
|
||||
fn intersects_ray_at(&self, ray: &Ray)
|
||||
-> Result<Option<IntersectionContext>>;
|
||||
pub fn intersects_ray_at(
|
||||
&self,
|
||||
ray: &Ray,
|
||||
) -> Result<Option<IntersectionContext>> {
|
||||
match self {
|
||||
ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(ray),
|
||||
ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(ray),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An object in the scene
|
||||
#[derive(Debug)]
|
||||
pub struct Object {
|
||||
pub kind: Box<dyn ObjectKind>,
|
||||
pub kind: ObjectKind,
|
||||
|
||||
/// Index into the scene's material color list
|
||||
pub material: usize,
|
||||
|
@ -66,12 +81,36 @@ pub struct Light {
|
|||
pub color: Vector3<f64>,
|
||||
}
|
||||
|
||||
impl Light {
|
||||
/// Get the unit directional vector pointing from the given point to this
|
||||
/// light source
|
||||
pub fn direction_from(&self, point: Vector3<f64>) -> Vector3<f64> {
|
||||
match self.kind {
|
||||
LightKind::Point { location } => location - point,
|
||||
LightKind::Directional { direction } => -direction,
|
||||
}
|
||||
.normalize()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DepthCueing {
|
||||
/// The color to tint (should be the same as the background color, to avoid
|
||||
/// bizarre visual effects)
|
||||
pub color: Color,
|
||||
|
||||
/// Proportion of the color influenced by the depth tint when the distance is
|
||||
/// maxed (caps at 1.0)
|
||||
pub a_max: f64,
|
||||
|
||||
/// Proportion of the color influenced by the depth tint when the distance is
|
||||
/// at the minimum (caps at 1.0)
|
||||
pub a_min: f64,
|
||||
|
||||
/// The max distance that should be affected by the depth tint
|
||||
pub dist_max: f64,
|
||||
|
||||
/// The min distance that should be affected by the depth tint
|
||||
pub dist_min: f64,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +1,16 @@
|
|||
use anyhow::Result;
|
||||
use nalgebra::Vector3;
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use crate::image::Color;
|
||||
|
||||
use super::{
|
||||
data::{DepthCueing, LightKind},
|
||||
Scene,
|
||||
use rayon::prelude::{
|
||||
IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator,
|
||||
};
|
||||
|
||||
/// Information about an intersection
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug, PartialEq, PartialOrd, Ord)]
|
||||
pub struct IntersectionContext {
|
||||
/// The time of the intersection in the parametric ray
|
||||
///
|
||||
/// Unfortunately, IEEE floats in Rust don't have total ordering, because
|
||||
/// NaNs violate ordering properties. The way to remedy this is to ensure we
|
||||
/// don't have NaNs by wrapping it into this type, which then implements
|
||||
/// total ordering.
|
||||
pub time: NotNan<f64>,
|
||||
use crate::{image::Color, ray::Ray};
|
||||
|
||||
/// The intersection point.
|
||||
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
||||
pub point: Vector3<f64>,
|
||||
|
||||
/// The normal vector protruding from the surface of the object at the
|
||||
/// intersection point
|
||||
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
||||
pub normal: Vector3<f64>,
|
||||
}
|
||||
|
||||
impl Eq for IntersectionContext {}
|
||||
use super::{
|
||||
data::{DepthCueing, Light, LightKind},
|
||||
Scene,
|
||||
};
|
||||
|
||||
impl Scene {
|
||||
/// Determine the color that should be used to fill this pixel.
|
||||
|
@ -42,6 +22,7 @@ impl Scene {
|
|||
/// Also known as Shade_Ray in the slides.
|
||||
pub fn compute_pixel_color(
|
||||
&self,
|
||||
obj_idx: usize,
|
||||
material_idx: usize,
|
||||
intersection_context: IntersectionContext,
|
||||
) -> Color {
|
||||
|
@ -57,16 +38,10 @@ impl Scene {
|
|||
// Diffuse and specular lighting for each separate light
|
||||
let diffuse_and_specular: Vector3<f64> = self
|
||||
.lights
|
||||
.iter()
|
||||
.par_iter()
|
||||
.map(|light| {
|
||||
// The vector pointing in the direction of the light
|
||||
let light_direction = match light.kind {
|
||||
LightKind::Point { location } => {
|
||||
location - intersection_context.point
|
||||
}
|
||||
LightKind::Directional { direction } => -direction,
|
||||
}
|
||||
.normalize();
|
||||
let light_direction = light.direction_from(intersection_context.point);
|
||||
|
||||
let normal = intersection_context.normal.normalize();
|
||||
let viewer_direction = self.eye_pos - intersection_context.point;
|
||||
|
@ -84,8 +59,14 @@ impl Scene {
|
|||
.max(0.0)
|
||||
.powf(material.exponent);
|
||||
|
||||
let shadow_flag =
|
||||
match self.in_shadow_of(obj_idx, intersection_context.point, light) {
|
||||
true => 0.0,
|
||||
false => 1.0,
|
||||
};
|
||||
|
||||
let diffuse_and_specular = diffuse_component + specular_component;
|
||||
light.color.component_mul(&diffuse_and_specular)
|
||||
shadow_flag * light.color.component_mul(&diffuse_and_specular)
|
||||
})
|
||||
.sum();
|
||||
|
||||
|
@ -119,4 +100,93 @@ impl Scene {
|
|||
|
||||
clamped_result
|
||||
}
|
||||
|
||||
/// Perform another ray casting to see if there are any objects obstructing
|
||||
/// the light source to this particular point
|
||||
pub fn in_shadow_of(
|
||||
&self,
|
||||
obj_idx: usize,
|
||||
point: Vector3<f64>,
|
||||
light: &Light,
|
||||
) -> bool {
|
||||
let light_direction = light.direction_from(point);
|
||||
let ray = Ray {
|
||||
origin: point,
|
||||
direction: light_direction.normalize(),
|
||||
};
|
||||
|
||||
// Small helper for iterating over all of the objects in the scene except
|
||||
// for the current one
|
||||
let other_objects = self
|
||||
.objects
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| *i != obj_idx);
|
||||
|
||||
// Get the list of intersections with all the other objects in the scene
|
||||
let intersections = other_objects
|
||||
.filter_map(|(_, object)| {
|
||||
let intersection_context = match object.kind.intersects_ray_at(&ray) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
error!("Error while performing shadow casting: {err}");
|
||||
None
|
||||
}
|
||||
}?;
|
||||
|
||||
match light.kind {
|
||||
// In the case of point lights, we must check to see if both t > 0 and
|
||||
// t is less than the time it took to even get to the light.
|
||||
LightKind::Point { location } => {
|
||||
let light_time = (location - ray.origin).norm();
|
||||
let intersection_time = *intersection_context.time;
|
||||
|
||||
if intersection_time <= 0.0 || intersection_time >= light_time {
|
||||
None
|
||||
} else {
|
||||
Some(intersection_context)
|
||||
}
|
||||
}
|
||||
|
||||
// In the case of directional lights, only t > 0 needs to be checked,
|
||||
// otherwise
|
||||
LightKind::Directional { .. } => {
|
||||
if *intersection_context.time <= 0.0 {
|
||||
None
|
||||
} else {
|
||||
Some(intersection_context)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
!intersections.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about an intersection
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug, PartialEq, PartialOrd, Ord)]
|
||||
pub struct IntersectionContext {
|
||||
/// The time of the intersection in the parametric ray
|
||||
///
|
||||
/// Unfortunately, IEEE floats in Rust don't have total ordering, because
|
||||
/// NaNs violate ordering properties. The way to remedy this is to ensure we
|
||||
/// don't have NaNs by wrapping it into this type, which then implements
|
||||
/// total ordering.
|
||||
pub time: NotNan<f64>,
|
||||
|
||||
/// The intersection point.
|
||||
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
||||
pub point: Vector3<f64>,
|
||||
|
||||
/// The normal vector protruding from the surface of the object at the
|
||||
/// intersection point
|
||||
#[derivative(PartialEq = "ignore", Ord = "ignore")]
|
||||
pub normal: Vector3<f64>,
|
||||
}
|
||||
|
||||
impl Eq for IntersectionContext {}
|
||||
|
||||
impl IntersectionContext {}
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::scene::{
|
|||
Scene,
|
||||
};
|
||||
|
||||
use super::data::DepthCueing;
|
||||
use super::data::{DepthCueing, ObjectKind};
|
||||
|
||||
impl Scene {
|
||||
/// Parse the input file into a scene
|
||||
|
@ -125,7 +125,7 @@ impl Scene {
|
|||
}
|
||||
|
||||
"sphere" => scene.objects.push(Object {
|
||||
kind: Box::new(Sphere {
|
||||
kind: ObjectKind::Sphere(Sphere {
|
||||
center: read_vec3(0)?,
|
||||
radius: parts[3],
|
||||
}),
|
||||
|
@ -138,7 +138,7 @@ impl Scene {
|
|||
}),
|
||||
|
||||
"cylinder" => scene.objects.push(Object {
|
||||
kind: Box::new(Cylinder {
|
||||
kind: ObjectKind::Cylinder(Cylinder {
|
||||
center: read_vec3(0)?,
|
||||
direction: read_vec3(3)?,
|
||||
radius: parts[6],
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
pub mod cylinder;
|
||||
pub mod data;
|
||||
pub mod illumination;
|
||||
pub mod input_file;
|
||||
pub mod sphere;
|
||||
pub mod illumination;
|
||||
|
||||
use nalgebra::Vector3;
|
||||
|
||||
|
|
|
@ -12,12 +12,12 @@ pub struct Sphere {
|
|||
pub radius: f64,
|
||||
}
|
||||
|
||||
impl ObjectKind for Sphere {
|
||||
impl Sphere {
|
||||
/// Given a sphere, returns the first time at which this ray intersects the
|
||||
/// sphere.
|
||||
///
|
||||
/// If there is no intersection point, returns None.
|
||||
fn intersects_ray_at(
|
||||
pub fn intersects_ray_at(
|
||||
&self,
|
||||
ray: &Ray,
|
||||
) -> Result<Option<IntersectionContext>> {
|
||||
|
|
11
flake.nix
11
flake.nix
|
@ -13,14 +13,15 @@
|
|||
in rec {
|
||||
devShell = pkgs.mkShell {
|
||||
packages = (with pkgs; [
|
||||
cargo-watch
|
||||
cargo-deny
|
||||
cargo-edit
|
||||
pandoc
|
||||
zip
|
||||
unzip
|
||||
texlive.combined.scheme-full
|
||||
cargo-flamegraph
|
||||
cargo-watch
|
||||
imagemagick
|
||||
pandoc
|
||||
texlive.combined.scheme-full
|
||||
unzip
|
||||
zip
|
||||
|
||||
(python310.withPackages (p: with p; [ numpy ]))
|
||||
]) ++ (with toolchain; [
|
||||
|
|
Loading…
Reference in a new issue