diff options
author | Clément Foucault <foucault.clem@gmail.com> | 2019-08-22 17:04:25 +0300 |
---|---|---|
committer | Clément Foucault <foucault.clem@gmail.com> | 2019-09-05 18:37:50 +0300 |
commit | d8aaf25c23fa10ee121dc4fdd1cafe544bcca355 (patch) | |
tree | b5a16157beac22e4b474c699c1ce25f3d8c5be51 /source/blender/draw/engines/eevee/eevee_shadows_cascade.c | |
parent | ca58936f2ff2b14a649722be20d98f8fa35831ff (diff) |
Eevee: Shadow map refactor
Reviewed By: brecht
Differential Revision: http://developer.blender.org/D5659
Diffstat (limited to 'source/blender/draw/engines/eevee/eevee_shadows_cascade.c')
-rw-r--r-- | source/blender/draw/engines/eevee/eevee_shadows_cascade.c | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/source/blender/draw/engines/eevee/eevee_shadows_cascade.c b/source/blender/draw/engines/eevee/eevee_shadows_cascade.c new file mode 100644 index 00000000000..d5e038f716c --- /dev/null +++ b/source/blender/draw/engines/eevee/eevee_shadows_cascade.c @@ -0,0 +1,439 @@ +/* + * 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. + * + * Copyright 2019, Blender Foundation. + */ + +/** \file + * \ingroup EEVEE + */ + +#include "BLI_rect.h" +#include "BLI_sys_types.h" /* bool */ + +#include "BKE_object.h" + +#include "eevee_private.h" + +#include "BLI_rand.h" /* needs to be after for some reason. */ + +void EEVEE_shadows_cascade_add(EEVEE_LightsInfo *linfo, EEVEE_Light *evli, Object *ob) +{ + if (linfo->cascade_len >= MAX_SHADOW_CASCADE) { + return; + } + + const Light *la = (Light *)ob->data; + EEVEE_Shadow *sh_data = linfo->shadow_data + linfo->shadow_len; + EEVEE_ShadowCascade *csm_data = linfo->shadow_cascade_data + linfo->cascade_len; + EEVEE_ShadowCascadeRender *csm_render = linfo->shadow_cascade_render + linfo->cascade_len; + + sh_data->bias = max_ff(la->bias * 0.00002f, 0.0f); + eevee_contact_shadow_setup(la, sh_data); + + linfo->shadow_cascade_light_indices[linfo->cascade_len] = linfo->num_light; + evli->shadow_id = linfo->shadow_len++; + sh_data->type_data_id = linfo->cascade_len++; + csm_data->tex_id = linfo->num_cascade_layer; + csm_render->cascade_fade = la->cascade_fade; + csm_render->cascade_count = la->cascade_count; + csm_render->cascade_exponent = la->cascade_exponent; + csm_render->cascade_max_dist = la->cascade_max_dist; + + linfo->num_cascade_layer += la->cascade_count; +} + +static void shadow_cascade_random_matrix_set(float mat[4][4], float radius, int sample_ofs) +{ + float jitter[3]; +#ifndef DEBUG_SHADOW_DISTRIBUTION + EEVEE_sample_ellipse(sample_ofs, mat[0], mat[1], radius, radius, jitter); +#else + for (int i = 0; i <= sample_ofs; ++i) { + EEVEE_sample_ellipse(i, mat[0], mat[1], radius, radius, jitter); + float p[3]; + add_v3_v3v3(p, jitter, mat[2]); + DRW_debug_sphere(p, 0.01f, (float[4]){1.0f, (sample_ofs == i) ? 1.0f : 0.0f, 0.0f, 1.0f}); + } +#endif + add_v3_v3(mat[2], jitter); + orthogonalize_m4(mat, 2); +} + +static double round_to_digits(double value, int digits) +{ + double factor = pow(10.0, digits - ceil(log10(fabs(value)))); + return round(value * factor) / factor; +} + +static void frustum_min_bounding_sphere(const float corners[8][3], + float r_center[3], + float *r_radius) +{ +#if 0 /* Simple solution but waste too much space. */ + float minvec[3], maxvec[3]; + + /* compute the bounding box */ + INIT_MINMAX(minvec, maxvec); + 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 + /* Find averaged center. */ + zero_v3(r_center); + for (int i = 0; i < 8; ++i) { + add_v3_v3(r_center, corners[i]); + } + mul_v3_fl(r_center, 1.0f / 8.0f); + + /* Search the largest distance from the sphere center. */ + *r_radius = 0.0f; + for (int i = 0; i < 8; ++i) { + float rad = len_squared_v3v3(corners[i], r_center); + if (rad > *r_radius) { + *r_radius = rad; + } + } + + /* TODO try to reduce the radius further by moving the center. + * Remember we need a __stable__ solution! */ + + /* Try to reduce float imprecision leading to shimmering. */ + *r_radius = (float)round_to_digits(sqrtf(*r_radius), 3); +#endif +} + +BLI_INLINE float lerp(float t, float a, float b) +{ + return ((a) + (t) * ((b) - (a))); +} + +static void eevee_shadow_cascade_setup(EEVEE_LightsInfo *linfo, + EEVEE_Light *evli, + DRWView *view, + float view_near, + float view_far, + int sample_ofs) +{ + EEVEE_Shadow *shdw_data = linfo->shadow_data + (int)evli->shadow_id; + EEVEE_ShadowCascade *csm_data = linfo->shadow_cascade_data + (int)shdw_data->type_data_id; + EEVEE_ShadowCascadeRender *csm_render = linfo->shadow_cascade_render + + (int)shdw_data->type_data_id; + int cascade_nbr = csm_render->cascade_count; + float cascade_fade = csm_render->cascade_fade; + float cascade_max_dist = csm_render->cascade_max_dist; + float cascade_exponent = csm_render->cascade_exponent; + + float jitter_ofs[2]; + double ht_point[2]; + double ht_offset[2] = {0.0, 0.0}; + uint ht_primes[2] = {2, 3}; + + BLI_halton_2d(ht_primes, ht_offset, sample_ofs, ht_point); + + /* Not really sure why we need 4.0 factor here. */ + jitter_ofs[0] = (ht_point[0] * 2.0 - 1.0) * 4.0 / linfo->shadow_cascade_size; + jitter_ofs[1] = (ht_point[1] * 2.0 - 1.0) * 4.0 / linfo->shadow_cascade_size; + + /* Camera Matrices */ + float persinv[4][4], vp_projmat[4][4]; + DRW_view_persmat_get(view, persinv, true); + DRW_view_winmat_get(view, vp_projmat, false); + bool is_persp = DRW_view_is_persp_get(view); + + /* obmat = Object Space > World Space */ + /* viewmat = World Space > View Space */ + float(*viewmat)[4] = csm_render->viewmat; + eevee_light_matrix_get(evli, viewmat); + /* At this point, viewmat == normalize_m4(obmat) */ + + if (linfo->soft_shadows) { + shadow_cascade_random_matrix_set(viewmat, evli->radius, sample_ofs); + } + + copy_m4_m4(csm_render->viewinv, viewmat); + invert_m4(viewmat); + + copy_v3_v3(csm_data->shadow_vec, csm_render->viewinv[2]); + + /* Compute near and far value based on all shadow casters cumulated AABBs. */ + float sh_near = -1.0e30f, sh_far = 1.0e30f; + BoundBox shcaster_bounds; + BKE_boundbox_init_from_minmax( + &shcaster_bounds, linfo->shcaster_aabb.min, linfo->shcaster_aabb.max); +#ifdef DEBUG_CSM + float dbg_col1[4] = {1.0f, 0.5f, 0.6f, 1.0f}; + DRW_debug_bbox(&shcaster_bounds, dbg_col1); +#endif + for (int i = 0; i < 8; i++) { + mul_m4_v3(viewmat, shcaster_bounds.vec[i]); + sh_near = max_ff(sh_near, shcaster_bounds.vec[i][2]); + sh_far = min_ff(sh_far, shcaster_bounds.vec[i][2]); + } +#ifdef DEBUG_CSM + float dbg_col2[4] = {0.5f, 1.0f, 0.6f, 1.0f}; + float pts[2][3] = {{0.0, 0.0, sh_near}, {0.0, 0.0, sh_far}}; + mul_m4_v3(csm_render->viewinv, pts[0]); + mul_m4_v3(csm_render->viewinv, pts[1]); + DRW_debug_sphere(pts[0], 1.0f, dbg_col1); + DRW_debug_sphere(pts[1], 1.0f, dbg_col2); +#endif + /* The rest of the function is assuming inverted Z. */ + /* Add a little bias to avoid invalid matrices. */ + sh_far = -(sh_far - 1e-3); + sh_near = -sh_near; + + /* 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, -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) { + csm_data->split_start[c] = csm_end; + csm_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(vp_projmat, 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(vp_projmat, p); + splits_end_ndc[cascade_nbr - 1] = p[2]; + if (is_persp) { + splits_end_ndc[cascade_nbr - 1] /= p[3]; + } + } + + csm_data->split_start[0] = csm_start; + csm_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) { + csm_data->split_start[c] = lerp(cascade_exponent, linear_split, exp_split); + } + else { + csm_data->split_start[c] = linear_split; + } + csm_data->split_end[c - 1] = csm_data->split_start[c]; + + /* Add some overlap for smooth transition */ + csm_data->split_start[c] = lerp(cascade_fade, + csm_data->split_end[c - 1], + (c > 1) ? csm_data->split_end[c - 2] : + csm_data->split_start[0]); + + /* NDC Space */ + { + float p[4] = {1.0f, 1.0f, csm_data->split_start[c], 1.0f}; + /* TODO: we don't need full m4 multiply here */ + mul_m4_v4(vp_projmat, p); + splits_start_ndc[c] = p[2]; + + if (is_persp) { + splits_start_ndc[c] /= p[3]; + } + } + + { + float p[4] = {1.0f, 1.0f, csm_data->split_end[c - 1], 1.0f}; + /* TODO: we don't need full m4 multiply here */ + mul_m4_v4(vp_projmat, p); + splits_end_ndc[c - 1] = p[2]; + + if (is_persp) { + splits_end_ndc[c - 1] /= p[3]; + } + } + } + + /* Set last cascade split fade distance into the first split_start. */ + float prev_split = (cascade_nbr > 1) ? csm_data->split_end[cascade_nbr - 2] : + csm_data->split_start[0]; + csm_data->split_start[0] = lerp(cascade_fade, csm_data->split_end[cascade_nbr - 1], prev_split); + + /* For each cascade */ + for (int c = 0; c < cascade_nbr; ++c) { + float(*projmat)[4] = csm_render->projmat[c]; + /* Given 8 frustum corners */ + float corners[8][3] = { + /* Near Cap */ + {1.0f, -1.0f, splits_start_ndc[c]}, + {-1.0f, -1.0f, splits_start_ndc[c]}, + {-1.0f, 1.0f, splits_start_ndc[c]}, + {1.0f, 1.0f, splits_start_ndc[c]}, + /* Far Cap */ + {1.0f, -1.0f, splits_end_ndc[c]}, + {-1.0f, -1.0f, splits_end_ndc[c]}, + {-1.0f, 1.0f, splits_end_ndc[c]}, + {1.0f, 1.0f, splits_end_ndc[c]}, + }; + + /* Transform them into world space */ + for (int i = 0; i < 8; ++i) { + mul_project_m4_v3(persinv, corners[i]); + } + + float center[3]; + frustum_min_bounding_sphere(corners, center, &(csm_render->radius[c])); + +#ifdef DEBUG_CSM + float dbg_col[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + if (c < 3) { + dbg_col[c] = 1.0f; + } + DRW_debug_bbox((BoundBox *)&corners, dbg_col); + DRW_debug_sphere(center, csm_render->radius[c], dbg_col); +#endif + + /* Project into lightspace */ + mul_m4_v3(viewmat, center); + + /* Snap projection center to nearest texel to cancel shimmering. */ + float shadow_origin[2], shadow_texco[2]; + /* Light to texture space. */ + mul_v2_v2fl( + shadow_origin, center, linfo->shadow_cascade_size / (2.0f * csm_render->radius[c])); + + /* Find the nearest texel. */ + shadow_texco[0] = roundf(shadow_origin[0]); + shadow_texco[1] = roundf(shadow_origin[1]); + + /* Compute offset. */ + sub_v2_v2(shadow_texco, shadow_origin); + /* Texture to light space. */ + mul_v2_fl(shadow_texco, (2.0f * csm_render->radius[c]) / linfo->shadow_cascade_size); + + /* Apply offset. */ + add_v2_v2(center, shadow_texco); + + /* Expand the projection to cover frustum range */ + rctf rect_cascade; + BLI_rctf_init_pt_radius(&rect_cascade, center, csm_render->radius[c]); + orthographic_m4(projmat, + rect_cascade.xmin, + rect_cascade.xmax, + rect_cascade.ymin, + rect_cascade.ymax, + sh_near, + sh_far); + + /* Anti-Aliasing */ + if (linfo->soft_shadows) { + add_v2_v2(projmat[3], jitter_ofs); + } + + float viewprojmat[4][4]; + mul_m4_m4m4(viewprojmat, projmat, viewmat); + mul_m4_m4m4(csm_data->shadowmat[c], texcomat, viewprojmat); + +#ifdef DEBUG_CSM + DRW_debug_m4_as_bbox(viewprojmat, dbg_col, true); +#endif + } + + shdw_data->near = sh_near; + shdw_data->far = sh_far; +} + +static void eevee_ensure_cascade_views(EEVEE_ShadowCascadeRender *csm_render, + DRWView *view[MAX_CASCADE_NUM]) +{ + for (int i = 0; i < csm_render->cascade_count; i++) { + if (view[i] == NULL) { + view[i] = DRW_view_create(csm_render->viewmat, csm_render->projmat[i], NULL, NULL, NULL); + } + else { + DRW_view_update(view[i], csm_render->viewmat, csm_render->projmat[i], NULL, NULL); + } + } +} + +void EEVEE_shadows_draw_cascades(EEVEE_ViewLayerData *sldata, + EEVEE_Data *vedata, + DRWView *view, + int cascade_index) +{ + EEVEE_PassList *psl = vedata->psl; + EEVEE_StorageList *stl = vedata->stl; + EEVEE_EffectsInfo *effects = stl->effects; + EEVEE_PrivateData *g_data = stl->g_data; + EEVEE_LightsInfo *linfo = sldata->lights; + + EEVEE_Light *evli = linfo->light_data + linfo->shadow_cascade_light_indices[cascade_index]; + EEVEE_Shadow *shdw_data = linfo->shadow_data + (int)evli->shadow_id; + EEVEE_ShadowCascade *csm_data = linfo->shadow_cascade_data + (int)shdw_data->type_data_id; + EEVEE_ShadowCascadeRender *csm_render = linfo->shadow_cascade_render + + (int)shdw_data->type_data_id; + + float near = DRW_view_near_distance_get(view); + float far = DRW_view_far_distance_get(view); + + eevee_shadow_cascade_setup(linfo, evli, view, near, far, effects->taa_current_sample - 1); + + /* Meh, Reusing the cube views. */ + BLI_assert(MAX_CASCADE_NUM <= 6); + eevee_ensure_cascade_views(csm_render, g_data->cube_views); + + /* Render shadow cascades */ + /* Render cascade separately: seems to be faster for the general case. + * The only time it's more beneficial is when the CPU culling overhead + * outweigh the instancing overhead. which is rarely the case. */ + for (int j = 0; j < csm_render->cascade_count; j++) { + DRW_view_set_active(g_data->cube_views[j]); + int layer = csm_data->tex_id + j; + GPU_framebuffer_texture_layer_attach( + sldata->shadow_fb, sldata->shadow_cascade_pool, 0, layer, 0); + GPU_framebuffer_bind(sldata->shadow_fb); + GPU_framebuffer_clear_depth(sldata->shadow_fb, 1.0f); + DRW_draw_pass(psl->shadow_pass); + } +}
\ No newline at end of file |