EEVEE-Next: Virtual Shadow Map initial implementation
buildbot/vdev-code-daily-coordinator Build done. Details

Implements virtual shadow mapping for EEVEE-Next primary shadow solution.
This technique aims to deliver really high precision shadowing for many
lights while keeping a relatively low cost.

The technique works by splitting each shadows in tiles that are only
allocated & updated on demand by visible surfaces and volumes.
Local lights use cubemap projection with mipmap level of detail to adapt
the resolution to the receiver distance.
Sun lights use clipmap distribution or cascade distribution (depending on
which is better) for selecting the level of detail with the distance to
the camera.

Current maximum shadow precision for local light is about 1 pixel per 0.01
degrees.
For sun light, the maximum resolution is based on the camera far clip
distance which sets the most coarse clipmap.

## Limitation:
Alpha Blended surfaces might not get correct shadowing in some corner
casses. This is to be fixed in another commit.
While resolution is greatly increase, it is still finite. It is virtually
equivalent to one 8K shadow per shadow cube face and per clipmap level.
There is no filtering present for now.

## Parameters:
Shadow Pool Size: In bytes, amount of GPU memory to dedicate to the
shadow pool (is allocated per viewport).
Shadow Scaling: Scale the shadow resolution. Base resolution should
target subpixel accuracy (within the limitation of the technique).

Related to #93220
Related to #104472
This commit is contained in:
Clément Foucault 2023-02-08 21:08:54 +01:00
parent 0ab3ac7a41
commit a0f5240089
63 changed files with 5921 additions and 242 deletions

View File

@ -140,7 +140,7 @@ class DATA_PT_EEVEE_light_distance(DataButtonsPanel, Panel):
class DATA_PT_EEVEE_shadow(DataButtonsPanel, Panel):
bl_label = "Shadow"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE'}
COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
@ -168,7 +168,8 @@ class DATA_PT_EEVEE_shadow(DataButtonsPanel, Panel):
if light.type != 'SUN':
sub.prop(light, "shadow_buffer_clip_start", text="Clip Start")
col.prop(light, "shadow_buffer_bias", text="Bias")
if context.engine != 'BLENDER_EEVEE_NEXT':
col.prop(light, "shadow_buffer_bias", text="Bias")
class DATA_PT_EEVEE_shadow_cascaded_shadow_map(DataButtonsPanel, Panel):

View File

@ -460,6 +460,27 @@ class RENDER_PT_eevee_shadows(RenderButtonsPanel, Panel):
col.prop(props, "light_threshold")
class RENDER_PT_eevee_next_shadows(RenderButtonsPanel, Panel):
bl_label = "Shadows"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.eevee
col = layout.column()
col.prop(props, "shadow_pool_size", text="Pool Size")
col.prop(props, "light_threshold")
class RENDER_PT_eevee_sampling(RenderButtonsPanel, Panel):
bl_label = "Sampling"
COMPAT_ENGINES = {'BLENDER_EEVEE'}
@ -808,6 +829,10 @@ class RENDER_PT_simplify_viewport(RenderButtonsPanel, Panel):
col = flow.column()
col.prop(rd, "simplify_volumes", text="Volume Resolution")
if context.engine in 'BLENDER_EEVEE_NEXT':
col = flow.column()
col.prop(rd, "simplify_shadows", text="Shadow Resolution")
class RENDER_PT_simplify_render(RenderButtonsPanel, Panel):
bl_label = "Render"
@ -835,6 +860,10 @@ class RENDER_PT_simplify_render(RenderButtonsPanel, Panel):
col = flow.column()
col.prop(rd, "simplify_child_particles_render", text="Max Child Particles")
if context.engine in 'BLENDER_EEVEE_NEXT':
col = flow.column()
col.prop(rd, "simplify_shadows_render", text="Shadow Resolution")
class RENDER_PT_simplify_greasepencil(RenderButtonsPanel, Panel, GreasePencilSimplifyPanel):
bl_label = "Grease Pencil"
@ -869,6 +898,7 @@ classes = (
RENDER_PT_eevee_performance,
RENDER_PT_eevee_hair,
RENDER_PT_eevee_shadows,
RENDER_PT_eevee_next_shadows,
RENDER_PT_eevee_indirect_lighting,
RENDER_PT_eevee_indirect_lighting_display,
RENDER_PT_eevee_film,

View File

@ -252,6 +252,16 @@ template<typename T, int Size>
return result;
}
template<typename T, int Size>
[[nodiscard]] inline VecBase<T, Size> round(const VecBase<T, Size> &a)
{
VecBase<T, Size> result;
for (int i = 0; i < Size; i++) {
result[i] = std::round(a[i]);
}
return result;
}
template<typename T, int Size>
[[nodiscard]] inline VecBase<T, Size> ceil(const VecBase<T, Size> &a)
{

View File

@ -3911,6 +3911,14 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
* \note Keep this message at the bottom of the function.
*/
{
if (!DNA_struct_elem_find(fd->filesdna, "SceneEEVEE", "int", "shadow_pool_size")) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->eevee.shadow_pool_size = 512;
scene->r.simplify_shadows = 1.0f;
scene->r.simplify_shadows_render = 1.0f;
}
}
/* Keep this block, even when empty. */
}
}

View File

