Soft shadows
This commit is contained in:
parent
abe4aa6115
commit
b915006848
6 changed files with 193 additions and 17 deletions
61
assignment-1b/Cargo.lock
generated
61
assignment-1b/Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 $@
|
||||
|
|
44
assignment-1b/examples/soft-shadow-demo.txt
Normal file
44
assignment-1b/examples/soft-shadow-demo.txt
Normal 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
|
|
@ -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.
|
||||
|
|
|
@ -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<f64>,
|
||||
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::<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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue