EEVEE-Next: Jittered Shadow Transparency

Smooth transparent shadows by jittering their opacity threshold every
sample.
Always enabled on final renders, optionally enabled in the viewport with
`scene.eevee.shadow_jittered_transparency`.

Pull Request: https://projects.blender.org/blender/blender/pulls/119480
This commit is contained in:
Miguel Pozo 2024-03-20 15:55:58 +01:00
parent 0c8b96d1e0
commit 881fd2dbd5
9 changed files with 86 additions and 18 deletions

View File

@ -727,6 +727,9 @@ class RENDER_PT_eevee_next_shadows(RenderButtonsPanel, Panel):
col = layout.column()
col.prop(props, "shadow_normal_bias", text="Normal Bias")
col = layout.column()
col.prop(props, "use_shadow_jittered_viewport", text="Jittered Transparency (Viewport)")
class RENDER_PT_eevee_sampling(RenderButtonsPanel, Panel):
bl_label = "Sampling"

View File

@ -356,6 +356,10 @@ Material &MaterialModule::material_sync(Object *ob,
mat.is_alpha_blend_transparent = use_forward_pipeline &&
GPU_material_flag_get(mat.shading.gpumat,
GPU_MATFLAG_TRANSPARENT);
mat.has_transparent_shadows = blender_mat->blend_flag & MA_BL_TRANSPARENT_SHADOW &&
GPU_material_flag_get(mat.shading.gpumat,
GPU_MATFLAG_TRANSPARENT);
return mat;
});

View File

