me-main #1

Merged
Nate Rupsis merged 123 commits from me-main into main 2023-02-13 18:39:11 +01:00
63 changed files with 5921 additions and 242 deletions
Showing only changes of commit a0f5240089 - Show all commits

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");