use super::{EnvironmentSource, LightingModel, LightingPreset, LightingRigParams};
use glam::Vec3;
#[inline]
pub(in super::super) fn sample_environment_channel(
environment: EnvironmentSource<'_>,
dir: Vec3,
lambda_nm: f32,
studio_rig: Option<&crate::optics::studio_rig::StudioRig>,
observer: Vec3,
) -> f32 {
match environment {
EnvironmentSource::Studio {
preset, exposure, ..
} => {
let rig = studio_rig
.expect("sample_environment_channel: Studio environment needs a pre-built rig");
sample_studio_environment_with_rig(dir, lambda_nm, preset, exposure, rig, observer)
}
EnvironmentSource::HdrMap(map) => map.radiance_at(dir, lambda_nm),
}
}
pub(in super::super) fn fill_backdrop<const N: usize>(
environment: EnvironmentSource<'_>,
lambdas: &[f32; N],
radiance: &mut [f32; N],
) -> bool {
match environment {
EnvironmentSource::Studio {
preset, backdrop, ..
} if backdrop > 0.0 => {
for (out, &lambda_nm) in radiance.iter_mut().zip(lambdas) {
*out = backdrop * preset.spectral_power(lambda_nm);
}
true
}
_ => false,
}
}
pub(in super::super) struct EnvNeeSample {
pub(in super::super) dir: Vec3,
pub(in super::super) pdf: f32,
pub(in super::super) rgb: [f32; 3],
}
#[must_use]
pub(in super::super) fn sample_environment_for_nee(
environment: EnvironmentSource<'_>,
u0: f32,
u1: f32,
) -> Option<EnvNeeSample> {
match environment {
EnvironmentSource::Studio { .. } => None,
EnvironmentSource::HdrMap(map) => {
let (dir, rgb, pdf) = map.sample(u0, u1);
(pdf > 0.0).then_some(EnvNeeSample { dir, pdf, rgb })
}
}
}
#[must_use]
pub(in super::super) fn environment_nee_pdf(environment: EnvironmentSource<'_>, dir: Vec3) -> f32 {
match environment {
EnvironmentSource::Studio { .. } => 0.0,
EnvironmentSource::HdrMap(map) => map.pdf(dir),
}
}
const HEAD_SHADOW_OUTER_COS: f32 = 0.951_056_5;
const HEAD_SHADOW_INNER_COS: f32 = 0.970_295_7;
const SUN_OUTER_COS: f32 = 0.997_564_1;
const SUN_INNER_COS: f32 = 0.999_390_8;
const TENT_KEY_OUTER_COS: f32 = 0.766_044_4;
const TENT_KEY_INNER_COS: f32 = 0.939_692_6;
const SPARK_OUTER_COS: f32 = 0.996_194_7;
const SPARK_INNER_COS: f32 = 0.999_390_8;
const CARD_OUTER_COS: f32 = 0.898_794;
const CARD_INNER_COS: f32 = 0.961_261_7;
const CARD_RING_SLOTS: [usize; 3] = [4, 8, 12];
fn smoothstep(e0: f32, e1: f32, x: f32) -> f32 {
let t = ((x - e0) / (e1 - e0)).clamp(0.0, 1.0);
t * t * (-2.0f32).mul_add(t, 3.0)
}
fn observer_visibility(d: Vec3, observer: Vec3) -> f32 {
1.0 - smoothstep(
HEAD_SHADOW_OUTER_COS,
HEAD_SHADOW_INNER_COS,
d.dot(observer),
)
}
fn horizon_blend(d: Vec3) -> f32 {
smoothstep(-0.05, 0.05, d.y)
}
fn sample_iso_hemisphere(d: Vec3, spec_power: f32, exposure: f32, observer: Vec3) -> f32 {
(horizon_blend(d) * observer_visibility(d, observer)) * (spec_power * exposure)
}
fn sample_light_tent(
d: Vec3,
spec_power: f32,
spot_mult: f32,
exposure: f32,
rig: &crate::optics::studio_rig::StudioRig,
observer: Vec3,
) -> f32 {
let horizon = horizon_blend(d);
let mut walls = 0.08f32.mul_add(d.y.max(0.0), 0.14);
let mut card = 0.0f32;
for slot in CARD_RING_SLOTS {
card = card.max(smoothstep(
CARD_OUTER_COS,
CARD_INNER_COS,
d.dot(rig.ring_dirs[slot]),
));
}
walls *= card.mul_add(-0.9, 1.0);
let key =
smoothstep(TENT_KEY_OUTER_COS, TENT_KEY_INNER_COS, d.dot(rig.key_dir)) * (1.4 * spot_mult);
let spark =
smoothstep(SPARK_OUTER_COS, SPARK_INNER_COS, d.dot(rig.fill_dir)) * (5.0 * spot_mult);
let above = ((walls + key) + spark) * (horizon * observer_visibility(d, observer));
let ground = 0.02 * (1.0 - horizon);
(above + ground) * (spec_power * exposure)
}
fn sample_daylight_dome(
d: Vec3,
spec_power: f32,
exposure: f32,
rig: &crate::optics::studio_rig::StudioRig,
observer: Vec3,
) -> f32 {
let horizon = horizon_blend(d);
let sun_dot = d.dot(rig.key_dir);
let sky = 0.08f32.mul_add(1.0 - d.y.max(0.0), 0.10);
let glow = sun_dot.max(0.0);
let glow2 = glow * glow;
let glow4 = glow2 * glow2;
let aureole = (glow4 * glow4) * 0.30;
let sun = smoothstep(SUN_OUTER_COS, SUN_INNER_COS, sun_dot) * 10.0;
let above = ((sky + aureole) + sun) * (horizon * observer_visibility(d, observer));
let ground = 0.04 * (1.0 - horizon);
(above + ground) * (spec_power * exposure)
}
fn sample_studio_rig(
d: Vec3,
spec_power: f32,
spot_mult: f32,
exposure: f32,
rig: &crate::optics::studio_rig::StudioRig,
) -> f32 {
let bg_val = 0.012f32.mul_add(d.y.mul_add(0.5, 0.5), 0.015).max(0.005) * exposure;
let mut radiance = bg_val * spec_power;
let key_dot = d.dot(rig.key_dir).max(0.0);
if key_dot > 0.0 {
let softbox = key_dot.powi(28) * 12.0 * spot_mult * exposure;
radiance = softbox.mul_add(spec_power, radiance);
}
let fill_dot = d.dot(rig.fill_dir).max(0.0);
if fill_dot > 0.0 {
let fill = fill_dot.powi(18) * 4.5 * exposure;
radiance = fill.mul_add(spec_power, radiance);
}
for ring_dir in rig.ring_dirs {
let ring_dot = d.dot(ring_dir).max(0.0);
if ring_dot > 0.96 {
let spark = (ring_dot - 0.96) / 0.04;
let intensity = spark.powi(6) * 22.0 * spot_mult * exposure;
radiance = intensity.mul_add(spec_power, radiance);
}
}
radiance
}
#[must_use]
pub(super) fn sample_studio_environment_with_rig(
dir: Vec3,
lambda_nm: f32,
lighting_preset: LightingPreset,
exposure: f32,
rig: &crate::optics::studio_rig::StudioRig,
observer: Vec3,
) -> f32 {
let d = dir.normalize();
let LightingRigParams { spot_mult, .. } = lighting_preset.params();
let spec_power = lighting_preset.spectral_power(lambda_nm);
match lighting_preset.model() {
LightingModel::Studio => sample_studio_rig(d, spec_power, spot_mult, exposure, rig),
LightingModel::IsoHemisphere => sample_iso_hemisphere(d, spec_power, exposure, observer),
LightingModel::LightTent => {
sample_light_tent(d, spec_power, spot_mult, exposure, rig, observer)
}
LightingModel::DaylightDome => sample_daylight_dome(d, spec_power, exposure, rig, observer),
}
}