ARkit连载二之太阳系

在上一节中简单介绍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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容