255 lines
7.9 KiB
Rust
255 lines
7.9 KiB
Rust
use super::{
|
|
DenselySampledSpectrum, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait,
|
|
LightType, RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
|
};
|
|
use crate::core::interaction::{
|
|
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
|
};
|
|
use crate::core::medium::MediumInterface;
|
|
use crate::core::pbrt::{Float, PI};
|
|
use crate::core::texture::{
|
|
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
|
|
UniversalTextureEvaluator,
|
|
};
|
|
use crate::geometry::{
|
|
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
|
};
|
|
use crate::image::Image;
|
|
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait};
|
|
use crate::utils::color::RGB;
|
|
use crate::utils::colorspace::RGBColorSpace;
|
|
use crate::utils::hash::hash_float;
|
|
use crate::utils::transform::Transform;
|
|
|
|
use std::sync::Arc;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct DiffuseAreaLight {
|
|
base: LightBase,
|
|
shape: Shape,
|
|
alpha: Option<FloatTexture>,
|
|
area: Float,
|
|
two_sided: bool,
|
|
lemit: Arc<DenselySampledSpectrum>,
|
|
scale: Float,
|
|
image: Option<Image>,
|
|
image_color_space: Option<Arc<RGBColorSpace>>,
|
|
}
|
|
|
|
impl DiffuseAreaLight {
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn new(
|
|
render_from_light: Transform<Float>,
|
|
medium_interface: MediumInterface,
|
|
le: Spectrum,
|
|
scale: Float,
|
|
shape: Shape,
|
|
alpha: FloatTexture,
|
|
image: Option<Image>,
|
|
image_color_space: Option<Arc<RGBColorSpace>>,
|
|
two_sided: bool,
|
|
) -> Self {
|
|
let is_constant_zero = match &alpha {
|
|
FloatTexture::FloatConstant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
|
|
_ => false,
|
|
};
|
|
|
|
let (light_type, stored_alpha) = if is_constant_zero {
|
|
(LightType::DeltaPosition, None)
|
|
} else {
|
|
(LightType::Area, Some(alpha))
|
|
};
|
|
|
|
let base = LightBase::new(light_type, &render_from_light, &medium_interface);
|
|
|
|
let lemit = LightBase::lookup_spectrum(&le);
|
|
if let Some(im) = &image {
|
|
let desc = im
|
|
.get_channel_desc(&["R", "G", "B"])
|
|
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
|
|
|
|
assert_eq!(3, desc.size(), "Image channel description size mismatch");
|
|
assert!(
|
|
desc.is_identity(),
|
|
"Image channel description is not identity"
|
|
);
|
|
|
|
assert!(
|
|
image_color_space.is_some(),
|
|
"Image provided but ColorSpace is missing"
|
|
);
|
|
}
|
|
let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
|
|
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
|
|
println!(
|
|
"Scaling detected in rendering to light space transformation! \
|
|
The system has numerous assumptions, implicit and explicit, \
|
|
that this transform will have no scale factors in it. \
|
|
Proceed at your own risk; your image may have errors."
|
|
);
|
|
}
|
|
|
|
Self {
|
|
base,
|
|
area: shape.area(),
|
|
shape,
|
|
alpha: stored_alpha,
|
|
two_sided,
|
|
lemit,
|
|
scale,
|
|
image,
|
|
image_color_space,
|
|
}
|
|
}
|
|
|
|
fn alpha_masked(&self, intr: &Interaction) -> bool {
|
|
let Some(alpha_tex) = &self.alpha else {
|
|
return false;
|
|
};
|
|
let ctx = TextureEvalContext::from(intr);
|
|
let a = UniversalTextureEvaluator.evaluate_float(alpha_tex, &ctx);
|
|
if a >= 1.0 {
|
|
return false;
|
|
}
|
|
if a <= 0.0 {
|
|
return true;
|
|
}
|
|
hash_float(&intr.p()) > a
|
|
}
|
|
}
|
|
|
|
impl LightTrait for DiffuseAreaLight {
|
|
fn base(&self) -> &LightBase {
|
|
&self.base
|
|
}
|
|
|
|
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
|
let mut l = SampledSpectrum::new(0.);
|
|
if let Some(image) = &self.image {
|
|
for y in 0..image.resolution().y() {
|
|
for x in 0..image.resolution().x() {
|
|
let mut rgb = RGB::default();
|
|
for c in 0..3 {
|
|
rgb[c] = image.get_channel(Point2i::new(x, y), c);
|
|
}
|
|
l += RGBIlluminantSpectrum::new(
|
|
self.image_color_space.as_ref().unwrap(),
|
|
RGB::clamp_zero(rgb),
|
|
)
|
|
.sample(&lambda);
|
|
}
|
|
}
|
|
l *= self.scale / (image.resolution().x() * image.resolution().y()) as Float;
|
|
} else {
|
|
l = self.lemit.sample(&lambda) * self.scale;
|
|
}
|
|
let two_side = if self.two_sided { 2. } else { 1. };
|
|
PI * two_side * self.area * l
|
|
}
|
|
|
|
fn sample_li(
|
|
&self,
|
|
ctx: &LightSampleContext,
|
|
u: Point2f,
|
|
lambda: &SampledWavelengths,
|
|
_allow_incomplete_pdf: bool,
|
|
) -> Option<LightLiSample> {
|
|
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
|
|
let ss = self.shape.sample_from_context(&shape_ctx, u)?;
|
|
let mut intr: SurfaceInteraction = ss.intr.as_ref().clone();
|
|
|
|
intr.common.medium_interface = Some(self.base.medium_interface.clone());
|
|
let p = intr.p();
|
|
let n = intr.n();
|
|
let uv = intr.uv;
|
|
|
|
let generic_intr = Interaction::Surface(intr);
|
|
if self.alpha_masked(&generic_intr) {
|
|
return None;
|
|
}
|
|
|
|
let wi = (p - ctx.p()).normalize();
|
|
let le = self.l(p, n, uv, -wi, lambda);
|
|
|
|
if le.is_black() {
|
|
return None;
|
|
}
|
|
|
|
Some(LightLiSample::new(le, wi, ss.pdf, generic_intr))
|
|
}
|
|
|
|
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
|
|
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.);
|
|
self.shape.pdf_from_context(&shape_ctx, wi)
|
|
}
|
|
|
|
fn l(
|
|
&self,
|
|
p: Point3f,
|
|
n: Normal3f,
|
|
mut uv: Point2f,
|
|
w: Vector3f,
|
|
lambda: &SampledWavelengths,
|
|
) -> SampledSpectrum {
|
|
if self.two_sided && n.dot(w.into()) < 0. {
|
|
return SampledSpectrum::new(0.);
|
|
}
|
|
let intr = Interaction::Surface(SurfaceInteraction::new_minimal(
|
|
Point3fi::new_from_point(p),
|
|
uv,
|
|
));
|
|
|
|
if self.alpha_masked(&intr) {
|
|
return SampledSpectrum::new(0.);
|
|
}
|
|
if let Some(image) = &self.image {
|
|
let mut rgb = RGB::default();
|
|
uv[1] = 1. - uv[1];
|
|
for c in 0..3 {
|
|
rgb[c] = image.bilerp_channel(uv, c);
|
|
}
|
|
|
|
let spec = RGBIlluminantSpectrum::new(
|
|
self.image_color_space.as_ref().unwrap(),
|
|
RGB::clamp_zero(rgb),
|
|
);
|
|
|
|
self.scale * spec.sample(lambda)
|
|
} else {
|
|
self.scale * self.lemit.sample(lambda)
|
|
}
|
|
}
|
|
|
|
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
|
todo!()
|
|
}
|
|
|
|
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn bounds(&self) -> Option<LightBounds> {
|
|
let mut phi = 0.;
|
|
if let Some(image) = &self.image {
|
|
for y in 0..image.resolution.y() {
|
|
for x in 0..image.resolution.x() {
|
|
for c in 0..3 {
|
|
phi += image.get_channel(Point2i::new(x, y), c);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
phi = self.lemit.max_value();
|
|
}
|
|
|
|
let nb = self.shape.normal_bounds();
|
|
Some(LightBounds::new(
|
|
&self.shape.bounds(),
|
|
nb.w,
|
|
phi,
|
|
nb.cos_theta,
|
|
(PI / 2.).cos(),
|
|
self.two_sided,
|
|
))
|
|
}
|
|
}
|