Eevee: Cascaded Shadow Maps, follow up.
- Compute coarse bounding box of split frustum. Can be improved - Make use of 4 cascade. - View dependant glitches are fixed. - Optimized shader code.
This commit is contained in:
@@ -295,14 +295,65 @@ static void eevee_shadow_map_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE_Lam
|
||||
|
||||
#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);
|
||||
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) {
|
||||
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)
|
||||
{
|
||||
/* Camera Matrices */
|
||||
float persmat[4][4], persinv[4][4];
|
||||
float viewprojmat[4][4], projinv[4][4];
|
||||
float near, 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);
|
||||
near = near_v[2];
|
||||
far = far_v[2]; /* TODO: Should be a shadow parameter */
|
||||
if (is_persp) {
|
||||
near /= near_v[3];
|
||||
far /= far_v[3];
|
||||
}
|
||||
|
||||
/* Lamps Matrices */
|
||||
float viewmat[4][4], projmat[4][4];
|
||||
float minvec[3], maxvec[3];
|
||||
int cascade_ct = MAX_CASCADE_NUM;
|
||||
float shadow_res = 512.0f; /* TODO parameter */
|
||||
|
||||
EEVEE_ShadowCascadeData *evscp = (EEVEE_ShadowCascadeData *)led->sto;
|
||||
EEVEE_Light *evli = linfo->light_data + evscp->light_id;
|
||||
@@ -313,28 +364,56 @@ static void eevee_shadow_cascade_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE
|
||||
* the view frustum into several sub-frustum
|
||||
* that are individually receiving one shadow map */
|
||||
|
||||
/* init near/far */
|
||||
for (int c = 0; c < MAX_CASCADE_NUM; ++c) {
|
||||
evsh->split[c] = far;
|
||||
}
|
||||
|
||||
/* Compute split planes */
|
||||
float splits_ndc[MAX_CASCADE_NUM + 1];
|
||||
splits_ndc[0] = -1.0f;
|
||||
splits_ndc[cascade_ct] = 1.0f;
|
||||
for (int c = 1; c < cascade_ct; ++c) {
|
||||
const float lambda = 0.8f; /* TODO : Parameter */
|
||||
|
||||
/* View Space */
|
||||
float linear_split = LERP(((float)(c) / (float)cascade_ct), near, far);
|
||||
float exp_split = near * powf(far / near, (float)(c) / (float)cascade_ct);
|
||||
|
||||
if (is_persp) {
|
||||
evsh->split[c-1] = LERP(lambda, linear_split, exp_split);
|
||||
}
|
||||
else {
|
||||
evsh->split[c-1] = linear_split;
|
||||
}
|
||||
|
||||
/* NDC Space */
|
||||
float p[4] = {1.0f, 1.0f, evsh->split[c-1], 1.0f};
|
||||
mul_m4_v4(viewprojmat, p);
|
||||
splits_ndc[c] = p[2];
|
||||
|
||||
if (is_persp) {
|
||||
splits_ndc[c] /= p[3];
|
||||
}
|
||||
}
|
||||
|
||||
/* For each cascade */
|
||||
for (int c = 0; c < cascade_ct; ++c) {
|
||||
float splitnear = LERP(((float)(c) / cascade_ct), -1.0f, 1.0f);
|
||||
float splitfar = LERP(((float)(c + 1) / cascade_ct), -1.0f, 1.0f);
|
||||
|
||||
/* Given 8 frustrum corners */
|
||||
float corners[8][4] = {
|
||||
/* Far Cap */
|
||||
{-1.0f, -1.0f, splitfar, 1.0f},
|
||||
{ 1.0f, -1.0f, splitfar, 1.0f},
|
||||
{-1.0f, 1.0f, splitfar, 1.0f},
|
||||
{ 1.0f, 1.0f, splitfar, 1.0f},
|
||||
/* Near Cap */
|
||||
{-1.0f, -1.0f, splitnear, 1.0f},
|
||||
{ 1.0f, -1.0f, splitnear, 1.0f},
|
||||
{-1.0f, 1.0f, splitnear, 1.0f},
|
||||
{ 1.0f, 1.0f, splitnear, 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_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}
|
||||
};
|
||||
|
||||
/* Transform them into world space */
|
||||
DRW_viewport_matrix_get(persmat, DRW_MAT_PERS);
|
||||
invert_m4_m4(persinv, persmat);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
mul_m4_v4(persinv, corners[i]);
|
||||
mul_v3_fl(corners[i], 1.0f / corners[i][3]);
|
||||
@@ -351,21 +430,38 @@ static void eevee_shadow_cascade_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE
|
||||
mul_m4_v4(viewmat, corners[i]);
|
||||
}
|
||||
|
||||
/* compute the minimum bounding box */
|
||||
INIT_MINMAX(minvec, maxvec);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
minmax_v3v3_v3(minvec, maxvec, corners[i]);
|
||||
}
|
||||
float center[3], radius;
|
||||
frustum_min_bounding_sphere(corners, center, &radius);
|
||||
|
||||
/* expand the bounding box to cover light range */
|
||||
orthographic_m4(projmat, minvec[0], maxvec[0], minvec[1], maxvec[1], la->clipsta, la->clipend);
|
||||
/* Snap projection center to nearest texel to cancel shimering. */
|
||||
float shadow_origin[2], shadow_texco[2];
|
||||
mul_v2_v2fl(shadow_origin, center, shadow_res / (2.0f * radius)); /* Light to texture space. */
|
||||
|
||||
/* 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 * radius) / shadow_res); /* Texture to light space. */
|
||||
|
||||
/* Apply offset. */
|
||||
add_v2_v2(center, shadow_texco);
|
||||
|
||||
/* Expand the projection to cover frustum range */
|
||||
orthographic_m4(projmat,
|
||||
center[0] - radius,
|
||||
center[0] + radius,
|
||||
center[1] - radius,
|
||||
center[1] + radius,
|
||||
la->clipsta, la->clipend);
|
||||
|
||||
mul_m4_m4m4(evscp->viewprojmat[c], projmat, viewmat);
|
||||
mul_m4_m4m4(evsh->shadowmat[c], texcomat, evscp->viewprojmat[c]);
|
||||
}
|
||||
|
||||
evsh->bias = 0.005f * la->bias;
|
||||
evsh->count = (float)cascade_ct;
|
||||
/* TODO modify bias depending on the cascade radius */
|
||||
evsh->bias[c] = 0.005f * la->bias;
|
||||
}
|
||||
|
||||
evli->shadowid = (float)(MAX_SHADOW_CUBE + MAX_SHADOW_MAP + evscp->shadow_id);
|
||||
}
|
||||
|
@@ -112,9 +112,8 @@ typedef struct EEVEE_ShadowMap {
|
||||
|
||||
typedef struct EEVEE_ShadowCascade {
|
||||
float shadowmat[MAX_CASCADE_NUM][4][4]; /* World->Lamp->NDC->Tex : used for sampling the shadow map. */
|
||||
float bias, count, pad[2];
|
||||
float near[MAX_CASCADE_NUM];
|
||||
float far[MAX_CASCADE_NUM];
|
||||
float split[4];
|
||||
float bias[4];
|
||||
} EEVEE_ShadowCascade;
|
||||
|
||||
typedef struct EEVEE_ShadowRender {
|
||||
|
@@ -59,16 +59,11 @@ struct ShadowMapData {
|
||||
|
||||
struct ShadowCascadeData {
|
||||
mat4 shadowmat[MAX_CASCADE_NUM];
|
||||
vec4 bias_count;
|
||||
float near[MAX_CASCADE_NUM];
|
||||
float far[MAX_CASCADE_NUM];
|
||||
/* arrays of float are not aligned so use vec4 */
|
||||
vec4 split_distances;
|
||||
vec4 bias;
|
||||
};
|
||||
|
||||
/* convenience aliases */
|
||||
#define sh_cascade_bias bias_count.x
|
||||
#define sh_cascade_count bias_count.y
|
||||
|
||||
|
||||
struct AreaData {
|
||||
vec3 corner[4];
|
||||
float solid_angle;
|
||||
|
@@ -91,17 +91,38 @@ float light_visibility(LightData ld, ShadingData sd)
|
||||
float shid = ld.l_shadowid - (MAX_SHADOW_CUBE + MAX_SHADOW_MAP);
|
||||
ShadowCascadeData smd = shadows_cascade_data[int(shid)];
|
||||
|
||||
for (int i = 0; i < int(smd.sh_cascade_count); i++) {
|
||||
vec4 shpos = smd.shadowmat[i] * vec4(sd.W, 1.0);
|
||||
shpos.z -= smd.sh_cascade_bias * shpos.w;
|
||||
shpos.xyz /= shpos.w;
|
||||
/* Finding Cascade index */
|
||||
vec4 z = vec4(-dot(cameraPos - worldPosition, normalize(eye)));
|
||||
vec4 comp = step(z, smd.split_distances);
|
||||
float cascade = dot(comp, comp);
|
||||
mat4 shadowmat;
|
||||
float bias;
|
||||
|
||||
if (shpos.w > 0.0 && min(shpos.x, shpos.y) > 0.0 && max(shpos.x, shpos.y) < 1.0) {
|
||||
vis *= texture(shadowCascades, vec4(shpos.xy, shid * float(MAX_CASCADE_NUM) + float(i), shpos.z));
|
||||
// vis = float(i) / float(MAX_CASCADE_NUM);
|
||||
break;
|
||||
}
|
||||
/* 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 = smd.shadowmat[0];
|
||||
bias = smd.bias[0];
|
||||
}
|
||||
else if (cascade == 1.0) {
|
||||
shadowmat = smd.shadowmat[1];
|
||||
bias = smd.bias[1];
|
||||
}
|
||||
else if (cascade == 2.0) {
|
||||
shadowmat = smd.shadowmat[2];
|
||||
bias = smd.bias[2];
|
||||
}
|
||||
else {
|
||||
shadowmat = smd.shadowmat[3];
|
||||
bias = smd.bias[3];
|
||||
}
|
||||
|
||||
vec4 shpos = shadowmat * vec4(sd.W, 1.0);
|
||||
shpos.z -= bias * shpos.w;
|
||||
shpos.xyz /= shpos.w;
|
||||
|
||||
vis *= texture(shadowCascades, vec4(shpos.xy, shid * float(MAX_CASCADE_NUM) + cascade, shpos.z));
|
||||
}
|
||||
else if (ld.l_shadowid >= MAX_SHADOW_CUBE) {
|
||||
/* Shadow Map */
|
||||
|
Reference in New Issue
Block a user