Memo

メモ > 技術 > 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

Advertisement