use super::color::illuminant_white_balance;
use crate::renderer::env_map::EnvironmentMap;
use glam::Vec3;
mod rig;
mod spectral;
#[cfg(test)]
mod tests;
pub use rig::sample_studio_environment_with_rig;
pub(super) use rig::{
environment_nee_pdf, fill_backdrop, sample_environment_channel, sample_environment_channels,
sample_environment_for_nee,
};
use spectral::{BlackbodyNorm, IlluminantSpectrum};
pub use spectral::{blackbody_spectrum, d65_relative_spectral_power};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LightingRigParams {
pub temp_k: f32,
pub spot_mult: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LightingPreset {
#[default]
Daylight,
Incandescent,
RingLights,
DarkSpotlight,
IsoHemisphere,
LightTent,
DaylightDome,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LightingModel {
Studio,
IsoHemisphere,
LightTent,
DaylightDome,
}
impl LightingModel {
#[must_use]
pub const fn gpu_id(self) -> u32 {
match self {
Self::Studio => 0,
Self::IsoHemisphere => 1,
Self::LightTent => 2,
Self::DaylightDome => 3,
}
}
}
impl LightingPreset {
pub const ALL: [Self; 7] = [
Self::Daylight,
Self::Incandescent,
Self::RingLights,
Self::DarkSpotlight,
Self::IsoHemisphere,
Self::LightTent,
Self::DaylightDome,
];
#[must_use]
pub const fn params(self) -> LightingRigParams {
match self {
Self::Incandescent => LightingRigParams {
temp_k: 3200.0,
spot_mult: 1.2,
},
Self::RingLights => LightingRigParams {
temp_k: 5000.0,
spot_mult: 1.6,
},
Self::DarkSpotlight => LightingRigParams {
temp_k: 6000.0,
spot_mult: 2.4,
},
Self::LightTent => LightingRigParams {
temp_k: 5000.0,
spot_mult: 1.0,
},
Self::Daylight | Self::IsoHemisphere | Self::DaylightDome => LightingRigParams {
temp_k: 6500.0,
spot_mult: 1.0,
},
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Daylight => "D65 Daylight (6500K)",
Self::Incandescent => "Incandescent (3200K)",
Self::RingLights => "Gem Studio Ring Lights",
Self::DarkSpotlight => "Dramatic Dark Spotlight",
Self::IsoHemisphere => "ISO hemisphere",
Self::LightTent => "Light tent + black cards",
Self::DaylightDome => "Daylight sky + sun",
}
}
#[must_use]
pub fn from_label(label: &str) -> Self {
match label {
"Incandescent (3200K)" => Self::Incandescent,
"Gem Studio Ring Lights" => Self::RingLights,
"Dramatic Dark Spotlight" => Self::DarkSpotlight,
"ISO hemisphere" | "ISO hemisphere (GemRay-style)" => Self::IsoHemisphere,
"Light tent + black cards" | "Soft dome + ring lights" => Self::LightTent,
"Daylight sky + sun" | "Daylight dome + sun" => Self::DaylightDome,
_ => Self::Daylight,
}
}
#[must_use]
pub const fn index(self) -> i32 {
match self {
Self::Daylight => 0,
Self::Incandescent => 1,
Self::RingLights => 2,
Self::DarkSpotlight => 3,
Self::IsoHemisphere => 4,
Self::LightTent => 5,
Self::DaylightDome => 6,
}
}
#[must_use]
pub const fn from_index(index: i32) -> Self {
match index {
1 => Self::Incandescent,
2 => Self::RingLights,
3 => Self::DarkSpotlight,
4 => Self::IsoHemisphere,
5 => Self::LightTent,
6 => Self::DaylightDome,
_ => Self::Daylight,
}
}
#[must_use]
pub const fn model(self) -> LightingModel {
match self {
Self::Daylight | Self::Incandescent | Self::RingLights | Self::DarkSpotlight => {
LightingModel::Studio
}
Self::IsoHemisphere => LightingModel::IsoHemisphere,
Self::LightTent => LightingModel::LightTent,
Self::DaylightDome => LightingModel::DaylightDome,
}
}
#[must_use]
pub const fn uses_d65(self) -> bool {
matches!(
self,
Self::Daylight | Self::IsoHemisphere | Self::DaylightDome
)
}
#[must_use]
pub fn spectral_power(self, lambda_nm: f32) -> f32 {
self.illuminant_spectrum().power(lambda_nm)
}
fn illuminant_spectrum(self) -> IlluminantSpectrum {
if self.uses_d65() {
IlluminantSpectrum::D65
} else {
IlluminantSpectrum::Blackbody(BlackbodyNorm::new(self.params().temp_k))
}
}
#[must_use]
pub const fn studio(
self,
exposure: f32,
light_yaw: f32,
light_pitch: f32,
) -> EnvironmentSource<'static> {
EnvironmentSource::Studio {
preset: self,
exposure,
light_yaw,
light_pitch,
backdrop: 0.0,
}
}
}
#[derive(Clone, Copy)]
pub enum EnvironmentSource<'a> {
Studio {
preset: LightingPreset,
exposure: f32,
light_yaw: f32,
light_pitch: f32,
backdrop: f32,
},
HdrMap(&'a EnvironmentMap),
}
pub const BACKDROP_GREY: f32 = 0.23;
pub const BACKDROP_WHITE: f32 = 8.0;
impl EnvironmentSource<'_> {
#[must_use]
pub const fn with_backdrop(self, backdrop: f32) -> Self {
match self {
Self::Studio {
preset,
exposure,
light_yaw,
light_pitch,
..
} => Self::Studio {
preset,
exposure,
light_yaw,
light_pitch,
backdrop,
},
hdr @ Self::HdrMap(_) => hdr,
}
}
}
#[inline]
pub(crate) fn environment_white_balance(environment: EnvironmentSource<'_>) -> Vec3 {
match environment {
EnvironmentSource::Studio { preset, .. } => illuminant_white_balance(preset),
EnvironmentSource::HdrMap(_) => Vec3::ONE,
}
}
#[must_use]
pub fn sample_studio_environment(
dir: Vec3,
lambda_nm: f32,
lighting_preset: LightingPreset,
exposure: f32,
light_yaw: f32,
light_pitch: f32,
) -> f32 {
sample_studio_environment_observed(
dir,
lambda_nm,
lighting_preset,
exposure,
light_yaw,
light_pitch,
Vec3::ZERO,
)
}
#[must_use]
pub fn sample_studio_environment_observed(
dir: Vec3,
lambda_nm: f32,
lighting_preset: LightingPreset,
exposure: f32,
light_yaw: f32,
light_pitch: f32,
observer: Vec3,
) -> f32 {
let rig = crate::optics::studio_rig::StudioRig::new(light_yaw, light_pitch);
sample_studio_environment_with_rig(dir, lambda_nm, lighting_preset, exposure, &rig, observer)
}