Finishing material creators
This commit is contained in:
parent
6e23698e2d
commit
28bb963268
9 changed files with 263 additions and 48 deletions
|
|
@ -5,7 +5,7 @@ use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction}
|
|||
use crate::core::shape::Shape;
|
||||
use crate::core::{LightIdx, MaterialIdx};
|
||||
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
|
||||
use crate::utils::math::{catmull_rom_weights, square};
|
||||
use crate::utils::math::{catmull_rom_weights, invert_catmull_rom, square};
|
||||
use crate::utils::sampling::sample_catmull_rom_2d;
|
||||
use crate::{Float, GVec, PI, Ptr, gvec_with_capacity};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
|
@ -147,6 +147,22 @@ impl BSSRDFTable {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn subsurface_from_diffuse(
|
||||
t: &BSSRDFTable,
|
||||
rho_eff: &SampledSpectrum,
|
||||
mfp: &SampledSpectrum,
|
||||
) -> (SampledSpectrum, SampledSpectrum) {
|
||||
// (sigma_a, sigma_s)
|
||||
let mut sigma_a = SampledSpectrum::zero();
|
||||
let mut sigma_s = SampledSpectrum::zero();
|
||||
for c in 0..N_SPECTRUM_SAMPLES {
|
||||
let rho = invert_catmull_rom(&t.rho_samples, &t.rho_eff, rho_eff[c]);
|
||||
sigma_s[c] = rho / mfp[c];
|
||||
sigma_a[c] = (1. - rho) / mfp[c];
|
||||
}
|
||||
(sigma_a, sigma_s)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct BSSRDFProbeSegment {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,15 @@ pub enum Spectrum {
|
|||
RGBUnbounded(RGBUnboundedSpectrum),
|
||||
}
|
||||
|
||||
/// `enum_dispatch` already generates `From<Variant> for Spectrum`, so wrapping a
|
||||
/// `ConstantSpectrum` etc. is `.into()`. Only the plain-`Float` hop is missing,
|
||||
/// and it is the one written most often at default-value sites.
|
||||
impl From<Float> for Spectrum {
|
||||
fn from(c: Float) -> Self {
|
||||
Spectrum::Constant(ConstantSpectrum::new(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SpectrumTrait> SpectrumTrait for Ptr<T> {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
self.get().unwrap().evaluate(lambda)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::bxdfs::{
|
|||
MeasuredBxDF, MeasuredBxDFData,
|
||||
};
|
||||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bssrdf::{BSSRDF, BSSRDFTable};
|
||||
use crate::core::bssrdf::{BSSRDF, BSSRDFTable, TabulatedBSSRDF, subsurface_from_diffuse};
|
||||
use crate::core::bxdf::BxDF;
|
||||
use crate::core::image::Image;
|
||||
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
|
|
@ -118,12 +118,30 @@ impl MaterialTrait for HairMaterial {
|
|||
None
|
||||
}
|
||||
|
||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||
todo!()
|
||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
|
||||
match self.hair_absorption {
|
||||
HairAbsorption::SigmaA(t) | HairAbsorption::Color(t) => {
|
||||
tex_eval.can_evaluate(&[self.eta, self.beta_m, self.beta_n, self.alpha], &[t])
|
||||
}
|
||||
HairAbsorption::Melanin {
|
||||
eumelanin,
|
||||
pheomelanin,
|
||||
} => tex_eval.can_evaluate(
|
||||
&[
|
||||
self.eta,
|
||||
self.beta_m,
|
||||
self.beta_n,
|
||||
self.alpha,
|
||||
eumelanin,
|
||||
pheomelanin,
|
||||
],
|
||||
&[],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
todo!()
|
||||
None
|
||||
}
|
||||
|
||||
fn get_displacement(&self) -> Ptr<FloatTexture> {
|
||||
|
|
@ -210,31 +228,78 @@ pub struct SubsurfaceMaterial {
|
|||
impl MaterialTrait for SubsurfaceMaterial {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
&self,
|
||||
_tex_eval: &T,
|
||||
_ctx: &MaterialEvalContext,
|
||||
tex_eval: &T,
|
||||
ctx: &MaterialEvalContext,
|
||||
_lambda: &mut SampledWavelengths,
|
||||
) -> BSDF {
|
||||
todo!()
|
||||
}
|
||||
fn get_bssrdf<T>(
|
||||
&self,
|
||||
_tex_eval: &T,
|
||||
_ctx: &MaterialEvalContext,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> Option<BSSRDF> {
|
||||
todo!()
|
||||
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);
|
||||
}
|
||||
|
||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||
todo!()
|
||||
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
|
||||
let bxdf = BxDF::Dielectric(DielectricBxDF::new(self.eta, distrib));
|
||||
BSDF::new(ctx.ns, ctx.dpdus, bxdf)
|
||||
}
|
||||
|
||||
fn get_bssrdf<T: TextureEvaluator>(
|
||||
&self,
|
||||
tex_eval: &T,
|
||||
ctx: &MaterialEvalContext,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> Option<BSSRDF> {
|
||||
let (sig_a, sig_s) = match self.scattering {
|
||||
SubsurfaceScattering::Coefficients { sigma_a, sigma_s } => {
|
||||
let s_a = SampledSpectrum::clamp_zero(
|
||||
&(self.scale * tex_eval.evaluate_spectrum(&sigma_a, ctx, lambda)),
|
||||
);
|
||||
let s_s = SampledSpectrum::clamp_zero(
|
||||
&(self.scale * tex_eval.evaluate_spectrum(&sigma_s, ctx, lambda)),
|
||||
);
|
||||
(s_a, s_s)
|
||||
}
|
||||
SubsurfaceScattering::Reflectance { reflectance, mfp } => {
|
||||
debug_assert!(!reflectance.is_null() && !mfp.is_null());
|
||||
let mfree =
|
||||
SampledSpectrum::clamp_zero(&tex_eval.evaluate_spectrum(&mfp, ctx, lambda));
|
||||
let r = SampledSpectrum::clamp_zero(&tex_eval.evaluate_spectrum(
|
||||
&reflectance,
|
||||
ctx,
|
||||
lambda,
|
||||
));
|
||||
subsurface_from_diffuse(&self.table, &r, &mfree)
|
||||
}
|
||||
};
|
||||
|
||||
Some(BSSRDF::Tabulated(TabulatedBSSRDF::new(
|
||||
ctx.p,
|
||||
ctx.wo,
|
||||
ctx.ns,
|
||||
self.eta,
|
||||
&sig_a,
|
||||
&sig_s,
|
||||
&self.table,
|
||||
)))
|
||||
}
|
||||
|
||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
|
||||
// Slight divergence from PBRT, we check against reflectance and mfp as well in reflectance
|
||||
// mode. Test thoroughly, keep as is for now (20260902)
|
||||
let spectra = match self.scattering {
|
||||
SubsurfaceScattering::Coefficients { sigma_a, sigma_s } => [sigma_a, sigma_s],
|
||||
SubsurfaceScattering::Reflectance { reflectance, mfp } => [reflectance, mfp],
|
||||
};
|
||||
tex_eval.can_evaluate(&[self.u_roughness, self.v_roughness], &spectra)
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
todo!()
|
||||
Some(&*self.normal_map)
|
||||
}
|
||||
|
||||
fn get_displacement(&self) -> Ptr<FloatTexture> {
|
||||
todo!()
|
||||
self.displacement
|
||||
}
|
||||
|
||||
fn has_subsurface_scattering(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub struct ConductorMaterial {
|
|||
}
|
||||
|
||||
impl ConductorMaterial {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
normal_map: Ptr<Image>,
|
||||
reflectance: Ptr<SpectrumTexture>,
|
||||
|
|
@ -92,7 +93,7 @@ impl MaterialTrait for ConductorMaterial {
|
|||
_ctx: &MaterialEvalContext,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> Option<BSSRDF> {
|
||||
todo!()
|
||||
None
|
||||
}
|
||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
|
||||
tex_eval.can_evaluate(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::core::color::{RGB, XYZ};
|
||||
use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
|
||||
use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4};
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::utils::hash::{hash_buffer, mix_bits};
|
||||
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
|
||||
use crate::utils::{find_interval, gpu_array_from_fn};
|
||||
use crate::{GVec, Ptr, gvec, gvec_with_capacity};
|
||||
use core::fmt::{self, Display, Write};
|
||||
use core::iter::{Product, Sum};
|
||||
|
|
@ -379,6 +379,61 @@ pub fn integrate_catmull_rom(nodes: &[Float], f: &[Float], cdf: &mut [Float]) ->
|
|||
sum
|
||||
}
|
||||
|
||||
pub fn invert_catmull_rom(nodes: &[Float], f: &[Float], u: Float) -> Float {
|
||||
// Stop when _u_ is out of bounds
|
||||
if !(u > f[0]) {
|
||||
return nodes[0];
|
||||
} else if !(u < f[f.len() - 1]) {
|
||||
return nodes[nodes.len() - 1];
|
||||
}
|
||||
|
||||
// Map _u_ to a spline interval by inverting _f_
|
||||
let i = find_interval(f.len() as u32, |j| f[j as usize] <= u) as usize;
|
||||
|
||||
// Look up $x_i$ and function values of spline segment _i_
|
||||
let x0 = nodes[i];
|
||||
let x1 = nodes[i + 1];
|
||||
let f0 = f[i];
|
||||
let f1 = f[i + 1];
|
||||
let width = x1 - x0;
|
||||
|
||||
// Approximate derivatives using finite differences
|
||||
let d0 = if i > 0 {
|
||||
width * (f1 - f[i - 1]) / (x1 - nodes[i - 1])
|
||||
} else {
|
||||
f1 - f0
|
||||
};
|
||||
let d1 = if i + 2 < nodes.len() {
|
||||
width * (f[i + 2] - f0) / (nodes[i + 2] - x0)
|
||||
} else {
|
||||
f1 - f0
|
||||
};
|
||||
|
||||
// Invert the spline interpolant using Newton-Bisection
|
||||
let eval = |t: Float| -> (Float, Float) {
|
||||
// Compute powers of _t_
|
||||
let t2 = t * t;
|
||||
let t3 = t2 * t;
|
||||
|
||||
// Set _Fhat_ using Equation (\ref{eq:cubicspline-as-basisfunctions})
|
||||
let f_cap_hat = (2. * t3 - 3. * t2 + 1.) * f0
|
||||
+ (-2. * t3 + 3. * t2) * f1
|
||||
+ (t3 - 2. * t2 + t) * d0
|
||||
+ (t3 - t2) * d1;
|
||||
|
||||
// Set _fhat_ using Equation (\ref{eq:cubicspline-derivative})
|
||||
let f_hat = (6. * t2 - 6. * t) * f0
|
||||
+ (-6. * t2 + 6. * t) * f1
|
||||
+ (3. * t2 - 4. * t + 1.) * d0
|
||||
+ (3. * t2 - 2. * t) * d1;
|
||||
|
||||
return (f_cap_hat - u, f_hat);
|
||||
};
|
||||
|
||||
let t = newton_bisection(0., 1., eval);
|
||||
return x0 + t * width;
|
||||
}
|
||||
|
||||
pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4])> {
|
||||
if nodes.len() < 4 {
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use shared::Float;
|
|||
use shared::core::color::ColorEncoding;
|
||||
use shared::core::geometry::Vector3f;
|
||||
use shared::core::image::WrapMode;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
use shared::core::texture::{
|
||||
CylindricalMapping, PlanarMapping, PointTransformMapping, SphericalMapping, TextureEvalContext,
|
||||
|
|
@ -95,6 +96,21 @@ pub enum SpectrumTexture {
|
|||
DirectionMix(SpectrumDirectionMixTexture),
|
||||
}
|
||||
|
||||
/// A bare `Spectrum` used as a texture is always a constant texture. Saves
|
||||
/// writing `SpectrumTexture::Constant(SpectrumConstantTexture::new(s))` at every
|
||||
/// default-value site.
|
||||
impl From<Spectrum> for SpectrumTexture {
|
||||
fn from(s: Spectrum) -> Self {
|
||||
SpectrumTexture::Constant(SpectrumConstantTexture::new(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Float> for FloatTexture {
|
||||
fn from(v: Float) -> Self {
|
||||
FloatTexture::Constant(FloatConstantTexture::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CreateSpectrumTexture {
|
||||
fn create(
|
||||
render_from_texture: Transform,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use crate::core::texture::SpectrumTexture;
|
|||
use crate::globals::get_options;
|
||||
use crate::spectra::data::get_named_spectrum;
|
||||
use crate::utils::TextureParameterDictionary;
|
||||
use crate::{Arena, FileLoc, ArenaUpload};
|
||||
use anyhow::{bail, Result};
|
||||
use crate::{Arena, ArenaUpload, FileLoc};
|
||||
use anyhow::{Result, bail};
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
|
|
@ -36,12 +36,13 @@ impl CreateMaterial for CoatedDiffuseMaterial {
|
|||
parameters.get_float_texture_with_fallback("vroughness", "roughness", 0.5)?;
|
||||
|
||||
let thickness = parameters.get_float_texture("thickness", 0.01)?;
|
||||
let eta = parameters
|
||||
.get_float_array("eta")?
|
||||
.first()
|
||||
.map(|&v| Spectrum::Constant(ConstantSpectrum::new(v)))
|
||||
.or_else(|| parameters.get_one_spectrum("eta", None, SpectrumType::Unbounded))
|
||||
.unwrap_or_else(|| Spectrum::Constant(ConstantSpectrum::new(1.5)));
|
||||
let eta = if let Some(&v) = parameters.get_float_array("eta")?.first() {
|
||||
Spectrum::from(v)
|
||||
} else {
|
||||
parameters
|
||||
.get_one_spectrum("eta", None, SpectrumType::Unbounded)
|
||||
.unwrap_or_else(|| Spectrum::from(1.5))
|
||||
};
|
||||
|
||||
let max_depth = parameters.get_one_int("maxdepth", 10)?;
|
||||
let n_samples = parameters.get_one_int("nsamples", 1)?;
|
||||
|
|
|
|||
|
|
@ -297,6 +297,14 @@ impl CreateMaterial for SubsurfaceMaterial {
|
|||
}
|
||||
}
|
||||
|
||||
// fn brdf_data_from_filename(filename: String) -> Ptr<MeasuredBxDFData> {
|
||||
// static std::map<std::string, MeasuredBxDFData *> loadedData;
|
||||
// if (loadedData.find(filename) == loadedData.end())
|
||||
// loadedData[filename] = MeasuredBxDFData::Create(filename, alloc);
|
||||
// return loadedData[filename];
|
||||
//
|
||||
// }
|
||||
|
||||
impl CreateMaterial for MeasuredMaterial {
|
||||
fn create(
|
||||
parameters: &TextureParameterDictionary,
|
||||
|
|
@ -305,12 +313,14 @@ impl CreateMaterial for MeasuredMaterial {
|
|||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Material> {
|
||||
let filename = resolve_filename(parameters.get_one_string("filename", "")?);
|
||||
let displacement = parameters.get_float_texture_or_null("displacement")?;
|
||||
let brdf = MeasuredBxDF::brdf_data_from_file(filename);
|
||||
let mat = MeasuredMaterial {
|
||||
displacement: arena.upload(displacement),
|
||||
normal_map: arena.upload(normal_map)
|
||||
}
|
||||
// let filename = resolve_filename(parameters.get_one_string("filename", "")?);
|
||||
// let displacement = parameters.get_float_texture_or_null("displacement")?;
|
||||
// let brdf = MeasuredBxDF::brdf_data_from_file(filename);
|
||||
// let mat = MeasuredMaterial {
|
||||
// displacement: arena.upload(displacement),
|
||||
// normal_map: arena.upload(normal_map)
|
||||
// brdf
|
||||
// }
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,75 @@
|
|||
use crate::Arena;
|
||||
use crate::core::image::HostImage;
|
||||
use crate::core::material::CreateMaterial;
|
||||
use crate::utils::{FileLoc, TextureParameterDictionary};
|
||||
use crate::{Arena, ArenaUpload};
|
||||
use anyhow::Result;
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
use shared::materials::{DielectricMaterial, ThinDielectricMaterial};
|
||||
use shared::spectra::ConstantSpectrum;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl CreateMaterial for DielectricMaterial {
|
||||
fn create(
|
||||
_parameters: &TextureParameterDictionary,
|
||||
_normal_map: Option<Arc<HostImage>>,
|
||||
parameters: &TextureParameterDictionary,
|
||||
normal_map: Option<Arc<HostImage>>,
|
||||
_named_materials: &HashMap<String, Material>,
|
||||
_loc: &FileLoc,
|
||||
_arena: &Arena,
|
||||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Material> {
|
||||
todo!()
|
||||
let eta = if let Some(&v) = parameters.get_float_array("eta")?.first() {
|
||||
Spectrum::from(v)
|
||||
} else {
|
||||
parameters
|
||||
.get_one_spectrum("eta", None, SpectrumType::Unbounded)
|
||||
.unwrap_or_else(|| Spectrum::from(1.5))
|
||||
};
|
||||
|
||||
let u_roughness =
|
||||
parameters.get_float_texture_with_fallback("uroughness", "roughness", 0.)?;
|
||||
let v_roughness =
|
||||
parameters.get_float_texture_with_fallback("vroughness", "roughness", 0.)?;
|
||||
let displacement = parameters.get_float_texture_or_null("displacement")?;
|
||||
let remap_roughness = parameters.get_one_bool("remaproughness", true)?;
|
||||
|
||||
let mat = DielectricMaterial {
|
||||
normal_map: arena.upload(normal_map),
|
||||
displacement: arena.upload(displacement),
|
||||
u_roughness: arena.upload(u_roughness),
|
||||
v_roughness: arena.upload(v_roughness),
|
||||
eta: arena.alloc(eta),
|
||||
remap_roughness,
|
||||
};
|
||||
|
||||
Ok(Material::Dielectric(mat))
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateMaterial for ThinDielectricMaterial {
|
||||
fn create(
|
||||
_parameters: &TextureParameterDictionary,
|
||||
_normal_map: Option<Arc<HostImage>>,
|
||||
parameters: &TextureParameterDictionary,
|
||||
normal_map: Option<Arc<HostImage>>,
|
||||
_named_materials: &HashMap<String, Material>,
|
||||
_loc: &FileLoc,
|
||||
_arena: &Arena,
|
||||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Material> {
|
||||
todo!()
|
||||
let eta = if let Some(&v) = parameters.get_float_array("eta")?.first() {
|
||||
Spectrum::from(v)
|
||||
} else {
|
||||
parameters
|
||||
.get_one_spectrum("eta", None, SpectrumType::Unbounded)
|
||||
.unwrap_or_else(|| Spectrum::from(1.5))
|
||||
};
|
||||
|
||||
let displacement = parameters.get_float_texture_or_null("displacement")?;
|
||||
let mat = ThinDielectricMaterial {
|
||||
displacement: arena.upload(displacement),
|
||||
normal_map: arena.upload(normal_map),
|
||||
eta: arena.alloc(eta),
|
||||
};
|
||||
|
||||
Ok(Material::ThinDielectric(mat))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue