pbrt/src/textures/image.rs
2026-06-08 16:32:59 +01:00

287 lines
8.5 KiB
Rust

use crate::core::texture::{get_texture_cache, CreateTextureMapping, TexInfo};
use crate::core::texture::{
CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture,
SpectrumTextureTrait,
};
use crate::utils::mipmap::{MIPMap, MIPMapFilterOptions};
use crate::utils::{resolve_filename, FileLoc, TextureParameterDictionary};
use crate::Arena;
use anyhow::Result;
use shared::core::color::RGB;
use shared::core::color::{ColorEncoding, SRGBEncoding};
use shared::core::geometry::Vector2f;
use shared::core::image::{FilterFunction, WrapMode};
use shared::core::spectrum::SpectrumTrait;
use shared::core::texture::{SpectrumType, TexCoord2D, TextureEvalContext, TextureMapping2D};
use shared::spectra::{
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths,
};
use shared::utils::Transform;
use shared::Float;
use std::path::Path;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct ImageTextureBase {
pub mapping: TextureMapping2D,
pub filename: String,
pub scale: Float,
pub invert: bool,
pub mipmap: Arc<MIPMap>,
}
impl ImageTextureBase {
pub fn new(
mapping: TextureMapping2D,
filename: String,
filter_options: MIPMapFilterOptions,
wrap_mode: WrapMode,
scale: Float,
invert: bool,
encoding: ColorEncoding,
) -> Self {
let tex_info = TexInfo {
filename: filename.clone(),
filter_options,
wrap_mode,
encoding,
};
let cache_mutex = get_texture_cache();
{
let cache = cache_mutex.lock().unwrap();
if let Some(mipmap) = cache.get(&tex_info) {
return Self {
mapping,
filename,
scale,
invert,
mipmap: mipmap.clone(),
};
}
}
let path = Path::new(&filename);
let mipmap_raw = MIPMap::create_from_file(path, filter_options, wrap_mode, encoding)
.expect("Failed to create MIPMap from file");
let mipmap_arc = Arc::new(mipmap_raw);
{
let mut cache = cache_mutex.lock().unwrap();
let stored_mipmap = cache.entry(tex_info).or_insert(mipmap_arc);
Self {
mapping,
filename,
scale,
invert,
mipmap: stored_mipmap.clone(),
}
}
}
pub fn clear_cache() {
let mut cache = get_texture_cache().lock().unwrap();
cache.clear();
}
pub fn multiply_scale(&mut self, s: Float) {
self.scale *= s;
}
}
#[derive(Clone, Debug)]
pub struct SpectrumImageTexture {
pub base: ImageTextureBase,
pub spectrum_type: SpectrumType,
}
impl SpectrumImageTexture {
#[allow(clippy::too_many_arguments)]
pub fn new(
mapping: TextureMapping2D,
filename: String,
filter_options: MIPMapFilterOptions,
wrap_mode: WrapMode,
scale: Float,
invert: bool,
encoding: ColorEncoding,
spectrum_type: SpectrumType,
) -> Self {
let base = ImageTextureBase::new(
mapping,
filename,
filter_options,
wrap_mode,
scale,
invert,
encoding,
);
Self {
base,
spectrum_type,
}
}
}
impl SpectrumTextureTrait for SpectrumImageTexture {
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut c = self.base.mapping.map(ctx);
c.st[1] = 1. - c.st[1];
let dst0 = Vector2f::new(c.dsdx, c.dtdx);
let dst1 = Vector2f::new(c.dsdy, c.dtdy);
let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1);
let rgb = RGB::clamp_zero(&rgb_unclamp);
if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
match self.spectrum_type {
SpectrumType::Unbounded => {
return RGBUnboundedSpectrum::new(&cs, rgb).sample(lambda);
}
SpectrumType::Albedo => {
return RGBAlbedoSpectrum::new(&cs, rgb.clamp(0., 1.)).sample(lambda);
}
_ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda),
}
}
assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]);
SampledSpectrum::new(rgb[0])
}
}
impl CreateSpectrumTexture for SpectrumImageTexture {
fn create(
render_from_texture: Transform,
parameters: TextureParameterDictionary,
spectrum_type: SpectrumType,
loc: FileLoc,
) -> Result<SpectrumTexture> {
let mapping = TextureMapping2D::create(&parameters, &render_from_texture, &loc)?;
let filename = crate::utils::resolve_filename(&parameters.get_one_string("filename", "")?);
let scale = parameters.get_one_float("scale", 1.0)?;
let invert = parameters.get_one_bool("invert", false)?;
let filter_options = MIPMapFilterOptions::default();
let wrap_str = parameters.get_one_string("wrap", "repeat")?;
// let wrap_mode = WrapMode::parse(wrap_str)?;
let wrap_mode = match wrap_str.as_str() {
"repeat" => WrapMode::Repeat,
"clamp" => WrapMode::Clamp,
"black" => WrapMode::Black,
_ => WrapMode::Repeat,
};
let encoding = ColorEncoding::SRGB(SRGBEncoding);
let tex = SpectrumImageTexture::new(
mapping,
filename,
filter_options,
wrap_mode,
scale,
invert,
encoding,
spectrum_type,
);
Ok(SpectrumTexture::Image(tex))
}
}
#[derive(Debug, Clone)]
pub struct FloatImageTexture {
pub base: ImageTextureBase,
}
impl FloatImageTexture {
pub fn new(
mapping: TextureMapping2D,
filename: String,
filter_options: MIPMapFilterOptions,
wrap_mode: WrapMode,
scale: Float,
invert: bool,
encoding: ColorEncoding,
) -> Self {
Self {
base: ImageTextureBase::new(
mapping,
filename,
filter_options,
wrap_mode,
scale,
invert,
encoding,
),
}
}
}
impl FloatTextureTrait for FloatImageTexture {
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let mut c: TexCoord2D = self.base.mapping.map(ctx);
c.st[1] = 1. - c.st[1];
let v: Float = self.base.scale
* self.base.mipmap.filter::<Float>(
c.st,
Vector2f::new(c.dsdx, c.dtdx),
Vector2f::new(c.dsdy, c.dtdy),
);
if self.base.invert {
(1. - v).max(0.)
} else {
v
}
}
}
impl CreateFloatTexture for FloatImageTexture {
fn create(
render_from_texture: Transform,
parameters: TextureParameterDictionary,
loc: FileLoc,
_arena: &Arena,
) -> Result<FloatTexture> {
let mapping = TextureMapping2D::create(&parameters, &render_from_texture, &loc)?;
let max_aniso = parameters.get_one_float("maxanisotropy", 8.)?;
let filter = parameters.get_one_string("filter", "bilinear")?;
let mut filter_options = MIPMapFilterOptions::default();
filter_options.max_anisotropy = max_aniso;
let ff = FilterFunction::parse(&filter)?;
filter_options.filter = ff;
let wrap_string = parameters.get_one_string("wrap", "repeat")?;
let wrap_mode = WrapMode::parse(&wrap_string)?;
let scale = parameters.get_one_float("scale", 1.)?;
let invert = parameters.get_one_bool("invert", false)?;
let filename = resolve_filename(&parameters.get_one_string("filename", "")?);
let default_encoding = if Path::new(&filename)
.extension()
.map_or(false, |ext| ext == "png")
{
"sRGB"
} else {
"linear"
};
let encoding_str = parameters.get_one_string("encoding", default_encoding)?;
let encoding = ColorEncoding::from_name(&encoding_str)?;
let tex = FloatImageTexture::new(
mapping,
filename,
filter_options,
wrap_mode,
scale,
invert,
encoding,
);
Ok(FloatTexture::Image(tex))
}
}