Add light attenuation

This commit is contained in:
Michael Zhang 2023-02-16 01:44:31 -06:00
parent 00000460ab
commit 0000047066
6 changed files with 140 additions and 17 deletions

View file

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

View file

@ -66,7 +66,12 @@ pub struct Material {
#[derive(Debug)] #[derive(Debug)]
pub enum LightKind { pub enum LightKind {
/// A point light source exists at a point and emits light in all directions /// A point light source exists at a point and emits light in all directions
Point { location: Vector3<f64> }, Point {
location: Vector3<f64>,
/// Whether light attenuation is enabled for this light
attenuation: Option<Attenuation>,
},
/// A directional light source exists at an infinitely far location but emits /// A directional light source exists at an infinitely far location but emits
/// light in a specific direction /// light in a specific direction
@ -87,7 +92,7 @@ impl Light {
/// light source /// light source
pub fn direction_from(&self, point: Vector3<f64>) -> Vector3<f64> { pub fn direction_from(&self, point: Vector3<f64>) -> Vector3<f64> {
match self.kind { match self.kind {
LightKind::Point { location } => location - point, LightKind::Point { location, .. } => location - point,
LightKind::Directional { direction } => -direction, LightKind::Directional { direction } => -direction,
} }
.normalize() .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 { impl Scene {
/// Determine the boundaries of the viewing window in world coordinates /// Determine the boundaries of the viewing window in world coordinates
pub fn compute_viewing_window(&self, distance: f64) -> Rect { pub fn compute_viewing_window(&self, distance: f64) -> Rect {

View file

@ -61,14 +61,37 @@ impl Scene {
.max(0.0) .max(0.0)
.powf(material.exponent); .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( let shadow_coefficient = self.compute_shadow_coefficient(
obj_idx, obj_idx,
intersection_context.point, intersection_context.point,
light, 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; 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(); .sum();
@ -141,7 +164,7 @@ impl Scene {
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();
if intersection_time <= 0.0 || intersection_time >= light_time { if intersection_time <= 0.0 || intersection_time >= light_time {

View file

@ -5,7 +5,7 @@ use nalgebra::Vector3;
use crate::scene::{ use crate::scene::{
cylinder::Cylinder, cylinder::Cylinder,
data::{Light, LightKind, Material, Object}, data::{Light, LightKind, Material, Object, Attenuation},
sphere::Sphere, sphere::Sphere,
Scene, Scene,
}; };
@ -79,6 +79,33 @@ impl Scene {
}, },
1 => LightKind::Point { 1 => LightKind::Point {
location: read_vec3(0)?, 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"), _ => bail!("Invalid w; must be either 0 or 1"),
}; };

View file

@ -8,7 +8,7 @@ use nalgebra::Vector3;
use crate::image::Color; use crate::image::Color;
use self::data::{DepthCueing, Light, Material, Object}; use self::data::{DepthCueing, Light, Material, Object, Attenuation};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Scene { pub struct Scene {
@ -26,6 +26,7 @@ pub struct Scene {
/// Background color /// Background color
pub bkg_color: Color, pub bkg_color: Color,
pub depth_cueing: DepthCueing, pub depth_cueing: DepthCueing,
pub attenuation: Attenuation,
pub materials: Vec<Material>, pub materials: Vec<Material>,
pub lights: Vec<Light>, pub lights: Vec<Light>,

View file

@ -5,13 +5,15 @@ output: pdf_document
# Raytracer part B # Raytracer part B
This project implements a raytracer with Blinn-Phong illumination implemented. This project implements a raytracer with Blinn-Phong illumination and shadows
The primary formula that is used by this implementation is: implemented. The primary formula that is used by this implementation is:
\begin{equation} \begin{equation}
I_{\lambda} = I_{\lambda} =
k_a O_{d\lambda} + k_a O_{d\lambda} +
\sum_{i=1}^{n_\textrm{lights}} \left( \sum_{i=1}^{n_\textrm{lights}} \left(
f_\textrm{att} \cdot
S_i \cdot
IL_{i\lambda} \left[ IL_{i\lambda} \left[
k_d O_{d\lambda} \max ( 0, \vec{N} \cdot \vec{L_i} ) + 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 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_d$ is the material's diffuse reflectivity
- $k_s$ is the material's specular reflectivity - $k_s$ is the material's specular reflectivity
- $n_\textrm{lights}$ is the number of lights - $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$ - $IL_{i\lambda}$ is the intensity of light $i$
- $O_{d\lambda}$ is the object's diffuse color - $O_{d\lambda}$ is the object's diffuse color
- $O_{s\lambda}$ is the object's specular color - $O_{s\lambda}$ is the object's specular color
@ -35,6 +39,11 @@ Where:
direction to the viewer direction to the viewer
- $n$ is the exponent for the specular component - $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$ ## Varying $k_a$
$k_a$ is the strength of ambient light. It's used as a coefficient for the $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. decreases in brightness when all other factors remain constant.
![Varying $k_a$](examples/ka-demo.png){width=360px} ![Varying $k_a$](examples/ka-demo.png){width=360px}
\ \
## Varying $k_d$ ## Varying $k_d$
TODO TODO
![Varying $k_d$](examples/kd-demo.png){width=360px} ![Varying $k_d$](examples/kd-demo.png){width=360px}
\ \
## Varying $k_s$ ## Varying $k_s$
TODO TODO
![Varying $k_s$](examples/ks-demo.png){width=360px} ![Varying $k_s$](examples/ks-demo.png){width=360px}
\ \
## Varying $n$ ## 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. is more focused but covers a smaller area as $n$ increases.
![Varying $n$](examples/n-demo.png){width=360px} ![Varying $n$](examples/n-demo.png){width=360px}
\ \
## Multiple lights ## 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: the left and one to the right:
![Multiple lights](examples/multiple-lights-demo.png){width=360px} ![Multiple lights](examples/multiple-lights-demo.png){width=360px}
\ \
## Shadows ## 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: we can get some soft shadow effects like this:
![Soft shadows](examples/soft-shadow-demo.png){width=360px} ![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 ## 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. are away from the eye.
![Depth cueing](examples/depth-cueing-demo.png){width=360px} ![Depth cueing](examples/depth-cueing-demo.png){width=360px}
\ \
## Shortcomings of the model ## Shortcomings of the model
@ -116,5 +147,4 @@ The model cannot be used to represent TODO
# Arbitrary Objects # Arbitrary Objects
![Objects in the scene](examples/objects.png){width=360px} ![Objects in the scene](examples/objects.png){width=360px}
\ \