@ -151,6 +151,7 @@ set(SRC
engines/eevee_next/eevee_renderbuffers.cc
engines/eevee_next/eevee_sampling.cc
engines/eevee_next/eevee_shader.cc
engines/eevee_next/eevee_shadow.cc
engines/eevee_next/eevee_sync.cc
engines/eevee_next/eevee_velocity.cc
engines/eevee_next/eevee_view.cc
@ -281,6 +282,7 @@ set(SRC
engines/eevee_next/eevee_renderbuffers.hh
engines/eevee_next/eevee_sampling.hh
engines/eevee_next/eevee_shader.hh
engines/eevee_next/eevee_shadow.hh
engines/eevee_next/eevee_sync.hh
engines/eevee_next/eevee_velocity.hh
engines/eevee_next/eevee_view.hh
@ -422,6 +424,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_camera_lib.glsl
engines/eevee_next/shaders/eevee_colorspace_lib.glsl
engines/eevee_next/shaders/eevee_cryptomatte_lib.glsl
engines/eevee_next/shaders/eevee_transparency_lib.glsl
engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl
engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl
engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl
@ -462,10 +465,29 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
engines/eevee_next/shaders/eevee_sampling_lib.glsl
engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl
engines/eevee_next/shaders/eevee_shadow_lib.glsl
engines/eevee_next/shaders/eevee_shadow_page_allocate_comp.glsl
engines/eevee_next/shaders/eevee_shadow_page_clear_comp.glsl
engines/eevee_next/shaders/eevee_shadow_page_defrag_comp.glsl
engines/eevee_next/shaders/eevee_shadow_page_free_comp.glsl
engines/eevee_next/shaders/eevee_shadow_page_mask_comp.glsl
engines/eevee_next/shaders/eevee_shadow_page_ops_lib.glsl
engines/eevee_next/shaders/eevee_shadow_tag_update_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_frag.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl
engines/eevee_next/shaders/eevee_shadow_tag_usage_vert.glsl
engines/eevee_next/shaders/eevee_shadow_test.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_bounds_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_finalize_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_init_comp.glsl
engines/eevee_next/shaders/eevee_shadow_tilemap_lib.glsl
engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl
engines/eevee_next/shaders/eevee_surf_depth_frag.glsl
engines/eevee_next/shaders/eevee_surf_forward_frag.glsl
engines/eevee_next/shaders/eevee_surf_lib.glsl
engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl
engines/eevee_next/shaders/eevee_surf_world_frag.glsl
engines/eevee_next/shaders/eevee_velocity_lib.glsl
@ -797,6 +819,7 @@ if(WITH_GTESTS)
set(TEST_SRC
tests/draw_pass_test.cc
tests/draw_testing.cc
tests/eevee_test.cc
tests/shaders_test.cc
tests/draw_testing.hh

View File

@ -135,8 +135,9 @@ void Camera::sync()
#endif
}
else if (inst_.drw_view) {
data.clip_near = DRW_view_near_distance_get(inst_.drw_view);
data.clip_far = DRW_view_far_distance_get(inst_.drw_view);
/* \note: Follow camera parameters where distances are positive in front of the camera. */
data.clip_near = -DRW_view_near_distance_get(inst_.drw_view);
data.clip_far = -DRW_view_far_distance_get(inst_.drw_view);
data.fisheye_fov = data.fisheye_lens = -1.0f;
data.equirect_bias = float2(0.0f);
data.equirect_scale = float2(0.0f);
@ -144,6 +145,57 @@ void Camera::sync()
data_.initialized = true;
data_.push_update();
update_bounds();
}
void Camera::update_bounds()
{
float left, right, bottom, top, near, far;
projmat_dimensions(data_.winmat.ptr(), &left, &right, &bottom, &top, &near, &far);
BoundBox bbox;
bbox.vec[0][2] = bbox.vec[3][2] = bbox.vec[7][2] = bbox.vec[4][2] = -near;
bbox.vec[0][0] = bbox.vec[3][0] = left;
bbox.vec[4][0] = bbox.vec[7][0] = right;
bbox.vec[0][1] = bbox.vec[4][1] = bottom;
bbox.vec[7][1] = bbox.vec[3][1] = top;
/* Get the coordinates of the far plane. */
if (!this->is_orthographic()) {
float sca_far = far / near;
left *= sca_far;
right *= sca_far;
bottom *= sca_far;
top *= sca_far;
}
bbox.vec[1][2] = bbox.vec[2][2] = bbox.vec[6][2] = bbox.vec[5][2] = -far;
bbox.vec[1][0] = bbox.vec[2][0] = left;
bbox.vec[6][0] = bbox.vec[5][0] = right;
bbox.vec[1][1] = bbox.vec[5][1] = bottom;
bbox.vec[2][1] = bbox.vec[6][1] = top;
bound_sphere.center = {0.0f, 0.0f, 0.0f};
bound_sphere.radius = 0.0f;
for (auto i : IndexRange(8)) {
bound_sphere.center += float3(bbox.vec[i]);
}
bound_sphere.center /= 8.0f;
for (auto i : IndexRange(8)) {
float dist_sqr = math::distance_squared(bound_sphere.center, float3(bbox.vec[i]));
bound_sphere.radius = max_ff(bound_sphere.radius, dist_sqr);
}
bound_sphere.radius = sqrtf(bound_sphere.radius);
/* Transform into world space. */
bound_sphere.center = math::transform_point(data_.viewinv, bound_sphere.center);
/* Compute diagonal length. */
float2 p0 = float2(bbox.vec[0]) / (this->is_perspective() ? bbox.vec[0][2] : 1.0f);
float2 p1 = float2(bbox.vec[7]) / (this->is_perspective() ? bbox.vec[7][2] : 1.0f);
data_.screen_diagonal_length = math::distance(p0, p1);
}
/** \} */

View File

@ -94,6 +94,11 @@ class Camera {
CameraDataBuf data_;
struct {
float3 center;
float radius;
} bound_sphere;
public:
Camera(Instance &inst) : inst_(inst){};
~Camera(){};
@ -133,6 +138,17 @@ class Camera {
{
return data_.viewinv.z_axis();
}
const float3 &bound_center() const
{
return bound_sphere.center;
}
const float &bound_radius() const
{
return bound_sphere.radius;
}
private:
void update_bounds();
};
/** \} */

View File

@ -32,15 +32,29 @@
* SHADOW_TILEMAP_RES max is 32 because of the shared bitmaps used for LOD tagging.
* It is also limited by the maximum thread group size (1024).
*/
#define SHADOW_TILEMAP_RES 16
#define SHADOW_TILEMAP_LOD 4 /* LOG2(SHADOW_TILEMAP_RES) */
#define SHADOW_TILEMAP_RES 32
#define SHADOW_TILEMAP_LOD 5 /* LOG2(SHADOW_TILEMAP_RES) */
#define SHADOW_TILEMAP_LOD0_LEN ((SHADOW_TILEMAP_RES / 1) * (SHADOW_TILEMAP_RES / 1))
#define SHADOW_TILEMAP_LOD1_LEN ((SHADOW_TILEMAP_RES / 2) * (SHADOW_TILEMAP_RES / 2))
#define SHADOW_TILEMAP_LOD2_LEN ((SHADOW_TILEMAP_RES / 4) * (SHADOW_TILEMAP_RES / 4))
#define SHADOW_TILEMAP_LOD3_LEN ((SHADOW_TILEMAP_RES / 8) * (SHADOW_TILEMAP_RES / 8))
#define SHADOW_TILEMAP_LOD4_LEN ((SHADOW_TILEMAP_RES / 16) * (SHADOW_TILEMAP_RES / 16))
#define SHADOW_TILEMAP_LOD5_LEN ((SHADOW_TILEMAP_RES / 32) * (SHADOW_TILEMAP_RES / 32))
#define SHADOW_TILEMAP_PER_ROW 64
#define SHADOW_PAGE_COPY_GROUP_SIZE 32
#define SHADOW_DEPTH_SCAN_GROUP_SIZE 32
#define SHADOW_TILEDATA_PER_TILEMAP \
(SHADOW_TILEMAP_LOD0_LEN + SHADOW_TILEMAP_LOD1_LEN + SHADOW_TILEMAP_LOD2_LEN + \
SHADOW_TILEMAP_LOD3_LEN + SHADOW_TILEMAP_LOD4_LEN + SHADOW_TILEMAP_LOD5_LEN)
#define SHADOW_PAGE_CLEAR_GROUP_SIZE 32
#define SHADOW_PAGE_RES 256
#define SHADOW_DEPTH_SCAN_GROUP_SIZE 8
#define SHADOW_AABB_TAG_GROUP_SIZE 64
#define SHADOW_MAX_TILEMAP 4096
#define SHADOW_MAX_TILE (SHADOW_MAX_TILEMAP * SHADOW_TILEDATA_PER_TILEMAP)
#define SHADOW_MAX_PAGE 4096
#define SHADOW_PAGE_PER_ROW 64
#define SHADOW_ATLAS_SLOT 5
#define SHADOW_BOUNDS_GROUP_SIZE 64
#define SHADOW_VIEW_MAX 64 /* Must match DRW_VIEW_MAX. */
/* Ray-tracing. */
#define RAYTRACE_GROUP_SIZE 16
@ -74,6 +88,11 @@
/* Resource bindings. */
/* Texture. */
#define SHADOW_TILEMAPS_TEX_SLOT 12
/* Only during surface shading. */
#define SHADOW_ATLAS_TEX_SLOT 13
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_SLOT 13
#define RBUFS_UTILITY_TEX_SLOT 14
/* Images. */
@ -99,7 +118,10 @@
#define LIGHT_BUF_SLOT 1
#define LIGHT_ZBIN_BUF_SLOT 2
#define LIGHT_TILE_BUF_SLOT 3
/* Only during surface shading. */
#define RBUFS_AOV_BUF_SLOT 5
/* Only during shadow rendering. */
#define SHADOW_PAGE_INFO_SLOT 5
#define SAMPLING_BUF_SLOT 6
#define CRYPTOMATTE_BUF_SLOT 7

View File

@ -67,6 +67,7 @@ void Instance::init(const int2 &output_res,
film.init(output_res, output_rect);
velocity.init();
depth_of_field.init();
shadows.init();
motion_blur.init();
main_view.init();
}
@ -102,6 +103,7 @@ void Instance::begin_sync()
materials.begin_sync();
velocity.begin_sync(); /* NOTE: Also syncs camera. */
lights.begin_sync();
shadows.begin_sync();
cryptomatte.begin_sync();
gpencil_engine_enabled = false;
@ -197,6 +199,7 @@ void Instance::object_sync_render(void *instance_,
void Instance::end_sync()
{
velocity.end_sync();
shadows.end_sync(); /** \note: Needs to be before lights. */
lights.end_sync();
sampling.end_sync();
film.end_sync();

View File

@ -27,6 +27,7 @@
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
#include "eevee_shader.hh"
#include "eevee_shadow.hh"
#include "eevee_sync.hh"
#include "eevee_view.hh"
#include "eevee_world.hh"
@ -46,6 +47,7 @@ class Instance {
SyncModule sync;
MaterialModule materials;
PipelineModule pipelines;
ShadowModule shadows;
LightModule lights;
VelocityModule velocity;
MotionBlurModule motion_blur;
@ -89,6 +91,7 @@ class Instance {
sync(*this),
materials(*this),
pipelines(*this),
shadows(*this),
lights(*this),
velocity(*this),
motion_blur(*this),

View File

@ -41,7 +41,7 @@ static eLightType to_light_type(short blender_light_type, short blender_area_typ
/** \name Light Object
* \{ */
void Light::sync(/* ShadowModule &shadows , */ const Object *ob, float threshold)
void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
{
const ::Light *la = (const ::Light *)ob->data;
float scale[3];
@ -75,67 +75,49 @@ void Light::sync(/* ShadowModule &shadows , */ const Object *ob, float threshold
this->volume_power = la->volume_fac * point_power;
eLightType new_type = to_light_type(la->type, la->area_shape);
if (this->type != new_type) {
/* shadow_discard_safe(shadows); */
this->type = new_type;
if (assign_if_different(this->type, new_type)) {
shadow_discard_safe(shadows);
}
#if 0
if (la->mode & LA_SHADOW) {
if (la->type == LA_SUN) {
if (this->shadow_id == LIGHT_NO_SHADOW) {
this->shadow_id = shadows.directionals.alloc();
}
ShadowDirectional &shadow = shadows.directionals[this->shadow_id];
shadow.sync(this->object_mat, la->bias * 0.05f, 1.0f);
shadow_ensure(shadows);
if (is_sun_light(this->type)) {
this->directional->sync(this->object_mat, 1.0f);
}
else {
float cone_aperture = DEG2RAD(360.0);
if (la->type == LA_SPOT) {
cone_aperture = min_ff(DEG2RAD(179.9), la->spotsize);
}
else if (la->type == LA_AREA) {
cone_aperture = DEG2RAD(179.9);
}
if (this->shadow_id == LIGHT_NO_SHADOW) {
this->shadow_id = shadows.punctuals.alloc();
}
ShadowPunctual &shadow = shadows.punctuals[this->shadow_id];
shadow.sync(this->type,
this->object_mat,
cone_aperture,
la->clipsta,
this->influence_radius_max,
la->bias * 0.05f);
this->punctual->sync(
this->type, this->object_mat, la->spotsize, la->clipsta, this->influence_radius_max);
}
}
else {
shadow_discard_safe(shadows);
}
#endif
this->initialized = true;
}
#if 0
void Light::shadow_discard_safe(ShadowModule &shadows)
{
if (shadow_id != LIGHT_NO_SHADOW) {
if (this->type != LIGHT_SUN) {
shadows.punctuals.free(shadow_id);
}
else {
shadows.directionals.free(shadow_id);
}
shadow_id = LIGHT_NO_SHADOW;
if (this->directional != nullptr) {
shadows.directional_pool.destruct(*directional);
this->directional = nullptr;
}
if (this->punctual != nullptr) {
shadows.punctual_pool.destruct(*punctual);
this->punctual = nullptr;
}
}
void Light::shadow_ensure(ShadowModule &shadows)
{
if (is_sun_light(this->type) && this->directional == nullptr) {
this->directional = &shadows.directional_pool.construct(shadows);
}
else if (this->punctual == nullptr) {
this->punctual = &shadows.punctual_pool.construct(shadows);
}
}
#endif
/* Returns attenuation radius inverted & squared for easy bound checking inside the shader. */
float Light::attenuation_radius_get(const ::Light *la, float light_threshold, float light_power)
{
if (la->type == LA_SUN) {
@ -265,6 +247,14 @@ void Light::debug_draw()
/** \name LightModule
* \{ */
LightModule::~LightModule()
{
/* WATCH: Destructor order. Expect shadow module to be destructed later. */
for (Light &light : light_map_.values()) {
light.shadow_discard_safe(inst_.shadows);
}
};
void LightModule::begin_sync()
{
use_scene_lights_ = inst_.use_scene_lights();
@ -286,61 +276,44 @@ 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.sync(/* inst_.shadows, */ ob, light_threshold_);
light.initialized = true;
light.sync(inst_.shadows, ob, light_threshold_);
}
sun_lights_len_ += int(light.type == LIGHT_SUN);
local_lights_len_ += int(light.type != LIGHT_SUN);
sun_lights_len_ += int(is_sun_light(light.type));
local_lights_len_ += int(!is_sun_light(light.type));
}
void LightModule::end_sync()
{
// ShadowModule &shadows = inst_.shadows;
/* NOTE: We resize this buffer before removing deleted lights. */
int lights_allocated = ceil_to_multiple_u(max_ii(light_map_.size(), 1), LIGHT_CHUNK);
light_buf_.resize(lights_allocated);
/* Track light deletion. */
Vector<ObjectKey, 0> deleted_keys;
/* Indices inside GPU data array. */
int sun_lights_idx = 0;
int local_lights_idx = sun_lights_len_;
/* Fill GPU data with scene data. */
for (auto item : light_map_.items()) {
Light &light = item.value;
auto it_end = light_map_.items().end();
for (auto it = light_map_.items().begin(); it != it_end; ++it) {
Light &light = (*it).value;
if (!light.used) {
/* Deleted light. */
deleted_keys.append(item.key);
// light.shadow_discard_safe(shadows);
light_map_.remove(it);
continue;
}
int dst_idx = (light.type == LIGHT_SUN) ? sun_lights_idx++ : local_lights_idx++;
int dst_idx = is_sun_light(light.type) ? sun_lights_idx++ : local_lights_idx++;
/* Put all light data into global data SSBO. */
light_buf_[dst_idx] = light;
#if 0
if (light.shadow_id != LIGHT_NO_SHADOW) {
if (light.type == LIGHT_SUN) {
light_buf_[dst_idx].shadow_data = shadows.directionals[light.shadow_id];
}
else {
light_buf_[dst_idx].shadow_data = shadows.punctuals[light.shadow_id];
}
}
#endif
/* Untag for next sync. */
light.used = false;
}
/* This scene data buffer is then immutable after this point. */
light_buf_.push_update();
for (auto &key : deleted_keys) {
light_map_.remove(key);
}
/* Update sampling on deletion or un-hiding (use_scene_lights). */
if (assign_if_different(light_map_size_, light_map_.size())) {
inst_.sampling.reset();

View File

@ -34,25 +34,52 @@
namespace blender::eevee {
class Instance;
class ShadowModule;
/* -------------------------------------------------------------------- */
/** \name Light Object
* \{ */
struct Light : public LightData {
struct Light : public LightData, NonCopyable {
public:
bool initialized = false;
bool used = false;
/** Pointers to source Shadow. Type depends on `LightData::type`. */
ShadowDirectional *directional = nullptr;
ShadowPunctual *punctual = nullptr;
public:
Light()
{
shadow_id = LIGHT_NO_SHADOW;
/* Avoid valgrind warning. */
this->type = LIGHT_SUN;
}
void sync(/* ShadowModule &shadows, */ const Object *ob, float threshold);
/* Only used for debugging. */
#ifndef NDEBUG
Light(Light &&other)
{
*static_cast<LightData *>(this) = other;
this->initialized = other.initialized;
this->used = other.used;
this->directional = other.directional;
this->punctual = other.punctual;
other.directional = nullptr;
other.punctual = nullptr;
}
// void shadow_discard_safe(ShadowModule &shadows);
~Light()
{
BLI_assert(directional == nullptr);
BLI_assert(punctual == nullptr);
}
#endif
void sync(ShadowModule &shadows, const Object *ob, float threshold);
void shadow_ensure(ShadowModule &shadows);
void shadow_discard_safe(ShadowModule &shadows);
void debug_draw();
@ -73,7 +100,7 @@ struct Light : public LightData {
* The light module manages light data buffers and light culling system.
*/
class LightModule {
// friend ShadowModule;
friend ShadowModule;
private:
/* Keep tile count reasonable for memory usage and 2D culling performance. */
@ -125,7 +152,7 @@ class LightModule {
public:
LightModule(Instance &inst) : inst_(inst){};
~LightModule(){};
~LightModule();
void begin_sync();
void sync_light(const Object *ob, ObjectHandle &handle);
@ -138,21 +165,8 @@ class LightModule {
void debug_draw(View &view, GPUFrameBuffer *view_fb);
void bind_resources(DRWShadingGroup *grp)
{
DRW_shgroup_storage_block_ref(grp, "light_buf", &culling_light_buf_);
DRW_shgroup_storage_block_ref(grp, "light_cull_buf", &culling_data_buf_);
DRW_shgroup_storage_block_ref(grp, "light_zbin_buf", &culling_zbin_buf_);
DRW_shgroup_storage_block_ref(grp, "light_tile_buf", &culling_tile_buf_);
#if 0
DRW_shgroup_uniform_texture(grp, "shadow_atlas_tx", inst_.shadows.atlas_tx_get());
DRW_shgroup_uniform_texture(grp, "shadow_tilemaps_tx", inst_.shadows.tilemap_tx_get());
#endif
}
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
/* Storage Buf. */
pass->bind_ssbo(LIGHT_CULL_BUF_SLOT, &culling_data_buf_);
pass->bind_ssbo(LIGHT_BUF_SLOT, &culling_light_buf_);
pass->bind_ssbo(LIGHT_ZBIN_BUF_SLOT, &culling_zbin_buf_);

View File

@ -300,7 +300,9 @@ MaterialArray &MaterialModule::material_array_get(Object *ob, bool has_motion)
for (auto i : IndexRange(materials_len)) {
::Material *blender_mat = material_from_slot(ob, i);
Material &mat = material_sync(ob, blender_mat, to_material_geometry(ob), has_motion);
material_array_.materials.append(&mat);
/* \note: Perform a whole copy since next material_sync() can move the Material memory location
* (i.e: because of its container growing) */
material_array_.materials.append(mat);
material_array_.gpu_materials.append(mat.shading.gpumat);
}
return material_array_;

View File

@ -213,7 +213,7 @@ struct Material {
};
struct MaterialArray {
Vector<Material *> materials;
Vector<Material> materials;
Vector<GPUMaterial *> gpu_materials;
};

View File

@ -46,6 +46,8 @@ void WorldPipeline::sync(GPUMaterial *gpumat)
world_ps_.bind_image("rp_emission_img", &rbufs.emission_tx);
world_ps_.bind_image("rp_cryptomatte_img", &rbufs.cryptomatte_tx);
world_ps_.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
world_ps_.draw(DRW_cache_fullscreen_quad_get(), handle);
/* To allow opaque pass rendering over it. */
world_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
@ -58,6 +60,39 @@ void WorldPipeline::render(View &view)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pipeline
*
* \{ */
void ShadowPipeline::sync()
{
surface_ps_.init();
/* TODO(fclem): Add state for rendering to empty framebuffer without depth test.
* For now this is only here for avoiding the rasterizer discard state. */
surface_ps_.state_set(DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS);
surface_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
surface_ps_.bind_texture(SHADOW_RENDER_MAP_SLOT, &inst_.shadows.render_map_tx_);
surface_ps_.bind_image(SHADOW_ATLAS_SLOT, &inst_.shadows.atlas_tx_);
surface_ps_.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
surface_ps_.bind_ssbo(SHADOW_PAGE_INFO_SLOT, &inst_.shadows.pages_infos_data_);
inst_.sampling.bind_resources(&surface_ps_);
surface_ps_.framebuffer_set(&inst_.shadows.render_fb_);
}
PassMain::Sub *ShadowPipeline::surface_material_add(GPUMaterial *gpumat)
{
return &surface_ps_.sub(GPU_material_get_name(gpumat));
}
void ShadowPipeline::render(View &view)
{
inst_.manager->submit(surface_ps_, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Forward Pass
*
@ -123,6 +158,7 @@ void ForwardPipeline::sync()
opaque_ps_.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
inst_.lights.bind_resources(&opaque_ps_);
inst_.shadows.bind_resources(&opaque_ps_);
inst_.sampling.bind_resources(&opaque_ps_);
inst_.cryptomatte.bind_resources(&opaque_ps_);
}
@ -145,9 +181,10 @@ void ForwardPipeline::sync()
/* Textures. */
sub.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
/* Uniform Buf. */
opaque_ps_.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
sub.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
inst_.lights.bind_resources(&sub);
inst_.shadows.bind_resources(&sub);
inst_.sampling.bind_resources(&sub);
}
}
@ -225,7 +262,7 @@ void ForwardPipeline::render(View &view,
// inst_.hiz_buffer.update();
// }
// inst_.shadows.set_view(view, depth_tx);
inst_.shadows.set_view(view);
GPU_framebuffer_bind(combined_fb);
inst_.manager->submit(opaque_ps_, view);

View File

@ -43,6 +43,28 @@ class WorldPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pass
*
* \{ */
class ShadowPipeline {
private:
Instance &inst_;
PassMain surface_ps_ = {"Shadow.Surface"};
public:
ShadowPipeline(Instance &inst) : inst_(inst){};
PassMain::Sub *surface_material_add(GPUMaterial *gpumat);
void sync();
void render(View &view);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Forward Pass
*
@ -177,19 +199,19 @@ class PipelineModule {
WorldPipeline world;
// DeferredPipeline deferred;
ForwardPipeline forward;
// ShadowPipeline shadow;
ShadowPipeline shadow;
// VelocityPipeline velocity;
UtilityTexture utility_tx;
public:
PipelineModule(Instance &inst) : world(inst), forward(inst){};
PipelineModule(Instance &inst) : world(inst), forward(inst), shadow(inst){};
void sync()
{
// deferred.sync();
forward.sync();
// shadow.sync();
shadow.sync();
// velocity.sync();
}
@ -227,8 +249,7 @@ class PipelineModule {
/* TODO(fclem) volume pass. */
return nullptr;
case MAT_PIPE_SHADOW:
// return shadow.material_add(blender_mat, gpumat);
break;
return shadow.surface_material_add(gpumat);
}
return nullptr;
}

View File

@ -142,6 +142,30 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_light_culling_tile";
case LIGHT_CULLING_ZBIN:
return "eevee_light_culling_zbin";
case SHADOW_DEBUG:
return "eevee_shadow_debug";
case SHADOW_PAGE_ALLOCATE:
return "eevee_shadow_page_allocate";
case SHADOW_PAGE_CLEAR:
return "eevee_shadow_page_clear";
case SHADOW_PAGE_DEFRAG:
return "eevee_shadow_page_defrag";
case SHADOW_PAGE_FREE:
return "eevee_shadow_page_free";
case SHADOW_PAGE_MASK:
return "eevee_shadow_page_mask";
case SHADOW_TILEMAP_BOUNDS:
return "eevee_shadow_tilemap_bounds";
case SHADOW_TILEMAP_FINALIZE:
return "eevee_shadow_tilemap_finalize";
case SHADOW_TILEMAP_INIT:
return "eevee_shadow_tilemap_init";
case SHADOW_TILEMAP_TAG_UPDATE:
return "eevee_shadow_tag_update";
case SHADOW_TILEMAP_TAG_USAGE_OPAQUE:
return "eevee_shadow_tag_usage_opaque";
case SHADOW_TILEMAP_TAG_USAGE_TRANSPARENT:
return "eevee_shadow_tag_usage_transparent";
/* To avoid compiler warning about missing case. */
case MAX_SHADER_TYPE:
return "";
@ -198,8 +222,17 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
/* WORKAROUND: Avoid utility texture merge error. TODO: find a cleaner fix. */
for (auto &resource : info.batch_resources_) {
if (resource.bind_type == ShaderCreateInfo::Resource::BindType::SAMPLER) {
if (resource.slot == RBUFS_UTILITY_TEX_SLOT) {
resource.slot = GPU_max_textures_frag() - 1;
switch (resource.slot) {
case RBUFS_UTILITY_TEX_SLOT:
resource.slot = GPU_max_textures_frag() - 1;
break;
// case SHADOW_RENDER_MAP_SLOT: /* Does not compile because it is a define. */
case SHADOW_ATLAS_TEX_SLOT:
resource.slot = GPU_max_textures_frag() - 2;
break;
case SHADOW_TILEMAPS_TEX_SLOT:
resource.slot = GPU_max_textures_frag() - 3;
break;
}
}
}
@ -214,9 +247,10 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) == false &&
pipeline_type == MAT_PIPE_FORWARD) {
/* Opaque forward do support AOVs and render pass. */
/* Opaque forward do support AOVs and render pass if not using transparency. */
info.additional_info("eevee_aov_out");
info.additional_info("eevee_render_pass_out");
info.additional_info("eevee_cryptomatte_out");
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_BARYCENTRIC)) {
@ -389,9 +423,11 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
break;
case MAT_PIPE_FORWARD_PREPASS:
case MAT_PIPE_DEFERRED_PREPASS:
case MAT_PIPE_SHADOW:
info.additional_info("eevee_surf_depth");
break;
case MAT_PIPE_SHADOW:
info.additional_info("eevee_surf_shadow");
break;
case MAT_PIPE_DEFERRED:
info.additional_info("eevee_surf_deferred");
break;

View File

@ -62,6 +62,19 @@ enum eShaderType {
MOTION_BLUR_TILE_FLATTEN_RENDER,
MOTION_BLUR_TILE_FLATTEN_VIEWPORT,
SHADOW_DEBUG,
SHADOW_PAGE_ALLOCATE,
SHADOW_PAGE_CLEAR,
SHADOW_PAGE_DEFRAG,
SHADOW_PAGE_FREE,
SHADOW_PAGE_MASK,
SHADOW_TILEMAP_BOUNDS,
SHADOW_TILEMAP_FINALIZE,
SHADOW_TILEMAP_INIT,
SHADOW_TILEMAP_TAG_UPDATE,
SHADOW_TILEMAP_TAG_USAGE_OPAQUE,
SHADOW_TILEMAP_TAG_USAGE_TRANSPARENT,
MAX_SHADER_TYPE,
};

View File

@ -21,6 +21,9 @@
namespace blender::eevee {
struct ShadowDirectional;
struct ShadowPunctual;
using namespace draw;
constexpr eGPUSamplerState no_filter = GPU_SAMPLER_DEFAULT;
@ -46,36 +49,21 @@ enum eDebugMode : uint32_t {
*/
DEBUG_HIZ_VALIDATION = 2u,
/**
* Tile-maps to screen. Is also present in other modes.
* - Black pixels, no pages allocated.
* - Green pixels, pages cached.
* - Red pixels, pages allocated.
* Show tiles depending on their status.
*/
DEBUG_SHADOW_TILEMAPS = 10u,
/**
* Random color per pages. Validates page density allocation and sampling.
* Show content of shadow map. Used to verify projection code.
*/
DEBUG_SHADOW_PAGES = 11u,
DEBUG_SHADOW_VALUES = 11u,
/**
* Outputs random color per tile-map (or tile-map level). Validates tile-maps coverage.
* Black means not covered by any tile-maps LOD of the shadow.
* Show random color for each tile. Verify allocation and LOD assignment.
*/
DEBUG_SHADOW_LOD = 12u,
DEBUG_SHADOW_TILE_RANDOM_COLOR = 12u,
/**
* Outputs white pixels for pages allocated and black pixels for unused pages.
* This needs DEBUG_SHADOW_PAGE_ALLOCATION_ENABLED defined in order to work.
* Show random color for each tile. Verify distribution and LOD transitions.
*/
DEBUG_SHADOW_PAGE_ALLOCATION = 13u,
/**
* Outputs the tile-map atlas. Default tile-map is too big for the usual screen resolution.
* Try lowering SHADOW_TILEMAP_PER_ROW and SHADOW_MAX_TILEMAP before using this option.
*/
DEBUG_SHADOW_TILE_ALLOCATION = 14u,
/**
* Visualize linear depth stored in the atlas regions of the active light.
* This way, one can check if the rendering, the copying and the shadow sampling functions works.
*/
DEBUG_SHADOW_SHADOW_DEPTH = 15u
DEBUG_SHADOW_TILEMAP_RANDOM_COLOR = 13u,
};
/** \} */
@ -176,6 +164,11 @@ struct CameraData {
float clip_near;
float clip_far;
eCameraType type;
/** World space distance between view corners at unit distance from camera. */
float screen_diagonal_length;
float _pad0;
float _pad1;
float _pad2;
bool1 initialized;
@ -501,8 +494,7 @@ static inline float regular_polygon_side_length(float sides_count)
* Start first corners at theta == 0. */
static inline float circle_to_polygon_radius(float sides_count, float theta)
{
/* From Graphics Gems from CryENGINE 3 (Siggraph 2013) by Tiago Sousa (slide
* 36). */
/* From Graphics Gems from CryENGINE 3 (Siggraph 2013) by Tiago Sousa (slide 36). */
float side_angle = (2.0f * M_PI) / sides_count;
return cosf(side_angle * 0.5f) /
cosf(theta - side_angle * floorf((sides_count * theta + M_PI) / (2.0f * M_PI)));
@ -582,10 +574,11 @@ BLI_STATIC_ASSERT_ALIGN(LightCullingData, 16)
enum eLightType : uint32_t {
LIGHT_SUN = 0u,
LIGHT_POINT = 1u,
LIGHT_SPOT = 2u,
LIGHT_RECT = 3u,
LIGHT_ELLIPSE = 4u
LIGHT_SUN_ORTHO = 1u,
LIGHT_POINT = 10u,
LIGHT_SPOT = 11u,
LIGHT_RECT = 20u,
LIGHT_ELLIPSE = 21u
};
static inline bool is_area_light(eLightType type)
@ -593,6 +586,11 @@ static inline bool is_area_light(eLightType type)
return type >= LIGHT_RECT;
}
static inline bool is_sun_light(eLightType type)
{
return type < LIGHT_POINT;
}
struct LightData {
/** Normalized object matrix. Last column contains data accessible using the following macros. */
float4x4 object_mat;
@ -602,6 +600,9 @@ struct LightData {
#define _radius _area_size_x
#define _spot_mul object_mat[2][3]
#define _spot_bias object_mat[3][3]
/** Scale to convert from world units to tile space of the clipmap_lod_max. */
#define _clipmap_origin_x object_mat[2][3]
#define _clipmap_origin_y object_mat[3][3]
/** Aliases for axes. */
#ifndef USE_GPU_SHADER_CREATE_INFO
# define _right object_mat[0].xyz()
@ -614,34 +615,210 @@ struct LightData {
# define _back object_mat[2].xyz
# define _position object_mat[3].xyz
#endif
/** Influence radius (inverted and squared) adjusted for Surface / Volume power. */
/** Punctual : Influence radius (inverted and squared) adjusted for Surface / Volume power. */
float influence_radius_invsqr_surface;
float influence_radius_invsqr_volume;
/** Maximum influence radius. Used for culling. */
/** Punctual : Maximum influence radius. Used for culling. Equal to clip far distance. */
float influence_radius_max;
/** Index of the shadow struct on CPU. -1 means no shadow. */
int shadow_id;
/** Special radius factor for point lighting. */
float radius_squared;
/** NOTE: It is ok to use float3 here. A float is declared right after it.
* float3 is also aligned to 16 bytes. */
float3 color;
/** Light Type. */
eLightType type;
/** Spot size. Aligned to size of float2. */
float2 spot_size_inv;
/** Spot angle tangent. */
float spot_tan;
/** Reuse for directionnal lod bias. */
#define _clipmap_lod_bias spot_tan
/** Power depending on shader type. */
float diffuse_power;
float specular_power;
float volume_power;
float transmit_power;
/** Special radius factor for point lighting. */
float radius_squared;
/** Light Type. */
eLightType type;
/** Spot angle tangent. */
float spot_tan;
/** Spot size. Aligned to size of float2. */
float2 spot_size_inv;
/** Associated shadow data. Only valid if shadow_id is not LIGHT_NO_SHADOW. */
// ShadowData shadow_data;
/** --- Shadow Data --- */
/** Directional : Near clip distance. Float stored as int for atomic operations. */
int clip_near;
int clip_far;
/** Directional : Clip-map lod range to avoid sampling outside of valid range. */
int clipmap_lod_min;
int clipmap_lod_max;
/** Index of the first tile-map. */
int tilemap_index;
/** Directional : Offset of the lod min in lod min tile units. */
int2 clipmap_base_offset;
/** Punctual & Directional : Normal matrix packed for automatic bias. */
float2 normal_mat_packed;
};
BLI_STATIC_ASSERT_ALIGN(LightData, 16)
static inline int light_tilemap_max_get(LightData light)
{
/* This is not something we need in performance critical code. */
return light.tilemap_index + (light.clipmap_lod_max - light.clipmap_lod_min);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadows
*
* Shadow data for either a directional shadow or a punctual shadow.
*
* A punctual shadow is composed of 1, 5 or 6 shadow regions.
* Regions are sorted in this order -Z, +X, -X, +Y, -Y, +Z.
* Face index is computed from light's object space coordinates.
*
* A directional light shadow is composed of multiple clip-maps with each level
* covering twice as much area as the previous one.
* \{ */
enum eShadowProjectionType : uint32_t {
SHADOW_PROJECTION_CUBEFACE = 0u,
SHADOW_PROJECTION_CLIPMAP = 1u,
SHADOW_PROJECTION_CASCADE = 2u,
};
static inline int2 shadow_cascade_grid_offset(int2 base_offset, int level_relative)
{
return (base_offset * level_relative) / (1 << 16);
}
/**
* Small descriptor used for the tile update phase. Updated by CPU & uploaded to GPU each redraw.
*/
struct ShadowTileMapData {
/** Cached, used for rendering. */
float4x4 viewmat, winmat;
/** Punctual : Corners of the frustum. (vec3 padded to vec4) */
float4 corners[4];
/** Integer offset of the center of the 16x16 tiles from the origin of the tile space. */
int2 grid_offset;
/** Shift between previous and current grid_offset. Allows update tagging. */
int2 grid_shift;
/** True for punctual lights. */
eShadowProjectionType projection_type;
/** Multiple of SHADOW_TILEDATA_PER_TILEMAP. Offset inside the tile buffer. */
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;
};
BLI_STATIC_ASSERT_ALIGN(ShadowTileMapData, 16)
/**
* Per tilemap data persistent on GPU.
*/
struct ShadowTileMapClip {
/** Clip distances that were used to render the pages. */
float clip_near_stored;
float clip_far_stored;
/** Near and far clip distances for directional. Float stored as int for atomic operations. */
int clip_near;
int clip_far;
};
BLI_STATIC_ASSERT_ALIGN(ShadowTileMapClip, 16)
struct ShadowPagesInfoData {
/** Number of free pages in the free page buffer. */
int page_free_count;
/** Number of page allocations needed for this cycle. */
int page_alloc_count;
/** Index of the next cache page in the cached page buffer. */
uint page_cached_next;
/** Index of the first page in the buffer since the last defrag. */
uint page_cached_start;
/** Index of the last page in the buffer since the last defrag. */
uint page_cached_end;
/** Number of views to be rendered during the shadow update pass. */
int view_count;
/** Physical page size in pixel. Pages are all squares. */
int page_size;
int _pad0;
};
BLI_STATIC_ASSERT_ALIGN(ShadowPagesInfoData, 16)
struct ShadowStatistics {
/** Statistics that are read back to CPU after a few frame (to avoid stall). */
int page_used_count;
int page_update_count;
int page_allocated_count;
int page_rendered_count;
};
BLI_STATIC_ASSERT_ALIGN(ShadowStatistics, 16)
/** Decoded tile data structure. */
struct ShadowTileData {
/** Page inside the virtual shadow map atlas. */
uint2 page;
/** Page index inside pages_cached_buf. Only valid if `is_cached` is true. */
uint cache_index;
/** Lod pointed to LOD 0 tile page. (cubemap only) */
uint lod;
/** If the tile is needed for rendering. */
bool is_used;
/** True if an update is needed. This persists even if the tile gets unused. */
bool do_update;
/** True if the tile owns the page (mutually exclusive with `is_cached`). */
bool is_allocated;
/** True if the tile has been staged for rendering. This will remove the `do_update` flag. */
bool is_rendered;
/** True if the tile is inside the pages_cached_buf (mutually exclusive with `is_allocated`). */
bool is_cached;
};
/** \note Stored packed as a uint. */
#define ShadowTileDataPacked uint
enum eShadowFlag : uint32_t {
SHADOW_NO_DATA = 0u,
SHADOW_IS_CACHED = (1u << 27u),
SHADOW_IS_ALLOCATED = (1u << 28u),
SHADOW_DO_UPDATE = (1u << 29u),
SHADOW_IS_RENDERED = (1u << 30u),
SHADOW_IS_USED = (1u << 31u)
};
static inline ShadowTileData shadow_tile_unpack(ShadowTileDataPacked data)
{
ShadowTileData tile;
/* Tweaked for SHADOW_PAGE_PER_ROW = 64. */
tile.page.x = data & 63u;
tile.page.y = (data >> 6u) & 63u;
/* -- 12 bits -- */
/* Tweaked for SHADOW_TILEMAP_LOD < 8. */
tile.lod = (data >> 12u) & 7u;
/* -- 15 bits -- */
/* Tweaked for SHADOW_MAX_TILEMAP = 4096. */
tile.cache_index = (data >> 15u) & 4095u;
/* -- 27 bits -- */
tile.is_used = (data & SHADOW_IS_USED) != 0;
tile.is_cached = (data & SHADOW_IS_CACHED) != 0;
tile.is_allocated = (data & SHADOW_IS_ALLOCATED) != 0;
tile.is_rendered = (data & SHADOW_IS_RENDERED) != 0;
tile.do_update = (data & SHADOW_DO_UPDATE) != 0;
return tile;
}
static inline ShadowTileDataPacked shadow_tile_pack(ShadowTileData tile)
{
uint data;
data = (tile.page.x & 63u);
data |= (tile.page.y & 63u) << 6u;
data |= (tile.lod & 7u) << 12u;
data |= (tile.cache_index & 4095u) << 15u;
data |= (tile.is_used ? SHADOW_IS_USED : 0);
data |= (tile.is_allocated ? SHADOW_IS_ALLOCATED : 0);
data |= (tile.is_cached ? SHADOW_IS_CACHED : 0);
data |= (tile.is_rendered ? SHADOW_IS_RENDERED : 0);
data |= (tile.do_update ? SHADOW_DO_UPDATE : 0);
return data;
}
/** \} */
/* -------------------------------------------------------------------- */
@ -761,6 +938,13 @@ using LightDataBuf = draw::StorageArrayBuffer<LightData, LIGHT_CHUNK>;
using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
using SamplingDataBuf = draw::StorageBuffer<SamplingData>;
using ShadowStatisticsBuf = draw::StorageBuffer<ShadowStatistics>;
using ShadowPagesInfoDataBuf = draw::StorageBuffer<ShadowPagesInfoData>;
using ShadowPageHeapBuf = draw::StorageVectorBuffer<uint, SHADOW_MAX_PAGE>;
using ShadowPageCacheBuf = draw::StorageArrayBuffer<uint2, SHADOW_MAX_PAGE, true>;
using ShadowTileMapDataBuf = draw::StorageVectorBuffer<ShadowTileMapData, SHADOW_MAX_TILEMAP>;
using ShadowTileMapClipBuf = draw::StorageArrayBuffer<ShadowTileMapClip, SHADOW_MAX_TILEMAP, true>;
using ShadowTileDataBuf = draw::StorageArrayBuffer<ShadowTileDataPacked, SHADOW_MAX_TILE, true>;
using VelocityGeometryBuf = draw::StorageArrayBuffer<float4, 16, true>;
using VelocityIndexBuf = draw::StorageArrayBuffer<VelocityIndex, 16>;
using VelocityObjectBuf = draw::StorageArrayBuffer<float4x4, 16>;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,449 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation.
*/
/** \file
* \ingroup eevee
*
* The shadow module manages shadow update tagging & shadow rendering.
*/
#pragma once
#include "BLI_pool.hh"
#include "BLI_vector.hh"
#include "GPU_batch.h"
#include "eevee_material.hh"
#include "eevee_shader.hh"
#include "eevee_shader_shared.hh"
namespace blender::eevee {
class Instance;
class ShadowModule;
class ShadowPipeline;
struct Light;
enum eCubeFace {
/* Ordering by culling order. If cone aperture is shallow, we cull the later view. */
Z_NEG = 0,
X_POS,
X_NEG,
Y_POS,
Y_NEG,
Z_POS,
};
/* To be applied after view matrix. Follow same order as eCubeFace. */
constexpr static const float shadow_face_mat[6][4][4] = {
{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}, /* Z_NEG */
{{0, 0, -1, 0}, {-1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}, /* X_POS */
{{0, 0, 1, 0}, {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}, /* X_NEG */
{{1, 0, 0, 0}, {0, 0, -1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}, /* Y_POS */
{{-1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}, /* Y_NEG */
{{1, 0, 0, 0}, {0, -1, 0, 0}, {0, 0, -1, 0}, {0, 0, 0, 1}}, /* Z_POS */
};
/* Converts to [-SHADOW_TILEMAP_RES / 2..SHADOW_TILEMAP_RES / 2] for XY and [0..1] for Z. */
constexpr static const float shadow_clipmap_scale_mat[4][4] = {{SHADOW_TILEMAP_RES / 2, 0, 0, 0},
{0, SHADOW_TILEMAP_RES / 2, 0, 0},
{0, 0, 0.5, 0},
{0, 0, 0.5, 1}};
/* -------------------------------------------------------------------- */
/** \name Tile-Map
*
* Stores indirection table and states of each tile of a virtual shadow-map.
* One tile-map has the effective resolution of `pagesize * tile_map_resolution`.
* Each tile-map overhead is quite small if they do not have any pages allocated.
*
* \{ */
struct ShadowTileMap : public ShadowTileMapData {
static constexpr int64_t tile_map_resolution = SHADOW_TILEMAP_RES;
static constexpr int64_t tiles_count = tile_map_resolution * tile_map_resolution;
/** Level of detail for clipmap. */
int level = INT_MAX;
/** Cube face index. */
eCubeFace cubeface = Z_NEG;
/** Cached, used for detecting updates. */
float4x4 object_mat;
/** Near and far clip distances. For clip-map, computed on the GPU using casters BBoxes. */
float near, far;
public:
ShadowTileMap(int tiles_index_)
{
tiles_index = tiles_index_;
/* For now just the same index. */
clip_data_index = tiles_index_ / SHADOW_TILEDATA_PER_TILEMAP;
this->set_dirty();
}
void sync_orthographic(const float4x4 &object_mat_,
int2 origin_offset,
int clipmap_level,
float lod_bias_,
eShadowProjectionType projection_type_);
void sync_cubeface(
const float4x4 &object_mat, float near, float far, eCubeFace face, float lod_bias_);
void debug_draw() const;
void set_dirty()
{
grid_shift = int2(SHADOW_TILEMAP_RES);
}
void set_updated()
{
grid_shift = int2(0);
}
};
/**
* The tile-maps are managed on CPU and associated with each light shadow object.
*
* The number of tile-maps & tiles is unbounded (to the limit of SSBOs), but the actual number
* used for rendering is caped to 4096. This is to simplify tile-maps management on CPU.
*
* At sync end, all tile-maps are grouped by light inside the ShadowTileMapDataBuf so that each
* light has a contiguous range of tile-maps to refer to.
*/
struct ShadowTileMapPool {
public:
/** Limit the width of the texture. */
static constexpr int64_t maps_per_row = SHADOW_TILEMAP_PER_ROW;
/** Vector containing available offset to tile range in the ShadowTileDataBuf. */
Vector<uint> free_indices;
/** Pool containing shadow tile structure on CPU. */
Pool<ShadowTileMap> tilemap_pool;
/** Sorted descriptions for each tilemap in the pool. Updated each frame. */
ShadowTileMapDataBuf tilemaps_data = {"tilemaps_data"};
/** Previously used tile-maps that needs to release their tiles/pages. Updated each frame. */
ShadowTileMapDataBuf tilemaps_unused = {"tilemaps_unused"};
/** All possible tiles. A range of tiles tile is referenced by a tile-map. */
ShadowTileDataBuf tiles_data = {"tiles_data"};
/** Clip range for directional shadows. Updated on GPU. Persistent. */
ShadowTileMapClipBuf tilemaps_clip = {"tilemaps_clip"};
/** Texture equivalent of ShadowTileDataBuf but grouped by light. */
Texture tilemap_tx = {"tilemap_tx"};
/** Number of free tile-maps at the end of the previous sync. */
int64_t last_free_len = 0;
public:
ShadowTileMapPool();
ShadowTileMap *acquire();
/**
* Push the given list of ShadowTileMap onto the free stack. Their pages will be free.
*/
void release(Span<ShadowTileMap *> free_list);
void end_sync(ShadowModule &module);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Casters & Receivers
*
* \{ */
/* Can be either a shadow caster or a shadow receiver. */
struct ShadowObject {
ResourceHandle resource_handle = {0};
bool used = true;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name ShadowModule
*
* Manages shadow atlas and shadow region data.
* \{ */
class ShadowModule {
friend ShadowPunctual;
friend ShadowDirectional;
friend ShadowPipeline;
friend ShadowTileMapPool;
public:
/** Need to be first because of destructor order. */
ShadowTileMapPool tilemap_pool;
Pool<ShadowPunctual> punctual_pool;
Pool<ShadowDirectional> directional_pool;
private:
Instance &inst_;
/** Map of shadow casters to track deletion & update of intersected shadows. */
Map<ObjectKey, ShadowObject> objects_;
/* -------------------------------------------------------------------- */
/** \name Tilemap Management
* \{ */
PassSimple tilemap_setup_ps_ = {"TilemapSetup"};
PassMain tilemap_usage_ps_ = {"TagUsage"};
PassSimple tilemap_update_ps_ = {"TilemapUpdate"};
PassMain::Sub *tilemap_usage_transparent_ps_ = nullptr;
GPUBatch *box_batch_ = nullptr;
Framebuffer usage_tag_fb;
/** List of Resource IDs (to get bounds) for tagging passes. */
StorageVectorBuffer<uint, 128> past_casters_updated_ = {"PastCastersUpdated"};
StorageVectorBuffer<uint, 128> curr_casters_updated_ = {"CurrCastersUpdated"};
/** List of Resource IDs (to get bounds) for getting minimum clip-maps bounds. */
StorageVectorBuffer<uint, 128> curr_casters_ = {"CurrCasters"};
/** Indirect arguments for page clearing. */
StorageBuffer<DispatchCommand> clear_dispatch_buf_;
/** Pages to clear. */
StorageArrayBuffer<uint, SHADOW_MAX_PAGE> clear_page_buf_ = {"clear_page_buf"};
int3 dispatch_depth_scan_size_;
/* Ratio between tilemap pixel world "radius" and film pixel world "radius". */
float tilemap_projection_ratio_;
/* Statistics that are read back to CPU after a few frame (to avoid stall). */
SwapChain<ShadowStatisticsBuf, 5> statistics_buf_;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Page Management
* \{ */
static constexpr eGPUTextureFormat atlas_type = GPU_R32UI;
/** Atlas containing all physical pages. */
Texture atlas_tx_ = {"shadow_atlas_tx_"};
/** Pool of unallocated pages waiting to be assigned to specific tiles in the tilemap atlas. */
ShadowPageHeapBuf pages_free_data_ = {"PagesFreeBuf"};
/** Pool of cached tiles waiting to be reused. */
ShadowPageCacheBuf pages_cached_data_ = {"PagesCachedBuf"};
/** Infos for book keeping and debug. */
ShadowPagesInfoDataBuf pages_infos_data_ = {"PagesInfosBuf"};
int3 copy_dispatch_size_;
int3 scan_dispatch_size_;
int rendering_tilemap_;
int rendering_lod_;
bool do_full_update = true;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Rendering
* \{ */
/** Multi-View containing a maximum of 64 view to be rendered with the shadow pipeline. */
View shadow_multi_view_ = {"ShadowMultiView", SHADOW_VIEW_MAX, true};
/** Tile to physical page mapping. This is an array texture with one layer per view. */
Texture render_map_tx_ = {"ShadowRenderMap",
GPU_R32UI,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE,
int2(SHADOW_TILEMAP_RES),
64,
nullptr,
SHADOW_TILEMAP_LOD + 1};
/** An empty frame-buffer (no attachment) the size of a whole tilemap. */
Framebuffer render_fb_;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging
* \{ */
/** Display informations about the virtual shadows. */
PassSimple debug_draw_ps_ = {"Shadow.Debug"};
/** \} */
/** Scene immutable parameters. */
/** 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;
public:
ShadowModule(Instance &inst);
~ShadowModule(){};
void init();
void begin_sync();
/** Register a shadow caster or receiver. */
void sync_object(const ObjectHandle &handle,
const ResourceHandle &resource_handle,
bool is_shadow_caster,
bool is_alpha_blend);
void end_sync();
void set_lights_data();
void set_view(View &view);
void debug_end_sync();
void debug_draw(View &view, GPUFrameBuffer *view_fb);
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(SHADOW_ATLAS_TEX_SLOT, &atlas_tx_);
pass->bind_texture(SHADOW_TILEMAPS_TEX_SLOT, &tilemap_pool.tilemap_tx);
}
private:
void remove_unused();
void debug_page_map_call(DRWPass *pass);
/** Compute approximate screen pixel space radius. */
float screen_pixel_radius(const View &view, const int2 &extent);
/** Compute approximate punctual shadow pixel world space radius, 1 unit away of the light. */
float tilemap_pixel_radius();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow
*
* A shadow component is associated to a `eevee::Light` and manages its associated Tile-maps.
* \{ */
class ShadowPunctual : public NonCopyable, NonMovable {
private:
ShadowModule &shadows_;
/** Tile-map for each cube-face needed (in eCubeFace order). */
Vector<ShadowTileMap *> tilemaps_;
/** Area light size. */
float size_x_, size_y_;
/** Shape type. */
eLightType light_type_;
/** Random position on the light. In world space. */
float3 random_offset_;
/** Light position. */
float3 position_;
/** Near and far clip distances. */
float far_, near_;
/** Number of tile-maps needed to cover the light angular extents. */
int tilemaps_needed_;
/** Visibility cone angle from the light source. */
int cone_aperture_;
public:
ShadowPunctual(ShadowModule &module) : shadows_(module){};
ShadowPunctual(ShadowPunctual &&other)
: shadows_(other.shadows_), tilemaps_(std::move(other.tilemaps_)){};
~ShadowPunctual()
{
shadows_.tilemap_pool.release(tilemaps_);
}
/**
* Sync shadow parameters but do not allocate any shadow tile-maps.
*/
void sync(eLightType light_type,
const float4x4 &object_mat,
float cone_aperture,
float near_clip,
float far_clip);
/**
* Release the tile-maps that will not be used in the current frame.
*/
void release_excess_tilemaps();
/**
* Allocate shadow tile-maps and setup views for rendering.
*/
void end_sync(Light &light, float lod_bias);
};
class ShadowDirectional : public NonCopyable, NonMovable {
private:
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;
public:
ShadowDirectional(ShadowModule &module) : shadows_(module){};
ShadowDirectional(ShadowDirectional &&other)
: shadows_(other.shadows_), tilemaps_(std::move(other.tilemaps_)){};
~ShadowDirectional()
{
shadows_.tilemap_pool.release(tilemaps_);
}
/**
* Sync shadow parameters but do not allocate any shadow tile-maps.
*/
void sync(const float4x4 &object_mat, float min_resolution);
/**
* Release the tile-maps that will not be used in the current frame.
*/
void release_excess_tilemaps(const Camera &camera, float lod_bias);
/**
* Allocate shadow tile-maps and setup views for rendering.
*/
void end_sync(Light &light, const Camera &camera, float lod_bias);
/* Return coverage of the whole tilemap in world unit. */
static float coverage_get(int lvl)
{
/* This function should be kept in sync with shadow_directional_level(). */
/* \note: If we would to introduce a global scaling option it would be here. */
return exp2(lvl);
}
/* Return coverage of a single tile for a tilemap of this LOD in world unit. */
static float tile_size_get(int lvl)
{
return coverage_get(lvl) / SHADOW_TILEMAP_RES;
}
private:
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 cascade_tilemaps_distribution_near_far_points(const Camera &camera,
float3 &near_point,
float3 &far_point);
/* Choose between clipmap and cascade distribution of shadowmap precision depending on the camera
* projection type and bounds. */
static eShadowProjectionType directional_distribution_type_get(const Camera &camera);
};
/** \} */
} // namespace blender::eevee

View File

@ -41,6 +41,7 @@ ObjectHandle &SyncModule::sync_object(Object *ob)
ObjectHandle &eevee_dd = *reinterpret_cast<ObjectHandle *>(dd);
if (eevee_dd.object_key.ob == nullptr) {
ob = DEG_get_original_object(ob);
eevee_dd.object_key = ObjectKey(ob);
}
@ -48,7 +49,6 @@ ObjectHandle &SyncModule::sync_object(Object *ob)
ID_RECALC_GEOMETRY;
if ((eevee_dd.recalc & recalc_flags) != 0) {
inst_.sampling.reset();
UNUSED_VARS(inst_);
}
return eevee_dd;
@ -127,13 +127,13 @@ void SyncModule::sync_mesh(Object *ob,
if (geom == nullptr) {
continue;
}
Material *material = material_array.materials[i];
geometry_call(material->shading.sub_pass, geom, res_handle);
geometry_call(material->prepass.sub_pass, geom, res_handle);
geometry_call(material->shadow.sub_pass, geom, res_handle);
Material &material = material_array.materials[i];
geometry_call(material.shading.sub_pass, geom, res_handle);
geometry_call(material.prepass.sub_pass, geom, res_handle);
geometry_call(material.shadow.sub_pass, geom, res_handle);
is_shadow_caster = is_shadow_caster || material->shadow.sub_pass != nullptr;
is_alpha_blend = is_alpha_blend || material->is_alpha_blend_transparent;
is_shadow_caster = is_shadow_caster || material.shadow.sub_pass != nullptr;
is_alpha_blend = is_alpha_blend || material.is_alpha_blend_transparent;
GPUMaterial *gpu_material = material_array.gpu_materials[i];
::Material *mat = GPU_material_get_material(gpu_material);
@ -141,8 +141,9 @@ void SyncModule::sync_mesh(Object *ob,
}
inst_.manager->extract_object_attributes(res_handle, ob_ref, material_array.gpu_materials);
inst_.shadows.sync_object(ob_handle, res_handle, is_shadow_caster, is_alpha_blend);
inst_.cryptomatte.sync_object(ob, res_handle);
// shadows.sync_object(ob, ob_handle, is_shadow_caster, is_alpha_blend);
}
/** \} */
@ -236,7 +237,7 @@ static void gpencil_stroke_sync(bGPDlayer * /*gpl*/,
{
gpIterData &iter = *(gpIterData *)thunk;
Material *material = iter.material_array.materials[gps->mat_nr];
Material *material = &iter.material_array.materials[gps->mat_nr];
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(iter.ob, gps->mat_nr + 1);
bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0;
@ -280,9 +281,9 @@ void SyncModule::sync_gpencil(Object *ob, ObjectHandle &ob_handle, ResourceHandl
gpencil_drawcall_flush(iter);
// bool is_caster = true; /* TODO material.shadow.sub_pass. */
// bool is_alpha_blend = true; /* TODO material.is_alpha_blend. */
// shadows.sync_object(ob, ob_handle, is_caster, is_alpha_blend);
bool is_caster = true; /* TODO material.shadow.sub_pass. */
bool is_alpha_blend = true; /* TODO material.is_alpha_blend. */
inst_.shadows.sync_object(ob_handle, res_handle, is_caster, is_alpha_blend);
}
/** \} */
@ -347,9 +348,9 @@ void SyncModule::sync_curves(Object *ob,
/* TODO(fclem) Hair velocity. */
// shading_passes.velocity.gpencil_add(ob, ob_handle);
// bool is_caster = material.shadow.sub_pass != nullptr;
// bool is_alpha_blend = material.is_alpha_blend_transparent;
// shadows.sync_object(ob, ob_handle, is_caster, is_alpha_blend);
bool is_caster = material.shadow.sub_pass != nullptr;
bool is_alpha_blend = material.is_alpha_blend_transparent;
inst_.shadows.sync_object(ob_handle, res_handle, is_caster, is_alpha_blend);
}
/** \} */

View File

@ -261,7 +261,7 @@ void VelocityModule::end_sync()
{
Vector<ObjectKey, 0> deleted_obj;
uint32_t max_resource_id_ = 0u;
uint32_t max_resource_id_ = 1u;
for (Map<ObjectKey, VelocityObjectData>::Item item : velocity_map.items()) {
if (item.value.obj.resource_id == uint32_t(-1)) {

View File

@ -134,13 +134,12 @@ void ShadingView::render()
inst_.lights.debug_draw(render_view_new_, combined_fb_);
inst_.hiz_buffer.debug_draw(render_view_new_, combined_fb_);
inst_.shadows.debug_draw(render_view_new_, combined_fb_);
GPUTexture *combined_final_tx = render_postfx(rbufs.combined_tx);
inst_.film.accumulate(sub_view_, combined_final_tx);
// inst_.shadows.debug_draw();
rbufs.release();
postfx_tx_.release();

View File

@ -16,6 +16,7 @@
# ifdef OBINFO_LIB
vec3 attr_load_orco(vec4 orco)
{
# ifdef GPU_VERTEX_SHADER
/* We know when there is no orco layer when orco.w is 1.0 because it uses the generic vertex
* attribute (which is [0,0,0,1]). */
if (orco.w == 1.0) {
@ -23,6 +24,7 @@ vec3 attr_load_orco(vec4 orco)
* using the orco_madd factors. */
return OrcoTexCoFactors[0].xyz + pos * OrcoTexCoFactors[1].xyz;
}
# endif
return orco.xyz * 0.5 + 0.5;
}
# endif

View File

@ -9,6 +9,11 @@
void main()
{
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_interp.view_id = drw_view_id;
#endif
init_interface();
vec3 T;

View File

@ -7,6 +7,11 @@
void main()
{
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_interp.view_id = drw_view_id;
#endif
init_interface();
/* TODO(fclem): Expose through a node? */

View File

@ -7,6 +7,11 @@
void main()
{
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_interp.view_id = drw_view_id;
#endif
init_interface();
interp.P = point_object_to_world(pos);

View File

@ -22,7 +22,7 @@ void main()
}
/* Sun lights are packed at the end of the array. Perform early copy. */
if (light.type == LIGHT_SUN) {
if (is_sun_light(light.type)) {
/* NOTE: We know the index because sun lights are packed at the start of the input buffer. */
out_light_buf[light_cull_buf.local_lights_len + l_idx] = light;
return;

View File

@ -12,6 +12,7 @@
*/
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
/* TODO(fclem): We could reduce register pressure by only having static branches for sun lights. */
@ -19,6 +20,7 @@ void light_eval_ex(ClosureDiffuse diffuse,
ClosureReflection reflection,
const bool is_directional,
vec3 P,
vec3 Ng,
vec3 V,
float vP_z,
float thickness,
@ -34,17 +36,17 @@ void light_eval_ex(ClosureDiffuse diffuse,
float visibility = light_attenuation(light, L, dist);
#if 0 /* TODO(fclem): Shadows */
if ((light.shadow_id != LIGHT_NO_SHADOW) && (visibility > 0.0)) {
if (light.tilemap_index != LIGHT_NO_SHADOW && (visibility > 0.0)) {
vec3 lL = light_world_to_local(light, -L) * dist;
vec3 lNg = light_world_to_local(light, Ng);
float shadow_delta = shadow_delta_get(
shadow_atlas_tx, shadow_tilemaps_tx, light, light.shadow_data, lL, dist, P);
ShadowSample samp = shadow_sample(
is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, lL, lNg, P);
# ifdef SSS_TRANSMITTANCE
/* Transmittance evaluation first to use initial visibility. */
#ifdef SSS_TRANSMITTANCE
/* Transmittance evaluation first to use initial visibility without shadow. */
if (diffuse.sss_id != 0u && light.diffuse_power > 0.0) {
float delta = max(thickness, shadow_delta);
float delta = max(thickness, samp.occluder_delta + samp.bias);
vec3 intensity = visibility * light.transmit_power *
light_translucent(sss_transmittance_tx,
@ -57,11 +59,9 @@ void light_eval_ex(ClosureDiffuse diffuse,
delta);
out_diffuse += light.color * intensity;
}
# endif
visibility *= float(shadow_delta - light.shadow_data.bias <= 0.0);
}
#endif
visibility *= float(samp.occluder_delta + samp.bias >= 0.0);
}
if (visibility < 1e-6) {
return;
@ -84,6 +84,7 @@ void light_eval_ex(ClosureDiffuse diffuse,
void light_eval(ClosureDiffuse diffuse,
ClosureReflection reflection,
vec3 P,
vec3 Ng,
vec3 V,
float vP_z,
float thickness,
@ -100,6 +101,7 @@ void light_eval(ClosureDiffuse diffuse,
reflection,
true,
P,
Ng,
V,
vP_z,
thickness,
@ -117,6 +119,7 @@ void light_eval(ClosureDiffuse diffuse,
reflection,
false,
P,
Ng,
V,
vP_z,
thickness,

View File

@ -9,7 +9,8 @@
void light_vector_get(LightData ld, vec3 P, out vec3 L, out float dist)
{
if (ld.type == LIGHT_SUN) {
/* TODO(fclem): Static branching. */
if (is_sun_light(ld.type)) {
L = ld._back;
dist = 1.0;
}
@ -57,10 +58,13 @@ float light_attenuation(LightData ld, vec3 L, float dist)
if (ld.type == LIGHT_SPOT) {
vis *= light_spot_attenuation(ld, L);
}
if (ld.type >= LIGHT_SPOT) {
vis *= step(0.0, -dot(L, -ld._back));
}
if (ld.type != LIGHT_SUN) {
/* TODO(fclem): Static branching. */
if (!is_sun_light(ld.type)) {
#ifdef VOLUME_LIGHTING
vis *= light_influence_attenuation(dist, ld.influence_radius_invsqr_volume);
#else

View File

@ -0,0 +1,196 @@
/**
* Debug drawing for virtual shadowmaps.
* See eShadowDebug for more information.
*/
#pragma BLENDER_REQUIRE(common_debug_print_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
/** Control the scaling of the tilemap splat. */
const float pixel_scale = 4.0;
vec3 debug_random_color(ivec2 v)
{
float r = interlieved_gradient_noise(vec2(v), 0.0, 0.0);
return hue_gradient(r);
}
vec3 debug_random_color(int v)
{
return debug_random_color(ivec2(v, 0));
}
void debug_tile_print(ShadowTileData tile, ivec4 tile_coord)
{
drw_print("Tile (", tile_coord.x, ",", tile_coord.y, ") in Tilemap ", tile_coord.z, " : ");
drw_print(tile.lod);
drw_print(tile.page);
drw_print(tile.cache_index);
}
vec3 debug_tile_state_color(ShadowTileData tile)
{
if (tile.lod > 0) {
/* Uses data from another LOD. */
return neon_gradient(float(tile.lod) / float(SHADOW_TILEMAP_LOD));
}
if (tile.do_update && tile.is_used) {
/* Updated. */
return vec3(0.5, 1, 0);
}
if (tile.is_used) {
/* Used but was cached. */
return vec3(0, 1, 0);
}
vec3 col = vec3(0);
if (tile.is_cached) {
col += vec3(0.2, 0, 0.5);
if (tile.do_update) {
col += vec3(0.8, 0, 0);
}
}
return col;
}
ShadowSample debug_tile_get(vec3 P, LightData light)
{
vec3 lNg = vec3(1.0, 0.0, 0.0);
if (is_sun_light(light.type)) {
return shadow_directional_sample_get(shadow_atlas_tx, shadow_tilemaps_tx, light, P, lNg);
}
else {
vec3 lL = light_world_to_local(light, P - light._position);
return shadow_punctual_sample_get(shadow_atlas_tx, shadow_tilemaps_tx, light, lL, lNg);
}
}
LightData debug_light_get()
{
LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(light_cull_buf, l_idx)
{
LightData light = light_buf[l_idx];
if (light.tilemap_index == debug_tilemap_index) {
return light;
}
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_DIRECTIONAL(light_cull_buf, l_idx)
{
LightData light = light_buf[l_idx];
if (light.tilemap_index == debug_tilemap_index) {
return light;
}
}
LIGHT_FOREACH_END
}
/** Return true if a pixel was written. */
bool debug_tilemaps(vec3 P, LightData light)
{
const int debug_tile_size_px = 4;
ivec2 px = ivec2(gl_FragCoord.xy) / debug_tile_size_px;
int tilemap = px.x / SHADOW_TILEMAP_RES;
int tilemap_index = light.tilemap_index + tilemap;
if ((px.y < SHADOW_TILEMAP_RES) && (tilemap_index <= light_tilemap_max_get(light))) {
/* Debug actual values in the tilemap buffer. */
ShadowTileMapData tilemap = tilemaps_buf[tilemap_index];
int tile_index = shadow_tile_offset(px % SHADOW_TILEMAP_RES, tilemap.tiles_index, 0);
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
/* Leave 1 px border between tilemaps. */
if (!any(
equal(ivec2(gl_FragCoord.xy) % (SHADOW_TILEMAP_RES * debug_tile_size_px), ivec2(0)))) {
gl_FragDepth = 0.0;
out_color_add = vec4(debug_tile_state_color(tile), 0.0);
out_color_mul = vec4(0.0);
if (ivec2(gl_FragCoord.xy) == ivec2(0)) {
drw_print(light.object_mat);
}
return true;
}
}
return false;
}
void debug_tile_state(vec3 P, LightData light)
{
ShadowSample samp = debug_tile_get(P, light);
out_color_add = vec4(debug_tile_state_color(samp.tile), 0) * 0.5;
out_color_mul = vec4(0.5);
}
void debug_atlas_values(vec3 P, LightData light)
{
ShadowSample samp = debug_tile_get(P, light);
out_color_add = vec4(vec3(samp.occluder_dist), 0);
out_color_mul = vec4(0.0);
}
void debug_random_tile_color(vec3 P, LightData light)
{
ShadowSample samp = debug_tile_get(P, light);
out_color_add = vec4(debug_random_color(ivec2(samp.tile.page)), 0) * 0.5;
out_color_mul = vec4(0.5);
}
void debug_random_tilemap_color(vec3 P, LightData light)
{
ShadowCoordinates coord;
if (is_sun_light(light.type)) {
vec3 lP = shadow_world_to_local(light, P);
coord = shadow_directional_coordinates(light, lP);
}
else {
vec3 lP = light_world_to_local(light, P - light._position);
int face_id = shadow_punctual_face_index_get(lP);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
coord = shadow_punctual_coordinates(light, lP, face_id);
}
out_color_add = vec4(debug_random_color(ivec2(coord.tilemap_index)), 0) * 0.5;
out_color_mul = vec4(0.5);
}
void main()
{
/* Default to no output. */
gl_FragDepth = 1.0;
out_color_add = vec4(0.0);
out_color_mul = vec4(1.0);
float depth = texelFetch(hiz_tx, ivec2(gl_FragCoord.xy), 0).r;
vec3 P = get_world_space_from_depth(uvcoordsvar.xy, depth);
/* Make it pass the depth test. */
gl_FragDepth = depth - 1e-6;
LightData light = debug_light_get();
if (debug_tilemaps(P, light)) {
return;
}
if (depth != 1.0) {
switch (eDebugMode(debug_mode)) {
case DEBUG_SHADOW_TILEMAPS:
debug_tile_state(P, light);
break;
case DEBUG_SHADOW_VALUES:
debug_atlas_values(P, light);
break;
case DEBUG_SHADOW_TILE_RANDOM_COLOR:
debug_random_tile_color(P, light);
break;
case DEBUG_SHADOW_TILEMAP_RANDOM_COLOR:
debug_random_tilemap_color(P, light);
break;
}
}
}

View File

@ -0,0 +1,222 @@
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
/** \a unormalized_uv is the uv coordinates for the whole tilemap [0..SHADOW_TILEMAP_RES]. */
vec2 shadow_page_uv_transform(
vec2 atlas_size, uvec2 page, uint lod, vec2 unormalized_uv, ivec2 tile_lod0_coord)
{
/* Bias uv sample for LODs since custom raster aligns LOD pixels instead of centering them. */
if (lod != 0) {
unormalized_uv += 0.5 / float(SHADOW_PAGE_RES * SHADOW_TILEMAP_RES);
}
float lod_scaling = exp2(-float(lod));
vec2 target_tile = vec2(tile_lod0_coord >> lod);
vec2 page_uv = unormalized_uv * lod_scaling - target_tile;
/* Assumes atlas is squared. */
vec2 atlas_uv = (vec2(page) + min(page_uv, 0.99999)) * float(SHADOW_PAGE_RES) / atlas_size;
return atlas_uv;
}
/* Rotate vector to light's local space . Used for directional shadows. */
vec3 shadow_world_to_local(LightData ld, vec3 L)
{
/* Avoid relying on compiler to optimize this.
* vec3 lL = transpose(mat3(ld.object_mat)) * L; */
vec3 lL;
lL.x = dot(ld.object_mat[0].xyz, L);
lL.y = dot(ld.object_mat[1].xyz, L);
lL.z = dot(ld.object_mat[2].xyz, L);
return lL;
}
/* TODO(fclem) use utildef version. */
float shadow_orderedIntBitsToFloat(int int_value)
{
return intBitsToFloat((int_value < 0) ? (int_value ^ 0x7FFFFFFF) : int_value);
}
/* ---------------------------------------------------------------------- */
/** \name Shadow Sampling Functions
* \{ */
/* Turns local light coordinate into shadow region index. Matches eCubeFace order.
* \note lL does not need to be normalized. */
int shadow_punctual_face_index_get(vec3 lL)
{
vec3 aP = abs(lL);
if (all(greaterThan(aP.xx, aP.yz))) {
return (lL.x > 0.0) ? 1 : 2;
}
else if (all(greaterThan(aP.yy, aP.xz))) {
return (lL.y > 0.0) ? 3 : 4;
}
else {
return (lL.z > 0.0) ? 5 : 0;
}
}
mat4x4 shadow_load_normal_matrix(LightData light)
{
if (!is_sun_light(light.type)) {
/* FIXME: Why? */
float scale = 0.5;
return mat4x4(vec4(scale, 0.0, 0.0, 0.0),
vec4(0.0, scale, 0.0, 0.0),
vec4(0.0, 0.0, 0.0, -1.0),
vec4(0.0, 0.0, light.normal_mat_packed.x, light.normal_mat_packed.y));
}
else {
float near = shadow_orderedIntBitsToFloat(light.clip_near);
float far = shadow_orderedIntBitsToFloat(light.clip_far);
/* Could be store precomputed inside the light struct. Just have to find a how to update it. */
float z_scale = (far - near) * 0.5;
return mat4x4(vec4(light.normal_mat_packed.x, 0.0, 0.0, 0.0),
vec4(0.0, light.normal_mat_packed.x, 0.0, 0.0),
vec4(0.0, 0.0, z_scale, 0.0),
vec4(0.0, 0.0, 0.0, 1.0));
}
}
/* Returns minimum bias (in world space unit) needed for a given geometry normal and a shadowmap
* page to avoid self shadowing artifacts. Note that this can return a negative bias to better
* match the surface. */
float shadow_slope_bias_get(vec2 atlas_size, LightData light, vec3 lNg, vec3 lP, vec2 uv, uint lod)
{
/* Compute coordinate inside the pixel we are sampling. */
vec2 uv_subpixel_coord = fract(uv * atlas_size);
/* Bias uv sample for LODs since custom raster aligns LOD pixels instead of centering them. */
uv_subpixel_coord += (lod > 0) ? -exp2(-1.0 - float(lod)) : 0.0;
/* Compute delta to the texel center (where the sample is). */
vec2 ndc_texel_center_delta = uv_subpixel_coord * 2.0 - 1.0;
/* Create a normal plane equation and go through the normal projection matrix. */
vec4 lNg_plane = vec4(lNg, -dot(lNg, lP));
vec4 ndc_Ng = shadow_load_normal_matrix(light) * lNg_plane;
/* Get slope from normal vector. Note that this is signed. */
vec2 ndc_slope = ndc_Ng.xy / abs(ndc_Ng.z);
/* Clamp out to avoid the bias going to infinity. Remember this is in NDC space. */
ndc_slope = clamp(ndc_slope, -100.0, 100.0);
/* Compute slope to where the receiver should be by extending the plane to the texel center. */
float bias = dot(ndc_slope, ndc_texel_center_delta);
/* Bias for 1 pixel of the sampled LOD. */
bias /= ((SHADOW_TILEMAP_RES * SHADOW_PAGE_RES) >> lod);
return bias;
}
struct ShadowSample {
/* Signed delta in world units from the shading point to the occluder. Negative if occluded. */
float occluder_delta;
/* Tile coordinate inside the tilemap [0..SHADOW_TILEMAP_RES). */
ivec2 tile_coord;
/* UV coordinate inside the tilemap [0..SHADOW_TILEMAP_RES). */
vec2 uv;
/* Minimum slope bias to apply during comparison. */
float bias;
/* Distance from near clip plane in world space units. */
float occluder_dist;
/* Tile used loaded for page indirection. */
ShadowTileData tile;
};
float shadow_tile_depth_get(usampler2D atlas_tx, ShadowTileData tile, vec2 atlas_uv)
{
if (!tile.is_allocated) {
/* Far plane distance but with a bias to make sure there will be no shadowing.
* But also not FLT_MAX since it can cause issue with projection. */
return 1.1;
}
return uintBitsToFloat(texture(atlas_tx, atlas_uv).r);
}
vec2 shadow_punctual_linear_depth(vec2 z, float near, float far)
{
vec2 d = z * 2.0 - 1.0;
float z_delta = far - near;
/* Can we simplify? */
return ((-2.0 * near * far) / z_delta) / (d + (-(far + near) / z_delta));
}
float shadow_directional_linear_depth(float z, float near, float far)
{
return z * (near - far) - near;
}
ShadowSample shadow_punctual_sample_get(
usampler2D atlas_tx, usampler2D tilemaps_tx, LightData light, vec3 lP, vec3 lNg)
{
int face_id = shadow_punctual_face_index_get(lP);
lNg = shadow_punctual_local_position_to_face_local(face_id, lNg);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
ShadowCoordinates coord = shadow_punctual_coordinates(light, lP, face_id);
vec2 atlas_size = vec2(textureSize(atlas_tx, 0).xy);
ShadowSample samp;
samp.tile = shadow_tile_load(tilemaps_tx, coord.tile_coord, coord.tilemap_index);
samp.uv = shadow_page_uv_transform(
atlas_size, samp.tile.page, samp.tile.lod, coord.uv, coord.tile_coord);
samp.bias = shadow_slope_bias_get(atlas_size, light, lNg, lP, samp.uv, samp.tile.lod);
float occluder_ndc = shadow_tile_depth_get(atlas_tx, samp.tile, samp.uv);
/* Depth is cleared to FLT_MAX, clamp it to 1 to avoid issues when converting to linear. */
occluder_ndc = saturate(occluder_ndc);
/* NOTE: Given to be both positive, so can use intBitsToFloat instead of orderedInt version. */
float near = intBitsToFloat(light.clip_near);
float far = intBitsToFloat(light.clip_far);
/* Shadow is stored as gl_FragCoord.z. Convert to radial distance along with the bias. */
vec2 occluder = vec2(occluder_ndc, saturate(occluder_ndc + samp.bias));
vec2 occluder_z = shadow_punctual_linear_depth(occluder, near, far);
float receiver_dist = length(lP);
float radius_divisor = receiver_dist / abs(lP.z);
samp.occluder_dist = occluder_z.x * radius_divisor;
samp.bias = (occluder_z.y - occluder_z.x) * radius_divisor;
samp.occluder_delta = samp.occluder_dist - receiver_dist;
return samp;
}
ShadowSample shadow_directional_sample_get(
usampler2D atlas_tx, usampler2D tilemaps_tx, LightData light, vec3 P, vec3 lNg)
{
vec3 lP = shadow_world_to_local(light, P);
ShadowCoordinates coord = shadow_directional_coordinates(light, lP);
vec2 atlas_size = vec2(textureSize(atlas_tx, 0).xy);
ShadowSample samp;
samp.tile = shadow_tile_load(tilemaps_tx, coord.tile_coord, coord.tilemap_index);
samp.uv = shadow_page_uv_transform(
atlas_size, samp.tile.page, samp.tile.lod, coord.uv, coord.tile_coord);
samp.bias = shadow_slope_bias_get(atlas_size, light, lNg, lP, samp.uv, samp.tile.lod);
samp.bias *= exp2(float(coord.lod_relative));
float occluder_ndc = shadow_tile_depth_get(atlas_tx, samp.tile, samp.uv);
float near = shadow_orderedIntBitsToFloat(light.clip_near);
float far = shadow_orderedIntBitsToFloat(light.clip_far);
samp.occluder_dist = shadow_directional_linear_depth(occluder_ndc, near, far);
/* Receiver distance needs to also be increasing.
* Negate since Z distance follows blender camera convention of -Z as forward. */
float receiver_dist = -lP.z;
samp.bias *= near - far;
samp.occluder_delta = samp.occluder_dist - receiver_dist;
return samp;
}
ShadowSample shadow_sample(const bool is_directional,
usampler2D atlas_tx,
usampler2D tilemaps_tx,
LightData light,
vec3 lL,
vec3 lNg,
vec3 P)
{
if (is_directional) {
return shadow_directional_sample_get(atlas_tx, tilemaps_tx, light, P, lNg);
}
else {
return shadow_punctual_sample_get(atlas_tx, tilemaps_tx, light, lL, lNg);
}
}
/** \} */

View File

@ -0,0 +1,41 @@
/**
* Virtual shadowmapping: Allocation.
*
* Allocates pages to tiles needing them.
* Note that allocation can fail, in this case the tile is left with no page.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_page_ops_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
void main()
{
ShadowTileMapData tilemap_data = tilemaps_buf[gl_GlobalInvocationID.z];
int tile_start = tilemap_data.tiles_index;
for (int lod = 0; lod <= SHADOW_TILEMAP_LOD; lod++) {
int lod_len = SHADOW_TILEMAP_LOD0_LEN >> (lod * 2);
int local_tile = int(gl_LocalInvocationID.x);
if (local_tile < lod_len) {
int tile_index = tile_start + local_tile;
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
if (tile.is_used && !tile.is_allocated) {
shadow_page_alloc(tile);
tiles_buf[tile_index] = shadow_tile_pack(tile);
}
if (tile.is_used) {
atomicAdd(statistics_buf.page_used_count, 1);
}
if (tile.is_used && tile.do_update) {
atomicAdd(statistics_buf.page_update_count, 1);
}
if (tile.is_allocated) {
atomicAdd(statistics_buf.page_allocated_count, 1);
}
}
tile_start += lod_len;
}
}

View File

@ -0,0 +1,17 @@
/**
* Virtual shadowmapping: Page Clear.
*
* Equivalent to a framebuffer depth clear but only for pages pushed to the clear_page_buf.
*/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
void main()
{
uvec2 page_co = unpackUvec2x16(clear_page_buf[gl_GlobalInvocationID.z]);
uvec2 page_texel = page_co * pages_infos_buf.page_size + gl_GlobalInvocationID.xy;
/* Clear to FLT_MAX instead of 1 so the far plane doesn't cast shadows onto farther objects. */
imageStore(atlas_img, ivec2(page_texel), uvec4(floatBitsToUint(FLT_MAX)));
}

View File

@ -0,0 +1,129 @@
/**
* Virtual shadowmapping: Defrag.
*
* Defragment the cached page buffer making one continuous array.
*
* Also pop_front the cached pages if there is not enough free pages for the needed allocations.
* Here is an example of the behavior of this buffer during one update cycle:
*
* Initial state: 5 cached pages. Buffer starts at index 2 and ends at 6.
* [--xxxxx---------]
* After page free step: 2 cached pages were removed (r), 3 pages were inserted in the cache (i).
* [--xrxrxiii------]
* After page defrag step: The buffer is compressed into only 6 pages.
* [----xxxxxx------]
*/
#pragma BLENDER_REQUIRE(eevee_shadow_page_ops_lib.glsl)
const uint max_page = SHADOW_MAX_PAGE;
void find_first_valid(inout uint src, uint dst)
{
for (; src < dst; src++) {
if (pages_cached_buf[src % max_page].x != uint(-1)) {
return;
}
}
}
void page_cached_free(uint page_index)
{
uint tile_index = pages_cached_buf[page_index].y;
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
shadow_page_cache_remove(tile);
shadow_page_free(tile);
tiles_buf[tile_index] = shadow_tile_pack(tile);
}
#if 0 /* Can be used to debug heap and invalid pages inside the free buffer. */
# define check_heap_integrity(heap, start, size, invalid_val, result) \
result = true; \
for (int i = 0; i < max_page; i++) { \
if ((i >= start) && (i < (start + size))) { \
result = result && (heap[i].x != invalid_val); \
} \
else { \
result = result && (heap[i].x == invalid_val); \
} \
}
#else
# define check_heap_integrity(heap, start, size, invalid_val, result)
#endif
void main()
{
/* Pages we need to get off the cache for the allocation pass. */
int additional_pages = pages_infos_buf.page_alloc_count - pages_infos_buf.page_free_count;
uint src = pages_infos_buf.page_cached_start;
uint end = pages_infos_buf.page_cached_end;
find_first_valid(src, end);
bool valid_pre;
check_heap_integrity(pages_free_buf, 0, pages_infos_buf.page_free_count, uint(-1), valid_pre);
/* First free as much pages as needed from the end of the cached range to fulfill the allocation.
* Avoid defragmenting to then free them. */
for (; additional_pages > 0 && src < end; additional_pages--) {
page_cached_free(src % max_page);
find_first_valid(src, end);
}
/* Defrag page in "old" range. */
bool is_empty = (src == end);
if (!is_empty) {
/* `page_cached_end` refers to the next empty slot.
* Decrement by one to refer to the first slot we can defrag. */
for (uint dst = end - 1; dst > src; dst--) {
/* Find hole. */
if (pages_cached_buf[dst % max_page].x != uint(-1)) {
continue;
}
/* Update corresponding reference in tile. */
shadow_page_cache_update_page_ref(src % max_page, dst % max_page);
/* Move page. */
pages_cached_buf[dst % max_page] = pages_cached_buf[src % max_page];
pages_cached_buf[src % max_page] = uvec2(-1);
find_first_valid(src, dst);
}
}
end = pages_infos_buf.page_cached_next;
/* Free pages in the "new" range (these are compact). */
for (; additional_pages > 0 && src < end; additional_pages--, src++) {
page_cached_free(src % max_page);
}
bool valid_post;
check_heap_integrity(pages_free_buf, 0, pages_infos_buf.page_free_count, uint(-1), valid_post);
pages_infos_buf.page_cached_start = src;
pages_infos_buf.page_cached_end = end;
pages_infos_buf.page_alloc_count = 0;
pages_infos_buf.view_count = 0;
/* Stats. */
statistics_buf.page_used_count = 0;
statistics_buf.page_update_count = 0;
statistics_buf.page_allocated_count = 0;
statistics_buf.page_rendered_count = 0;
/* Wrap the cursor to avoid unsigned overflow. We do not do modulo arithmetic because it would
* produce a 0 length buffer if the buffer is full. */
if (pages_infos_buf.page_cached_start > max_page) {
pages_infos_buf.page_cached_next -= max_page;
pages_infos_buf.page_cached_start -= max_page;
pages_infos_buf.page_cached_end -= max_page;
}
/* Reset clear command indirect buffer. */
clear_dispatch_buf.num_groups_x = pages_infos_buf.page_size / SHADOW_PAGE_CLEAR_GROUP_SIZE;
clear_dispatch_buf.num_groups_y = pages_infos_buf.page_size / SHADOW_PAGE_CLEAR_GROUP_SIZE;
clear_dispatch_buf.num_groups_z = 0;
}

View File

@ -0,0 +1,54 @@
/**
* Virtual shadowmapping: Tile page freeing.
*
* Releases the allocated pages held by tilemaps that have been become unused.
* Also reclaim cached pages if the tiles needs them.
* Note that we also count the number of new page allocations needed.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_page_ops_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
void main()
{
ShadowTileMapData tilemap_data = tilemaps_buf[gl_GlobalInvocationID.z];
int tile_start = tilemap_data.tiles_index;
for (int lod = 0; lod <= SHADOW_TILEMAP_LOD; lod++) {
int lod_len = SHADOW_TILEMAP_LOD0_LEN >> (lod * 2);
int local_tile = int(gl_LocalInvocationID.x);
if (local_tile < lod_len) {
int tile_index = tile_start + local_tile;
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
bool is_orphaned = !tile.is_used && tile.do_update;
if (is_orphaned) {
if (tile.is_cached) {
shadow_page_cache_remove(tile);
}
if (tile.is_allocated) {
shadow_page_free(tile);
}
}
if (tile.is_used) {
if (tile.is_cached) {
shadow_page_cache_remove(tile);
}
if (!tile.is_allocated) {
atomicAdd(pages_infos_buf.page_alloc_count, 1);
}
}
else {
if (tile.is_allocated) {
shadow_page_cache_append(tile, tile_index);
}
}
tiles_buf[tile_index] = shadow_tile_pack(tile);
}
tile_start += lod_len;
}
}

View File

@ -0,0 +1,55 @@
/**
* Virtual shadowmapping: Usage un-tagging
*
* Remove used tag from masked tiles (LOD overlap).
*/
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
shared uint usage_grid[SHADOW_TILEMAP_RES / 2][SHADOW_TILEMAP_RES / 2];
void main()
{
uint tilemap_index = gl_GlobalInvocationID.z;
ShadowTileMapData tilemap = tilemaps_buf[tilemap_index];
if (tilemap.projection_type == SHADOW_PROJECTION_CUBEFACE) {
/* For each level collect the number of used (or masked) tile that are covering the tile from
* the level underneath. If this adds up to 4 the underneath tile is flag unused as its data
* is not needed for rendering.
*
* This is because 2 receivers can tag used the same area of the shadowmap but with different
* LODs. */
bool is_used = false;
ivec2 tile_co = ivec2(gl_GlobalInvocationID.xy);
uint lod_size = uint(SHADOW_TILEMAP_RES);
for (int lod = 0; lod <= SHADOW_TILEMAP_LOD; lod++, lod_size >>= 1u) {
bool thread_active = all(lessThan(tile_co, ivec2(lod_size)));
barrier();
ShadowTileData tile;
if (thread_active) {
int tile_offset = shadow_tile_offset(tile_co, tilemap.tiles_index, lod);
tile = shadow_tile_unpack(tiles_buf[tile_offset]);
if (lod > 0 && usage_grid[tile_co.y][tile_co.x] == 4u) {
/* Remove the usage flag as this tile is completely covered by higher LOD tiles. */
tiles_buf[tile_offset] &= ~SHADOW_IS_USED;
/* Consider this tile occluding lower levels. */
tile.is_used = true;
}
/* Reset count for next level. */
usage_grid[tile_co.y][tile_co.x] = 0u;
}
barrier();
if (thread_active) {
if (tile.is_used) {
atomicAdd(usage_grid[tile_co.y / 2][tile_co.x / 2], 1u);
}
}
}
}
}

View File

@ -0,0 +1,108 @@
/**
* Operations to move virtual shadow map pages between heaps and tiles.
* We reuse the blender::vector class denomination.
*
* The needed resources for this lib are:
* - tiles_buf
* - pages_free_buf
* - pages_cached_buf
* - pages_infos_buf
*
* A page is can be in 3 state (free, cached, acquired). Each one correspond to a different owner.
*
* - The pages_free_buf works in a regular stack containing only the page coordinates.
*
* - The pages_cached_buf is a ring buffer where newly cached pages gets added at the end and the
* old cached pages gets defragmented at the start of the used portion.
*
* - The tiles_buf only owns a page if it is used. If the page is cached, the tile contains a
* reference index inside the pages_cached_buf.
*
* IMPORTANT: Do not forget to manually store the tile data after doing operations on them.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
/* TODO(@fclem): Implement. */
#define assert(check)
/* Remove page ownership from the tile and append it to the cache. */
void shadow_page_free(inout ShadowTileData tile)
{
assert(tile.is_allocated);
int index = atomicAdd(pages_infos_buf.page_free_count, 1);
assert(index < SHADOW_MAX_PAGE);
/* Insert in heap. */
pages_free_buf[index] = packUvec2x16(tile.page);
/* Remove from tile. */
tile.page = uvec2(-1);
tile.is_cached = false;
tile.is_allocated = false;
}
/* Remove last page from the free heap and give ownership to the tile. */
void shadow_page_alloc(inout ShadowTileData tile)
{
assert(!tile.is_allocated);
int index = atomicAdd(pages_infos_buf.page_free_count, -1) - 1;
/* This can easily happen in really big scene. */
if (index < 0) {
return;
}
/* Insert in tile. */
tile.page = unpackUvec2x16(pages_free_buf[index]);
tile.is_allocated = true;
tile.do_update = true;
/* Remove from heap. */
pages_free_buf[index] = uint(-1);
}
/* Remove page ownership from the tile cache and append it to the cache. */
void shadow_page_cache_append(inout ShadowTileData tile, uint tile_index)
{
assert(tile.is_allocated);
/* The page_cached_next is also wrapped in the defrag phase to avoid unsigned overflow. */
uint index = atomicAdd(pages_infos_buf.page_cached_next, 1u) % uint(SHADOW_MAX_PAGE);
/* Insert in heap. */
pages_cached_buf[index] = uvec2(packUvec2x16(tile.page), tile_index);
/* Remove from tile. */
tile.page = uvec2(-1);
tile.cache_index = index;
tile.is_cached = true;
tile.is_allocated = false;
}
/* Remove page from cache and give ownership to the tile. */
void shadow_page_cache_remove(inout ShadowTileData tile)
{
assert(!tile.is_allocated);
assert(tile.is_cached);
uint index = tile.cache_index;
/* Insert in tile. */
tile.page = unpackUvec2x16(pages_cached_buf[index].x);
tile.cache_index = uint(-1);
tile.is_cached = false;
tile.is_allocated = true;
/* Remove from heap. Leaves hole in the buffer. This is handled by the defrag phase. */
pages_cached_buf[index] = uvec2(-1);
}
/* Update cached page reference when a cached page moves inside the cached page buffer. */
void shadow_page_cache_update_page_ref(uint page_index, uint new_page_index)
{
uint tile_index = pages_cached_buf[page_index].y;
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
tile.cache_index = new_page_index;
tiles_buf[tile_index] = shadow_tile_pack(tile);
}
/* Update cached page reference when a tile referencing a cached page moves inside the tilemap. */
void shadow_page_cache_update_tile_ref(uint page_index, uint new_tile_index)
{
pages_cached_buf[page_index].y = new_tile_index;
}

View File

@ -0,0 +1,94 @@
/**
* Virtual shadowmapping: Update tagging
*
* Any updated shadow caster needs to tag the shadow map tiles it was in and is now into.
* This is done in 2 pass of this same shader. One for past object bounds and one for new object
* bounds. The bounding boxes are roughly software rasterized (just a plain rect) in order to tag
* the appropriate tiles.
*/
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_aabb_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
vec3 safe_project(mat4 winmat, mat4 viewmat, inout int clipped, vec3 v)
{
vec4 tmp = winmat * (viewmat * vec4(v, 1.0));
/* Detect case when point is behind the camera. */
clipped += int(tmp.w < 0.0);
return tmp.xyz / tmp.w;
}
void main()
{
ShadowTileMapData tilemap = tilemaps_buf[gl_GlobalInvocationID.z];
IsectPyramid frustum;
if (tilemap.projection_type == SHADOW_PROJECTION_CUBEFACE) {
Pyramid pyramid = shadow_tilemap_cubeface_bounds(tilemap, ivec2(0), ivec2(SHADOW_TILEMAP_RES));
frustum = isect_data_setup(pyramid);
}
uint resource_id = resource_ids_buf[gl_GlobalInvocationID.x];
IsectBox box = isect_data_setup(bounds_buf[resource_id].bounding_corners[0].xyz,
bounds_buf[resource_id].bounding_corners[1].xyz,
bounds_buf[resource_id].bounding_corners[2].xyz,
bounds_buf[resource_id].bounding_corners[3].xyz);
int clipped = 0;
/* NDC space post projection [-1..1] (unclamped). */
AABB aabb_ndc = aabb_init_min_max();
for (int v = 0; v < 8; v++) {
aabb_merge(aabb_ndc, safe_project(tilemap.winmat, tilemap.viewmat, clipped, box.corners[v]));
}
if (tilemap.projection_type == SHADOW_PROJECTION_CUBEFACE) {
if (clipped == 8) {
/* All verts are behind the camera. */
return;
}
else if (clipped > 0) {
/* Not all verts are behind the near clip plane. */
if (intersect(frustum, box)) {
/* We cannot correctly handle this case so we fallback by covering the whole view. */
aabb_ndc.max = vec3(1.0);
aabb_ndc.min = vec3(-1.0);
}
else {
/* Still out of the frustum. Ignore. */
return;
}
}
}
AABB aabb_tag;
AABB aabb_map = AABB(vec3(-0.99999), vec3(0.99999));
/* Directionnal winmat have no correct near/far in the Z dimension at this point.
* Do not clip in this dimension. */
if (tilemap.projection_type != SHADOW_PROJECTION_CUBEFACE) {
aabb_map.min.z = -FLT_MAX;
aabb_map.max.z = FLT_MAX;
}
if (!aabb_clip(aabb_map, aabb_ndc, aabb_tag)) {
return;
}
/* Raster the bounding rectangle of the Box projection. */
const float tilemap_half_res = float(SHADOW_TILEMAP_RES / 2);
ivec2 box_min = ivec2(aabb_tag.min.xy * tilemap_half_res + tilemap_half_res);
ivec2 box_max = ivec2(aabb_tag.max.xy * tilemap_half_res + tilemap_half_res);
for (int lod = 0; lod <= SHADOW_TILEMAP_LOD; lod++, box_min >>= 1, box_max >>= 1) {
for (int y = box_min.y; y <= box_max.y; y++) {
for (int x = box_min.x; x <= box_max.x; x++) {
int tile_index = shadow_tile_offset(ivec2(x, y), tilemap.tiles_index, lod);
atomicOr(tiles_buf[tile_index], SHADOW_DO_UPDATE);
}
}
}
}

View File

@ -0,0 +1,32 @@
/**
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This pass scan the depth buffer and tag all tiles that are needed for light shadowing as
* needed.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_tag_usage_lib.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 tex_size = textureSize(depth_tx, 0).xy;
if (!in_range_inclusive(texel, ivec2(0), ivec2(tex_size - 1))) {
return;
}
float depth = texelFetch(depth_tx, texel, 0).r;
if (depth == 1.0) {
return;
}
vec2 uv = vec2(texel) / vec2(tex_size);
vec3 vP = get_view_space_from_depth(uv, depth);
vec3 P = transform_point(ViewMatrixInverse, vP);
vec2 pixel = vec2(gl_GlobalInvocationID.xy);
shadow_tag_usage(vP, P, pixel);
}

View File

@ -0,0 +1,15 @@
/**
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This pass scan the depth buffer and tag all tiles that are needed for light shadowing as
* needed.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_tag_usage_lib.glsl)
void main()
{
shadow_tag_usage(interp.vP, interp.P, gl_FragCoord.xy);
}

View File

@ -0,0 +1,106 @@
/**
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This pass scan the depth buffer and tag all tiles that are needed for light shadowing as
* needed.
*/
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
void shadow_tag_usage_tilemap(uint l_idx, vec3 P, float dist_to_cam, const bool is_directional)
{
LightData light = light_buf[l_idx];
if (light.tilemap_index == LIGHT_NO_SHADOW) {
return;
}
int lod = 0;
ivec2 tile_co;
int tilemap_index = light.tilemap_index;
if (is_directional) {
vec3 lP = shadow_world_to_local(light, P);
ShadowCoordinates coord = shadow_directional_coordinates(light, lP);
tile_co = coord.tile_coord;
tilemap_index = coord.tilemap_index;
}
else {
vec3 lP = light_world_to_local(light, P - light._position);
float dist_to_light = length(lP);
if (dist_to_light > light.influence_radius_max) {
return;
}
if (light.type == LIGHT_SPOT) {
/* Early out if out of cone. */
float angle_tan = length(lP.xy / dist_to_light);
if (angle_tan > light.spot_tan) {
return;
}
}
else if (is_area_light(light.type)) {
/* Early out if on the wrong side. */
if (lP.z > 0.0) {
return;
}
}
/* How much a shadow map pixel covers a final image pixel.
* 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 footprint_ratio = dist_to_light;
/* Project the radius to the screen. 1 unit away from the camera the same way
* pixel_world_radius_inv was computed. Not needed in orthographic mode. */
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
if (is_persp) {
footprint_ratio /= dist_to_cam;
}
/* Apply resolution ratio. */
footprint_ratio *= tilemap_projection_ratio;
int face_id = shadow_punctual_face_index_get(lP);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
ShadowCoordinates coord = shadow_punctual_coordinates(light, lP, face_id);
tile_co = coord.tile_coord;
tilemap_index = coord.tilemap_index;
lod = int(ceil(-log2(footprint_ratio) + tilemaps_buf[tilemap_index].lod_bias));
lod = clamp(lod, 0, SHADOW_TILEMAP_LOD);
}
tile_co >>= lod;
if (tilemap_index > light_tilemap_max_get(light)) {
return;
}
int tile_index = shadow_tile_offset(tile_co, tilemaps_buf[tilemap_index].tiles_index, lod);
atomicOr(tiles_buf[tile_index], SHADOW_IS_USED);
}
void shadow_tag_usage(vec3 vP, vec3 P, vec2 pixel)
{
float dist_to_cam = length(vP);
LIGHT_FOREACH_BEGIN_DIRECTIONAL(light_cull_buf, l_idx)
{
shadow_tag_usage_tilemap(l_idx, P, dist_to_cam, true);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL(light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx)
{
shadow_tag_usage_tilemap(l_idx, P, dist_to_cam, false);
}
LIGHT_FOREACH_END
}

View File

@ -0,0 +1,22 @@
/**
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This renders bounding boxes for transparent objects in order to tag the correct shadows.
*/
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
void main()
{
ObjectBounds bounds = bounds_buf[drw_ResourceID];
interp.P = bounds.bounding_corners[0].xyz;
interp.P += bounds.bounding_corners[1].xyz * pos.x;
interp.P += bounds.bounding_corners[2].xyz * pos.y;
interp.P += bounds.bounding_corners[3].xyz * pos.z;
interp.vP = point_world_to_view(interp.P);
gl_Position = point_world_to_ndc(interp.P);
}

View File

@ -0,0 +1,398 @@
/* Directive for resetting the line numbering so the failing tests lines can be printed.
* This conflict with the shader compiler error logging scheme.
* Comment out for correct compilation error line. */
#line 5
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_test_lib.glsl)
#define TEST(a, b) if (true)
void main()
{
TEST(eevee_shadow, DirectionalClipmapLevel)
{
LightData light;
light.type = LIGHT_SUN;
light.clipmap_lod_min = -5;
light.clipmap_lod_max = 8;
light._clipmap_lod_bias = 0.0;
float fac = float(SHADOW_TILEMAP_RES - 1) / float(SHADOW_TILEMAP_RES);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.0)), light.clipmap_lod_min);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.49)), 1);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.5)), 1);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.51)), 1);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.99)), 2);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 1.0)), 2);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 1.01)), 2);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 12.5)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 12.51)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 15.9999)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 16.0)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 16.00001)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 5000.0)), light.clipmap_lod_max);
/* Produces NaN / Inf, Undefined behavior. */
// EXPECT_EQ(shadow_directional_level(light, vec3(FLT_MAX)), light.clipmap_lod_max);
}
TEST(eevee_shadow, DirectionalCascadeLevel)
{
LightData light;
light.type = LIGHT_SUN_ORTHO;
light.clipmap_lod_min = 2;
light.clipmap_lod_max = 8;
float half_size = exp2(float(light.clipmap_lod_min - 1));
light._clipmap_lod_bias = light.clipmap_lod_min - 1;
float fac = float(SHADOW_TILEMAP_RES - 1) / float(SHADOW_TILEMAP_RES);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 0.0, 0.0, 0.0)), 2);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 0.5, 0.0, 0.0)), 2);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 1.0, 0.0, 0.0)), 3);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 1.5, 0.0, 0.0)), 3);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 2.0, 0.0, 0.0)), 4);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 5000.0)), light.clipmap_lod_max);
/* Produces NaN / Inf, Undefined behavior. */
// EXPECT_EQ(shadow_directional_level(light, vec3(FLT_MAX)), light.clipmap_lod_max);
}
TEST(eevee_shadow, DirectionalClipmapCoordinates)
{
ShadowCoordinates coords;
vec3 lP, camera_lP;
LightData light;
light.type = LIGHT_SUN;
light.clipmap_lod_min = 0; /* Range [-0.5..0.5]. */
light.clipmap_lod_max = 2; /* Range [-2..2]. */
light.tilemap_index = light.clipmap_lod_min;
light._position = vec3(0.0);
float lod_min_tile_size = exp2(float(light.clipmap_lod_min)) / float(SHADOW_TILEMAP_RES);
float lod_max_half_size = exp2(float(light.clipmap_lod_max)) / 2.0;
camera_lP = vec3(0.0, 0.0, 0.0);
/* Follows ShadowDirectional::end_sync(). */
light.clipmap_base_offset = ivec2(round(camera_lP.xy / lod_min_tile_size));
EXPECT_EQ(light.clipmap_base_offset, ivec2(0));
/* Test UVs and tile mapping. */
lP = vec3(1e-5, 1e-5, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(-1e-5, -1e-5, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2((SHADOW_TILEMAP_RES / 2) - 1));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(-0.5, -0.5, 0.0); /* Min of first LOD. */
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(0));
EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
lP = vec3(0.5, 0.5, 0.0); /* Max of first LOD. */
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES), 1e-3);
/* Test clipmap level selection. */
camera_lP = vec3(2.0, 2.0, 0.0);
/* Follows ShadowDirectional::end_sync(). */
light.clipmap_base_offset = ivec2(round(camera_lP.xy / lod_min_tile_size));
EXPECT_EQ(light.clipmap_base_offset, ivec2(32));
lP = vec3(2.00001, 2.00001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(1.50001, 1.50001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 1);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
lP = vec3(1.00001, 1.00001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 2);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
lP = vec3(-0.0001, -0.0001, 0.0); /* Out of bounds. */
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 2);
EXPECT_EQ(coords.tile_coord, ivec2(0));
EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
/* Test clipmap offset. */
light.clipmap_base_offset = ivec2(31, 1);
lP = vec3(2.0001, 0.0001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, -1));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
/* Test clipmap negative offsets. */
light.clipmap_base_offset = ivec2(-31, -1);
lP = vec3(-2.0001, -0.0001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 1));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
}
TEST(eevee_shadow, DirectionalCascadeCoordinates)
{
ShadowCoordinates coords;
vec3 lP, camera_lP;
LightData light;
light.type = LIGHT_SUN_ORTHO;
light.clipmap_lod_min = 0; /* Range [-0.5..0.5]. */
light.clipmap_lod_max = 2; /* 3 tilemaps. */
light.tilemap_index = 1;
light._position = vec3(0.0);
light._clipmap_lod_bias = light.clipmap_lod_min - 1;
light._clipmap_origin_x = 0.0;
light._clipmap_origin_y = 0.0;
float lod_tile_size = exp2(float(light.clipmap_lod_min)) / float(SHADOW_TILEMAP_RES);
float lod_half_size = exp2(float(light.clipmap_lod_min)) / 2.0;
float narrowing = float(SHADOW_TILEMAP_RES - 1) / float(SHADOW_TILEMAP_RES);
camera_lP = vec3(0.0, 0.0, 0.0);
int level_range_size = light.clipmap_lod_max - light.clipmap_lod_min + 1;
vec2 farthest_tilemap_center = vec2(lod_half_size * float(level_range_size - 1), 0.0);
light.clipmap_base_offset = floatBitsToInt(
vec2(lod_half_size / float(level_range_size - 1), 0.0));
/* Test UVs and tile mapping. */
lP = vec3(1e-8, 1e-8, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 1);
EXPECT_EQ(coords.lod_relative, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(lod_half_size * narrowing - 1e-5, 1e-8, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 1);
EXPECT_EQ(coords.lod_relative, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1, SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(float(SHADOW_TILEMAP_RES) - 0.5, SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(lod_half_size + 1e-5, 1e-5, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 2);
EXPECT_EQ(coords.lod_relative, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1, SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES, SHADOW_TILEMAP_RES / 2), 1e-3);
// lP = vec3(-0.5, -0.5, 0.0); /* Min of first LOD. */
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 0);
// EXPECT_EQ(coords.tile_coord, ivec2(0));
// EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
// lP = vec3(0.5, 0.5, 0.0); /* Max of first LOD. */
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 0);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES), 1e-3);
/* Test clipmap level selection. */
// camera_lP = vec3(2.0, 2.0, 0.0);
/* Follows ShadowDirectional::end_sync(). */
// light.clipmap_base_offset = ivec2(round(camera_lP.xy / lod_min_tile_size));
// EXPECT_EQ(light.clipmap_base_offset, ivec2(32));
// lP = vec3(2.00001, 2.00001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 0);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
// lP = vec3(1.50001, 1.50001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 1);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
// lP = vec3(1.00001, 1.00001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 2);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
// lP = vec3(-0.0001, -0.0001, 0.0); /* Out of bounds. */
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 2);
// EXPECT_EQ(coords.tile_coord, ivec2(0));
// EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
/* Test clipmap offset. */
// light.clipmap_base_offset = ivec2(31, 1);
// lP = vec3(2.0001, 0.0001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, -1));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
/* Test clipmap negative offsets. */
// light.clipmap_base_offset = ivec2(-31, -1);
// lP = vec3(-2.0001, -0.0001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 1));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
}
TEST(eevee_shadow, DirectionalSlopeBias)
{
float near = 0.0, far = 1.0;
LightData light;
light.type = LIGHT_SUN;
light.clip_near = floatBitsToInt(near);
light.clip_far = floatBitsToInt(far);
light.clipmap_lod_min = 0;
/* Position has no effect for directionnal. */
vec3 lP = vec3(0.0);
vec2 atlas_size = vec2(SHADOW_TILEMAP_RES);
{
vec3 lNg = vec3(0.0, 0.0, 1.0);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 0), 0.0, 3e-7);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 1), 0.0, 3e-7);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 2), 0.0, 3e-7);
}
{
vec3 lNg = normalize(vec3(0.0, 1.0, 1.0));
float expect = 1.0 / (SHADOW_TILEMAP_RES * SHADOW_PAGE_RES);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 0), expect, 3e-7);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 1), expect * 2.0, 3e-7);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 2), expect * 4.0, 3e-7);
}
{
vec3 lNg = normalize(vec3(1.0, 1.0, 1.0));
float expect = 2.0 / (SHADOW_TILEMAP_RES * SHADOW_PAGE_RES);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 0), expect, 3e-7);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 1), expect * 2.0, 3e-7);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 2), expect * 4.0, 3e-7);
}
light.clipmap_lod_min = -1;
{
vec3 lNg = normalize(vec3(1.0, 1.0, 1.0));
float expect = 0.5 * (2.0 / (SHADOW_TILEMAP_RES * SHADOW_PAGE_RES));
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 0), expect, 3e-7);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 1), expect * 2.0, 3e-7);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP, vec2(0.0), 2), expect * 4.0, 3e-7);
}
}
TEST(eevee_shadow, PunctualSlopeBias)
{
float near = 0.5, far = 1.0;
mat4 pers_mat = projection_perspective(-near, near, -near, near, near, far);
mat4 normal_mat = invert(transpose(pers_mat));
LightData light;
light.clip_near = floatBitsToInt(near);
light.clip_far = floatBitsToInt(far);
light.influence_radius_max = far;
light.type = LIGHT_SPOT;
light.normal_mat_packed.x = normal_mat[3][2];
light.normal_mat_packed.y = normal_mat[3][3];
vec2 atlas_size = vec2(SHADOW_TILEMAP_RES);
{
/* Simulate a "2D" plane crossing the frustum diagonaly. */
vec3 lP0 = vec3(-1.0, 0.0, -1.0);
vec3 lP1 = vec3(0.5, 0.0, -0.5);
vec3 lTg = normalize(lP1 - lP0);
vec3 lNg = vec3(-lTg.z, 0.0, lTg.x);
float expect = 1.0 / (SHADOW_TILEMAP_RES * SHADOW_PAGE_RES);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 0), expect, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 1), expect * 2.0, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 2), expect * 4.0, 1e-4);
}
{
/* Simulate a "2D" plane crossing the near plane at the center diagonaly. */
vec3 lP0 = vec3(-1.0, 0.0, -1.0);
vec3 lP1 = vec3(0.0, 0.0, -0.5);
vec3 lTg = normalize(lP1 - lP0);
vec3 lNg = vec3(-lTg.z, 0.0, lTg.x);
float expect = 2.0 / (SHADOW_TILEMAP_RES * SHADOW_PAGE_RES);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 0), expect, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 1), expect * 2.0, 1e-4);
EXPECT_NEAR(
shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 2), expect * 4.0, 1e-4);
}
{
/* Simulate a "2D" plane parallel to near clip plane. */
vec3 lP0 = vec3(-1.0, 0.0, -0.75);
vec3 lP1 = vec3(0.0, 0.0, -0.75);
vec3 lTg = normalize(lP1 - lP0);
vec3 lNg = vec3(-lTg.z, 0.0, lTg.x);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 0), 0.0, 1e-4);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 1), 0.0, 1e-4);
EXPECT_NEAR(shadow_slope_bias_get(atlas_size, light, lNg, lP0, vec2(0.0), 2), 0.0, 1e-4);
}
}
}

View File

@ -0,0 +1,78 @@
/**
* Virtual shadowmapping: Bounds computation for directional shadows.
*
* Iterate through all shadow casters and extract min/max per directional shadow.
* This needs to happen first in the pipeline to allow tagging all relevant tilemap as dirty if
* their range changes.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
shared int global_min;
shared int global_max;
void main()
{
uint index = gl_GlobalInvocationID.x;
/* Keep uniform control flow. Do not return. */
index = min(index, uint(resource_len) - 1);
uint resource_id = casters_id_buf[index];
ObjectBounds bounds = bounds_buf[resource_id];
IsectBox box = isect_data_setup(bounds.bounding_corners[0].xyz,
bounds.bounding_corners[1].xyz,
bounds.bounding_corners[2].xyz,
bounds.bounding_corners[3].xyz);
LIGHT_FOREACH_BEGIN_DIRECTIONAL(light_cull_buf, l_idx)
{
LightData light = light_buf[l_idx];
float local_min = FLT_MAX;
float local_max = -FLT_MAX;
for (int i = 0; i < 8; i++) {
float z = dot(box.corners[i].xyz, light._back);
local_min = min(local_min, z);
local_max = max(local_max, z);
}
if (gl_LocalInvocationID.x == 0) {
global_min = floatBitsToOrderedInt(FLT_MAX);
global_max = floatBitsToOrderedInt(-FLT_MAX);
}
barrier();
/* Quantization bias. */
local_min -= abs(local_min) * 0.01;
local_max += abs(local_max) * 0.01;
/* Intermediate result. Min/Max of a compute group. */
atomicMin(global_min, floatBitsToOrderedInt(local_min));
atomicMax(global_max, floatBitsToOrderedInt(local_max));
barrier();
if (gl_LocalInvocationID.x == 0) {
/* Final result. Min/Max of the whole dispatch. */
atomicMin(light_buf[l_idx].clip_far, global_min);
atomicMax(light_buf[l_idx].clip_near, global_max);
/* TODO(fclem): This feel unecessary but we currently have no indexing from
* tilemap to lights. This is because the lights are selected by culling phase. */
for (int i = light.tilemap_index; i <= light_tilemap_max_get(light); i++) {
int index = tilemaps_buf[i].clip_data_index;
atomicMin(tilemaps_clip_buf[index].clip_far, global_min);
atomicMax(tilemaps_clip_buf[index].clip_near, global_max);
}
}
/* No need for barrier here since global_min/max is only read by thread 0 before being reset by
* thread 0. */
}
LIGHT_FOREACH_END
}

