2019年3月5日火曜日

ARKitサンプルにライトを追加しよう



ARKitサンプルにライトを追加しよう


標準のARKitサンプルで表示される飛行機には影が無く、のっぺりとした印象でリアリティさが欠けてしまっています。

AR空間にライトを追加すると飛行機の凹凸に合わせて影ができて、よりリアルになります。

また、ARKitの特徴としてカメラのビデオキャプチャより周囲の現実世界の明るさを取得してAR空間のライトの強弱を調整することができます。





今回はそのライトの追加方法を紹介します。


修正方針


  1. ライトをSceneに追加する
  2. 現在のビデオキャプチャ情報を取得するハンドラを追加する
  3. 明るさ情報をライトに反映させる


ライトをSceneに追加する


viewDidLoad()に次のコードを追加します。
// Setup Omni Light
let light = SCNLight()
light.type = .omni

let LightNode = SCNNode()
LightNode.light = light
LightNode.name = "light"
LightNode.position = SCNVector3(-1,2,-1)
scene.rootNode.addChildNode(LightNode)

ライトの種類は.typeで指定をします。

次のように色々と種類がありますが、特にこだわりが無ければ電球の光を模したOmniライトが良いと思います。




この状態で動作させると飛行機に陰影がついてリアルになります。





現在のビデオキャプチャ情報を取得するハンドラを追加する


ビデオキャプチャ情報は現在のARFrameから取得することができます。

そして、ARFrameはARSessionから取得することができ、ARSessionが動いている状態では絶えず更新がされます。




ではいつARSessionを参照すれば良いのかという疑問が湧いてきますが、それは下記のハンドラが実行された時に取得するのがセオリーです。
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval){
}

上記のメソッドはARSCNViewDelegateのデリゲートメソッドであり、AR空間に何か変化がおきた時に呼ばれます。

当然、光量が変化した時にも呼ばれるためこのタイミングで処理をするのが一番です。

単純に上記コードを追加すればOKです。


明るさ情報をライトに反映させる


現在の明るさ情報はcurrentFrameのlightEstimate プロパティより取得することができます。


lightEstimateには次の2つのプロパティがあり、それぞれ先程追加したlightに設定します。

  • ambientIntensity → 光量
  • ambientColorTemperature → 光色


具体的には次のように設定をします。
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
    let ambientIntensity = lightEstimate.ambientIntensity
    let ambientColorTemperature = lightEstimate.ambientColorTemperature

    if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
        guard let light = lightNode.light else { return }
        light.intensity = ambientIntensity
        light.temperature = ambientColorTemperature
    }
}

変更後のソースコード


import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set the view's delegate
        sceneView.delegate = self
        
        // Show statistics such as fps and timing information
        sceneView.showsStatistics = true
        
        // Create a new scene
        let scene = SCNScene(named: "art.scnassets/ship.scn")!
        
        if let ship = scene.rootNode.childNode(withName: "ship", recursively: true) {
            ship.isHidden = true
        }
        
        // Setup Omni Light
        let light = SCNLight()
        light.type = .omni

        let LightNode = SCNNode()
        LightNode.light = light
        LightNode.name = "light"
        LightNode.position = SCNVector3(-1,2,-1)
        scene.rootNode.addChildNode(LightNode)
        
        // Set the scene to the view
        sceneView.scene = scene
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // Create a session configuration
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = [.horizontal]
        
        // Run the view's session
        sceneView.session.run(configuration)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // Pause the view's session
        sceneView.session.pause()
    }

    // MARK: - ARSCNViewDelegate
    
/*
    // Override to create and configure nodes for anchors added to the view's session.
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        let node = SCNNode()
     
        return node
    }
*/
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        
        if let ship = sceneView.scene.rootNode.childNode(withName: "ship", recursively: true) {
            ship.removeFromParentNode()
            node.addChildNode(ship)
            
            ship.isHidden = false
            
            let configuration = ARWorldTrackingConfiguration()
            sceneView.session.pause()
            sceneView.session.run(configuration)
        }
    }
    
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        guard let lightEstimate = self.sceneView.session.currentFrame?.lightEstimate else { return }
        let ambientIntensity = lightEstimate.ambientIntensity
        let ambientColorTemperature = lightEstimate.ambientColorTemperature
        
        if let lightNode = self.sceneView.scene.rootNode.childNode(withName: "light", recursively: true) {
            guard let light = lightNode.light else { return }
            light.intensity = ambientIntensity
            light.temperature = ambientColorTemperature
        }
    }
    
    func session(_ session: ARSession, didFailWithError error: Error) {
        // Present an error message to the user
        
    }
    
    func sessionWasInterrupted(_ session: ARSession) {
        // Inform the user that the session has been interrupted, for example, by presenting an overlay
        
    }
    
    func sessionInterruptionEnded(_ session: ARSession) {
        // Reset tracking and/or remove existing anchors if consistent tracking is required
        
    }
}

0 件のコメント:

コメントを投稿