ネイティブでiOSのARアプリを作るためのTIPS


概要

ARKitアドベントカレンダー2018 2日目は @kaoryuuu がお送りします!

今回はネイティブアプリでARKitでの開発してきた際に必要そうな内容を紹介したいと思います。

はじめに

ARKit、触ったことある方いますでしょうか。「興味はあるけど難しそう」と印象を持っている方は多いと思います。
個人的な主観ですが、ARKit自体はそこまで難しいものではありません。3DモデルをAR空間上に表示させたりするだけなら多分1日も要らないです。ただ、 ARをユーザー体験として落とし込むのは非常に難しいと自分は思っております。
Appleのサンプルアプリやドキュメントを使いながら、良いアプリつくるための知見を共有していきます。

ARKitの基礎を知りたい

(宣伝)こちらを参考にしていただけるとざっくりどのようなものか掴めるかと思います。
ARKitを扱う際の心構えとTips

以前、チートシート的な物も作成したので参考にしていただけると幸いです。

サンプルコードから学ぶ

ARKitでの開発にはApple公式のサンプルを参考にするとだいたいやりたいことが載っています。
自分がつくりたいものに合わせて見てみると良いかと思います。
https://developer.apple.com/documentation/arkit

Building Your First AR Experience
Creating a Persistent AR Experience
Handling 3D Interaction and UI Controls in Augmented Reality

複数人でARやりたい!

Creating a Multiuser AR Experience

ネイティブでゲーム作りたい!

ゲームだったらUnityの方が正直楽だと思います。。。が一応。
SwiftShot: Creating a Game for Augmented Reality

ARKitと何か組み合わせてみたい

ARKit+Vision
こちらのプロジェクトでは実際にMLモデルを読み込んでAR空間上に認識したラベルを表示するものが公開されております
https://github.com/hanleyweng/CoreML-in-ARKit

すでに学習済みのCore MLのデータもAppleが提供しているのでまずはこれを使ってみるとよいでしょう
https://developer.apple.com/jp/machine-learning/build-run-models/

ARKit+Core Location
Creating an Immersive AR Experience with Audio
Adding Realistic Reflections to an AR Experience

Haptic Feedback

UIFeedbackGenerator

ブルっと端末が震えるやつです。
アプリインストール時などに「ピコーン」と鳴ると同時に少し振動するのが気持ち良いですよね(?)

ARを扱う上でHaptic Feedbackは非常に良い働きをしてくれます。
AR上でのオブジェクトの配置やタップ判定などに、「あ、今このオブジェクトを触っているんだ」感をユーザーに伝える手段として活用できます。

参考:https://techblog.zozo.com/entry/ios_ui_feedbcak_generator

ARTrackingState

現在カメラに写っているフレームから、トラッキングの状態を取得することができます

NotAvailable

Camera position tracking is not available.

トラッキングが利用できない状態。

(例えば、カメラが何かに覆われて映し出されている画面が真っ黒になっているとか)

Limited(ARCamera.TrackingState.Reason)

Tracking is available, but the quality of results is questionable.

利用できるけど結果は微妙かも。。
また、その理由も取得できます。(後述参照)

Normal

Camera position tracking is providing optimal results.

ARCamera.TrackingState.Reason

どんなエラーなのか、例えばInsufficientFeaturesは画面に映し出されている特徴量が少ないという理由になっています。iOS12のMeasureアプリでは特徴点が少なかった際に画面全体に端末を動かすようアニメーションをオーバーレイさせています。

ARCamera.swift
public enum Reason {

    /** Tracking is limited due to initialization in progress. */
    case initializing

    /** Tracking is limited due to a excessive motion of the camera. */
    case excessiveMotion

    /** Tracking is limited due to a lack of features visible to the camera. */
    case insufficientFeatures

    /** Tracking is limited due to a relocalization in progress. */
    @available(iOS 11.3, *)
    case relocalizing
}

使い所 ~UseCase~

公式アプリでもトラッキングの状態が悪いと正しく認識させるように促すようなアニメーションを表示させたり、
注意が促されていますね。

ViewController.swift
extension ViewController: ARSCNViewDelegate {
    // カメラのトラッキング状態が変わった際に呼ばれる
    func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
        statusViewController.showTrackingQualityInfo(for: camera.trackingState, autoHide: true)

        switch camera.trackingState {
        case .notAvailable, .limited:
            // ここで何かしらの情報を出したり、動作を制限させてあげる
        case .normal:
            // 正常
        }
    }

ARAnchor

name

iOS12からARAnchorにnameを付与できるようになりました

ViewController.swift
let anchor = ARAnchor(name: "hoge", transform: float4x4)

※Anchorは文字のごとく、位置を固定化させるようなイメージです。
位置を変えたい場合は直接Anchorの座標を変更するのではなく、NodeのPositionを変更するなどしたほうが良いです。

使い所 ~UseCase~

ViewController.swift
func setObject() {
    // 任意の場所に何か表示させたい
    let anchor = ARAnchor(name: "hoge", transform: float4x4)
    sceneView.session.add(anchor: anchor)
}

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let anchorType = AnchorType(name: "hoge") else {
        return
    }
    // 追加されたものがhogeAnchorの場合だけ処理させたい
}

SCNNode

CategoryBitMask

詳しい内容は記事はこちらの記事がわかりやすいです。
SceneKitにおける物体衝突検知を理解する

HitTest時のOptionに設定することで特定のBitMaskに対応したものだけを認識させることができます。

TimingModeでEasing

https://developer.apple.com/documentation/scenekit/scnactiontimingmode
アニメーションにEasingをつけることも可能です

SCNNode.swift
let foo = SCNAction()
foo.timingMode = .easeIn //linear, easeOut, easeInEaseOut

オブジェクトをカメラの方向に向ける

SCNNode.swift
let constraint = SCNBillboardConstraint()
constraint.freeAxes = SCNBillboardAxis.Y
constraints = [constraint]

最後に

何か間違い等ありましたらコメントをいただけると幸いです!