🚪

【AR入門】ARKitでどこでもドアを実装してみた

2023/02/06に公開

はじめに

大学のとある先輩の影響でAR分野に魅力を感じ、最近AR分野をかじり始めた大学生です。
この記事では、過去の自分みたいな「ARやってみたいけど、どこから始めればいいんだ。。。」や「普段は全く別の技術領域触っているけど、趣味としてARもかじってみたい!」という方に向けて、比較的に簡単なARアプリの作り方を紹介します。

環境

  • MacOS (12.6以上)
  • iPhoneまたはiPad (iOS 16.0以上)
    ※ 実機デバッグで使います。iPhoneの機種によってはARKit非対応のものもあるのでこちらから確認してください。

開発

環境構築

今回は、ARKitというXcodeに付属しているARフレームワークを用います。
そのため、Xcodeをインストールしていない方はこちらからインストールしておいてください。
https://developer.apple.com/jp/augmented-reality/arkit/

ARKitの初期設定

ここでは、ARKitのプロジェクトを作成します。

  1. Xcodeを起動します。
  2. Create a new Xcode projectを選択。
  3. iOSタブを選択し、Augmented Reality Appを選択し、「Next」をクリック。
  4. 各欄に下記を入力し、「Next」をクリック。
項目欄 入力 説明
Product Name ARWarpDoor(任意) プロダクトの名前です。
Team (自分のAppleID) Xcodeに登録済みのAppleIDを選択。初めての方はプロジェクト生成後に設定してください。
Organization Identifier ar.com(任意) アプリの組織の識別子です。
Bundle Identifier 未入力 IDは自動で生成されます。
Interface Storyboard 画面作成時のフレームワークです。
Language Swift 今回はSwiftで実装します。
Content Technology SceneKit 3Dコンテンツの作成方法です。
Include Tests 未選択 Unitテストの設定です。
  1. アプリを展開する任意のディレクトリを選択して、Createをクリック。

以上でプロジェクトの作成は完了です。

ディレクトリ構成について


今回は、主にart.scnassetsViewController.swiftのファイルを扱ってARアプリの実装をします。

ファイル類 説明
art.scnassets 空間上に出現させる3Dモデルやテクスチャを格納するフォルダ。
ViewController.swift モデルをどのように出現させるかを記述するためのソースコード。

ソースコード

まずは、ViewController.swiftのソースコードを変更するところから始めます。

ViewController.swift
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
    }
    
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        // 型チェック
        if anchor is ARPlaneAnchor {
            // ARPlaneAnchor型にキャスト
            let planeAnchor = anchor as! ARPlaneAnchor
            
            // classroom.scnを読み込む
            let roomScene = SCNScene(named: "art.scnassets/classroom.scn")!
            
            // シーンからclassroomAllを取り出す
            if let roomNode = roomScene.rootNode.childNode(withName: "classroomAll", recursively: true) {
                
                // 平面検出の場所をレンダリング位置に設定する
                roomNode.position = SCNVector3(x: planeAnchor.center.x, y: planeAnchor.center.y, z: planeAnchor.center.z)
                
                // ノードを追加
                node.addChildNode(roomNode)
            }
            
            // 平面検出を止める
            let configuration = ARWorldTrackingConfiguration()
            sceneView.session.pause()
            sceneView.session.run(configuration)
            
        } else {
            return
        }
    }
    
    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()
    }

}

全体のソースコードは上記のようになります。
要所ごとで詳しく見ていきます。

// classroom.scnを読み込む
let roomScene = SCNScene(named: "art.scnassets/classroom.scn")!

