813 lines
23 KiB
Rust
813 lines
23 KiB
Rust
use bumpalo::Bump;
|
|
use enum_dispatch::enum_dispatch;
|
|
use std::sync::Arc;
|
|
|
|
use crate::core::pbrt::{Float, INV_4_PI, PI, clamp_t};
|
|
use crate::geometry::{
|
|
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
|
|
};
|
|
use crate::spectra::{
|
|
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
|
|
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
|
};
|
|
use crate::utils::containers::SampledGrid;
|
|
use crate::utils::math::square;
|
|
use crate::utils::rng::Rng;
|
|
use crate::utils::transform::Transform;
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct PhaseFunctionSample {
|
|
pub p: Float,
|
|
pub wi: Vector3f,
|
|
pub pdf: Float,
|
|
}
|
|
|
|
impl PhaseFunctionSample {
|
|
pub fn new(p: Float, wi: Vector3f, pdf: Float) -> Self {
|
|
Self { p, wi, pdf }
|
|
}
|
|
}
|
|
|
|
#[enum_dispatch]
|
|
pub trait PhaseFunctionTrait {
|
|
fn p(&self, wo: Vector3f, wi: Vector3f) -> Float;
|
|
fn sample_p(&self, wo: Vector3f, u: Point2f) -> Option<PhaseFunctionSample>;
|
|
fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float;
|
|
}
|
|
|
|
#[enum_dispatch(PhaseFunctionTrait)]
|
|
#[derive(Debug, Clone)]
|
|
pub enum PhaseFunction {
|
|
HenyeyGreenstein(HGPhaseFunction),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct HGPhaseFunction {
|
|
g: Float,
|
|
}
|
|
|
|
impl HGPhaseFunction {
|
|
pub fn new(g: Float) -> Self {
|
|
Self { g }
|
|
}
|
|
|
|
fn phase_hg(&self, cos_theta: Float) -> Float {
|
|
let denom = 1.0 + square(self.g) + 2.0 * self.g * cos_theta;
|
|
INV_4_PI * (1.0 - square(self.g)) / (denom * denom.sqrt())
|
|
}
|
|
}
|
|
|
|
impl PhaseFunctionTrait for HGPhaseFunction {
|
|
fn p(&self, wo: Vector3f, wi: Vector3f) -> Float {
|
|
self.phase_hg(-(wo.dot(wi)))
|
|
}
|
|
|
|
fn sample_p(&self, wo: Vector3f, u: Point2f) -> Option<PhaseFunctionSample> {
|
|
let cos_theta = if self.g.abs() < 1e-3 {
|
|
1.0 - 2.0 * u[0]
|
|
} else {
|
|
let square_term = (1.0 - square(self.g)) / (1.0 + self.g * (1.0 - 2.0 * u[0]));
|
|
-(1.0 + square(self.g) - square(square_term)) / (2.0 * self.g)
|
|
};
|
|
|
|
let sin_theta = (1.0 - square(cos_theta)).max(0.0).sqrt();
|
|
let phi = 2.0 * PI * u[1];
|
|
let w_frame = Frame::from_z(wo);
|
|
let wi = w_frame.from_local(spherical_direction(sin_theta, cos_theta, phi));
|
|
|
|
let p_val = self.phase_hg(cos_theta);
|
|
|
|
Some(PhaseFunctionSample::new(p_val, wi, p_val))
|
|
}
|
|
|
|
fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float {
|
|
self.p(wo, wi)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MajorantGrid {
|
|
pub bounds: Bounds3f,
|
|
pub res: Point3i,
|
|
pub voxels: Vec<Float>,
|
|
}
|
|
|
|
impl MajorantGrid {
|
|
pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
|
|
Self {
|
|
bounds,
|
|
res,
|
|
voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize),
|
|
}
|
|
}
|
|
pub fn lookup(&self, x: i32, y: i32, z: i32) -> Float {
|
|
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
|
|
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
|
self.voxels[idx as usize]
|
|
} else {
|
|
0.0
|
|
}
|
|
}
|
|
pub fn set(&mut self, x: i32, y: i32, z: i32, v: Float) {
|
|
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
|
|
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
|
self.voxels[idx as usize] = v;
|
|
}
|
|
}
|
|
|
|
pub fn voxel_bounds(&self, x: i32, y: i32, z: i32) -> Bounds3f {
|
|
let p0 = Point3f::new(
|
|
x as Float / self.res.x() as Float,
|
|
y as Float / self.res.y() as Float,
|
|
z as Float / self.res.z() as Float,
|
|
);
|
|
let p1 = Point3f::new(
|
|
(x + 1) as Float / self.res.x() as Float,
|
|
(y + 1) as Float / self.res.y() as Float,
|
|
(z + 1) as Float / self.res.z() as Float,
|
|
);
|
|
Bounds3f::from_points(p0, p1)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct RayMajorantSegment {
|
|
t_min: Float,
|
|
t_max: Float,
|
|
sigma_maj: SampledSpectrum,
|
|
}
|
|
|
|
pub enum RayMajorantIterator {
|
|
Homogeneous(HomogeneousMajorantIterator),
|
|
DDA(DDAMajorantIterator),
|
|
// Grid(GridMajorantIterator<'a>),
|
|
Void,
|
|
}
|
|
|
|
impl Iterator for RayMajorantIterator {
|
|
type Item = RayMajorantSegment;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
match self {
|
|
RayMajorantIterator::Homogeneous(iter) => iter.next(),
|
|
RayMajorantIterator::DDA(iter) => iter.next(),
|
|
RayMajorantIterator::Void => None,
|
|
}
|
|
}
|
|
}
|
|
pub struct HomogeneousMajorantIterator {
|
|
called: bool,
|
|
seg: RayMajorantSegment,
|
|
}
|
|
|
|
impl HomogeneousMajorantIterator {
|
|
pub fn new(t_min: Float, t_max: Float, sigma_maj: SampledSpectrum) -> Self {
|
|
Self {
|
|
seg: RayMajorantSegment {
|
|
t_min,
|
|
t_max,
|
|
sigma_maj,
|
|
},
|
|
called: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Iterator for HomogeneousMajorantIterator {
|
|
type Item = RayMajorantSegment;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.called {
|
|
return None;
|
|
}
|
|
|
|
self.called = true;
|
|
Some(self.seg)
|
|
}
|
|
}
|
|
|
|
pub struct DDAMajorantIterator {
|
|
sigma_t: SampledSpectrum,
|
|
t_min: Float,
|
|
t_max: Float,
|
|
grid: MajorantGrid,
|
|
next_crossing_t: [Float; 3],
|
|
delta_t: [Float; 3],
|
|
step: [i32; 3],
|
|
voxel_limit: [i32; 3],
|
|
voxel: [i32; 3],
|
|
}
|
|
impl DDAMajorantIterator {
|
|
pub fn new(
|
|
ray: &Ray,
|
|
t_min: Float,
|
|
t_max: Float,
|
|
grid: &MajorantGrid,
|
|
sigma_t: &SampledSpectrum,
|
|
) -> Self {
|
|
let mut iter = Self {
|
|
t_min,
|
|
t_max,
|
|
sigma_t: *sigma_t,
|
|
grid: grid.clone(),
|
|
next_crossing_t: [0.0; 3],
|
|
delta_t: [0.0; 3],
|
|
step: [0; 3],
|
|
voxel_limit: [0; 3],
|
|
voxel: [0; 3],
|
|
};
|
|
|
|
let diag = grid.bounds.diagonal();
|
|
|
|
let mut ray_grid_d = Vector3f::new(
|
|
ray.d.x() / diag.x(),
|
|
ray.d.y() / diag.y(),
|
|
ray.d.z() / diag.z(),
|
|
);
|
|
|
|
let p_grid_start = grid.bounds.offset(&ray.at(t_min));
|
|
let grid_intersect = Vector3f::from(p_grid_start);
|
|
|
|
for axis in 0..3 {
|
|
iter.voxel[axis] = clamp_t(
|
|
(grid_intersect[axis] * grid.res[axis] as Float) as i32,
|
|
0,
|
|
grid.res[axis] - 1,
|
|
);
|
|
|
|
iter.delta_t[axis] = 1.0 / (ray_grid_d[axis].abs() * grid.res[axis] as Float);
|
|
|
|
if ray_grid_d[axis] == -0.0 {
|
|
ray_grid_d[axis] = 0.0;
|
|
}
|
|
|
|
if ray_grid_d[axis] >= 0.0 {
|
|
let next_voxel_pos = (iter.voxel[axis] + 1) as Float / grid.res[axis] as Float;
|
|
iter.next_crossing_t[axis] =
|
|
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
|
iter.step[axis] = 1;
|
|
iter.voxel_limit[axis] = grid.res[axis];
|
|
} else {
|
|
let next_voxel_pos = (iter.voxel[axis]) as Float / grid.res[axis] as Float;
|
|
iter.next_crossing_t[axis] =
|
|
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
|
iter.step[axis] = -1;
|
|
iter.voxel_limit[axis] = -1;
|
|
}
|
|
}
|
|
|
|
iter
|
|
}
|
|
}
|
|
|
|
impl Iterator for DDAMajorantIterator {
|
|
type Item = RayMajorantSegment;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.t_min >= self.t_max {
|
|
return None;
|
|
}
|
|
|
|
// Find stepAxis for stepping to next voxel and exit point tVoxelExit
|
|
let d0 = self.next_crossing_t[0];
|
|
let d1 = self.next_crossing_t[1];
|
|
let d2 = self.next_crossing_t[2];
|
|
|
|
let bits = ((d0 < d1) as usize) << 2 | ((d0 < d2) as usize) << 1 | ((d1 < d2) as usize);
|
|
|
|
const CMP_TO_AXIS: [usize; 8] = [2, 1, 2, 1, 2, 2, 0, 0];
|
|
let step_axis = CMP_TO_AXIS[bits];
|
|
|
|
let t_voxel_exit = self.t_max.min(self.next_crossing_t[step_axis]);
|
|
// Get maxDensity for current voxel and initialize RayMajorantSegment, seg
|
|
|
|
let density_scale = self
|
|
.grid
|
|
.lookup(self.voxel[0], self.voxel[1], self.voxel[2]);
|
|
|
|
let sigma_maj = self.sigma_t * density_scale;
|
|
|
|
let seg = RayMajorantSegment {
|
|
t_min: self.t_min,
|
|
t_max: t_voxel_exit,
|
|
sigma_maj,
|
|
};
|
|
|
|
// Advance to next voxel in maximum density grid
|
|
self.t_min = t_voxel_exit;
|
|
|
|
if self.next_crossing_t[step_axis] > self.t_max {
|
|
self.t_min = self.t_max;
|
|
}
|
|
|
|
self.voxel[step_axis] += self.step[step_axis];
|
|
|
|
if self.voxel[step_axis] == self.voxel_limit[step_axis] {
|
|
self.t_min = self.t_max;
|
|
}
|
|
|
|
// Increment the crossing time for this axis
|
|
self.next_crossing_t[step_axis] += self.delta_t[step_axis];
|
|
|
|
Some(seg)
|
|
}
|
|
}
|
|
|
|
pub struct MediumProperties {
|
|
pub sigma_a: SampledSpectrum,
|
|
pub sigma_s: SampledSpectrum,
|
|
pub phase: PhaseFunction,
|
|
pub le: SampledSpectrum,
|
|
}
|
|
|
|
impl MediumProperties {
|
|
pub fn sigma_t(&self) -> SampledSpectrum {
|
|
self.sigma_a * self.sigma_s
|
|
}
|
|
}
|
|
|
|
#[enum_dispatch]
|
|
pub trait MediumTrait: Send + Sync + std::fmt::Debug {
|
|
fn is_emissive(&self) -> bool;
|
|
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties;
|
|
fn sample_ray(
|
|
&self,
|
|
ray: &Ray,
|
|
t_max: Float,
|
|
lambda: &SampledWavelengths,
|
|
buf: &Bump,
|
|
) -> RayMajorantIterator;
|
|
|
|
fn sample_t_maj<F>(
|
|
&self,
|
|
mut ray: Ray,
|
|
mut t_max: Float,
|
|
u: Float,
|
|
rng: &mut Rng,
|
|
lambda: &SampledWavelengths,
|
|
mut callback: F,
|
|
) -> SampledSpectrum
|
|
where
|
|
F: FnMut(Point3f, MediumProperties, SampledSpectrum, SampledSpectrum, &mut Rng) -> bool,
|
|
{
|
|
let len = ray.d.norm();
|
|
t_max *= len;
|
|
ray.d /= len;
|
|
|
|
let buf = Bump::new();
|
|
let mut iter = self.sample_ray(&ray, t_max, lambda, &buf);
|
|
let mut t_maj = SampledSpectrum::new(1.0);
|
|
|
|
while let Some(seg) = iter.next() {
|
|
if seg.sigma_maj[0] == 0. {
|
|
let dt = seg.t_max - seg.t_min;
|
|
let dt = if dt.is_infinite() { Float::MAX } else { dt };
|
|
|
|
t_maj *= (-dt * seg.sigma_maj).exp();
|
|
continue;
|
|
}
|
|
|
|
let mut t_min = seg.t_min;
|
|
loop {
|
|
let dist = -(1. - u.ln()) / seg.sigma_maj[0];
|
|
let t = t_min + dist;
|
|
|
|
if t < seg.t_max {
|
|
t_maj *= (-(t - t_min) * seg.sigma_maj).exp();
|
|
let p = ray.at(t);
|
|
let mp = self.sample_point(p, lambda);
|
|
|
|
if !callback(p, mp, seg.sigma_maj, t_maj, rng) {
|
|
return SampledSpectrum::new(1.);
|
|
}
|
|
|
|
t_maj = SampledSpectrum::new(1.);
|
|
t_min = t;
|
|
} else {
|
|
let dt = seg.t_max - t_min;
|
|
let dt = if dt.is_infinite() { Float::MAX } else { dt };
|
|
|
|
t_maj *= (-dt * seg.sigma_maj).exp();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SampledSpectrum::new(1.)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[enum_dispatch(MediumTrait)]
|
|
pub enum Medium {
|
|
Homogeneous(HomogeneousMedium),
|
|
Grid(GridMedium),
|
|
RGBGrid(RGBGridMedium),
|
|
Cloud(CloudMedium),
|
|
NanoVDB(NanoVDBMedium),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct HomogeneousMedium {
|
|
sigma_a_spec: DenselySampledSpectrum,
|
|
sigma_s_spec: DenselySampledSpectrum,
|
|
le_spec: DenselySampledSpectrum,
|
|
phase: HGPhaseFunction,
|
|
}
|
|
|
|
impl HomogeneousMedium {
|
|
pub fn new(
|
|
sigma_a: Spectrum,
|
|
sigma_s: Spectrum,
|
|
sigma_scale: Float,
|
|
le: Spectrum,
|
|
le_scale: Float,
|
|
g: Float,
|
|
) -> Self {
|
|
let mut sigma_a_spec =
|
|
DenselySampledSpectrum::from_spectrum(&sigma_a, LAMBDA_MIN, LAMBDA_MAX);
|
|
let mut sigma_s_spec =
|
|
DenselySampledSpectrum::from_spectrum(&sigma_s, LAMBDA_MIN, LAMBDA_MAX);
|
|
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le, LAMBDA_MIN, LAMBDA_MAX);
|
|
|
|
sigma_a_spec.scale(sigma_scale);
|
|
sigma_s_spec.scale(sigma_scale);
|
|
le_spec.scale(le_scale);
|
|
|
|
Self {
|
|
sigma_a_spec,
|
|
sigma_s_spec,
|
|
le_spec,
|
|
phase: HGPhaseFunction::new(g),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MediumTrait for HomogeneousMedium {
|
|
fn is_emissive(&self) -> bool {
|
|
self.le_spec.max_value() > 0.
|
|
}
|
|
|
|
fn sample_point(&self, _p: Point3f, lambda: &SampledWavelengths) -> MediumProperties {
|
|
let sigma_a = self.sigma_a_spec.sample(lambda);
|
|
let sigma_s = self.sigma_s_spec.sample(lambda);
|
|
let le = self.le_spec.sample(lambda);
|
|
MediumProperties {
|
|
sigma_a,
|
|
sigma_s,
|
|
phase: self.phase.into(),
|
|
le,
|
|
}
|
|
}
|
|
|
|
fn sample_ray(
|
|
&self,
|
|
_ray: &Ray,
|
|
t_max: Float,
|
|
lambda: &SampledWavelengths,
|
|
_scratch: &Bump,
|
|
) -> RayMajorantIterator {
|
|
let sigma_a = self.sigma_a_spec.sample(lambda);
|
|
let sigma_s = self.sigma_s_spec.sample(lambda);
|
|
let sigma_maj = sigma_a + sigma_s;
|
|
|
|
let iter = HomogeneousMajorantIterator::new(0.0, t_max, sigma_maj);
|
|
|
|
RayMajorantIterator::Homogeneous(iter)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct GridMedium {
|
|
bounds: Bounds3f,
|
|
render_from_medium: Transform<Float>,
|
|
sigma_a_spec: DenselySampledSpectrum,
|
|
sigma_s_spec: DenselySampledSpectrum,
|
|
density_grid: SampledGrid<Float>,
|
|
phase: HGPhaseFunction,
|
|
temperature_grid: Option<SampledGrid<Float>>,
|
|
le_spec: DenselySampledSpectrum,
|
|
le_scale: SampledGrid<Float>,
|
|
is_emissive: bool,
|
|
majorant_grid: MajorantGrid,
|
|
}
|
|
|
|
impl GridMedium {
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn new(
|
|
bounds: &Bounds3f,
|
|
render_from_medium: &Transform<Float>,
|
|
sigma_a: &Spectrum,
|
|
sigma_s: &Spectrum,
|
|
sigma_scale: Float,
|
|
g: Float,
|
|
density_grid: SampledGrid<Float>,
|
|
temperature_grid: Option<SampledGrid<Float>>,
|
|
le: &Spectrum,
|
|
le_scale: SampledGrid<Float>,
|
|
) -> Self {
|
|
let mut sigma_a_spec =
|
|
DenselySampledSpectrum::from_spectrum(sigma_a, LAMBDA_MIN, LAMBDA_MAX);
|
|
let mut sigma_s_spec =
|
|
DenselySampledSpectrum::from_spectrum(sigma_s, LAMBDA_MIN, LAMBDA_MAX);
|
|
let le_spec = DenselySampledSpectrum::from_spectrum(le, LAMBDA_MIN, LAMBDA_MAX);
|
|
sigma_a_spec.scale(sigma_scale);
|
|
sigma_s_spec.scale(sigma_scale);
|
|
|
|
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
|
|
let is_emissive = if temperature_grid.is_some() {
|
|
true
|
|
} else {
|
|
le_spec.max_value() > 0.
|
|
};
|
|
|
|
for z in 0..majorant_grid.res.z() {
|
|
for y in 0..majorant_grid.res.y() {
|
|
for x in 0..majorant_grid.res.x() {
|
|
let bounds = majorant_grid.voxel_bounds(x, y, z);
|
|
majorant_grid.set(x, y, z, density_grid.max_value(bounds));
|
|
}
|
|
}
|
|
}
|
|
|
|
Self {
|
|
bounds: *bounds,
|
|
render_from_medium: *render_from_medium,
|
|
sigma_a_spec,
|
|
sigma_s_spec,
|
|
density_grid,
|
|
phase: HGPhaseFunction::new(g),
|
|
temperature_grid,
|
|
le_spec,
|
|
le_scale,
|
|
is_emissive,
|
|
majorant_grid,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MediumTrait for GridMedium {
|
|
fn is_emissive(&self) -> bool {
|
|
self.is_emissive
|
|
}
|
|
|
|
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties {
|
|
let mut sigma_a = self.sigma_a_spec.sample(lambda);
|
|
let mut sigma_s = self.sigma_s_spec.sample(lambda);
|
|
let p_transform = self.render_from_medium.apply_inverse(p);
|
|
let p = Point3f::from(self.bounds.offset(&p_transform));
|
|
let d = self.density_grid.lookup(p);
|
|
sigma_a *= d;
|
|
sigma_s *= d;
|
|
let scale = if self.is_emissive {
|
|
self.le_scale.lookup(p)
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let le = if scale > 0.0 {
|
|
let raw_emission = match &self.temperature_grid {
|
|
Some(grid) => {
|
|
let temp = grid.lookup(p);
|
|
BlackbodySpectrum::new(temp).sample(lambda)
|
|
}
|
|
None => self.le_spec.sample(lambda),
|
|
};
|
|
|
|
raw_emission * scale
|
|
} else {
|
|
SampledSpectrum::new(0.0)
|
|
};
|
|
|
|
MediumProperties {
|
|
sigma_a,
|
|
sigma_s,
|
|
phase: self.phase.into(),
|
|
le,
|
|
}
|
|
}
|
|
|
|
fn sample_ray(
|
|
&self,
|
|
ray: &Ray,
|
|
t_max: Float,
|
|
lambda: &SampledWavelengths,
|
|
_buf: &Bump,
|
|
) -> RayMajorantIterator {
|
|
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
|
|
|
|
let inv_dir = Vector3f::new(
|
|
1.0 / local_ray.d.x(),
|
|
1.0 / local_ray.d.y(),
|
|
1.0 / local_ray.d.z(),
|
|
);
|
|
let dir_is_neg = [
|
|
if local_ray.d.x() < 0.0 { 1 } else { 0 },
|
|
if local_ray.d.y() < 0.0 { 1 } else { 0 },
|
|
if local_ray.d.z() < 0.0 { 1 } else { 0 },
|
|
];
|
|
|
|
let (t_min, t_max) =
|
|
match self
|
|
.bounds
|
|
.intersect_p(local_ray.o, local_t_max, inv_dir, &dir_is_neg)
|
|
{
|
|
Some((t0, t1)) => (t0, t1),
|
|
None => return RayMajorantIterator::Void, // Missed the medium bounds
|
|
};
|
|
|
|
let sigma_t = self.sigma_a_spec.sample(lambda) + self.sigma_s_spec.sample(lambda);
|
|
|
|
let iter =
|
|
DDAMajorantIterator::new(&local_ray, t_min, t_max, &self.majorant_grid, &sigma_t);
|
|
|
|
RayMajorantIterator::DDA(iter)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RGBGridMedium {
|
|
bounds: Bounds3f,
|
|
render_from_medium: Transform<Float>,
|
|
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
|
le_scale: Float,
|
|
phase: HGPhaseFunction,
|
|
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
|
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
|
sigma_scale: Float,
|
|
majorant_grid: MajorantGrid,
|
|
}
|
|
|
|
impl RGBGridMedium {
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn new(
|
|
bounds: &Bounds3f,
|
|
render_from_medium: &Transform<Float>,
|
|
g: Float,
|
|
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
|
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
|
sigma_scale: Float,
|
|
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
|
le_scale: Float,
|
|
) -> Self {
|
|
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
|
|
for z in 0..majorant_grid.res.x() {
|
|
for y in 0..majorant_grid.res.y() {
|
|
for x in 0..majorant_grid.res.x() {
|
|
let bounds = majorant_grid.voxel_bounds(x, y, z);
|
|
let convert = |s: &RGBUnboundedSpectrum| s.max_value();
|
|
let max_sigma_t = sigma_a_grid
|
|
.as_ref()
|
|
.map_or(1.0, |g| g.max_value_convert(bounds, convert))
|
|
+ sigma_s_grid
|
|
.as_ref()
|
|
.map_or(1.0, |g| g.max_value_convert(bounds, convert));
|
|
majorant_grid.set(x, y, z, sigma_scale * max_sigma_t);
|
|
}
|
|
}
|
|
}
|
|
|
|
Self {
|
|
bounds: *bounds,
|
|
render_from_medium: *render_from_medium,
|
|
le_grid,
|
|
le_scale,
|
|
phase: HGPhaseFunction::new(g),
|
|
sigma_a_grid,
|
|
sigma_s_grid,
|
|
sigma_scale,
|
|
majorant_grid,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MediumTrait for RGBGridMedium {
|
|
fn is_emissive(&self) -> bool {
|
|
self.le_grid.is_some() && self.le_scale > 0.
|
|
}
|
|
|
|
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties {
|
|
let p_transform = self.render_from_medium.apply_inverse(p);
|
|
let p = Point3f::from(self.bounds.offset(&p_transform));
|
|
let convert = |s: &RGBUnboundedSpectrum| s.sample(lambda);
|
|
let sigma_a = self.sigma_scale
|
|
* self
|
|
.sigma_a_grid
|
|
.as_ref()
|
|
.map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert));
|
|
|
|
let sigma_s = self.sigma_scale
|
|
* self
|
|
.sigma_s_grid
|
|
.as_ref()
|
|
.map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert));
|
|
|
|
let le = self
|
|
.le_grid
|
|
.as_ref()
|
|
.filter(|_| self.le_scale > 0.0)
|
|
.map(|g| g.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale)
|
|
.unwrap_or_else(|| SampledSpectrum::new(0.0));
|
|
|
|
MediumProperties {
|
|
sigma_a,
|
|
sigma_s,
|
|
phase: self.phase.into(),
|
|
le,
|
|
}
|
|
}
|
|
|
|
fn sample_ray(
|
|
&self,
|
|
ray: &Ray,
|
|
t_max: Float,
|
|
_lambda: &SampledWavelengths,
|
|
_buf: &Bump,
|
|
) -> RayMajorantIterator {
|
|
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
|
|
|
|
let inv_dir = Vector3f::new(
|
|
1.0 / local_ray.d.x(),
|
|
1.0 / local_ray.d.y(),
|
|
1.0 / local_ray.d.z(),
|
|
);
|
|
let dir_is_neg = [
|
|
if local_ray.d.x() < 0.0 { 1 } else { 0 },
|
|
if local_ray.d.y() < 0.0 { 1 } else { 0 },
|
|
if local_ray.d.z() < 0.0 { 1 } else { 0 },
|
|
];
|
|
let (t_min, t_max) =
|
|
match self
|
|
.bounds
|
|
.intersect_p(local_ray.o, local_t_max, inv_dir, &dir_is_neg)
|
|
{
|
|
Some((t0, t1)) => (t0, t1),
|
|
None => return RayMajorantIterator::Void, // Missed the medium bounds
|
|
};
|
|
|
|
let sigma_t = SampledSpectrum::new(1.);
|
|
|
|
let iter =
|
|
DDAMajorantIterator::new(&local_ray, t_min, t_max, &self.majorant_grid, &sigma_t);
|
|
RayMajorantIterator::DDA(iter)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct CloudMedium;
|
|
impl MediumTrait for CloudMedium {
|
|
fn is_emissive(&self) -> bool {
|
|
todo!()
|
|
}
|
|
fn sample_point(&self, _p: Point3f, _lambda: &SampledWavelengths) -> MediumProperties {
|
|
todo!()
|
|
}
|
|
fn sample_ray(
|
|
&self,
|
|
_ray: &Ray,
|
|
_t_max: Float,
|
|
_lambda: &SampledWavelengths,
|
|
_buf: &Bump,
|
|
) -> RayMajorantIterator {
|
|
todo!()
|
|
}
|
|
}
|
|
#[derive(Debug, Clone)]
|
|
pub struct NanoVDBMedium;
|
|
impl MediumTrait for NanoVDBMedium {
|
|
fn is_emissive(&self) -> bool {
|
|
todo!()
|
|
}
|
|
fn sample_point(&self, _p: Point3f, _lambda: &SampledWavelengths) -> MediumProperties {
|
|
todo!()
|
|
}
|
|
fn sample_ray(
|
|
&self,
|
|
_ray: &Ray,
|
|
_t_max: Float,
|
|
_lambda: &SampledWavelengths,
|
|
_buf: &Bump,
|
|
) -> RayMajorantIterator {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct MediumInterface {
|
|
pub inside: Option<Arc<Medium>>,
|
|
pub outside: Option<Arc<Medium>>,
|
|
}
|
|
|
|
impl MediumInterface {
|
|
pub fn new(inside: Option<Arc<Medium>>, outside: Option<Arc<Medium>>) -> Self {
|
|
Self { inside, outside }
|
|
}
|
|
|
|
pub fn is_medium_transition(&self) -> bool {
|
|
match (&self.inside, &self.outside) {
|
|
(Some(inside), Some(outside)) => !Arc::ptr_eq(inside, outside),
|
|
(None, None) => false,
|
|
_ => true,
|
|
}
|
|
}
|
|
}
|