'Make SceneKit light move with camera

I'm trying to achieve a spotlight effect whereby the model is illuminated from the camera's point of view.

In other words, the camera should have a light that always points at the model.

However, the model is always illuminated as if from a stationary light source rather than a dynamic one. The same surfaces of the model are illuminated regardless of the camera's position. If I rotate the camera to the back of the model, all I see is black.

I've seen several other SO questions on this topic, but they did not solve my problem.

In viewDidLoad:

let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()

scene.rootNode.addChildNode(cameraNode)

let light = SCNLight()
light.type = .directional //I've tried the other options, here, to no avail.
light.color = UIColor.init(red: 255/255, green: 255/255, blue: 255/255, alpha: 0.7)
light.castsShadow = true

cameraNode.light = light
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

let scnView = self.view as! SCNView
        
scnView.pointOfView = cameraNode

To move the camera, I'm using the default camera controls via allowsCameraControl = true.

Question: How can I achieve the desired behavior, whereby the camera illuminates the model like a flashlight?

Thank you!



Solution 1:[1]

I got this working by doing the following:

  1. Gave the light its own node, separate from the camera.
  2. Added a look-at constraint so the node holding the light always points at the model.
  3. Set up the SCNSceneRendererDelegate protocol and implemented renderer(_:updateAtTime:). Inside it, set the position of the node holding the light to the camera node's position.

Here's what the node configuration looks like:

        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()

        scene.rootNode.addChildNode(cameraNode)
        
        
        let lightTargetNode = SCNNode()
        lightTargetNode.position = SCNVector3(x: 0.0, y: 0.0, z: 0.0)
        scene.rootNode.addChildNode(lightTargetNode)

        let lightHolder = SCNNode()
        let light = SCNLight()
        light.type = .directional
        light.color = UIColor.init(red: 255/255, green: 255/255, blue: 255/255, alpha: 0.7)
        light.castsShadow = false

        let lightConstraint = SCNLookAtConstraint(target: lightTargetNode)
        lightHolder.constraints = [lightConstraint]
        lightHolder.light = light
        lightHolder.name = "lightHolder"

        scene.rootNode.addChildNode(lightHolder)
        lightHolder.position = SCNVector3(0, 0, 15)

This is a heavy-handed technique; it'd be a lot better to achieve this effect without using SCNSceneRendererDelegate, but this is the only way I was able to get this working.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 West1