// シーンからclassroomAllを取り出す
if let roomNode = roomScene.rootNode.childNode(withName: "classroomAll", recursively: true) {

// 平面検出の場所をレンダリング位置に設定する
roomNode.position = SCNVector3(x: planeAnchor.center.x, y: planeAnchor.center.y, z: planeAnchor.center.z)

// ノードを追加
node.addChildNode(roomNode)

この箇所でart.scnassets直下にある3Dモデルを読み込み、平面検知した地点をモデルの原点としています。

// 平面検出を止める
let configuration = ARWorldTrackingConfiguration()
sceneView.session.pause()
sceneView.session.run(configuration)

また、3Dモデルのレンダリング位置がズレる可能性があるので、平面検知が完了したら止めるように設定します。
ARWorldTrackingConfiguration()を設定することで平面検知をリセットしています。

モデルのダウンロード

今回はARどこでもドアを作成するため、ワープ先の部屋のモデルをart.scnassets内に組み込む必要があります。

今回はCGtraderという3Dモデル素材サイトからワープ先となる部屋のモデルをダウンロードします。

ダウンロードはこちらから。

ダウンロードには数十秒かかります。
その後、3Dモデルのダウンロードするファイル形式一覧が表示されるので、その中から、CLASSROOM.daeをダウンロードします。

ダウンロードしたファイルをart.scnassetsの直下に移動し、分かりやすいようにファイル名をclassroom.daeに変更しておきましょう。

ここで、「なんでdaeファイル形式(COLLADA)なん??」って疑問が浮かんだ人は以下の記事を参照してください。
https://qiita.com/shu223/items/0161f82d000d5cdf009c

.fbxは配布されているSDKを使うことでインポートできるが、頻繁にメンテされてるわけでもなさそうなので、そこに依存したくない
.objはSCNSceneSourceインポートできるが、そのファイル形式自体がアニメーションをサポートしていない
.3ds, .max はSceneKitにインポートできない(サードパーティ製ライブラリとかはあるかもしれない)
そしてこのCOLLADAの.daeファイルは大抵のモデリングツールでサポートされているらしい。

ただ、.dae形式のままでは使えないので、.scn形式に変換します。
classroom.daeファイルを選択し、Editor->Convert to SceneKit scene file format(.scn)を選択します。

変換すると、変換元のclassroom.daeと変換後のclassroom.scnが存在している状態になるので、変換元の.daeファイルは削除しておきましょう。

以上で、ワープ先の部屋モデルの準備は完了です。

モデルの加工

さあ、ここまで3Dモデルの下準備も完了したのでここからは、ダウンロードしたモデルを加工してワープ先の部屋を完成させたいと思います。

このモデルを見ると、窓が開いていたり、壁一面と天井なども空いています。
今回は、どこでもドアをモチーフにした異空間にワープできる世界を実装したいので、完全密閉空間にしていきます。

  1. まず、壁が一面だけ空いている状態なので、壁を新たに追加します。
    Xcodeの右上の「」ボタンをクリックして、Objectの追加を行います。

  2. ポップアップが現れたら、「plane」と入力し、候補に出てきたPlaneというオブジェクトを3D空間内にドラッグ&ドロップします。

    そうすると、、、

    ※ 字が小さくてすみません...

  3. planeという名のオブジェクトがScene graphに追加されるのでaddWallに名称変更しておきます。(命名は任意で大丈夫です。)

  4. 次にオブジェクトの位置とサイズを定めます。
    Xcodeの右上のマークをクリックし、PositionEulerScaleの項目に以下の値を入力します。

  5. このままでもいいですが、色が不自然なので周りの壁と同じ色にします。
    下図のマークをクリックし、ShadingLambertに変更します。
    (輝度を均一にする。)

  6. Diffuseを選択し、出現したポップアップ画面においてスポイトのマークをクリックし、他の壁をクリックします。
    (色を抽出する。)

これで壁ができました。

同様に、天井も追加していきましょう。

  1. 壁と同様に、Planeのオブジェクトを追加して、addCeilingに名称変更します。
  2. PositionEulerScaleの値は以下の通りです。
  3. ShadingLambertに変更します。
    色は、壁との境界を明確にしたいので、ここではそのままにしておきます。

これで、密閉空間になっt......

ってないですね...窓が開いていました。
ここで、新たにオブジェクトを使用してもいいですが、もっと楽な方法があるので紹介します。

Scene graphより、Window-LまたはWindow-Rを選択し、Material inspector(先ほどの色設定のメニュー)のDouble sidedにチェックを入れます。


すると......

窓を閉めることができます!

ドアは削除しても残しても大丈夫です。(ここでは削除しておきます。)
ちなみに、ドアの部分をクリックしてDeleteキーで削除できます。

このままだと、密閉空間内が暗くなるのでライトアップします。

  1. Planeと同様に、オブジェクトの追加画面からOmni lightを展開します。
  2. 名称はaddInsideLightにしておきます。
  3. PositionEulerScaleの値は以下の通りです。
  4. 外側のライトも追加します。
    同様に、Omni Lightを展開し、addOutsideLightに名称変更します。
  5. PositionEulerScaleの値は以下の通りです。

モデルの透明化

モデルを加工して、密閉空間が完成しました。
ドアは開いていますが......
次に、どこでもドアを実装するために、入り口以外の部分を透明にして隠したいと思います。

透明化の実装方法は、
「モデルの外側に別の薄いオブジェクトを貼り付けて、その外側のオブジェクトのレンダリング順序を内側のオブジェクトより優先して、かつ、不透明度を極限まで下げることでドア部分以外を透明化します。」

ややこしいので、実装しながら理解していきましょう。

  1. オブジェクトの追加画面からBoxというオブジェクトを追加します。
  2. 名称はaddBoxにしておきます。
  3. PositionEulerScaleの値は以下の通りです。
  4. Material inspectorを開き、Identity欄のNameをsurface1を指定します。
  5. 外側六面を新たに追加していきます。
    Materials欄の下にある「+」ボタンを押して、New materialを選択します。
  6. 新たに追加したmaterialの名前をsurface2に変更します。
  7. 同様に、surface3~surface6を追加します。
  8. surface3を選択し、Diffuseをクリックして、Opacity(不透明度)のスライドを0%にします。

    そうすると、ドア側の壁が透明になりました。
  9. 次に、ドア側の壁に新たにオブジェクトを貼り付けていきます。
    オブジェクトの追加画面から、Planeのオブジェクトを展開し、名称をaddSideWall1とします。
  10. PositionEulerScaleの値は以下の通りです。
  11. 同様に、addSideWall2addSideWall3を追加していきます。


    これでドア側の壁も埋まりました。
  12. いよいよオブジェクトの表面を透明化していきます。
    addBoxをクリックし、surface1~surface6surface3を除く)を選択します。
  13. Diffuseの色画面から色のスライドを左端に指定して、Opacityを1%にします。
    (0%にすると非表示になります。)
  14. Transparentをクリックし、Opacityを1%にします。
  15. 最後に、下のDouble sidedにチェックを入れます。

