SceneKit – Shadow from directional light dependent on camera angle

225 views Asked by At

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?

2

There are 2 answers

1
Andy Jazz On

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          
2
ZAY On

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)