ARkit连载二之太阳系

作者: 阿文灬 | 来源:发表于2017-09-07 19:57 被阅读48次

    在上一节中简单介绍ARKit及其所依赖的SceneKit,但是还有好多API中的东西没有介绍,详情可查看http://blog.csdn.net/u013263917/article/details/73156679,最好还能自己研究一下,这里东西不多且难度不大,你一定会有更多收获。另外这方面开发主要在于创建3D场景,也就是使用SceneKit。
    下面是一个从零开始的太阳系demo,主要内容是节点与动画。希望能帮助大家理解在3D场景中,万物皆节点。
    呵呵,给了一句自己都似懂非懂的话,其实我自己想过为什么动画不是节点呢?在3D世界,还有时间的概念等等,他们是或者可以是节点吗?不过貌似在SceneKit中,动画是作为节点的属性或者方法存在,而不是子节点。
    不想太多,我们还是挺近太阳系吧!

    太阳系

    1, 创建一个空项目,搭建一个最初AR代码环境

    • Info.plist添加照相机权限
        <key>NSCameraUsageDescription</key>
        <string>This application will use the camera for Augmented Reality.</string>
    
    • 导入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连载二之太阳系

        本文链接:https://www.haomeiwen.com/subject/nmbbjxtx.html