Eevee: Add Cascaded Shadow Map options.

This commit is contained in:
Clément Foucault 2017-09-07 15:31:11 +02:00
parent cc6e97d426
commit adeaf37e77
10 changed files with 202 additions and 72 deletions

View File

@ -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"

View File

@ -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);
}

View File

@ -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,

View File

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

View File

@ -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 {

View File

@ -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 */

View File

@ -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 --------------------- */
/* ----------------------------------------------------------- */

View File

@ -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

View File

@ -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;

View File

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