Soft shadows

This commit is contained in:
Michael Zhang 2023-02-15 18:25:22 -06:00
parent abe4aa6115
commit b915006848
6 changed files with 193 additions and 17 deletions

View file

@ -22,11 +22,13 @@ name = "assignment-1b"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64",
"clap", "clap",
"derivative", "derivative",
"nalgebra", "nalgebra",
"num", "num",
"ordered-float", "ordered-float",
"rand",
"rayon", "rayon",
"tracing", "tracing",
] ]
@ -37,6 +39,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -179,6 +187,17 @@ dependencies = [
"libc", "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]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@ -395,6 +414,12 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -437,6 +462,36 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rawpointer" name = "rawpointer"
version = "0.2.1" version = "0.2.1"
@ -589,6 +644,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 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]] [[package]]
name = "wide" name = "wide"
version = "0.7.5" version = "0.7.5"

View file

@ -14,10 +14,12 @@ path = "src/main.rs"
[dependencies] [dependencies]
anyhow = "1.0.68" anyhow = "1.0.68"
base64 = "0.21.0"
clap = { version = "4.1.4", features = ["cargo", "derive"] } clap = { version = "4.1.4", features = ["cargo", "derive"] }
derivative = "2.2.0" derivative = "2.2.0"
nalgebra = "0.32.1" nalgebra = "0.32.1"
num = { version = "0.4.0", features = ["serde"] } num = { version = "0.4.0", features = ["serde"] }
ordered-float = "3.4.0" ordered-float = "3.4.0"
rand = "0.8.5"
rayon = "1.6.1" rayon = "1.6.1"
tracing = "0.1.37" tracing = "0.1.37"

View file

@ -19,12 +19,14 @@ EXAMPLES_PNG := $(patsubst %.txt,%.png,$(EXAMPLES))
all: $(HANDIN) all: $(HANDIN)
$(BINARY): $(SOURCES) $(BINARY): $(SOURCES)
mkdir -p target/docker
$(DOCKER) run \ $(DOCKER) run \
--rm \ --rm \
-v "$(shell pwd)":/usr/src/myapp \ -v "$(shell pwd)":/usr/src/myapp \
-v cargo-registry:/usr/local/cargo \ -v cargo-registry:/usr/local/cargo \
--user "$(shell id -u)":"$(shell id -g)" \ --user "$(shell id -u)":"$(shell id -g)" \
-w /usr/src/myapp \ -w /usr/src/myapp \
-e CARGO_TARGET_DIR=/usr/src/myapp/target/docker \
rust \ rust \
cargo build --release cargo build --release
mv target/release/raytracer1b $@ mv target/release/raytracer1b $@

View file

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

View file

@ -1,11 +1,16 @@
use std::fs::File; use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Result; use anyhow::{ensure, Result};
use assignment_1b::image::Image; use assignment_1b::image::Image;
use assignment_1b::ray::Ray; use assignment_1b::ray::Ray;
use assignment_1b::scene::Scene; use assignment_1b::scene::Scene;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use clap::Parser; use clap::Parser;
use rand::{
rngs::{StdRng, ThreadRng},
thread_rng, Rng, RngCore, SeedableRng,
};
use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use rayon::prelude::{IntoParallelIterator, ParallelIterator};
/// Simple raycaster. /// Simple raycaster.

View file

@ -1,14 +1,17 @@
use anyhow::Result; use std::iter;
use nalgebra::Vector3; use nalgebra::Vector3;
use ordered_float::NotNan; use ordered_float::NotNan;
use rand::Rng;
use rayon::prelude::{ use rayon::prelude::{
IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator, IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator,
ParallelIterator,
}; };
use crate::{image::Color, ray::Ray}; use crate::{image::Color, ray::Ray};
use super::{ use super::{
data::{DepthCueing, Light, LightKind}, data::{DepthCueing, Light, LightKind, Object},
Scene, Scene,
}; };
@ -59,14 +62,14 @@ impl Scene {
.max(0.0) .max(0.0)
.powf(material.exponent); .powf(material.exponent);
let shadow_flag = let shadow_coefficient = self.compute_shadow_coefficient(
match self.in_shadow_of(obj_idx, intersection_context.point, light) { obj_idx,
true => 0.0, intersection_context.point,
false => 1.0, light,
}; );
let diffuse_and_specular = diffuse_component + specular_component; 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(); .sum();
@ -103,12 +106,12 @@ impl Scene {
/// Perform another ray casting to see if there are any objects obstructing /// Perform another ray casting to see if there are any objects obstructing
/// the light source to this particular point /// the light source to this particular point
pub fn in_shadow_of( pub fn compute_shadow_coefficient(
&self, &self,
obj_idx: usize, obj_idx: usize,
point: Vector3<f64>, point: Vector3<f64>,
light: &Light, light: &Light,
) -> bool { ) -> f64 {
let light_direction = light.direction_from(point); let light_direction = light.direction_from(point);
let ray = Ray { let ray = Ray {
origin: point, origin: point,
@ -124,6 +127,7 @@ impl Scene {
.filter(|(i, _)| *i != obj_idx); .filter(|(i, _)| *i != obj_idx);
// Get the list of intersections with all the other objects in the scene // 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 let intersections = other_objects
.filter_map(|(_, object)| { .filter_map(|(_, object)| {
let intersection_context = match object.kind.intersects_ray_at(&ray) { let intersection_context = match object.kind.intersects_ray_at(&ray) {
@ -133,35 +137,93 @@ impl Scene {
None None
} }
}?; }?;
let intersection_time = *intersection_context.time;
match light.kind { match light.kind {
// In the case of point lights, we must check to see if both t > 0 and // 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. // t is less than the time it took to even get to the light.
LightKind::Point { location } => { LightKind::Point { location } => {
let light_time = (location - ray.origin).norm(); let light_time = (location - ray.origin).norm();
let intersection_time = *intersection_context.time;
if intersection_time <= 0.0 || intersection_time >= light_time { if intersection_time <= 0.0 || intersection_time >= light_time {
None None
} else { } 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, // In the case of directional lights, only t > 0 needs to be checked,
// otherwise // otherwise
LightKind::Directional { .. } => { LightKind::Directional { .. } => {
if *intersection_context.time <= 0.0 { if intersection_time <= 0.0 {
None None
} else { } else {
Some(intersection_context) Some(0.0) // complete obstruction
} }
} }
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
!intersections.is_empty() let average =
intersections.iter().cloned().sum::<f64>() / intersections.len() as f64;
match intersections.is_empty() {
true => 1.0,
false => average,
}
}
fn compute_soft_shadow_coefficient(
&self,
light_location: Vector3<f64>,
original_intersection_point: Vector3<f64>,
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::<Vec<_>>();
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
} }
} }