非ゲーム系アプリの開発が中心だと、3Dオブジェクトを取り扱う事がほとんどないので、3Dゲーム開発用のフレームワークであるSceneKitを利用する機会がありません。
しかし現在リサーチしている、ARKitを使いこなすには、SceneKitの理解は必須です。なぜならARKitが現実世界の検出したあとの3DオブジェクトのハンドリングはSceneKitが担当するからです。
2次元世界であるUIKitの文化圏と大分異なるSceanKitですが、しっかり理解していきたいと思います。
2次元 → 3次元への入り口
-
シーン(空間)
-
オブジェクト(物体)
-
ライト(照明)
-
カメラ
x軸、y軸だけでなく、奥行きとしてz軸が存在します。
SceanKitの基本的な構成と流れ
- 大元となるSCNViewのsceanプロパティにSCNSceneのオブジェクト(scene)を設定
- SCNNodeに、3Dモデルや、カメラ、ライトなどを設定
- sceneのrootNodeに、childNodeとして各ノードを追加
シーンを設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import UIKit
import SceneKit
class ViewController: UIViewController {
@IBOutlet weak var scnView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
scnView.backgroundColor = UIColor.gray
//カメラ位置をタップでコントロール可能にする
scnView.allowsCameraControl = true
scnView.scene = scene
}
}
|
3Dモデルノードを追加
まずSCNGeometryでジオメトリを生成。SCNGeometryは箱型、球体、円柱など様々な形があります。
ジオメトリには、色やテクスチャもマテリアルとして設定可能です。
SCNNodeのgeometryプロパティにジオメトリを代入して、ノードを生成。
ノードにpostion、nameを設定する。
このpositionの設定が、3次元慣れしていないとややこしい…。
箱型ノードの設定
1
2
3
4
|
let box: SCNGeometry = SCNBox(width: 10, height: 5, length: 2, chamferRadius: 1)
box.firstMaterial?.diffuse.contents = UIColor.red
let boxNode = SCNNode(geometry: box)
boxNode.name = “box”
|
箱型ジオメトリを代入したノードに、文字列ジオメトリを代入したノードを追加します。
文字列を画面中央に配置するために、positonも調整します。最後に文字列ノードがぶら下がった、箱型ノードをroodNodeに追加します。
文字列ノードの設定 + rootNodeに箱型ノードを追加
1
2
3
4
5
6
7
8
9
10
|
let text = SCNText(string: “techpartner”, extrusionDepth: 1)
let textNode = SCNNode(geometry: text)
text.font = UIFont(name: “GurmukhiMN-Bold”, size: 1);
let (min, max) = (textNode.boundingBox)
textNode.name = “text”
let x = CGFloat(max.x – min.x)
textNode.position = SCNVector3(–(x/2), –1, 1)
boxNode.addChildNode(textNode)
scene.rootNode.addChildNode(boxNode)
|
※参考にさせていただいたサイト
https://dev.classmethod.jp/smartphone/ios-11-arkit-scntext-2/
カメラノードを追加
SCNCameraをSCNNodeのcameraプロパティに設定。カメラが全体を写せるように、positonのz軸を調整します。
1
2
3
4
|
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 20)
scene.rootNode.addChildNode(cameraNode)
|
ライトノードを追加
SCNLightをSCNNodeのlightプロパティに設定。こちらも、positonを調整して光の当て方を調整します。
各種光源タイプの詳細は、Apple公式を参照
https://developer.apple.com/documentation/scenekit/scnlight/lighttype
1
2
3
4
5
|
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .spot
lightNode.position = SCNVector3(x: 0, y: 5, z: 20)
scene.rootNode.addChildNode(lightNode)
|
途中経過
インタラクションさせてみる
UITapGestureRecognizerでタップされた位置情報を取得し、SCNViewのhitTestメソッドでタップした位置にあるノードの情報を取得します。
もしヒットしたノードがboxノードであれば、タップする度にz軸を-10して奥に押し込まれる様なインタラクションを実現します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func addTapGesture() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
scnView.addGestureRecognizer(tapGesture)
}
@objc func tapped(recognizer: UIGestureRecognizer) {
let view = recognizer.view as? SCNView
let touchLocation = recognizer.location(in: view)
let hitTestResult = scnView.hitTest(touchLocation, options: nil)
if hitTestResult.count > 0 {
if hitTestResult.first!.node.name == “box” {
let boxNode = hitTestResult.first!.node
boxNode.position.z -= 10
}
}
}
|
hitTest
https://developer.apple.com/documentation/scenekit/scnscenerenderer/1522929-hittest
アニメーションさせてみる
CoreAnimationを利用して3Dオブジェクトにアニメーションをつける事も可能です。
CABasicAnimationのkeyPathに変化させたいノードのプロパティ(ここではposition)を指定して、fromValueとtoValueで変化をつけます。
アニメーションをつけたいノードにaddAnimationするだけでOK。
1
2
3
4
5
6
7
|
let animation = CABasicAnimation(keyPath: “position”)
animation.fromValue = SCNVector3(x: 0, y: 0, z: 0)
animation.toValue = SCNVector3(x: 0, y: 0, z: 100)
animation.duration = 30
animation.repeatCount = .infinity
boxNode.addAnimation(animation, forKey: nil)
|
CABasicAnimation
https://developer.apple.com/documentation/quartzcore/cabasicanimation
これで箱型ノードのオブジェクトが迫ってくる様なアニメーションをつけれました。
ライトノードの位置は固定のままなので、ライトノードのz軸より手前に移動すると箱型ノードが照らされなくなっているのがわかります。
コメント