Updated Hair bsdf getter, added enum for clearer variant construction on absorption

This commit is contained in:
Wito Wiala 2026-09-02 10:45:36 +01:00
parent 0fcfcbd467
commit f496c6721e
3 changed files with 101 additions and 41 deletions

View file

@ -8,7 +8,7 @@ use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect, TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect,
refract, 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::gpu_array_from_fn;
use crate::utils::math::{ use crate::utils::math::{
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, 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::{ use crate::utils::sampling::{
cosine_hemisphere_pdf, sample_cosine_hemisphere, sample_trimmed_logistic, 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 core::any::Any;
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
@ -34,7 +34,6 @@ pub struct HairBxDF {
pub s: Float, pub s: Float,
pub sin_2k_alpha: [Float; P_MAX], pub sin_2k_alpha: [Float; P_MAX],
pub cos_2k_alpha: [Float; P_MAX], pub cos_2k_alpha: [Float; P_MAX],
pub colorspaces: DeviceStandardColorSpaces,
} }
impl HairBxDF { impl HairBxDF {
@ -45,7 +44,6 @@ impl HairBxDF {
beta_m: Float, beta_m: Float,
beta_n: Float, beta_n: Float,
alpha: Float, alpha: Float,
colorspaces: DeviceStandardColorSpaces,
) -> Self { ) -> Self {
let mut sin_2k_alpha = [0.; P_MAX]; let mut sin_2k_alpha = [0.; P_MAX];
let mut cos_2k_alpha = [0.; P_MAX]; let mut cos_2k_alpha = [0.; P_MAX];
@ -67,7 +65,6 @@ impl HairBxDF {
s: 0., s: 0.,
sin_2k_alpha, sin_2k_alpha,
cos_2k_alpha, cos_2k_alpha,
colorspaces,
} }
} }
@ -141,12 +138,25 @@ impl HairBxDF {
pub fn sigma_a_from_concentration( pub fn sigma_a_from_concentration(
ce: Float, ce: Float,
cp: Float, cp: Float,
stdcs: DeviceStandardColorSpaces, srgb: Ptr<RGBColorSpace>,
) -> RGBUnboundedSpectrum { ) -> RGBUnboundedSpectrum {
let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37); 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 pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05);
let sigma_a = ce * eumelanin_sigma_a + cp * pheomelanin_sigma_a; 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
} }
} }

View file

@ -11,46 +11,50 @@ use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{RGBColorSpace, SampledSpectrum, SampledWavelengths};
use crate::textures::SpectrumMixTexture; use crate::textures::SpectrumMixTexture;
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::clamp; use crate::utils::math::clamp;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub enum HairAbsorption {
SigmaA(Ptr<SpectrumTexture>),
Color(Ptr<SpectrumTexture>),
Melanin {
eumelanin: Ptr<FloatTexture>,
pheomelanin: Ptr<FloatTexture>,
},
}
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct HairMaterial { pub struct HairMaterial {
pub sigma_a: Ptr<SpectrumTexture>, pub hair_absorption: HairAbsorption,
pub color: Ptr<SpectrumTexture>,
pub eumelanin: Ptr<FloatTexture>,
pub pheomelanin: Ptr<FloatTexture>,
pub eta: Ptr<FloatTexture>, pub eta: Ptr<FloatTexture>,
pub beta_m: Ptr<FloatTexture>, pub beta_m: Ptr<FloatTexture>,
pub beta_n: Ptr<FloatTexture>, pub beta_n: Ptr<FloatTexture>,
pub alpha: Ptr<FloatTexture>, pub alpha: Ptr<FloatTexture>,
pub colorspace: Ptr<RGBColorSpace>,
} }
impl HairMaterial { impl HairMaterial {
#[cfg(not(gpu))] #[cfg(not(gpu))]
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
sigma_a: Ptr<SpectrumTexture>, hair_absorption: HairAbsorption,
color: Ptr<SpectrumTexture>,
eumelanin: Ptr<FloatTexture>,
pheomelanin: Ptr<FloatTexture>,
eta: Ptr<FloatTexture>, eta: Ptr<FloatTexture>,
beta_m: Ptr<FloatTexture>, beta_m: Ptr<FloatTexture>,
beta_n: Ptr<FloatTexture>, beta_n: Ptr<FloatTexture>,
alpha: Ptr<FloatTexture>, alpha: Ptr<FloatTexture>,
colorspace: Ptr<RGBColorSpace>,
) -> Self { ) -> Self {
Self { Self {
sigma_a, hair_absorption,
color,
eumelanin,
pheomelanin,
eta, eta,
beta_m, beta_m,
beta_n, beta_n,
alpha, alpha,
colorspace,
} }
} }
} }
@ -58,19 +62,60 @@ impl HairMaterial {
impl MaterialTrait for HairMaterial { impl MaterialTrait for HairMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
_tex_eval: &T, tex_eval: &T,
_ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
_lambda: &mut SampledWavelengths, lambda: &mut SampledWavelengths,
) -> BSDF { ) -> 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<T>( fn get_bssrdf<T>(
&self, &self,
_tex_eval: &T, _tex_eval: &T,
_ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
todo!() None
} }
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {

View file

@ -29,31 +29,36 @@ impl CreateMaterial for HairMaterial {
let eumelanin = parameters.get_float_texture_or_null("eumelanin")?; let eumelanin = parameters.get_float_texture_or_null("eumelanin")?;
let pheomelanin = parameters.get_float_texture_or_null("pheomelanin")?; let pheomelanin = parameters.get_float_texture_or_null("pheomelanin")?;
let has_melanin = eumelanin.is_some() || pheomelanin.is_some(); 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 eta = parameters.get_float_texture("eta", 1.55)?;
let beta_m = parameters.get_float_texture("beta_m", 0.3)?; let beta_m = parameters.get_float_texture("beta_m", 0.3)?;
let beta_n = parameters.get_float_texture("beta_n", 0.3)?; let beta_n = parameters.get_float_texture("beta_n", 0.3)?;
let alpha = parameters.get_float_texture("alpha", 2.)?; 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( let material = HairMaterial::new(
arena.upload(sigma_a), absorption,
arena.upload(reflectance),
arena.upload(eumelanin),
arena.upload(pheomelanin),
arena.upload(eta), arena.upload(eta),
arena.upload(beta_m), arena.upload(beta_m),
arena.upload(beta_n), arena.upload(beta_n),
arena.upload(alpha), arena.upload(alpha),
stdcs.srgb,
); );
Ok(Material::Hair(material)) Ok(Material::Hair(material))