EEVEE-Next: Jittered Soft Shadows #119753

Open
Miguel Pozo wants to merge 46 commits from pragma37/blender:pull-eevee-jittered-shoft-shadows into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
22 changed files with 466 additions and 234 deletions

View File

@ -162,6 +162,17 @@ class DATA_PT_EEVEE_light_shadow(DataButtonsPanel, Panel):
col.prop(light, "shadow_filter_radius", text="Filter")
col.prop(light, "shadow_resolution_scale", text="Resolution")
col.separator()
col = layout.column(align=False, heading="Jitter")
row = col.row(align=True)
sub = row.row(align=True)
sub.prop(light, "use_shadow_jittering", text="")
sub = sub.row(align=True)
sub.active = light.use_shadow_jittering
sub.prop(light, "shadow_jitter_overblur_percentage", text="Overblur")
class DATA_PT_EEVEE_light_influence(DataButtonsPanel, Panel):
bl_label = "Influence"

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 19
#define BLENDER_FILE_SUBVERSION 20
/* 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

View File

@ -3184,6 +3184,12 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 20)) {
LISTBASE_FOREACH (Light *, light, &bmain->lights) {
light->shadow_jitter_overblur_percentage = 10.0f;
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@ -399,6 +399,8 @@ void Instance::render_sample()
sampling.step();
lights.sync_jitter();
capture_view.render_world();
capture_view.render_probes();

View File

@ -93,32 +93,10 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
if (la->mode & LA_SHADOW) {
shadow_ensure(shadows);
if (is_sun_light(this->type)) {
this->directional->sync(this->object_mat,
1.0f,
la->sun_angle * la->shadow_softness_factor,
la->shadow_trace_distance);
this->directional->sync(la, this->object_mat, 1.0f);
}
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);
float shadow_radius = la->shadow_softness_factor * radius;
if (ELEM(la->type, LA_LOCAL, LA_SPOT)) {
/* `shape_parameters_set` can increase the radius of point and spot lights to ensure a
* minimum radius/energy ratio.
* But we don't want to take that into account for computing the shadow-map projection,
* since non-zero radius introduces padding (required for soft-shadows tracing), reducing
* the effective resolution of shadow-maps.
* So we use the original light radius instead. */
shadow_radius = la->shadow_softness_factor * la->radius;
}
this->punctual->sync(this->type,
this->object_mat,
la->spotsize,
radius,
this->local.influence_radius_max,
la->shadow_softness_factor,
shadow_radius);
this->punctual->sync(la, this->type, this->object_mat, this->local.influence_radius_max);
pragma37 marked this conversation as resolved Outdated

Why use sun.radius here? it is Clampped to a min and thus will always produce some flicker if jitter is enabled. Prefer computing the disk size again from the angle la->sun_angle * la->shadow_softness_factor when jittering the position (code is less hard to follow).

I feel the same way as for punctual. Just pass sun_angle, jitter amount and tracing amount.

Why use sun.radius here? it is Clampped to a min and thus will always produce some flicker if jitter is enabled. Prefer computing the disk size again from the angle `la->sun_angle * la->shadow_softness_factor` when jittering the position (code is less hard to follow). I feel the same way as for punctual. Just pass `sun_angle`, jitter amount and tracing amount.
}
}
else {
@ -346,10 +324,16 @@ void LightModule::sync_light(const Object *ob, ObjectHandle &handle)
Light &light = light_map_.lookup_or_add_default(handle.object_key);
light.used = true;
if (handle.recalc != 0 || !light.initialized) {
::Light *bl_light = static_cast<::Light *>(ob->data);
bool do_jittering = inst_.shadows.do_jittering() && bl_light->mode & LA_SHADOW_JITTER;
bool jittering_changed = assign_if_different(light.do_jittering, do_jittering);
pragma37 marked this conversation as resolved Outdated

unused var

unused var

It was at some point, but not since I added the toggle on viewport playback/navigation.

It was at some point, but not since I added the toggle on viewport playback/navigation.
if (handle.recalc != 0 || !light.initialized || do_jittering || jittering_changed) {
light.initialized = true;
light.sync(inst_.shadows, ob, light_threshold_);
}
sun_lights_len_ += int(is_sun_light(light.type));
local_lights_len_ += int(!is_sun_light(light.type));
}
@ -505,6 +489,31 @@ void LightModule::debug_pass_sync()
}
}
void LightModule::sync_jitter()
{
if (!inst_.shadows.do_jittering()) {
return;
}
/* Indices inside GPU data array. */
int sun_lights_idx = 0;
int local_lights_idx = sun_lights_len_;
for (Light &light : light_map_.values()) {
int dst_idx = is_sun_light(light.type) ? sun_lights_idx++ : local_lights_idx++;
if (!light.do_jittering) {
continue;
}
if (light.directional != nullptr) {
light.directional->end_sync(light, inst_.camera, true);
}
else if (light.punctual != nullptr) {
light.punctual->end_sync(light, true);
}
light_buf_[dst_idx] = light;
}
light_buf_.push_update();
inst_.shadows.tilemap_pool.tilemaps_data.push_update();
}
void LightModule::set_view(View &view, const int2 extent)
{
float far_z = view.far_clip();

View File

@ -44,6 +44,7 @@ struct Light : public LightData, NonCopyable {
public:
bool initialized = false;
bool used = false;
bool do_jittering = false;
/** Pointers to source Shadow. Type depends on `LightData::type`. */
ShadowDirectional *directional = nullptr;
@ -63,6 +64,7 @@ struct Light : public LightData, NonCopyable {
*static_cast<LightData *>(this) = other;
this->initialized = other.initialized;
this->used = other.used;
this->do_jittering = other.do_jittering;
this->directional = other.directional;
this->punctual = other.punctual;
other.directional = nullptr;
@ -158,6 +160,8 @@ class LightModule {
void sync_light(const Object *ob, ObjectHandle &handle);
void end_sync();
void sync_jitter();
/**
* Update acceleration structure for the given view.
*/

View File

@ -767,23 +767,20 @@ static inline bool is_local_light(eLightType type)
float influence_radius_invsqr_surface; \
float influence_radius_invsqr_volume; \
/** --- Shadow Data --- */ \
/** Shift to apply to the light origin to get the shadow projection origin. */ \
packed_float3 shadow_projection_shift; \
/** Other parts of the perspective matrix. Assumes symmetric frustum. */ \
float clip_side; \
/** Number of allocated tilemap for this local light. */ \
int tilemaps_count; \
/** Scaling factor to the light shape for shadow ray casting. */ \
float shadow_scale; \
/** Shift to apply to the light origin to get the shadow projection origin. */ \
float shadow_projection_shift;
float shadow_scale;
/* Untyped local light data. Gets reinterpreted to LightSpotData and LightAreaData.
* Allow access to local light common data without casting. */
struct LightLocalData {
LOCAL_LIGHT_COMMON
/** Padding reserved for when shadow_projection_shift will become a vec3. */
float _pad0_reserved;
float _pad1_reserved;
float _pad1;
float _pad2;
@ -797,9 +794,6 @@ BLI_STATIC_ASSERT_ALIGN(LightLocalData, 16)
struct LightSpotData {
LOCAL_LIGHT_COMMON
/** Padding reserved for when shadow_projection_shift will become a vec3. */
float _pad0_reserved;
float _pad1_reserved;
/** Sphere light radius. */
float radius;
/** Scale and bias to spot equation parameter. Used for adjusting the falloff. */
@ -816,9 +810,6 @@ BLI_STATIC_ASSERT(sizeof(LightSpotData) == sizeof(LightLocalData), "Data size mu
struct LightAreaData {
LOCAL_LIGHT_COMMON
/** Padding reserved for when shadow_projection_shift will become a vec3. */
float _pad0_reserved;
float _pad1_reserved;
float _pad2;
float _pad3;
@ -831,21 +822,18 @@ BLI_STATIC_ASSERT(sizeof(LightAreaData) == sizeof(LightLocalData), "Data size mu
struct LightSunData {
float radius;
float _pad0;
float _pad1;
float _pad2;
float _pad3;
float _pad4;
/** --- Shadow Data --- */
/** Offset of the LOD min in LOD min tile units. Split positive and negative for bit-shift. */
int2 clipmap_base_offset_neg;
int2 clipmap_base_offset_pos;
/** Angle covered by the light shape for shadow ray casting. */
float shadow_angle;
/** Trace distance around the shading point. */
float shadow_trace_distance;
float _pad0;
/** --- Shadow Data --- */
/** Offset of the LOD min in LOD min tile units. Split positive and negative for bit-shift. */
int2 clipmap_base_offset_neg;
int2 clipmap_base_offset_pos;
float4 shadow_projection_rotation;
/** Offset to convert from world units to tile space of the clipmap_lod_max. */
float2 clipmap_origin;
@ -925,10 +913,12 @@ BLI_STATIC_ASSERT_ALIGN(LightData, 16)
#ifdef GPU_SHADER
# define CHECK_TYPE_PAIR(a, b)
# define CHECK_TYPE(a, b)
# define INT_AS_FLOAT intBitsToFloat
# define FLOAT_AS_INT floatBitsToInt
# define TYPECAST_NOOP
#else /* C++ */
# define INT_AS_FLOAT int_as_float
# define FLOAT_AS_INT float_as_int
# define TYPECAST_NOOP
#endif
@ -985,7 +975,9 @@ BLI_STATIC_ASSERT_ALIGN(LightData, 16)
#define SAFE_ASSIGN_FLOAT(a, b) SAFE_ASSIGN(a, TYPECAST_NOOP, float, b);
#define SAFE_ASSIGN_FLOAT2(a, b) SAFE_ASSIGN(a, TYPECAST_NOOP, float2, b);
#define SAFE_ASSIGN_FLOAT3(a, b) SAFE_ASSIGN(a, TYPECAST_NOOP, float3, b);
#define SAFE_ASSIGN_INT(a, b) SAFE_ASSIGN(a, TYPECAST_NOOP, int, b);
#define SAFE_ASSIGN_INT_AS_FLOAT(a, b) SAFE_ASSIGN(a, INT_AS_FLOAT, int, b);
#define SAFE_ASSIGN_FLOAT_AS_INT(a, b) SAFE_ASSIGN(a, FLOAT_AS_INT, float, b);
#define SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE(a, b, c) \
SAFE_ASSIGN_FLOAT_AS_INT(a.x, b); \
@ -1008,7 +1000,7 @@ static inline LightSpotData light_local_data_get(LightData light)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_volume, influence_radius_invsqr_volume)
SAFE_ASSIGN_FLOAT(clip_side, clip_side)
SAFE_ASSIGN_FLOAT(shadow_scale, shadow_scale)
SAFE_ASSIGN_FLOAT(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_FLOAT3(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_INT(tilemaps_count, tilemaps_count)
return data;
}
@ -1022,7 +1014,7 @@ static inline LightSpotData light_spot_data_get(LightData light)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_volume, influence_radius_invsqr_volume)
SAFE_ASSIGN_FLOAT(clip_side, clip_side)
SAFE_ASSIGN_FLOAT(shadow_scale, shadow_scale)
SAFE_ASSIGN_FLOAT(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_FLOAT3(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_INT(tilemaps_count, tilemaps_count)
SAFE_ASSIGN_FLOAT(radius, _pad1)
SAFE_ASSIGN_FLOAT(spot_mul, _pad2)
@ -1041,7 +1033,7 @@ static inline LightAreaData light_area_data_get(LightData light)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_volume, influence_radius_invsqr_volume)
SAFE_ASSIGN_FLOAT(clip_side, clip_side)
SAFE_ASSIGN_FLOAT(shadow_scale, shadow_scale)
SAFE_ASSIGN_FLOAT(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_FLOAT3(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_INT(tilemaps_count, tilemaps_count)
SAFE_ASSIGN_FLOAT2(size, _pad3)
return data;
@ -1051,10 +1043,15 @@ static inline LightSunData light_sun_data_get(LightData light)
{
SAFE_BEGIN(LightSunData, is_sun_light(light.type))
SAFE_ASSIGN_FLOAT(radius, radius_squared)
SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE(clipmap_base_offset_neg, shadow_scale, shadow_projection_shift)
SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE(clipmap_base_offset_pos, _pad0_reserved, _pad1_reserved)
SAFE_ASSIGN_FLOAT(shadow_angle, _pad1)
SAFE_ASSIGN_FLOAT(shadow_trace_distance, _pad2)
SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE(
clipmap_base_offset_neg, shadow_projection_shift.x, shadow_projection_shift.y)
SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE(clipmap_base_offset_pos, shadow_projection_shift.z, clip_side)
SAFE_ASSIGN_FLOAT(shadow_angle, influence_radius_max)
SAFE_ASSIGN_FLOAT(shadow_trace_distance, influence_radius_invsqr_surface)
SAFE_ASSIGN_INT_AS_FLOAT(shadow_projection_rotation.x, tilemaps_count);
SAFE_ASSIGN_FLOAT(shadow_projection_rotation.y, shadow_scale);
SAFE_ASSIGN_FLOAT(shadow_projection_rotation.z, _pad1);
SAFE_ASSIGN_FLOAT(shadow_projection_rotation.w, _pad2);
SAFE_ASSIGN_FLOAT2(clipmap_origin, _pad3)
SAFE_ASSIGN_FLOAT_AS_INT(clipmap_lod_min, _pad4)
SAFE_ASSIGN_FLOAT_AS_INT(clipmap_lod_max, _pad5)

View File

@ -9,7 +9,6 @@
*/
#include "BKE_global.hh"
#include "BLI_math_rotation.h"
#include "BLI_rect.h"
#include "eevee_instance.hh"
@ -44,7 +43,7 @@ void ShadowTileMap::sync_orthographic(const float4x4 &object_mat_,
}
grid_offset = origin_offset;
if (!equals_m4m4(object_mat.ptr(), object_mat_.ptr())) {
if (object_mat != object_mat_) {
object_mat = object_mat_;
set_dirty();
}
@ -73,7 +72,6 @@ void ShadowTileMap::sync_cubeface(const float4x4 &object_mat_,
float near_,
float far_,
float side_,
float shift,
eCubeFace face,
float lod_bias_)
{
@ -94,15 +92,14 @@ void ShadowTileMap::sync_cubeface(const float4x4 &object_mat_,
half_size = side_;
center_offset = float2(0.0f);
if (!equals_m4m4(object_mat.ptr(), object_mat_.ptr())) {
if (object_mat != object_mat_) {
object_mat = object_mat_;
set_dirty();
}
winmat = math::projection::perspective(
-half_size, half_size, -half_size, half_size, clip_near, clip_far);
viewmat = float4x4(shadow_face_mat[cubeface]) *
math::from_location<float4x4>(float3(0.0f, 0.0f, -shift)) * math::invert(object_mat);
viewmat = float4x4(shadow_face_mat[cubeface]) * math::invert(object_mat);
/* Update corners. */
float4x4 viewinv = object_mat;
@ -221,16 +218,15 @@ void ShadowTileMapPool::end_sync(ShadowModule &module)
*
* \{ */
void ShadowPunctual::sync(eLightType light_type,
void ShadowPunctual::sync(const ::Light *bl_light,
eLightType light_type,
const float4x4 &object_mat,
float cone_aperture,
float light_shape_radius,
float max_distance,
float softness_factor,
float shadow_radius)
float max_distance)
{
light_type_ = light_type;
if (is_spot_light(light_type)) {
tilemaps_needed_ = (cone_aperture > DEG2RADF(90.0f)) ? 5 : 1;
tilemaps_needed_ = (bl_light->spotsize > DEG2RADF(90.0f)) ? 5 : 1;
}
else if (is_area_light(light_type)) {
tilemaps_needed_ = 5;
@ -239,14 +235,19 @@ void ShadowPunctual::sync(eLightType light_type,
tilemaps_needed_ = 6;
}
/* 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_ = object_mat.location();
max_distance_ = max_distance;
softness_factor_ = bl_light->shadow_softness_factor;
jitter_overblur_ = bl_light->shadow_jitter_overblur_percentage / 100.0f;
position_ = float3(object_mat[3]);
softness_factor_ = softness_factor;
shadow_radius_ = shadow_radius;
if (is_area_light(light_type)) {
const bool is_irregular = ELEM(bl_light->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE);
light_radius_ = math::length(
float2(bl_light->area_size, is_irregular ? bl_light->area_sizey : bl_light->area_size));
}
else {
light_radius_ = bl_light->radius;
}
}
void ShadowPunctual::release_excess_tilemaps()
@ -320,6 +321,8 @@ void ShadowPunctual::compute_projection_boundaries(float light_radius,
*
* TODO(fclem): Explain derivation.
*/
max_lit_distance = max_ff(max_lit_distance, 4e-4f);
light_radius = min_ff(light_radius, max_lit_distance - 1e-4f);
float cos_alpha = shadow_radius / max_lit_distance;
float sin_alpha = sqrt(1.0f - math::square(cos_alpha));
float near_shift = M_SQRT2 * shadow_radius * 0.5f * (sin_alpha - cos_alpha);
@ -336,41 +339,68 @@ void ShadowPunctual::compute_projection_boundaries(float light_radius,
}
}
void ShadowPunctual::end_sync(Light &light, float lod_bias)
void ShadowPunctual::end_sync(Light &light, bool is_render_sync)
{
ShadowTileMapPool &tilemap_pool = shadows_.tilemap_pool;
if (!is_render_sync) {
/* Acquire missing tile-maps. */
while (tilemaps_.size() < tilemaps_needed_) {
tilemaps_.append(tilemap_pool.acquire());
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
for (ShadowTileMap *tilemap : tilemaps_) {
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
tilemap_pool.tilemaps_data.append(*tilemap);
}
if (light.do_jittering) {
/* Just acquire the tilemaps, we must sync them at render time. */
return;
}
}
float shadow_radius = shadow_radius_get();
if (light.do_jittering) {
shadow_radius *= jitter_overblur_;
}
float side, near, far;
compute_projection_boundaries(light_radius_, shadow_radius_, max_distance_, near, far, side);
compute_projection_boundaries(light_radius_, shadow_radius, max_distance_, near, far, side);
/* Shift shadow map origin for area light to avoid clipping nearby geometry. */
float shift = is_area_light(light.type) ? near : 0.0f;
float3 origin_shift = float3(0.0f);
float4x4 obmat_tmp = light.object_mat;
/* Clear embedded custom data. */
obmat_tmp[0][3] = obmat_tmp[1][3] = obmat_tmp[2][3] = 0.0f;
obmat_tmp[3][3] = 1.0f;
/* Acquire missing tile-maps. */
while (tilemaps_.size() < tilemaps_needed_) {
tilemaps_.append(tilemap_pool.acquire());
if (is_render_sync) {
/* Render sync is only needed for jittered shadows. */
BLI_assert(light.do_jittering);
Sampling &sampling = shadows_.inst_.sampling;
/* TODO: de-correlate. */
float3 random = sampling.rng_3d_get(SAMPLING_SHADOW_U);
if (is_area_light(light.type)) {
origin_shift = light.type == LIGHT_RECT ? float3(random.xy() * 2.0f - 1.0f, 0.0f) :
float3(sampling.sample_disk(random.xy()), 0.0f);
origin_shift.x *= light.area.size.x;
origin_shift.y *= light.area.size.y;
}
else {
origin_shift = sampling.sample_ball(random);
origin_shift *= shadow_radius_get();
}
pragma37 marked this conversation as resolved Outdated

This can be removed. And you can use light.object_mat directly as it is junk free now!.

This can be removed. And you can use `light.object_mat` directly as it is junk free now!.
}
tilemaps_[Z_NEG]->sync_cubeface(obmat_tmp, near, far, side, shift, Z_NEG, lod_bias);
if (tilemaps_needed_ >= 5) {
tilemaps_[X_POS]->sync_cubeface(obmat_tmp, near, far, side, shift, X_POS, lod_bias);
tilemaps_[X_NEG]->sync_cubeface(obmat_tmp, near, far, side, shift, X_NEG, lod_bias);
tilemaps_[Y_POS]->sync_cubeface(obmat_tmp, near, far, side, shift, Y_POS, lod_bias);
tilemaps_[Y_NEG]->sync_cubeface(obmat_tmp, near, far, side, shift, Y_NEG, lod_bias);
}
if (tilemaps_needed_ == 6) {
tilemaps_[Z_POS]->sync_cubeface(obmat_tmp, near, far, side, shift, Z_POS, lod_bias);
if (is_area_light(light.type)) {
/* Shift shadow map origin, so the near plane overlaps with the area light plane. */
origin_shift.z += near;
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
float4x4 shifted_mat = light.object_mat * math::from_location<float4x4>(origin_shift);
for (int i : IndexRange(tilemaps_needed_)) {
tilemaps_[i]->sync_cubeface(shifted_mat, near, far, side, eCubeFace(i), light.lod_bias);
tilemap_pool.tilemaps_data[light.tilemap_index + i] = *tilemaps_[i];
tilemaps_[i]->set_updated();
}
light.local.tilemaps_count = tilemaps_needed_;
/* TODO(fclem): `as_uint()`. */
union {
float f;
@ -381,14 +411,9 @@ void ShadowPunctual::end_sync(Light &light, float lod_bias)
as_int.f = far;
light.clip_far = as_int.i;
light.local.clip_side = side;
light.local.shadow_projection_shift = shift;
light.local.shadow_scale = softness_factor_;
for (ShadowTileMap *tilemap : tilemaps_) {
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
tilemap_pool.tilemaps_data.append(*tilemap);
tilemap->set_updated();
}
light.local.shadow_projection_shift = origin_shift;
light.local.shadow_scale = softness_factor_get(light.do_jittering);
light.local.tilemaps_count = tilemaps_needed_;
}
/** \} */
@ -509,9 +534,7 @@ void ShadowDirectional::cascade_tilemaps_distribution(Light &light, const Camera
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);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
shadows_.tilemap_pool.tilemaps_data[light.tilemap_index + i] = *tilemap;
tilemap->set_updated();
}
@ -559,9 +582,7 @@ IndexRange ShadowDirectional::clipmap_level_range(const Camera &camera)
return range;
}
void ShadowDirectional::clipmap_tilemaps_distribution(Light &light,
const Camera &camera,
float lod_bias)
void ShadowDirectional::clipmap_tilemaps_distribution(Light &light, const Camera &camera)
{
for (int lod : IndexRange(levels_range.size())) {
ShadowTileMap *tilemap = tilemaps_[lod];
@ -575,10 +596,8 @@ void ShadowDirectional::clipmap_tilemaps_distribution(Light &light,
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);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
object_mat_, level_offset, level, light.lod_bias, SHADOW_PROJECTION_CLIPMAP);
shadows_.tilemap_pool.tilemaps_data[light.tilemap_index + lod] = *tilemap;
tilemap->set_updated();
}
@ -614,25 +633,21 @@ void ShadowDirectional::clipmap_tilemaps_distribution(Light &light,
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 trace_distance)
void ShadowDirectional::sync(const ::Light *bl_light,
const float4x4 &object_mat,
float min_resolution)
{
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);
light_angle_ = bl_light->sun_angle;
min_resolution_ = min_resolution;
disk_shape_angle_ = min_ff(shadow_disk_angle, DEG2RADF(179.9f)) / 2.0f;
trace_distance_ = trace_distance;
trace_distance_ = bl_light->shadow_trace_distance;
softness_factor_ = bl_light->shadow_softness_factor;
jitter_overblur_ = bl_light->shadow_jitter_overblur_percentage / 100.0f;
}
void ShadowDirectional::release_excess_tilemaps(const Camera &camera, float lod_bias)
@ -657,43 +672,82 @@ void ShadowDirectional::release_excess_tilemaps(const Camera &camera, float lod_
levels_range = isect_range;
}
void ShadowDirectional::end_sync(Light &light, const Camera &camera, float lod_bias)
void ShadowDirectional::end_sync(Light &light, const Camera &camera, bool is_render_sync)
{
ShadowTileMapPool &tilemap_pool = shadows_.tilemap_pool;
IndexRange levels_new = directional_distribution_type_get(camera) == SHADOW_PROJECTION_CASCADE ?
cascade_level_range(camera, lod_bias) :
clipmap_level_range(camera);
if (levels_range != levels_new) {
/* Acquire missing tile-maps. */
IndexRange isect_range = levels_new.intersect(levels_range);
int64_t before_range = isect_range.start() - levels_new.start();
int64_t after_range = levels_new.one_after_last() - isect_range.one_after_last();
Vector<ShadowTileMap *> cached_tilemaps = tilemaps_;
tilemaps_.clear();
for (int64_t i = 0; i < before_range; i++) {
tilemaps_.append(tilemap_pool.acquire());
if (!is_render_sync) {
IndexRange levels_new;
if (directional_distribution_type_get(camera) == SHADOW_PROJECTION_CASCADE) {
levels_new = cascade_level_range(camera, light.lod_bias);
}
/* Keep cached LOD's. */
tilemaps_.extend(cached_tilemaps);
for (int64_t i = 0; i < after_range; i++) {
tilemaps_.append(tilemap_pool.acquire());
else {
levels_new = clipmap_level_range(camera);
}
if (levels_range != levels_new) {
/* Acquire missing tile-maps. */
IndexRange isect_range = levels_new.intersect(levels_range);
int64_t before_range = isect_range.start() - levels_new.start();
int64_t after_range = levels_new.one_after_last() - isect_range.one_after_last();
Vector<ShadowTileMap *> cached_tilemaps = tilemaps_;
tilemaps_.clear();
for (int64_t i = 0; i < before_range; i++) {
tilemaps_.append(tilemap_pool.acquire());
}
/* Keep cached LOD's. */
tilemaps_.extend(cached_tilemaps);
for (int64_t i = 0; i < after_range; i++) {
tilemaps_.append(tilemap_pool.acquire());
}
levels_range = levels_new;
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
for (int lod : IndexRange(levels_range.size())) {
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
tilemap_pool.tilemaps_data.append(*tilemaps_[lod]);
}
if (light.do_jittering) {
/* Just acquire the tilemaps, we must sync them at render time. */
return;
}
levels_range = levels_new;
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
float3x3 jitter_mat = float3x3::identity();
if (is_render_sync) {
/* Render sync is only needed for jittered shadows. */
BLI_assert(light.do_jittering);
Sampling &sampling = shadows_.inst_.sampling;
/* TODO: de-correlate. */
float2 random = sampling.rng_2d_get(SAMPLING_SHADOW_U);
float2 r_disk = sampling.sample_disk(random) * shadow_radius_get();
pragma37 marked this conversation as resolved Outdated

I am confused, you are jittering the light matrix that is use for shading? This is wrong as it changes the shading and doesn't even change the shadow projection.

Although the shadowing looks correct. So you should be able to generate the tilemaps with this matrix and only use this matrix for shadowing (have to find trick to pack it inside the LightData, might be able to pack a quaternion in there).

I am confused, you are jittering the light matrix that is use for shading? This is wrong as it changes the shading and doesn't even change the shadow projection. Although the shadowing looks correct. So you should be able to generate the tilemaps with this matrix and only use this matrix for shadowing (have to find trick to pack it inside the LightData, might be able to pack a quaternion in there).

Yes, I mentioned it at the last week meeting, and you seemed to agree (?).
Maybe I didn't explain it properly.

I don't understand what you mean by not changing the shadow projection.

The shading is jittered as well, which is not ideal, but I think the final image should converge to the same result anyway.

Yes, I mentioned it at the last week meeting, and you seemed to agree (?). Maybe I didn't explain it properly. I don't understand what you mean by not changing the shadow projection. The shading is jittered as well, which is not ideal, but I think the final image should converge to the same result anyway.

Yes, I mentioned it at the last week meeting, and you seemed to agree (?).

Rotating the matrix to render and sample the shadow map is the right approach, but using it for shading is not. I thought you mentioned this because you looked up what legacy EEVEE was doing (which is effectively what I just mentioned).

I don't understand what you mean by not changing the shadow projection.

I mean the tilemap.viewmat is not changed, which is the viewmat the shadow is rendered with. So you effectively only jitter the tracing / projection direction (I think, not sure) but the shadow maps are still render from the initial position of the light.

The shading is jittered as well, which is not ideal, but I think the final image should converge to the same result anyway.

No, it doesn't converge to the same result. Make a sunlight with 20° angle and look up the difference on a smooth reflective surface (i.e: metal).

> Yes, I mentioned it at the last week meeting, and you seemed to agree (?). Rotating the matrix to render and sample the shadow map is the right approach, but using it for shading is not. I thought you mentioned this because you looked up what legacy EEVEE was doing (which is effectively what I just mentioned). > I don't understand what you mean by not changing the shadow projection. I mean the `tilemap.viewmat` is not changed, which is the `viewmat` the shadow is rendered with. So you effectively only jitter the tracing / projection direction (I think, not sure) but the shadow maps are still render from the initial position of the light. > The shading is jittered as well, which is not ideal, but I think the final image should converge to the same result anyway. No, it doesn't converge to the same result. Make a sunlight with 20° angle and look up the difference on a smooth reflective surface (i.e: metal).

I mean the tilemap.viewmat is not changed, which is the viewmat the shadow is rendered with. So you effectively only jitter the tracing / projection direction (I think, not sure) but the shadow maps are still render from the initial position of the light.

It's changed. This is changing object_mat_ just before calling the tilemaps_distribution, which is what those functions pass to tilemap->sync_orthographic.

I don't understand why the position of a directional light would need to be changed. They're not supposed to have a position at all?

> I mean the `tilemap.viewmat` is not changed, which is the `viewmat` the shadow is rendered with. So you effectively only jitter the tracing / projection direction (I think, not sure) but the shadow maps are still render from the initial position of the light. It's changed. This is changing `object_mat_` just before calling the `tilemaps_distribution`, which is what those functions pass to `tilemap->sync_orthographic`. I don't understand why the position of a directional light would need to be changed. They're not supposed to have a position at all?

It's changed. This is changing object_mat_ just before calling the tilemaps_distribution, which is what those functions pass to tilemap->sync_orthographic.

Ok, my bad on this.

I don't understand why the position of a directional light would need to be changed. They're not supposed to have a position at all?

Sorry, I mean the initial light direction. But whatever, I was wrong.

> It's changed. This is changing object_mat_ just before calling the tilemaps_distribution, which is what those functions pass to tilemap->sync_orthographic. Ok, my bad on this. > I don't understand why the position of a directional light would need to be changed. They're not supposed to have a position at all? Sorry, I mean the initial light direction. But whatever, I was wrong.

I've made the split of jitter rotation into its own quaternion, but it's still WIP.

I've made the split of jitter rotation into its own quaternion, but it's still WIP.
float3 jitter = object_mat_.x_axis() * r_disk.x + object_mat_.y_axis() * r_disk.y;
jitter_mat[2] += jitter;
jitter_mat = math::orthogonalize(jitter_mat, math::Axis::Z);
jitter_mat = math::normalize(jitter_mat);
object_mat_ = light.object_mat;
object_mat_.location() = float3(0.0f);
object_mat_ = object_mat_ * float4x4(jitter_mat);
}
math::Quaternion q = math::to_quaternion(jitter_mat);
light.sun.shadow_projection_rotation = {q.x, q.y, q.z, q.w};
light.clip_near = 0x7F7FFFFF; /* floatBitsToOrderedInt(FLT_MAX) */
light.clip_far = int(0xFF7FFFFFu ^ 0x7FFFFFFFu); /* floatBitsToOrderedInt(-FLT_MAX) */
light.sun.shadow_trace_distance = trace_distance_;
light.sun.shadow_angle = disk_shape_angle_;
light.sun.shadow_angle = shadow_angle_get(light.do_jittering);
if (directional_distribution_type_get(camera) == SHADOW_PROJECTION_CASCADE) {
cascade_tilemaps_distribution(light, camera);
}
else {
clipmap_tilemaps_distribution(light, camera, lod_bias);
clipmap_tilemaps_distribution(light, camera);
}
}
@ -735,8 +789,9 @@ void ShadowModule::init()
}
}
jittered_transparency_ = !inst_.is_viewport() ||
scene.eevee.flag & SCE_EEVEE_SHADOW_JITTERED_VIEWPORT;
do_jittering_ = !inst_.is_viewport() ||
((scene.eevee.flag & SCE_EEVEE_SHADOW_JITTERED_VIEWPORT) &&
!inst_.is_navigating() && !inst_.is_playback());
data_.ray_count = clamp_i(inst_.scene->eevee.shadow_ray_count, 1, SHADOW_MAX_RAY);
data_.step_count = clamp_i(inst_.scene->eevee.shadow_step_count, 1, SHADOW_MAX_STEP);
@ -906,7 +961,7 @@ void ShadowModule::sync_object(const Object *ob,
ShadowObject &shadow_ob = objects_.lookup_or_add_default(handle.object_key);
shadow_ob.used = true;
const bool is_initialized = shadow_ob.resource_handle.raw != 0;
const bool has_jittered_transparency = has_transparent_shadows && jittered_transparency_;
const bool has_jittered_transparency = has_transparent_shadows && do_jittering_;
if (is_shadow_caster && (handle.recalc || !is_initialized || has_jittered_transparency)) {
if (handle.recalc && is_initialized) {
past_casters_updated_.append(shadow_ob.resource_handle.raw);
@ -952,10 +1007,10 @@ void ShadowModule::end_sync()
light.tilemap_index = LIGHT_NO_SHADOW;
}
else if (light.directional != nullptr) {
light.directional->end_sync(light, inst_.camera, light.lod_bias);
light.directional->end_sync(light, inst_.camera);
}
else if (light.punctual != nullptr) {
light.punctual->end_sync(light, light.lod_bias);
light.punctual->end_sync(light);
}
else {
light.tilemap_index = LIGHT_NO_SHADOW;

View File

@ -10,6 +10,7 @@
#pragma once
#include "BLI_math_rotation.h"
#include "BLI_pool.hh"
#include "BLI_vector.hh"
@ -17,6 +18,7 @@
#include "eevee_camera.hh"
#include "eevee_material.hh"
#include "eevee_sampling.hh"
#include "eevee_shader.hh"
#include "eevee_shader_shared.hh"
#include "eevee_sync.hh"
@ -107,7 +109,6 @@ struct ShadowTileMap : public ShadowTileMapData {
float near,
float far,
float side,
float shift,
eCubeFace face,
float lod_bias_);
@ -216,7 +217,7 @@ class ShadowModule {
/* Used to call caster_update_ps_ only once per sync (Initialized on begin_sync). */
bool update_casters_ = false;
bool jittered_transparency_ = false;
bool do_jittering_ = false;
/* -------------------------------------------------------------------- */
/** \name Tile-map Management
@ -371,6 +372,11 @@ class ShadowModule {
return lod_bias_;
}
bool do_jittering()
{
return do_jittering_;
}
private:
void remove_unused();
void debug_page_map_call(DRWPass *pass);
@ -400,19 +406,18 @@ class ShadowPunctual : public NonCopyable, NonMovable {
Vector<ShadowTileMap *> tilemaps_;
/** Shape type. */
eLightType light_type_;
/** Light position. */
float3 position_;
/** Used to compute near and far clip distances. */
float max_distance_, light_radius_;
/** Number of tile-maps needed to cover the light angular extents. */
int tilemaps_needed_;
/** Scaling factor to the light shape for shadow ray casting. */
/** Light position. */
float3 position_;
/** Unmodified light shape radius. */
float light_radius_;
/** Used to compute near and far clip distances. */
float max_distance_;
/** Scaling factor to the light shape for shadows. */
float softness_factor_;
/**
* `radius * softness_factor` (Bypasses LightModule radius modifications
* to avoid unnecessary padding in the shadow projection).
*/
float shadow_radius_;
/** Softness scale for jittered shadows. */
float jitter_overblur_;
public:
ShadowPunctual(ShadowModule &module) : shadows_(module){};
@ -427,13 +432,10 @@ class ShadowPunctual : public NonCopyable, NonMovable {
/**
* Sync shadow parameters but do not allocate any shadow tile-maps.
*/
void sync(eLightType light_type,
void sync(const ::Light *bl_light,
eLightType light_type,
const float4x4 &object_mat,
float cone_aperture,
float light_shape_radius,
float max_distance,
float softness_factor,
float shadow_radius);
float max_distance);
/**
* Release the tile-maps that will not be used in the current frame.
@ -443,9 +445,21 @@ class ShadowPunctual : public NonCopyable, NonMovable {
/**
* Allocate shadow tile-maps and setup views for rendering.
*/
void end_sync(Light &light, float lod_bias);
void end_sync(Light &light, bool is_render_sync = false);
private:
/** Scaling factor to the light shape for shadow tracing. */
float softness_factor_get(bool do_jittering)
{
return softness_factor_ * (do_jittering ? jitter_overblur_ : 1.0f);
}
/** Light shape radius used for shadows. */
float shadow_radius_get()
{
return light_radius_ * softness_factor_;
}
/**
* Compute the projection matrix inputs.
* Make sure that the projection encompass all possible rays that can start in the projection
@ -470,10 +484,14 @@ class ShadowDirectional : public NonCopyable, NonMovable {
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_;
/** Unmodified light shape angle. */
float light_angle_;
/** Maximum distance a shadow map ray can be travel. */
float trace_distance_;
/** Scaling factor to the light shape for shadows. */
float softness_factor_;
/** Softness scale for jittered shadows. */
float jitter_overblur_;
public:
ShadowDirectional(ShadowModule &module) : shadows_(module){};
@ -488,10 +506,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 trace_distance);
void sync(const ::Light *bl_light, const float4x4 &object_mat, float min_resolution);
/**
* Release the tile-maps that will not be used in the current frame.
@ -501,7 +516,7 @@ class ShadowDirectional : public NonCopyable, NonMovable {
/**
* Allocate shadow tile-maps and setup views for rendering.
*/
void end_sync(Light &light, const Camera &camera, float lod_bias);
void end_sync(Light &light, const Camera &camera, bool is_render_sync = false);
/* Return coverage of the whole tile-map in world unit. */
static float coverage_get(int lvl)
@ -518,11 +533,29 @@ class ShadowDirectional : public NonCopyable, NonMovable {
}
private:
/** Scaling factor to the light shape for shadow tracing. */
float softness_factor_get(bool do_jittering)
{
return softness_factor_ * (do_jittering ? jitter_overblur_ : 1.0f);
}
/** Light shape angle used for shadows. */
float shadow_angle_get(bool do_jittering)
{
return min_ff(light_angle_ * softness_factor_get(do_jittering), DEG2RADF(179.9f)) / 2.0f;
}
/** Light shape radius used for shadows. */
float shadow_radius_get()
{
return tanf(min_ff(light_angle_ * softness_factor_, DEG2RADF(179.9f)) / 2.0f);
}
IndexRange clipmap_level_range(const Camera &camera);
IndexRange cascade_level_range(const Camera &camera, float lod_bias);
void cascade_tilemaps_distribution(Light &light, const Camera &camera);
void clipmap_tilemaps_distribution(Light &light, const Camera &camera, float lod_bias);
void clipmap_tilemaps_distribution(Light &light, const Camera &camera);
void cascade_tilemaps_distribution_near_far_points(const Camera &camera,
float3 &near_point,

View File

@ -70,7 +70,7 @@ void light_shadow_single(uint l_idx,
#endif
ShadowEvalResult result = shadow_eval(
light, is_directional, is_transmission, false, P, Ng, Ng, ray_count, ray_step_count);
light, is_directional, is_transmission, false, P, Ng, Ng, 0.0, ray_count, ray_step_count);
shadow_bits |= shadow_pack(result.light_visibilty, ray_count, shift);
shift += ray_count;
@ -252,15 +252,14 @@ void light_eval_single(uint l_idx,
shadow = shadow_unpack(packed_shadows, ray_count, shift);
shift += ray_count;
#else
vec3 shadow_P = (is_transmission) ? P + lv.L * stack.cl[0].shadow_offset : P;
ShadowEvalResult result = shadow_eval(light,
is_directional,
is_transmission,
is_translucent_with_thickness,
shadow_P,
P,
Ng,
lv.L,
shadow_vector_get(light, is_directional, P),
stack.cl[0].shadow_offset,
ray_count,
ray_step_count);
pragma37 marked this conversation as resolved

At this point, just pass stack.cl[0].shadow_offset to shadow_eval. It will be needed to fix the SSS shadow anyway.

Also prefer calling shadow_dir instead of L. L is reserved for direction towards the light (except in small function / sampling context where it can just refer to outgoing ray direction). But I don't think it is worth changing all existing usage of it right now.

At this point, just pass `stack.cl[0].shadow_offset` to `shadow_eval`. It will be needed to fix the SSS shadow anyway. Also prefer calling `shadow_dir` instead of `L`. `L` is reserved for direction towards the light (except in small function / sampling context where it can just refer to outgoing ray direction). But I don't think it is worth changing all existing usage of it right now.
shadow = result.light_visibilty;

View File

@ -216,7 +216,7 @@ float light_sphere_disk_radius(float sphere_radius, float distance_to_sphere)
{
/* The sine of the half-angle spanned by a sphere light is equal to the tangent of the
* half-angle spanned by a disk light with the same radius. */
return sphere_radius * inversesqrt(1.0 - square(sphere_radius / distance_to_sphere));
return sphere_radius * inversesqrt(max(1e-8, 1.0 - square(sphere_radius / distance_to_sphere)));
}
float light_ltc(

View File

@ -4,6 +4,7 @@
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_rotation_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
#define EEVEE_SHADOW_LIB
@ -14,6 +15,39 @@
# define SHADOW_ATLAS_TYPE usampler2DArray
#endif
vec3 shadow_vector_get(LightData light, const bool is_directional, vec3 P)
{
if (is_directional) {
vec3 b = float3(0, 0, 1);
b = rotate(as_quaternion(light_sun_data_get(light).shadow_projection_rotation), b);
return mat3(light.object_mat) * b;
}
else {
return safe_normalize((light._position + light_local_data_get(light).shadow_projection_shift) -
P);
}
}
vec3 light_local_to_shadow_local(LightData light, vec3 lP, const bool is_directional)

Use const bool is_directional for all three functions.

Use `const bool is_directional` for all three functions.

Done, but why?

Done, but why?
{
if (is_directional) {
return rotate(invert(as_quaternion(light_sun_data_get(light).shadow_projection_rotation)), lP);
}
else {
return lP - light_local_data_get(light).shadow_projection_shift;
}
}
vec3 shadow_local_to_light_local(LightData light, vec3 lP, const bool is_directional)
{
if (is_directional) {
return rotate(as_quaternion(light_sun_data_get(light).shadow_projection_rotation), lP);
}
else {
return lP + light_local_data_get(light).shadow_projection_shift;
}
}
struct ShadowSampleParams {
vec3 lP;
vec3 uv;
@ -152,12 +186,14 @@ vec3 shadow_punctual_reconstruct_position(ShadowSampleParams params,
vec3 lP = project_point(wininv, clip_P);
int face_id = params.tilemap_index - light.tilemap_index;
lP = shadow_punctual_face_local_to_local_position(face_id, lP);
lP = shadow_local_to_light_local(light, lP, false);
return mat3(light.object_mat) * lP + light._position;
}
ShadowSampleParams shadow_punctual_sample_params_get(LightData light, vec3 P)
{
vec3 lP = (P - light._position) * mat3(light.object_mat);
lP = light_local_to_shadow_local(light, lP, false);
int face_id = shadow_punctual_face_index_get(lP);
/* Local Light Space > Face Local (View) Space. */
@ -234,6 +270,8 @@ vec3 shadow_directional_reconstruct_position(ShadowSampleParams params, LightDat
lP.xy = clipmap_pos + info.clipmap_origin;
lP.z = (params.uv.z + info.clip_near) * -1.0;
lP = shadow_local_to_light_local(light, lP, true);
return mat3(light.object_mat) * lP;
}
@ -242,6 +280,8 @@ ShadowSampleParams shadow_directional_sample_params_get(usampler2D tilemaps_tx,
vec3 P)
{
vec3 lP = P * mat3(light.object_mat);
lP = light_local_to_shadow_local(light, lP, true);
ShadowDirectionalSampleInfo info = shadow_directional_sample_info_get(light, lP);
ShadowCoordinates coord = shadow_directional_coordinates(light, lP);

View File

@ -35,6 +35,7 @@ void shadow_tag_usage_tilemap_directional_at_level(uint l_idx, vec3 P, int level
}
vec3 lP = light_world_to_local(light, P);
lP = light_local_to_shadow_local(light, lP, true);
level = clamp(
level, light_sun_data_get(light).clipmap_lod_min, light_sun_data_get(light).clipmap_lod_max);
@ -52,6 +53,7 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
}
vec3 lP = light_world_to_local(light, P);
lP = light_local_to_shadow_local(light, lP, true);
/* TODO(Miguel Pozo): Implement lod_bias support. */
if (radius == 0.0) {
@ -61,7 +63,11 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
}
else {
vec3 start_lP = light_world_to_local(light, P - V * radius);
start_lP = light_local_to_shadow_local(light, start_lP, true);
vec3 end_lP = light_world_to_local(light, P + V * radius);
end_lP = light_local_to_shadow_local(light, end_lP, true);
int min_level = shadow_directional_level(light, start_lP - light._position);
int max_level = shadow_directional_level(light, end_lP - light._position);
@ -90,6 +96,7 @@ void shadow_tag_usage_tilemap_punctual(
}
vec3 lP = light_world_to_local(light, P - light._position);
float dist_to_light = max(length(lP) - radius, 1e-5);
if (dist_to_light > light_local_data_get(light).influence_radius_max) {
return;
@ -108,8 +115,7 @@ void shadow_tag_usage_tilemap_punctual(
}
}
/* TODO(fclem): 3D shift for jittered soft shadows. */
lP += vec3(0.0, 0.0, -light_local_data_get(light).shadow_projection_shift);
lP = light_local_to_shadow_local(light, lP, false);
float footprint_ratio = shadow_punctual_footprint_ratio(
light, P, drw_view_is_perspective(), dist_to_cam, tilemap_proj_ratio);

View File

@ -30,10 +30,9 @@ void set_clipmap_data(inout LightData light,
void set_clipmap_base_offset(inout LightData light, ivec2 clipmap_base_offset)
{
/* WATCH: Can get out of sync with light_sun_data_get(). */
light.do_not_access_directly.shadow_scale = intBitsToFloat(0);
light.do_not_access_directly.shadow_projection_shift = intBitsToFloat(0);
light.do_not_access_directly._pad0_reserved = intBitsToFloat(clipmap_base_offset.x);
light.do_not_access_directly._pad1_reserved = intBitsToFloat(clipmap_base_offset.y);
light.do_not_access_directly.shadow_projection_shift.xy = vec2(intBitsToFloat(0));
light.do_not_access_directly.shadow_projection_shift.z = intBitsToFloat(clipmap_base_offset.x);

This needs to be fixed.

This needs to be fixed.

I've updated the code, but I'm not sure if it's right.
The test is disabled and fails even in main if I enable it back.

I've updated the code, but I'm not sure if it's right. The test is disabled and fails even in main if I enable it back.
light.do_not_access_directly.clip_side = intBitsToFloat(clipmap_base_offset.y);
}
void main()

View File

@ -11,9 +11,11 @@
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_rotation_lib.glsl)
#pragma BLENDER_REQUIRE(draw_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
shared int global_min;
shared int global_max;
@ -52,8 +54,10 @@ void main()
float local_min = FLT_MAX;
float local_max = -FLT_MAX;
vec3 shadow_forward_z_axis = -shadow_vector_get(light, true, vec3(0));
pragma37 marked this conversation as resolved Outdated

Negate and rename shadow_forward_z_axis.

Negate and rename `shadow_forward_z_axis`.
for (int i = 0; i < 8; i++) {
float z = dot(box.corners[i].xyz, -light._back);
float z = dot(box.corners[i].xyz, shadow_forward_z_axis);
local_min = min(local_min, z);
local_max = max(local_max, z);
}

View File

@ -266,7 +266,7 @@ vec3 shadow_punctual_face_local_to_local_position(int face_id, vec3 fL)
}
}
/* Turns local light coordinate into shadow region index. Matches eCubeFace order.
/* Turns local shadow coordinate into shadow region index. Matches eCubeFace order.
* \note lL does not need to be normalized. */
int shadow_punctual_face_index_get(vec3 lL)
{

View File

@ -185,16 +185,16 @@ ShadowMapTraceResult shadow_map_trace_finish(ShadowMapTracingState state)
/** \} */
/* If the ray direction `L` is below the horizon defined by N (normalized) at the shading point,
* push it just above the horizon so that this ray will never be below it and produce
/* If the ray direction `shadow_dir` is below the horizon defined by N (normalized) at the shading
* point, push it just above the horizon so that this ray will never be below it and produce
* over-shadowing (since light evaluation already clips the light shape). */
vec3 shadow_ray_above_horizon_ensure(vec3 L, vec3 N)
vec3 shadow_ray_above_horizon_ensure(vec3 shadow_dir, vec3 N)
{
float distance_to_plan = dot(L, -N);
float distance_to_plan = dot(shadow_dir, -N);
if (distance_to_plan > 0.0) {
L += N * (0.01 + distance_to_plan);
shadow_dir += N * (0.01 + distance_to_plan);
}
return L;
return shadow_dir;
}
/* ---------------------------------------------------------------------- */
@ -309,16 +309,13 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, vec2 random_2d,
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light_local_data_get(light).clip_side;
/* TODO(fclem): 3D shift for jittered soft shadows. */
vec3 projection_origin = vec3(0.0, 0.0, -light_local_data_get(light).shadow_projection_shift);
vec3 direction;
if (is_area_light(light.type)) {
random_2d *= light_area_data_get(light).size;
vec3 point_on_light_shape = vec3(random_2d, 0.0);
/* Progressively blend the shape back to the projection origin. */
point_on_light_shape = mix(
-projection_origin, point_on_light_shape, light_local_data_get(light).shadow_scale);
point_on_light_shape = point_on_light_shape * light_local_data_get(light).shadow_scale;
point_on_light_shape.z -= light_local_data_get(light).shadow_projection_shift.z;
direction = point_on_light_shape - lP;
direction = shadow_ray_above_horizon_ensure(direction, lNg);
@ -331,14 +328,13 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, vec2 random_2d,
}
else {
float dist;
vec3 L = normalize_and_get_length(lP, dist);
vec3 shadow_dir = normalize_and_get_length(lP, dist);
/* Disk rotated towards light vector. */
vec3 right, up;
make_orthonormal_basis(L, right, up);
make_orthonormal_basis(shadow_dir, right, up);
float shape_radius = light_spot_data_get(light).radius;
if (is_sphere_light(light.type)) {
/* FIXME(weizhen): this is not well-defined when `dist < light.spot.radius`. */
shape_radius = light_sphere_disk_radius(shape_radius, dist);
}
random_2d *= shape_radius;
@ -355,13 +351,13 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, vec2 random_2d,
}
/* Apply shadow origin shift. */
vec3 local_ray_start = lP + projection_origin;
vec3 local_ray_start = lP;
vec3 local_ray_end = local_ray_start + direction;
/* Use an offset in the ray direction to jitter which face is traced.
* This helps hiding some harsh discontinuity. */
int face_id = shadow_punctual_face_index_get(local_ray_start + direction * 0.5);
/* Local Light Space > Face Local (View) Space. */
/* Local Shadow Space > Face Local (View) Space. */
vec3 view_ray_start = shadow_punctual_local_position_to_face_local(face_id, local_ray_start);
vec3 view_ray_end = shadow_punctual_local_position_to_face_local(face_id, local_ray_end);
@ -405,15 +401,15 @@ SHADOW_MAP_TRACE_FN(ShadowRayPunctual)
/* Compute the world space offset of the shading position required for
* stochastic percentage closer filtering of shadow-maps. */
vec3 shadow_pcf_offset(LightData light, const bool is_directional, vec3 P, vec3 Ng, vec2 random)
vec3 shadow_pcf_offset(
LightData light, const bool is_directional, vec3 shadow_dir, vec3 P, vec3 Ng, vec2 random)
{
if (light.pcf_radius <= 0.001) {
/* Early return. */
return vec3(0.0);
}
vec3 L = light_vector_get(light, is_directional, P).L;
if (dot(L, Ng) < 0.001) {
if (dot(shadow_dir, Ng) < 0.001) {
/* Don't apply PCF to almost perpendicular,
* since we can't project the offset to the surface. */
return vec3(0.0);
@ -482,22 +478,22 @@ vec3 shadow_pcf_offset(LightData light, const bool is_directional, vec3 P, vec3
#ifdef GPU_NVIDIA
/* Workaround for a bug in the Nvidia shader compiler.
* If we don't compute L here again, it breaks shadows on reflection probes. */
L = light_vector_get(light, is_directional, P).L;
* If we don't compute shadow_dir here again, it breaks shadows on reflection probes. */
shadow_dir = shadow_vector_get(light, is_directional, P);
#endif
if (abs(dot(Ng, L)) > 0.999) {
if (abs(dot(Ng, shadow_dir)) > 0.999) {
return ws_offset;
}
offset_P = line_plane_intersect(offset_P, L, P, Ng);
offset_P = line_plane_intersect(offset_P, shadow_dir, P, Ng);
ws_offset = offset_P - P;
if (dot(ws_offset, L) < 0.0) {
if (dot(ws_offset, shadow_dir) < 0.0) {
/* Project the offset position into the perpendicular plane, since it's closer to the light
* (avoids overshadowing at geometry angles). */
vec3 perpendicular_plane_normal = cross(Ng, normalize(cross(Ng, L)));
offset_P = line_plane_intersect(offset_P, L, P, perpendicular_plane_normal);
vec3 perpendicular_plane_normal = cross(Ng, normalize(cross(Ng, shadow_dir)));
offset_P = line_plane_intersect(offset_P, shadow_dir, P, perpendicular_plane_normal);
ws_offset = offset_P - P;
}
@ -513,10 +509,23 @@ ShadowEvalResult shadow_eval(LightData light,
bool is_translucent_with_thickness,
vec3 P,
vec3 Ng,
vec3 L,
vec3 shadow_dir,
float shadow_offset,
int ray_count,
int ray_step_count)
{
if (!is_directional && (is_point_light(light.type) || is_spot_light(light.type))) {
bool inside_light = distance_squared(P, light._position) <
light_spot_data_get(light).radius_squared;
if (inside_light) {
/* Early out. */
ShadowEvalResult result;
result.light_visibilty = 1.0;
result.occluder_distance = 0.0;
return result;
}
}
#ifdef EEVEE_SAMPLING_DATA
# ifdef GPU_FRAGMENT_SHADER
vec2 pixel = floor(gl_FragCoord.xy);
@ -535,13 +544,20 @@ ShadowEvalResult shadow_eval(LightData light,
float normal_offset = 0.02;
#endif
P += shadow_pcf_offset(light, is_directional, P, Ng, random_pcf_2d);
P = is_transmission ? (P + shadow_dir * shadow_offset) : P;
P += shadow_pcf_offset(light, is_directional, shadow_dir, P, Ng, random_pcf_2d);
/* We want to bias inside the object for transmission to go through the object itself.
* But doing so split the shadow in two different directions at the horizon. Also this
* doesn't fix the the aliasing issue. So we reflect the normal so that it always go towards
* the light. */
vec3 N_bias = is_transmission ? reflect(Ng, L) : Ng;
vec3 N_bias = is_transmission ? reflect(Ng, shadow_dir) : Ng;
if (dot(Ng, shadow_dir) < 0.0) {
/* Always bias towards the light. */
N_bias -= 2.0;
}
/* Avoid self intersection. */
P = offset_ray(P, N_bias);

This is very shady, I'm pretty sure it make things explode to jitter the shading point position (it can move it behind the light). And if doesn't, it is still super confusing. What is the reason to move this here?

Also if you change it here why not in shadow_sample()?

This is very shady, I'm pretty sure it make things explode to jitter the shading point position (it can move it behind the light). And if doesn't, it is still super confusing. What is the reason to move this here? Also if you change it here why not in `shadow_sample()`?

How is this shady? This is just computing the shadow map local space.
I moved it here because I found way more confusing the way it was.
Also, doesn't that mess some computations in shadow_ray_generate_punctual, like the direction?

Also if you change it here why not in shadow_sample()?

I forgot, but that's needed regardless, isn't it?

How is this shady? This is just computing the shadow map local space. I moved it here because I found way more confusing the way it was. Also, doesn't that mess some computations in `shadow_ray_generate_punctual`, like the `direction`? > Also if you change it here why not in shadow_sample()? I forgot, but that's needed regardless, isn't it?

I forgot, but that's needed regardless, isn't it?

Yes. Both need to be changed.

How is this shady?

Because lP refers to object local position which in this case, is the light.

Also, doesn't that mess some computations in shadow_ray_generate_punctual, like the direction?

Yes you are right. The existing code is also quite confusing if not wrong. That said, it isn't a reason to move this out of the existing branch.

> I forgot, but that's needed regardless, isn't it? Yes. Both need to be changed. > How is this shady? Because `lP` refers to object local position which in this case, is the light. > Also, doesn't that mess some computations in shadow_ray_generate_punctual, like the direction? Yes you are right. The existing code is also quite confusing if not wrong. That said, it isn't a reason to move this out of the existing branch.

The existing code is also quite confusing if not wrong

The existing code made sense because it was only used for area light shadow shift.
projection_origin is the point in light object space where the shadow is recorded at. So after the shadow scale mix, you need to move the point to shadow space for local_ray_start/end. Here the local_ denotes the shadow space. But I agree it could be better named.

> The existing code is also quite confusing if not wrong The existing code made sense because it was only used for area light shadow shift. `projection_origin` is the point in light object space where the shadow is recorded at. So after the shadow scale mix, you need to move the point to shadow space for `local_ray_start/end`. Here the `local_` denotes the shadow space. But I agree it could be better named.
@ -549,9 +565,18 @@ ShadowEvalResult shadow_eval(LightData light,
/* TODO(fclem): Scale based on depth. */
P += N_bias * normal_offset;
vec3 lP = is_directional ? light_world_to_local(light, P) :
light_world_to_local(light, P - light._position);
vec3 lNg = light_world_to_local(light, Ng);
vec3 lP, lNg;
if (is_directional) {
lP = light_world_to_local(light, P);
lP = light_local_to_shadow_local(light, lP, true);
lNg = light_world_to_local(light, Ng);
lNg = light_local_to_shadow_local(light, lNg, true);
}
else {
lP = light_world_to_local(light, P - light._position);
lP = light_local_to_shadow_local(light, lP, false);
lNg = light_world_to_local(light, Ng);
}
/* Invert horizon clipping. */
lNg = (is_transmission) ? -lNg : lNg;
/* Don't do a any horizon clipping in this case as the closure is lit from both sides. */

View File

@ -233,7 +233,7 @@ static void test_eevee_shadow_tag_update()
{
ShadowTileMap tilemap(0 * SHADOW_TILEDATA_PER_TILEMAP);
tilemap.sync_cubeface(float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemap.sync_cubeface(float4x4::identity(), 0.01f, 1.0f, 0.01f, Z_NEG, 0.0f);
tilemaps_data.append(tilemap);
}
{
@ -1541,7 +1541,7 @@ static void test_eevee_shadow_page_mask_ex(int max_view_per_tilemap)
{
ShadowTileMap tilemap(0);
tilemap.sync_cubeface(float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemap.sync_cubeface(float4x4::identity(), 0.01f, 1.0f, 0.01f, Z_NEG, 0.0f);
tilemaps_data.append(tilemap);
}

View File

@ -50,6 +50,11 @@ vec4 as_vec4(Quaternion quat)
return vec4(quat.x, quat.y, quat.z, quat.w);
}
Quaternion as_quaternion(vec4 quat)
{
return Quaternion(quat.x, quat.y, quat.z, quat.w);
}
Quaternion Quaternion_identity()
pragma37 marked this conversation as resolved Outdated

Move this function next to as_vec4 as it is a conversion function.

Move this function next to `as_vec4` as it is a conversion function.
{
return Quaternion(1, 0, 0, 0);
@ -113,6 +118,19 @@ Quaternion interpolate(Quaternion a, Quaternion b, float t)
return Quaternion(UNPACK4(quat));
}
Quaternion invert(Quaternion quat)
{
return Quaternion(-quat.x, -quat.y, -quat.z, quat.w);
}
vec3 rotate(Quaternion quat, vec3 vec)
{
/* https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/ */
vec4 q = as_vec4(quat);
vec3 t = cross(q.xyz, vec) * 2.0;
return vec + t * q.w + cross(q.xyz, t);
}
Quaternion to_quaternion(EulerXYZ eul)
{
float ti = eul.x * 0.5;

View File

@ -46,6 +46,7 @@
.shadow_trace_distance = 10.0f, \
.shadow_filter_radius = 3.0f, \
.shadow_resolution_scale = 1.0f, \
.shadow_jitter_overblur_percentage = 10.0f, \
.att_dist = 40.0f, \
.sun_angle = DEG2RADF(0.526f), \
.area_spread = DEG2RADF(180.0f), \

View File

@ -83,6 +83,9 @@ typedef struct Light {
float shadow_trace_distance;
float shadow_filter_radius;
float shadow_resolution_scale;
float shadow_jitter_overblur_percentage;
float _pad2;
/* Preview */
struct PreviewImage *preview;
@ -93,7 +96,7 @@ typedef struct Light {
/* Deprecated. */
struct Ipo *ipo DNA_DEPRECATED; /* Old animation system. */
float energy_deprecated DNA_DEPRECATED;
float _pad2;
float _pad3;
} Light;
/* **************** LIGHT ********************* */
@ -145,6 +148,7 @@ enum {
LA_SHAD_CONTACT = 1 << 19,
LA_CUSTOM_ATTENUATION = 1 << 20,
LA_USE_SOFT_FALLOFF = 1 << 21,
LA_SHADOW_JITTER = 1 << 22
};
/** #Light::falloff_type */

View File

@ -322,6 +322,25 @@ 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, "use_shadow_jittering", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "mode", LA_SHADOW_JITTER);
RNA_def_property_ui_text(prop,
"Shadow Jittering",
"Enable jittered shoft shadows (Disabled in the Viewport unless "
"enabled in the Scene settings)");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Light_update");
prop = RNA_def_property(srna, "shadow_jitter_overblur_percentage", PROP_FLOAT, PROP_PERCENTAGE);
pragma37 marked this conversation as resolved Outdated

Should be PROP_PERCENTAGE.

Should be `PROP_PERCENTAGE`.

Done, but I can't say I like it.

Done, but I can't say I like it.
RNA_def_property_range(prop, 0.0f, 100.0f);
pragma37 marked this conversation as resolved Outdated

Nitpick: It might be copied from somewhere else, but 1.0 is missing a f.

Nitpick: It might be copied from somewhere else, but `1.0` is missing a `f`.
RNA_def_property_ui_range(prop, 0.0f, 20.0f, 10.0f, 0);
pragma37 marked this conversation as resolved Outdated

UI range (soft range) should be similar to the DOF overblur. 20% soft max sounds good.

UI range (soft range) should be similar to the DOF overblur. `20%` soft max sounds good.
RNA_def_property_ui_text(
prop,
"Shadow Jitter Overblur",
"Apply shadow tracing to each jittered sample to reduce under-sampling artifacts");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, 0, "rna_Light_update");
if (sun) {
prop = RNA_def_property(srna, "shadow_cascade_max_distance", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, nullptr, "cascade_max_dist");