Example code for fading in / out RealityKit spotlights

Can anyone provide or point me to example code to fade in / out spotlights over 1 second?

Did not find anything on this topic in the docs: https://developer.apple.com/documentation/realitykit/spotlight

Answered by Vision Pro Engineer in 797888022

Hi @mesqueeb,

The following code demonstrates one approach to fading a spotlight in and out. It works by increasing and decreasing the intensity property of a SpotLightComponent.

More specifically, RealityKit's Entity Component System is used to create a SpotLightFaderComponent which stores information about the spotlight fade in/out animation, such as its current state and duration. A SpotLightFaderSystem is then responsible for updating the intensity poperty of the SpotLightComponent every frame in correspondance with the animation data held in SpotLightFaderComponent.

enum FaderState {
    case fadingIn
    case fadingOut
    case stopped
}

class SpotLightFaderComponent: Component {
    private(set) var state: FaderState = .stopped
    let targetIntensity: Float
    let fadeInDuration: Float
    let fadeOutDuration: Float
    
    init(targetIntensity: Float, fadeInDuration: Float, fadeOutDuration: Float) {
        self.state = .stopped
        self.targetIntensity = targetIntensity
        self.fadeInDuration = fadeInDuration
        self.fadeOutDuration = fadeOutDuration
    }
    
    func fadeIn() { state = .fadingIn }
    func fadeOut() { state = .fadingOut }
    func stop() { state = .stopped }
}

struct SpotLightFaderSystem: System {
    private let query = EntityQuery(where: .has(SpotLightFaderComponent.self) && .has(SpotLightComponent.self))
    
    init(scene: RealityKit.Scene) { }
    
    func update(context: SceneUpdateContext) {
        for spotlightFaderEntity in context.entities(matching: self.query, updatingSystemWhen: .rendering) {
            guard let spotlightFader = spotlightFaderEntity.components[SpotLightFaderComponent.self],
                  let spotlight = spotlightFaderEntity.components[SpotLightComponent.self] else { continue }
            // Get the current spotlight intensity
            var intensity = spotlight.intensity
            switch spotlightFader.state {
            case .fadingIn:
                // Increase spotlight intensity up to target intensity when fading in
                if intensity < spotlightFader.targetIntensity {
                    // (deltaTime / fadeInDuration) gives the percentage of the total fadeInDuration that has elapsed since last frame,
                    // so multiplying it by targetIntensity gives the intensity increase over the last frame
                    intensity += (Float(context.deltaTime) / spotlightFader.fadeInDuration) * spotlightFader.targetIntensity
                }
                if intensity >= spotlightFader.targetIntensity {
                    intensity = spotlightFader.targetIntensity
                    spotlightFader.stop()
                }
            case .fadingOut:
                // Decrease spotlight intensity down to zero when fading out
                if intensity > 0 {
                    // (deltaTime / fadeOutDuration) gives the percentage of the total fadeOutDuration that has elapsed since last frame,
                    // so multiplying it by targetIntensity gives the intensity decrease over the last frame
                    intensity -= (Float(context.deltaTime) / spotlightFader.fadeOutDuration) * spotlightFader.targetIntensity
                }
                if intensity <= 0 {
                    intensity = 0
                    spotlightFader.stop()
                }
            case .stopped:
                // Do nothing when fading animation is stopped
                continue
            }
            // Update the spotlight intensity
            spotlightFaderEntity.components[SpotLightComponent.self]?.intensity = intensity
        }
    }
}

To create a spotlight that can fade in and out using the code above, add an entity with both a SpotLightComponent and a SpotLightFaderComponent to your RealityView. You can use the fadeInDuration and fadeOutDuration properties of the SpotLightFaderComponent to control the duration of the fade in and fade out animations, and use the targetIntensity property to set the default intensity the spotlight returns to when fading back in. To initiate a spotlight fade out animation, call the fadeOut() method on the SpotLightFaderComponent. Likewise, call the fadeIn() method to fade the spotlight back in.

@State var spotlightFader:SpotLightFaderComponent?
    
