From e2b74d4559443e95f95dbde098b398d11eca2b14 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Thu, 16 Feb 2023 01:44:31 -0600 Subject: [PATCH] Add light attenuation --- assignment-1b/examples/attenuation-demo.txt | 15 ++++++ assignment-1b/src/scene/data.rs | 31 +++++++++++- assignment-1b/src/scene/illumination.rs | 27 ++++++++++- assignment-1b/src/scene/input_file.rs | 29 +++++++++++- assignment-1b/src/scene/mod.rs | 3 +- assignment-1b/writeup.md | 52 ++++++++++++++++----- 6 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 assignment-1b/examples/attenuation-demo.txt diff --git a/assignment-1b/examples/attenuation-demo.txt b/assignment-1b/examples/attenuation-demo.txt new file mode 100644 index 000000000..f90d239 --- /dev/null +++ b/assignment-1b/examples/attenuation-demo.txt @@ -0,0 +1,15 @@ +imsize 600 200 +eye 0 0 15 +viewdir 0 0 -1 +hfov 90 +updir 0 1 0 +bkgcolor 0.4 0.4 0.4 + +attlight -15 10 5 1 1 1 1 0 0.25 0.03 + +mtlcolor 0.6 1 0.8 1 1 1 0.4 1 0.5 15 +sphere -10 0 0 2 +sphere -5 0 0 2 +sphere 0 0 0 2 +sphere 5 0 0 2 +sphere 10 0 0 2 diff --git a/assignment-1b/src/scene/data.rs b/assignment-1b/src/scene/data.rs index 2ae887f..f054d65 100644 --- a/assignment-1b/src/scene/data.rs +++ b/assignment-1b/src/scene/data.rs @@ -66,7 +66,12 @@ pub struct Material { #[derive(Debug)] pub enum LightKind { /// A point light source exists at a point and emits light in all directions - Point { location: Vector3 }, + Point { + location: Vector3, + + /// Whether light attenuation is enabled for this light + attenuation: Option, + }, /// A directional light source exists at an infinitely far location but emits /// light in a specific direction @@ -87,7 +92,7 @@ impl Light { /// light source pub fn direction_from(&self, point: Vector3) -> Vector3 { match self.kind { - LightKind::Point { location } => location - point, + LightKind::Point { location, .. } => location - point, LightKind::Directional { direction } => -direction, } .normalize() @@ -131,6 +136,28 @@ impl Default for DepthCueing { } } +/// Light attenuation dropoff coefficients +#[derive(Debug)] +pub struct Attenuation { + pub c1: f64, + pub c2: f64, + pub c3: f64, +} + +/// A default implementation here needs to simulate what would happen if there +/// was no light attenuation specified. In this case, c1 would just be a +/// constant of 1 and all the coefficients for anything involving distance would +/// be zeroed out +impl Default for Attenuation { + fn default() -> Self { + Self { + c1: 1.0, + c2: 0.0, + c3: 0.0, + } + } +} + impl Scene { /// Determine the boundaries of the viewing window in world coordinates pub fn compute_viewing_window(&self, distance: f64) -> Rect { diff --git a/assignment-1b/src/scene/illumination.rs b/assignment-1b/src/scene/illumination.rs index 424fee9..446bf85 100644 --- a/assignment-1b/src/scene/illumination.rs +++ b/assignment-1b/src/scene/illumination.rs @@ -61,14 +61,37 @@ impl Scene { .max(0.0) .powf(material.exponent); + // Shadow coefficient between 0 and 1 to control how bright this pixel + // should be from being in the shadow of another object (could be + // between 0 and 1 when applying soft shadows) let shadow_coefficient = self.compute_shadow_coefficient( obj_idx, intersection_context.point, light, ); + let attenuation_coefficient = match &light.kind { + LightKind::Point { + location, + attenuation: Some(att), + } => { + let dist = (location - intersection_context.point).norm(); + let denom = att.c1 + att.c2 * dist + att.c3 * dist.powi(2); + if denom == 0.0 { + warn!("Light attenuation coefficients produced a denominator of 0. Check your inputs..."); + 1.0 // Some kind of graceful fallback here + } else { + 1.0 / denom + } + } + _ => 1.0, + }; + let diffuse_and_specular = diffuse_component + specular_component; - shadow_coefficient * light.color.component_mul(&diffuse_and_specular) + + attenuation_coefficient + * shadow_coefficient + * light.color.component_mul(&diffuse_and_specular) }) .sum(); @@ -141,7 +164,7 @@ impl Scene { 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 } => { + LightKind::Point { location, .. } => { let light_time = (location - ray.origin).norm(); if intersection_time <= 0.0 || intersection_time >= light_time { diff --git a/assignment-1b/src/scene/input_file.rs b/assignment-1b/src/scene/input_file.rs index 6c5c637..9942464 100644 --- a/assignment-1b/src/scene/input_file.rs +++ b/assignment-1b/src/scene/input_file.rs @@ -5,7 +5,7 @@ use nalgebra::Vector3; use crate::scene::{ cylinder::Cylinder, - data::{Light, LightKind, Material, Object}, + data::{Light, LightKind, Material, Object, Attenuation}, sphere::Sphere, Scene, }; @@ -79,6 +79,33 @@ impl Scene { }, 1 => LightKind::Point { location: read_vec3(0)?, + attenuation: None, + }, + _ => bail!("Invalid w; must be either 0 or 1"), + }; + let light = Light { + kind, + color: read_vec3(4)?, + }; + scene.lights.push(light); + } + + // attlight x y z w r g b c1 c2 c3 + "attlight" => { + ensure!(parts.len() == 10, "Attenuated light requires 10 params"); + + let kind = match parts[3] as usize { + // TODO: Is this even defined? Pending TA answer + 0 => LightKind::Directional { + direction: read_vec3(0)?, + }, + 1 => LightKind::Point { + location: read_vec3(0)?, + attenuation: Some(Attenuation { + c1: parts[7], + c2: parts[8], + c3: parts[9], + }), }, _ => bail!("Invalid w; must be either 0 or 1"), }; diff --git a/assignment-1b/src/scene/mod.rs b/assignment-1b/src/scene/mod.rs index 4bf217b..cc683ab 100644 --- a/assignment-1b/src/scene/mod.rs +++ b/assignment-1b/src/scene/mod.rs @@ -8,7 +8,7 @@ use nalgebra::Vector3; use crate::image::Color; -use self::data::{DepthCueing, Light, Material, Object}; +use self::data::{DepthCueing, Light, Material, Object, Attenuation}; #[derive(Debug, Default)] pub struct Scene { @@ -26,6 +26,7 @@ pub struct Scene { /// Background color pub bkg_color: Color, pub depth_cueing: DepthCueing, + pub attenuation: Attenuation, pub materials: Vec, pub lights: Vec, diff --git a/assignment-1b/writeup.md b/assignment-1b/writeup.md index 8d0df45..c9aaf07 100644 --- a/assignment-1b/writeup.md +++ b/assignment-1b/writeup.md @@ -5,13 +5,15 @@ output: pdf_document # Raytracer part B -This project implements a raytracer with Blinn-Phong illumination implemented. -The primary formula that is used by this implementation is: +This project implements a raytracer with Blinn-Phong illumination and shadows +implemented. The primary formula that is used by this implementation is: \begin{equation} I_{\lambda} = k_a O_{d\lambda} + \sum_{i=1}^{n_\textrm{lights}} \left( +f_\textrm{att} \cdot +S_i \cdot IL_{i\lambda} \left[ k_d O_{d\lambda} \max ( 0, \vec{N} \cdot \vec{L_i} ) + k_s O_{s\lambda} \max ( 0, \vec{N} \cdot \vec{H_i} )^n @@ -26,6 +28,8 @@ Where: - $k_d$ is the material's diffuse reflectivity - $k_s$ is the material's specular reflectivity - $n_\textrm{lights}$ is the number of lights +- $f_\textrm{att}$ is the light attenuation factor (1.0 if attenuation is not on) +- $S_i$ is the shadow coefficient for light $i$ - $IL_{i\lambda}$ is the intensity of light $i$ - $O_{d\lambda}$ is the object's diffuse color - $O_{s\lambda}$ is the object's specular color @@ -35,6 +39,11 @@ Where: direction to the viewer - $n$ is the exponent for the specular component +In this report we will look through how these various factors influence the +rendering of the scene. All the images along with their source `.txt` files, +rendered `.ppm` files, and converted `.png` files can be found in the `examples` +directory of this handin. + ## Varying $k_a$ $k_a$ is the strength of ambient light. It's used as a coefficient for the @@ -44,21 +53,21 @@ $k_a$ between 0.2 and 1. Note how the overall color of the ball increases or decreases in brightness when all other factors remain constant. ![Varying $k_a$](examples/ka-demo.png){width=360px} -\ +\ ## Varying $k_d$ TODO ![Varying $k_d$](examples/kd-demo.png){width=360px} -\ +\ ## Varying $k_s$ TODO ![Varying $k_s$](examples/ks-demo.png){width=360px} -\ +\ ## Varying $n$ @@ -68,7 +77,7 @@ the image below, I varied $n$ between 2 and 100. Note how the size of the shine is more focused but covers a smaller area as $n$ increases. ![Varying $n$](examples/n-demo.png){width=360px} -\ +\ ## Multiple lights @@ -80,7 +89,7 @@ is clamped against 1.0. Below is an example of a scene with two lights; one to the left and one to the right: ![Multiple lights](examples/multiple-lights-demo.png){width=360px} -\ +\ ## Shadows @@ -97,7 +106,29 @@ object. Taking the proportion of rays that hit as a coefficient for the shadow, we can get some soft shadow effects like this: ![Soft shadows](examples/soft-shadow-demo.png){width=360px} -\ +\ + +## Light attenuation + +Light attenuation is when more of the light is applied for objects that are +closer to a particular light source. The function that's applied is an inverse +quadratic formula with respect to the distance the object is from the light: + +\begin{equation} +f_\textrm{att}(d) = \frac{1}{c_1 + c_2 d + c_3 d^2} +\end{equation} + +Where: + +- $f_\textrm{att}$ is the attenuation factor +- $d$ is the distance the object is from the light +- $c_1$, $c_2$, and $c_3$ are user-supplied coefficients + +As you can see below, the effect of the light drops off with the distance from +the light (light coming from the left): + +![Light attenuation](examples/attenuation-demo.png){width=360px} +\ ## Depth Cueing @@ -107,7 +138,7 @@ the image below; note how the objects are less and less bright the further they are away from the eye. ![Depth cueing](examples/depth-cueing-demo.png){width=360px} -\ +\ ## Shortcomings of the model @@ -116,5 +147,4 @@ The model cannot be used to represent TODO # Arbitrary Objects ![Objects in the scene](examples/objects.png){width=360px} -\ - +\