メモ > 技術 > IDE: Xcode > UIKit+ARKit
■UIKit+ARKit
■デフォルトのプロジェクト
「Augmented Reality App」で以下の設定で新規作成する
Product Name: arkit
Content Technology: SceneKit
Interface: Storyboad
拡張現実ではカメラを利用するので、info.plist にはカメラのプライバシー設定(Privacy - Camera Usage Description)が追加されている
プロジェクトをビルドすると、カメラを通して宇宙船が浮いているのを確認できる
作成された初期プログラムは以下のとおり(コメントは調整してある)
ViewController.swift
import UIKit
import SceneKit // SceneKitのインポート
import ARKit // ARKitのインポート
class ViewController: UIViewController, ARSCNViewDelegate { // ARSCNViewDelegateの利用
// ストーリーボードのARSCNViewとOutlet接続
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// シーンビューのデリゲートになる
sceneView.delegate = self
// FPSやタイミングの情報を表示する
sceneView.showsStatistics = true
// シーンを新しく作る
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// シーンビューにシーンを設定する
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// セッションのコンフィグを作成する
let configuration = ARWorldTrackingConfiguration()
// ビューのセッションを開始する
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ビューのセッションを停止する
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 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
}
}
■アニメーションするオブジェクト
Assets.xcassets に earth_1024.jpg を読み込ませておく
ViewController.swift
import UIKit
import SceneKit // SceneKitのインポート
import ARKit // ARKitのインポート
class ViewController: UIViewController, ARSCNViewDelegate { // ARSCNViewDelegateの利用
// ストーリーボードのARSCNViewとOutlet接続
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// シーンビューのデリゲートになる
sceneView.delegate = self
// FPSやタイミングの情報を表示する
sceneView.showsStatistics = true
// カラのシーンを新しく作る
//let scene = SCNScene(named: "art.scnassets/ship.scn")!
let scene = SCNScene()
// シーンビューにシーンを設定する
sceneView.scene = scene
// ジオメトリ(半径20cmの球体を作る)
let earch = SCNSphere(radius: 0.2)
// テクスチャ(地球のテクスチャを貼り付ける)
earch.firstMaterial?.diffuse.contents = UIImage(named: "earth_1024")
// ノード(地球のノードを作る)
let earchNode = SCNNode(geometry: earch)
// アニメーション(100秒でY軸回転を1回行う)
let action = SCNAction.rotateBy(x: 0, y: .pi * 2, z: 0, duration: 100)
earchNode.runAction(SCNAction.repeatForever(action))
// 位置決め(右へ0.2m、上へ0.3m、奥へ0.2 に配置)
earchNode.position = SCNVector3(0.2, 0.3, -0.2)
// シーンに追加
sceneView.scene.rootNode.addChildNode(earchNode)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// セッションのコンフィグを作成する
let configuration = ARWorldTrackingConfiguration()
// ビューのセッションを開始する
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ビューのセッションを停止する
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 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
}
}
■環境マッピング
ARKit で球体に環境マッピング (Storyboard などを使わずソースコードで実現) - Qiita
https://qiita.com/niwasawa/items/04714f1603b98a713ca1
ARKit Hello World (宙に浮く Hello World テキスト in 拡張現実) - Qiita
https://qiita.com/niwasawa/items/fbc2e6231a1b7d0da672
ViewController.swift
import UIKit
import SceneKit // SceneKitのインポート
import ARKit // ARKitのインポート
class ViewController: UIViewController, ARSCNViewDelegate { // ARSCNViewDelegateの利用
// ストーリーボードのARSCNViewとOutlet接続
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// シーンビューのデリゲートになる
sceneView.delegate = self
// ワイヤーフレームを表示する
//sceneView.debugOptions = .showWireframe
// FPSやタイミングの情報を表示する
sceneView.showsStatistics = true
// カラのシーンを新しく作る
let scene = SCNScene()
// シーンビューにシーンを設定する
sceneView.scene = scene
// 箱を作る
let box1 = SCNBox(width: 0.3, height: 0.1, length: 0.3, chamferRadius: 0.01)
// 塗り
box1.firstMaterial?.diffuse.contents = UIColor.blue
// 物理ベースのレンダリング
box1.firstMaterial?.lightingModel = .physicallyBased
// 反射
box1.firstMaterial?.metalness.contents = 0.5
box1.firstMaterial?.metalness.intensity = 0.5
// 粗さ
box1.firstMaterial?.roughness.intensity = 0.5
// 箱のノードを作る
let box1Node = SCNNode(geometry: box1)
// 右へ0.5m、下へ0.5m、奥へ0.8m に配置
box1Node.position = SCNVector3(0.5, -0.5, -0.8)
// シーンに追加
sceneView.scene.rootNode.addChildNode(box1Node)
// 反射する箱を作る
let box2 = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0.01)
// 塗り
//box2.firstMaterial?.diffuse.contents = UIColor.gray
// 物理ベースのレンダリング
box2.firstMaterial?.lightingModel = .physicallyBased
// 反射
box2.firstMaterial?.metalness.contents = 1.0
box2.firstMaterial?.metalness.intensity = 1.0
// 粗さ
box2.firstMaterial?.roughness.intensity = 0.0
// 箱のノードを作る
let box2Node = SCNNode(geometry: box2)
// 左へ0.5m、下へ0.5m、奥へ0.8m に配置
box2Node.position = SCNVector3(-0.5, -0.5, -0.8)
// シーンに追加
sceneView.scene.rootNode.addChildNode(box2Node)
// 球体を作る
let sphere = SCNSphere(radius: 0.1)
// 塗り
sphere.firstMaterial?.diffuse.contents = UIColor.black
// 物理ベースのレンダリング
sphere.firstMaterial?.lightingModel = .physicallyBased
// 反射
sphere.firstMaterial?.metalness.contents = 0.2
sphere.firstMaterial?.metalness.intensity = 0.2
// 粗さ
sphere.firstMaterial?.roughness.intensity = 0.8
// 球体のノードを作る
let sphereNode = SCNNode(geometry: sphere)
// 左へ1.0m、下へ0.5m、奥へ0.5m に配置
sphereNode.position = SCNVector3(-1.0, -0.5, -0.5)
// シーンに追加
sceneView.scene.rootNode.addChildNode(sphereNode)
// 地球を作る
let earthNode = EarthNode()
// 100秒かけてY軸回転を1回行う
let action = SCNAction.rotateBy(x: 0, y: .pi * 2, z: 0, duration: 100)
earthNode.runAction(SCNAction.repeatForever(action))
// 右へ0.2m、上へ0.3m、奥へ1.2m に配置
earthNode.position = SCNVector3(0.2, 0.3, -1.2)
// シーンに追加
sceneView.scene.rootNode.addChildNode(earthNode)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// セッションのコンフィグを作成する
let configuration = ARWorldTrackingConfiguration()
// 環境マッピングを有効にする
configuration.environmentTexturing = .automatic
// ビューのセッションを開始する
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ビューのセッションを停止する
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 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
}
}
EarthNode.swift
import SceneKit
import ARKit
class EarthNode: SCNNode {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init() {
super.init()
// 球体を作る
let earch = SCNSphere(radius: 0.2)
// 地球のテクスチャを貼り付ける
earch.firstMaterial?.diffuse.contents = UIImage(named: "earth_1024")
// ノードのgeometryプロパティに設定する
geometry = earch
}
}
■タップした場所にオブジェクトを配置
ストーリーボードで Tap Gesture Recognizer ドラッグ&ドロップで配置
ViewController.swift に以下の設定でAction接続
Connection: Action
Object: View Controller
Name: tapSceneView
Type: UITapGestureRecognizer
ViewController.swift
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// シーンビューのデリゲートになる
sceneView.delegate = self
// ワイヤーフレームを表示する
//sceneView.debugOptions = .showWireframe
// FPSやタイミングの情報を表示する
sceneView.showsStatistics = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// セッションのコンフィグを作成する
let configuration = ARWorldTrackingConfiguration()
// 環境マッピングを有効にする
configuration.environmentTexturing = .automatic
// ビューのセッションを開始する
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ビューのセッションを停止する
sceneView.session.pause()
}
// シーンビューsceneViewをタップした
@IBAction func tapSceneView(_ sender: UITapGestureRecognizer) {
// 現在のフレーム
let frame = sceneView.session.currentFrame!
// トランスフォームを作る
var transform = matrix_identity_float4x4
transform.columns.3.z = -0.2
// カメラ正面の位置を作る
let tf = simd_mul(frame.camera.transform, transform)
let pos = SCNVector3(tf.columns.3.x, tf.columns.3.y, tf.columns.3.z)
// 箱を作って追加する
let boxNode = BoxNode()
boxNode.position = pos
sceneView.scene.rootNode.addChildNode(boxNode)
}
// 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 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
}
}
PlaneNode.swift
import SceneKit
import ARKit
class BoxNode: SCNNode {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init() {
super.init()
// 箱を作る
let box = SCNBox(width: 0.1, height: 0.05, length: 0.1, chamferRadius: 0.01)
// 塗り
box.firstMaterial?.diffuse.contents = UIColor.gray
// 物理ベースのレンダリング
box.firstMaterial?.lightingModel = .physicallyBased
// 反射
box.firstMaterial?.metalness.contents = 0.5
box.firstMaterial?.metalness.intensity = 0.5
// 粗さ
box.firstMaterial?.roughness.intensity = 0.5
// ノードのgeometryプロパティに設定する
geometry = box
}
}
■タップした場所にオブジェクトを配置&オブジェクトをタップすると飛ばす
ストーリーボードで Tap Gesture Recognizer ドラッグ&ドロップで配置
ViewController.swift に以下の設定でAction接続
Connection: Action
Object: View Controller
Name: tapSceneView
Type: UITapGestureRecognizer
ViewController.swift
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// シーンビューのデリゲートになる
sceneView.delegate = self
// ワイヤーフレームを表示する
//sceneView.debugOptions = .showWireframe
// FPSやタイミングの情報を表示する
sceneView.showsStatistics = true
// 箱1を作って追加する(右へ0.5m、下へ0.1m、奥へ0.8m に配置)
let boxNode1 = BoxNode()
boxNode1.position = SCNVector3(0.5, -0.1, -0.8)
sceneView.scene.rootNode.addChildNode(boxNode1)
// 箱2を作って追加する(右へ0.8m、下へ0.5m、奥へ0.2m に配置)
let boxNode2 = BoxNode()
boxNode2.position = SCNVector3(0.8, -0.5, -0.2)
sceneView.scene.rootNode.addChildNode(boxNode2)
// 箱3を作って追加する(左へ1.0m、上へ0.3m、奥へ0.5m に配置)
let boxNode3 = BoxNode()
boxNode3.position = SCNVector3(0.8, -0.5, -0.8)
sceneView.scene.rootNode.addChildNode(boxNode3)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// セッションのコンフィグを作成する
let configuration = ARWorldTrackingConfiguration()
// 環境マッピングを有効にする
configuration.environmentTexturing = .automatic
// ビューのセッションを開始する
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ビューのセッションを停止する
sceneView.session.pause()
}
// シーンビューsceneViewをタップした
@IBAction func tapSceneView(_ sender: UITapGestureRecognizer) {
// タップした2D座標
let tapLoc = sender.location(in: sceneView)
// 2D座標のヒットテスト
let hitTestOptions = [SCNHitTestOption:Any]()
let results = sceneView.hitTest(tapLoc, options: hitTestOptions)
if let result = results.first {
if let node = result.node as? BoxNode {
// 現在のフレーム
let frame = sceneView.session.currentFrame!
// トランスフォームを作る
let transform = matrix_identity_float4x4
// カメラ正面の位置を作る
let tf = simd_mul(frame.camera.transform, transform)
let pos = SCNVector3(tf.columns.3.x, tf.columns.3.y, tf.columns.3.z)
// カメラとノードの距離
let x = node.position.x - pos.x
let y = node.position.y - pos.y
let z = node.position.z - pos.z
let len = sqrt(x*x + y*y + z*z)
// 距離を確認
if (len < 0.3) {
// カメラからノード方向への単位ベクトル
let unitVec = SCNVector3(x/len, y/len, z/len)
// 力
let force:Float = 2.0
// 力のベクトル
let forceVec = SCNVector3(force*unitVec.x, force*unitVec.y, force*unitVec.z)
// ノードを弾くように力を加える
node.physicsBody?.applyForce(forceVec, asImpulse: true)
// 重力の影響を受けるように変更する
node.physicsBody?.isAffectedByGravity = true
}
}
} else {
// 現在のフレーム
let frame = sceneView.session.currentFrame!
// トランスフォームを作る
var transform = matrix_identity_float4x4
transform.columns.3.z = -0.2
// カメラ正面の位置を作る
let tf = simd_mul(frame.camera.transform, transform)
let pos = SCNVector3(tf.columns.3.x, tf.columns.3.y, tf.columns.3.z)
// 箱を作って追加する
let boxNode = BoxNode()
boxNode.position = pos
sceneView.scene.rootNode.addChildNode(boxNode)
}
}
// 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 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
}
}
PlaneNode.swift
import SceneKit
import ARKit
class BoxNode: SCNNode {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init() {
super.init()
// 箱を作る
let box = SCNBox(width: 0.1, height: 0.05, length: 0.1, chamferRadius: 0.01)
// 塗り
box.firstMaterial?.diffuse.contents = UIColor.gray
// 物理ベースのレンダリング
box.firstMaterial?.lightingModel = .physicallyBased
// 反射
box.firstMaterial?.metalness.contents = 0.5
box.firstMaterial?.metalness.intensity = 0.5
// 粗さ
box.firstMaterial?.roughness.intensity = 0.5
// ノードのgeometryプロパティに設定する
geometry = box
// 物理ボディを設定する
let bodyShape = SCNPhysicsShape(geometry: geometry!, options: [:])
physicsBody = SCNPhysicsBody(type: .dynamic, shape: bodyShape)
// 重力の影響を受けない状態でスタートする
physicsBody?.isAffectedByGravity = false
// 摩擦
physicsBody?.friction = 2.0
// 反発力
physicsBody?.restitution = 0.2
}
}
■平面検出
ViewController.swift
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// シーンビューのデリゲートになる
sceneView.delegate = self
// ワイヤーフレームを表示する
//sceneView.debugOptions = .showWireframe
// FPSやタイミングの情報を表示する
sceneView.showsStatistics = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// セッションのコンフィグを作成する
let configuration = ARWorldTrackingConfiguration()
// 平面の検出を有効にする(水平面と垂直面)
configuration.planeDetection = [.horizontal, .vertical]
// ビューのセッションを開始する
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// ビューのセッションを停止する
sceneView.session.pause()
}
// ノードが追加された
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
// 平面アンカーではないときは中断する
guard let planeAnchor = anchor as? ARPlaneAnchor else {
return
}
// アンカーが示す位置に水平ノードを追加する
node.addChildNode(PlaneNode(anchor: planeAnchor))
}
// ノードが更新された
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
// 平面アンカーではないときは中断する
guard let planeAnchor = anchor as? ARPlaneAnchor else {
return
}
// PlaneNodeではないときは中断する
guard let planeNode = node.childNodes.first as? PlaneNode else {
return
}
// ノードの位置とサイズを調整する
planeNode.update(anchor: planeAnchor)
}
// 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 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
}
}
PlaneNode.swift
import SceneKit
import ARKit
class PlaneNode: SCNNode {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(anchor: ARPlaneAnchor) {
super.init()
// 平面のジオメトリを作る
let plane = SCNBox(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z), length: CGFloat(anchor.extent.z), chamferRadius: 0.0)
// 塗り(緑で半透明)
plane.firstMaterial?.diffuse.contents = UIColor.green.withAlphaComponent(0.5)
// ワイヤーフレーム表示の分割数(ワイヤーフレームにするかどうかはsceneViewで指定)
plane.widthSegmentCount = 10
plane.heightSegmentCount = 1
plane.lengthSegmentCount = 10
// ノードのgeometryプロパティに設定する
geometry = plane
// X軸回りで90度回転
transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0)
// 位置決め
position = SCNVector3Make(anchor.center.x, 0, anchor.center.z)
}
// 位置とサイズを更新する
func update(anchor: ARPlaneAnchor) {
// ジオメトリを取り出す
let plane = geometry as! SCNBox
// アンカーから平面のサイズを更新する
plane.width = CGFloat(anchor.extent.x)
plane.length = CGFloat(anchor.extent.z)
// 位置を更新する
position = SCNVector3Make(anchor.center.x, 0, anchor.center.z)
}
}
■ARKit+Blender
※未検証
Hello AR !!: Blenderを使って3Dデータを作成&表示させてみよう
https://www.nsunrise.work/2019/09/blender3d.html
【ARKit入門】Xcodeに3Dファイルを入れてみる - Qiita
https://qiita.com/yyokii/items/667998a28d46b93fb1e3