From b915006848c5f60c1933c0de59234cdce7fb1146 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Wed, 15 Feb 2023 18:25:22 -0600 Subject: [PATCH] Soft shadows --- assignment-1b/Cargo.lock | 61 +++++++++++++ assignment-1b/Cargo.toml | 2 + assignment-1b/Makefile | 2 + assignment-1b/examples/soft-shadow-demo.txt | 44 ++++++++++ assignment-1b/src/main.rs | 7 +- assignment-1b/src/scene/illumination.rs | 94 +++++++++++++++++---- 6 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 assignment-1b/examples/soft-shadow-demo.txt diff --git a/assignment-1b/Cargo.lock b/assignment-1b/Cargo.lock index 4d21e1b..ece12db 100644 --- a/assignment-1b/Cargo.lock +++ b/assignment-1b/Cargo.lock @@ -22,11 +22,13 @@ name = "assignment-1b" version = "0.1.0" dependencies = [ "anyhow", + "base64", "clap", "derivative", "nalgebra", "num", "ordered-float", + "rand", "rayon", "tracing", ] @@ -37,6 +39,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "bitflags" version = "1.3.2" @@ -179,6 +187,17 @@ dependencies = [ "libc", ] +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.4.1" @@ -395,6 +414,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -437,6 +462,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -589,6 +644,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wide" version = "0.7.5" diff --git a/assignment-1b/Cargo.toml b/assignment-1b/Cargo.toml index 2e779a3..84f1c5b 100644 --- a/assignment-1b/Cargo.toml +++ b/assignment-1b/Cargo.toml @@ -14,10 +14,12 @@ path = "src/main.rs" [dependencies] anyhow = "1.0.68" +base64 = "0.21.0" clap = { version = "4.1.4", features = ["cargo", "derive"] } derivative = "2.2.0" nalgebra = "0.32.1" num = { version = "0.4.0", features = ["serde"] } ordered-float = "3.4.0" +rand = "0.8.5" rayon = "1.6.1" tracing = "0.1.37" diff --git a/assignment-1b/Makefile b/assignment-1b/Makefile index 8f1e734..676d843 100644 --- a/assignment-1b/Makefile +++ b/assignment-1b/Makefile @@ -19,12 +19,14 @@ EXAMPLES_PNG := $(patsubst %.txt,%.png,$(EXAMPLES)) all: $(HANDIN) $(BINARY): $(SOURCES) + mkdir -p target/docker $(DOCKER) run \ --rm \ -v "$(shell pwd)":/usr/src/myapp \ -v cargo-registry:/usr/local/cargo \ --user "$(shell id -u)":"$(shell id -g)" \ -w /usr/src/myapp \ + -e CARGO_TARGET_DIR=/usr/src/myapp/target/docker \ rust \ cargo build --release mv target/release/raytracer1b $@ diff --git a/assignment-1b/examples/soft-shadow-demo.txt b/assignment-1b/examples/soft-shadow-demo.txt new file mode 100644 index 000000000..d98a020 --- /dev/null +++ b/assignment-1b/examples/soft-shadow-demo.txt @@ -0,0 +1,44 @@ +imsize 640 480 +eye 0 0 15 +viewdir 0 0 -1 +hfov 60 +updir 0 1 0 +bkgcolor 0.5 0.5 0.5 +depthcueing 0.5 0.5 0.5 1 0.4 40 20 + +light 10 10 -10 1 1 1 1 + +mtlcolor 0.5 1 0.5 1 1 1 0.2 0.4 0 10 +sphere 5 5 -20 5 +sphere -5 -5 -20 5 + +mtlcolor 1 0.5 0.5 1 1 1 0.2 0.4 0 10 +sphere -10 0 -30 4 +sphere -20 0 -30 4 +sphere -30 0 -30 4 +sphere -40 0 -30 4 +sphere 0 0 -30 4 +sphere 10 0 -30 4 +sphere 20 0 -30 4 +sphere 30 0 -30 4 +sphere 40 0 -30 4 + +sphere -10 -10 -30 4 +sphere -20 -10 -30 4 +sphere -30 -10 -30 4 +sphere -40 -10 -30 4 +sphere 0 -10 -30 4 +sphere 10 -10 -30 4 +sphere 20 -10 -30 4 +sphere 30 -10 -30 4 +sphere 40 -10 -30 4 + +sphere -10 10 -30 4 +sphere -20 10 -30 4 +sphere -30 10 -30 4 +sphere -40 10 -30 4 +sphere 0 10 -30 4 +sphere 10 10 -30 4 +sphere 20 10 -30 4 +sphere 30 10 -30 4 +sphere 40 10 -30 4 diff --git a/assignment-1b/src/main.rs b/assignment-1b/src/main.rs index 5faa46e..486cfcf 100644 --- a/assignment-1b/src/main.rs +++ b/assignment-1b/src/main.rs @@ -1,11 +1,16 @@ use std::fs::File; use std::path::PathBuf; -use anyhow::Result; +use anyhow::{ensure, Result}; use assignment_1b::image::Image; use assignment_1b::ray::Ray; use assignment_1b::scene::Scene; +use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; use clap::Parser; +use rand::{ + rngs::{StdRng, ThreadRng}, + thread_rng, Rng, RngCore, SeedableRng, +}; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; /// Simple raycaster. diff --git a/assignment-1b/src/scene/illumination.rs b/assignment-1b/src/scene/illumination.rs index c2f5934..ba1dd00 100644 --- a/assignment-1b/src/scene/illumination.rs +++ b/assignment-1b/src/scene/illumination.rs @@ -1,14 +1,17 @@ -use anyhow::Result; +use std::iter; + use nalgebra::Vector3; use ordered_float::NotNan; +use rand::Rng; use rayon::prelude::{ - IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator, + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, + ParallelIterator, }; use crate::{image::Color, ray::Ray}; use super::{ - data::{DepthCueing, Light, LightKind}, + data::{DepthCueing, Light, LightKind, Object}, Scene, }; @@ -59,14 +62,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 shadow_coefficient = self.compute_shadow_coefficient( + obj_idx, + intersection_context.point, + light, + ); let diffuse_and_specular = diffuse_component + specular_component; - shadow_flag * light.color.component_mul(&diffuse_and_specular) + shadow_coefficient * light.color.component_mul(&diffuse_and_specular) }) .sum(); @@ -103,12 +106,12 @@ impl Scene { /// Perform another ray casting to see if there are any objects obstructing /// the light source to this particular point - pub fn in_shadow_of( + pub fn compute_shadow_coefficient( &self, obj_idx: usize, point: Vector3, light: &Light, - ) -> bool { + ) -> f64 { let light_direction = light.direction_from(point); let ray = Ray { origin: point, @@ -124,6 +127,7 @@ impl Scene { .filter(|(i, _)| *i != obj_idx); // Get the list of intersections with all the other objects in the scene + // This list will be a set of opacities let intersections = other_objects .filter_map(|(_, object)| { let intersection_context = match object.kind.intersects_ray_at(&ray) { @@ -133,35 +137,93 @@ impl Scene { None } }?; + let intersection_time = *intersection_context.time; 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) + let soft_shadow_coefficient = + self.compute_soft_shadow_coefficient(location, point, object); + Some(soft_shadow_coefficient) } } // In the case of directional lights, only t > 0 needs to be checked, // otherwise LightKind::Directional { .. } => { - if *intersection_context.time <= 0.0 { + if intersection_time <= 0.0 { None } else { - Some(intersection_context) + Some(0.0) // complete obstruction } } } }) .collect::>(); - !intersections.is_empty() + let average = + intersections.iter().cloned().sum::() / intersections.len() as f64; + + match intersections.is_empty() { + true => 1.0, + false => average, + } + } + + fn compute_soft_shadow_coefficient( + &self, + light_location: Vector3, + original_intersection_point: Vector3, + object: &Object, + ) -> f64 { + // Soft shadows: jitter some rays here to somewhere close to the + // actual location as well, and measure the proportion + // of them that intersect any objects + const JITTER_RADIUS: f64 = 1.0; + const JITTER_RAYS: usize = 75; + + let mut rng = rand::thread_rng(); + let locations = iter::repeat_with(|| { + let x = rng.gen_range(0.0..JITTER_RADIUS); + let y = rng.gen_range(0.0..JITTER_RADIUS); + let z = rng.gen_range(0.0..JITTER_RADIUS); + let delta = Vector3::new(x, y, z); + light_location + delta + }) + .take(JITTER_RAYS) + .collect::>(); + let num_obstructed_rays = locations + .into_par_iter() + .filter(|location| { + let direction = (location - original_intersection_point).normalize(); + let ray = Ray { + origin: original_intersection_point, + direction, + }; + + let intersection_context = match object.kind.intersects_ray_at(&ray) { + Ok(Some(v)) => v, + Ok(None) => return false, + Err(err) => { + error!("Error while performing shadow casting: {err}"); + return false; + } + }; + + let light_time = (location - ray.origin).norm(); + let intersection_time = *intersection_context.time; + + 0.0 < intersection_time && intersection_time < light_time + }) + .count(); + + (JITTER_RAYS - num_obstructed_rays) as f64 / JITTER_RAYS as f64 } }