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 {
return;
} 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())) {
*xfo = merge(xf1, xf2);
}
@ -216,14 +217,12 @@ mod tests {
use super::*;
fn quick_select_ints(list: &mut [i32], i: usize) {
quick_select(list, i, |a, b| {
if a < b {
quick_select(list, i, |a, b| if a < b {
Ordering::Less
} else if a == b {
Ordering::Equal
} else {
Ordering::Greater
}
});
}

View File

@ -48,7 +48,8 @@ impl Assembly {
} else {
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,
pos * sel_xform,
nor * sel_xform,
@ -239,8 +240,7 @@ impl AssemblyBuilder {
// sources.
let mut light_instances: Vec<_> = self.instances
.iter()
.filter(|inst| {
match inst.instance_type {
.filter(|inst| match inst.instance_type {
InstanceType::Object => {
if let Object::Light(_) = self.objects[inst.data_index] {
true
@ -252,7 +252,6 @@ impl AssemblyBuilder {
InstanceType::Assembly => {
self.assemblies[inst.data_index].light_accel.approximate_energy() > 0.0
}
}
})
.map(|&a| a)
.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 sphere_light;
@ -7,14 +8,17 @@ use boundable::Boundable;
use color::SpectralSample;
use math::{Vector, Point, Matrix4x4};
pub use self::distant_disk_light::DistantDiskLight;
pub use self::rectangle_light::RectangleLight;
pub use self::sphere_light::SphereLight;
/// A finite light source that can be bounded in space.
pub trait LightSource: Boundable + Debug + Sync {
/// 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.
/// - v: Random parameter V.
/// - wavelength: The wavelength of light to sample at.
@ -77,6 +81,54 @@ pub trait LightSource: Boundable + Debug + Sync {
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
/// source. Note that this does not need to be exact: it is used for
/// importance sampling.

View File

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

View File

@ -41,6 +41,7 @@ mod timer;
mod tracer;
mod transform_stack;
mod triangle;
mod world;
use std::fs::File;
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 i: u32 = ((1 << 23) as f32 *
(clipp + 121.2740575 + 27.7280233 / (4.84252568 - z) -
1.49012907 * z)) as u32;
(clipp + 121.2740575 + 27.7280233 / (4.84252568 - z) - 1.49012907 * z)) as
u32;
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
let sah_bins = {
let mut sah_bins =
[[(BBox::new(), BBox::new(), 0, 0); SAH_BIN_COUNT - 1]; SPLIT_PLANE_COUNT];
let mut sah_bins = [[(BBox::new(), BBox::new(), 0, 0); SAH_BIN_COUNT - 1];
SPLIT_PLANE_COUNT];
for obj in objects.iter() {
let tb = lerp_slice(bounder(obj), 0.5);
let centroid = tb.center().into_vector();

View File

@ -9,11 +9,14 @@ use camera::Camera;
use color::{XYZ, rec709e_to_xyz};
use math::Matrix4x4;
use renderer::Renderer;
use world::World;
use scene::Scene;
use light::WorldLightSource;
use super::basics::{ws_u32, ws_f32};
use super::DataTree;
use super::psy_assembly::parse_assembly;
use super::psy_light::parse_distant_disk_light;
#[derive(Copy, Clone, Debug)]
@ -24,7 +27,6 @@ pub enum PsyParseError {
/// Takes in a DataTree representing a Scene node and returns
/// a renderer.
pub fn parse_scene(tree: &DataTree) -> Result<Renderer, PsyParseError> {
// Verify we have the right number of each section
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 {
name: scene_name,
background_color: world,
camera: camera,
world: world,
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() {
let background_color;
let mut lights: Vec<Box<WorldLightSource>> = Vec::new();
// Parse background shader
let bgs = {
@ -281,7 +284,8 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
if bgs.iter_children_with_type("Type").count() != 1 {
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)
.unwrap() {
contents.trim()
@ -291,11 +295,11 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
};
match bgs_type {
"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) {
if let IResult::Done(_, color) = closure!(tuple!(ws_f32,
ws_f32,
ws_f32))(contents.trim()
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.trim()
.as_bytes()) {
// TODO: proper color space management, not just assuming
// rec.709.
@ -311,7 +315,22 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
_ => 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 {
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> {
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,

View File

@ -4,14 +4,70 @@ use std::result::Result;
use nom::IResult;
use math::Vector;
use color::{XYZ, rec709e_to_xyz};
use light::{SphereLight, RectangleLight};
use light::{DistantDiskLight, SphereLight, RectangleLight};
use super::basics::ws_f32;
use super::DataTree;
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> {
if let &DataTree::Internal { ref children, .. } = tree {
let mut radii = Vec::new();
@ -32,9 +88,8 @@ pub fn parse_sphere_light(tree: &DataTree) -> Result<SphereLight, PsyParseError>
// 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()) {
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...?
@ -76,9 +131,8 @@ pub fn parse_rectangle_light(tree: &DataTree) -> Result<RectangleLight, PsyParse
// 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()) {
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...?

View File

@ -35,9 +35,8 @@ pub fn parse_mesh_surface(tree: &DataTree) -> Result<TriangleMesh, PsyParseError
// Collect verts for this time sample
let mut vert_count = 0;
while let IResult::Done(remaining, vert) = closure!(tuple!(ws_f32,
ws_f32,
ws_f32))(raw_text) {
while let IResult::Done(remaining, vert) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(raw_text) {
raw_text = remaining;
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(1, offset + si as u32)),
halton::sample(2, offset + si as u32),
map_0_1_to_wavelength(
halton::sample(3, offset + si as u32)
),
map_0_1_to_wavelength(halton::sample(3,
offset +
si as
u32)),
offset + si as u32);
paths.push(path);
rays.push(ray);
@ -297,7 +298,8 @@ impl LightPath {
let light_n = self.next_lds_samp();
let light_uvw = (self.next_lds_samp(), self.next_lds_samp(), self.next_lds_samp());
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,
light_n,
light_uvw,
@ -327,7 +329,7 @@ impl LightPath {
}
} else {
// 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;
return false;
}

View File

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