CSM split dynamic calculation and seams

So I’ve decided to implement CSM to enhance the resolution of shadows in my scenes.
It works pretty well as can be seen here:


Right now I’m passing the number of splits as an argument but the ratio of each split is kinda hardcoded. I’m wondering if there’s a good generic solution to calculate splits for best shadows based on something or it’s more art than anything and I should just play with the values?

Also I get shadow seams on the border of each shadow map because the resolution of the shadow map obviously changes because of how the light frustum getting bigger after each cascade making resolution go down:

Now I couldn’t figure out what I can do about it. I’ve read online that I should sample both splits shadow maps and linear interpolate them… but I’m not sure even how?
The way I split the cascades right now is hardcoded fixed ratio.

the first split is 0.0366... * far.

the second split is 0.5 * far.

the second split is 1.0 * far.

These values were only meant for testing and to demonstrate the seams between the first and second split.

The way I sample the correct shadow map is by calculating the fragment z position in clip space and pass it to the fragment shader:

	vs_out.v_FragPosClipSpaceZ = (u_Projection * u_View * u_Model * vec4(position, 1.0).z;

I pass a uniform uniform float u_CascadeEndClipSpace[NR_LIGHT_SPACE];
to the fragment shader which is all the far planes of each view frustum the CSM is splitting in camera clip space.
Then I check if v_FragPosClipSpaceZ is lower than u_CascadeEndClipSpace[i] and if so sample uniform sampler2D u_ShadowMap[NR_LIGHT_SPACE]; according to i in the fragment shader:

	float shadow = 0.0f;
	for (int i = 0; i < NR_LIGHT_SPACE; i++) {
		if (fs_in.v_FragPosClipSpaceZ <= u_CascadeEndClipSpace[i]) {
			shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i);
			break;
		}
	}

All the splits have the exact same map resolution right now fixed to 8192.

Help on that would be appreciated!

Tried to split as follows, result are the same:

float shadow = 0.0
	for (int i = 0; i < NR_LIGHT_SPACE; i++) {
		if (fs_in.v_FragPosClipSpaceZ <= u_CascadeEndClipSpace[i]) {
			shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i);
			float diff = u_CascadeEndClipSpace[i] - fs_in.v_FragPosClipSpaceZ;
			float frustumLength = u_CascadeEndClipSpace[i] - (i > 0 ? u_CascadeEndClipSpace[i - 1] : 0.0);
			float diffRatio = clamp(diff / frustumLength, 0.0, 1.0) * 0.1;
			if (i < NR_LIGHT_SPACE - 1 && diffRatio < 1.0) {
				float nextShadow = isInShadow(fs_in.v_FragPosLightSpace[i + 1], normal, lightDirection, i + 1);
				shadow = mix(shadow, nextShadow, diffRatio);
			}
			break;
		}
	}

Looking at it again it had Blending Cascades section more at the bottom of the page. I’ve used their algorithm with the fading as follows:

    for (int i = 0; i < NR_LIGHT_SPACE; i++) {
		if (fs_in.v_FragPosClipSpaceZ <= u_CascadeEndClipSpace[i]) {
			shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i);
			float fade = clamp((1.0 - fs_in.v_FragPosClipSpaceZ / u_CascadeEndClipSpace[i]) / 0.05, 0.0, 1.0);

			if (fade < 1.0) {
				float nextShadow = isInShadow(fs_in.v_FragPosLightSpace[i + 1], normal, lightDirection, i + 1);
				shadow = mix(nextShadow, shadow, fade);
			}
			break;
		}
	}

And I get the following result:


look more blended than before but it’s not the nicest result. I think that’s because the shadow is calculated with simple 3x3 PCF and the second cascade ratio is alot larger from the first cascade which mean there’s smaller resolution for the shadow map texels (I think) I’m gonna test this out tomorrow but would like to hear opinions!
Btw I achieved this blending the same way as presented in this awesome tutorial: