Add Filament lighting and shadow features and fixes.
- Add a hidden directional light to Filament scenes with no lights to work around soft shadow blackout bug. - Respect quality/shadowsize as default shadow map resolution. - Interpret light/bulbradius in correct units for each shadow type and update XML reference docs. - Support updating the shadow map resolution of live Filament lights. - Clamp light intensities to non-negative values in the Filament debug panel. PiperOrigin-RevId: 957305808 Change-Id: Ic7a3430aaa3b117be7fbb7ace836d68e54cdf65a
This commit is contained in:
committed by
Copybara-Service
parent
0ca53dacf8
commit
7bc1aa9b05
@@ -3429,6 +3429,15 @@ Enables or disables the light.
|
|||||||
|
|
||||||
Sets the intensity of the light, in candela.
|
Sets the intensity of the light, in candela.
|
||||||
|
|
||||||
|
.. _mjrf_setLightShadowMapSize:
|
||||||
|
|
||||||
|
`mjrf_setLightShadowMapSize <#mjrf_setLightShadowMapSize>`__
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjrf_setLightShadowMapSize
|
||||||
|
|
||||||
|
Sets the resolution of the light's shadow map, in texels.
|
||||||
|
|
||||||
.. _mjrf_setLightColor:
|
.. _mjrf_setLightColor:
|
||||||
|
|
||||||
`mjrf_setLightColor <#mjrf_setLightColor>`__
|
`mjrf_setLightColor <#mjrf_setLightColor>`__
|
||||||
|
|||||||
@@ -3301,8 +3301,8 @@ Attributes may be applied or ignored depending on the lighting model being used.
|
|||||||
.. _body-light-bulbradius:
|
.. _body-light-bulbradius:
|
||||||
|
|
||||||
:at:`bulbradius`: :at-val:`real, "0.02"`
|
:at:`bulbradius`: :at-val:`real, "0.02"`
|
||||||
The radius of the light source which can affect shadow softness depending on the
|
The radius of the light-emitting surface. Larger radii produce softer shadows in renderers that
|
||||||
renderer. This only applies to spotlights.
|
support soft shadows. Ignored by the classic renderer.
|
||||||
|
|
||||||
.. _body-light-attenuation:
|
.. _body-light-attenuation:
|
||||||
|
|
||||||
|
|||||||
@@ -3442,6 +3442,7 @@ mjrfLight* mjrf_createLight(mjrfContext* ctx, const mjrfLightParams* params);
|
|||||||
void mjrf_destroyLight(mjrfLight* light);
|
void mjrf_destroyLight(mjrfLight* light);
|
||||||
void mjrf_setLightEnabled(mjrfLight* light, mjtBool enabled);
|
void mjrf_setLightEnabled(mjrfLight* light, mjtBool enabled);
|
||||||
void mjrf_setLightIntensity(mjrfLight* light, float intensity);
|
void mjrf_setLightIntensity(mjrfLight* light, float intensity);
|
||||||
|
void mjrf_setLightShadowMapSize(mjrfLight* light, int map_size);
|
||||||
void mjrf_setLightColor(mjrfLight* light, const float color[3]);
|
void mjrf_setLightColor(mjrfLight* light, const float color[3]);
|
||||||
void mjrf_setLightTransform(mjrfLight* light, const float position[3], const float direction[3]);
|
void mjrf_setLightTransform(mjrfLight* light, const float position[3], const float direction[3]);
|
||||||
int mjrf_getLightType(const mjrfLight* light);
|
int mjrf_getLightType(const mjrfLight* light);
|
||||||
|
|||||||
@@ -281,6 +281,9 @@ void mjrf_setLightEnabled(mjrfLight* light, mjtBool enabled);
|
|||||||
// Sets the intensity of the light, in candela.
|
// Sets the intensity of the light, in candela.
|
||||||
void mjrf_setLightIntensity(mjrfLight* light, float intensity);
|
void mjrf_setLightIntensity(mjrfLight* light, float intensity);
|
||||||
|
|
||||||
|
// Sets the resolution of the light's shadow map, in texels.
|
||||||
|
void mjrf_setLightShadowMapSize(mjrfLight* light, int map_size);
|
||||||
|
|
||||||
// Sets the RGB color of the light.
|
// Sets the RGB color of the light.
|
||||||
void mjrf_setLightColor(mjrfLight* light, const float color[3]);
|
void mjrf_setLightColor(mjrfLight* light, const float color[3]);
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ mat4 CalculateClipFromWorld(const mjrRect& viewport, const mjrCamera& cam) {
|
|||||||
void SceneBridge::Update(const mjrRect& viewport, const mjvScene* scene) {
|
void SceneBridge::Update(const mjrRect& viewport, const mjvScene* scene) {
|
||||||
const mjModel* model = model_objects_->GetModel();
|
const mjModel* model = model_objects_->GetModel();
|
||||||
|
|
||||||
|
model_lights_->Update();
|
||||||
|
|
||||||
mjtNum hpos[3], hfwd[3];
|
mjtNum hpos[3], hfwd[3];
|
||||||
float headpos[3], gazedir[3];
|
float headpos[3], gazedir[3];
|
||||||
mjv_cameraInModel(hpos, hfwd, nullptr, scene);
|
mjv_cameraInModel(hpos, hfwd, nullptr, scene);
|
||||||
|
|||||||
@@ -187,6 +187,19 @@ bool Ui(std::string_view label, T* value, UiOpts<T> opts = {}) {
|
|||||||
static_assert(dependent_false<T>::value, "Unsupported type");
|
static_assert(dependent_false<T>::value, "Unsupported type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Drag widgets above do not enforce bounds when only one of min/max is
|
||||||
|
// given, and typed input (ctrl+click) bypasses Slider bounds; clamp here.
|
||||||
|
if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>) {
|
||||||
|
if (changed) {
|
||||||
|
if (opts.min.has_value()) {
|
||||||
|
*value = std::max(*value, *opts.min);
|
||||||
|
}
|
||||||
|
if (opts.max.has_value()) {
|
||||||
|
*value = std::min(*value, *opts.max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (modified) {
|
if (modified) {
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
ImGui::SetItemTooltip("RMB to revert change");
|
ImGui::SetItemTooltip("RMB to revert change");
|
||||||
@@ -565,7 +578,7 @@ void DrawIndirectLightGui(SceneView* scene_view) {
|
|||||||
float intensity = 10000.0f;
|
float intensity = 10000.0f;
|
||||||
if (ibl) {
|
if (ibl) {
|
||||||
intensity = ibl->getIntensity();
|
intensity = ibl->getIntensity();
|
||||||
if (Ui("Intensity", &intensity, {.step = 1000.0f, .fstep = 10000.0f})) {
|
if (Ui("Intensity", &intensity, {.min = 0.0f, .step = 1000.0f, .fstep = 10000.0f})) {
|
||||||
ibl->setIntensity(intensity);
|
ibl->setIntensity(intensity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,7 +599,7 @@ void DrawLightGui(filament::LightManager& lm,
|
|||||||
lm.setDirection(li, direction);
|
lm.setDirection(li, direction);
|
||||||
}
|
}
|
||||||
float intensity = lm.getIntensity(li);
|
float intensity = lm.getIntensity(li);
|
||||||
if (Ui("Intensity", &intensity, {.step = 1000.0f, .fstep = 10000.0f})) {
|
if (Ui("Intensity", &intensity, {.min = 0.0f, .step = 1000.0f, .fstep = 10000.0f})) {
|
||||||
lm.setIntensityCandela(li, intensity);
|
lm.setIntensityCandela(li, intensity);
|
||||||
}
|
}
|
||||||
float falloff = lm.getFalloff(li);
|
float falloff = lm.getFalloff(li);
|
||||||
|
|||||||
@@ -165,6 +165,18 @@ void Light::SetIntensity(float intensity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Light::SetShadowMapSize(int map_size) {
|
||||||
|
params_.shadow_map_size = map_size;
|
||||||
|
if (ibl_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filament::LightManager& lm = engine_->getLightManager();
|
||||||
|
const filament::LightManager::Instance li = lm.getInstance(entity_);
|
||||||
|
filament::LightManager::ShadowOptions opts = lm.getShadowOptions(li);
|
||||||
|
opts.mapSize = map_size;
|
||||||
|
lm.setShadowOptions(li, opts);
|
||||||
|
}
|
||||||
|
|
||||||
void Light::Enable() {
|
void Light::Enable() {
|
||||||
if (!enabled_) {
|
if (!enabled_) {
|
||||||
enabled_ = true;
|
enabled_ = true;
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ class Light : public mjrfLight {
|
|||||||
// Sets the intensity of this light, in candela.
|
// Sets the intensity of this light, in candela.
|
||||||
void SetIntensity(float intensity);
|
void SetIntensity(float intensity);
|
||||||
|
|
||||||
|
// Sets the resolution of this light's shadow map, in texels.
|
||||||
|
void SetShadowMapSize(int map_size);
|
||||||
|
|
||||||
// Returns the type of the light.
|
// Returns the type of the light.
|
||||||
mjtLightType GetType() const;
|
mjtLightType GetType() const;
|
||||||
|
|
||||||
|
|||||||
@@ -234,6 +234,10 @@ void mjrf_setLightIntensity(mjrfLight* light, float intensity) {
|
|||||||
mujoco::Light::downcast(light)->SetIntensity(intensity);
|
mujoco::Light::downcast(light)->SetIntensity(intensity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mjrf_setLightShadowMapSize(mjrfLight* light, int map_size) {
|
||||||
|
mujoco::Light::downcast(light)->SetShadowMapSize(map_size);
|
||||||
|
}
|
||||||
|
|
||||||
void mjrf_setLightColor(mjrfLight* light, const float color[3]) {
|
void mjrf_setLightColor(mjrfLight* light, const float color[3]) {
|
||||||
mujoco::Light::downcast(light)->SetColor({color[0], color[1], color[2]});
|
mujoco::Light::downcast(light)->SetColor({color[0], color[1], color[2]});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,13 @@
|
|||||||
|
|
||||||
#include "render/filament/support/model_lights.h"
|
#include "render/filament/support/model_lights.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <numbers>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <math/TVecHelpers.h>
|
||||||
#include <math/mat4.h>
|
#include <math/mat4.h>
|
||||||
#include <math/mathfwd.h>
|
#include <math/mathfwd.h>
|
||||||
#include <math/vec3.h>
|
#include <math/vec3.h>
|
||||||
@@ -74,8 +77,7 @@ static UniquePtr<mjrfTexture> CreateFallbackIndirectLightTexture(
|
|||||||
ModelLights::ModelLights(mjrfScene* scene, ModelObjects* model_objects)
|
ModelLights::ModelLights(mjrfScene* scene, ModelObjects* model_objects)
|
||||||
: scene_(scene), model_objects_(model_objects) {
|
: scene_(scene), model_objects_(model_objects) {
|
||||||
const mjModel* model = model_objects->GetModel();
|
const mjModel* model = model_objects->GetModel();
|
||||||
default_shadow_map_size_ =
|
shadowsize_ = model->vis.quality.shadowsize;
|
||||||
ReadElement(model, "filament.shadows.map_size", default_shadow_map_size_);
|
|
||||||
fallback_head_light_intensity_ =
|
fallback_head_light_intensity_ =
|
||||||
ReadElement(model, "filament.fallback.head_light_intensity",
|
ReadElement(model, "filament.fallback.head_light_intensity",
|
||||||
fallback_head_light_intensity_);
|
fallback_head_light_intensity_);
|
||||||
@@ -97,16 +99,28 @@ ModelLights::~ModelLights() {
|
|||||||
mjrf_removeLightFromScene(scene_, fallback_ibl_.get());
|
mjrf_removeLightFromScene(scene_, fallback_ibl_.get());
|
||||||
}
|
}
|
||||||
fallback_ibl_.reset();
|
fallback_ibl_.reset();
|
||||||
|
if (fallback_directional_) {
|
||||||
|
mjrf_removeLightFromScene(scene_, fallback_directional_.get());
|
||||||
|
}
|
||||||
|
fallback_directional_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelLights::Prepare() {
|
void ModelLights::Prepare() {
|
||||||
mjrfContext* ctx = model_objects_->GetContext();
|
mjrfContext* ctx = model_objects_->GetContext();
|
||||||
const mjModel* model = model_objects_->GetModel();
|
const mjModel* model = model_objects_->GetModel();
|
||||||
|
|
||||||
|
// Default to the shadow map resolution in mjVisual, like the classic
|
||||||
|
// renderer. Filament clamps shadow map sizes to 2048.
|
||||||
|
int default_shadow_map_size = std::min(model->vis.quality.shadowsize, 2048);
|
||||||
|
default_shadow_map_size =
|
||||||
|
ReadElement(model, "filament.shadows.map_size", default_shadow_map_size);
|
||||||
|
|
||||||
bool has_image_based_light = false;
|
bool has_image_based_light = false;
|
||||||
|
bool has_directional_light = false;
|
||||||
float total_light_intensity = 0.0f;
|
float total_light_intensity = 0.0f;
|
||||||
for (int i = 0; i < model->nlight; ++i) {
|
for (int i = 0; i < model->nlight; ++i) {
|
||||||
total_light_intensity += model->light_intensity[i];
|
total_light_intensity += model->light_intensity[i];
|
||||||
|
has_directional_light |= model->light_type[i] == mjLIGHT_DIRECTIONAL;
|
||||||
|
|
||||||
if (model->light_type[i] == mjLIGHT_IMAGE) {
|
if (model->light_type[i] == mjLIGHT_IMAGE) {
|
||||||
mjrfLightParams params;
|
mjrfLightParams params;
|
||||||
@@ -126,13 +140,42 @@ void ModelLights::Prepare() {
|
|||||||
params.color[2] = model->light_diffuse[2];
|
params.color[2] = model->light_diffuse[2];
|
||||||
params.type = (mjtLightType)model->light_type[i];
|
params.type = (mjtLightType)model->light_type[i];
|
||||||
params.cast_shadows = model->light_castshadow[i];
|
params.cast_shadows = model->light_castshadow[i];
|
||||||
// The bulb_radius is only used by DPCF or PCSS shadows. For VSM shadows,
|
// light_bulbradius is the radius of the emitting surface in meters.
|
||||||
// we use light_bulbradius to control the blur width.
|
// Filament's DPCF/PCSS internally scale shadowBulbRadius from meters
|
||||||
params.bulb_radius = model->light_bulbradius[i];
|
// to light-space texels, so we pass it through in meters for all types.
|
||||||
params.vsm_blur_width = model->light_bulbradius[i];
|
// VSM requires a single uniform blur width for the map; we approximate
|
||||||
|
// this using the angular size of the bulb from the scene center.
|
||||||
|
const float bulb_radius = model->light_bulbradius[i];
|
||||||
|
const float map_size = default_shadow_map_size;
|
||||||
|
const float3 to_center = ReadFloat3(model->stat.center) -
|
||||||
|
ReadFloat3(model->light_pos0, i);
|
||||||
|
const float distance = std::max(length(to_center), 1e-6f);
|
||||||
|
const float bulb_angle = bulb_radius / distance;
|
||||||
|
params.bulb_radius = bulb_radius;
|
||||||
|
switch (params.type) {
|
||||||
|
case mjLIGHT_SPOT: {
|
||||||
|
const float fov =
|
||||||
|
2.0f * model->light_cutoff[i] * std::numbers::pi / 180.0f;
|
||||||
|
params.vsm_blur_width = bulb_angle * map_size / fov;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case mjLIGHT_POINT:
|
||||||
|
params.vsm_blur_width =
|
||||||
|
bulb_angle * map_size / (0.5f * std::numbers::pi);
|
||||||
|
break;
|
||||||
|
case mjLIGHT_DIRECTIONAL: {
|
||||||
|
const float coverage =
|
||||||
|
2.0f * model->vis.map.shadowclip * model->stat.extent;
|
||||||
|
params.vsm_blur_width = bulb_radius * map_size / coverage;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
params.vsm_blur_width = std::min(params.vsm_blur_width, 125.0f);
|
||||||
params.range = model->light_range[i];
|
params.range = model->light_range[i];
|
||||||
params.intensity = model->light_intensity[i];
|
params.intensity = model->light_intensity[i];
|
||||||
params.shadow_map_size = default_shadow_map_size_;
|
params.shadow_map_size = default_shadow_map_size;
|
||||||
if (params.type == mjLIGHT_SPOT) {
|
if (params.type == mjLIGHT_SPOT) {
|
||||||
params.spot_cone_angle = model->light_cutoff[i];
|
params.spot_cone_angle = model->light_cutoff[i];
|
||||||
}
|
}
|
||||||
@@ -162,6 +205,23 @@ void ModelLights::Prepare() {
|
|||||||
lights_.emplace_back(std::move(light_obj));
|
lights_.emplace_back(std::move(light_obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround for an upstream filament bug, present since 1.74.0
|
||||||
|
// (https://github.com/google/filament/issues/10249): under the non-PCF
|
||||||
|
// shadow types (VSM/DPCF/PCSS), a scene where a punctual (spot/point) light
|
||||||
|
// casts shadows renders fully black unless a directional light is also
|
||||||
|
// present. Mere presence suffices: zero intensity, shadows off. Keep
|
||||||
|
// filament's directional slot occupied with an invisible light whenever the
|
||||||
|
// model has no directional light.
|
||||||
|
if (!has_directional_light) {
|
||||||
|
mjrfLightParams params;
|
||||||
|
mjrf_defaultLightParams(¶ms);
|
||||||
|
params.type = mjLIGHT_DIRECTIONAL;
|
||||||
|
params.cast_shadows = 0;
|
||||||
|
params.intensity = 0.0f;
|
||||||
|
fallback_directional_ = CreateLight(ctx, params);
|
||||||
|
mjrf_addLightToScene(scene_, fallback_directional_.get());
|
||||||
|
}
|
||||||
|
|
||||||
if (!has_image_based_light && total_light_intensity > 0.0f) {
|
if (!has_image_based_light && total_light_intensity > 0.0f) {
|
||||||
// Create a black indirect light to ensure that the skybox is
|
// Create a black indirect light to ensure that the skybox is
|
||||||
// oriented to respect mujoco's Z-up convention.
|
// oriented to respect mujoco's Z-up convention.
|
||||||
@@ -203,7 +263,22 @@ void ModelLights::Prepare() {
|
|||||||
mjrf_setSceneSkybox(scene_, model_objects_->GetSkyboxTexture());
|
mjrf_setSceneSkybox(scene_, model_objects_->GetSkyboxTexture());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModelLights::UpdateShadowMapSize() {
|
||||||
|
const mjModel* model = model_objects_->GetModel();
|
||||||
|
if (model->vis.quality.shadowsize != shadowsize_) {
|
||||||
|
shadowsize_ = model->vis.quality.shadowsize;
|
||||||
|
const int map_size = std::min(shadowsize_, 2048);
|
||||||
|
for (auto& light : lights_) {
|
||||||
|
mjrf_setLightShadowMapSize(light.get(), map_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ModelLights::Update(const mjData* data) {
|
void ModelLights::Update(const mjData* data) {
|
||||||
|
UpdateShadowMapSize();
|
||||||
|
if (data == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mjModel* model = model_objects_->GetModel();
|
const mjModel* model = model_objects_->GetModel();
|
||||||
for (int i = 0; i <= model->nlight; ++i) {
|
for (int i = 0; i <= model->nlight; ++i) {
|
||||||
// Light with index nlight is the headlight.
|
// Light with index nlight is the headlight.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class ModelLights {
|
|||||||
~ModelLights();
|
~ModelLights();
|
||||||
|
|
||||||
// Updates the state of the lights in the scene.
|
// Updates the state of the lights in the scene.
|
||||||
void Update(const mjData* data);
|
void Update(const mjData* data = nullptr);
|
||||||
|
|
||||||
// Returns the light with the given index in the mjModel. Note that an extra
|
// Returns the light with the given index in the mjModel. Note that an extra
|
||||||
// headlight is assigned of the index `nlight`.
|
// headlight is assigned of the index `nlight`.
|
||||||
@@ -43,13 +43,19 @@ class ModelLights {
|
|||||||
private:
|
private:
|
||||||
void Prepare();
|
void Prepare();
|
||||||
|
|
||||||
|
// Applies changes of mjVisual quality.shadowsize to the lights' shadow
|
||||||
|
// maps. A filament.shadows.map_size custom element remains in effect until
|
||||||
|
// shadowsize is edited.
|
||||||
|
void UpdateShadowMapSize();
|
||||||
|
|
||||||
mjrfScene* scene_ = nullptr;
|
mjrfScene* scene_ = nullptr;
|
||||||
ModelObjects* model_objects_ = nullptr;
|
ModelObjects* model_objects_ = nullptr;
|
||||||
|
|
||||||
UniquePtr<mjrfLight> fallback_ibl_{nullptr, nullptr};
|
UniquePtr<mjrfLight> fallback_ibl_{nullptr, nullptr};
|
||||||
|
UniquePtr<mjrfLight> fallback_directional_{nullptr, nullptr};
|
||||||
UniquePtr<mjrfTexture> fallback_ibl_texture_{nullptr, nullptr};
|
UniquePtr<mjrfTexture> fallback_ibl_texture_{nullptr, nullptr};
|
||||||
std::vector<UniquePtr<mjrfLight>> lights_;
|
std::vector<UniquePtr<mjrfLight>> lights_;
|
||||||
int default_shadow_map_size_ = 2048;
|
int shadowsize_ = 0;
|
||||||
float fallback_head_light_intensity_ = 0.f;
|
float fallback_head_light_intensity_ = 0.f;
|
||||||
float fallback_scene_light_intensity_ = 80'000.f;
|
float fallback_scene_light_intensity_ = 80'000.f;
|
||||||
float fallback_environment_light_intensity_ = 5'000.f;
|
float fallback_environment_light_intensity_ = 5'000.f;
|
||||||
|
|||||||
Reference in New Issue
Block a user