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,
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<RGBColorSpace>,
) -> 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
}
}

View file

@ -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<SpectrumTexture>),
Color(Ptr<SpectrumTexture>),
Melanin {
eumelanin: Ptr<FloatTexture>,
pheomelanin: Ptr<FloatTexture>,
},
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HairMaterial {
pub sigma_a: Ptr<SpectrumTexture>,
pub color: Ptr<SpectrumTexture>,
pub eumelanin: Ptr<FloatTexture>,
pub pheomelanin: Ptr<FloatTexture>,
pub hair_absorption: HairAbsorption,
pub eta: Ptr<FloatTexture>,
pub beta_m: Ptr<FloatTexture>,
pub beta_n: Ptr<FloatTexture>,
pub alpha: Ptr<FloatTexture>,
pub colorspace: Ptr<RGBColorSpace>,
}
impl HairMaterial {
#[cfg(not(gpu))]
#[allow(clippy::too_many_arguments)]
pub fn new(
sigma_a: Ptr<SpectrumTexture>,
color: Ptr<SpectrumTexture>,
eumelanin: Ptr<FloatTexture>,
pheomelanin: Ptr<FloatTexture>,
hair_absorption: HairAbsorption,
eta: Ptr<FloatTexture>,
beta_m: Ptr<FloatTexture>,
beta_n: Ptr<FloatTexture>,
alpha: Ptr<FloatTexture>,
colorspace: Ptr<RGBColorSpace>,
) -> 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<T: TextureEvaluator>(
&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<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
None
}
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 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))