View File

@ -0,0 +1,181 @@
/**
* Virtual shadowmapping: Tilemap to texture conversion.
*
* For all visible light tilemaps, copy page coordinate to a texture.
* This avoids one level of indirection when evaluating shadows and allows
* to use a sampler instead of a SSBO bind.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
shared uint tile_updates_count;
shared int view_index;
void page_clear_buf_append(uint page_packed)
{
uint clear_page_index = atomicAdd(clear_dispatch_buf.num_groups_z, 1u);
clear_page_buf[clear_page_index] = page_packed;
}
void page_tag_as_rendered(ivec2 tile_co, int tiles_index, int lod)
{
int tile_index = shadow_tile_offset(tile_co, tiles_index, lod);
tiles_buf[tile_index] |= SHADOW_IS_RENDERED;
atomicAdd(statistics_buf.page_rendered_count, 1);
}
void main()
{
if (all(equal(gl_LocalInvocationID, uvec3(0)))) {
tile_updates_count = uint(0);
}
barrier();
int tilemap_index = int(gl_GlobalInvocationID.z);
ivec2 tile_co = ivec2(gl_GlobalInvocationID.xy);
ivec2 atlas_texel = shadow_tile_coord_in_atlas(tile_co, tilemap_index);
ShadowTileMapData tilemap_data = tilemaps_buf[tilemap_index];
int lod_max = (tilemap_data.projection_type == SHADOW_PROJECTION_CUBEFACE) ? SHADOW_TILEMAP_LOD :
0;
int lod_valid = 0;
/* One bit per lod. */
int do_lod_update = 0;
/* Packed page (packUvec2x16) to render per LOD. */
uint updated_lod_page[SHADOW_TILEMAP_LOD + 1];
uvec2 page_valid;
/* With all threads (LOD0 size dispatch) load each lod tile from the highest lod
* to the lowest, keeping track of the lowest one allocated which will be use for shadowing.
* Also save which page are to be updated. */
for (int lod = SHADOW_TILEMAP_LOD; lod >= 0; lod--) {
if (lod > lod_max) {
updated_lod_page[lod] = 0xFFFFFFFFu;
continue;
}
int tile_index = shadow_tile_offset(tile_co >> lod, tilemap_data.tiles_index, lod);
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
if (tile.is_used && tile.do_update) {
do_lod_update = 1 << lod;
updated_lod_page[lod] = packUvec2x16(tile.page);
}
else {
updated_lod_page[lod] = 0xFFFFFFFFu;
}
/* Save highest lod for this thread. */
if (tile.is_used && lod > 0) {
/* Reload the page in case there was an allocation in the valid thread. */
page_valid = tile.page;
lod_valid = lod;
}
else if (lod == 0 && lod_valid != 0 && !tile.is_allocated) {
/* If the tile is not used, store the valid LOD level in LOD0. */
tile.page = page_valid;
tile.lod = lod_valid;
/* This is not a real ownership. It is just a tag so that the shadowing is deemed correct. */
tile.is_allocated = true;
}
if (lod == 0) {
imageStore(tilemaps_img, atlas_texel, uvec4(shadow_tile_pack(tile)));
}
}
if (do_lod_update > 0) {
atomicAdd(tile_updates_count, 1u);
}
barrier();
if (all(equal(gl_LocalInvocationID, uvec3(0)))) {
/* No update by default. */
view_index = 64;
if (tile_updates_count > 0) {
view_index = atomicAdd(pages_infos_buf.view_count, 1);
if (view_index < 64) {
view_infos_buf[view_index].viewmat = tilemap_data.viewmat;
view_infos_buf[view_index].viewinv = inverse(tilemap_data.viewmat);
if (tilemap_data.projection_type != SHADOW_PROJECTION_CUBEFACE) {
int clip_index = tilemap_data.clip_data_index;
/* For directionnal, we need to modify winmat to encompass all casters. */
float clip_far = -tilemaps_clip_buf[clip_index].clip_far_stored;
float clip_near = -tilemaps_clip_buf[clip_index].clip_near_stored;
tilemap_data.winmat[2][2] = -2.0 / (clip_far - clip_near);
tilemap_data.winmat[3][2] = -(clip_far + clip_near) / (clip_far - clip_near);
}
view_infos_buf[view_index].winmat = tilemap_data.winmat;
view_infos_buf[view_index].wininv = inverse(tilemap_data.winmat);
}
}
}
barrier();
if (view_index < 64) {
ivec3 render_map_texel = ivec3(tile_co, view_index);
/* Store page indirection for rendering. Update every texel in the view array level. */
if (true) {
imageStore(render_map_lod0_img, render_map_texel, uvec4(updated_lod_page[0]));
if (updated_lod_page[0] != 0xFFFFFFFFu) {
page_clear_buf_append(updated_lod_page[0]);
page_tag_as_rendered(render_map_texel.xy, tilemap_data.tiles_index, 0);
}
}
render_map_texel.xy >>= 1;
if (all(equal(tile_co, render_map_texel.xy << 1u))) {
imageStore(render_map_lod1_img, render_map_texel, uvec4(updated_lod_page[1]));
if (updated_lod_page[1] != 0xFFFFFFFFu) {
page_clear_buf_append(updated_lod_page[1]);
page_tag_as_rendered(render_map_texel.xy, tilemap_data.tiles_index, 1);
}
}
render_map_texel.xy >>= 1;
if (all(equal(tile_co, render_map_texel.xy << 2u))) {
imageStore(render_map_lod2_img, render_map_texel, uvec4(updated_lod_page[2]));
if (updated_lod_page[2] != 0xFFFFFFFFu) {
page_clear_buf_append(updated_lod_page[2]);
page_tag_as_rendered(render_map_texel.xy, tilemap_data.tiles_index, 2);
}
}
render_map_texel.xy >>= 1;
if (all(equal(tile_co, render_map_texel.xy << 3u))) {
imageStore(render_map_lod3_img, render_map_texel, uvec4(updated_lod_page[3]));
if (updated_lod_page[3] != 0xFFFFFFFFu) {
page_clear_buf_append(updated_lod_page[3]);
page_tag_as_rendered(render_map_texel.xy, tilemap_data.tiles_index, 3);
}
}
render_map_texel.xy >>= 1;
if (all(equal(tile_co, render_map_texel.xy << 4u))) {
imageStore(render_map_lod4_img, render_map_texel, uvec4(updated_lod_page[4]));
if (updated_lod_page[4] != 0xFFFFFFFFu) {
page_clear_buf_append(updated_lod_page[4]);
page_tag_as_rendered(render_map_texel.xy, tilemap_data.tiles_index, 4);
}
}
render_map_texel.xy >>= 1;
if (all(equal(tile_co, render_map_texel.xy << 5u))) {
imageStore(render_map_lod5_img, render_map_texel, uvec4(updated_lod_page[5]));
if (updated_lod_page[5] != 0xFFFFFFFFu) {
page_clear_buf_append(updated_lod_page[5]);
page_tag_as_rendered(render_map_texel.xy, tilemap_data.tiles_index, 5);
}
}
}
if (all(equal(gl_GlobalInvocationID, uvec3(0)))) {
/* Clamp it as it can underflow if there is too much tile present on screen. */
pages_infos_buf.page_free_count = max(pages_infos_buf.page_free_count, 0);
}
}

