73回生 object
みなさんこんにちは、73回生(高1)のobjectです。今年はARKitのことについて部誌を書きました。
さて、多くの人はARKitというのが何なのかわからないと思いますが、なんとなくAR関連の何かだということはわかるでしょう。じゃあそのARとは何なのでしょうか。
AR(拡張現実)とは、人が知覚する現実環境をコンピュータにより拡張する技術、およびコンピュータにより拡張された現実環境そのものを指す言葉。(Wikipediaより抜粋)
ARの定義を言葉で言い表すのは難しいですが、わかりやすく言うと現実空間に仮想のものをくっつける技術です。
社会現象にもなったPokemon GO(もう二年近く前)で有名になりましたが、それよりも前からARは存在します。
図1.1: 現実にポケモンがいるように見える
VRとよく混同されますが、ARとVRは全く違う技術です。
ARが何なのかわかったところで、次はARKitの説明をします。ARKitというのはAppleが発表したiOS用のARフレームワークです。
もっとわかりやすく言うとARKitはARを使ったiOSアプリを簡単に作るためのキットで、いままでとても難しかったiOSでのAR開発が劇的に簡単になるという画期的なものです。
ARKitはA9チップ以降を搭載し、iOS11が入ったiOS端末(つまりiPhoneなら6s以降)さえあれば特別なカメラ等が必要なく、誰でも簡単に体験できるのが特徴です。
ARKitを使えばiOSでのAR開発が劇的に簡単になるとか言いましたが、実際にどれだけ簡単なのかというと、数行コードを書くだけでただのアプリをARアプリにできるレベルです。
それでは実際に数行コードを書くだけでARアプリを作ってみましょう。
ARKitは実質三行でARが実装できます。早速Hello,World!を表示するARアプリを作ってみましょう。
まず、iOSアプリを作るにはXcodeというアプリが必要です。そして、XcodeはMacでしか使えません。つまりiOSアプリ開発にはMacが必要です。
Macが準備できたら、次にXcodeをインストールします。Xcodeはここからインストールできます。
また、表示する3Dモデルを作るためにBlenderを使うのでここからインストールしておきましょう。
インストールしたBlenderを開くと立方体のオブジェクトが出てきますが、使わないのでXキーで削除しておきます。
次に、左側のツールシェルフの作成タブからテキストを選択し、Tabキーで編集モードにした後、予め表示されたテキストをすべて消してHello,World!と打ち込みます。
図1.2: Hello,World!を打ち込んだ状態
次に右側にある[ジオメトリ]プロパティの[押し出し]で厚さを調整します。また、[フォント]プロパティではフォントを変更することもできます。
図1.3: 厚さとフォントを変更した状態
UV展開ができるように左下のオブジェクト→変換でテキストをメッシュに変換します。
次にテクスチャを貼ります。左上のスクリーンレイアウトをDefaultからUV Editingに変更し、テキストを編集モードで選択した状態でUキーを押してスマートUV展開を選択します。
今回は全部白くていいので白い正方形の画像を用意して左下の[開く]から用意した画像を開きましょう。
図1.4: UV展開後画像を開いた状態
できたらスクリーンレイアウトをDefaultに戻し、右のマテリアル(丸のアイコン)からマテリアルを追加し、その横のテクスチャで[新規作成]を押して下の[画像]のところでさっきの画像を開きます。
下にあるシェーディングのところでソリッドからテクスチャに切り替えて白くなっていればモデル作成は成功です。
図1.5: テクスチャが反映された状態
次に、作成したモデルをXcodeで使えるようにエクスポートします。Xcodeで使える3Dモデルの拡張子は.scnと.daeと.objです。メニューバーからエクスポートを選択し、Collada(.dae)でエクスポートします。
インストールしておいたXcodeを開きます。。"create a new project"のところを選択するとこのような画面が出てきます。
図1.6: アプリテンプレート選択
ここでアプリのテンプレートを選択することができます。中央上にAR用のテンプレートがあるのでそれを選びたくなりますが、AR用テンプレートを選ぶと最初からARが実装されてしまっているので、ARKitの凄さを伝えるためにあえてSingle View Appを選びます。
名前を適当に入れてアプリを作ったら、info.plistを開き、"Information Properly List"に"Privacy - Camera Usage Description"を追加します。
次に、Main.storyboardを開き、右下の検索欄で"ARKit SceneKit View"を探し、画面全体に広げます。
図1.7: storyboard
次に先程作成し、エクスポートしたモデルとテクスチャをプロジェクト内に追加します。モデルのサイズが合わないので右上のところで調整しておきましょう
図1.8: Xcodeでの表示
次にARを表示するためのコードを書きます。
まず最初に右上のベン図みたいなマークを押してMain.storyboardとViewController.swiftを並べ、Ctrlキーを押しながらARSCNViewをViewController.swiftの最後の中括弧の上にドラッグ&ドロップし、sceneViewという名前でIBOutlet接続します。
ViewController.swift
// IBOutlet接続 @IBOutlet var sceneView: ARSCNView!
次に、ARKitをインポートします。
ViewController.swift
//ARKitのインポート import ARKit
最後に.viewDidLoad()
に次の三行を追加します。
ViewController.swift
// シーンの生成 sceneView.scene = SCNScene(named: "helloworld.dae")! // セッションのコンフィギュレーション生成 let configuration = ARWorldTrackingConfiguration() // セッション開始 sceneView.session.run(configuration)
これでARアプリを作ることができました。早速動かしてみましょう。
ARKitの起動にはカメラが必要なのでシュミレータではなく実機を接続してシュミレーションします。
平面を検出できたらHello,World!が表示されると思います。
図1.9: Hello,World!
違う方向から見ても固定されているようにみえるのがわかります
図1.10: 違う方向から
ね?簡単でしょ?
モデルをいちいち作ったりしたので若干面倒でしたが、肝心のARの実装は非常に簡単にできました。
次のアプリを作る前にさっきのHello,World!の説明をします。
ViewController.swift
import UIKit import ARKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() sceneView.scene = SCNScene(named: "helloworld.dae")! let configuration = ARWorldTrackingConfiguration() sceneView.session.run(configuration) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } @IBOutlet var sceneView: ARSCNView! }
コードを書き加えるとこのようになっているはずです。
最初から書かれているコードは無視して、まず2行目のimport ARKit
というのはアプリ内でARKitを使えるようにするためのコードです。
次に、8行目のsceneView.scene = SCNScene(named: "helloworld.dae")!
はシーン(表示するもの)を生成して、ARSCNViewにセットするコードで、この場合はhelloworld.daeという名前の3Dモデルを表示しています。
10行目のlet configuration = ARWorldTrackingConfiguration()
はコンフィギュレーションの作成です。
ARWorldTrackingConfiguration
というのはデバイスの向き等をトラッキングし、現実世界の面を検出するコンフィギュレーションです。
12行目のsceneView.session.run(configuration)
はセッションの開始、つまり現実世界の認識やデバイスのモーションの解析を開始するということです。
また、session.pause()
メソッドでセッションを中断することもできます。
ARKitには平面検出及び垂直面検出の機能があります。*1
[*1] 垂直面検出はiOS11.3以降でのみ使用可能
デフォルトではこの機能が無効になっているので、使いたい場合は自分で設定する必要があります。
まずは平面を検出して、可視化するアプリを作りましょう。
Xcodeを開いてARテンプレートでアプリを作った後、ViewController.swiftをこのように変更します。
ViewController.swift
import UIKit import ARKit class ViewController: UIViewController, ARSCNViewDelegate,ARSessionDelegate{ @IBOutlet var sceneView: ARSCNView! override func viewDidLoad() { super.viewDidLoad() // デリゲートをセット sceneView.delegate = self sceneView.session.delegate = self // シーンをセット sceneView.scene = SCNScene() // コンフィギュレーションの生成 let configuration = ARWorldTrackingConfiguration() // 平面検出を有効化 configuration.planeDetection = .horizontal // セッション開始 sceneView.session.run(configuration) } // ARSCNViewDelegate // 追加されたとき func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor else {fatalError()} print("anchor:\(anchor), node: \(node), node geometry: \(String(describing: node.geometry))") // 平面ジオメトリを作成 let geometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z)) geometry.materials.first?.diffuse.contents = UIColor.yellow.withAlphaComponent(0.5) // 平面ジオメトリを持つノードを作成 let planeNode = SCNNode(geometry: geometry) //x-z平面に合わせる planeNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1, 0, 0) DispatchQueue.main.async(execute: { // 検出したアンカーに対応するノードに子ノードとして持たせる node.addChildNode(planeNode) }) } // 更新されたとき func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor else {fatalError()} DispatchQueue.main.async(execute: { // 平面ジオメトリのサイズを更新 for childNode in node.childNodes { guard let plane = childNode.geometry as? SCNPlane else {continue} plane.width = CGFloat(planeAnchor.extent.x) plane.height = CGFloat(planeAnchor.extent.z) break } }) } // 削除されたとき func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) { print("\(self.classForCoder)/" + #function) } // ARSessionDelegate // 追加されたとき func session(_ session: ARSession, didAdd anchors: [ARAnchor]) { print("\(self.classForCoder)/" + #function) } // 更新されたとき func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) { print("\(self.classForCoder)/" + #function) } // 削除されたとき func session(_ session: ARSession, didRemove anchors: [ARAnchor]) { print("\(self.classForCoder)/" + #function) } }
これで動かすとしっかりと平面検出ができるのがわかります。
図1.11: 平面検出
まず平面検出を有効化するためには、コンフィギュレーションのplaneDetection
プロパティに.horizontal
を追加する必要があります。
垂直面検出の場合は.horizontal
の代わりに.vertical
を追加します。
図1.12: 垂直面検出
// 平面検出の有効化 configuration.planeDetection = .horizontal
ARAnchorはARの空間内に何らかのオブジェクトを配置するための位置、向きを表すクラスです。
これによって、デバイスを動かしてもオブジェクトはアンカー(錨)で固定されているように表示されます。
また、ARAnchorにはARPlaneAnchorというサブクラスがあり、平面,垂直面検出時に得られるアンカーはこの型で動きます。
ARPlaneAnchorには平面の中心を表すcenterプロパティと大きさを表すextentプロパティ、アラインメントを示すalignmentプロパティがあります。
center、extentともにvector_float3型ですが、extentは平面の大きさを示すのでx,zのみで大きさは決まり、yの値は常にゼロです。
ARSCNViewDelegateではアンカーと対応するノードが追加、更新、削除されるときにメソッドを呼ぶことができます。
// ノードが追加されたとき func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) // 更新 func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) // 削除 func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor)
ARSessionDelegateではアンカーが追加、更新、削除されたときにメソッドを呼ぶことができます。
// アンカーが追加されたとき func session(_ session: ARSession, didAdd anchors: [ARAnchor]) // 更新 func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) // 削除 func session(_ session: ARSession, didRemove anchors: [ARAnchor])
最後に、平面を検出した後、そこにオブジェクトを置くアプリを作ってみましょう。
ViewController.swift
import UIKit import ARKit class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate { @IBOutlet var sceneView: ARSCNView! override func viewDidLoad() { super.viewDidLoad() sceneView.delegate = self sceneView.session.delegate = self // シーンを生成してARSCNViewにセット sceneView.scene = SCNScene() // セッションのコンフィギュレーションを生成 let configuration = ARWorldTrackingConfiguration() configuration.planeDetection = .horizontal // セッション開始 sceneView.session.run(configuration) } // MARK: - ARSCNViewDelegate func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor else {fatalError()} print("anchor:\(anchor), node: \(node), node geometry: \(String(describing: node.geometry))") // 平面ジオメトリを作成 let geometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z)) geometry.materials.first?.diffuse.contents = UIColor.yellow.withAlphaComponent(0.5) // 平面ジオメトリを持つノードを作成 let planeNode = SCNNode(geometry: geometry) planeNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1, 0, 0) DispatchQueue.main.async(execute: { // 検出したアンカーに対応するノードに子ノードとして持たせる node.addChildNode(planeNode) }) // シーンの生成 let scene = SCNScene(named: "art.scnassets/ship.scn")! // オブジェクトのノード作成 let virtualObjectNode = SCNNode() // 別のノードに載せ替える for child in scene.rootNode.childNodes { virtualObjectNode.addChildNode(child) } // オブジェクトを載せる node.addChildNode(virtualObjectNode) } func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor else {fatalError()} DispatchQueue.main.async(execute: { // 平面ジオメトリのサイズを更新 for childNode in node.childNodes { guard let plane = childNode.geometry as? SCNPlane else {continue} plane.width = CGFloat(planeAnchor.extent.x) plane.height = CGFloat(planeAnchor.extent.z) break } }) } func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) { print("\(self.classForCoder)/" + #function) } // MARK: - ARSessionDelegate func session(_ session: ARSession, didAdd anchors: [ARAnchor]) { print("\(self.classForCoder)/" + #function) } func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) { print("\(self.classForCoder)/" + #function) } func session(_ session: ARSession, didRemove anchors: [ARAnchor]) { print("\(self.classForCoder)/" + #function) } }
このように変更して、動かしてみましょう。
図1.13: 平面検出とオブジェクト
平面の上に3Dモデルが乗っているのがわかります。
内容は簡単で、先程は平面のアンカーに対応したノードを作りましたが、そこに別の、オブジェクトのノードを載せただけです。
ARSCNViewにはdebugOptionsというプロパティがあり、その名の通りデバッグに関する機能を利用できます。
2018年5月現在、debugOptionsの型であるSCNDebugOptionsの持つオプション一覧は以下のとおりです。
表1.1: SCNDebugOptions一覧
オプション名 | 内容 |
---|---|
showBoundingBoxes | 任意のノードのバウンディングボックス(境界を示す箱)を可視化する |
showWireframe | シーンのジオメトリをワイヤーフレーム付きで描画する |
renderAsWireframe | ジオメトリをワイヤーフレームだけで描画する |
showSkeletons | ボーンを可視化する |
showCreases | 細分化した状態で表示する #@#英語がよくわからなかったので消すかも |
showConstraints | シーン内のノードに作用する制約オブジェクトを可視化する |
showCameras | カメラを可視化する |
showLightInfluences | シーン内のSCNLightの位置を可視化する |
showLightExtents | 各SCNLightに影響を受ける領域を可視化する |
showPhysicsShapes | 各ノードに追加されているSCNPhysicsBodyを可視化する |
showPhysicsFields | 各SCNPhysicsBodyに影響を受ける領域を可視化する |
ARKitではSCNDebugOptionsに加えて、ARSCNView用のARSCNDebugOptionsが用意されています。
表1.2: ARSCNDebugOptions一覧
オプション名 | 内容 |
---|---|
showWorldOrigin | AR空間の座標軸を可視化する |
showFeaturePoints | ARKitが検出した特徴点群を可視化する |
最も、ARSCNDebugOptionsはSCNDebugOptions型なのでdebugOptionsプロパティに普通に指定できます。
まずはshowBoundingBoxesオプションを適用してみましょう。
sceneView.debugOptions = [.showBoundingBoxes]
このコードをセッションを開始するコードの前に記述して動かすとこうなります。
他のオプションを使う場合は.showBoundingBoxes
を.showWireframe
等に変更します。
また、ARSCNDebugOptionsを使う場合はARSCNDebugOptions.showFeaturePoints
のようにします。
図1.14: .showBoundingBoxes
以下、オプションを適用した場合の画像を載せます。
図1.15: .showWireframe
図1.16: .renderAsWireframe
図1.17: .showSkeletons
showSkeletonsオプションはボーンが存在するモデルでのみ機能します。
図1.18: .showCameras
図1.19: .showLightInfluences
ライトを追加したためモデルの見た目が変わっていますが、このオプションの影響ではありません。
図1.20: .showLightExtents
図1.21: .showWorldOrigin
図1.22: .showFeaturePoints
最後まで読んでいただき、ありがとうございます。
この記事ではARKitの基本的な機能を説明したつもりですが、それはARKitの機能のほんの一部に過ぎません。
この記事を機にARおよびARKitに興味を持っていただけたら幸いです。
それでは、また来年お会いしましょう!(部誌を書くとは言ってない)