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)]
pub enum LightKind {
/// 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
/// light in a specific direction
@ -87,7 +92,7 @@ impl Light {
/// light source
pub fn direction_from(&self, point: Vector3<f64>) -> Vector3<f64> {
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 {

View file

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

View file

@ -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"),
};

View file

@ -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<Material>,
pub lights: Vec<Light>,

View file

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