ドア側の壁も同様に設定していきます。

  1. addSideWall1を選択し、Diffuseの色のスライドを左端に、Opacityを1%にします。
  2. TransparentのOpacityを1%にします。
  3. addSideWall2addSideWall3も同様に設定します。

それに加えて、ドア側の壁には、Rendering order(レンダリング順序)を内側のオブジェクトより低く設定することで、優先的にレンダリングされるようにします。

  1. まず、外側のオブジェクトから設定していきます。
    addBoxaddSideWall1~addSideWall3を選択します。
  2. Node inspectorを開き、Rendering orderの値を5にします。
  3. 内側のオブジェクト(上記のオブジェクト以外)も同様に、Rendering orderの値を10にします。

これでいい感じに透明になりました!!

最後に、全体のサイズを調整します。

  1. Scene graphclassroomという名前の空ノードを追加し、すべての3Dモデルをその直下に配置します。
  2. また、classroomAllという空ノードも作成し、その直下にclassroomを配置します。

最終的に下図のようなディレクトリ構造になればOKです。

  1. classroomを選択し、Node inspectorからPositionEulerScaleの値を以下の通りに設定します。

以上で、3Dモデルの構築は完了です!
お疲れ様でした。

いざ、AR実践!!

それでは、作成したARを試してみましょう!

  1. MacBookとiPhoneまたはiPadを接続します。
  2. Xcode上部において、接続したデバイスを選択します。
  3. 左上の再生マークをクリックし、デバッグします。

しばらくすると、デバイス側で自動的にアプリが起動するのでカメラへのアクセス許可をし、カメラを周囲に動かしてみましょう。
以下の動画のように表示されれば成功です。
YouTubeのvideoIDが不正ですhttps://youtube.com/shorts/WUs6SxXtovk?feature=share

まとめ

今回は、はじめてZennで記事を執筆してみました。
やっぱり、QiitaよりもZennのUIが個人的に好きだなぁ〜と感じました。
XR分野は他の技術領域と比べ、インパクトが大きいので達成感をより感じやすくていいですね。

Discussion