View File

@ -0,0 +1,93 @@
/**
* Virtual shadowmapping: Setup phase for tilemaps.
*
* Clear the usage flag.
* Also tag for update shifted tiles for directional shadow clipmaps.
* Dispatched with one local thread per LOD0 tile and one workgroup per tilemap.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
shared int directional_range_changed;
ShadowTileDataPacked init_tile_data(ShadowTileDataPacked tile, bool do_update)
{
if (flag_test(tile, SHADOW_IS_RENDERED)) {
tile &= ~(SHADOW_DO_UPDATE | SHADOW_IS_RENDERED);
}
if (do_update) {
tile |= SHADOW_DO_UPDATE;
}
tile &= ~SHADOW_IS_USED;
return tile;
}
void main()
{
uint tilemap_index = gl_GlobalInvocationID.z;
ShadowTileMapData tilemap = tilemaps_buf[tilemap_index];
barrier();
if (all(equal(gl_LocalInvocationID, uvec3(0)))) {
/* Reset shift to not tag for update more than once per sync cycle. */
tilemaps_buf[tilemap_index].grid_shift = ivec2(0);
if (tilemap.projection_type != SHADOW_PROJECTION_CUBEFACE) {
int clip_index = tilemap.clip_data_index;
ShadowTileMapClip clip_data = tilemaps_clip_buf[clip_index];
float clip_near_new = orderedIntBitsToFloat(clip_data.clip_near);
float clip_far_new = orderedIntBitsToFloat(clip_data.clip_far);
bool near_changed = clip_near_new != clip_data.clip_near_stored;
bool far_changed = clip_far_new != clip_data.clip_far_stored;
directional_range_changed = int(near_changed || far_changed);
/* NOTE(fclem): This assumes clip near/far are computed each time the init phase runs. */
tilemaps_clip_buf[clip_index].clip_near_stored = clip_near_new;
tilemaps_clip_buf[clip_index].clip_far_stored = clip_far_new;
/* Reset for next update. */
tilemaps_clip_buf[clip_index].clip_near = floatBitsToOrderedInt(-FLT_MAX);
tilemaps_clip_buf[clip_index].clip_far = floatBitsToOrderedInt(FLT_MAX);
}
}
barrier();
ivec2 tile_co = ivec2(gl_GlobalInvocationID.xy);
ivec2 tile_shifted = tile_co + tilemap.grid_shift;
ivec2 tile_wrapped = ivec2(tile_shifted % SHADOW_TILEMAP_RES);
/* If this tile was shifted in and contains old information, update it.
* Note that cubemap always shift all tiles on update. */
bool do_update = !in_range_inclusive(tile_shifted, ivec2(0), ivec2(SHADOW_TILEMAP_RES - 1));
/* TODO(fclem): Might be better to resize the depth stored instead of a full render update. */
if (tilemap.projection_type != SHADOW_PROJECTION_CUBEFACE && directional_range_changed != 0) {
do_update = true;
}
int lod_max = (tilemap.projection_type == SHADOW_PROJECTION_CUBEFACE) ? SHADOW_TILEMAP_LOD : 0;
uint lod_size = uint(SHADOW_TILEMAP_RES);
for (int lod = 0; lod <= lod_max; lod++, lod_size >>= 1u) {
bool thread_active = all(lessThan(tile_co, ivec2(lod_size)));
ShadowTileDataPacked tile;
int tile_load = shadow_tile_offset(tile_wrapped, tilemap.tiles_index, lod);
if (thread_active) {
tile = init_tile_data(tiles_buf[tile_load], do_update);
}
/* Uniform control flow for barrier. Needed to avoid race condition on shifted loads. */
barrier();
if (thread_active) {
int tile_store = shadow_tile_offset(tile_co, tilemap.tiles_index, lod);
if ((tile_load != tile_store) && flag_test(tile, SHADOW_IS_CACHED)) {
/* Inlining of shadow_page_cache_update_tile_ref to avoid buffer depedencies. */
pages_cached_buf[shadow_tile_unpack(tile).cache_index].y = tile_store;
}
tiles_buf[tile_store] = tile;
}
}
}

