Got DistantDiscLights working! Yay!

This involved fixing a bug in PsychoBlend.  It was including
translation in how it was transforming the sun lights' direction
vector.
This commit is contained in:
Nathan Vegdahl 2017-02-13 00:03:34 -08:00
parent 3cbb816d4b
commit d504ca5e6a
7 changed files with 118 additions and 15 deletions

View File

@ -447,7 +447,7 @@ class PsychoExporter:
time_rad = []
for i in range(self.time_samples):
self.set_frame(self.fr, self.shutter_start + (self.shutter_diff*i))
time_dir += [tuple(ob.matrix_world * Vector((0, 0, -1)))]
time_dir += [tuple(ob.matrix_world.to_3x3() * Vector((0, 0, -1)))]
time_col += [ob.data.color * ob.data.energy]
time_rad += [ob.data.shadow_soft_size]

View File

@ -8,6 +8,31 @@ use hash::hash_u64;
use lerp::{Lerp, lerp_slice};
/// Selects an item from a slice based on a weighting function and a
/// number (n) between 0.0 and 1.0. Returns the index of the selected
/// item and the probability that it would have been selected with a
/// random n.
pub fn weighted_choice<T, F>(slc: &[T], n: f32, weight: F) -> (usize, f32)
where F: Fn(&T) -> f32
{
assert!(slc.len() > 0);
let total_weight = slc.iter().fold(0.0, |sum, v| sum + weight(v));
let n = n * total_weight;
let mut x = 0.0;
for (i, v) in slc.iter().enumerate() {
let w = weight(v);
x += w;
if x > n || i == slc.len() {
return (i, w / total_weight);
}
}
unreachable!()
}
/// Partitions a slice in-place with the given unary predicate, returning
/// the index of the first element for which the predicate evaluates
/// false.

View File

@ -36,19 +36,17 @@ impl WorldLightSource for DistantDiskLight {
// 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();
let (z, x, y) = coordinate_system_from_vector(-direction.normalized());
// Sample the cone subtended by the light.
let cos_theta_max: f64 = radius.cos();
let sample = uniform_sample_cone(u, v, cos_theta_max).normalized();
// Calculate the final values and return everything.
let spectral_sample = (col * solid_angle_inv as f32).to_spectral_sample(wavelength);
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);
}

View File

@ -114,6 +114,10 @@ pub fn log2_64(value: u64) -> u64 {
/// Creates a coordinate system from a single vector.
///
/// The input vector, v, becomes the first vector of the
/// returned tuple, with the other two vectors in the returned
/// tuple defining the orthoganal axes.
pub fn coordinate_system_from_vector(v: Vector) -> (Vector, Vector, Vector) {
let v2 = if v.x().abs() > v.y().abs() {
let invlen = 1.0 / ((v.x() * v.x()) + (v.z() * v.z())).sqrt();

View File

@ -1,3 +1,4 @@
use std;
use std::cell::Cell;
use std::cmp;
use std::cmp::min;
@ -298,14 +299,13 @@ 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
.sample_lights(xform_stack,
light_n,
light_uvw,
self.wavelength,
self.time,
isect) {
if let Some((light_color, shadow_vec, light_pdf, light_sel_pdf, is_infinite)) =
scene.sample_lights(xform_stack,
light_n,
light_uvw,
self.wavelength,
self.time,
isect) {
// Calculate and store the light that will be contributed
// to the film plane if the light is not in shadow.
self.pending_color_addition = {
@ -323,6 +323,11 @@ impl LightPath {
self.time,
true);
// For distant lights
if is_infinite {
ray.max_t = std::f32::INFINITY;
}
return true;
} else {
return false;

View File

@ -58,6 +58,12 @@ pub fn uniform_sample_sphere(u: f32, v: f32) -> Vector {
Vector::new(x, y, z)
}
/// Samples a solid angle defined by a cone originating from (0,0,0)
/// and pointing down the positive z-axis.
///
/// u, v: sampling variables, should each be in the interval [0,1]
/// cos_theta_max: cosine of the max angle from the z-axis, defining
/// the outer extent of the cone.
pub fn uniform_sample_cone(u: f32, v: f32, cos_theta_max: f64) -> Vector {
let cos_theta = (1.0 - u as f64) + (u as f64 * cos_theta_max);
let sin_theta = (1.0 - (cos_theta * cos_theta)).sqrt();

View File

@ -1,6 +1,12 @@
use assembly::Assembly;
use camera::Camera;
use world::World;
use algorithm::weighted_choice;
use transform_stack::TransformStack;
use color::SpectralSample;
use surface::SurfaceIntersection;
use math::Vector;
use light_accel::LightAccel;
@ -11,3 +17,62 @@ pub struct Scene {
pub world: World,
pub root: Assembly,
}
impl Scene {
pub fn sample_lights(&self,
xform_stack: &mut TransformStack,
n: f32,
uvw: (f32, f32, f32),
wavelength: f32,
time: f32,
intr: &SurfaceIntersection)
-> Option<(SpectralSample, Vector, f32, f32, bool)> {
// TODO: this just selects between world lights and local lights
// with a 50/50 chance. We should do something more sophisticated
// than this, accounting for the estimated impact of the lights
// on the point being lit.
// Calculate relative probabilities of traversing into world lights
// or local lights.
let wl_energy = if self.world
.lights
.iter()
.fold(0.0, |energy, light| energy + light.approximate_energy()) <=
0.0 {
0.0
} else {
1.0
};
let ll_energy = if self.root.light_accel.approximate_energy() <= 0.0 {
0.0
} else {
1.0
};
let tot_energy = wl_energy + ll_energy;
// Decide either world or local lights, and select and sample a light.
if tot_energy <= 0.0 {
return None;
} else {
let wl_prob = wl_energy / tot_energy;
if n < wl_prob {
// World lights
let n = n / wl_prob;
let (i, p) = weighted_choice(&self.world.lights, n, |l| l.approximate_energy());
let (ss, sv, pdf) = self.world.lights[i].sample(uvw.0, uvw.1, wavelength, time);
return Some((ss, sv, pdf, p * wl_prob, true));
} else {
// Local lights
let n = (n - wl_prob) / (1.0 - wl_prob);
if let Some((ss, sv, pdf, spdf)) =
self.root.sample_lights(xform_stack, n, uvw, wavelength, time, intr) {
return Some((ss, sv, pdf, spdf * (1.0 - wl_prob), false));
} else {
return None;
}
}
}
}
}