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
@ -99,6 +108,28 @@ 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
Depth cueing is when the objects further from the viewer have a lower opacity to Depth cueing is when the objects further from the viewer have a lower opacity to
@ -117,4 +148,3 @@ The model cannot be used to represent TODO
![Objects in the scene](examples/objects.png){width=360px} ![Objects in the scene](examples/objects.png){width=360px}
\ \