var body: some View {
    RealityView { content, attachments in
        // Register the spotlight fader system
        SpotLightFaderSystem.registerSystem()
        
        // Create an entity with a spotlight component
        let spotlightEntity = Entity()
        let spotlight = SpotLightComponent()
        spotlightEntity.components.set(spotlight)
        
        // Create a spotlight fader component and add it to the same entity
        let spotlightFader = SpotLightFaderComponent(targetIntensity: spotlight.intensity, fadeInDuration: 1, fadeOutDuration: 1)
        spotlightEntity.components.set(spotlightFader)
        self.spotlightFader = spotlightFader
        
        // Create a ground plane entity to help visualize the spotlight
        let groundPlaneEntity = ModelEntity(mesh: .generatePlane(width: 5, depth: 5), materials: [SimpleMaterial()])
        
        // Point the spotlight toward the ground
        spotlightEntity.transform.rotation = simd_quatf(angle: -90 * (.pi / 180), axis: [1,0,0])
        spotlightEntity.position = [0, 2, -1.5]
        
        // Add the spotlight and the ground plane to the scene
        content.add(spotlightEntity)
        content.add(groundPlaneEntity)

        // Add fade in/out buttons to scene
        guard let fadeInButtonEntity = attachments.entity(for: "fadeInButton"),
              let fadeOutButtonEntity = attachments.entity(for: "fadeOutButton") else { return }
        fadeInButtonEntity.position = [0.1, 1, -0.5]
        fadeOutButtonEntity.position = [-0.1, 1, -0.5]
        content.add(fadeInButtonEntity)
        content.add(fadeOutButtonEntity)

    } attachments: {
        Attachment(id: "fadeInButton") {
            Button("Fade In") {
                guard let spotlightFader = self.spotlightFader else { return }
                spotlightFader.fadeIn()
            }
        }
        Attachment(id: "fadeOutButton") {
            Button("Fade Out") {
                guard let spotlightFader = self.spotlightFader else { return }
                spotlightFader.fadeOut()
            }
        }
    }
}
Accepted Answer

Hi @mesqueeb,

The following code demonstrates one approach to fading a spotlight in and out. It works by increasing and decreasing the intensity property of a SpotLightComponent.

More specifically, RealityKit's Entity Component System is used to create a SpotLightFaderComponent which stores information about the spotlight fade in/out animation, such as its current state and duration. A SpotLightFaderSystem is then responsible for updating the intensity poperty of the SpotLightComponent every frame in correspondance with the animation data held in SpotLightFaderComponent.

enum FaderState {
    case fadingIn
    case fadingOut
    case stopped
}

class SpotLightFaderComponent: Component {
    private(set) var state: FaderState = .stopped
    let targetIntensity: Float
    let fadeInDuration: Float
    let fadeOutDuration: Float
    
    init(targetIntensity: Float, fadeInDuration: Float, fadeOutDuration: Float) {
        self.state = .stopped
        self.targetIntensity = targetIntensity
        self.fadeInDuration = fadeInDuration
        self.fadeOutDuration = fadeOutDuration
    }
    
    func fadeIn() { state = .fadingIn }
    func fadeOut() { state = .fadingOut }
    func stop() { state = .stopped }
}

struct SpotLightFaderSystem: System {
    private let query = EntityQuery(where: .has(SpotLightFaderComponent.self) && .has(SpotLightComponent.self))
    
    init(scene: RealityKit.Scene) { }
    
