Search code examples
swiftscenekitaugmented-realityarkitshadow

Cast shadows from distant light


I have an AR app in which I have calculated current sun position from the user. I reduced this position to distance of 10,000 meters, so it kinda "stays" in place when I move inside the scene. Now I would like to cast shadows from this node on other nodes inside my scene. I tried few types of lightning, but none was successful. Some didn't drop shadow at all, others behave strange. What would be the best method to create a light source from such distant node to cast shadows on invisible floor node? Should I also add ambient light to the scene? Can it be added to camera node, or should be somewhere else?


Solution

  • Use directional light for Sun simulation (Sun has parallel rays that for us are primary rays from very distant light source) and use ambient light for simulating fake secondary rays (in case you do not use Global Illumination).

    // DIRECTIONAL LIGHT for `primary light rays` simulation
    let lightNode = SCNNode()
    lightNode.light = SCNLight()
    lightNode.light!.type = .directional
    lightNode.light!.castsShadow = true
    lightNode.light!.shadowMode = .deferred
    lightNode.light!.categoryBitMask = -1
    lightNode.light!.automaticallyAdjustsShadowProjection = true
    //lightNode.light!.maximumShadowDistance = 11000
    lightNode.position = SCNVector3(x: 0, y: -5000, z: 0)
    lightNode.rotation = SCNVector4(x: -1, y: 0, z: 0, w: .pi/2)
    scene.rootNode.addChildNode(lightNode)
    

    Tip: The position of directional light could be any (in the following code it is even under the plane y:-5000), the most important thing is direction of directional light!

    Also directional light has no falloff parameter or, so called, quadratic light decay.

    // AMBIENT LIGHT for `fake secondary light rays` simulation
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light!.type = .ambient
    ambientLightNode.light!.intensity = 1000
    ambientLightNode.light!.color = NSColor.white
    scene.rootNode.addChildNode(ambientLightNode)
    

    Do not mistakenly use this Boolean value:

    lightNode.castsShadow = true
    

    instead of this one:

    lightNode.light!.castsShadow = true
    

    The Boolean value lightNode.castsShadow determines whether SceneKit renders the node’s contents into shadow maps.

    And here's a screenshot if you wish to enable shadows in manually created directional light:

    enter image description here

    Sometimes, you get some benefits if you attach a light to the camera. In that case object's surfaces with normals parallel to light's rays are lit.