Add light softness: spotlight edge softness for physically-based rendering.

The new spotlight attribute softness (real in [0, 1], default 0) is the
fraction of the cone, measured inward from the cutoff, over which
intensity falls to zero. It is used by physically-based lighting models;
the Phong model's corresponding knob remains exponent.

The filament renderer previously hardcoded the inner cone angle to 0,
making the entire beam penumbra: the shader attenuates by the squared
smoothstep ((cos(theta) - cos(outer)) / (cos(inner) - cos(outer)))^2, so
a cutoff-25 spot delivered its rated candela only exactly on-axis and
about a third of it averaged over the light pool, with the deficit
shrinking as the cutoff widens. The inner angle is now
(1 - softness) * cutoff, so at the default the light delivers its full
intensity everywhere inside the cone and illuminance follows E = I/d^2
independent of the cutoff. Setting softness to 1 reproduces the previous
appearance exactly (verified bit-identical), which is the migration path
for models tuned against the old behavior.

The filament light type also changes from FOCUSED_SPOT to SPOT. With
intensity given in candela and the cone set at build time the two types
produce identical output (FOCUSED_SPOT's power-conserving rescale only
applies when the cone changes after the intensity is set), but SPOT
guarantees that candela never rescales with cone angle should the cone
ever become runtime-editable.

Verified with headless renders under a linear tone mapper against an
equal-candela point light at cutoffs 25/45/80: softness 0 gives
spot/point luminance ratio 1.000 at all sampled angles inside the cone;
softness 0.2 is flat over the inner 80% of the cone; softness 1 matches
the previous renderer with zero linear-pixel difference. XML round-trip
and the [0, 1] compile-time check verified. Introspect and wasm bindings
regenerated.

PiperOrigin-RevId: 959334706
Change-Id: I0f0729781899880de1729ea9b3d8c055d715a025
This commit is contained in:
Yuval Tassa
2026-08-04 18:11:03 -07:00
committed by Copybara-Service
parent 92261d9095
commit f9a00bd5b5
27 changed files with 118 additions and 15 deletions
+10 -4
View File
@@ -60,7 +60,9 @@ Light::Light(filament::Engine* engine, const mjrfLightParams& params)
filament::LightManager::Type type;
switch (params.type) {
case mjLIGHT_SPOT:
type = filament::LightManager::Type::FOCUSED_SPOT;
// SPOT rather than FOCUSED_SPOT: MJCF intensity is candela, which must
// not rescale with the cone angle.
type = filament::LightManager::Type::SPOT;
break;
case mjLIGHT_DIRECTIONAL:
type = filament::LightManager::Type::DIRECTIONAL;
@@ -77,9 +79,13 @@ Light::Light(filament::Engine* engine, const mjrfLightParams& params)
builder.color(ReadFloat3(params.color));
builder.intensityCandela(params.intensity);
builder.castShadows(params.cast_shadows);
if (type == filament::LightManager::Type::FOCUSED_SPOT) {
builder.spotLightCone(0,
params.spot_cone_angle * std::numbers::pi / 180.0f);
if (type == filament::LightManager::Type::SPOT) {
// The light delivers its full intensity inside the inner cone, so that
// illuminance follows E = I/d^2, and falls to zero over the outer
// softness fraction of the cone angle.
const float outer = params.spot_cone_angle * std::numbers::pi / 180.0f;
const float inner = (1.0f - params.spot_softness) * outer;
builder.spotLightCone(inner, outer);
}
if (type != filament::LightManager::Type::DIRECTIONAL) {
builder.falloff(params.range);
+1
View File
@@ -85,6 +85,7 @@ void mjrf_defaultLightParams(mjrfLightParams* params) {
params->cast_shadows = true;
params->range = 10.0f;
params->spot_cone_angle = 180.f;
params->spot_softness = 0.0f;
params->bulb_radius = 0.0f;
params->shadow_map_size = 2048;
params->vsm_blur_width = 0.0f;
@@ -178,6 +178,7 @@ void ModelLights::Prepare() {
params.shadow_map_size = default_shadow_map_size;
if (params.type == mjLIGHT_SPOT) {
params.spot_cone_angle = model->light_cutoff[i];
params.spot_softness = model->light_softness[i];
}
auto light_obj = CreateLight(ctx, params);