diff --git a/release/scripts/startup/bl_ui/properties_data_lamp.py b/release/scripts/startup/bl_ui/properties_data_lamp.py index 39b5f0eba55..f4d4e87ac39 100644 --- a/release/scripts/startup/bl_ui/properties_data_lamp.py +++ b/release/scripts/startup/bl_ui/properties_data_lamp.py @@ -386,6 +386,13 @@ class DATA_PT_EEVEE_shadow(DataButtonsPanel, Panel): col.prop(lamp, "shadow_buffer_exp", text="Exponent") col.prop(lamp, "shadow_buffer_bleed_bias", text="Bleed Bias") + col = layout.column() + col.label("Cascaded Shadow Map:") + col.prop(lamp, "shadow_cascade_max_distance", text="Max Distance") + col.prop(lamp, "shadow_cascade_count", text="Count") + col.prop(lamp, "shadow_cascade_exponent", text="Distribution") + col.prop(lamp, "shadow_cascade_fade", text="Fade") + class DATA_PT_area(DataButtonsPanel, Panel): bl_label = "Area Shape" diff --git a/source/blender/blenkernel/intern/lamp.c b/source/blender/blenkernel/intern/lamp.c index dc736b3f084..b8626535893 100644 --- a/source/blender/blenkernel/intern/lamp.c +++ b/source/blender/blenkernel/intern/lamp.c @@ -102,6 +102,10 @@ void BKE_lamp_init(Lamp *la) la->sky_colorspace = BLI_XYZ_CIE; la->sky_exposure = 1.0f; la->shadow_frustum_size = 10.0f; + la->cascade_max_dist = 1000.0f; + la->cascade_count = 4; + la->cascade_exponent = 0.8f; + la->cascade_fade = 0.1f; curvemapping_initialize(la->curfalloff); } diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 810bf5a3a46..f022b393967 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -432,6 +432,15 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *main) } } + if (!DNA_struct_elem_find(fd->filesdna, "Lamp", "float", "cascade_max_dist")) { + for (Lamp *la = main->lamp.first; la; la = la->id.next) { + la->cascade_max_dist = 1000.0f; + la->cascade_count = 4; + la->cascade_exponent = 0.8f; + la->cascade_fade = 0.1f; + } + } + { typedef enum eNTreeDoVersionErrors { NTREE_DOVERSION_NO_ERROR = 0, diff --git a/source/blender/draw/engines/eevee/eevee_lights.c b/source/blender/draw/engines/eevee/eevee_lights.c index 2bd33f0a755..857bdfc47f7 100644 --- a/source/blender/draw/engines/eevee/eevee_lights.c +++ b/source/blender/draw/engines/eevee/eevee_lights.c @@ -493,10 +493,12 @@ static void frustum_min_bounding_sphere(const float corners[8][4], float r_cente 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 near, far; + 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(); @@ -507,75 +509,134 @@ static void eevee_shadow_cascade_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE invert_m4_m4(projinv, viewprojmat); mul_m4_v4(projinv, near_v); mul_m4_v4(projinv, far_v); - near = near_v[2]; - far = far_v[2]; /* TODO: Should be a shadow parameter */ + view_near = near_v[2]; + view_far = far_v[2]; /* TODO: Should be a shadow parameter */ if (is_persp) { - near /= near_v[3]; - far /= far_v[3]; + 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 = MAX_CASCADE_NUM; /* TODO : Custom cascade number */ + 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; - Lamp *la = (Lamp *)ob->data; /* 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[c] = far; + cascade_data->split_start[c] = csm_end; + cascade_data->split_end[c] = csm_end; } /* Compute split planes */ - float splits_ndc[MAX_CASCADE_NUM + 1]; - splits_ndc[0] = -1.0f; - splits_ndc[cascade_nbr] = 1.0f; - for (int c = 1; c < cascade_nbr; ++c) { - const float lambda = 0.8f; /* TODO : Parameter */ + float splits_start_ndc[MAX_CASCADE_NUM]; + float splits_end_ndc[MAX_CASCADE_NUM]; - /* View Space */ - float linear_split = LERP(((float)(c) / (float)cascade_nbr), near, far); - float exp_split = near * powf(far / near, (float)(c) / (float)cascade_nbr); - - if (is_persp) { - cascade_data->split[c-1] = LERP(lambda, linear_split, exp_split); - } - else { - cascade_data->split[c-1] = linear_split; - } - - /* NDC Space */ - float p[4] = {1.0f, 1.0f, cascade_data->split[c-1], 1.0f}; + { + /* 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_ndc[c] = p[2]; - + splits_start_ndc[0] = p[2]; if (is_persp) { - splits_ndc[c] /= p[3]; + 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; + } + cascade_data->split_end[c-1] = cascade_data->split_start[c]; + + /* Add some overlap for smooth transition */ + 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]; + } + } + + { + 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); + 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) ? 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_ndc[c], 1.0f}, - { 1.0f, -1.0f, splits_ndc[c], 1.0f}, - {-1.0f, 1.0f, splits_ndc[c], 1.0f}, - { 1.0f, 1.0f, splits_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}, + { 1.0f, 1.0f, splits_start_ndc[c], 1.0f}, /* Far Cap */ - {-1.0f, -1.0f, splits_ndc[c+1], 1.0f}, - { 1.0f, -1.0f, splits_ndc[c+1], 1.0f}, - {-1.0f, 1.0f, splits_ndc[c+1], 1.0f}, - { 1.0f, 1.0f, splits_ndc[c+1], 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}, + { 1.0f, 1.0f, splits_end_ndc[c], 1.0f} }; /* Transform them into world space */ @@ -585,6 +646,7 @@ static void eevee_shadow_cascade_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE corners[i][3] = 1.0f; } + /* Project them into light space */ invert_m4_m4(viewmat, ob->obmat); normalize_v3(viewmat[0]); @@ -837,7 +899,7 @@ void EEVEE_draw_shadows(EEVEE_SceneLayerData *sldata, EEVEE_PassList *psl) srd->shadow_inv_samples_ct = 1.0f / srd->shadow_samples_ct; srd->clip_near = la->clipsta; srd->clip_far = la->clipend; - for (int j = 0; j < MAX_CASCADE_NUM; ++j) { + 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); @@ -849,7 +911,7 @@ void EEVEE_draw_shadows(EEVEE_SceneLayerData *sldata, EEVEE_PassList *psl) DRW_draw_pass(psl->shadow_cascade_pass); for (linfo->current_shadow_cascade = 0; - linfo->current_shadow_cascade < MAX_CASCADE_NUM; + linfo->current_shadow_cascade < la->cascade_count; ++linfo->current_shadow_cascade) { linfo->filter_size = la->soft * 0.0005f / (evscd->radius[linfo->current_shadow_cascade] * 0.05f); diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h index 157f3076be4..65b1ddfd949 100644 --- a/source/blender/draw/engines/eevee/eevee_private.h +++ b/source/blender/draw/engines/eevee/eevee_private.h @@ -223,7 +223,8 @@ typedef struct EEVEE_ShadowCube { typedef struct EEVEE_ShadowCascade { float shadowmat[MAX_CASCADE_NUM][4][4]; /* World->Lamp->NDC->Tex : used for sampling the shadow map. */ - float split[4]; + float split_start[4]; + float split_end[4]; } EEVEE_ShadowCascade; typedef struct EEVEE_ShadowRender { diff --git a/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl b/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl index bb48a204ae5..6ad5b46f683 100644 --- a/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl @@ -89,7 +89,8 @@ struct ShadowCubeData { struct ShadowCascadeData { mat4 shadowmat[MAX_CASCADE_NUM]; - vec4 split_distances; + vec4 split_start_distances; + vec4 split_end_distances; }; /* convenience aliases */ diff --git a/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl b/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl index 6a263aee880..8e4d0cb548b 100644 --- a/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl @@ -78,37 +78,15 @@ float shadow_cubemap(ShadowData sd, ShadowCubeData scd, float texid, vec3 W) #endif } -float shadow_cascade(ShadowData sd, ShadowCascadeData scd, float texid, vec3 W) +float evaluate_cascade(ShadowData sd, mat4 shadowmat, vec3 W, float range, float texid) { - /* Finding Cascade index */ - vec4 view_z = vec4(dot(W - cameraPos, cameraForward)); - vec4 comp = step(view_z, scd.split_distances); - float cascade = dot(comp, comp); - mat4 shadowmat; - - /* Manual Unrolling of a loop for better performance. - * Doing fetch directly with cascade index leads to - * major performance impact. (0.27ms -> 10.0ms for 1 light) */ - if (cascade == 0.0) { - shadowmat = scd.shadowmat[0]; - } - else if (cascade == 1.0) { - shadowmat = scd.shadowmat[1]; - } - else if (cascade == 2.0) { - shadowmat = scd.shadowmat[2]; - } - else { - shadowmat = scd.shadowmat[3]; - } - vec4 shpos = shadowmat * vec4(W, 1.0); - float dist = shpos.z * abs(sd.sh_far - sd.sh_near); /* Same factor as in get_cascade_world_distance(). */ + float dist = shpos.z * range; #if defined(SHADOW_VSM) - vec2 moments = texture(shadowTexture, vec3(shpos.xy, texid + cascade)).rg; + vec2 moments = texture(shadowTexture, vec3(shpos.xy, texid)).rg; #else - float z = texture(shadowTexture, vec3(shpos.xy, texid + cascade)).r; + float z = texture(shadowTexture, vec3(shpos.xy, texid)).r; #endif #if defined(SHADOW_VSM) @@ -120,6 +98,38 @@ float shadow_cascade(ShadowData sd, ShadowCascadeData scd, float texid, vec3 W) #endif } +float shadow_cascade(ShadowData sd, ShadowCascadeData scd, float texid, vec3 W) +{ + vec4 view_z = vec4(dot(W - cameraPos, cameraForward)); + vec4 weights = smoothstep(scd.split_end_distances, scd.split_start_distances.yzwx, view_z); + weights.yzw -= weights.xyz; + + vec4 vis = vec4(1.0); + float range = abs(sd.sh_far - sd.sh_near); /* Same factor as in get_cascade_world_distance(). */ + if (weights.x > 0.0) { + vis.x = evaluate_cascade(sd, scd.shadowmat[0], W, range, texid + 0); + } + if (weights.y > 0.0) { + vis.y = evaluate_cascade(sd, scd.shadowmat[1], W, range, texid + 1); + } + if (weights.z > 0.0) { + vis.z = evaluate_cascade(sd, scd.shadowmat[2], W, range, texid + 2); + } + if (weights.w > 0.0) { + vis.w = evaluate_cascade(sd, scd.shadowmat[3], W, range, texid + 3); + } + + float weight_sum = dot(vec4(1.0), weights); + if (weight_sum > 0.9999) { + float vis_sum = dot(vec4(1.0), vis * weights); + return vis_sum / weight_sum; + } + else { + float vis_sum = dot(vec4(1.0), vis * step(0.001, weights)); + return mix(1.0, vis_sum, weight_sum); + } +} + /* ----------------------------------------------------------- */ /* --------------------- Light Functions --------------------- */ /* ----------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee/shaders/shadow_store_frag.glsl b/source/blender/draw/engines/eevee/shaders/shadow_store_frag.glsl index 5a9d0292ddf..6564fd87d04 100644 --- a/source/blender/draw/engines/eevee/shaders/shadow_store_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/shadow_store_frag.glsl @@ -57,6 +57,10 @@ float linear_depth(float z) float get_cascade_world_distance(vec2 uvs) { float zdepth = texture(shadowTexture, vec3(uvs, float(cascadeId))).r; + if (zdepth == 1.0) { + /* Background case */ + return 1e16; + } return zdepth * abs(farClip - nearClip); /* Same factor as in shadow_cascade(). */ } #else diff --git a/source/blender/makesdna/DNA_lamp_types.h b/source/blender/makesdna/DNA_lamp_types.h index cb5b44f3268..4c99b062f27 100644 --- a/source/blender/makesdna/DNA_lamp_types.h +++ b/source/blender/makesdna/DNA_lamp_types.h @@ -106,6 +106,12 @@ typedef struct Lamp { short pr_texture, use_nodes; char pad6[4]; + /* Eevee */ + float cascade_max_dist; + float cascade_exponent; + float cascade_fade; + int cascade_count; + /* preview */ struct PreviewImage *preview; diff --git a/source/blender/makesrna/intern/rna_lamp.c b/source/blender/makesrna/intern/rna_lamp.c index 613dc4998b6..15dabba0c4d 100644 --- a/source/blender/makesrna/intern/rna_lamp.c +++ b/source/blender/makesrna/intern/rna_lamp.c @@ -476,7 +476,7 @@ static void rna_def_lamp_falloff(StructRNA *srna) RNA_def_property_update(prop, 0, "rna_Lamp_draw_update"); } -static void rna_def_lamp_shadow(StructRNA *srna, int spot, int area) +static void rna_def_lamp_shadow(StructRNA *srna, int spot, int area, int sun) { PropertyRNA *prop; @@ -698,6 +698,32 @@ static void rna_def_lamp_shadow(StructRNA *srna, int spot, int area) RNA_def_property_boolean_sdna(prop, NULL, "mode", LA_LAYER_SHADOW); RNA_def_property_ui_text(prop, "Shadow Layer", "Objects on the same layers only cast shadows"); RNA_def_property_update(prop, 0, "rna_Lamp_update"); + + if (sun) { + prop = RNA_def_property(srna, "shadow_cascade_max_distance", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_float_sdna(prop, NULL, "cascade_max_dist"); + RNA_def_property_range(prop, 0.0f, 9999.0f); + RNA_def_property_ui_text(prop, "Cascade Max Distance", "End distance of the cascaded shadow map (only in perspective view)"); + RNA_def_property_update(prop, 0, "rna_Lamp_update"); + + prop = RNA_def_property(srna, "shadow_cascade_count", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "cascade_count"); + RNA_def_property_range(prop, 1, 4); + RNA_def_property_ui_text(prop, "Cascade Count", "Number of texture used by the cascaded shadow map"); + RNA_def_property_update(prop, 0, "rna_Lamp_update"); + + prop = RNA_def_property(srna, "shadow_cascade_exponent", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "cascade_exponent"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Exponential Distribution", "Higher value increase resolution towards the viewpoint"); + RNA_def_property_update(prop, 0, "rna_Lamp_update"); + + prop = RNA_def_property(srna, "shadow_cascade_fade", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "cascade_fade"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Cascade Fade", "How smooth is the transition between each cascade"); + RNA_def_property_update(prop, 0, "rna_Lamp_update"); + } } static void rna_def_point_lamp(BlenderRNA *brna) @@ -710,7 +736,7 @@ static void rna_def_point_lamp(BlenderRNA *brna) RNA_def_struct_ui_icon(srna, ICON_LAMP_POINT); rna_def_lamp_falloff(srna); - rna_def_lamp_shadow(srna, 0, 0); + rna_def_lamp_shadow(srna, 0, 0, 0); } static void rna_def_area_lamp(BlenderRNA *brna) @@ -729,7 +755,7 @@ static void rna_def_area_lamp(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Area Lamp", "Directional area lamp"); RNA_def_struct_ui_icon(srna, ICON_LAMP_AREA); - rna_def_lamp_shadow(srna, 0, 1); + rna_def_lamp_shadow(srna, 0, 1, 0); rna_def_lamp_falloff(srna); prop = RNA_def_property(srna, "use_umbra", PROP_BOOLEAN, PROP_NONE); @@ -786,7 +812,7 @@ static void rna_def_spot_lamp(BlenderRNA *brna) RNA_def_struct_ui_icon(srna, ICON_LAMP_SPOT); rna_def_lamp_falloff(srna); - rna_def_lamp_shadow(srna, 1, 0); + rna_def_lamp_shadow(srna, 1, 0, 0); prop = RNA_def_property(srna, "use_square", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "mode", LA_SQUARE); @@ -839,7 +865,7 @@ static void rna_def_sun_lamp(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Sun Lamp", "Constant direction parallel ray lamp"); RNA_def_struct_ui_icon(srna, ICON_LAMP_SUN); - rna_def_lamp_shadow(srna, 0, 0); + rna_def_lamp_shadow(srna, 0, 0, 1); /* sky */ prop = RNA_def_property(srna, "sky", PROP_POINTER, PROP_NONE);