2

This question will leave you all puzzled: In my project, a spaceship is flying over a landscape. It casts a shadow (straight below) that is created by using a directional light, so its size doesn't change with the height of the ship. I was trying a lot of times to get it completely right, and sometimes it works and other times it doesn't. I have a moving camera, meaning the angle to the ship changes depending on the angle it flies.

Now I found out why the shadow isn't always working the way it is supposed to: below a certain view angle (the more it comes to the horizontal view), the shadow will become pixeled, then flicker until it's completely gone when the camera is entirely horizontal. Blurry Shadow

As the view is directed increasingly below, the shadow of the ship will become more and more resolved and at above approx. 30° it's just right. desired shadow

I have checked in Stack Overflow, but it seems a changing angles camera is rather unusual. I guess a fixed camera above the player's node is more common.

Anyone know this behavior? This is my code.

self.dirLightNode = SCNNode()
self.dirLightNode.light = SCNLight()
self.dirLightNode.position = SCNVector3(x: -0 * dividerx, 
                                        y: 20 * dividery, 
                                        z: -00)
self.dirLightNode.light?.type = .directional
self.dirLightNode.light!.color = UIColor.black
self.dirLightNode.light?.castsShadow = true
self.dirLightNode.light?.shadowMode = .deferred
self.dirLightNode.light?.categoryBitMask = 1
self.dirLightNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.8)
self.dirLightNode.rotation = SCNVector4Make(1,0,0, 1.5 * Float.pi)
self.dirLightNode.light?.orthographicScale = 10
self.dirLightNode.light?.shadowRadius = 10
self.dirLightNode.light?.shadowBias = 1
self.dirLightNode.light?.zFar = 100
self.dirLightNode.light?.zNear = 0
self.dirLightNode.light?.maximumShadowDistance = 10000
self.dirLightNode.light?.shadowSampleCount = 1
self.dirLightNode.light?.shadowMapSize = CGSize(width: 4096, height: 4096)

I tried to change the shadowradius, shadowBias,samplecount, automaticshadowprojection, etc. Nothing seems to fix the problem. Does anyone know a reference where all the parameters are explained thoroughly?

ULI
  • 41
  • 7
  • one more thing: this behavior also doesn't change if I change the shadow direction to horizontal (being projected to a wall rather than to the floor) – ULI Mar 23 '23 at 17:47
  • can you provide screenshots or a video? – ZAY Mar 23 '23 at 20:26
  • I added some screenshots of the blurry/flickering shadow and the right one - without having tried Andy Jazz‘s comments. Thanks for those, Andy Jazz – ULI Mar 26 '23 at 09:25
  • Would it be possible to share your project as download? I'd like to have a look at it. – ZAY Mar 27 '23 at 11:39
  • ZAY, I appreciate your interest in my work very much, and I adore the solutions you provided to other users at Stack Overflow. Yet I am afraid my app is far from being ready to download. But maybe you already have an idea, how the vertical angle of the camera can affect the shadow of an object - or rather the resolution of the shadow. It seems that the 30° angle mentioned before is the first point at which the shadow has the rough (very pixely) shape of the ship (like you can see on my picture). Below, it‘s like the shadow is getting zoomed in (though at constant size). – ULI Mar 28 '23 at 05:33
  • Please have a look at my answer. – ZAY Mar 28 '23 at 07:10

2 Answers2

1

To render SceneKit's Depth Map (a.k.a. projected) shadows without artifacts, use the following values.

.shadowBias

shadowBias property specifies the correction to apply to the shadow map to correct acne artifacts (or so called shadow banding artifacts on light-facing surfaces). It's multiplied by an implementation-specific value to create a constant depth offset. Try to increase or decrease this value.

.shadowMapSize

The larger the shadow map is, the more precise the shadows are but the slower the computation is. If set to (0, 0) the size of the shadow map is automatically chosen. So, better try the default.

.shadowSampleCount

On iOS 10.0 and greater or macOS 10.12 and greater, when the shadowSampleCount is set to 0, a default sample count is chosen depending on the platform.


self.dirLightNode.light?.shadowMapSize = CGSizeZero         // default
self.dirLightNode.light?.shadowRadius = 3.0                 // default

self.dirLightNode.light?.shadowBias = 0.1               
self.dirLightNode.light?.shadowSampleCount = 0          
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • Thanks for telling these details, especially the order of magnitude of the changes. Yet none of them make a change - no change at all. And somehow I have the impression that this mechanism is somehow on purpose: in reality, if the camera view is completely horizontal, shadows are shrunken in their height. So a physics engine could assume that the full complexity of the shadow-shape is not required and thus reduce it to a standard shape but blurry? Maybe this is a behavior to spare GPU capacity. And maybe it can be switched of somehow? – ULI Mar 26 '23 at 14:58
1

Well, it is hard to tell, what's the issue with your project. Here is my configuration, how I setup the Lights (in this case a directional Light). Give it a try and let me know, if it helped you in any way.

static func getSceneLight() -> SCNLight {
    
    let light = SCNLight()
    
    // Light Config
    light.type                                 = .directional
    light.color                                = UIColor.white
    light.shadowMode                           = .forward
    light.castsShadow                          = true
    light.categoryBitMask                      = -1 // Shine on Everything
    light.shadowCascadeCount                   = 4  // Important for good Shadows
    light.automaticallyAdjustsShadowProjection = true // NEW, good for sharp shadows
    
    print("Scene Light requested.")
    
    // Not Configured
    // light.shadowCascadeSplittingFactor         = 0.1  // DO NOT SET!!!
    // light.shadowRadius                         = 2.0  // 3.25 // suggestion by StackOverflow
    // light.shadowCascadeCount                   = 3    // suggestion by StackOverflow
    // light.shadowCascadeSplittingFactor         = 0.09 // suggestion by StackOverflow
    // light.shadowBias                           = 2.0  // 0.1 // (in testing)
    // light.parallaxCorrectionEnabled            = true // what is this
    // light.shadowSampleCount                    = 1.0  // DO NOT SET!!!
    // light.zNear                                = 0.1
    // light.zFar                                 = 1000000.0
    // light.orthographicScale                    = 1.0
    
    return light
    
}

As you can see, there are many options unconfigured. I had no luck using them. I hope, I could help you.

PS: usually I use an intensity value of 1000.0 and also add an ambient light with same color and intensity of 250.0 (I do this in a post configuration)

ZAY
  • 3,882
  • 2
  • 13
  • 21
  • Believe it or not, the problem is solved. At first, I tried 'shadowCascadeCount', because I have never heard of that before. And it worked!!! Second, I found out that this is not at all documented (or better: it is documented to exist, but there is no description). So, because of the nice result, I tried to decrease my shadowmapsize and got again a pixely shadow, but not dependent on the angle of the camera. So somehow these 2 seem to be connected. Anyone have a clue if larger numbers of SCC mean higher GPU usage? Anyway: thank You so much! – ULI Mar 28 '23 at 16:11
  • I did not notice a real impact on GPU performence using the SCC value described. Glad I could help. (btw: the ShadowMapSize make a really big impact in Memory and GPU usage) – ZAY Mar 28 '23 at 17:06