Added DistantDiskLight (a.k.a. sun light) parsing and data structures.

Also created a proper World struct in the process, to store all
infinite-extent type stuff.

Note that I goofed and did a new rustfmt pass but forgot to
commit before making these changes, so there's a lot of
formatting changes in this too.  *sigh*
This commit is contained in:
Nathan Vegdahl 2017-02-12 20:29:08 -08:00
parent 746b3b0c1f
commit 3cbb816d4b
15 changed files with 329 additions and 111 deletions

View File

@ -191,7 +191,8 @@ pub fn merge_slices_to<T: Lerp + Copy, F>(slice1: &[T],
if slice1.len() == 0 || slice2.len() == 0 { if slice1.len() == 0 || slice2.len() == 0 {
return; return;
} else if slice1.len() == slice2.len() { } else if slice1.len() == slice2.len() {
for (xfo, (xf1, xf2)) in Iterator::zip(slice_out.iter_mut(), for (xfo, (xf1, xf2)) in
Iterator::zip(slice_out.iter_mut(),
Iterator::zip(slice1.iter(), slice2.iter())) { Iterator::zip(slice1.iter(), slice2.iter())) {
*xfo = merge(xf1, xf2); *xfo = merge(xf1, xf2);
} }
@ -216,14 +217,12 @@ mod tests {
use super::*; use super::*;
fn quick_select_ints(list: &mut [i32], i: usize) { fn quick_select_ints(list: &mut [i32], i: usize) {
quick_select(list, i, |a, b| { quick_select(list, i, |a, b| if a < b {
if a < b {
Ordering::Less Ordering::Less
} else if a == b { } else if a == b {
Ordering::Equal Ordering::Equal
} else { } else {
Ordering::Greater Ordering::Greater
}
}); });
} }

View File

@ -48,7 +48,8 @@ impl Assembly {
} else { } else {
Matrix4x4::new() Matrix4x4::new()
}; };
if let Some((light_i, sel_pdf, whittled_n)) = self.light_accel if let Some((light_i, sel_pdf, whittled_n)) =
self.light_accel
.select(incoming * sel_xform, .select(incoming * sel_xform,
pos * sel_xform, pos * sel_xform,
nor * sel_xform, nor * sel_xform,
@ -239,8 +240,7 @@ impl AssemblyBuilder {
// sources. // sources.
let mut light_instances: Vec<_> = self.instances let mut light_instances: Vec<_> = self.instances
.iter() .iter()
.filter(|inst| { .filter(|inst| match inst.instance_type {
match inst.instance_type {
InstanceType::Object => { InstanceType::Object => {
if let Object::Light(_) = self.objects[inst.data_index] { if let Object::Light(_) = self.objects[inst.data_index] {
true true
@ -252,7 +252,6 @@ impl AssemblyBuilder {
InstanceType::Assembly => { InstanceType::Assembly => {
self.assemblies[inst.data_index].light_accel.approximate_energy() > 0.0 self.assemblies[inst.data_index].light_accel.approximate_energy() > 0.0
} }
}
}) })
.map(|&a| a) .map(|&a| a)
.collect(); .collect();

View File

@ -0,0 +1,82 @@
use std::f64::consts::PI as PI_64;
use color::{XYZ, SpectralSample, Color};
use lerp::lerp_slice;
use math::{Vector, coordinate_system_from_vector};
use sampling::{uniform_sample_cone, uniform_sample_cone_pdf};
use super::WorldLightSource;
// TODO: handle case where radius = 0.0.
#[derive(Debug)]
pub struct DistantDiskLight {
radii: Vec<f32>,
directions: Vec<Vector>,
colors: Vec<XYZ>,
}
impl DistantDiskLight {
pub fn new(radii: Vec<f32>, directions: Vec<Vector>, colors: Vec<XYZ>) -> DistantDiskLight {
DistantDiskLight {
radii: radii,
directions: directions,
colors: colors,
}
}
}
impl WorldLightSource for DistantDiskLight {
fn sample(&self, u: f32, v: f32, wavelength: f32, time: f32) -> (SpectralSample, Vector, f32) {
// Calculate time interpolated values
let radius: f64 = lerp_slice(&self.radii, time) as f64;
let direction = lerp_slice(&self.directions, time);
let col = lerp_slice(&self.colors, time);
let solid_angle_inv = 1.0 / (2.0 * PI_64 * (1.0 - radius.cos()));
// Create a coordinate system from the vector pointing at the center of
// of the light.
let (z, x, y) = coordinate_system_from_vector(-direction);
let (x, y, z) = (x.normalized(), y.normalized(), z.normalized());
// Calculate the radius in terms of cosine
let cos_theta_max: f64 = radius.cos();
// Sample the cone subtended by the light.
let sample = uniform_sample_cone(u, v, cos_theta_max).normalized();
// Calculate the final values and return everything.
let shadow_vec = (x * sample.x()) + (y * sample.y()) + (z * sample.z());
let pdf = uniform_sample_cone_pdf(cos_theta_max);
let spectral_sample = (col * solid_angle_inv as f32).to_spectral_sample(wavelength);
return (spectral_sample, shadow_vec, pdf as f32);
}
fn sample_pdf(&self, sample_dir: Vector, wavelength: f32, time: f32) -> f32 {
// We're not using these, silence warnings
let _ = (sample_dir, wavelength);
let radius: f64 = lerp_slice(&self.radii, time) as f64;
return uniform_sample_cone_pdf(radius.cos()) as f32;
}
fn outgoing(&self, dir: Vector, wavelength: f32, time: f32) -> SpectralSample {
// We're not using this, silence warning
let _ = dir;
let radius = lerp_slice(&self.radii, time) as f64;
let col = lerp_slice(&self.colors, time);
let solid_angle = 2.0 * PI_64 * (1.0 - radius.cos());
(col / solid_angle as f32).to_spectral_sample(wavelength)
}
fn is_delta(&self) -> bool {
false
}
fn approximate_energy(&self) -> f32 {
let color: XYZ = self.colors.iter().fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b) /
self.colors.len() as f32;
color.y
}
}

View File

@ -1,3 +1,4 @@
mod distant_disk_light;
mod rectangle_light; mod rectangle_light;
mod sphere_light; mod sphere_light;
@ -7,14 +8,17 @@ use boundable::Boundable;
use color::SpectralSample; use color::SpectralSample;
use math::{Vector, Point, Matrix4x4}; use math::{Vector, Point, Matrix4x4};
pub use self::distant_disk_light::DistantDiskLight;
pub use self::rectangle_light::RectangleLight; pub use self::rectangle_light::RectangleLight;
pub use self::sphere_light::SphereLight; pub use self::sphere_light::SphereLight;
/// A finite light source that can be bounded in space.
pub trait LightSource: Boundable + Debug + Sync { pub trait LightSource: Boundable + Debug + Sync {
/// Samples the light source for a given point to be illuminated. /// Samples the light source for a given point to be illuminated.
/// ///
/// - arr: The point to be illuminated. /// - space: The world-to-object space transform of the light.
/// - arr: The point to be illuminated (in world space).
/// - u: Random parameter U. /// - u: Random parameter U.
/// - v: Random parameter V. /// - v: Random parameter V.
/// - wavelength: The wavelength of light to sample at. /// - wavelength: The wavelength of light to sample at.
@ -77,6 +81,54 @@ pub trait LightSource: Boundable + Debug + Sync {
fn is_delta(&self) -> bool; fn is_delta(&self) -> bool;
/// Returns an approximation of the total energy emitted by the light
/// source. Note that this does not need to be exact: it is used for
/// importance sampling.
fn approximate_energy(&self) -> f32;
}
/// An infinite light source that cannot be bounded in space. E.g.
/// a sun light source.
pub trait WorldLightSource: Debug + Sync {
/// Samples the light source for a given point to be illuminated.
///
/// - u: Random parameter U.
/// - v: Random parameter V.
/// - wavelength: The wavelength of light to sample at.
/// - time: The time to sample at.
///
/// Returns: The light arriving from the shadow-testing direction, the
/// vector to use for shadow testing, and the pdf of the sample.
fn sample(&self, u: f32, v: f32, wavelength: f32, time: f32) -> (SpectralSample, Vector, f32);
/// Calculates the pdf of sampling the given sample_dir. This is used
/// primarily to calculate probabilities for multiple importance sampling.
///
/// NOTE: this function CAN assume that sample_dir is a valid sample for
/// the light source (i.e. hits/lies on the light source). No guarantees
/// are made about the correctness of the return value if it isn't valid.
fn sample_pdf(&self, sample_dir: Vector, wavelength: f32, time: f32) -> f32;
/// Returns the color emitted in the given direction from the
/// given parameters on the light.
///
/// - dir: The direction of the outgoing light.
/// - wavelength: The hero wavelength of light to sample at.
/// - time: The time to sample at.
fn outgoing(&self, dir: Vector, wavelength: f32, time: f32) -> SpectralSample;
/// Returns whether the light has a delta distribution.
///
/// If a light has no chance of a ray hitting it through random process
/// then it is a delta light source. For example, point light sources,
/// lights that only emit in a single direction, etc.
fn is_delta(&self) -> bool;
/// Returns an approximation of the total energy emitted by the light /// Returns an approximation of the total energy emitted by the light
/// source. Note that this does not need to be exact: it is used for /// source. Note that this does not need to be exact: it is used for
/// importance sampling. /// importance sampling.

View File

@ -9,6 +9,7 @@ use sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphe
use super::LightSource; use super::LightSource;
// TODO: handle case where radius = 0.0.
#[derive(Debug)] #[derive(Debug)]
pub struct SphereLight { pub struct SphereLight {

View File

@ -41,6 +41,7 @@ mod timer;
mod tracer; mod tracer;
mod transform_stack; mod transform_stack;
mod triangle; mod triangle;
mod world;
use std::fs::File; use std::fs::File;
use std::io; use std::io;

View File

@ -48,8 +48,8 @@ pub fn fast_pow2(p: f32) -> f32 {
let z: f32 = clipp - w as f32 + offset; let z: f32 = clipp - w as f32 + offset;
let i: u32 = ((1 << 23) as f32 * let i: u32 = ((1 << 23) as f32 *
(clipp + 121.2740575 + 27.7280233 / (4.84252568 - z) - (clipp + 121.2740575 + 27.7280233 / (4.84252568 - z) - 1.49012907 * z)) as
1.49012907 * z)) as u32; u32;
unsafe { transmute_copy::<u32, f32>(&i) } unsafe { transmute_copy::<u32, f32>(&i) }
} }

View File

@ -64,8 +64,8 @@ pub fn free_sah_split<'a, T, F>(seed: u32, objects: &mut [T], bounder: &F) -> (u
// Build SAH bins // Build SAH bins
let sah_bins = { let sah_bins = {
let mut sah_bins = let mut sah_bins = [[(BBox::new(), BBox::new(), 0, 0); SAH_BIN_COUNT - 1];
[[(BBox::new(), BBox::new(), 0, 0); SAH_BIN_COUNT - 1]; SPLIT_PLANE_COUNT]; SPLIT_PLANE_COUNT];
for obj in objects.iter() { for obj in objects.iter() {
let tb = lerp_slice(bounder(obj), 0.5); let tb = lerp_slice(bounder(obj), 0.5);
let centroid = tb.center().into_vector(); let centroid = tb.center().into_vector();

View File

@ -9,11 +9,14 @@ use camera::Camera;
use color::{XYZ, rec709e_to_xyz}; use color::{XYZ, rec709e_to_xyz};
use math::Matrix4x4; use math::Matrix4x4;
use renderer::Renderer; use renderer::Renderer;
use world::World;
use scene::Scene; use scene::Scene;
use light::WorldLightSource;
use super::basics::{ws_u32, ws_f32}; use super::basics::{ws_u32, ws_f32};
use super::DataTree; use super::DataTree;
use super::psy_assembly::parse_assembly; use super::psy_assembly::parse_assembly;
use super::psy_light::parse_distant_disk_light;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -24,7 +27,6 @@ pub enum PsyParseError {
/// Takes in a DataTree representing a Scene node and returns /// Takes in a DataTree representing a Scene node and returns
/// a renderer.
pub fn parse_scene(tree: &DataTree) -> Result<Renderer, PsyParseError> { pub fn parse_scene(tree: &DataTree) -> Result<Renderer, PsyParseError> {
// Verify we have the right number of each section // Verify we have the right number of each section
if tree.iter_children_with_type("Output").count() != 1 { if tree.iter_children_with_type("Output").count() != 1 {
@ -79,8 +81,8 @@ pub fn parse_scene(tree: &DataTree) -> Result<Renderer, PsyParseError> {
}; };
let scene = Scene { let scene = Scene {
name: scene_name, name: scene_name,
background_color: world,
camera: camera, camera: camera,
world: world,
root: assembly, root: assembly,
}; };
@ -266,9 +268,10 @@ fn parse_camera(tree: &DataTree) -> Result<Camera, PsyParseError> {
fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> { fn parse_world(tree: &DataTree) -> Result<World, PsyParseError> {
if tree.is_internal() { if tree.is_internal() {
let background_color; let background_color;
let mut lights: Vec<Box<WorldLightSource>> = Vec::new();
// Parse background shader // Parse background shader
let bgs = { let bgs = {
@ -281,7 +284,8 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
if bgs.iter_children_with_type("Type").count() != 1 { if bgs.iter_children_with_type("Type").count() != 1 {
return Err(PsyParseError::UnknownError); return Err(PsyParseError::UnknownError);
} }
if let &DataTree::Leaf { contents, .. } = bgs.iter_children_with_type("Type") if let &DataTree::Leaf { contents, .. } =
bgs.iter_children_with_type("Type")
.nth(0) .nth(0)
.unwrap() { .unwrap() {
contents.trim() contents.trim()
@ -291,11 +295,11 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
}; };
match bgs_type { match bgs_type {
"Color" => { "Color" => {
if let Some(&DataTree::Leaf { contents, .. }) = bgs.iter_children_with_type("Color") if let Some(&DataTree::Leaf { contents, .. }) =
bgs.iter_children_with_type("Color")
.nth(0) { .nth(0) {
if let IResult::Done(_, color) = closure!(tuple!(ws_f32, if let IResult::Done(_, color) =
ws_f32, closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.trim()
ws_f32))(contents.trim()
.as_bytes()) { .as_bytes()) {
// TODO: proper color space management, not just assuming // TODO: proper color space management, not just assuming
// rec.709. // rec.709.
@ -311,7 +315,22 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
_ => return Err(PsyParseError::UnknownError), _ => return Err(PsyParseError::UnknownError),
} }
return Ok(background_color); // Parse light sources
for child in tree.iter_children() {
match child {
&DataTree::Internal { type_name, .. } if type_name == "DistantDiskLight" => {
lights.push(Box::new(parse_distant_disk_light(&child)?));
}
_ => {}
}
}
// Build and return the world
return Ok(World {
background_color: background_color,
lights: lights,
});
} else { } else {
return Err(PsyParseError::UnknownError); return Err(PsyParseError::UnknownError);
} }
@ -321,7 +340,8 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
pub fn parse_matrix(contents: &str) -> Result<Matrix4x4, PsyParseError> { pub fn parse_matrix(contents: &str) -> Result<Matrix4x4, PsyParseError> {
if let IResult::Done(_, ns) = closure!(terminated!(tuple!(ws_f32, if let IResult::Done(_, ns) =
closure!(terminated!(tuple!(ws_f32,
ws_f32, ws_f32,
ws_f32, ws_f32,
ws_f32, ws_f32,

View File

@ -4,14 +4,70 @@ use std::result::Result;
use nom::IResult; use nom::IResult;
use math::Vector;
use color::{XYZ, rec709e_to_xyz}; use color::{XYZ, rec709e_to_xyz};
use light::{SphereLight, RectangleLight}; use light::{DistantDiskLight, SphereLight, RectangleLight};
use super::basics::ws_f32; use super::basics::ws_f32;
use super::DataTree; use super::DataTree;
use super::psy::PsyParseError; use super::psy::PsyParseError;
pub fn parse_distant_disk_light(tree: &DataTree) -> Result<DistantDiskLight, PsyParseError> {
if let &DataTree::Internal { ref children, .. } = tree {
let mut radii = Vec::new();
let mut directions = Vec::new();
let mut colors = Vec::new();
// Parse
for child in children.iter() {
match child {
// Radius
&DataTree::Leaf { type_name, contents } if type_name == "Radius" => {
if let IResult::Done(_, radius) = ws_f32(contents.as_bytes()) {
radii.push(radius);
} else {
// Found radius, but its contents is not in the right format
return Err(PsyParseError::UnknownError);
}
}
// Direction
&DataTree::Leaf { type_name, contents } if type_name == "Direction" => {
if let IResult::Done(_, direction) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
directions.push(Vector::new(direction.0, direction.1, direction.2));
} else {
// Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError);
}
}
// Color
&DataTree::Leaf { type_name, contents } if type_name == "Color" => {
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
colors.push(XYZ::from_tuple(rec709e_to_xyz(color)));
} else {
// Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError);
}
}
_ => {}
}
}
return Ok(DistantDiskLight::new(radii, directions, colors));
} else {
return Err(PsyParseError::UnknownError);
}
}
pub fn parse_sphere_light(tree: &DataTree) -> Result<SphereLight, PsyParseError> { pub fn parse_sphere_light(tree: &DataTree) -> Result<SphereLight, PsyParseError> {
if let &DataTree::Internal { ref children, .. } = tree { if let &DataTree::Internal { ref children, .. } = tree {
let mut radii = Vec::new(); let mut radii = Vec::new();
@ -32,9 +88,8 @@ pub fn parse_sphere_light(tree: &DataTree) -> Result<SphereLight, PsyParseError>
// Color // Color
&DataTree::Leaf { type_name, contents } if type_name == "Color" => { &DataTree::Leaf { type_name, contents } if type_name == "Color" => {
if let IResult::Done(_, color) = closure!(tuple!(ws_f32, if let IResult::Done(_, color) =
ws_f32, closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
ws_f32))(contents.as_bytes()) {
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
@ -76,9 +131,8 @@ pub fn parse_rectangle_light(tree: &DataTree) -> Result<RectangleLight, PsyParse
// Color // Color
&DataTree::Leaf { type_name, contents } if type_name == "Color" => { &DataTree::Leaf { type_name, contents } if type_name == "Color" => {
if let IResult::Done(_, color) = closure!(tuple!(ws_f32, if let IResult::Done(_, color) =
ws_f32, closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
ws_f32))(contents.as_bytes()) {
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?

View File

@ -35,9 +35,8 @@ pub fn parse_mesh_surface(tree: &DataTree) -> Result<TriangleMesh, PsyParseError
// Collect verts for this time sample // Collect verts for this time sample
let mut vert_count = 0; let mut vert_count = 0;
while let IResult::Done(remaining, vert) = closure!(tuple!(ws_f32, while let IResult::Done(remaining, vert) =
ws_f32, closure!(tuple!(ws_f32, ws_f32, ws_f32))(raw_text) {
ws_f32))(raw_text) {
raw_text = remaining; raw_text = remaining;
verts.push(Point::new(vert.0, vert.1, vert.2)); verts.push(Point::new(vert.0, vert.1, vert.2));

View File

@ -113,9 +113,10 @@ impl Renderer {
(halton::sample(0, offset + si as u32), (halton::sample(0, offset + si as u32),
halton::sample(1, offset + si as u32)), halton::sample(1, offset + si as u32)),
halton::sample(2, offset + si as u32), halton::sample(2, offset + si as u32),
map_0_1_to_wavelength( map_0_1_to_wavelength(halton::sample(3,
halton::sample(3, offset + si as u32) offset +
), si as
u32)),
offset + si as u32); offset + si as u32);
paths.push(path); paths.push(path);
rays.push(ray); rays.push(ray);
@ -297,7 +298,8 @@ impl LightPath {
let light_n = self.next_lds_samp(); let light_n = self.next_lds_samp();
let light_uvw = (self.next_lds_samp(), self.next_lds_samp(), self.next_lds_samp()); let light_uvw = (self.next_lds_samp(), self.next_lds_samp(), self.next_lds_samp());
xform_stack.clear(); xform_stack.clear();
if let Some((light_color, shadow_vec, light_pdf, light_sel_pdf)) = scene.root if let Some((light_color, shadow_vec, light_pdf, light_sel_pdf)) =
scene.root
.sample_lights(xform_stack, .sample_lights(xform_stack,
light_n, light_n,
light_uvw, light_uvw,
@ -327,7 +329,7 @@ impl LightPath {
} }
} else { } else {
// Didn't hit anything, so background color // Didn't hit anything, so background color
self.color += scene.background_color.to_spectral_sample(self.wavelength) * self.color += scene.world.background_color.to_spectral_sample(self.wavelength) *
self.light_attenuation; self.light_attenuation;
return false; return false;
} }

View File

@ -1,12 +1,13 @@
use assembly::Assembly; use assembly::Assembly;
use camera::Camera; use camera::Camera;
use color::XYZ; use world::World;
#[derive(Debug)] #[derive(Debug)]
pub struct Scene { pub struct Scene {
pub name: Option<String>, pub name: Option<String>,
pub background_color: XYZ,
pub camera: Camera, pub camera: Camera,
pub world: World,
pub root: Assembly, pub root: Assembly,
} }

8
src/world.rs Normal file
View File

@ -0,0 +1,8 @@
use color::XYZ;
use light::WorldLightSource;
#[derive(Debug)]
pub struct World {
pub background_color: XYZ,
pub lights: Vec<Box<WorldLightSource>>,
}