    func update(context: SceneUpdateContext) {
        for spotlightFaderEntity in context.entities(matching: self.query, updatingSystemWhen: .rendering) {
            guard let spotlightFader = spotlightFaderEntity.components[SpotLightFaderComponent.self],
                  let spotlight = spotlightFaderEntity.components[SpotLightComponent.self] else { continue }
            // Get the current spotlight intensity
            var intensity = spotlight.intensity
            switch spotlightFader.state {
            case .fadingIn:
                // Increase spotlight intensity up to target intensity when fading in
                if intensity < spotlightFader.targetIntensity {
                    // (deltaTime / fadeInDuration) gives the percentage of the total fadeInDuration that has elapsed since last frame,
                    // so multiplying it by targetIntensity gives the intensity increase over the last frame
                    intensity += (Float(context.deltaTime) / spotlightFader.fadeInDuration) * spotlightFader.targetIntensity
                }
                if intensity >= spotlightFader.targetIntensity {
                    intensity = spotlightFader.targetIntensity
                    spotlightFader.stop()
                }
            case .fadingOut:
                // Decrease spotlight intensity down to zero when fading out
                if intensity > 0 {
                    // (deltaTime / fadeOutDuration) gives the percentage of the total fadeOutDuration that has elapsed since last frame,
                    // so multiplying it by targetIntensity gives the intensity decrease over the last frame
                    intensity -= (Float(context.deltaTime) / spotlightFader.fadeOutDuration) * spotlightFader.targetIntensity
                }
                if intensity <= 0 {
                    intensity = 0
                    spotlightFader.stop()
                }
            case .stopped:
                // Do nothing when fading animation is stopped
                continue
            }
            // Update the spotlight intensity
            spotlightFaderEntity.components[SpotLightComponent.self]?.intensity = intensity
        }
    }
}

To create a spotlight that can fade in and out using the code above, add an entity with both a SpotLightComponent and a SpotLightFaderComponent to your RealityView. You can use the fadeInDuration and fadeOutDuration properties of the SpotLightFaderComponent to control the duration of the fade in and fade out animations, and use the targetIntensity property to set the default intensity the spotlight returns to when fading back in. To initiate a spotlight fade out animation, call the fadeOut() method on the SpotLightFaderComponent. Likewise, call the fadeIn() method to fade the spotlight back in.

@State var spotlightFader:SpotLightFaderComponent?
    
var body: some View {
    RealityView { content, attachments in
        // Register the spotlight fader system
        SpotLightFaderSystem.registerSystem()
        
        // Create an entity with a spotlight component
        let spotlightEntity = Entity()
        let spotlight = SpotLightComponent()
        spotlightEntity.components.set(spotlight)
        
        // Create a spotlight fader component and add it to the same entity
        let spotlightFader = SpotLightFaderComponent(targetIntensity: spotlight.intensity, fadeInDuration: 1, fadeOutDuration: 1)
        spotlightEntity.components.set(spotlightFader)
        self.spotlightFader = spotlightFader
        
        // Create a ground plane entity to help visualize the spotlight
        let groundPlaneEntity = ModelEntity(mesh: .generatePlane(width: 5, depth: 5), materials: [SimpleMaterial()])
        
        // Point the spotlight toward the ground
        spotlightEntity.transform.rotation = simd_quatf(angle: -90 * (.pi / 180), axis: [1,0,0])
        spotlightEntity.position = [0, 2, -1.5]
        
        // Add the spotlight and the ground plane to the scene
        content.add(spotlightEntity)
        content.add(groundPlaneEntity)

        // Add fade in/out buttons to scene
        guard let fadeInButtonEntity = attachments.entity(for: "fadeInButton"),
              let fadeOutButtonEntity = attachments.entity(for: "fadeOutButton") else { return }
        fadeInButtonEntity.position = [0.1, 1, -0.5]
        fadeOutButtonEntity.position = [-0.1, 1, -0.5]
        content.add(fadeInButtonEntity)
        content.add(fadeOutButtonEntity)

    } attachments: {
        Attachment(id: "fadeInButton") {
            Button("Fade In") {
                guard let spotlightFader = self.spotlightFader else { return }
                spotlightFader.fadeIn()
            }
        }
        Attachment(id: "fadeOutButton") {
            Button("Fade Out") {
                guard let spotlightFader = self.spotlightFader else { return }
                spotlightFader.fadeOut()
            }
        }
    }
}

@Vision Pro Engineer thank you for the sample and suggestion to use an ECS system to do so. I was hoping for an action similar to the fromToByAction: https://developer.apple.com/documentation/realitykit/fromtobyaction that we can apply to the intensity, just like we can with other properties like position etc. Do you know if this is possible?

Example code for fading in / out RealityKit spotlights
 
 
Q