use bumpalo::Bump; use enum_dispatch::enum_dispatch; use std::ops::Deref; use std::sync::Arc; use crate::core::bssrdf::BSSRDF; use crate::core::bxdf::{ BSDF, BxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, }; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike}; use crate::core::interaction::InteractionTrait; use crate::core::interaction::{Interaction, Shadinggeom, SurfaceInteraction}; use crate::core::pbrt::Float; use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::texture::{ FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator, }; use crate::image::{Image, WrapMode, WrapMode2D}; use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider}; use crate::utils::hash::hash_float; use crate::utils::math::clamp; #[derive(Clone, Debug)] pub struct MaterialEvalContext { pub texture: TextureEvalContext, pub wo: Vector3f, pub ns: Normal3f, pub dpdus: Vector3f, } impl Deref for MaterialEvalContext { type Target = TextureEvalContext; fn deref(&self) -> &Self::Target { &self.texture } } impl From<&SurfaceInteraction> for MaterialEvalContext { fn from(si: &SurfaceInteraction) -> Self { Self { texture: TextureEvalContext::from(si), wo: si.common.wo, ns: si.shading.n, dpdus: si.shading.dpdu, } } } #[derive(Clone, Debug, Default)] pub struct NormalBumpEvalContext { p: Point3f, uv: Point2f, n: Normal3f, shading: Shadinggeom, dpdx: Vector3f, dpdy: Vector3f, // All 0 dudx: Float, dudy: Float, dvdx: Float, dvdy: Float, face_index: usize, } impl From<&SurfaceInteraction> for NormalBumpEvalContext { fn from(si: &SurfaceInteraction) -> Self { Self { p: si.p(), uv: si.uv, n: si.n(), shading: si.shading.clone(), dudx: si.dudx, dudy: si.dudy, dvdx: si.dvdx, dvdy: si.dvdy, dpdx: si.dpdx, dpdy: si.dpdy, face_index: si.face_index, } } } impl From<&NormalBumpEvalContext> for TextureEvalContext { fn from(ctx: &NormalBumpEvalContext) -> Self { Self { p: ctx.p, uv: ctx.uv, n: ctx.n, dpdx: ctx.dpdx, dpdy: ctx.dpdy, dudx: ctx.dudx, dudy: ctx.dudy, dvdx: ctx.dvdx, dvdy: ctx.dvdy, face_index: ctx.face_index, } } } pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) { let wrap = WrapMode2D::from(WrapMode::Repeat); let uv = Point2f::new(ctx.uv[0], 1. - ctx.uv[1]); let r = normal_map.bilerp_channel_with_wrap(uv, 0, wrap); let g = normal_map.bilerp_channel_with_wrap(uv, 1, wrap); let b = normal_map.bilerp_channel_with_wrap(uv, 2, wrap); let mut ns = Vector3f::new(2.0 * r - 1.0, 2.0 * g - 1.0, 2.0 * b - 1.0); ns = ns.normalize(); let frame = Frame::from_xz(ctx.shading.dpdu.normalize(), Vector3f::from(ctx.shading.n)); ns = frame.from_local(ns); let ulen = ctx.shading.dpdu.norm(); let vlen = ctx.shading.dpdv.norm(); let dpdu = ctx.shading.dpdu.gram_schmidt(ns).normalize() * ulen; let dpdv = ctx.shading.dpdu.cross(dpdu).normalize() * vlen; (dpdu, dpdv) } pub fn bump_map( tex_eval: &T, displacement: &FloatTexture, ctx: &NormalBumpEvalContext, ) -> (Vector3f, Vector3f) { debug_assert!(tex_eval.can_evaluate(&[displacement], &[])); let mut du = 0.5 * (ctx.dudx.abs() + ctx.dudy.abs()); if du == 0.0 { du = 0.0005; } let mut dv = 0.5 * (ctx.dvdx.abs() + ctx.dvdy.abs()); if dv == 0.0 { dv = 0.0005; } let mut shifted_ctx = TextureEvalContext::from(ctx); shifted_ctx.p = ctx.p + ctx.shading.dpdu * du; shifted_ctx.uv = ctx.uv + Vector2f::new(du, 0.0); let u_displace = tex_eval.evaluate_float(displacement, &shifted_ctx); shifted_ctx.p = ctx.p + ctx.shading.dpdv * dv; shifted_ctx.uv = ctx.uv + Vector2f::new(0.0, dv); let v_displace = tex_eval.evaluate_float(displacement, &shifted_ctx); let center_ctx = TextureEvalContext::from(ctx); let displace = tex_eval.evaluate_float(displacement, ¢er_ctx); let d_displace_du = (u_displace - displace) / du; let d_displace_dv = (v_displace - displace) / dv; let n_vec = Vector3f::from(ctx.shading.n); let dndu_vec = Vector3f::from(ctx.shading.dndu); let dndv_vec = Vector3f::from(ctx.shading.dndv); let dpdu = ctx.shading.dpdu + n_vec * d_displace_du + dndu_vec * displace; let dpdv = ctx.shading.dpdv + n_vec * d_displace_dv + dndv_vec * displace; (dpdu, dpdv) } #[enum_dispatch] pub trait MaterialTrait: Send + Sync + std::fmt::Debug { fn get_bsdf( &self, tex_eval: &T, ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> BSDF; fn get_bssrdf( &self, tex_eval: &T, ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> Option; fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool; fn get_normal_map(&self) -> Option>; fn get_displacement(&self) -> Option; fn has_surface_scattering(&self) -> bool; } #[derive(Clone, Debug)] #[enum_dispatch(MaterialTrait)] pub enum Material { CoatedDiffuse(CoatedDiffuseMaterial), CoatedConductor(CoatedConductorMaterial), Conductor(ConductorMaterial), Dielectric(DielectricMaterial), Diffuse(DiffuseMaterial), DiffuseTransmission(DiffuseTransmissionMaterial), Hair(HairMaterial), Measured(MeasuredMaterial), Subsurface(SubsurfaceMaterial), ThinDielectric(ThinDielectricMaterial), Mix(MixMaterial), } #[derive(Clone, Debug)] pub struct CoatedDiffuseMaterial { displacement: FloatTexture, normal_map: Option>, reflectance: SpectrumTexture, albedo: SpectrumTexture, u_roughness: FloatTexture, v_roughness: FloatTexture, thickness: FloatTexture, g: FloatTexture, eta: Spectrum, remap_roughness: bool, max_depth: usize, n_samples: usize, } impl CoatedDiffuseMaterial { #[allow(clippy::too_many_arguments)] pub fn new( reflectance: SpectrumTexture, u_roughness: FloatTexture, v_roughness: FloatTexture, thickness: FloatTexture, albedo: SpectrumTexture, g: FloatTexture, eta: Spectrum, displacement: FloatTexture, normal_map: Option>, remap_roughness: bool, max_depth: usize, n_samples: usize, ) -> Self { Self { displacement, normal_map, reflectance, albedo, u_roughness, v_roughness, thickness, g, eta, remap_roughness, max_depth, n_samples, } } } impl MaterialTrait for CoatedDiffuseMaterial { fn get_bsdf( &self, tex_eval: &T, ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> BSDF { let r = SampledSpectrum::clamp( &tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda), 0., 1., ); let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx); let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx); if self.remap_roughness { u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough); v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough); } let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough); let thick = tex_eval.evaluate_float(&self.thickness, ctx); let mut sampled_eta = self.eta.evaluate(lambda[0]); if self.eta.is_constant() { let mut lambda = *lambda; lambda.terminate_secondary_inplace(); } if sampled_eta == 0. { sampled_eta = 1. } let a = SampledSpectrum::clamp( &tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda), 0., 1., ); let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.); let bxdf = BxDF::CoatedDiffuse(CoatedDiffuseBxDF::new( DielectricBxDF::new(sampled_eta, distrib), DiffuseBxDF::new(r), thick, a, gg, self.max_depth, self.n_samples, )); BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf)) } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { None } fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { tex_eval.can_evaluate( &[ &self.u_roughness, &self.v_roughness, &self.thickness, &self.g, ], &[&self.reflectance, &self.albedo], ) } fn get_normal_map(&self) -> Option> { self.normal_map.clone() } fn get_displacement(&self) -> Option { Some(self.displacement.clone()) } fn has_surface_scattering(&self) -> bool { false } } #[derive(Clone, Debug)] pub struct CoatedConductorMaterial { displacement: FloatTexture, normal_map: Option>, interface_uroughness: FloatTexture, interface_vroughness: FloatTexture, thickness: FloatTexture, interface_eta: Spectrum, g: FloatTexture, albedo: SpectrumTexture, conductor_uroughness: FloatTexture, conductor_vroughness: FloatTexture, conductor_eta: Option, k: Option, reflectance: SpectrumTexture, remap_roughness: bool, max_depth: usize, n_samples: usize, } impl CoatedConductorMaterial { #[allow(clippy::too_many_arguments)] pub fn new( displacement: FloatTexture, normal_map: Option>, interface_uroughness: FloatTexture, interface_vroughness: FloatTexture, thickness: FloatTexture, interface_eta: Spectrum, g: FloatTexture, albedo: SpectrumTexture, conductor_uroughness: FloatTexture, conductor_vroughness: FloatTexture, conductor_eta: Option, k: Option, reflectance: SpectrumTexture, remap_roughness: bool, max_depth: usize, n_samples: usize, ) -> Self { Self { displacement, normal_map, interface_uroughness, interface_vroughness, thickness, interface_eta, g, albedo, conductor_uroughness, conductor_vroughness, conductor_eta, k, reflectance, remap_roughness, max_depth, n_samples, } } } impl MaterialTrait for CoatedConductorMaterial { fn get_bsdf( &self, tex_eval: &T, ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> BSDF { let mut iurough = tex_eval.evaluate_float(&self.interface_uroughness, ctx); let mut ivrough = tex_eval.evaluate_float(&self.interface_vroughness, ctx); if self.remap_roughness { iurough = TrowbridgeReitzDistribution::roughness_to_alpha(iurough); ivrough = TrowbridgeReitzDistribution::roughness_to_alpha(ivrough); } let interface_distrib = TrowbridgeReitzDistribution::new(iurough, ivrough); let thick = tex_eval.evaluate_float(&self.thickness, ctx); let mut ieta = self.interface_eta.evaluate(lambda[0]); if self.interface_eta.is_constant() { let mut lambda = *lambda; lambda.terminate_secondary_inplace(); } if ieta == 0. { ieta = 1.; } let (mut ce, mut ck) = if let Some(eta_tex) = &self.conductor_eta { let k_tex = self .k .as_ref() .expect("CoatedConductor: 'k' must be provided if 'conductor_eta' is present"); let ce = tex_eval.evaluate_spectrum(eta_tex, ctx, lambda); let ck = tex_eval.evaluate_spectrum(k_tex, ctx, lambda); (ce, ck) } else { let r = SampledSpectrum::clamp( &tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda), 0., 0.9999, ); let ce = SampledSpectrum::new(1.0); let one_minus_r = SampledSpectrum::new(1.) - r; let ck = 2. * r.sqrt() / SampledSpectrum::clamp_zero(&one_minus_r).sqrt(); (ce, ck) }; ce /= ieta; ck /= ieta; let mut curough = tex_eval.evaluate_float(&self.conductor_uroughness, ctx); let mut cvrough = tex_eval.evaluate_float(&self.conductor_vroughness, ctx); if self.remap_roughness { curough = TrowbridgeReitzDistribution::roughness_to_alpha(curough); cvrough = TrowbridgeReitzDistribution::roughness_to_alpha(cvrough); } let conductor_distrib = TrowbridgeReitzDistribution::new(curough, cvrough); let a = SampledSpectrum::clamp( &tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda), 0., 1., ); let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.); let bxdf = BxDF::CoatedConductor(CoatedConductorBxDF::new( DielectricBxDF::new(ieta, interface_distrib), ConductorBxDF::new(&conductor_distrib, ce, ck), thick, a, gg, self.max_depth, self.n_samples, )); BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf)) } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { None } fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { let float_textures = [ &self.interface_uroughness, &self.interface_vroughness, &self.thickness, &self.g, &self.conductor_uroughness, &self.conductor_vroughness, ]; let mut spectrum_textures = Vec::with_capacity(4); spectrum_textures.push(&self.albedo); if let Some(eta) = &self.conductor_eta { spectrum_textures.push(eta); } if let Some(k) = &self.k { spectrum_textures.push(k); } if self.conductor_eta.is_none() { spectrum_textures.push(&self.reflectance); } tex_eval.can_evaluate(&float_textures, &spectrum_textures) } fn get_normal_map(&self) -> Option> { self.normal_map.clone() } fn get_displacement(&self) -> Option { Some(self.displacement.clone()) } fn has_surface_scattering(&self) -> bool { false } } #[derive(Clone, Debug)] pub struct ConductorMaterial; impl MaterialTrait for ConductorMaterial { fn get_bsdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> BSDF { todo!() } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { todo!() } fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { todo!() } fn get_normal_map(&self) -> Option> { todo!() } fn get_displacement(&self) -> Option { todo!() } fn has_surface_scattering(&self) -> bool { todo!() } } #[derive(Clone, Debug)] pub struct DielectricMaterial { normal_map: Option>, displacement: FloatTexture, u_roughness: FloatTexture, v_roughness: FloatTexture, remap_roughness: bool, eta: Spectrum, } impl MaterialTrait for DielectricMaterial { fn get_bsdf( &self, tex_eval: &T, ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> BSDF { let mut sampled_eta = self.eta.evaluate(lambda[0]); if !self.eta.is_constant() { lambda.terminate_secondary(); } if sampled_eta == 0.0 { sampled_eta = 1.0; } let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx); let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx); if self.remap_roughness { u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough); v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough); } let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough); let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib)); BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf)) } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { None } fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { tex_eval.can_evaluate(&[&self.u_roughness, &self.v_roughness], &[]) } fn get_normal_map(&self) -> Option> { self.normal_map.clone() } fn get_displacement(&self) -> Option { Some(self.displacement.clone()) } fn has_surface_scattering(&self) -> bool { false } } #[derive(Clone, Debug)] pub struct DiffuseMaterial { normal_map: Option>, displacement: FloatTexture, reflectance: SpectrumTexture, } impl MaterialTrait for DiffuseMaterial { fn get_bsdf( &self, tex_eval: &T, ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> BSDF { let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda); let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r)); BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf)) } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { todo!() } fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { tex_eval.can_evaluate(&[], &[&self.reflectance]) } fn get_normal_map(&self) -> Option> { self.normal_map.clone() } fn get_displacement(&self) -> Option { Some(self.displacement.clone()) } fn has_surface_scattering(&self) -> bool { false } } #[derive(Clone, Debug)] pub struct DiffuseTransmissionMaterial; impl MaterialTrait for DiffuseTransmissionMaterial { fn get_bsdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> BSDF { todo!() } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { todo!() } fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { todo!() } fn get_normal_map(&self) -> Option> { todo!() } fn get_displacement(&self) -> Option { todo!() } fn has_surface_scattering(&self) -> bool { todo!() } } #[derive(Clone, Debug)] pub struct HairMaterial; impl MaterialTrait for HairMaterial { fn get_bsdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> BSDF { todo!() } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { todo!() } fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { todo!() } fn get_normal_map(&self) -> Option> { todo!() } fn get_displacement(&self) -> Option { todo!() } fn has_surface_scattering(&self) -> bool { todo!() } } #[derive(Clone, Debug)] pub struct MeasuredMaterial; impl MaterialTrait for MeasuredMaterial { fn get_bsdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> BSDF { todo!() } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { todo!() } fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { todo!() } fn get_normal_map(&self) -> Option> { todo!() } fn get_displacement(&self) -> Option { todo!() } fn has_surface_scattering(&self) -> bool { todo!() } } #[derive(Clone, Debug)] pub struct SubsurfaceMaterial; impl MaterialTrait for SubsurfaceMaterial { fn get_bsdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> BSDF { todo!() } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { todo!() } fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { todo!() } fn get_normal_map(&self) -> Option> { todo!() } fn get_displacement(&self) -> Option { todo!() } fn has_surface_scattering(&self) -> bool { todo!() } } #[derive(Clone, Debug)] pub struct ThinDielectricMaterial; impl MaterialTrait for ThinDielectricMaterial { fn get_bsdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> BSDF { todo!() } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { todo!() } fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { todo!() } fn get_normal_map(&self) -> Option> { todo!() } fn get_displacement(&self) -> Option { todo!() } fn has_surface_scattering(&self) -> bool { todo!() } } #[derive(Clone, Debug)] pub struct MixMaterial { pub amount: FloatTexture, pub materials: [Box; 2], } impl MixMaterial { pub fn choose_material( &self, tex_eval: &T, ctx: &MaterialEvalContext, ) -> &Material { let amt = tex_eval.evaluate_float(&self.amount, ctx); if amt <= 0.0 { return &self.materials[0]; } if amt >= 1.0 { return &self.materials[1]; } let u = hash_float(&(ctx.p, ctx.wo)); if amt < u { &self.materials[0] } else { &self.materials[1] } } } impl MaterialTrait for MixMaterial { fn get_bsdf( &self, tex_eval: &T, ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> BSDF { let chosen_mat = self.choose_material(tex_eval, ctx); chosen_mat.get_bsdf(tex_eval, ctx, lambda) } fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { None } fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { tex_eval.can_evaluate(&[&self.amount], &[]) } fn get_normal_map(&self) -> Option> { None } fn get_displacement(&self) -> Option { panic!( "MixMaterial::get_displacement() shouldn't be called. \ Displacement is not supported on Mix materials directly." ); } fn has_surface_scattering(&self) -> bool { false } }