This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/draw/engines/eevee/eevee_lights.c

1091 lines
38 KiB
C
Raw Normal View History

/*
* Copyright 2016, Blender Foundation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Blender Institute
*
*/
/** \file eevee_lights.c
* \ingroup DNA
*/
#include "DRW_render.h"
#include "BLI_dynstr.h"
#include "BKE_object.h"
#include "eevee_engine.h"
#include "eevee_private.h"
/* Theses are the structs stored inside Objects.
* It works with even if the object is in multiple layers
* because we don't get the same "Object *" for each layer. */
typedef struct EEVEE_LightData {
short light_id, shadow_id;
} EEVEE_LightData;
typedef struct EEVEE_ShadowCubeData {
short light_id, shadow_id, cube_id, layer_id;
} EEVEE_ShadowCubeData;
typedef struct EEVEE_ShadowCascadeData {
short light_id, shadow_id, cascade_id, layer_id;
float viewprojmat[MAX_CASCADE_NUM][4][4]; /* World->Lamp->NDC : used for rendering the shadow map. */
float radius[MAX_CASCADE_NUM];
} EEVEE_ShadowCascadeData;
typedef struct ShadowCaster {
struct ShadowCaster *next, *prev;
void *ob;
bool prune;
} ShadowCaster;
static struct {
struct GPUShader *shadow_sh;
struct GPUShader *shadow_store_cube_sh[SHADOW_METHOD_MAX];
struct GPUShader *shadow_store_cascade_sh[SHADOW_METHOD_MAX];
struct GPUShader *shadow_copy_cube_sh[SHADOW_METHOD_MAX];
struct GPUShader *shadow_copy_cascade_sh[SHADOW_METHOD_MAX];
} e_data = {NULL}; /* Engine data */
extern char datatoc_shadow_vert_glsl[];
extern char datatoc_shadow_geom_glsl[];
extern char datatoc_shadow_frag_glsl[];
extern char datatoc_shadow_store_frag_glsl[];
extern char datatoc_shadow_copy_frag_glsl[];
extern char datatoc_concentric_samples_lib_glsl[];
/* *********** FUNCTIONS *********** */
void EEVEE_lights_init(EEVEE_SceneLayerData *sldata)
{
const unsigned int shadow_ubo_size = sizeof(EEVEE_Shadow) * MAX_SHADOW +
sizeof(EEVEE_ShadowCube) * MAX_SHADOW_CUBE +
sizeof(EEVEE_ShadowCascade) * MAX_SHADOW_CASCADE;
const DRWContextState *draw_ctx = DRW_context_state_get();
SceneLayer *scene_layer = draw_ctx->scene_layer;
IDProperty *props = BKE_scene_layer_engine_evaluated_get(scene_layer, COLLECTION_MODE_NONE, RE_engine_id_BLENDER_EEVEE);
if (!e_data.shadow_sh) {
e_data.shadow_sh = DRW_shader_create(
datatoc_shadow_vert_glsl, datatoc_shadow_geom_glsl, datatoc_shadow_frag_glsl, NULL);
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, datatoc_concentric_samples_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_shadow_store_frag_glsl);
char *store_shadow_shader_str = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
2017-11-20 14:28:24 +11:00
e_data.shadow_store_cube_sh[SHADOW_ESM] = DRW_shader_create_fullscreen(
store_shadow_shader_str,
"#define ESM\n");
e_data.shadow_store_cascade_sh[SHADOW_ESM] = DRW_shader_create_fullscreen(
store_shadow_shader_str, "#define ESM\n"
"#define CSM\n");
e_data.shadow_store_cube_sh[SHADOW_VSM] = DRW_shader_create_fullscreen(
store_shadow_shader_str,
"#define VSM\n");
e_data.shadow_store_cascade_sh[SHADOW_VSM] = DRW_shader_create_fullscreen(
store_shadow_shader_str,
"#define VSM\n"
"#define CSM\n");
MEM_freeN(store_shadow_shader_str);
2017-11-20 14:28:24 +11:00
e_data.shadow_copy_cube_sh[SHADOW_ESM] = DRW_shader_create_fullscreen(
datatoc_shadow_copy_frag_glsl,
"#define ESM\n"
"#define COPY\n");
e_data.shadow_copy_cascade_sh[SHADOW_ESM] = DRW_shader_create_fullscreen(
datatoc_shadow_copy_frag_glsl, "#define ESM\n"
"#define COPY\n"
"#define CSM\n");
e_data.shadow_copy_cube_sh[SHADOW_VSM] = DRW_shader_create_fullscreen(
datatoc_shadow_copy_frag_glsl,
"#define VSM\n"
"#define COPY\n");
e_data.shadow_copy_cascade_sh[SHADOW_VSM] = DRW_shader_create_fullscreen(
datatoc_shadow_copy_frag_glsl,
"#define VSM\n"
"#define COPY\n"
"#define CSM\n");
}
if (!sldata->lamps) {
sldata->lamps = MEM_callocN(sizeof(EEVEE_LampsInfo), "EEVEE_LampsInfo");
sldata->light_ubo = DRW_uniformbuffer_create(sizeof(EEVEE_Light) * MAX_LIGHT, NULL);
sldata->shadow_ubo = DRW_uniformbuffer_create(shadow_ubo_size, NULL);
sldata->shadow_render_ubo = DRW_uniformbuffer_create(sizeof(EEVEE_ShadowRender), NULL);
}
int sh_method = BKE_collection_engine_property_value_get_int(props, "shadow_method");
int sh_size = BKE_collection_engine_property_value_get_int(props, "shadow_size");
int sh_high_bitdepth = BKE_collection_engine_property_value_get_int(props, "shadow_high_bitdepth");
EEVEE_LampsInfo *linfo = sldata->lamps;
if ((linfo->shadow_size != sh_size) ||
2017-10-07 15:57:14 +11:00
(linfo->shadow_method != sh_method) ||
(linfo->shadow_high_bitdepth != sh_high_bitdepth))
{
BLI_assert((sh_size > 0) && (sh_size <= 8192));
DRW_TEXTURE_FREE_SAFE(sldata->shadow_pool);
DRW_TEXTURE_FREE_SAFE(sldata->shadow_cube_target);
DRW_TEXTURE_FREE_SAFE(sldata->shadow_cascade_target);
DRW_TEXTURE_FREE_SAFE(sldata->shadow_cube_blur);
DRW_TEXTURE_FREE_SAFE(sldata->shadow_cascade_blur);
linfo->shadow_high_bitdepth = sh_high_bitdepth;
linfo->shadow_method = sh_method;
linfo->shadow_size = sh_size;
linfo->shadow_render_data.stored_texel_size = 1.0 / (float)linfo->shadow_size;
/* Compute adequate size for the cubemap render target.
* The 3.0f factor is here to make sure there is no under sampling between
* the octahedron mapping and the cubemap. */
int new_cube_target_size = (int)ceil(sqrt((float)(sh_size * sh_size) / 6.0f) * 3.0f);
CLAMP(new_cube_target_size, 1, 4096);
linfo->shadow_cube_target_size = new_cube_target_size;
linfo->shadow_render_data.cube_texel_size = 1.0 / (float)linfo->shadow_cube_target_size;
}
}
void EEVEE_lights_cache_init(EEVEE_SceneLayerData *sldata, EEVEE_PassList *psl)
{
EEVEE_LampsInfo *linfo = sldata->lamps;
linfo->num_light = 0;
linfo->num_layer = 0;
linfo->gpu_cube_ct = linfo->gpu_cascade_ct = linfo->gpu_shadow_ct = 0;
linfo->cpu_cube_ct = linfo->cpu_cascade_ct = 0;
memset(linfo->light_ref, 0, sizeof(linfo->light_ref));
memset(linfo->shadow_cube_ref, 0, sizeof(linfo->shadow_cube_ref));
memset(linfo->shadow_cascade_ref, 0, sizeof(linfo->shadow_cascade_ref));
{
psl->shadow_cube_store_pass = DRW_pass_create("Shadow Storage Pass", DRW_STATE_WRITE_COLOR);
2017-11-20 14:28:24 +11:00
DRWShadingGroup *grp = DRW_shgroup_create(
e_data.shadow_store_cube_sh[linfo->shadow_method], psl->shadow_cube_store_pass);
DRW_shgroup_uniform_buffer(grp, "shadowTexture", &sldata->shadow_cube_blur);
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_float(grp, "shadowFilterSize", &linfo->filter_size, 1);
DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
}
{
psl->shadow_cascade_store_pass = DRW_pass_create("Shadow Cascade Storage Pass", DRW_STATE_WRITE_COLOR);
2017-11-20 14:28:24 +11:00
DRWShadingGroup *grp = DRW_shgroup_create(
e_data.shadow_store_cascade_sh[linfo->shadow_method], psl->shadow_cascade_store_pass);
DRW_shgroup_uniform_buffer(grp, "shadowTexture", &sldata->shadow_cascade_blur);
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_int(grp, "cascadeId", &linfo->current_shadow_cascade, 1);
DRW_shgroup_uniform_float(grp, "shadowFilterSize", &linfo->filter_size, 1);
DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
}
{
psl->shadow_cube_copy_pass = DRW_pass_create("Shadow Copy Pass", DRW_STATE_WRITE_COLOR);
2017-11-20 14:28:24 +11:00
DRWShadingGroup *grp = DRW_shgroup_create(
e_data.shadow_copy_cube_sh[linfo->shadow_method], psl->shadow_cube_copy_pass);
DRW_shgroup_uniform_buffer(grp, "shadowTexture", &sldata->shadow_cube_target);
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_float(grp, "shadowFilterSize", &linfo->filter_size, 1);
DRW_shgroup_uniform_int(grp, "faceId", &linfo->current_shadow_face, 1);
DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
}
{
psl->shadow_cascade_copy_pass = DRW_pass_create("Shadow Cascade Copy Pass", DRW_STATE_WRITE_COLOR);
2017-11-20 14:28:24 +11:00
DRWShadingGroup *grp = DRW_shgroup_create(
e_data.shadow_copy_cascade_sh[linfo->shadow_method], psl->shadow_cascade_copy_pass);
DRW_shgroup_uniform_buffer(grp, "shadowTexture", &sldata->shadow_cascade_target);
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_float(grp, "shadowFilterSize", &linfo->filter_size, 1);
DRW_shgroup_uniform_int(grp, "cascadeId", &linfo->current_shadow_cascade, 1);
DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
}
{
2017-11-20 14:28:24 +11:00
psl->shadow_cube_pass = DRW_pass_create(
"Shadow Cube Pass",
DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS);
}
{
2017-11-20 14:28:24 +11:00
psl->shadow_cascade_pass = DRW_pass_create(
"Shadow Cascade Pass",
DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS);
}
/* Reset shadow casters list */
BLI_freelistN(&sldata->shadow_casters);
}
void EEVEE_lights_cache_add(EEVEE_SceneLayerData *sldata, Object *ob)
{
EEVEE_LampsInfo *linfo = sldata->lamps;
/* Step 1 find all lamps in the scene and setup them */
if (linfo->num_light >= MAX_LIGHT) {
printf("Too much lamps in the scene !!!\n");
linfo->num_light = MAX_LIGHT - 1;
}
else {
Lamp *la = (Lamp *)ob->data;
EEVEE_LampEngineData *led = EEVEE_lamp_data_get(ob);
if ((ob->deg_update_flag & DEG_RUNTIME_DATA_UPDATE) != 0) {
led->need_update = true;
}
MEM_SAFE_FREE(led->storage);
if (la->mode & (LA_SHAD_BUF | LA_SHAD_RAY)) {
2017-10-07 15:57:14 +11:00
if (la->type == LA_SUN) {
int sh_nbr = 1; /* TODO : MSM */
int cascade_nbr = MAX_CASCADE_NUM; /* TODO : Custom cascade number */
if ((linfo->gpu_cascade_ct + sh_nbr) <= MAX_SHADOW_CASCADE) {
/* Save Light object. */
linfo->shadow_cascade_ref[linfo->cpu_cascade_ct] = ob;
/* Create storage and store indices. */
2017-11-20 14:28:24 +11:00
EEVEE_ShadowCascadeData *data = MEM_mallocN(
sizeof(EEVEE_ShadowCascadeData), "EEVEE_ShadowCascadeData");
data->shadow_id = linfo->gpu_shadow_ct;
data->cascade_id = linfo->gpu_cascade_ct;
data->layer_id = linfo->num_layer;
led->storage = data;
/* Increment indices. */
linfo->gpu_shadow_ct += 1;
linfo->gpu_cascade_ct += sh_nbr;
linfo->num_layer += sh_nbr * cascade_nbr;
linfo->cpu_cascade_ct += 1;
}
}
else if (la->type == LA_SPOT || la->type == LA_LOCAL || la->type == LA_AREA) {
int sh_nbr = 1; /* TODO : MSM */
if ((linfo->gpu_cube_ct + sh_nbr) <= MAX_SHADOW_CUBE) {
/* Save Light object. */
linfo->shadow_cube_ref[linfo->cpu_cube_ct] = ob;
/* Create storage and store indices. */
2017-11-20 14:28:24 +11:00
EEVEE_ShadowCubeData *data = MEM_mallocN(
sizeof(EEVEE_ShadowCubeData), "EEVEE_ShadowCubeData");
data->shadow_id = linfo->gpu_shadow_ct;
data->cube_id = linfo->gpu_cube_ct;
data->layer_id = linfo->num_layer;
led->storage = data;
/* Increment indices. */
linfo->gpu_shadow_ct += 1;
linfo->gpu_cube_ct += sh_nbr;
linfo->num_layer += sh_nbr;
linfo->cpu_cube_ct += 1;
}
}
}
/* Default light without shadows */
if (!led->storage) {
led->storage = MEM_mallocN(sizeof(EEVEE_LightData), "EEVEE_LightData");
((EEVEE_LightData *)led->storage)->shadow_id = -1;
}
((EEVEE_LightData *)led->storage)->light_id = linfo->num_light;
linfo->light_ref[linfo->num_light] = ob;
linfo->num_light++;
}
}
/* Add a shadow caster to the shadowpasses */
2017-11-20 14:28:24 +11:00
void EEVEE_lights_cache_shcaster_add(
EEVEE_SceneLayerData *sldata, EEVEE_PassList *psl, struct Gwn_Batch *geom, float (*obmat)[4])
{
DRWShadingGroup *grp = DRW_shgroup_instance_create(e_data.shadow_sh, psl->shadow_cube_pass, geom);
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_mat4(grp, "ShadowModelMatrix", (float *)obmat);
DRW_shgroup_set_instance_count(grp, 6);
grp = DRW_shgroup_instance_create(e_data.shadow_sh, psl->shadow_cascade_pass, geom);
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_mat4(grp, "ShadowModelMatrix", (float *)obmat);
DRW_shgroup_set_instance_count(grp, MAX_CASCADE_NUM);
}
void EEVEE_lights_cache_shcaster_material_add(
2017-09-12 15:06:07 +02:00
EEVEE_SceneLayerData *sldata, EEVEE_PassList *psl, struct GPUMaterial *gpumat,
struct Gwn_Batch *geom, struct Object *ob, float (*obmat)[4], float *alpha_threshold)
{
2017-09-12 15:06:07 +02:00
DRWShadingGroup *grp = DRW_shgroup_material_instance_create(gpumat, psl->shadow_cube_pass, geom, ob);
if (grp == NULL) return;
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_mat4(grp, "ShadowModelMatrix", (float *)obmat);
if (alpha_threshold != NULL)
DRW_shgroup_uniform_float(grp, "alphaThreshold", alpha_threshold, 1);
DRW_shgroup_set_instance_count(grp, 6);
2017-09-12 15:06:07 +02:00
grp = DRW_shgroup_material_instance_create(gpumat, psl->shadow_cascade_pass, geom, ob);
DRW_shgroup_uniform_block(grp, "shadow_render_block", sldata->shadow_render_ubo);
DRW_shgroup_uniform_mat4(grp, "ShadowModelMatrix", (float *)obmat);
if (alpha_threshold != NULL)
DRW_shgroup_uniform_float(grp, "alphaThreshold", alpha_threshold, 1);
DRW_shgroup_set_instance_count(grp, MAX_CASCADE_NUM);
}
void EEVEE_lights_cache_finish(EEVEE_SceneLayerData *sldata)
{
EEVEE_LampsInfo *linfo = sldata->lamps;
2017-10-28 14:08:30 +02:00
DRWTextureFormat shadow_pool_format = DRW_TEX_R_32;
/* Setup enough layers. */
/* Free textures if number mismatch. */
if (linfo->num_layer != linfo->cache_num_layer) {
DRW_TEXTURE_FREE_SAFE(sldata->shadow_pool);
linfo->cache_num_layer = linfo->num_layer;
linfo->update_flag |= LIGHT_UPDATE_SHADOW_CUBE;
}
switch (linfo->shadow_method) {
case SHADOW_ESM: shadow_pool_format = ((linfo->shadow_high_bitdepth) ? DRW_TEX_R_32 : DRW_TEX_R_16); break;
case SHADOW_VSM: shadow_pool_format = ((linfo->shadow_high_bitdepth) ? DRW_TEX_RG_32 : DRW_TEX_RG_16); break;
default:
BLI_assert(!"Incorrect Shadow Method");
break;
}
if (!sldata->shadow_cube_target) {
/* TODO render everything on the same 2d render target using clip planes and no Geom Shader. */
/* Cubemaps */
2017-11-20 14:28:24 +11:00
sldata->shadow_cube_target = DRW_texture_create_cube(
linfo->shadow_cube_target_size, DRW_TEX_DEPTH_24, 0, NULL);
sldata->shadow_cube_blur = DRW_texture_create_cube(
linfo->shadow_cube_target_size, shadow_pool_format, DRW_TEX_FILTER, NULL);
}
if (!sldata->shadow_cascade_target) {
/* CSM */
sldata->shadow_cascade_target = DRW_texture_create_2D_array(
linfo->shadow_size, linfo->shadow_size, MAX_CASCADE_NUM, DRW_TEX_DEPTH_24, 0, NULL);
sldata->shadow_cascade_blur = DRW_texture_create_2D_array(
linfo->shadow_size, linfo->shadow_size, MAX_CASCADE_NUM, shadow_pool_format, DRW_TEX_FILTER, NULL);
}
/* Initialize Textures Array first so DRW_framebuffer_init just bind them. */
if (!sldata->shadow_pool) {
/* All shadows fit in this array */
sldata->shadow_pool = DRW_texture_create_2D_array(
linfo->shadow_size, linfo->shadow_size, max_ff(1, linfo->num_layer),
shadow_pool_format, DRW_TEX_FILTER, NULL);
}
/* Render FB */
DRWFboTexture tex_cascade = {&sldata->shadow_cube_target, DRW_TEX_DEPTH_24, 0};
DRW_framebuffer_init(&sldata->shadow_target_fb, &draw_engine_eevee_type,
linfo->shadow_size, linfo->shadow_size,
&tex_cascade, 1);
/* Storage FB */
DRWFboTexture tex_pool = {&sldata->shadow_pool, shadow_pool_format, DRW_TEX_FILTER};
DRW_framebuffer_init(&sldata->shadow_store_fb, &draw_engine_eevee_type,
linfo->shadow_size, linfo->shadow_size,
&tex_pool, 1);
/* Restore */
DRW_framebuffer_texture_detach(sldata->shadow_cube_target);
/* Update Lamps UBOs. */
EEVEE_lights_update(sldata);
}
/* Update buffer with lamp data */
static void eevee_light_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE_LampEngineData *led)
{
/* TODO only update if data changes */
EEVEE_LightData *evld = led->storage;
EEVEE_Light *evli = linfo->light_data + evld->light_id;
Lamp *la = (Lamp *)ob->data;
float mat[4][4], scale[3], power;
/* Position */
copy_v3_v3(evli->position, ob->obmat[3]);
/* Color */
2017-05-29 12:37:34 +02:00
copy_v3_v3(evli->color, &la->r);
/* Influence Radius */
evli->dist = la->dist;
/* Vectors */
normalize_m4_m4_ex(mat, ob->obmat, scale);
copy_v3_v3(evli->forwardvec, mat[2]);
normalize_v3(evli->forwardvec);
negate_v3(evli->forwardvec);
copy_v3_v3(evli->rightvec, mat[0]);
normalize_v3(evli->rightvec);
copy_v3_v3(evli->upvec, mat[1]);
normalize_v3(evli->upvec);
/* Spot size & blend */
if (la->type == LA_SPOT) {
evli->sizex = scale[0] / scale[2];
evli->sizey = scale[1] / scale[2];
evli->spotsize = cosf(la->spotsize * 0.5f);
evli->spotblend = (1.0f - evli->spotsize) * la->spotblend;
2017-04-27 22:27:41 +02:00
evli->radius = max_ff(0.001f, la->area_size);
}
else if (la->type == LA_AREA) {
2017-04-27 22:27:41 +02:00
evli->sizex = max_ff(0.0001f, la->area_size * scale[0] * 0.5f);
if (la->area_shape == LA_AREA_RECT) {
2017-04-27 22:27:41 +02:00
evli->sizey = max_ff(0.0001f, la->area_sizey * scale[1] * 0.5f);
}
else {
2017-04-27 22:27:41 +02:00
evli->sizey = max_ff(0.0001f, la->area_size * scale[1] * 0.5f);
}
}
else {
2017-04-27 22:27:41 +02:00
evli->radius = max_ff(0.001f, la->area_size);
}
/* Make illumination power constant */
if (la->type == LA_AREA) {
2017-10-07 15:57:14 +11:00
power = 1.0f / (evli->sizex * evli->sizey * 4.0f * M_PI) * /* 1/(w*h*Pi) */
80.0f; /* XXX : Empirical, Fit cycles power */
}
else if (la->type == LA_SPOT || la->type == LA_LOCAL) {
2017-10-07 15:57:14 +11:00
power = 1.0f / (4.0f * evli->radius * evli->radius * M_PI * M_PI) * /* 1/(4*r²*Pi²) */
M_PI * M_PI * M_PI * 10.0; /* XXX : Empirical, Fit cycles power */
/* for point lights (a.k.a radius == 0.0) */
// power = M_PI * M_PI * 0.78; /* XXX : Empirical, Fit cycles power */
}
else {
power = 1.0f;
}
mul_v3_fl(evli->color, power * la->energy);
/* Lamp Type */
evli->lamptype = (float)la->type;
/* No shadow by default */
evli->shadowid = -1.0f;
}
static void eevee_shadow_cube_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE_LampEngineData *led)
{
EEVEE_ShadowCubeData *sh_data = (EEVEE_ShadowCubeData *)led->storage;
EEVEE_Light *evli = linfo->light_data + sh_data->light_id;
EEVEE_Shadow *ubo_data = linfo->shadow_data + sh_data->shadow_id;
EEVEE_ShadowCube *cube_data = linfo->shadow_cube_data + sh_data->cube_id;
Lamp *la = (Lamp *)ob->data;
int sh_nbr = 1; /* TODO: MSM */
for (int i = 0; i < sh_nbr; ++i) {
/* TODO : choose MSM sample point here. */
copy_v3_v3(cube_data->position, ob->obmat[3]);
}
ubo_data->bias = 0.05f * la->bias;
ubo_data->near = la->clipsta;
ubo_data->far = la->clipend;
ubo_data->exp = (linfo->shadow_method == SHADOW_VSM) ? la->bleedbias : la->bleedexp;
evli->shadowid = (float)(sh_data->shadow_id);
ubo_data->shadow_start = (float)(sh_data->layer_id);
ubo_data->data_start = (float)(sh_data->cube_id);
ubo_data->multi_shadow_count = (float)(sh_nbr);
ubo_data->contact_dist = (la->mode & LA_SHAD_CONTACT) ? la->contact_dist : 0.0f;
ubo_data->contact_bias = 0.05f * la->contact_bias;
ubo_data->contact_spread = la->contact_spread;
ubo_data->contact_thickness = la->contact_thickness;
}
#define LERP(t, a, b) ((a) + (t) * ((b) - (a)))
static void frustum_min_bounding_sphere(const float corners[8][4], float r_center[3], float *r_radius)
{
#if 0 /* Simple solution but waist too much space. */
float minvec[3], maxvec[3];
/* compute the bounding box */
INIT_MINMAX(minvec, maxvec);
2017-10-07 15:57:14 +11:00
for (int i = 0; i < 8; ++i) {
minmax_v3v3_v3(minvec, maxvec, corners[i]);
}
/* compute the bounding sphere of this box */
r_radius = len_v3v3(minvec, maxvec) * 0.5f;
add_v3_v3v3(r_center, minvec, maxvec);
mul_v3_fl(r_center, 0.5f);
#else
/* Make the bouding sphere always centered on the front diagonal */
add_v3_v3v3(r_center, corners[4], corners[7]);
mul_v3_fl(r_center, 0.5f);
*r_radius = len_v3v3(corners[0], r_center);
/* Search the largest distance between the sphere center
* and the front plane corners. */
for (int i = 0; i < 4; ++i) {
2017-10-07 15:57:14 +11:00
float rad = len_v3v3(corners[4 + i], r_center);
if (rad > *r_radius) {
*r_radius = rad;
}
}
#endif
}
static void eevee_shadow_cascade_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE_LampEngineData *led)
{
Lamp *la = (Lamp *)ob->data;
/* Camera Matrices */
float persmat[4][4], persinv[4][4];
float viewprojmat[4][4], projinv[4][4];
float view_near, view_far;
float near_v[4] = {0.0f, 0.0f, -1.0f, 1.0f};
float far_v[4] = {0.0f, 0.0f, 1.0f, 1.0f};
bool is_persp = DRW_viewport_is_persp_get();
DRW_viewport_matrix_get(persmat, DRW_MAT_PERS);
invert_m4_m4(persinv, persmat);
/* FIXME : Get near / far from Draw manager? */
DRW_viewport_matrix_get(viewprojmat, DRW_MAT_WIN);
invert_m4_m4(projinv, viewprojmat);
mul_m4_v4(projinv, near_v);
mul_m4_v4(projinv, far_v);
view_near = near_v[2];
view_far = far_v[2]; /* TODO: Should be a shadow parameter */
if (is_persp) {
view_near /= near_v[3];
view_far /= far_v[3];
}
/* Lamps Matrices */
float viewmat[4][4], projmat[4][4];
int sh_nbr = 1; /* TODO : MSM */
int cascade_nbr = la->cascade_count;
EEVEE_ShadowCascadeData *sh_data = (EEVEE_ShadowCascadeData *)led->storage;
EEVEE_Light *evli = linfo->light_data + sh_data->light_id;
EEVEE_Shadow *ubo_data = linfo->shadow_data + sh_data->shadow_id;
EEVEE_ShadowCascade *cascade_data = linfo->shadow_cascade_data + sh_data->cascade_id;
/* The technique consists into splitting
* the view frustum into several sub-frustum
* that are individually receiving one shadow map */
float csm_start, csm_end;
if (is_persp) {
csm_start = view_near;
csm_end = max_ff(view_far, -la->cascade_max_dist);
/* Avoid artifacts */
csm_end = min_ff(view_near, csm_end);
}
else {
csm_start = -view_far;
csm_end = view_far;
}
/* init near/far */
for (int c = 0; c < MAX_CASCADE_NUM; ++c) {
cascade_data->split_start[c] = csm_end;
cascade_data->split_end[c] = csm_end;
}
/* Compute split planes */
float splits_start_ndc[MAX_CASCADE_NUM];
float splits_end_ndc[MAX_CASCADE_NUM];
{
/* Nearest plane */
float p[4] = {1.0f, 1.0f, csm_start, 1.0f};
/* TODO: we don't need full m4 multiply here */
mul_m4_v4(viewprojmat, p);
splits_start_ndc[0] = p[2];
if (is_persp) {
splits_start_ndc[0] /= p[3];
}
}
{
/* Farthest plane */
float p[4] = {1.0f, 1.0f, csm_end, 1.0f};
/* TODO: we don't need full m4 multiply here */
mul_m4_v4(viewprojmat, p);
splits_end_ndc[cascade_nbr - 1] = p[2];
if (is_persp) {
splits_end_ndc[cascade_nbr - 1] /= p[3];
}
}
cascade_data->split_start[0] = csm_start;
cascade_data->split_end[cascade_nbr - 1] = csm_end;
for (int c = 1; c < cascade_nbr; ++c) {
/* View Space */
float linear_split = LERP(((float)(c) / (float)cascade_nbr), csm_start, csm_end);
float exp_split = csm_start * powf(csm_end / csm_start, (float)(c) / (float)cascade_nbr);
if (is_persp) {
cascade_data->split_start[c] = LERP(la->cascade_exponent, linear_split, exp_split);
}
else {
cascade_data->split_start[c] = linear_split;
}
2017-10-07 15:57:14 +11:00
cascade_data->split_end[c - 1] = cascade_data->split_start[c];
/* Add some overlap for smooth transition */
2017-10-07 15:57:14 +11:00
cascade_data->split_start[c] = LERP(la->cascade_fade, cascade_data->split_end[c - 1],
(c > 1) ? cascade_data->split_end[c - 2] : cascade_data->split_start[0]);
/* NDC Space */
{
float p[4] = {1.0f, 1.0f, cascade_data->split_start[c], 1.0f};
/* TODO: we don't need full m4 multiply here */
mul_m4_v4(viewprojmat, p);
splits_start_ndc[c] = p[2];
if (is_persp) {
splits_start_ndc[c] /= p[3];
}
}
{
2017-10-07 15:57:14 +11:00
float p[4] = {1.0f, 1.0f, cascade_data->split_end[c - 1], 1.0f};
/* TODO: we don't need full m4 multiply here */
mul_m4_v4(viewprojmat, p);
2017-10-07 15:57:14 +11:00
splits_end_ndc[c - 1] = p[2];
if (is_persp) {
2017-10-07 15:57:14 +11:00
splits_end_ndc[c - 1] /= p[3];
}
}
}
/* Set last cascade split fade distance into the first split_start. */
2017-10-07 15:57:14 +11:00
float prev_split = (cascade_nbr > 1) ? cascade_data->split_end[cascade_nbr - 2] : cascade_data->split_start[0];
cascade_data->split_start[0] = LERP(la->cascade_fade, cascade_data->split_end[cascade_nbr - 1], prev_split);
/* For each cascade */
for (int c = 0; c < cascade_nbr; ++c) {
/* Given 8 frustum corners */
float corners[8][4] = {
/* Near Cap */
{-1.0f, -1.0f, splits_start_ndc[c], 1.0f},
{ 1.0f, -1.0f, splits_start_ndc[c], 1.0f},
{-1.0f, 1.0f, splits_start_ndc[c], 1.0f},
{ 1.0f, 1.0f, splits_start_ndc[c], 1.0f},
/* Far Cap */
{-1.0f, -1.0f, splits_end_ndc[c], 1.0f},
{ 1.0f, -1.0f, splits_end_ndc[c], 1.0f},
{-1.0f, 1.0f, splits_end_ndc[c], 1.0f},
{ 1.0f, 1.0f, splits_end_ndc[c], 1.0f}
};
/* Transform them into world space */
2017-10-07 15:57:14 +11:00
for (int i = 0; i < 8; ++i) {
mul_m4_v4(persinv, corners[i]);
mul_v3_fl(corners[i], 1.0f / corners[i][3]);
corners[i][3] = 1.0f;
}
/* Project them into light space */
invert_m4_m4(viewmat, ob->obmat);
normalize_v3(viewmat[0]);
normalize_v3(viewmat[1]);
normalize_v3(viewmat[2]);
2017-10-07 15:57:14 +11:00
for (int i = 0; i < 8; ++i) {
mul_m4_v4(viewmat, corners[i]);
}
float center[3];
frustum_min_bounding_sphere(corners, center, &(sh_data->radius[c]));
/* Snap projection center to nearest texel to cancel shimmering. */
float shadow_origin[2], shadow_texco[2];
2017-11-20 14:28:24 +11:00
/* Light to texture space. */
mul_v2_v2fl(shadow_origin, center, linfo->shadow_size / (2.0f * sh_data->radius[c]));
/* Find the nearest texel. */
shadow_texco[0] = round(shadow_origin[0]);
shadow_texco[1] = round(shadow_origin[1]);
/* Compute offset. */
sub_v2_v2(shadow_texco, shadow_origin);
mul_v2_fl(shadow_texco, (2.0f * sh_data->radius[c]) / linfo->shadow_size); /* Texture to light space. */
/* Apply offset. */
add_v2_v2(center, shadow_texco);
/* Expand the projection to cover frustum range */
orthographic_m4(projmat,
center[0] - sh_data->radius[c],
center[0] + sh_data->radius[c],
center[1] - sh_data->radius[c],
center[1] + sh_data->radius[c],
la->clipsta, la->clipend);
mul_m4_m4m4(sh_data->viewprojmat[c], projmat, viewmat);
mul_m4_m4m4(cascade_data->shadowmat[c], texcomat, sh_data->viewprojmat[c]);
}
ubo_data->bias = 0.05f * la->bias;
ubo_data->near = la->clipsta;
ubo_data->far = la->clipend;
ubo_data->exp = (linfo->shadow_method == SHADOW_VSM) ? la->bleedbias : la->bleedexp;
evli->shadowid = (float)(sh_data->shadow_id);
ubo_data->shadow_start = (float)(sh_data->layer_id);
ubo_data->data_start = (float)(sh_data->cascade_id);
ubo_data->multi_shadow_count = (float)(sh_nbr);
ubo_data->contact_dist = (la->mode & LA_SHAD_CONTACT) ? la->contact_dist : 0.0f;
ubo_data->contact_bias = 0.05f * la->contact_bias;
ubo_data->contact_spread = la->contact_spread;
ubo_data->contact_thickness = la->contact_thickness;
}
/* Used for checking if object is inside the shadow volume. */
static bool cube_bbox_intersect(const float cube_center[3], float cube_half_dim, const BoundBox *bb, float (*obmat)[4])
{
float min[3], max[4], tmp[4][4];
unit_m4(tmp);
translate_m4(tmp, -cube_center[0], -cube_center[1], -cube_center[2]);
mul_m4_m4m4(tmp, tmp, obmat);
/* Just simple AABB intersection test in world space. */
INIT_MINMAX(min, max);
for (int i = 0; i < 8; ++i) {
float vec[3];
copy_v3_v3(vec, bb->vec[i]);
mul_m4_v3(tmp, vec);
minmax_v3v3_v3(min, max, vec);
}
2017-10-07 15:57:14 +11:00
if (MAX3(max[0], max[1], max[2]) < -cube_half_dim) return false;
if (MIN3(min[0], min[1], min[2]) > cube_half_dim) return false;
return true;
}
static ShadowCaster *search_object_in_list(ListBase *list, Object *ob)
{
for (ShadowCaster *ldata = list->first; ldata; ldata = ldata->next) {
if (ldata->ob == ob)
return ldata;
}
return NULL;
}
static void delete_pruned_shadowcaster(EEVEE_LampEngineData *led)
{
ShadowCaster *next;
for (ShadowCaster *ldata = led->shadow_caster_list.first; ldata; ldata = next) {
next = ldata->next;
if (ldata->prune == true) {
led->need_update = true;
BLI_freelinkN(&led->shadow_caster_list, ldata);
}
}
}
static void light_tag_shadow_update(Object *lamp, Object *ob)
{
Lamp *la = lamp->data;
EEVEE_LampEngineData *led = EEVEE_lamp_data_get(lamp);
bool is_inside_range = cube_bbox_intersect(lamp->obmat[3], la->clipend, BKE_object_boundbox_get(ob), ob->obmat);
ShadowCaster *ldata = search_object_in_list(&led->shadow_caster_list, ob);
if (is_inside_range) {
if (ldata == NULL) {
/* Object was not a shadow caster previously but is now. Add it. */
ldata = MEM_callocN(sizeof(ShadowCaster), "ShadowCaster");
ldata->ob = ob;
BLI_addtail(&led->shadow_caster_list, ldata);
led->need_update = true;
}
else {
EEVEE_ObjectEngineData *oedata = EEVEE_object_data_get(ob);
if (oedata->need_update) {
led->need_update = true;
}
}
ldata->prune = false;
}
else if (ldata != NULL) {
/* Object was a shadow caster previously and is not anymore. Remove it. */
led->need_update = true;
BLI_freelinkN(&led->shadow_caster_list, ldata);
}
}
static void eevee_lights_shcaster_updated(EEVEE_SceneLayerData *sldata, Object *ob)
{
Object *lamp;
EEVEE_LampsInfo *linfo = sldata->lamps;
/* Iterate over all shadow casting lamps to see if
* each of them needs update because of this object */
for (int i = 0; (lamp = linfo->shadow_cube_ref[i]) && (i < MAX_SHADOW_CUBE); i++) {
light_tag_shadow_update(lamp, ob);
}
}
void EEVEE_lights_update(EEVEE_SceneLayerData *sldata)
{
EEVEE_LampsInfo *linfo = sldata->lamps;
Object *ob;
int i;
/* Prune shadow casters to remove if object does not exists anymore (unprune them if object exists) */
Object *lamp;
for (i = 0; (lamp = linfo->shadow_cube_ref[i]) && (i < MAX_SHADOW_CUBE); i++) {
EEVEE_LampEngineData *led = EEVEE_lamp_data_get(lamp);
if ((linfo->update_flag & LIGHT_UPDATE_SHADOW_CUBE) != 0) {
led->need_update = true;
}
for (ShadowCaster *ldata = led->shadow_caster_list.first; ldata; ldata = ldata->next) {
ldata->prune = true;
}
}
for (LinkData *ldata = sldata->shadow_casters.first; ldata; ldata = ldata->next) {
eevee_lights_shcaster_updated(sldata, ldata->data);
}
for (i = 0; (ob = linfo->light_ref[i]) && (i < MAX_LIGHT); i++) {
EEVEE_LampEngineData *led = EEVEE_lamp_data_get(ob);
eevee_light_setup(ob, linfo, led);
}
for (i = 0; (ob = linfo->shadow_cube_ref[i]) && (i < MAX_SHADOW_CUBE); i++) {
EEVEE_LampEngineData *led = EEVEE_lamp_data_get(ob);
eevee_shadow_cube_setup(ob, linfo, led);
delete_pruned_shadowcaster(led);
}
}
/* this refresh lamps shadow buffers */
void EEVEE_draw_shadows(EEVEE_SceneLayerData *sldata, EEVEE_PassList *psl)
{
EEVEE_LampsInfo *linfo = sldata->lamps;
Object *ob;
int i;
float clear_col[4] = {FLT_MAX};
/* Cube Shadow Maps */
DRW_stats_group_start("Cube Shadow Maps");
DRW_framebuffer_texture_attach(sldata->shadow_target_fb, sldata->shadow_cube_target, 0, 0);
/* Render each shadow to one layer of the array */
for (i = 0; (ob = linfo->shadow_cube_ref[i]) && (i < MAX_SHADOW_CUBE); i++) {
EEVEE_LampEngineData *led = EEVEE_lamp_data_get(ob);
Lamp *la = (Lamp *)ob->data;
float cube_projmat[4][4];
perspective_m4(cube_projmat, -la->clipsta, la->clipsta, -la->clipsta, la->clipsta, la->clipsta, la->clipend);
if (led->need_update) {
EEVEE_ShadowRender *srd = &linfo->shadow_render_data;
2017-09-26 22:30:48 +02:00
EEVEE_ShadowCubeData *evscd = (EEVEE_ShadowCubeData *)led->storage;
srd->clip_near = la->clipsta;
srd->clip_far = la->clipend;
copy_v3_v3(srd->position, ob->obmat[3]);
2017-07-13 12:04:30 +02:00
for (int j = 0; j < 6; j++) {
float tmp[4][4];
unit_m4(tmp);
negate_v3_v3(tmp[3], ob->obmat[3]);
mul_m4_m4m4(srd->viewmat[j], cubefacemat[j], tmp);
mul_m4_m4m4(srd->shadowmat[j], cube_projmat, srd->viewmat[j]);
}
DRW_uniformbuffer_update(sldata->shadow_render_ubo, srd);
DRW_framebuffer_bind(sldata->shadow_target_fb);
DRW_framebuffer_clear(true, true, false, clear_col, 1.0f);
/* Render shadow cube */
DRW_draw_pass(psl->shadow_cube_pass);
/* 0.001f is arbitrary, but it should be relatively small so that filter size is not too big. */
float filter_texture_size = la->soft * 0.001f;
float filter_pixel_size = ceil(filter_texture_size / linfo->shadow_render_data.cube_texel_size);
linfo->filter_size = linfo->shadow_render_data.cube_texel_size * ((filter_pixel_size > 1.0f) ? 1.5f : 0.0f);
/* TODO: OPTI: Filter all faces in one/two draw call */
for (linfo->current_shadow_face = 0;
linfo->current_shadow_face < 6;
linfo->current_shadow_face++)
{
/* Copy using a small 3x3 box filter */
DRW_framebuffer_cubeface_attach(sldata->shadow_store_fb, sldata->shadow_cube_blur, 0, linfo->current_shadow_face, 0);
DRW_framebuffer_bind(sldata->shadow_store_fb);
DRW_draw_pass(psl->shadow_cube_copy_pass);
DRW_framebuffer_texture_detach(sldata->shadow_cube_blur);
}
/* Push it to shadowmap array */
/* Adjust constants if concentric samples change. */
const float max_filter_size = 7.5f;
const float previous_box_filter_size = 9.0f; /* Dunno why but that works. */
const int max_sample = 256;
if (filter_pixel_size > 2.0f) {
linfo->filter_size = linfo->shadow_render_data.cube_texel_size * max_filter_size * previous_box_filter_size;
filter_pixel_size = max_ff(0.0f, filter_pixel_size - 3.0f);
/* Compute number of concentric samples. Depends directly on filter size. */
float pix_size_sqr = filter_pixel_size * filter_pixel_size;
srd->shadow_samples_ct = min_ii(max_sample, 4 + 8 * (int)filter_pixel_size + 4 * (int)(pix_size_sqr));
}
else {
linfo->filter_size = 0.0f;
srd->shadow_samples_ct = 4;
}
srd->shadow_inv_samples_ct = 1.0f / (float)srd->shadow_samples_ct;
DRW_uniformbuffer_update(sldata->shadow_render_ubo, srd);
2017-09-26 22:30:48 +02:00
DRW_framebuffer_texture_layer_attach(sldata->shadow_store_fb, sldata->shadow_pool, 0, evscd->layer_id, 0);
DRW_framebuffer_bind(sldata->shadow_store_fb);
DRW_draw_pass(psl->shadow_cube_store_pass);
led->need_update = false;
}
}
linfo->update_flag &= ~LIGHT_UPDATE_SHADOW_CUBE;
DRW_framebuffer_texture_detach(sldata->shadow_cube_target);
DRW_stats_group_end();
/* Cascaded Shadow Maps */
DRW_stats_group_start("Cascaded Shadow Maps");
DRW_framebuffer_texture_attach(sldata->shadow_target_fb, sldata->shadow_cascade_target, 0, 0);
for (i = 0; (ob = linfo->shadow_cascade_ref[i]) && (i < MAX_SHADOW_CASCADE); i++) {
EEVEE_LampEngineData *led = EEVEE_lamp_data_get(ob);
Lamp *la = (Lamp *)ob->data;
EEVEE_ShadowCascadeData *evscd = (EEVEE_ShadowCascadeData *)led->storage;
EEVEE_ShadowRender *srd = &linfo->shadow_render_data;
eevee_shadow_cascade_setup(ob, linfo, led);
srd->clip_near = la->clipsta;
srd->clip_far = la->clipend;
for (int j = 0; j < la->cascade_count; ++j) {
copy_m4_m4(srd->shadowmat[j], evscd->viewprojmat[j]);
}
DRW_uniformbuffer_update(sldata->shadow_render_ubo, &linfo->shadow_render_data);
DRW_framebuffer_bind(sldata->shadow_target_fb);
DRW_framebuffer_clear(false, true, false, NULL, 1.0);
/* Render shadow cascades */
DRW_draw_pass(psl->shadow_cascade_pass);
/* TODO: OPTI: Filter all cascade in one/two draw call */
for (linfo->current_shadow_cascade = 0;
linfo->current_shadow_cascade < la->cascade_count;
++linfo->current_shadow_cascade)
{
/* 0.01f factor to convert to percentage */
float filter_texture_size = la->soft * 0.01f / evscd->radius[linfo->current_shadow_cascade];
float filter_pixel_size = ceil(linfo->shadow_size * filter_texture_size);
/* Copy using a small 3x3 box filter */
linfo->filter_size = linfo->shadow_render_data.stored_texel_size * ((filter_pixel_size > 1.0f) ? 1.0f : 0.0f);
2017-11-20 14:28:24 +11:00
DRW_framebuffer_texture_layer_attach(
sldata->shadow_store_fb, sldata->shadow_cascade_blur, 0, linfo->current_shadow_cascade, 0);
DRW_framebuffer_bind(sldata->shadow_store_fb);
DRW_draw_pass(psl->shadow_cascade_copy_pass);
DRW_framebuffer_texture_detach(sldata->shadow_cascade_blur);
/* Push it to shadowmap array and blur more */
/* Adjust constants if concentric samples change. */
const float max_filter_size = 7.5f;
const float previous_box_filter_size = 3.2f; /* Arbitrary: less banding */
const int max_sample = 256;
if (filter_pixel_size > 2.0f) {
linfo->filter_size = linfo->shadow_render_data.stored_texel_size * max_filter_size * previous_box_filter_size;
filter_pixel_size = max_ff(0.0f, filter_pixel_size - 3.0f);
/* Compute number of concentric samples. Depends directly on filter size. */
float pix_size_sqr = filter_pixel_size * filter_pixel_size;
srd->shadow_samples_ct = min_ii(max_sample, 4 + 8 * (int)filter_pixel_size + 4 * (int)(pix_size_sqr));
}
else {
linfo->filter_size = 0.0f;
srd->shadow_samples_ct = 4;
}
srd->shadow_inv_samples_ct = 1.0f / (float)srd->shadow_samples_ct;
DRW_uniformbuffer_update(sldata->shadow_render_ubo, &linfo->shadow_render_data);
int layer = evscd->layer_id + linfo->current_shadow_cascade;
DRW_framebuffer_texture_layer_attach(sldata->shadow_store_fb, sldata->shadow_pool, 0, layer, 0);
DRW_framebuffer_bind(sldata->shadow_store_fb);
DRW_draw_pass(psl->shadow_cascade_store_pass);
}
}
DRW_framebuffer_texture_detach(sldata->shadow_cascade_target);
DRW_stats_group_end();
DRW_uniformbuffer_update(sldata->light_ubo, &linfo->light_data);
DRW_uniformbuffer_update(sldata->shadow_ubo, &linfo->shadow_data); /* Update all data at once */
}
void EEVEE_lights_free(void)
{
DRW_SHADER_FREE_SAFE(e_data.shadow_sh);
for (int i = 0; i < SHADOW_METHOD_MAX; ++i) {
DRW_SHADER_FREE_SAFE(e_data.shadow_store_cube_sh[i]);
DRW_SHADER_FREE_SAFE(e_data.shadow_store_cascade_sh[i]);
DRW_SHADER_FREE_SAFE(e_data.shadow_copy_cube_sh[i]);
DRW_SHADER_FREE_SAFE(e_data.shadow_copy_cascade_sh[i]);
}
}