diff --git a/shared/src/bxdfs/complex.rs b/shared/src/bxdfs/complex.rs index dbbbde9..ff053e2 100644 --- a/shared/src/bxdfs/complex.rs +++ b/shared/src/bxdfs/complex.rs @@ -8,7 +8,7 @@ use crate::core::scattering::{ TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect, refract, }; -use crate::spectra::{DeviceStandardColorSpaces, RGBUnboundedSpectrum, SampledSpectrum}; +use crate::spectra::{N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum}; use crate::utils::gpu_array_from_fn; use crate::utils::math::{ clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, @@ -17,7 +17,7 @@ use crate::utils::math::{ use crate::utils::sampling::{ cosine_hemisphere_pdf, sample_cosine_hemisphere, sample_trimmed_logistic, }; -use crate::{Float, INV_2_PI, INV_PI, PI}; +use crate::{Float, INV_2_PI, INV_PI, PI, Ptr}; use core::any::Any; use num_traits::Float as NumFloat; @@ -34,7 +34,6 @@ pub struct HairBxDF { pub s: Float, pub sin_2k_alpha: [Float; P_MAX], pub cos_2k_alpha: [Float; P_MAX], - pub colorspaces: DeviceStandardColorSpaces, } impl HairBxDF { @@ -45,7 +44,6 @@ impl HairBxDF { beta_m: Float, beta_n: Float, alpha: Float, - colorspaces: DeviceStandardColorSpaces, ) -> Self { let mut sin_2k_alpha = [0.; P_MAX]; let mut cos_2k_alpha = [0.; P_MAX]; @@ -67,7 +65,6 @@ impl HairBxDF { s: 0., sin_2k_alpha, cos_2k_alpha, - colorspaces, } } @@ -141,12 +138,25 @@ impl HairBxDF { pub fn sigma_a_from_concentration( ce: Float, cp: Float, - stdcs: DeviceStandardColorSpaces, + srgb: Ptr, ) -> RGBUnboundedSpectrum { let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37); let pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05); let sigma_a = ce * eumelanin_sigma_a + cp * pheomelanin_sigma_a; - RGBUnboundedSpectrum::new(&stdcs.srgb, sigma_a) + RGBUnboundedSpectrum::new(&srgb, sigma_a) + } + + pub fn sigma_a_from_reflectance(c: SampledSpectrum, beta_n: Float) -> SampledSpectrum { + let mut sigma_a = SampledSpectrum::zero(); + for i in 0..N_SPECTRUM_SAMPLES { + sigma_a[i] = square( + c[i].ln() + / (5.969 - 0.215 * beta_n + 2.532 * square(beta_n) - 10.73 * beta_n.powf(3.) + + 5.574 * beta_n.powf(4.) + + 0.245 * beta_n.powf(5.)), + ); + } + sigma_a } } diff --git a/shared/src/materials/complex.rs b/shared/src/materials/complex.rs index 4a55468..38a482c 100644 --- a/shared/src/materials/complex.rs +++ b/shared/src/materials/complex.rs @@ -11,46 +11,50 @@ use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; -use crate::spectra::{SampledSpectrum, SampledWavelengths}; +use crate::spectra::{RGBColorSpace, SampledSpectrum, SampledWavelengths}; use crate::textures::SpectrumMixTexture; use crate::utils::Ptr; use crate::utils::math::clamp; +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub enum HairAbsorption { + SigmaA(Ptr), + Color(Ptr), + Melanin { + eumelanin: Ptr, + pheomelanin: Ptr, + }, +} + #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct HairMaterial { - pub sigma_a: Ptr, - pub color: Ptr, - pub eumelanin: Ptr, - pub pheomelanin: Ptr, + pub hair_absorption: HairAbsorption, pub eta: Ptr, pub beta_m: Ptr, pub beta_n: Ptr, pub alpha: Ptr, + pub colorspace: Ptr, } impl HairMaterial { #[cfg(not(gpu))] - #[allow(clippy::too_many_arguments)] pub fn new( - sigma_a: Ptr, - color: Ptr, - eumelanin: Ptr, - pheomelanin: Ptr, + hair_absorption: HairAbsorption, eta: Ptr, beta_m: Ptr, beta_n: Ptr, alpha: Ptr, + colorspace: Ptr, ) -> Self { Self { - sigma_a, - color, - eumelanin, - pheomelanin, + hair_absorption, eta, beta_m, beta_n, alpha, + colorspace, } } } @@ -58,19 +62,60 @@ impl HairMaterial { impl MaterialTrait for HairMaterial { fn get_bsdf( &self, - _tex_eval: &T, - _ctx: &MaterialEvalContext, - _lambda: &mut SampledWavelengths, + tex_eval: &T, + ctx: &MaterialEvalContext, + lambda: &mut SampledWavelengths, ) -> BSDF { - todo!() + let bm = tex_eval.evaluate_float(&self.beta_m, ctx).clamp(1e-2, 1.0); + let bn = tex_eval.evaluate_float(&self.beta_n, ctx).clamp(1e-2, 1.0); + let a = tex_eval.evaluate_float(&self.alpha, ctx); + let e = tex_eval.evaluate_float(&self.eta, ctx); + let sig_a = match self.hair_absorption { + // Absorption coefficient, not a reflectance, can be larger than 1 + HairAbsorption::SigmaA(sigma_a) => { + SampledSpectrum::clamp_zero(&tex_eval.evaluate_spectrum(&sigma_a, ctx, lambda)) + } + HairAbsorption::Color(color) => { + let c = SampledSpectrum::clamp( + &tex_eval.evaluate_spectrum(&color, ctx, lambda), + 0., + 1., + ); + HairBxDF::sigma_a_from_reflectance(c, bn) + } + HairAbsorption::Melanin { + eumelanin, + pheomelanin, + } => { + debug_assert!(!eumelanin.is_null() || !pheomelanin.is_null()); + let eu = if !eumelanin.is_null() { + tex_eval.evaluate_float(&eumelanin, ctx) + } else { + 0. + }; + let pheo = if !pheomelanin.is_null() { + tex_eval.evaluate_float(&pheomelanin, ctx) + } else { + 0. + }; + + HairBxDF::sigma_a_from_concentration(eu.max(0.0), pheo.max(0.0), self.colorspace) + .sample(lambda) + } + }; + + let h = -1. + 2. * ctx.uv[1]; + let bxdf = BxDF::Hair(HairBxDF::new(h, e, sig_a, bm, bn, a)); + BSDF::new(ctx.ns, ctx.dpdus, bxdf) } + fn get_bssrdf( &self, _tex_eval: &T, _ctx: &MaterialEvalContext, _lambda: &SampledWavelengths, ) -> Option { - todo!() + None } fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { diff --git a/src/materials/complex.rs b/src/materials/complex.rs index e69bdea..609ed03 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -29,31 +29,36 @@ impl CreateMaterial for HairMaterial { let eumelanin = parameters.get_float_texture_or_null("eumelanin")?; let pheomelanin = parameters.get_float_texture_or_null("pheomelanin")?; let has_melanin = eumelanin.is_some() || pheomelanin.is_some(); - - let sigma_a = if sigma_a.is_none() && reflectance.is_none() && !has_melanin { - let stdcs = get_colorspace_device(); - let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs); - let spectrum = Spectrum::RGBUnbounded(default_rgb); - let texture = SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)); - Some(Arc::new(texture)) - } else { - sigma_a - }; - let eta = parameters.get_float_texture("eta", 1.55)?; let beta_m = parameters.get_float_texture("beta_m", 0.3)?; let beta_n = parameters.get_float_texture("beta_n", 0.3)?; let alpha = parameters.get_float_texture("alpha", 2.)?; + let stdcs = get_colorspace_device(); + + let absorption = if let Some(s) = sigma_a { + HairAbsorption::SigmaA(arena.upload(s)) + } else if let Some(r) = reflectance { + HairAbsorption::Color(arena.upload(r)) + } else if has_melanin { + HairAbsorption::Melanin { + eumelanin: arena.upload(eumelanin), + pheomelanin: arena.upload(pheomelanin), + } + } else { + let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs.srgb); + let spectrum = Spectrum::RGBUnbounded(default_rgb); + let texture = SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)); + HairAbsorption::SigmaA(arena.upload(Arc::new(texture))) + }; + let material = HairMaterial::new( - arena.upload(sigma_a), - arena.upload(reflectance), - arena.upload(eumelanin), - arena.upload(pheomelanin), + absorption, arena.upload(eta), arena.upload(beta_m), arena.upload(beta_n), arena.upload(alpha), + stdcs.srgb, ); Ok(Material::Hair(material))