LoginSignup
6
11

More than 3 years have passed since last update.

【平面検出】で、ARをより現実っぽく

Last updated at Posted at 2021-04-25

ARPlaneDetection【平面検出】

平面検出で床や壁を利用して、オブジェクトを配置できます。

IMG_4557.PNG

1、平面検出設定でARセッションを開始

ARSCNViewから平面のトラッキング結果を受け取るために、ARSCNViewDelegateを継承します。

class ViewController: UIViewController, ARSCNViewDelegate {

     override func viewDidLoad() {
        super.viewDidLoad()

        sceneView.delegate = self
    }

}

Plane Detection【平面検出設定】のオプションをつけてARWorldTrackingConfiguration【世界検出構成】でARKitのセッションを開始します。
ARWorldTrackingConfiguration.PlaneDetection.horizontalを設定すると地面や床やテーブル面など水平面を検出します。
.verticalを設定すると、壁などの垂直面を検出します。
.horizontal.verticalを同時に検出することもできます。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    let configuration = ARWorldTrackingConfiguration()
    configuration.planeDetection = [.horizontal, .vertical]
    sceneView.session.run(configuration)
}

2、検出結果を受け取る

アンカーを検出したら情報を得る

ARSCNViewのデリゲートメソッドで平面検出結果を取得します。

// 新しいアンカーがみつかったときに呼ばれるデリゲートメソッド
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    print(planeAnchor)
}

床を検出してみた結果

平面アンカーのid、向き(horizontal)、平面アンカーの中心、範囲 (1.4m,1.1m)を取得しました。

ARPlaneAnchor: 0x1165ab6c0 identifier="C843F11D-80C0-41DB-B7F5-492BA12B52FD" transform= alignment=horizontal center=(-0.011180 0.000000 -0.055902) extent=(1.498166 0.000000 1.185117)IMG_4539のコピー.png

わかりやすいようにPlane geometryを持ったSCNNodeを平面に自動追加された空のSCNNodeにaddChildしてみます。

var visualizedPlaneNode = SCNNode() // 平面視覚化用のノードをつくる

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    print(planeAnchor)

    let planeGeometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z)) // アンカーと同じ大きさのPlaneGeometryをつくる
    visualizedPlaneNode.geometry = planeGeometry // ノードのgeometryにPlaneを設定
    planeGeometry.firstMaterial?.diffuse.contents = UIColor.blue.withAlphaComponent(0.7) // 青色にしてみる

    visualizedPlaneNode.simdPosition = planeAnchor.center // アンカーの中心に配置
    visualizedPlaneNode.eulerAngles.x = -.pi / 2  // SCNPlaneはデフォルトでは垂直なため、回転させます。

    node.addChildNode(visualizedPlaneNode) // 平面に自動追加された空ノードに入れる
}

IMG_4541.PNG

平面情報をアップデートしていく

別のレンダラーメソッドで平面情報をアップデートしていきます。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor, // 更新された平面アンカーを取得
          let visualizedPlaneNode = node.childNodes.first, // 自動追加された空のノードの子ノードを確認
          let planeGeometry = visualizedPlaneNode.geometry as? SCNPlane else { return } // 子ノードのplanegeometryを確認
    planeGeometry.width = CGFloat(planeAnchor.extent.x) // 幅を更新された平面アンカーの幅に
    planeGeometry.height = CGFloat(planeAnchor.extent.z) // 高さを更新された平面アンカーの高さに
    visualizedPlaneNode.simdPosition = planeAnchor.center // 中心を更新された平面アンカーの中心に
}

Apr-25-2021 10-11-53.gif

ちなみに、組み込まれている検出された平面アンカーそのものの正方形ではないgeometryの形を取ることもできます。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor,
          let meshGeometry = ARSCNPlaneGeometry(device: sceneView.device!)
            else { fatalError("Can't create plane geometry") }
    meshGeometry.update(from: planeAnchor.geometry)
    meshNode = SCNNode(geometry: meshGeometry)
    meshNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red.withAlphaComponent(0.7)
    node.addChildNode(meshNode)
}

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

    guard let planeAnchor = anchor as? ARPlaneAnchor
    if let meshGeometry = meshNode.geometry as? ARSCNPlaneGeometry {
        meshGeometry.update(from: planeAnchor.geometry)
    }
}

Apr-25-2021 10-27-38.gif

3、検出した平面の利用

壁紙を貼ってみる

planeGeometry.firstMaterial?.diffuse.contents = UIImage(named:"flowerWallPaper")

IMG_4548.PNG

オブジェクトをおいてみる

画面をタップした先に平面geometry(ここではARPlaneGeometryをとってます)があれば、オブジェクトのy座標を平面ノードに合わせて、x,z軸を平面とタップの交差点に合わせます。

@objc func sceneViewTapped(recognizer:UITapGestureRecognizer) {
    let location = recognizer.location(in: sceneView)

    let hitResults = sceneView.hitTest(location, options: [:])
    if !hitResults.isEmpty {
        guard let planeNode = hitResults.first?.node else {return}

        let interiorHeight = interior.boundingBox.max.y-interior.boundingBox.min.y // オブジェクトノードの高さを計算
        let tappedCoodinates = hitResults.first?.localCoordinates // タップと平面ノードの交差点
        interior.position = SCNVector3(tappedCoodinates!.x, interiorHeight/2, tappedCoodinates!.z) // オブジェクトの原点はオブジェクトの中心にしたので、高さの半分だけ上に置く
        planeNode.addChildNode(interior)
}

Apr-25-2021 12-43-24.gif

壁でオブジェクトを遮蔽する

.verticalの子ノードを手前にレンダリングされるようにすることで、後ろのノードが壁に隠れている表現ができます。

if planeAnchor.alignment == .vertical {
    planeNode.renderingOrder = -1            
    planeNode.geometry!.firstMaterial!.colorBufferWriteMask = []
}

IMG_4557.PNG

🐣


フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

6
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
11