pbrt/src/core/interaction.rs

561 lines
16 KiB
Rust

use crate::camera::{Camera, CameraTrait};
use crate::core::bxdf::{BSDF, BxDFFlags};
use crate::core::material::MaterialTrait;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::{Float, clamp_t};
use crate::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
};
use crate::lights::Light;
use crate::shapes::Shape;
use crate::utils::math::{difference_of_products, square};
use std::any::Any;
use std::sync::Arc;
#[derive(Default, Clone, Debug)]
pub struct InteractionData {
pub pi: Point3fi,
pub n: Normal3f,
pub time: Float,
pub wo: Vector3f,
pub medium_interface: Option<Arc<MediumInterface>>,
pub medium: Option<Arc<Medium>>,
}
pub trait Interaction: Send + Sync {
fn get_common(&self) -> &InteractionData;
fn get_common_mut(&mut self) -> &mut InteractionData;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
fn p(&self) -> Point3f {
self.get_common().pi.into()
}
fn pi(&self) -> Point3fi {
self.get_common().pi
}
fn time(&self) -> Float {
self.get_common().time
}
fn wo(&self) -> Vector3f {
self.get_common().wo
}
fn n(&self) -> Normal3f {
self.get_common().n
}
fn is_surface_interaction(&self) -> bool {
self.as_any().is::<SurfaceInteraction>()
}
fn is_medium_interaction(&self) -> bool {
self.as_any().is::<MediumInteraction>()
}
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>>;
fn spawn_ray(&self, d: Vector3f) -> Ray;
fn spawn_ray_to_point(&self, p2: Point3f) -> Ray {
let origin = self.p();
let direction = p2 - origin;
Ray {
o: origin,
d: direction,
time: self.time(),
medium: self.get_medium(direction),
differential: None,
}
}
fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray {
self.spawn_ray_to_point(other.p())
}
fn offset_ray_vector(&self, w: Vector3f) -> Point3f {
Ray::offset_origin(&self.pi(), &self.n(), &w)
}
fn offset_ray_point(&self, pt: Point3f) -> Point3f {
self.offset_ray_vector(pt - self.p())
}
}
#[derive(Default, Clone, Debug)]
pub struct ShadingGeometry {
pub n: Normal3f,
pub dpdu: Vector3f,
pub dpdv: Vector3f,
pub dndu: Normal3f,
pub dndv: Normal3f,
}
#[derive(Default, Debug, Clone)]
pub struct SurfaceInteraction {
pub common: InteractionData,
pub uv: Point2f,
pub dpdu: Vector3f,
pub dpdv: Vector3f,
pub dndu: Normal3f,
pub dndv: Normal3f,
pub shading: ShadingGeometry,
pub medium_interface: Option<Arc<MediumInterface>>,
pub face_index: usize,
pub area_light: Option<Arc<Light>>,
pub material: Option<Arc<dyn MaterialTrait>>,
pub dpdx: Vector3f,
pub dpdy: Vector3f,
pub dudx: Float,
pub dvdx: Float,
pub dudy: Float,
pub dvdy: Float,
pub shape: Arc<Shape>,
pub bsdf: Option<BSDF>,
}
impl SurfaceInteraction {
pub fn compute_differentials(&mut self, r: &Ray, camera: Camera, samples_per_pixel: i32) {
let computed = if let Some(diff) = &r.differential {
let dot_rx = self.common.n.dot(diff.rx_direction.into());
let dot_ry = self.common.n.dot(diff.ry_direction.into());
if dot_rx != 0.0 && dot_ry != 0.0 {
// Estimate screen-space change in p using ray differentials>
let p_as_vec = Normal3f::new(self.p().x(), self.p().y(), self.p().z());
let d = -self.common.n.dot(p_as_vec);
// Compute t for x-auxiliary ray
let rx_origin_vec =
Normal3f::new(diff.rx_origin.x(), diff.rx_origin.y(), diff.rx_origin.z());
let tx = (-self.common.n.dot(rx_origin_vec) - d) / dot_rx;
// Compute intersection point px
let px = diff.rx_origin + diff.rx_direction * tx;
// Compute t for y-auxiliary ray
let ry_origin_vec =
Normal3f::new(diff.ry_origin.x(), diff.ry_origin.y(), diff.ry_origin.z());
let ty = (-self.common.n.dot(ry_origin_vec) - d) / dot_ry;
let py = diff.ry_origin + diff.ry_direction * ty;
self.dpdx = px - self.p();
self.dpdy = py - self.p();
true
} else {
false
}
} else {
false
};
if !computed {
camera.approximate_dp_dxy(
self.p(),
self.n(),
self.time(),
samples_per_pixel,
&mut self.dpdx,
&mut self.dpdy,
);
}
let ata00 = self.dpdu.dot(self.dpdu);
let ata01 = self.dpdu.dot(self.dpdv);
let ata11 = self.dpdv.dot(self.dpdv);
let mut inv_det = 1. / difference_of_products(ata00, ata11, ata01, ata01);
inv_det = if inv_det.is_finite() { inv_det } else { 0. };
let atb0x = self.dpdu.dot(self.dpdx);
let atb1x = self.dpdv.dot(self.dpdx);
let atb0y = self.dpdu.dot(self.dpdy);
let atb1y = self.dpdv.dot(self.dpdy);
// Compute u and v derivatives in x and y
self.dudx = difference_of_products(ata11, atb0x, ata01, atb1x) * inv_det;
self.dvdx = difference_of_products(ata00, atb1x, ata01, atb0x) * inv_det;
self.dudy = difference_of_products(ata11, atb0y, ata01, atb1y) * inv_det;
self.dvdy = difference_of_products(ata00, atb1y, ata01, atb0y) * inv_det;
// Clamp derivatives
self.dudx = if self.dudx.is_finite() {
clamp_t(self.dudx, -1e8, 1e8)
} else {
0.
};
self.dvdx = if self.dvdx.is_finite() {
clamp_t(self.dvdx, -1e8, 1e8)
} else {
0.
};
self.dudy = if self.dudy.is_finite() {
clamp_t(self.dudy, -1e8, 1e8)
} else {
0.
};
self.dvdy = if self.dvdy.is_finite() {
clamp_t(self.dvdy, -1e8, 1e8)
} else {
0.
};
}
pub fn skip_intersection(&self, ray: &mut Ray, t: Float) {
let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d);
ray.o = new_ray.o;
// Skipping other variables, since they should not change when passing through surface
if let Some(diff) = &mut ray.differential {
diff.rx_origin += diff.rx_direction * t;
diff.ry_origin += diff.ry_direction * t;
}
}
pub fn spawn_ray(&self, d: Vector3f) -> Ray {
let mut ray = Ray::spawn(&self.pi(), &self.n(), self.time(), d);
ray.medium = self.get_medium(d);
ray
}
pub fn spawn_ray_to(&self, p2: Point3f) -> Ray {
let mut ray = Ray::spawn_to_point(&self.pi(), &self.n(), self.time(), p2);
ray.medium = self.get_medium(ray.d);
ray
}
// fn get_medium(&self, w: &Vector3f) -> Option<Arc<Medium>> {
// if w.dot(self.n().into()) > 0.0 {
// self.medium_interface.outside.clone()
// } else {
// self.medium_interface.inside.clone()
// }
// }
pub fn spawn_ray_with_differentials(
&self,
ray_i: &Ray,
wi: Vector3f,
flags: BxDFFlags,
eta: Float,
) -> Ray {
let mut rd = self.spawn_ray(wi);
if let Some(diff_i) = &ray_i.differential {
let mut n = self.shading.n;
let mut dndx = self.shading.dndu * self.dudx + self.shading.dndv * self.dvdx;
let mut dndy = self.shading.dndu * self.dudy + self.shading.dndv * self.dvdy;
let dwodx = -diff_i.rx_direction - self.wo();
let dwody = -diff_i.ry_direction - self.wo();
let new_diff_rx_origin = self.p() + self.dpdx;
let new_diff_ry_origin = self.p() + self.dpdy;
let mut new_diff_rx_dir = Vector3f::default();
let mut new_diff_ry_dir = Vector3f::default();
let mut valid_differentials = false;
if flags.contains(BxDFFlags::SPECULAR_REFLECTION) {
valid_differentials = true;
let d_wo_dot_n_dx = dwodx.dot(n.into()) + self.wo().dot(dndx.into());
let d_wo_dot_n_dy = dwody.dot(n.into()) + self.wo().dot(dndy.into());
let wo_dot_n = self.wo().dot(n.into());
new_diff_rx_dir = wi - dwodx
+ (Vector3f::from(dndx) * wo_dot_n + Vector3f::from(n) * d_wo_dot_n_dx) * 2.0;
new_diff_ry_dir = wi - dwody
+ (Vector3f::from(dndy) * wo_dot_n + Vector3f::from(n) * d_wo_dot_n_dy) * 2.0;
} else if flags.contains(BxDFFlags::SPECULAR_TRANSMISSION) {
valid_differentials = true;
if self.wo().dot(n.into()) < 0.0 {
n = -n;
dndx = -dndx;
dndy = -dndy;
}
// Compute partial derivatives
let d_wo_dot_n_dx = dwodx.dot(n.into()) + self.wo().dot(dndx.into());
let d_wo_dot_n_dy = dwody.dot(n.into()) + self.wo().dot(dndy.into());
let wo_dot_n = self.wo().dot(n.into());
let wi_dot_n = wi.dot(Vector3f::from(n));
let abs_wi_dot_n = wi.abs_dot(n.into());
let mu = wo_dot_n / eta - abs_wi_dot_n;
let f_eta = 1.0 / eta;
let f_eta2 = 1.0 / square(eta);
let term = f_eta + (f_eta2 * wo_dot_n / wi_dot_n);
let dmudx = d_wo_dot_n_dx * term;
let dmudy = d_wo_dot_n_dy * term;
new_diff_rx_dir =
wi - dwodx * eta + (Vector3f::from(dndx) * mu + Vector3f::from(n) * dmudx);
new_diff_ry_dir =
wi - dwody * eta + (Vector3f::from(dndy) * mu + Vector3f::from(n) * dmudy);
}
if valid_differentials {
let threshold = 1e16;
if new_diff_rx_dir.norm_squared() > threshold
|| new_diff_ry_dir.norm_squared() > threshold
|| Vector3f::from(new_diff_rx_origin).norm_squared() > threshold
|| Vector3f::from(new_diff_ry_origin).norm_squared() > threshold
{
rd.differential = None;
} else {
rd.differential = Some(RayDifferential {
rx_origin: new_diff_rx_origin,
ry_origin: new_diff_ry_origin,
rx_direction: new_diff_rx_dir,
ry_direction: new_diff_ry_dir,
});
}
}
}
rd
}
}
#[derive(Default, Debug, Clone)]
pub struct PhaseFunction;
pub struct MediumInteraction {
pub common: InteractionData,
pub medium: Arc<Medium>,
pub phase: PhaseFunction,
}
impl Interaction for SurfaceInteraction {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn get_common(&self) -> &InteractionData {
&self.common
}
fn get_common_mut(&mut self) -> &mut InteractionData {
&mut self.common
}
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> {
self.medium_interface.as_ref().and_then(|interface| {
if self.n().dot(w.into()) > 0.0 {
interface.outside.clone()
} else {
interface.inside.clone()
}
})
}
fn spawn_ray(&self, d: Vector3f) -> Ray {
let mut ray = Ray::spawn(&self.pi(), &self.n(), self.time(), d);
ray.medium = self.get_medium(d);
ray
}
fn spawn_ray_to_point(&self, p2: Point3f) -> Ray {
let mut ray = Ray::spawn_to_point(&self.pi(), &self.n(), self.time(), p2);
ray.medium = self.get_medium(ray.d);
ray
}
fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray {
// Check if the other interaction is a surface to use the robust spawn method
if let Some(si_to) = other.as_any().downcast_ref::<SurfaceInteraction>() {
let mut ray = Ray::spawn_to_interaction(
&self.pi(),
&self.n(),
self.time(),
&si_to.pi(),
&si_to.n(),
);
ray.medium = self.get_medium(ray.d);
ray
} else {
// Fallback for non-surface interactions
self.spawn_ray_to_point(other.p())
}
}
}
impl SurfaceInteraction {
pub fn new(
pi: Point3fi,
uv: Point2f,
wo: Vector3f,
dpdu: Vector3f,
dpdv: Vector3f,
dndu: Normal3f,
dndv: Normal3f,
time: Float,
flip: bool,
) -> Self {
let mut n = Normal3f::from(dpdu.cross(dpdv).normalize());
let mut shading_n = n;
if flip {
n *= -1.0;
shading_n *= -1.0;
}
Self {
common: InteractionData {
pi,
n,
time,
wo,
medium_interface: None,
medium: None,
},
uv,
dpdu,
dpdv,
dndu,
dndv,
shading: ShadingGeometry {
n: shading_n,
dpdu,
dpdv,
dndu,
dndv,
},
medium_interface: None,
material: None,
face_index: 0,
area_light: None,
dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(),
dudx: 0.0,
dudy: 0.0,
dvdx: 0.0,
dvdy: 0.0,
shape: Arc::new(Shape::default()),
bsdf: None,
}
}
pub fn new_with_face(
pi: Point3fi,
uv: Point2f,
wo: Vector3f,
dpdu: Vector3f,
dpdv: Vector3f,
dndu: Normal3f,
dndv: Normal3f,
time: Float,
flip: bool,
face_index: usize,
) -> Self {
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
si.face_index = face_index;
si
}
pub fn set_shading_geometry(
&mut self,
ns: Normal3f,
dpdus: Vector3f,
dpdvs: Vector3f,
dndus: Normal3f,
dndvs: Normal3f,
orientation: bool,
) {
self.shading.n = ns;
if orientation {
self.common.n = self.n().face_forward(self.shading.n.into());
}
self.shading.dpdu = dpdus;
self.shading.dpdv = dpdvs;
self.shading.dndu = dndus;
self.shading.dndv = dndvs;
}
pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self {
Self {
common: InteractionData {
pi,
n,
time: 0.,
wo: Vector3f::zero(),
medium_interface: None,
medium: None,
},
uv,
..Default::default()
}
}
pub fn set_intersection_properties(
&mut self,
mtl: Arc<dyn MaterialTrait>,
area: Arc<Light>,
prim_medium_interface: Option<Arc<MediumInterface>>,
ray_medium: Arc<Medium>,
) {
self.material = Some(mtl);
self.area_light = Some(area);
if prim_medium_interface
.as_ref()
.is_some_and(|mi| mi.is_medium_transition())
{
self.common.medium_interface = prim_medium_interface;
} else {
self.common.medium = Some(ray_medium);
}
}
}
impl Interaction for MediumInteraction {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn get_common(&self) -> &InteractionData {
&self.common
}
fn get_common_mut(&mut self) -> &mut InteractionData {
&mut self.common
}
fn get_medium(&self, _w: Vector3f) -> Option<Arc<Medium>> {
Some(self.medium.clone())
}
fn spawn_ray(&self, d: Vector3f) -> Ray {
Ray {
o: self.p(),
d,
time: self.time(),
medium: Some(self.medium.clone()),
differential: None,
}
}
}