Add light attenuation
This commit is contained in:
parent
b49f171f32
commit
e2b74d4559
6 changed files with 140 additions and 17 deletions
15
assignment-1b/examples/attenuation-demo.txt
Normal file
15
assignment-1b/examples/attenuation-demo.txt
Normal 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
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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}
|
||||||
\
|
\
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue