ARkit连载二之太阳系

在上一节中简单介绍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")
    }
太阳系1.gif

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")
        */
    }
太阳系2.gif

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)
    }
太阳系3.gif

这里图片背景是黑的,是因为我挡住了摄像头,另外太阳晕的效果与真实效果也稍有不一样。

好了,太阳系就这么愉快的完成了,是不是特别酷炫,特别拽呢?
“万物皆节点”更多理解:虽然动画不是节点,但就这个项目而言。为了动画,我们专门创建了一个空白节点,然后让需要改动画效果的节点成为该节点的子节点。从这个方面讲,我[们]是不是可以将其理解成动画也是节点呢?!
ARKit不止如此,未完待续...
太阳系完整代码:https://github.com/taoGod/ARKit2.git

你可能感兴趣的:(ARkit连载二之太阳系)