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:
committed by
Copybara-Service
parent
92261d9095
commit
f9a00bd5b5
@@ -2986,6 +2986,7 @@ void mjv_makeLights(const mjModel* m, const mjData* d, mjvScene* scn) {
|
||||
f2f(thislight->attenuation, m->light_attenuation+3*i, 3);
|
||||
thislight->exponent = m->light_exponent[i];
|
||||
thislight->cutoff = m->light_cutoff[i];
|
||||
thislight->softness = m->light_softness[i];
|
||||
}
|
||||
|
||||
// copy colors
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3163,6 +3163,7 @@ void mjCModel::CopyTree(mjModel* m) {
|
||||
m->light_range[lid] = pl->range;
|
||||
mjuu_copyvec(m->light_attenuation+3*lid, pl->attenuation, 3);
|
||||
m->light_cutoff[lid] = pl->cutoff;
|
||||
m->light_softness[lid] = pl->softness;
|
||||
m->light_exponent[lid] = pl->exponent;
|
||||
mjuu_copyvec(m->light_ambient+3*lid, pl->ambient, 3);
|
||||
mjuu_copyvec(m->light_diffuse+3*lid, pl->diffuse, 3);
|
||||
|
||||
@@ -4594,6 +4594,11 @@ void mjCLight::Compile(void) {
|
||||
throw mjCError(this, "zero direction in light");
|
||||
}
|
||||
|
||||
// check softness range
|
||||
if (softness < 0 || softness > 1) {
|
||||
throw mjCError(this, "light softness must be in [0, 1]");
|
||||
}
|
||||
|
||||
// get targetbodyid and texid
|
||||
ResolveReferences(model);
|
||||
}
|
||||
|
||||
@@ -1495,6 +1495,7 @@
|
||||
<xs:attribute name="range" type="xs:float" default="10"/>
|
||||
<xs:attribute name="attenuation" type="float3" default="1 0 0"/>
|
||||
<xs:attribute name="cutoff" type="xs:float" default="45"/>
|
||||
<xs:attribute name="softness" type="xs:float" default="0"/>
|
||||
<xs:attribute name="exponent" type="xs:float" default="10"/>
|
||||
<xs:attribute name="ambient" type="float3"/>
|
||||
<xs:attribute name="diffuse" type="float3" default="0.7 0.7 0.7"/>
|
||||
@@ -2273,6 +2274,7 @@
|
||||
<xs:attribute name="range" type="xs:float" default="10"/>
|
||||
<xs:attribute name="attenuation" type="float3" default="1 0 0"/>
|
||||
<xs:attribute name="cutoff" type="xs:float" default="45"/>
|
||||
<xs:attribute name="softness" type="xs:float" default="0"/>
|
||||
<xs:attribute name="exponent" type="xs:float" default="10"/>
|
||||
<xs:attribute name="ambient" type="float3"/>
|
||||
<xs:attribute name="diffuse" type="float3" default="0.7 0.7 0.7"/>
|
||||
|
||||
@@ -394,6 +394,7 @@ static const mjXDefaultEntry kDefaults_mjsLight[] = {
|
||||
{"range", (int)offsetof(mjsLight, range), 1, 1, 1, 0, {10.0}},
|
||||
{"attenuation", (int)offsetof(mjsLight, attenuation), 1, 3, 3, 0, {1.0, 0.0, 0.0}},
|
||||
{"cutoff", (int)offsetof(mjsLight, cutoff), 1, 1, 1, 0, {45.0}},
|
||||
{"softness", (int)offsetof(mjsLight, softness), 1, 1, 1, 0, {0.0}},
|
||||
{"exponent", (int)offsetof(mjsLight, exponent), 1, 1, 1, 0, {10.0}},
|
||||
{"ambient", (int)offsetof(mjsLight, ambient), 1, 3, 0, 0, {0}},
|
||||
{"diffuse", (int)offsetof(mjsLight, diffuse), 1, 3, 3, 0, {0.7, 0.7, 0.7}},
|
||||
|
||||
@@ -426,6 +426,7 @@ inline constexpr mjXAttr kLightAttrs[] = {
|
||||
{"range", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, range)},
|
||||
{"attenuation", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjsLight, attenuation)},
|
||||
{"cutoff", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, cutoff)},
|
||||
{"softness", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, softness)},
|
||||
{"exponent", mjXAttr::kFloat, 1, true, false, false, false, (int)offsetof(mjsLight, exponent)},
|
||||
{"ambient", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjsLight, ambient)},
|
||||
{"diffuse", mjXAttr::kFloat, 3, true, false, false, false, (int)offsetof(mjsLight, diffuse)},
|
||||
|
||||
@@ -92,8 +92,8 @@ std::vector<const char*> MJCF[] = {
|
||||
"axisangle", "xyaxes", "zaxis", "euler", "mode", "focal", "focalpixel", "principal",
|
||||
"principalpixel", "sensorsize", "user"},
|
||||
{"light", "?", "directional", "type", "castshadow", "active", "pos", "dir", "bulbradius",
|
||||
"intensity", "range", "attenuation", "cutoff", "exponent", "ambient", "diffuse",
|
||||
"specular", "mode"},
|
||||
"intensity", "range", "attenuation", "cutoff", "softness", "exponent", "ambient",
|
||||
"diffuse", "specular", "mode"},
|
||||
{"pair", "?", "condim", "friction", "solref", "solreffriction", "solimp", "gap", "margin",
|
||||
"adhesion"},
|
||||
{"equality", "?", "active", "solref", "solimp"},
|
||||
@@ -204,8 +204,8 @@ std::vector<const char*> MJCF[] = {
|
||||
"pos", "quat", "axisangle", "xyaxes", "zaxis", "euler", "mode", "target", "focal",
|
||||
"focalpixel", "principal", "principalpixel", "sensorsize", "user"},
|
||||
{"light", "*", "name", "class", "directional", "type", "castshadow", "active", "pos",
|
||||
"dir", "bulbradius", "intensity", "range", "attenuation", "cutoff", "exponent",
|
||||
"ambient", "diffuse", "specular", "mode", "target", "texture"},
|
||||
"dir", "bulbradius", "intensity", "range", "attenuation", "cutoff", "softness",
|
||||
"exponent", "ambient", "diffuse", "specular", "mode", "target", "texture"},
|
||||
{"plugin", "*", "plugin", "instance"},
|
||||
{"<"},
|
||||
{"config", "*", "key", "value"},
|
||||
|
||||
@@ -1117,6 +1117,7 @@ element light : mjsLight {
|
||||
range : float = 10
|
||||
attenuation : float[3] = {1, 0, 0}
|
||||
cutoff : float = 45
|
||||
softness : float = 0
|
||||
exponent : float = 10
|
||||
ambient : float[3]
|
||||
diffuse : float[3] = {0.7, 0.7, 0.7}
|
||||
|
||||
Reference in New Issue
Block a user