#Weird U-shaped artifacts at shadow cubemap seams

1 messages · Page 1 of 1 (latest)

keen echo
#

Hey all,

I'm hitting a wall with a weird shadow issue using a point light and a cubemap for Gaussian Splatting and could really use some fresh eyes on this.

I'm getting these big, ugly, U-shaped black artifacts where the faces of my shadow cubemap meet. This isn't the usual single-pixel seam bleeding; it's a much larger pattern.

I've already tried messing with the bias. If I crank _ShadowBias way up to something crazy like 1.0, the U-shape disappears, but then I get horrible Peter Panning, so that's not a real fix.

What's really confusing is that my old implementation didn't have this problem. It was slow, but it worked. I was using 6 separate 2D textures instead of a single cubemap and manually picked the right View-Projection matrix in the shader for each face. This makes me pretty sure the problem is specifically with how UNITY_SAMPLE_TEXCUBE is behaving at the seams, not an issue with my scene data itself.

So, it really seems like this has to be a sampler problem, but I'm not sure how to approach debugging it from here. Is this a known issue with projection distortion near the 90-degree frustum edges? Or maybe the GPU is calculating wild derivatives at the seams and messing up the sample?

Any ideas on what could be causing this or how to investigate it further would be a huge help.

Thanks!

#

Unity 6, URP, DX12 environment.

my sampling function right now:

bool SamplePointShadow(float3 worldPos)
{
    // Get the vector from light to pixel
    float3 lightVec = worldPos - _PointLightPosition;
    float currentLinearDepth = length(lightVec);

    // Sample the cubemap
    float shadowMapNonLinearDepth = UNITY_SAMPLE_TEXCUBE(_ShadowCubemap, lightVec).r;

    // Convert stored depth back to real-world distance
    float shadowMapLinearDepth = LinearEyeDepth(shadowMapNonLinearDepth, _ZBufferParams);

    // Compare and apply bias
    bool visibility = currentLinearDepth <= shadowMapLinearDepth + _ShadowBias;
    return visibility;
}
lost condor
keen echo
#
public RenderTextureDescriptor GetShadowFaceDescriptor()
        {
            var desc = new RenderTextureDescriptor(shadowCubemapResolution, shadowCubemapResolution)
            {
                graphicsFormat = GraphicsFormat.None,
                depthStencilFormat = GraphicsFormat.D32_SFloat,
                dimension = TextureDimension.Cube,
                autoGenerateMips = false,
                useMipMap = false,
                msaaSamples = 1,
                memoryless = RenderTextureMemoryless.None,
                shadowSamplingMode = ShadowSamplingMode.None
            };
            return desc;
        }

RenderTexture created at runtime, configured as a cube map (TextureDimension.Cube). The format is a 32-bit depth format (GraphicsFormat.D32_SFloat).

#
cmd.SetRenderTarget(shadowCubemapHandle, 0, face);

My C# script dynamically generates it by rendering the scene six times: once for each face of the cube map (+X, -X, +Y, -Y, +Z, -Z) using a camera with a 90-degree field of view (FOV).

#

Although I couldn't verify the cubemap results in the frame debugger (for reasons unknown), it's certain that the cubemap is created and passed correctly.

lost condor
#

I don't know if it's relevant but URP almost always usesSAMPLE_TEXTURECUBE and related methods
UNITY_SAMPLE_TEXCUBE seems to be barely relevant outside of BiRP

#

Could try the same method to project the cubemap to a sphere mesh to see if issues appear there
Though if bias is what seems to affect it, it sounds like it's related to what the bias does to the sampling of the texture

#

The "U" shape appears because every face of the cube texture seems to be squashed into a circle
So maybe the bias isn't the right type for a cube texture

#

What it mostly looks like is that every face projects the cubemap as a 2D "spheremap", but not sure how that is possible

keen echo
#

This is when the bias is 0.01 and when it is 1.0.

keen echo
lost condor
#

My speculation that it shows each cube face as a spheremap doesn't seem to be correct, as the comparison shows that the cubemap itself isn't being squashed into circles, but that the circular shadow appears over the texture which is projected correctly

#

Sure the cube or something else isn't actually blocking the shadow caster? The shadow is exactly in the shape of a cube seen from inside if a part of it was clipped by distance

keen echo
#

My rendering pipeline only handles GS data, so it's incompatible with the existing Unity pipeline, but I don't think that's the reason. As you said, the bias is 1.0, so it produces the desired result except for the clipping at close distances, but it's frustrating that I don't know why.

#

When I implemented it using only coordinate system calculations without a cube map, this is what it looked like.

lost condor
#

I don't really know, so I'd just question all assumptions

#

I guess it also visually resembles trying to sample behind the frustum, if the bias in other direction causes it to sample too far ahead

keen echo
#

It's been a while, but I've figured out the cause of the issue. The problem was an incorrect comparison between the axis-aligned depth stored in the cubemap and the fragment's distance (norm). I fixed it by using the dominant axis-based depth (max(abs(lightVec))) for the comparison, ensuring consistent shadow determination in all directions.

#
bool SamplePointShadow(float3 worldPos)
{
    float3 lightVec = worldPos - _PointLightPosition;

    float3 absLightVec = abs(lightVec);
    float currentLinearDepth = max(absLightVec.x, max(absLightVec.y, absLightVec.z));

    float shadowMapNonLinearDepth = SAMPLE_TEXTURECUBE(_ShadowCubemap, sampler_ShadowCubemap, lightVec).r;
    float shadowMapLinear01Depth = LinearEyeDepth(shadowMapNonLinearDepth, _LightZBufferParams);

    bool visibility = currentLinearDepth <= shadowMapLinear01Depth + _ShadowBias;
    return visibility;
}
#

Thank you for your interest in my problem. @lost condor

lost condor