@ -291,6 +291,7 @@ struct MaterialPass {
struct Material {
bool is_alpha_blend_transparent;
bool has_transparent_shadows;
bool has_surface;
bool has_volume;
MaterialPass shadow;

View File

@ -742,6 +742,9 @@ void ShadowModule::init()
}
}
jittered_transparency_ = !inst_.is_viewport() ||
scene.eevee.flag & SCE_EEVEE_SHADOW_JITTERED_VIEWPORT;
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);
data_.normal_bias = max_ff(inst_.scene->eevee.shadow_normal_bias, 0.0f);
@ -824,6 +827,8 @@ void ShadowModule::begin_sync()
past_casters_updated_.clear();
curr_casters_updated_.clear();
curr_casters_.clear();
jittered_transparent_casters_.clear();
update_casters_ = true;
{
Manager &manager = *inst_.manager;
@ -897,7 +902,8 @@ void ShadowModule::begin_sync()
void ShadowModule::sync_object(const Object *ob,
const ObjectHandle &handle,
const ResourceHandle &resource_handle,
bool is_alpha_blend)
bool is_alpha_blend,
bool has_transparent_shadows)
{
bool is_shadow_caster = !(ob->visibility_flag & OB_HIDE_SHADOW);
if (!is_shadow_caster && !is_alpha_blend) {
@ -907,11 +913,18 @@ 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;
if ((handle.recalc != 0 || !is_initialized) && is_shadow_caster) {
if (shadow_ob.resource_handle.raw != 0) {
const bool has_jittered_transparency = has_transparent_shadows && jittered_transparency_;
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);
}
curr_casters_updated_.append(resource_handle.raw);
if (has_jittered_transparency) {
jittered_transparent_casters_.append(resource_handle.raw);
}
else {
curr_casters_updated_.append(resource_handle.raw);
}
}
shadow_ob.resource_handle = resource_handle;
@ -973,6 +986,7 @@ void ShadowModule::end_sync()
}
past_casters_updated_.push_update();
curr_casters_updated_.push_update();
jittered_transparent_casters_.push_update();
curr_casters_.push_update();
@ -1080,6 +1094,22 @@ void ShadowModule::end_sync()
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
/* Mark for update all shadow pages touching a jittered transparency shadow caster. */
PassSimple &pass = jittered_transparent_caster_update_ps_;
pass.init();
if (jittered_transparent_casters_.size() > 0) {
pass.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_TAG_UPDATE));
pass.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data);
pass.bind_ssbo("tiles_buf", tilemap_pool.tiles_data);
pass.bind_ssbo("bounds_buf", &manager.bounds_buf.current());
pass.bind_ssbo("resource_ids_buf", jittered_transparent_casters_);
pass.dispatch(
int3(jittered_transparent_casters_.size(), 1, tilemap_pool.tilemaps_data.size()));
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
}
/* Non volume usage tagging happens between these two steps.
* (Setup at begin_sync) */
@ -1339,7 +1369,7 @@ void ShadowModule::set_view(View &view, GPUTexture *depth_tx)
}
inst_.hiz_buffer.update();
bool update_casters = true;
bool first_loop = true;
do {
DRW_stats_group_start("Shadow");
@ -1347,12 +1377,15 @@ void ShadowModule::set_view(View &view, GPUTexture *depth_tx)
GPU_uniformbuf_clear_to_zero(shadow_multi_view_.matrices_ubo_get());
inst_.manager->submit(tilemap_setup_ps_, view);
if (assign_if_different(update_casters, false)) {
if (assign_if_different(update_casters_, false)) {
/* Run caster update only once. */
/* TODO(fclem): There is an optimization opportunity here where we can
* test casters only against the static tilemaps instead of all of them. */
inst_.manager->submit(caster_update_ps_, view);
}
if (assign_if_different(first_loop, false)) {
inst_.manager->submit(jittered_transparent_caster_update_ps_, view);
}
inst_.manager->submit(tilemap_usage_ps_, view);
inst_.manager->submit(tilemap_update_ps_, view);

View File

@ -213,6 +213,11 @@ class ShadowModule {
/** Map of shadow casters to track deletion & update of intersected shadows. */
Map<ObjectKey, ShadowObject> objects_;
/* Used to call caster_update_ps_ only once per sync (Initialized on begin_sync). */
bool update_casters_ = false;
bool jittered_transparency_ = false;
/* -------------------------------------------------------------------- */
/** \name Tile-map Management
* \{ */
@ -229,9 +234,11 @@ class ShadowModule {
Framebuffer usage_tag_fb;
PassSimple caster_update_ps_ = {"CasterUpdate"};
PassSimple jittered_transparent_caster_update_ps_ = {"TransparentCasterUpdate"};
/** List of Resource IDs (to get bounds) for tagging passes. */
StorageVectorBuffer<uint, 128> past_casters_updated_ = {"PastCastersUpdated"};
StorageVectorBuffer<uint, 128> curr_casters_updated_ = {"CurrCastersUpdated"};
StorageVectorBuffer<uint, 128> jittered_transparent_casters_ = {"JitteredTransparentCasters"};
/** List of Resource IDs (to get bounds) for getting minimum clip-maps bounds. */
StorageVectorBuffer<uint, 128> curr_casters_ = {"CurrCasters"};
@ -332,7 +339,8 @@ class ShadowModule {
void sync_object(const Object *ob,
const ObjectHandle &handle,
const ResourceHandle &resource_handle,
bool is_alpha_blend);
bool is_alpha_blend,
bool has_transparent_shadows);
void end_sync();
void set_lights_data();

View File

@ -114,6 +114,7 @@ void SyncModule::sync_mesh(Object *ob,
}
bool is_alpha_blend = false;
bool has_transparent_shadows = false;
float inflate_bounds = 0.0f;
for (auto i : material_array.gpu_materials.index_range()) {
GPUBatch *geom = mat_geom[i];
@ -147,6 +148,7 @@ void SyncModule::sync_mesh(Object *ob,
geometry_call(material.reflection_probe_shading.sub_pass, geom, res_handle);
is_alpha_blend = is_alpha_blend || material.is_alpha_blend_transparent;
has_transparent_shadows = has_transparent_shadows || material.has_transparent_shadows;
::Material *mat = GPU_material_get_material(gpu_material);
inst_.cryptomatte.sync_material(mat);
@ -162,7 +164,7 @@ void SyncModule::sync_mesh(Object *ob,
inst_.manager->extract_object_attributes(res_handle, ob_ref, material_array.gpu_materials);
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend);
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend, has_transparent_shadows);
inst_.cryptomatte.sync_object(ob, res_handle);
}
@ -192,6 +194,7 @@ bool SyncModule::sync_sculpt(Object *ob,
MaterialArray &material_array = inst_.materials.material_array_get(ob, has_motion);
bool is_alpha_blend = false;
bool has_transparent_shadows = false;
float inflate_bounds = 0.0f;
for (SculptBatch &batch :
sculpt_batches_per_material_get(ob_ref.object, material_array.gpu_materials))
@ -226,6 +229,7 @@ bool SyncModule::sync_sculpt(Object *ob,
geometry_call(material.reflection_probe_shading.sub_pass, geom, res_handle);
is_alpha_blend = is_alpha_blend || material.is_alpha_blend_transparent;
has_transparent_shadows = has_transparent_shadows || material.has_transparent_shadows;
GPUMaterial *gpu_material = material_array.gpu_materials[batch.material_slot];
::Material *mat = GPU_material_get_material(gpu_material);
@ -245,7 +249,7 @@ bool SyncModule::sync_sculpt(Object *ob,
inst_.manager->extract_object_attributes(res_handle, ob_ref, material_array.gpu_materials);
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend);
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend, has_transparent_shadows);
inst_.cryptomatte.sync_object(ob, res_handle);
return true;
@ -308,13 +312,15 @@ void SyncModule::sync_point_cloud(Object *ob,
::Material *mat = GPU_material_get_material(gpu_material);
inst_.cryptomatte.sync_material(mat);
bool is_alpha_blend = material.is_alpha_blend_transparent;
if (GPU_material_has_displacement_output(gpu_material) && mat->inflate_bounds != 0.0f) {
inst_.manager->update_handle_bounds(res_handle, ob_ref, mat->inflate_bounds);
}
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend);
inst_.shadows.sync_object(ob,
ob_handle,
res_handle,
material.is_alpha_blend_transparent,
material.has_transparent_shadows);
}
/** \} */
@ -485,8 +491,9 @@ void SyncModule::sync_gpencil(Object *ob, ObjectHandle &ob_handle, ResourceHandl
gpencil_drawcall_flush(iter);
bool is_alpha_blend = true; /* TODO material.is_alpha_blend. */
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend);
bool is_alpha_blend = true; /* TODO material.is_alpha_blend. */
bool has_transparent_shadows = true; /* TODO material.has_transparent_shadows. */
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend, has_transparent_shadows);
}
/** \} */
@ -560,13 +567,15 @@ void SyncModule::sync_curves(Object *ob,
::Material *mat = GPU_material_get_material(gpu_material);
inst_.cryptomatte.sync_material(mat);
bool is_alpha_blend = material.is_alpha_blend_transparent;
if (GPU_material_has_displacement_output(gpu_material) && mat->inflate_bounds != 0.0f) {
inst_.manager->update_handle_bounds(res_handle, ob_ref, mat->inflate_bounds);
}
inst_.shadows.sync_object(ob, ob_handle, res_handle, is_alpha_blend);
inst_.shadows.sync_object(ob,
ob_handle,
res_handle,
material.is_alpha_blend_transparent,
material.has_transparent_shadows);
}
/** \} */

View File

@ -31,7 +31,8 @@ void main()
nodetree_surface(0.0);
float noise_offset = sampling_rng_1D_get(SAMPLING_TRANSPARENCY);
float random_threshold = transparency_hashed_alpha_threshold(1.0, noise_offset, g_data.P);
float random_threshold = transparency_hashed_alpha_threshold(
uniform_buf.pipeline.alpha_hash_scale, noise_offset, g_data.P);
float transparency = average(g_transmittance);
if (transparency > random_threshold) {

View File

@ -2887,6 +2887,7 @@ enum {
SCE_EEVEE_DOF_JITTER = (1 << 23),
SCE_EEVEE_SHADOW_ENABLED = (1 << 24),
SCE_EEVEE_RAYTRACE_OPTIONS_SPLIT = (1 << 25),
SCE_EEVEE_SHADOW_JITTERED_VIEWPORT = (1 << 26),
};
typedef enum RaytraceEEVEE_Flag {

View File

@ -8038,6 +8038,14 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
prop, "Tracing Method", "Select the tracing method used to find scene-ray intersections");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
prop = RNA_def_property(srna, "use_shadow_jittered_viewport", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", SCE_EEVEE_SHADOW_JITTERED_VIEWPORT);
RNA_def_property_ui_text(prop,
"Jittered Shadows (Viewport)",
"Enable jittered shadows on the viewport. (Jittered shadows are always "
"enabled for final renders)");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
/* Volumetrics */
prop = RNA_def_property(srna, "volumetric_start", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_ui_text(prop, "Start", "Start distance of the volumetric effect");