在上一節(jié)中簡單介紹ARKit及其所依賴的SceneKit,但是還有好多API中的東西沒有介紹元莫,詳情可查看http://blog.csdn.net/u013263917/article/details/73156679赖阻,最好還能自己研究一下,這里東西不多且難度不大柒竞,你一定會有更多收獲政供。另外這方面開發(fā)主要在于創(chuàng)建3D場景,也就是使用SceneKit朽基。
下面是一個從零開始的太陽系demo布隔,主要內(nèi)容是節(jié)點(diǎn)與動畫。希望能幫助大家理解在3D場景中稼虎,萬物皆節(jié)點(diǎn)衅檀。
呵呵,給了一句自己都似懂非懂的話霎俩,其實(shí)我自己想過為什么動畫不是節(jié)點(diǎn)呢哀军?在3D世界,還有時(shí)間的概念等等打却,他們是或者可以是節(jié)點(diǎn)嗎杉适?不過貌似在SceneKit中,動畫是作為節(jié)點(diǎn)的屬性或者方法存在柳击,而不是子節(jié)點(diǎn)猿推。
不想太多,我們還是挺近太陽系吧捌肴!
太陽系
1, 創(chuàng)建一個空項(xiàng)目蹬叭,搭建一個最初AR代碼環(huán)境
- Info.plist添加照相機(jī)權(quán)限
<key>NSCameraUsageDescription</key>
<string>This application will use the camera for Augmented Reality.</string>
- 導(dǎo)入ARKit、SceneKit
import ARKit
import SceneKit
- 添加ARSCNView状知,并設(shè)置代理與ARSession秽五、ARWorldTrackingConfiguration
lazy var arSCNView: ARSCNView = {
let arSCNView = ARSCNView(frame: view.bounds)
arSCNView.session = arSession
arSCNView.automaticallyUpdatesLighting = true
return arSCNView
}()
lazy var arSession: ARSession = ARSession()
var arConfiguration: ARConfiguration!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(arSCNView)
arSCNView.delegate = self
// 設(shè)置節(jié)點(diǎn)與動畫
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
arConfiguration = ARWorldTrackingConfiguration()
arConfiguration.isLightEstimationEnabled = true
arSession.run(arConfiguration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
arSession.pause()
}
2, 創(chuàng)建節(jié)點(diǎn),并使用節(jié)點(diǎn)之間的層級結(jié)構(gòu)處理旋轉(zhuǎn)饥悴。節(jié)點(diǎn)有點(diǎn)像layer坦喘,都可以添加動畫盲再,只不過在展示上,節(jié)點(diǎn)是3D的瓣铣。如果earthNode作為sunNode的子節(jié)點(diǎn)洲胖,然后讓sunNode自身旋轉(zhuǎn),則earthNode會以sunNode為原點(diǎn)坯沪,繞半徑旋轉(zhuǎn)。月球繞地球旋轉(zhuǎn)同理擒滑。
func setupNodes() {
sunNode = SCNNode()
earthNode = SCNNode()
moonNode = SCNNode()
sunNode.geometry = SCNSphere(radius: 3)
earthNode.geometry = SCNSphere(radius: 1)
moonNode.geometry = SCNSphere(radius: 0.3)
sunNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "sun.jpg")
earthNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "earth-diffuse-mini.jpg")
moonNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "moon.jpg")
sunNode.position = SCNVector3Make(0, 0, -30)
earthNode.position = SCNVector3Make(10, 0, 0)
moonNode.position = SCNVector3Make(2, 0, 0)
arSCNView.scene.rootNode.addChildNode(sunNode!)
sunNode?.addChildNode(earthNode!)
earthNode?.addChildNode(moonNode!)
}
func setupAnimation() {
let animation = CABasicAnimation(keyPath: "rotation")
animation.duration = 3
animation.toValue = SCNVector4(0, 1, 0, Float.pi * 2)
animation.repeatCount = MAXFLOAT
earthNode.addAnimation(animation, forKey: "moon rotation around earth")
let animation2 = CABasicAnimation(keyPath: "rotation")
animation2.duration = 10
animation2.toValue = SCNVector4(0, 1, 0, Float.pi * 2)
animation2.repeatCount = MAXFLOAT
sunNode.addAnimation(animation, forKey: "earth rotation around sun")
}
3, 從以上效果看腐晾,貌似已經(jīng)基本實(shí)現(xiàn)了。但總有點(diǎn)怪丐一,因?yàn)榈厍蚬D(zhuǎn)不是跟太陽自傳同步的藻糖,月亮公轉(zhuǎn)也不應(yīng)該跟地球自傳同步。所以上面那種簡單的做法是不行库车,我們必須將公轉(zhuǎn)與自轉(zhuǎn)剝離開巨柒。
- 我們創(chuàng)建一個與太陽同層級的節(jié)點(diǎn),并且與太陽的位置相同柠衍。該節(jié)點(diǎn)有點(diǎn)像黃道洋满,所以就取名黃道吧。
- 讓地球作為黃道的子節(jié)點(diǎn)珍坊。也就是讓黃道控制了地球的公轉(zhuǎn)牺勾。
- 月亮公轉(zhuǎn)同上。
- 在這里不管是公轉(zhuǎn)還是自轉(zhuǎn)阵漏,其實(shí)都是自轉(zhuǎn)驻民。
var earthPathNode: SCNNode! // 黃道(ecliptic),控制地球公轉(zhuǎn)
var moonPathNode: SCNNode! // 白道履怯,控制月球公轉(zhuǎn)
// 這兩個節(jié)點(diǎn)回还,如果只為地球公轉(zhuǎn)與月球公轉(zhuǎn),則不需要幾何形與渲染
earthPathNode = SCNNode()
moonPathNode = SCNNode()
// 位置
sunNode.position = SCNVector3Make(0, -10, -20)
earthPathNode.position = sunNode.position
earthNode.position = SCNVector3Make(10, 0, 0)
moonPathNode.position = earthNode.position
moonNode.position = SCNVector3Make(3, 0, 0)
// 節(jié)點(diǎn)層級關(guān)系
arSCNView.scene.rootNode.addChildNode(sunNode)
arSCNView.scene.rootNode.addChildNode(earthPathNode)
earthPathNode.addChildNode(earthNode)
earthPathNode.addChildNode(moonPathNode)
moonPathNode.addChildNode(moonNode)
func setupAnimation() {
// 月亮自轉(zhuǎn)
moonNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 4, z: 0, duration: 1)))
// 地球自轉(zhuǎn)
earthNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// 太陽自轉(zhuǎn)叹洲,這里采用
var sunAnimation = CABasicAnimation(keyPath: "contentsTransform")
sunAnimation.duration = 10.0
sunAnimation.fromValue = CATransform3DConcat(CATransform3DMakeTranslation(0, 0, 0), CATransform3DMakeScale(3, 3, 3))
sunAnimation.fromValue = CATransform3DConcat(CATransform3DMakeTranslation(1, 0, 0), CATransform3DMakeScale(3, 3, 3))
sunAnimation.repeatCount = MAXFLOAT
sunNode.geometry?.firstMaterial?.diffuse.addAnimation(sunAnimation, forKey: "sun rotation")
sunAnimation = CABasicAnimation(keyPath: "contentsTransform")
sunAnimation.duration = 30.0
sunAnimation.fromValue = CATransform3DConcat(CATransform3DMakeTranslation(0, 0, 0), CATransform3DMakeScale(5, 5, 5))
sunAnimation.fromValue = CATransform3DConcat(CATransform3DMakeTranslation(1, 0, 0), CATransform3DMakeScale(5, 5, 5))
sunAnimation.repeatCount = MAXFLOAT
sunNode.geometry?.firstMaterial?.multiply.addAnimation(sunAnimation, forKey: "sun rotation2")
// 月亮公轉(zhuǎn)
moonPathNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 3, z: 0, duration: 1)))
// 地球公轉(zhuǎn)
earthPathNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1)))
/*
// 月亮公轉(zhuǎn)
let moonPathAnimation = CABasicAnimation(keyPath: "rotation")
moonPathAnimation.duration = 3
moonPathAnimation.toValue = SCNVector4(0, 1, 0, Float.pi * 2)
moonPathAnimation.repeatCount = MAXFLOAT
moonPathNode.addAnimation(moonPathAnimation, forKey: "moon rotation around earth")
*/
}
4, 添加太陽光柠硕、暈、地球公轉(zhuǎn)軌道
var sunHaloNode: SCNNode? // 太陽光環(huán)(暈)
func setupLight() {
// 太陽光
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.color = UIColor.black
lightNode.light?.type = .omni
lightNode.light?.attenuationStartDistance = 3.0
lightNode.light?.attenuationEndDistance = 20.0
sunNode.addChildNode(lightNode)
// 動畫
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
SCNTransaction.completionBlock = {
lightNode.light?.color = UIColor.white
self.sunHaloNode?.opacity = 0.5
}
SCNTransaction.commit()
// 暈
sunHaloNode = SCNNode()
sunHaloNode?.geometry = SCNPlane(width: 25, height: 25)
sunHaloNode?.rotation = SCNVector4Make(1, 0, 0, 0)
sunHaloNode?.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "sun-halo")
sunHaloNode?.geometry?.firstMaterial?.lightingModel = .constant
sunHaloNode?.geometry?.firstMaterial?.writesToDepthBuffer = false
sunHaloNode?.opacity = 0.9
sunNode.addChildNode(sunHaloNode!)
// 地球公轉(zhuǎn)軌道
let earthOrbitNode = SCNNode()
earthOrbitNode.opacity = 0.4
earthOrbitNode.geometry = SCNPlane(width: 21, height: 21)
earthOrbitNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "orbit")
earthOrbitNode.geometry?.firstMaterial?.multiply.contents = UIImage(named: "orbit")
earthOrbitNode.geometry?.firstMaterial?.lightingModel = .constant
earthOrbitNode.geometry?.firstMaterial?.diffuse.mipFilter = .linear
earthOrbitNode.rotation = SCNVector4Make(1, 0, 0, -Float.pi/2)
earthOrbitNode.geometry?.firstMaterial?.lightingModel = .constant
sunNode.addChildNode(earthOrbitNode)
}
這里圖片背景是黑的疹味,是因?yàn)槲覔踝×藬z像頭仅叫,另外太陽暈的效果與真實(shí)效果也稍有不一樣。
好了糙捺,太陽系就這么愉快的完成了诫咱,是不是特別酷炫,特別拽呢洪灯?
“萬物皆節(jié)點(diǎn)”更多理解:雖然動畫不是節(jié)點(diǎn)坎缭,但就這個項(xiàng)目而言竟痰。為了動畫,我們專門創(chuàng)建了一個空白節(jié)點(diǎn)掏呼,然后讓需要改動畫效果的節(jié)點(diǎn)成為該節(jié)點(diǎn)的子節(jié)點(diǎn)坏快。從這個方面講,我[們]是不是可以將其理解成動畫也是節(jié)點(diǎn)呢憎夷?莽鸿!
ARKit不止如此,未完待續(xù)...
太陽系完整代碼:https://github.com/taoGod/ARKit2.git