EEVEE-Next: Shadow: Add Maximum Resolution Option

This adds a light parameter to avoid near camera pixels
allocating too much shadow resolution.

This is more intuitive than the scale shadow setting and
allows reducing the precision without changing the look
of distant shadows.

For sun lights, the property sets the minimum pixel
size the shadow can contains. This allows to
remove a lot tilemap close up.

For local lights, there is another additional property
to set the maximum resolution in shadow space (like
EEVEE-Legacy) instead of in world space (like sun
lights). This allows making older files lighter and
allow a more conservative approach to resolution.

This add versionning code to handle EEVEE-Legacy files
that had fixed resolution per light type.

The resolution setting is always in world space distance
as the maximum shadow resolution distribution might change
in the future or be dependent on other parameters
(like beam angle). This ensure that there is no
dependency on these parameters, but make the
setting use very small units. But this is more of
a UI problem.

Pull Request: https://projects.blender.org/blender/blender/pulls/121701
This commit is contained in:
Clément Foucault 2024-05-13 14:34:11 +02:00 committed by Clément Foucault
parent bacfec10bb
commit c7bc3334ad
16 changed files with 179 additions and 139 deletions

@ -154,7 +154,12 @@ class DATA_PT_EEVEE_light_shadow(DataButtonsPanel, Panel):
col = layout.column()
col.prop(light, "shadow_filter_radius", text="Filter")
col.prop(light, "shadow_resolution_scale", text="Resolution")
sub = col.column(align=True)
row = sub.row(align=True)
row.prop(light, "shadow_maximum_resolution", text="Resolution")
row.prop(light, "use_absolute_resolution", text="", icon='DRIVER_DISTANCE')
sub.prop(light, "shadow_resolution_scale", text="Scale")
class DATA_PT_EEVEE_light_influence(DataButtonsPanel, Panel):

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 33
#define BLENDER_FILE_SUBVERSION 34
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

@ -3479,6 +3479,32 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 34)) {
float shadow_max_res_sun = 0.001f;
float shadow_max_res_local = 0.001f;
bool shadow_resolution_absolute = false;
/* Try to get default resolution from scene setting. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
shadow_max_res_local = (2.0f * M_SQRT2) / scene->eevee.shadow_cube_size;
/* Round to avoid weird numbers in the UI. */
shadow_max_res_local = ceil(shadow_max_res_local * 1000.0f) / 1000.0f;
shadow_resolution_absolute = true;
break;
}
LISTBASE_FOREACH (Light *, light, &bmain->lights) {
if (light->type == LA_SUN) {
/* Sun are too complex to convert. Need user interaction. */
light->shadow_maximum_resolution = shadow_max_res_sun;
SET_FLAG_FROM_TEST(light->mode, false, LA_SHAD_RES_ABSOLUTE);
}
else {
light->shadow_maximum_resolution = shadow_max_res_local;
SET_FLAG_FROM_TEST(light->mode, shadow_resolution_absolute, LA_SHAD_RES_ABSOLUTE);
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

@ -909,4 +909,11 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
}
}
}
{
LISTBASE_FOREACH (Light *, light, &bmain->lights) {
light->shadow_maximum_resolution = 0.001f;
SET_FLAG_FROM_TEST(light->mode, false, LA_SHAD_RES_ABSOLUTE);
}
}
}

@ -86,21 +86,17 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
this->pcf_radius = la->shadow_filter_radius;
this->lod_bias = (1.0f - la->shadow_resolution_scale) * SHADOW_TILEMAP_LOD;
this->lod_min = shadow_lod_min_get(la);
if (la->mode & LA_SHADOW) {
shadow_ensure(shadows);
if (is_sun_light(this->type)) {
this->directional->sync(object_to_world, 1.0f, la->sun_angle, la->shadow_resolution_scale);
this->directional->sync(object_to_world, la->sun_angle);
}
else {
/* Reuse shape radius as near clip plane. */
/* This assumes `shape_parameters_set` has already set `radius_squared`. */
float radius = math::sqrt(this->local.radius_squared);
this->punctual->sync(this->type,
object_to_world,
la->spotsize,
radius,
this->local.influence_radius_max,
la->shadow_resolution_scale);
this->punctual->sync(
this->type, object_to_world, la->spotsize, this->local.influence_radius_max);
}
}
else {
@ -110,6 +106,17 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
this->initialized = true;
}
float Light::shadow_lod_min_get(const ::Light *la)
{
/* Property is in mm. Convert to unit. */
float max_res_unit = la->shadow_maximum_resolution;
if (is_sun_light(this->type)) {
return log2f(max_res_unit * SHADOW_MAP_MAX_RES) - 1.0f;
}
/* Store absolute mode as negative. */
return (la->mode & LA_SHAD_RES_ABSOLUTE) ? -max_res_unit : max_res_unit;
}
void Light::shadow_discard_safe(ShadowModule &shadows)
{
if (this->directional != nullptr) {
@ -156,8 +163,12 @@ void Light::shape_parameters_set(const ::Light *la, const float3 &scale, float t
const bool is_irregular = ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE);
this->area.size = float2(la->area_size, is_irregular ? la->area_sizey : la->area_size);
/* Scale and clamp to minimum value before float imprecision artifacts appear. */
this->area.size = max(float2(0.003f), this->area.size * scale.xy() / 2.0f);
this->area.size = this->area.size * scale.xy() / 2.0f;
/* Do not render lights that virtually have no area or clamp to minimum value before float
* imprecision artifacts appear. */
this->area.size = (this->area.size.x * this->area.size.y < 0.00001f) ?
float2(0.0) :
max(float2(0.003f), this->area.size * scale.xy() / 2.0f);
/* For volume point lighting. */
this->local.radius_squared = square(max(0.001f, length(this->area.size) / 2.0f));
}

@ -84,6 +84,7 @@ struct Light : public LightData, NonCopyable {
void debug_draw();
private:
float shadow_lod_min_get(const ::Light *la);
float attenuation_radius_get(const ::Light *la, float light_threshold, float light_power);
void shape_parameters_set(const ::Light *la, const float3 &scale, float threshold);
float shape_radiance_get();

@ -963,7 +963,8 @@ struct LightData {
/* Shadow Map resolution bias. */
float lod_bias;
float _pad0;
/* Shadow Map resolution maximum resolution. */
float lod_min;
float _pad1;
float _pad2;
@ -1216,8 +1217,8 @@ struct ShadowTileMapData {
int tiles_index;
/** Index of persistent data in the persistent data buffer. */
int clip_data_index;
/** Bias LOD to tag for usage to lower the amount of tile used. */
float lod_bias;
float _pad0;
/** Light type this tilemap is from. */
eLightType light_type;
/** True if the tilemap is part of area light shadow and is one of the side projections. */

@ -29,7 +29,6 @@ ShadowTechnique ShadowModule::shadow_technique = ShadowTechnique::ATOMIC_RASTER;
void ShadowTileMap::sync_orthographic(const float4x4 &object_mat_,
int2 origin_offset,
int clipmap_level,
float lod_bias_,
eShadowProjectionType projection_type_)
{
if ((projection_type != projection_type_) || (level != clipmap_level)) {
@ -51,8 +50,6 @@ void ShadowTileMap::sync_orthographic(const float4x4 &object_mat_,
set_dirty();
}
lod_bias = lod_bias_;
float tile_size = ShadowDirectional::tile_size_get(level);
/* object_mat is a rotation matrix. Reduce imprecision by taking the transpose which is also the
@ -77,8 +74,7 @@ void ShadowTileMap::sync_cubeface(eLightType light_type_,
float far_,
float side_,
float shift,
eCubeFace face,
float lod_bias_)
eCubeFace face)
{
if (projection_type != SHADOW_PROJECTION_CUBEFACE || (cubeface != face)) {
set_dirty();
@ -86,7 +82,6 @@ void ShadowTileMap::sync_cubeface(eLightType light_type_,
projection_type = SHADOW_PROJECTION_CUBEFACE;
cubeface = face;
grid_offset = int2(0);
lod_bias = lod_bias_;
light_type = light_type_;
is_area_side = is_area_light(light_type) && (face != eCubeFace::Z_NEG);
@ -230,9 +225,7 @@ void ShadowTileMapPool::end_sync(ShadowModule &module)
void ShadowPunctual::sync(eLightType light_type,
const float4x4 &object_mat,
float cone_aperture,
float light_shape_radius,
float max_distance,
float shadow_resolution_scale)
float max_distance)
{
if (is_spot_light(light_type)) {
tilemaps_needed_ = (cone_aperture > DEG2RADF(90.0f)) ? 5 : 1;
@ -244,16 +237,11 @@ void ShadowPunctual::sync(eLightType light_type,
tilemaps_needed_ = 6;
}
light_type_ = light_type;
/* Clamp for near/far clip distance calculation. */
max_distance_ = max_ff(max_distance, 4e-4f);
light_radius_ = min_ff(light_shape_radius, max_distance_ - 1e-4f);
light_type_ = light_type;
position_ = float3(object_mat[3]);
float resolution_scale = clamp_f(shadow_resolution_scale, 0.0f, 2.0f);
lod_bias_ = (resolution_scale < 1.0) ? -log2(resolution_scale) : -(resolution_scale - 1.0f);
lod_bias_ += shadows_.get_global_lod_bias();
}
void ShadowPunctual::release_excess_tilemaps()
@ -288,20 +276,15 @@ void ShadowPunctual::end_sync(Light &light)
tilemaps_.append(tilemap_pool.acquire());
}
tilemaps_[Z_NEG]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, Z_NEG, lod_bias_);
tilemaps_[Z_NEG]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, Z_NEG);
if (tilemaps_needed_ >= 5) {
tilemaps_[X_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, X_POS, lod_bias_);
tilemaps_[X_NEG]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, X_NEG, lod_bias_);
tilemaps_[Y_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Y_POS, lod_bias_);
tilemaps_[Y_NEG]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Y_NEG, lod_bias_);
tilemaps_[X_POS]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, X_POS);
tilemaps_[X_NEG]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, X_NEG);
tilemaps_[Y_POS]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, Y_POS);
tilemaps_[Y_NEG]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, Y_NEG);
}
if (tilemaps_needed_ == 6) {
tilemaps_[Z_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Z_POS, lod_bias_);
tilemaps_[Z_POS]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, Z_POS);
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
@ -318,7 +301,6 @@ void ShadowPunctual::end_sync(Light &light)
light.clip_far = as_int.i;
light.local.clip_side = side;
light.local.shadow_projection_shift = shift;
light.lod_bias = lod_bias_;
for (ShadowTileMap *tilemap : tilemaps_) {
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
@ -394,10 +376,8 @@ IndexRange ShadowDirectional::cascade_level_range(const Camera &camera)
min_diagonal_tilemap_size *= cam_data.clip_far / cam_data.clip_near;
}
/* Allow better tile-map usage without missing pages near end of view. */
float lod_bias = this->lod_bias_ + 0.5f;
/* Level of detail (or size) of every tile-maps of this light. */
int lod_level = ceil(log2(max_ff(min_depth_tilemap_size, min_diagonal_tilemap_size)) + lod_bias);
int lod_level = ceil(log2(max_ff(min_depth_tilemap_size, min_diagonal_tilemap_size)) + 0.5);
/* Tile-maps "rotate" around the first one so their effective range is only half their size. */
float per_tilemap_coverage = ShadowDirectional::coverage_get(lod_level) * 0.5f;
@ -446,7 +426,7 @@ void ShadowDirectional::cascade_tilemaps_distribution(Light &light, const Camera
/* Equal spacing between cascades layers since we want uniform shadow density. */
int2 level_offset = origin_offset +
shadow_cascade_grid_offset(light.sun.clipmap_base_offset_pos, i);
tilemap->sync_orthographic(object_mat_, level_offset, level, 0.0f, SHADOW_PROJECTION_CASCADE);
tilemap->sync_orthographic(object_mat_, level_offset, level, SHADOW_PROJECTION_CASCADE);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
@ -461,37 +441,27 @@ void ShadowDirectional::cascade_tilemaps_distribution(Light &light, const Camera
* the scaling. */
light.sun.clipmap_lod_min = levels_range.first();
light.sun.clipmap_lod_max = levels_range.last();
/* The bias is applied in cascade_level_range().
* Using clipmap_lod_min here simplify code in shadow_directional_level().
* Minus 1 because of the ceil(). */
light.lod_bias = light.sun.clipmap_lod_min - 1;
}
/************************************************************************
* Clip-map Distribution *
************************************************************************/
IndexRange ShadowDirectional::clipmap_level_range(const Camera &camera)
IndexRange ShadowDirectional::clipmap_level_range(const Camera &cam)
{
using namespace blender::math;
/* 32 to be able to pack offset into two single int2. */
const int max_tilemap_per_shadows = 32;
int user_min_level = floorf(log2(min_resolution_));
/* Covers the farthest points of the view. */
int max_level = ceil(
log2(camera.bound_radius() + distance(camera.bound_center(), camera.position())));
int max_level = ceil(log2(cam.bound_radius() + distance(cam.bound_center(), cam.position())));
/* We actually need to cover a bit more because of clipmap origin snapping. */
max_level += 1;
/* Covers the closest points of the view. */
int min_level = floor(log2(abs(camera.data_get().clip_near)));
min_level = clamp_i(user_min_level, min_level, max_level);
/* FIXME: IndexRange does not support negative range. Clamp to 1 for now. */
int min_level = max(0.0f, floor(log2(abs(cam.data_get().clip_near))));
IndexRange range(min_level, max_level - min_level + 1);
/* The maximum level count is bounded by the mantissa of a 32bit float. Take top-most level to
* still cover the whole view. */
/* 32 to be able to pack offset into a single int2.
* The maximum level count is bounded by the mantissa of a 32bit float. */
const int max_tilemap_per_shadows = 24;
/* Take top-most level to still cover the whole view. */
range = range.take_back(max_tilemap_per_shadows);
return range;
@ -510,8 +480,7 @@ void ShadowDirectional::clipmap_tilemaps_distribution(Light &light, const Camera
float2 light_space_camera_position = camera.position() * float2x3(object_mat_.view<2, 3>());
int2 level_offset = int2(math::round(light_space_camera_position / tile_size));
tilemap->sync_orthographic(
object_mat_, level_offset, level, lod_bias_, SHADOW_PROJECTION_CLIPMAP);
tilemap->sync_orthographic(object_mat_, level_offset, level, SHADOW_PROJECTION_CLIPMAP);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
@ -553,28 +522,15 @@ void ShadowDirectional::clipmap_tilemaps_distribution(Light &light, const Camera
light.sun.clipmap_lod_min = levels_range.first();
light.sun.clipmap_lod_max = levels_range.last();
light.lod_bias = lod_bias_;
}
void ShadowDirectional::sync(const float4x4 &object_mat,
float min_resolution,
float shadow_disk_angle,
float shadow_resolution_scale)
void ShadowDirectional::sync(const float4x4 &object_mat, float shadow_disk_angle)
{
object_mat_ = object_mat;
/* Clear embedded custom data. */
object_mat_[0][3] = object_mat_[1][3] = object_mat_[2][3] = 0.0f;
object_mat_[3][3] = 1.0f;
/* Remove translation. */
object_mat_.location() = float3(0.0f);
min_resolution_ = min_resolution;
disk_shape_angle_ = min_ff(shadow_disk_angle, DEG2RADF(179.9f)) / 2.0f;
float resolution_scale = clamp_f(shadow_resolution_scale, 0.0f, 2.0f);
lod_bias_ = (resolution_scale < 1.0) ? -log2(resolution_scale) : -(resolution_scale - 1.0f);
lod_bias_ += shadows_.get_global_lod_bias();
}
void ShadowDirectional::release_excess_tilemaps(const Camera &camera)
@ -693,8 +649,6 @@ void ShadowModule::init()
shadow_page_len_ = int(divide_ceil_ul(pool_byte_size, page_byte_size));
shadow_page_len_ = min_ii(shadow_page_len_, SHADOW_MAX_PAGE);
lod_bias_ = -log2f(scene.eevee.shadow_resolution_scale);
const int2 atlas_extent = shadow_page_size_ * int2(SHADOW_PAGE_PER_ROW);
const int atlas_layers = divide_ceil_u(shadow_page_len_, SHADOW_PAGE_PER_LAYER);

@ -100,7 +100,6 @@ struct ShadowTileMap : public ShadowTileMapData {
void sync_orthographic(const float4x4 &object_mat_,
int2 origin_offset,
int clipmap_level,
float lod_bias_,
eShadowProjectionType projection_type_);
void sync_cubeface(eLightType light_type_,
@ -109,8 +108,7 @@ struct ShadowTileMap : public ShadowTileMapData {
float far,
float side,
float shift,
eCubeFace face,
float lod_bias_);
eCubeFace face);
void debug_draw() const;
@ -323,8 +321,6 @@ class ShadowModule {
/** For now, needs to be hardcoded. */
int shadow_page_size_ = SHADOW_PAGE_RES;
/** Amount of bias to apply to the LOD computed at the tile usage tagging stage. */
float lod_bias_ = 0.0f;
/** Maximum number of allocated pages. Maximum value is SHADOW_MAX_TILEMAP. */
int shadow_page_len_ = SHADOW_MAX_TILEMAP;
/** Global switch. */
@ -368,11 +364,6 @@ class ShadowModule {
return data_;
}
float get_global_lod_bias()
{
return lod_bias_;
}
/* Set all shadows to update. To be called before `end_sync`. */
void reset()
{
@ -411,11 +402,9 @@ class ShadowPunctual : public NonCopyable, NonMovable {
/** Light position. */
float3 position_;
/** Used to compute near and far clip distances. */
float max_distance_, light_radius_;
float max_distance_;
/** Number of tile-maps needed to cover the light angular extents. */
int tilemaps_needed_;
/** Shadow LOD bias calculated based on global and light shadow resolution scale. */
float lod_bias_;
public:
ShadowPunctual(ShadowModule &module) : shadows_(module){};
@ -433,9 +422,7 @@ class ShadowPunctual : public NonCopyable, NonMovable {
void sync(eLightType light_type,
const float4x4 &object_mat,
float cone_aperture,
float light_shape_radius,
float max_distance,
float shadow_resolution_scale);
float max_distance);
/**
* Release the tile-maps that will not be used in the current frame.
@ -462,16 +449,12 @@ class ShadowDirectional : public NonCopyable, NonMovable {
ShadowModule &shadows_;
/** Tile-map for each clip-map level. */
Vector<ShadowTileMap *> tilemaps_;
/** User minimum resolution. */
float min_resolution_;
/** Copy of object matrix. Normalized. */
float4x4 object_mat_;
/** Current range of clip-map / cascades levels covered by this shadow. */
IndexRange levels_range;
/** Angle of the shadowed light shape. Might be scaled compared to the shading disk. */
float disk_shape_angle_;
/** Shadow LOD bias calculated based on global and light shadow resolution scale. */
float lod_bias_;
public:
ShadowDirectional(ShadowModule &module) : shadows_(module){};
@ -486,10 +469,7 @@ class ShadowDirectional : public NonCopyable, NonMovable {
/**
* Sync shadow parameters but do not allocate any shadow tile-maps.
*/
void sync(const float4x4 &object_mat,
float min_resolution,
float shadow_disk_angle,
float shadow_resolution_scale);
void sync(const float4x4 &object_mat, float shadow_disk_angle);
/**
* Release the tile-maps that will not be used in the current frame.

@ -103,6 +103,37 @@ ShadowSamplingTile shadow_tile_load(usampler2D tilemaps_tx, uvec2 tile_co, int t
return shadow_sampling_tile_unpack(tile_data);
}
#if 0 /* TODO(fclem): Finish. We can simplify sampling logic and only tag radially. */
/**
* Return the tilemap at a given point.
*
* This function should be the inverse of ShadowDirectional::coverage_get().
*
* \a lP shading point position in light space, relative to the to camera position snapped to
* the smallest clip-map level (`shadow_world_to_local(light, P) - light_position_get(light)`).
*/
int shadow_directional_tilemap_index(LightData light, vec3 lP)
{
LightSunData sun = light_sun_data_get(light);
int lvl;
if (light.type == LIGHT_SUN) {
/* We need to hide one tile worth of data to hide the moving transition. */
const float narrowing = float(SHADOW_TILEMAP_RES) / (float(SHADOW_TILEMAP_RES) - 1.0001);
/* Avoid using log2 when we can just get the exponent from the floating point. */
frexp(reduce_max(abs(lP)) * narrowing * 2.0, lvl);
}
else {
/* Since we want half of the size, bias the level by -1. */
/* TODO(fclem): Precompute. */
float lod_min_half_size = exp2(float(sun.clipmap_lod_min - 1));
lvl = reduce_max(lP.xy) / lod_min_half_size;
}
return light.clamp(lvl, sun.clipmap_lod_min, sun.clipmap_lod_max);
}
#endif
/**
* This function should be the inverse of ShadowDirectional::coverage_get().
*
@ -127,7 +158,7 @@ float shadow_directional_level_fractional(LightData light, vec3 lP)
float lod_min_half_size = exp2(float(light_sun_data_get(light).clipmap_lod_min - 1));
lod = length(lP.xy) * narrowing / lod_min_half_size;
}
float clipmap_lod = lod + light.lod_bias;
float clipmap_lod = max(lod + light.lod_bias, light.lod_min);
return clamp(clipmap_lod,
float(light_sun_data_get(light).clipmap_lod_min),
float(light_sun_data_get(light).clipmap_lod_max));
@ -138,11 +169,6 @@ int shadow_directional_level(LightData light, vec3 lP)
return int(ceil(shadow_directional_level_fractional(light, lP)));
}
float shadow_punctual_frustum_padding_get(LightData light)
{
return light_local_data_get(light).clip_side / orderedIntBitsToFloat(light.clip_near);
}
/**
* Returns the ratio of radius between shadow map pixels and screen pixels.
* `distance_to_camera` is Z distance to the camera origin.
@ -153,22 +179,24 @@ float shadow_punctual_pixel_ratio(LightData light,
float distance_to_camera,
float film_pixel_radius)
{
film_pixel_radius *= exp2(light.lod_bias);
float distance_to_light = reduce_max(abs(lP));
/* We project a shadow map pixel (as a sphere for simplicity) to the receiver plane.
* We then reproject this sphere onto the camera screen and compare it to the film pixel size.
* This gives a good approximation of what LOD to select to get a somewhat uniform shadow map
* resolution in screen space. */
float film_footprint = (is_perspective) ? film_pixel_radius * distance_to_camera :
film_pixel_radius;
/* Compute approximate screen pixel world space radius at 1 unit away of the light. */
float shadow_pixel_footprint = 2.0 * M_SQRT2 / SHADOW_MAP_MAX_RES;
/* Take the frustum padding into account. */
shadow_pixel_footprint *= shadow_punctual_frustum_padding_get(light);
float distance_to_light = reduce_max(abs(lP));
float shadow_footprint = shadow_pixel_footprint * distance_to_light;
/* TODO(fclem): Ideally, this should be modulated by N.L. */
float ratio = shadow_footprint / film_footprint;
return ratio;
float film_pixel_footprint = (is_perspective) ? film_pixel_radius * distance_to_camera :
film_pixel_radius;
/* Clamp in world space. */
film_pixel_footprint = max(film_pixel_footprint, light.lod_min);
/* Project onto light's unit plane (per cubeface). */
film_pixel_footprint /= distance_to_light;
/* Clamp in shadow space. */
film_pixel_footprint = max(film_pixel_footprint, -light.lod_min);
/* Cube-face diagonal divided by LOD0 resolution. */
const float shadow_pixel_radius = (2.0 * M_SQRT2) / SHADOW_MAP_MAX_RES;
return saturate(shadow_pixel_radius / film_pixel_footprint);
}
/**
@ -185,8 +213,8 @@ float shadow_punctual_level_fractional(LightData light,
light, lP, is_perspective, distance_to_camera, film_pixel_radius);
/* NOTE: Bias by one to counteract the ceil in the `int` variant. This is done because this
* function should return an upper bound. */
float lod = -log2(ratio) - 1.0 + light.lod_bias;
lod = clamp(lod, 0.0, float(SHADOW_TILEMAP_LOD));
float lod = -log2(ratio) - 1.0;
lod = min(lod, float(SHADOW_TILEMAP_LOD));
return lod;
}

@ -354,28 +354,26 @@ float shadow_texel_radius_at_position(LightData light, const bool is_directional
/* Simplification of `coverage_get(shadow_directional_level_fractional)`. */
const float narrowing = float(SHADOW_TILEMAP_RES) / (float(SHADOW_TILEMAP_RES) - 1.0001);
scale = length(lP) * narrowing;
scale *= exp2(light.lod_bias);
scale = clamp(scale, float(1 << sun.clipmap_lod_min), float(1 << sun.clipmap_lod_max));
scale = max(scale * exp2(light.lod_bias), exp2(light.lod_min));
scale = min(scale, float(1 << sun.clipmap_lod_max));
}
else {
/* Uniform distribution everywhere. No distance scaling.
* shadow_directional_level_fractional returns the cascade level, but all levels have the
* same density as the level 0. So the effective density only depends on the `lod_bias`. */
scale = exp2(light.lod_bias);
scale = max(exp2(light.lod_bias), exp2(light.lod_min));
}
}
else {
/* Simplification of `coverage_get(shadow_punctual_level_fractional)`. */
/* Simplification of `exp2(shadow_punctual_level_fractional)`. */
scale = shadow_punctual_pixel_ratio(light,
lP,
drw_view_is_perspective(),
drw_view_z_distance(P),
uniform_buf.shadow.film_pixel_radius);
/* This gives the size of pixels at Z = 1. */
scale = 1.0 / scale;
scale *= exp2(-1.0 + light.lod_bias);
scale = clamp(scale, float(1 << 0), float(1 << SHADOW_TILEMAP_LOD));
scale *= shadow_punctual_frustum_padding_get(light);
scale = 0.5 / scale;
scale = min(scale, float(1 << (SHADOW_TILEMAP_LOD - 1)));
/* Now scale by distance to the light. */
scale *= reduce_max(abs(lP));
}

@ -234,12 +234,12 @@ static void test_eevee_shadow_tag_update()
{
ShadowTileMap tilemap(0 * SHADOW_TILEDATA_PER_TILEMAP);
tilemap.sync_cubeface(
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG);
tilemaps_data.append(tilemap);
}
{
ShadowTileMap tilemap(1 * SHADOW_TILEDATA_PER_TILEMAP);
tilemap.sync_orthographic(float4x4::identity(), int2(0), 1, 0.0f, SHADOW_PROJECTION_CLIPMAP);
tilemap.sync_orthographic(float4x4::identity(), int2(0), 1, SHADOW_PROJECTION_CLIPMAP);
tilemaps_data.append(tilemap);
}
@ -1543,7 +1543,7 @@ static void test_eevee_shadow_page_mask_ex(int max_view_per_tilemap)
{
ShadowTileMap tilemap(0);
tilemap.sync_cubeface(
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG);
tilemaps_data.append(tilemap);
}

@ -44,6 +44,7 @@
.volume_fac = 1.0f, \
.shadow_filter_radius = 1.0f, \
.shadow_resolution_scale = 1.0f, \
.shadow_maximum_resolution = 0.001f, \
.att_dist = 40.0f, \
.sun_angle = DEG2RADF(0.526f), \
.area_spread = DEG2RADF(180.0f), \

@ -81,6 +81,8 @@ typedef struct Light {
float att_dist;
float shadow_filter_radius;
float shadow_resolution_scale;
float shadow_maximum_resolution;
char _pad3[4];
/* Preview */
struct PreviewImage *preview;
@ -143,6 +145,8 @@ enum {
LA_SHAD_CONTACT = 1 << 19,
LA_CUSTOM_ATTENUATION = 1 << 20,
LA_USE_SOFT_FALLOFF = 1 << 21,
/** Use absolute resolution clamping instead of relative. */
LA_SHAD_RES_ABSOLUTE = 1 << 22,
};
/** #Light::falloff_type */

@ -305,6 +305,16 @@ static void rna_def_light_shadow(StructRNA *srna, bool sun)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Light_update");
prop = RNA_def_property(srna, "shadow_maximum_resolution", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0001f, 0.020f, 0.0005f, 4);
RNA_def_property_ui_text(
prop,
"Shadows Maximum Resolution",
"Lower values will reduce the cost of the shadow map in close-up regions");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Light_update");
prop = RNA_def_property(srna, "shadow_resolution_scale", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.25f, 2);
@ -347,6 +357,16 @@ static void rna_def_light_shadow(StructRNA *srna, bool sun)
prop, "Cascade Fade", "How smooth is the transition between each cascade");
RNA_def_property_update(prop, 0, "rna_Light_update");
}
else {
prop = RNA_def_property(srna, "use_absolute_resolution", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "mode", LA_SHAD_RES_ABSOLUTE);
RNA_def_property_ui_text(prop,
"Absolute Maximum Resolution",
"Set maximum resolution at 1 unit from the light instead of relative "
"to the shaded pixel distance");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Light_update");
}
}
static void rna_def_point_light(BlenderRNA *brna)

@ -60,6 +60,10 @@ def setup():
# Only include the plane in probes
for ob in scene.objects:
if ob.type == 'LIGHT':
# Set maximum resolution
ob.data.shadow_maximum_resolution = 0.0
if ob.name != 'Plane' and ob.type != 'LIGHT':
ob.hide_probe_volume = True
ob.hide_probe_sphere = True