在上一节中简单介绍ARKit及其所依赖的SceneKit,但是还有好多API中的东西没有介绍,详情可查看http://blog.csdn.net/u013263917/article/details/73156679,最好还能自己研究一下,这里东西不多且难度不大,你一定会有更多收获。另外这方面开发主要在于创建3D场景,也就是使用SceneKit。
下面是一个从零开始的太阳系demo,主要内容是节点与动画。希望能帮助大家理解在3D场景中,万物皆节点。
呵呵,给了一句自己都似懂非懂的话,其实我自己想过为什么动画不是节点呢?在3D世界,还有时间的概念等等,他们是或者可以是节点吗?不过貌似在SceneKit中,动画是作为节点的属性或者方法存在,而不是子节点。
不想太多,我们还是挺近太阳系吧!
太阳系
1, 创建一个空项目,搭建一个最初AR代码环境
- Info.plist添加照相机权限
NSCameraUsageDescription
This application will use the camera for Augmented Reality.
- 导入ARKit、SceneKit
import ARKit
import SceneKit
- 添加ARSCNView,并设置代理与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
// 设置节点与动画
}
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, 创建节点,并使用节点之间的层级结构处理旋转。节点有点像layer,都可以添加动画,只不过在展示上,节点是3D的。如果earthNode作为sunNode的子节点,然后让sunNode自身旋转,则earthNode会以sunNode为原点,绕半径旋转。月球绕地球旋转同理。
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, 从以上效果看,貌似已经基本实现了。但总有点怪,因为地球公转不是跟太阳自传同步的,月亮公转也不应该跟地球自传同步。所以上面那种简单的做法是不行,我们必须将公转与自转剥离开。
- 我们创建一个与太阳同层级的节点,并且与太阳的位置相同。该节点有点像黄道,所以就取名黄道吧。
- 让地球作为黄道的子节点。也就是让黄道控制了地球的公转。
- 月亮公转同上。
- 在这里不管是公转还是自转,其实都是自转。
var earthPathNode: SCNNode! // 黄道(ecliptic),控制地球公转
var moonPathNode: SCNNode! // 白道,控制月球公转
// 这两个节点,如果只为地球公转与月球公转,则不需要几何形与渲染
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)
// 节点层级关系
arSCNView.scene.rootNode.addChildNode(sunNode)
arSCNView.scene.rootNode.addChildNode(earthPathNode)
earthPathNode.addChildNode(earthNode)
earthPathNode.addChildNode(moonPathNode)
moonPathNode.addChildNode(moonNode)
func setupAnimation() {
// 月亮自转
moonNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 4, z: 0, duration: 1)))
// 地球自转
earthNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// 太阳自转,这里采用
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")
// 月亮公转
moonPathNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 3, z: 0, duration: 1)))
// 地球公转
earthPathNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1)))
/*
// 月亮公转
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, 添加太阳光、晕、地球公转轨道
var sunHaloNode: SCNNode? // 太阳光环(晕)
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!)
// 地球公转轨道
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)
}
这里图片背景是黑的,是因为我挡住了摄像头,另外太阳晕的效果与真实效果也稍有不一样。
好了,太阳系就这么愉快的完成了,是不是特别酷炫,特别拽呢?
“万物皆节点”更多理解:虽然动画不是节点,但就这个项目而言。为了动画,我们专门创建了一个空白节点,然后让需要改动画效果的节点成为该节点的子节点。从这个方面讲,我[们]是不是可以将其理解成动画也是节点呢?!
ARKit不止如此,未完待续...
太阳系完整代码:https://github.com/taoGod/ARKit2.git