View File

@ -0,0 +1,237 @@
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_shape_lib.glsl)
/* ---------------------------------------------------------------------- */
/** \name Tilemap data
* \{ */
int shadow_tile_index(ivec2 tile)
{
return tile.x + tile.y * SHADOW_TILEMAP_RES;
}
ivec2 shadow_tile_coord(int tile_index)
{
return ivec2(tile_index % SHADOW_TILEMAP_RES, tile_index / SHADOW_TILEMAP_RES);
}
/* Return bottom left pixel position of the tilemap inside the tilemap atlas. */
ivec2 shadow_tilemap_start(int tilemap_index)
{
return SHADOW_TILEMAP_RES *
ivec2(tilemap_index % SHADOW_TILEMAP_PER_ROW, tilemap_index / SHADOW_TILEMAP_PER_ROW);
}
ivec2 shadow_tile_coord_in_atlas(ivec2 tile, int tilemap_index)
{
return shadow_tilemap_start(tilemap_index) + tile;
}
/**
* Return tile index inside `tiles_buf` for a given tile coordinate inside a specific LOD.
* `tiles_index` should be `ShadowTileMapData.tiles_index`.
*/
int shadow_tile_offset(ivec2 tile, int tiles_index, int lod)
{
#if SHADOW_TILEMAP_LOD != 5
# error This needs to be adjusted
#endif
const int lod0_width = SHADOW_TILEMAP_RES / 1;
const int lod1_width = SHADOW_TILEMAP_RES / 2;
const int lod2_width = SHADOW_TILEMAP_RES / 4;
const int lod3_width = SHADOW_TILEMAP_RES / 8;
const int lod4_width = SHADOW_TILEMAP_RES / 16;
const int lod5_width = SHADOW_TILEMAP_RES / 32;
const int lod0_size = lod0_width * lod0_width;
const int lod1_size = lod1_width * lod1_width;
const int lod2_size = lod2_width * lod2_width;
const int lod3_size = lod3_width * lod3_width;
const int lod4_size = lod4_width * lod4_width;
const int lod5_size = lod5_width * lod5_width;
int offset = tiles_index;
switch (lod) {
case 5:
offset += lod0_size + lod1_size + lod2_size + lod3_size + lod4_size;
offset += tile.y * lod5_width;
break;
case 4:
offset += lod0_size + lod1_size + lod2_size + lod3_size;
offset += tile.y * lod4_width;
break;
case 3:
offset += lod0_size + lod1_size + lod2_size;
offset += tile.y * lod3_width;
break;
case 2:
offset += lod0_size + lod1_size;
offset += tile.y * lod2_width;
break;
case 1:
offset += lod0_size;
offset += tile.y * lod1_width;
break;
case 0:
default:
offset += tile.y * lod0_width;
break;
}
offset += tile.x;
return offset;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Load / Store functions.
* \{ */
/** \note: Will clamp if out of bounds. */
ShadowTileData shadow_tile_load(usampler2D tilemaps_tx, ivec2 tile_co, int tilemap_index)
{
/* NOTE(@fclem): This clamp can hide some small imprecision at clipmap transition.
* Can be disabled to check if the clipmap is well centered. */
tile_co = clamp(tile_co, ivec2(0), ivec2(SHADOW_TILEMAP_RES - 1));
uint tile_data =
texelFetch(tilemaps_tx, shadow_tile_coord_in_atlas(tile_co, tilemap_index), 0).x;
return shadow_tile_unpack(tile_data);
}
/* This function should be the inverse of ShadowDirectional::coverage_get(). */
int shadow_directional_level(LightData light, vec3 lP)
{
float lod;
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);
/* Since the distance is centered around the camera (and thus by extension the tilemap),
* we need to multiply by 2 to get the lod level which covers the following range:
* [-coverage_get(lod)/2..coverage_get(lod)/2] */
lod = log2(length(lP) * narrowing * 2.0);
}
else {
/* The narrowing need to be stronger since the tilemap position is not rounded but floored. */
const float narrowing = float(SHADOW_TILEMAP_RES) / (float(SHADOW_TILEMAP_RES) - 2.5001);
/* Since we want half of the size, bias the level by -1. */
float lod_min_half_size = exp2(float(light.clipmap_lod_min - 1));
lod = length(lP.xy) * narrowing / lod_min_half_size;
}
int clipmap_lod = int(ceil(lod + light._clipmap_lod_bias));
return clamp(clipmap_lod, light.clipmap_lod_min, light.clipmap_lod_max);
}
struct ShadowCoordinates {
/* Index of the tilemap to containing the tile. */
int tilemap_index;
/* LOD of the tile to load relative to the min level. Always positive. */
int lod_relative;
/* Tile coordinate inside the tilemap. */
ivec2 tile_coord;
/* UV coordinates in [0..SHADOW_TILEMAP_RES) range. */
vec2 uv;
};
/* Retain sign bit and avoid costly int division. */
ivec2 shadow_decompress_grid_offset(eLightType light_type, ivec2 offset, int level_relative)
{
if (light_type == LIGHT_SUN_ORTHO) {
return shadow_cascade_grid_offset(offset, level_relative);
}
else {
return ((offset & 0xFFFF) >> level_relative) - ((offset >> 16) >> level_relative);
}
}
/**
* \a lP shading point position in light space (world unit) and translated to camera position
* snapped to smallest clipmap level.
*/
ShadowCoordinates shadow_directional_coordinates(LightData light, vec3 lP)
{
ShadowCoordinates ret;
int level = shadow_directional_level(light, lP - light._position);
/* This difference needs to be less than 32 for the later shift to be valid.
* This is ensured by ShadowDirectional::clipmap_level_range(). */
int level_relative = level - light.clipmap_lod_min;
ret.tilemap_index = light.tilemap_index + level_relative;
ret.lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light.clipmap_lod_min : level;
/* Compute offset in tile. */
ivec2 clipmap_offset = shadow_decompress_grid_offset(
light.type, light.clipmap_base_offset, level_relative);
ret.uv = lP.xy - vec2(light._clipmap_origin_x, light._clipmap_origin_y);
ret.uv /= exp2(float(ret.lod_relative));
ret.uv = ret.uv * float(SHADOW_TILEMAP_RES) + float(SHADOW_TILEMAP_RES / 2);
ret.uv -= vec2(clipmap_offset);
/* Clamp to avoid out of tilemap access. */
ret.tile_coord = clamp(ivec2(ret.uv), ivec2(0.0), ivec2(SHADOW_TILEMAP_RES - 1));
return ret;
}
/* Transform vector to face local coordinate. */
vec3 shadow_punctual_local_position_to_face_local(int face_id, vec3 lL)
{
switch (face_id) {
case 1:
return vec3(-lL.y, lL.z, -lL.x);
case 2:
return vec3(lL.y, lL.z, lL.x);
case 3:
return vec3(lL.x, lL.z, -lL.y);
case 4:
return vec3(-lL.x, lL.z, lL.y);
case 5:
return vec3(lL.x, -lL.y, -lL.z);
default:
return lL;
}
}
/**
* \a lP shading point position in face local space (world unit).
* \a face_id is the one used to rotate lP using shadow_punctual_local_position_to_face_local().
*/
ShadowCoordinates shadow_punctual_coordinates(LightData light, vec3 lP, int face_id)
{
ShadowCoordinates ret;
ret.tilemap_index = light.tilemap_index + face_id;
/* UVs in [-1..+1] range. */
ret.uv = lP.xy / abs(lP.z);
/* UVs in [0..SHADOW_TILEMAP_RES] range. */
ret.uv = ret.uv * float(SHADOW_TILEMAP_RES / 2) + float(SHADOW_TILEMAP_RES / 2);
/* Clamp to avoid out of tilemap access. */
ret.tile_coord = clamp(ivec2(ret.uv), ivec2(0), ivec2(SHADOW_TILEMAP_RES - 1));
return ret;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Frustum shapes.
* \{ */
vec3 shadow_tile_corner_persp(ShadowTileMapData tilemap, ivec2 tile)
{
return tilemap.corners[1].xyz + tilemap.corners[2].xyz * float(tile.x) +
tilemap.corners[3].xyz * float(tile.y);
}
Pyramid shadow_tilemap_cubeface_bounds(ShadowTileMapData tilemap,
ivec2 tile_start,
const ivec2 extent)
{
Pyramid shape;
shape.corners[0] = tilemap.corners[0].xyz;
shape.corners[1] = shadow_tile_corner_persp(tilemap, tile_start + ivec2(0, 0));
shape.corners[2] = shadow_tile_corner_persp(tilemap, tile_start + ivec2(extent.x, 0));
shape.corners[3] = shadow_tile_corner_persp(tilemap, tile_start + extent);
shape.corners[4] = shadow_tile_corner_persp(tilemap, tile_start + ivec2(0, extent.y));
return shape;
}
/** \} */

View File

@ -10,6 +10,7 @@
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_transparency_lib.glsl)
vec4 closure_to_rgba(Closure cl)
{
@ -23,50 +24,6 @@ vec4 closure_to_rgba(Closure cl)
return out_color;
}
/* From the paper "Hashed Alpha Testing" by Chris Wyman and Morgan McGuire. */
float hash(vec2 a)
{
return fract(1e4 * sin(17.0 * a.x + 0.1 * a.y) * (0.1 + abs(sin(13.0 * a.y + a.x))));
}
float hash3d(vec3 a)
{
return hash(vec2(hash(a.xy), a.z));
}
float hashed_alpha_threshold(float hash_scale, float hash_offset, vec3 P)
{
/* Find the discretized derivatives of our coordinates. */
float max_deriv = max(length(dFdx(P)), length(dFdy(P)));
float pix_scale = 1.0 / (hash_scale * max_deriv);
/* Find two nearest log-discretized noise scales. */
float pix_scale_log = log2(pix_scale);
vec2 pix_scales;
pix_scales.x = exp2(floor(pix_scale_log));
pix_scales.y = exp2(ceil(pix_scale_log));
/* Compute alpha thresholds at our two noise scales. */
vec2 alpha;
alpha.x = hash3d(floor(pix_scales.x * P));
alpha.y = hash3d(floor(pix_scales.y * P));
/* Factor to interpolate lerp with. */
float fac = fract(log2(pix_scale));
/* Interpolate alpha threshold from noise at two scales. */
float x = mix(alpha.x, alpha.y, fac);
/* Pass into CDF to compute uniformly distrib threshold. */
float a = min(fac, 1.0 - fac);
float one_a = 1.0 - a;
float denom = 1.0 / (2 * a * one_a);
float one_x = (1 - x);
vec3 cases = vec3((x * x) * denom, (x - 0.5 * a) / one_a, 1.0 - (one_x * one_x * denom));
/* Find our final, uniformly distributed alpha threshold. */
float threshold = (x < one_a) ? ((x < a) ? cases.x : cases.y) : cases.z;
/* Jitter the threshold for TAA accumulation. */
threshold = fract(threshold + hash_offset);
/* Avoids threshold == 0. */
threshold = clamp(threshold, 1.0e-6, 1.0);
return threshold;
}
void main()
{
#ifdef MAT_TRANSPARENT
@ -75,7 +32,7 @@ void main()
nodetree_surface();
float noise_offset = sampling_rng_1D_get(SAMPLING_TRANSPARENCY);
float random_threshold = hashed_alpha_threshold(1.0, noise_offset, g_data.P);
float random_threshold = transparency_hashed_alpha_threshold(1.0, noise_offset, g_data.P);
float transparency = avg(g_transmittance);
if (transparency > random_threshold) {

View File

@ -24,6 +24,7 @@ vec4 closure_to_rgba(Closure cl)
light_eval(g_diffuse_data,
g_reflection_data,
g_data.P,
g_data.Ng,
cameraVec(g_data.P),
vP_z,
0.01 /* TODO(fclem) thickness. */,
@ -66,6 +67,7 @@ void main()
light_eval(g_diffuse_data,
g_reflection_data,
g_data.P,
g_data.Ng,
cameraVec(g_data.P),
vP_z,
0.01 /* TODO(fclem) thickness. */,

View File

@ -0,0 +1,91 @@
/**
* Virtual Shadow map output.
*
* Meshes are rasterize onto an empty framebuffer. Each generated fragment then checks which
* virtual page it is supposed to go and load the physical page adress.
* If a physical page exists, we then use atomicMin to mimic a less-than depth test and write to
* the destination texel.
**/
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_transparency_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
void write_depth(ivec2 texel_co, const int lod, ivec2 tile_co, float depth)
{
ivec2 texel_co_lod = texel_co >> lod;
ivec2 lod_corner_in_lod0 = texel_co_lod << lod;
/* Add half of the lod to get the top right pixel nearest to the lod pixel.
* This way we never get more than half a LOD0 pixel of offset from the center of any LOD.
* This offset is taken into account during sampling. */
const int lod_half_stride_in_lod0 = (1 << lod) / 2;
ivec2 closest_lod0_texel = lod_corner_in_lod0 + lod_half_stride_in_lod0;
if (!all(equal(closest_lod0_texel, texel_co))) {
return;
}
ivec3 render_map_coord = ivec3(tile_co >> lod, shadow_interp.view_id);
uint page_packed = texelFetch(shadow_render_map_tx, render_map_coord, lod).r;
/* Return if no valid page. */
if (page_packed == 0xFFFFFFFFu) {
return;
}
ivec2 page = ivec2(unpackUvec2x16(page_packed));
ivec2 texel_in_page = texel_co_lod % pages_infos_buf.page_size;
ivec2 out_texel = page * pages_infos_buf.page_size + texel_in_page;
uint u_depth = floatBitsToUint(depth);
/* Quantization bias. Equivalent to nextafter in C without all the safety. 1 is not enough. */
u_depth += 2;
imageAtomicMin(shadow_atlas_img, out_texel, u_depth);
}
void main()
{
#ifdef MAT_TRANSPARENT
init_globals();
nodetree_surface();
float noise_offset = sampling_rng_1D_get(SAMPLING_TRANSPARENCY);
float random_threshold = transparency_hashed_alpha_threshold(1.0, noise_offset, g_data.P);
float transparency = avg(g_transmittance);
if (transparency > random_threshold) {
discard;
return;
}
#endif
drw_view_id = shadow_interp.view_id;
ivec2 texel_co = ivec2(gl_FragCoord.xy);
ivec2 tile_co = texel_co / pages_infos_buf.page_size;
float depth = gl_FragCoord.z;
float slope_bias = fwidth(depth);
write_depth(texel_co, 0, tile_co, depth + slope_bias);
/* Only needed for local lights. */
bool is_persp = (drw_view.winmat[3][3] == 0.0);
if (is_persp) {
/* Note that even if texel center is offset, we store unmodified depth.
* We increase bias instead at sampling time. */
#if SHADOW_TILEMAP_LOD != 5
# error This needs to be adjusted
#endif
write_depth(texel_co, 1, tile_co, depth + slope_bias * 2.0);
write_depth(texel_co, 2, tile_co, depth + slope_bias * 4.0);
write_depth(texel_co, 3, tile_co, depth + slope_bias * 8.0);
write_depth(texel_co, 4, tile_co, depth + slope_bias * 16.0);
write_depth(texel_co, 5, tile_co, depth + slope_bias * 32.0);
}
}

View File

@ -0,0 +1,44 @@
/* From the paper "Hashed Alpha Testing" by Chris Wyman and Morgan McGuire. */
float transparency_hash(vec2 a)
{
return fract(1e4 * sin(17.0 * a.x + 0.1 * a.y) * (0.1 + abs(sin(13.0 * a.y + a.x))));
}
float transparency_hash_3d(vec3 a)
{
return transparency_hash(vec2(transparency_hash(a.xy), a.z));
}
float transparency_hashed_alpha_threshold(float hash_scale, float hash_offset, vec3 P)
{
/* Find the discretized derivatives of our coordinates. */
float max_deriv = max(length(dFdx(P)), length(dFdy(P)));
float pix_scale = 1.0 / (hash_scale * max_deriv);
/* Find two nearest log-discretized noise scales. */
float pix_scale_log = log2(pix_scale);
vec2 pix_scales;
pix_scales.x = exp2(floor(pix_scale_log));
pix_scales.y = exp2(ceil(pix_scale_log));
/* Compute alpha thresholds at our two noise scales. */
vec2 alpha;
alpha.x = transparency_hash_3d(floor(pix_scales.x * P));
alpha.y = transparency_hash_3d(floor(pix_scales.y * P));
/* Factor to interpolate lerp with. */
float fac = fract(log2(pix_scale));
/* Interpolate alpha threshold from noise at two scales. */
float x = mix(alpha.x, alpha.y, fac);
/* Pass into CDF to compute uniformly distrib threshold. */
float a = min(fac, 1.0 - fac);
float one_a = 1.0 - a;
float denom = 1.0 / (2 * a * one_a);
float one_x = (1 - x);
vec3 cases = vec3((x * x) * denom, (x - 0.5 * a) / one_a, 1.0 - (one_x * one_x * denom));
/* Find our final, uniformly distributed alpha threshold. */
float threshold = (x < one_a) ? ((x < a) ? cases.x : cases.y) : cases.z;
/* Jitter the threshold for TAA accumulation. */
threshold = fract(threshold + hash_offset);
/* Avoids threshold == 0. */
threshold = clamp(threshold, 1.0e-6, 1.0);
return threshold;
}

View File

@ -40,7 +40,7 @@ GPU_SHADER_CREATE_INFO(eevee_geom_gpencil)
.additional_info("eevee_shared")
.define("MAT_GEOM_GPENCIL")
.vertex_source("eevee_geom_gpencil_vert.glsl")
.additional_info("draw_gpencil", "draw_resource_id_varying", "draw_resource_handle");
.additional_info("draw_gpencil", "draw_resource_id_varying", "draw_resource_id_new");
GPU_SHADER_CREATE_INFO(eevee_geom_curves)
.additional_info("eevee_shared")
@ -49,7 +49,7 @@ GPU_SHADER_CREATE_INFO(eevee_geom_curves)
.additional_info("draw_hair",
"draw_curves_infos",
"draw_resource_id_varying",
"draw_resource_handle");
"draw_resource_id_new");
GPU_SHADER_CREATE_INFO(eevee_geom_world)
.additional_info("eevee_shared")
@ -95,8 +95,8 @@ GPU_SHADER_CREATE_INFO(eevee_render_pass_out)
.image_out(RBUFS_EMISSION_SLOT, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img");
GPU_SHADER_CREATE_INFO(eevee_cryptomatte_out)
.storage_buf(7, Qualifier::READ, "vec2", "cryptomatte_object_buf[]", Frequency::PASS)
.image_out(7, Qualifier::WRITE, GPU_RGBA32F, "rp_cryptomatte_img");
.storage_buf(CRYPTOMATTE_BUF_SLOT, Qualifier::READ, "vec2", "cryptomatte_object_buf[]")
.image_out(RBUFS_CRYPTOMATTE_SLOT, Qualifier::WRITE, GPU_RGBA32F, "rp_cryptomatte_img");
GPU_SHADER_CREATE_INFO(eevee_surf_deferred)
.vertex_out(eevee_surf_iface)
@ -128,14 +128,13 @@ GPU_SHADER_CREATE_INFO(eevee_surf_forward)
.fragment_out(0, Type::VEC4, "out_radiance", DualBlend::SRC_0)
.fragment_out(0, Type::VEC4, "out_transmittance", DualBlend::SRC_1)
.fragment_source("eevee_surf_forward_frag.glsl")
.additional_info("eevee_cryptomatte_out",
"eevee_light_data",
.additional_info("eevee_light_data",
"eevee_camera",
"eevee_utility_texture",
"eevee_sampling_data"
// "eevee_lightprobe_data",
// "eevee_shadow_data"
"eevee_sampling_data",
"eevee_shadow_data"
/* Optionally added depending on the material. */
// "eevee_cryptomatte_out",
// "eevee_raytrace_data",
// "eevee_transmittance_data",
// "eevee_aov_out",
@ -158,6 +157,23 @@ GPU_SHADER_CREATE_INFO(eevee_surf_world)
"eevee_camera",
"eevee_utility_texture");
GPU_SHADER_INTERFACE_INFO(eevee_shadow_iface, "shadow_interp").flat(Type::UINT, "view_id");
GPU_SHADER_CREATE_INFO(eevee_surf_shadow)
.define("DRW_VIEW_LEN", "64")
.define("MAT_SHADOW")
.vertex_out(eevee_surf_iface)
.vertex_out(eevee_shadow_iface)
.sampler(SHADOW_RENDER_MAP_SLOT, ImageType::UINT_2D_ARRAY, "shadow_render_map_tx")
.image(SHADOW_ATLAS_SLOT,
GPU_R32UI,
Qualifier::READ_WRITE,
ImageType::UINT_2D,
"shadow_atlas_img")
.storage_buf(SHADOW_PAGE_INFO_SLOT, Qualifier::READ, "ShadowPagesInfoData", "pages_infos_buf")
.fragment_source("eevee_surf_shadow_frag.glsl")
.additional_info("eevee_camera", "eevee_utility_texture", "eevee_sampling_data");
#undef image_out
#undef image_array_out
@ -210,7 +226,8 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub).define("EEVEE_MATERIAL_STUBS");
EEVEE_MAT_GEOM_VARIATIONS(name##_world, "eevee_surf_world", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_depth, "eevee_surf_depth", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_deferred, "eevee_surf_deferred", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_forward, "eevee_surf_forward", __VA_ARGS__)
EEVEE_MAT_GEOM_VARIATIONS(name##_forward, "eevee_surf_forward", __VA_ARGS__) \
EEVEE_MAT_GEOM_VARIATIONS(name##_shadow, "eevee_surf_shadow", __VA_ARGS__)
EEVEE_MAT_PIPE_VARIATIONS(eevee_surface, "eevee_material_stub")

View File

@ -0,0 +1,176 @@
#include "eevee_defines.hh"
#include "gpu_shader_create_info.hh"
/* -------------------------------------------------------------------- */
/** \name Shadow pipeline
* \{ */
GPU_SHADER_CREATE_INFO(eevee_shadow_tilemap_bounds)
.do_static_compilation(true)
.local_group_size(SHADOW_BOUNDS_GROUP_SIZE)
.storage_buf(LIGHT_BUF_SLOT, Qualifier::READ_WRITE, "LightData", "light_buf[]")
.storage_buf(LIGHT_CULL_BUF_SLOT, Qualifier::READ, "LightCullingData", "light_cull_buf")
.storage_buf(4, Qualifier::READ, "uint", "casters_id_buf[]")
.storage_buf(5, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(6, Qualifier::READ, "ObjectBounds", "bounds_buf[]")
.storage_buf(7, Qualifier::READ_WRITE, "ShadowTileMapClip", "tilemaps_clip_buf[]")
.push_constant(Type::INT, "resource_len")
.typedef_source("draw_shader_shared.h")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_tilemap_bounds_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_tilemap_init)
.do_static_compilation(true)
.local_group_size(SHADOW_TILEMAP_RES, SHADOW_TILEMAP_RES)
.storage_buf(0, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(1, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.storage_buf(2, Qualifier::READ_WRITE, "ShadowTileMapClip", "tilemaps_clip_buf[]")
.storage_buf(4, Qualifier::READ_WRITE, "uvec2", "pages_cached_buf[]")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_tilemap_init_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_tag_update)
.do_static_compilation(true)
.local_group_size(1, 1, 1)
.storage_buf(0, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(1, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.storage_buf(5, Qualifier::READ, "ObjectBounds", "bounds_buf[]")
.storage_buf(6, Qualifier::READ, "uint", "resource_ids_buf[]")
.additional_info("eevee_shared", "draw_view", "draw_view_culling")
.compute_source("eevee_shadow_tag_update_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_opaque)
.do_static_compilation(true)
.local_group_size(SHADOW_DEPTH_SCAN_GROUP_SIZE, SHADOW_DEPTH_SCAN_GROUP_SIZE)
.sampler(0, ImageType::DEPTH_2D, "depth_tx")
.storage_buf(5, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(6, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.push_constant(Type::FLOAT, "tilemap_projection_ratio")
.additional_info("eevee_shared", "draw_view", "draw_view_culling", "eevee_light_data")
.compute_source("eevee_shadow_tag_usage_comp.glsl");
GPU_SHADER_INTERFACE_INFO(eevee_shadow_tag_transparent_iface, "interp")
.smooth(Type::VEC3, "P")
.smooth(Type::VEC3, "vP");
GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_transparent)
.do_static_compilation(true)
.vertex_in(0, Type::VEC3, "pos")
.storage_buf(4, Qualifier::READ, "ObjectBounds", "bounds_buf[]")
.storage_buf(5, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(6, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.push_constant(Type::FLOAT, "tilemap_projection_ratio")
.vertex_out(eevee_shadow_tag_transparent_iface)
.additional_info(
"eevee_shared", "draw_view", "draw_view_culling", "draw_modelmat_new", "eevee_light_data")
.vertex_source("eevee_shadow_tag_usage_vert.glsl")
.fragment_source("eevee_shadow_tag_usage_frag.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_page_mask)
.do_static_compilation(true)
.local_group_size(SHADOW_TILEMAP_RES, SHADOW_TILEMAP_RES)
.storage_buf(0, Qualifier::READ, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(1, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_page_mask_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_page_free)
.do_static_compilation(true)
.local_group_size(SHADOW_TILEMAP_LOD0_LEN)
.storage_buf(0, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(1, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.storage_buf(2, Qualifier::READ_WRITE, "ShadowPagesInfoData", "pages_infos_buf")
.storage_buf(3, Qualifier::READ_WRITE, "uint", "pages_free_buf[]")
.storage_buf(4, Qualifier::READ_WRITE, "uvec2", "pages_cached_buf[]")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_page_free_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_page_defrag)
.do_static_compilation(true)
.local_group_size(1)
.typedef_source("draw_shader_shared.h")
.storage_buf(1, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.storage_buf(2, Qualifier::READ_WRITE, "ShadowPagesInfoData", "pages_infos_buf")
.storage_buf(3, Qualifier::READ_WRITE, "uint", "pages_free_buf[]")
.storage_buf(4, Qualifier::READ_WRITE, "uvec2", "pages_cached_buf[]")
.storage_buf(5, Qualifier::WRITE, "DispatchCommand", "clear_dispatch_buf")
.storage_buf(6, Qualifier::READ_WRITE, "ShadowStatistics", "statistics_buf")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_page_defrag_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_page_allocate)
.do_static_compilation(true)
.local_group_size(SHADOW_TILEMAP_LOD0_LEN)
.typedef_source("draw_shader_shared.h")
.storage_buf(0, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(1, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.storage_buf(2, Qualifier::READ_WRITE, "ShadowPagesInfoData", "pages_infos_buf")
.storage_buf(3, Qualifier::READ_WRITE, "uint", "pages_free_buf[]")
.storage_buf(4, Qualifier::READ_WRITE, "uvec2", "pages_cached_buf[]")
.storage_buf(6, Qualifier::READ_WRITE, "ShadowStatistics", "statistics_buf")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_page_allocate_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_tilemap_finalize)
.do_static_compilation(true)
.typedef_source("draw_shader_shared.h")
.local_group_size(SHADOW_TILEMAP_RES, SHADOW_TILEMAP_RES)
.storage_buf(0, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(1, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.storage_buf(2, Qualifier::READ_WRITE, "ShadowPagesInfoData", "pages_infos_buf")
.storage_buf(3, Qualifier::WRITE, "ViewMatrices", "view_infos_buf[64]")
.storage_buf(4, Qualifier::READ_WRITE, "ShadowStatistics", "statistics_buf")
.storage_buf(5, Qualifier::READ_WRITE, "DispatchCommand", "clear_dispatch_buf")
.storage_buf(6, Qualifier::READ_WRITE, "uint", "clear_page_buf[]")
.storage_buf(7, Qualifier::READ_WRITE, "ShadowTileMapClip", "tilemaps_clip_buf[]")
.image(0, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D, "tilemaps_img")
.image(1, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D_ARRAY, "render_map_lod0_img")
.image(2, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D_ARRAY, "render_map_lod1_img")
.image(3, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D_ARRAY, "render_map_lod2_img")
.image(4, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D_ARRAY, "render_map_lod3_img")
.image(5, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D_ARRAY, "render_map_lod4_img")
.image(6, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D_ARRAY, "render_map_lod5_img")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_tilemap_finalize_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_shadow_page_clear)
.do_static_compilation(true)
.local_group_size(SHADOW_PAGE_CLEAR_GROUP_SIZE, SHADOW_PAGE_CLEAR_GROUP_SIZE)
.storage_buf(2, Qualifier::READ, "ShadowPagesInfoData", "pages_infos_buf")
.storage_buf(6, Qualifier::READ, "uint", "clear_page_buf[]")
.image(0, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D, "atlas_img")
.additional_info("eevee_shared")
.compute_source("eevee_shadow_page_clear_comp.glsl");
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow resources
* \{ */
GPU_SHADER_CREATE_INFO(eevee_shadow_data)
.sampler(SHADOW_ATLAS_TEX_SLOT, ImageType::UINT_2D, "shadow_atlas_tx")
.sampler(SHADOW_TILEMAPS_TEX_SLOT, ImageType::UINT_2D, "shadow_tilemaps_tx");
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug
* \{ */
GPU_SHADER_CREATE_INFO(eevee_shadow_debug)
.do_static_compilation(true)
.additional_info("eevee_shared")
.storage_buf(5, Qualifier::READ, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(6, Qualifier::READ, "ShadowTileDataPacked", "tiles_buf[]")
.fragment_out(0, Type::VEC4, "out_color_add", DualBlend::SRC_0)
.fragment_out(0, Type::VEC4, "out_color_mul", DualBlend::SRC_1)
.push_constant(Type::INT, "debug_mode")
.push_constant(Type::INT, "debug_tilemap_index")
.fragment_source("eevee_shadow_debug_frag.glsl")
.additional_info(
"draw_fullscreen", "draw_view", "eevee_hiz_data", "eevee_light_data", "eevee_shadow_data");
/** \} */

View File

@ -460,6 +460,13 @@ class StorageBuffer : public T, public detail::StorageCommon<T, 1, device_only>
*static_cast<T *>(this) = other;
return *this;
}
static void swap(StorageBuffer<T> &a, StorageBuffer<T> &b)
{
/* Swap content, but not `data_` pointers since they point to `this`. */
SWAP(T, static_cast<T>(a), static_cast<T>(b));
std::swap(a.ssbo_, b.ssbo_);
}
};
/** \} */
@ -1162,6 +1169,11 @@ template<typename T, int64_t len> class SwapChain {
}
}
constexpr int64_t size()
{
return len;
}
T &current()
{
return chain_[0];

View File

@ -43,9 +43,15 @@ void Manager::begin_sync()
#ifdef DEBUG
/* Detect uninitialized data. */
memset(matrix_buf.current().data(), 0xF0, resource_len_ * sizeof(*matrix_buf.current().data()));
memset(bounds_buf.current().data(), 0xF0, resource_len_ * sizeof(*bounds_buf.current().data()));
memset(infos_buf.current().data(), 0xF0, resource_len_ * sizeof(*infos_buf.current().data()));
memset(matrix_buf.current().data(),
0xF0,
matrix_buf.current().size() * sizeof(*matrix_buf.current().data()));
memset(bounds_buf.current().data(),
0xF0,
matrix_buf.current().size() * sizeof(*bounds_buf.current().data()));
memset(infos_buf.current().data(),
0xF0,
matrix_buf.current().size() * sizeof(*infos_buf.current().data()));
#endif
resource_len_ = 0;
attribute_len_ = 0;

File diff suppressed because it is too large Load Diff

View File

@ -577,6 +577,7 @@ set(SRC_SHADER_CREATE_INFOS
../draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_vfx_info.hh

View File

@ -48,3 +48,9 @@ GPU_SHADER_CREATE_INFO(gpu_compute_ssbo_binding_test)
.storage_buf(1, Qualifier::WRITE, "int", "data1[]")
.compute_source("gpu_compute_dummy_test.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_shadow_test)
.fragment_source("eevee_shadow_test.glsl")
.additional_info("gpu_shader_test")
.additional_info("eevee_shared")
.do_static_compilation(true);

View File

@ -402,7 +402,7 @@ static StringRef print_test_line(StringRefNull test_src, int64_t test_line)
return "";
}
static void gpu_shader_lib_test(const char *test_src_name)
static void gpu_shader_lib_test(const char *test_src_name, const char *additional_info = nullptr)
{
using namespace shader;
@ -411,6 +411,9 @@ static void gpu_shader_lib_test(const char *test_src_name)
ShaderCreateInfo create_info(test_src_name);
create_info.fragment_source(test_src_name);
create_info.additional_info("gpu_shader_test");
if (additional_info) {
create_info.additional_info(additional_info);
}
StringRefNull test_src = gpu_shader_dependency_get_source(test_src_name);
@ -484,4 +487,10 @@ static void test_gpu_math_lib()
}
GPU_TEST(gpu_math_lib)
static void test_eevee_lib()
{
gpu_shader_lib_test("eevee_shadow_test.glsl", "eevee_shared");
}
GPU_TEST(eevee_lib)
} // namespace blender::gpu::tests

View File

@ -793,6 +793,8 @@ typedef struct RenderData {
float simplify_particles;
float simplify_particles_render;
float simplify_volumes;
float simplify_shadows;
float simplify_shadows_render;
/** Freestyle line thickness options. */
int line_thickness_mode;
@ -1829,6 +1831,8 @@ typedef struct SceneEEVEE {
int shadow_method DNA_DEPRECATED;
int shadow_cube_size;
int shadow_cascade_size;
int shadow_pool_size;
char _pad[4];
struct LightCache *light_cache DNA_DEPRECATED;
struct LightCache *light_cache_data;

View File

@ -6902,6 +6902,21 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
prop, "Simplify Volumes", "Resolution percentage of volume objects in viewport");
RNA_def_property_update(prop, 0, "rna_Scene_simplify_update");
/* EEVEE - Simplify Options */
prop = RNA_def_property(srna, "simplify_shadows_render", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_default(prop, 1.0);
RNA_def_property_range(prop, 0.0, 1.0f);
RNA_def_property_ui_text(
prop, "Simplify Shadows", "Resolution percentage of shadows in viewport");
RNA_def_property_update(prop, 0, "rna_Scene_simplify_update");
prop = RNA_def_property(srna, "simplify_shadows", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_default(prop, 1.0);
RNA_def_property_range(prop, 0.0, 1.0f);
RNA_def_property_ui_text(
prop, "Simplify Shadows", "Resolution percentage of shadows in viewport");
RNA_def_property_update(prop, 0, "rna_Scene_simplify_update");
/* Grease Pencil - Simplify Options */
prop = RNA_def_property(srna, "simplify_gpencil", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "simplify_gpencil", SIMPLIFY_GPENCIL_ENABLE);
@ -7257,6 +7272,17 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem eevee_shadow_pool_size_items[] = {
{16, "16", 0, "16 MB", ""},
{32, "32", 0, "32 MB", ""},
{64, "64", 0, "64 MB", ""},
{128, "128", 0, "128 MB", ""},
{256, "256", 0, "256 MB", ""},
{512, "512", 0, "512 MB", ""},
{1024, "1024", 0, "1 GB", ""},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem eevee_gi_visibility_size_items[] = {
{8, "8", 0, "8 px", ""},
{16, "16", 0, "16 px", ""},
@ -7745,6 +7771,16 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
prop = RNA_def_property(srna, "shadow_pool_size", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, eevee_shadow_pool_size_items);
RNA_def_property_ui_text(prop,
"Shadow Pool Size",
"Size of the shadow pool, "
"bigger pool size allows for more shadows in the scene "
"but might not fits into GPU memory");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
prop = RNA_def_property(srna, "use_shadow_high_bitdepth", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", SCE_EEVEE_SHADOW_HIGH_BITDEPTH);
RNA_def_property_ui_text(prop, "High Bit Depth", "Use 32-bit shadows");