ARKit如何將太陽系裝進iPhone(二)

轉載請註明原做者node

上篇文章咱們介紹如何建立一個ARKit項目,而且建立太陽、地球這些球體,接下來咱們來談一談如何讓它們動起來。git

演示視頻github

ARSolarPlay.gif


天文科普bash

首先科普下太陽系的結構,太陽系共有八大行星,水星、金星、地球、火星、木星、土星、天王星、海王星,還有顆矮行星冥王星。木星體積最大,且自轉週期最快,它和土星、天王星都自帶行星環,地球衛星是月球,金星和水星是太陽系中惟二不帶衛星的行星。太陽做爲恆星自己會自轉,而行星除了自轉外還會圍繞它的恆心公轉,因爲行星軌道可能是橢圓,爲了簡化難度(偷懶)咱們假定他們的公轉軌道都是圓形,而地球的自轉軌道也是斜的,這些細節後面會進一步完善。session

聖鬥士星矢.jpg


3D模型建立--SceneKitide

AR工程中有一個ARSCNView,它用來加載3D模型的AR視圖的,它繼承於SCNView,相對的加載2D視圖的就是ARSKView,視圖中的那些模型的建立運動就須要用到本章所說的SceneKit和SpriteKit。它們是iOS中用來開發3D模型和2D模型的引擎,因爲沒用過Unity3D開發,因此此處不介紹。函數

Sprite是用來建立2D模型,在遊戲開發中,指的是以圖像方式呈如今屏幕上的一個圖像。這個圖像也許能夠移動,用戶能夠與其交互,也有可能僅只是遊戲的一個靜止的背景圖。而在AR中,2D模型會隨着手機的遠近放大縮小,而不能像3D模型那樣能夠從側面觀察。動畫

SceneKit 創建在 OpenGL 的基礎上,包含了如光照、模型、材質、攝像機等高級引擎特性,咱們能夠基於它作出不少逼真的3D物理模型。網站

SCeneKit結構圖.jpg

#####SCNScene & SCNNode 每一個ARSCNView中都帶有一個場景SCNScene,它用來承載那些帶有幾何結構、光度、相機以及其餘屬性的節點SCNNode,一個完整的3D場景就這麼展示出來了。一個SCNScene能夠包含多個SCNNode子節點,它們通常都是呈樹狀結構,一個子節點SCNNode能夠有多個childNode,而SCNNode只有一個parentNode,rootNode做爲根節點,咱們經過rootNode添加本身的子節點SCNNode。 SCNNode的經常使用方法:ui

addChildNode(_:)
insertChildNode(_: atIndex:)
removeFromParentNode()
複製代碼

接下來介紹下SCNNode的幾種經常使用的屬性對象 ** 1. SCNGeometry ** SceneNode提供幾種幾何模型,例如六面體(SCNBox)、平面(SCNPlane,只有一面)、無限平面(SCNFloor,沿着x-z平面無限延伸)、球體(SCNSphere)等等。 例如咱們建立一個半徑爲0.25的球體

SCNNode *sunNode = [SCNNode new];
sunNode.geometry = [SCNSphere sphereWithRadius:0.25];
複製代碼

爲了突出行星運動軌跡,咱們給每顆星星添加了軌道,一開始我使用的是SCNPlane後來發現它只有一個平面,你從反面是看不到的,因而我使用的是SCNBox

SCNNode *mercuryOrbit = [SCNNode node];
//設置不透明度
mercuryOrbit.opacity = 0.4;
//設置軌道的結構體,height爲0
mercuryOrbit.geometry = [SCNBox boxWithWidth:0.86 height:0 length:0.86 chamferRadius:0];
mercuryOrbit.geometry.firstMaterial.diffuse.contents = @"art.scnassets/solar/orbit.png";
//紋理濾波
mercuryOrbit.geometry.firstMaterial.diffuse.mipFilter = SCNFilterModeLinear;
mercuryOrbit.rotation = SCNVector4Make(0, 1, 0, M_PI_2);
//光照模式
mercuryOrbit.geometry.firstMaterial.lightingModelName = SCNLightingModelConstant; // no lighting
[_sunNode addChildNode:mercuryOrbit];
複製代碼

補充一下紋理濾波這個屬性有什麼用? 當材料表面的部分出現較大或小於原來的紋理圖像時,紋理過濾決定了材料屬性的內容的外觀

@property(nonatomic) SCNFilterMode minificationFilter
可選項
typedef enum : NSInteger { 
SCNFilterModeNone = 0, // 當這個位置沒有紋理顏色時,會採樣離他最近的顏色值 
SCNFilterModeNearest = 1, //當這個位置沒有紋理顏色時,線性插值顏色做爲本身的顏色
SCNFilterModeLinear = 2, } SCNFilterMode;
默認值爲 SCNFilterModeLinear
複製代碼

** 2. SCNMaterial ** SceneNode提供8種屬性用來設置模型材質

  • Diffuse 漫發射屬性表示光和顏色在各個方向上的反射量
  • Ambient 環境光以固定的強度和固定的顏色從表面上的全部點反射出來。若是場景中沒有環境光對象,這個屬性對節點沒有影響
  • Specular 鏡面反射是直接反射到使用者身上的光線,相似於鏡子反射光線的方式。此屬性默認爲黑色,這將致使材料顯得呆滯
  • Normal 正常照明是一種用於製造材料表面光反射的技術,基本上,它試圖找出材料的顛簸和凹痕,以提供更現實發光效果
  • Reflective 反射光屬性是一個鏡像表面反射環境。表面不會真實地反映場景中的其餘物體
  • Emission 該屬性是由模型表面發出的顏色。默認狀況下,此屬性設置爲黑色。若是你提供了一個顏色,這個顏色就會體現出來,你能夠提供一個圖像。SceneKit將使用此圖像提供「基於材料的發光效應」。
  • Transparent 用來設置材質的透明度
  • Multiply 經過計算其餘全部屬性的因素生成最終的合成的顏色
// 地球貼圖
    _earthNode.geometry.firstMaterial.diffuse.contents = @"art.scnassets/solar/earth-diffuse-mini.jpg";
    _earthNode.geometry.firstMaterial.emission.contents = @"art.scnassets/solar/earth-emissive-mini.jpg";
    _earthNode.geometry.firstMaterial.specular.contents = @"art.scnassets/solar/earth-specular-mini.jpg";
複製代碼

另外咱們對SCNNode進行copy時,其屬性SCNMaterial並不會執行深拷貝,也就是說被拷貝對象屬性只是對原來屬性的引用而已。 **3. SCNLight ** SceneNode中徹底都是動態光照,提供四種類型的光照

  • SCNLightTypeAmbient 環境光
  • SCNLightTypeOmni 聚光燈
  • SCNLightTypeDirectional 定向光源
  • SCNLightTypeSpot 點光源 因爲太陽做爲太陽系的光源,因此咱們須要能從各個角度看到它發光,因此它的type = SCNLightTypeOmni,也就是聚光燈
//給sunNode添加光照
SCNNode *lightNode = [SCNNode node];
lightNode.light = [SCNLight light];
lightNode.light.color = [UIColor blackColor]; // initially switched off
lightNode.light.type = SCNLightTypeOmni;
[_sunNode addChildNode:lightNode];
    
 // Configure attenuation distances because we don't want to light the floor lightNode.light.attenuationEndDistance = 19; lightNode.light.attenuationStartDistance = 21; 複製代碼

添加動畫--CoreAnimation

地球自轉動畫

//earthNode以y軸不停的旋轉,每次旋轉的週期爲1s。
[_earthNoderunAction:[SCNActionrepeatActionForever:[SCNActionrotateByX:0y:2z:0duration:1]]];
複製代碼

月球自轉動畫

CABasicAnimation*animation = [CABasicAnimationanimationWithKeyPath:@"rotation"];//月球自轉
animation.duration=1.5; //自轉週期1.5s
animation.toValue= [NSValuevalueWithSCNVector4:SCNVector4Make(0,1,0,M_PI*2)];//此處的意思是圍繞y軸([0,0,0]->[0,1,0])旋轉360°
animation.repeatCount=FLT_MAX;//重複次數,此處無限次
[_moonNode addAnimation:animation forKey:@"moon rotation"];//將動畫添加至moonNode節點
複製代碼

接下來咱們來實現月球隨着地球公轉 moonRotationNode添加moonNode,moonNode因爲與原點有偏移,moonRotation自轉後就實現了moonNode圍繞原點公轉了,而後再加moonRotationNode添加至earthGroupNode便可。

_moonNode.position=SCNVector3Make(0.1,0,0);//設置moon的位置
SCNNode*moonRotationNode = [SCNNodenode];
[moonRotationNodeaddChildNode:_moonNode];
// Rotate the moon around the Earth
CABasicAnimation*moonRotationAnimation = [CABasicAnimationanimationWithKeyPath:@"rotation"];
moonRotationAnimation.duration=15.0;
moonRotationAnimation.toValue= [NSValuevalueWithSCNVector4:SCNVector4Make(0,1,0,M_PI*2)];
moonRotationAnimation.repeatCount=FLT_MAX;
[moonRotationNodeaddAnimation:animationforKey:@"moon rotation around earth"];
[_earthGroupNodeaddChildNode:moonRotationNode];//將moonRotationNode添加至earthGroupNode節點
複製代碼

如何實現地球子系統圍繞太陽公轉

SCNNode*earthRotationNode = [SCNNodenode];
[_sunNodeaddChildNode:earthRotationNode];
// Earth-group (will contain the Earth, and the Moon)
[earthRotationNodeaddChildNode:_earthGroupNode];
// Rotate the Earth around the Sun
animation = [CABasicAnimationanimationWithKeyPath:@"rotation"];
animation.duration=30.0;
animation.toValue= [NSValuevalueWithSCNVector4:SCNVector4Make(0,1,0,M_PI*2)];
animation.repeatCount=FLT_MAX;
[earthRotationNodeaddAnimation:animationforKey:@"earth rotation around sun"];
複製代碼

同理其餘幾顆星體也能夠如此,因爲土星自帶行星環,須要額外處理一下。

CABasicAnimation*animation = [CABasicAnimationanimationWithKeyPath:@"rotation"];//月球自轉
animation.duration=1.5; //自轉週期1.5s
animation.toValue= [NSValuevalueWithSCNVector4:SCNVector4Make(0,1,0,M_PI*2)];//此處的意思是圍繞y軸([0,0,0]->[0,1,0])旋轉360°
animation.repeatCount=FLT_MAX;//重複次數,此處無限次
[_moonNode addAnimation:animation forKey:@"moon rotation"];//將動畫添加至moonNode節點
複製代碼

爲了讓太陽的效果更佳逼真,咱們給它增長了光環

// Add a halo to the Sun (a simple textured plane that does not write to depth)
    _sunHaloNode = [SCNNode node];
    _sunHaloNode.geometry = [SCNPlane planeWithWidth:2.5 height:2.5];
    _sunHaloNode.rotation = SCNVector4Make(1, 0, 0, 0 * M_PI / 180.0);
    _sunHaloNode.geometry.firstMaterial.diffuse.contents = @"art.scnassets/solar/sun-halo.png";
    _sunHaloNode.geometry.firstMaterial.lightingModelName = SCNLightingModelConstant; // no lighting
    _sunHaloNode.geometry.firstMaterial.writesToDepthBuffer = NO; // do not write to depth
    _sunHaloNode.opacity = 0.2;
    [_sunNode addChildNode:_sunHaloNode];
複製代碼

咱們還給地球增長雲層

SCNNode *cloudsNode = [SCNNode node];
    cloudsNode.geometry = [SCNSphere sphereWithRadius:0.06];
    [_earthNode addChildNode:cloudsNode];
    
    cloudsNode.opacity = 0.5;
    // This effect can also be achieved with an image with some transparency set as the contents of the 'diffuse' property
    cloudsNode.geometry.firstMaterial.transparent.contents = @"art.scnassets/solar/cloudsTransparency.png";
    cloudsNode.geometry.firstMaterial.transparencyMode = SCNTransparencyModeRGBZero;
複製代碼

以上咱們就實現了太陽系的模型建立以及行星的自轉並週期的圍繞太陽公轉,可是如何纔能有更好的觀看效果呢,因而咱們記起了上章講到的ARKit,經過ARSession的一個Delegate函數

//pragma mark -ARSessionDelegate
//會話位置更新
-- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
    //監聽手機的移動,實現近距離查看太陽系細節,爲了凸顯效果變化值*3
    [_sunNode setPosition:SCNVector3Make(
                          -3 * frame.camera.transform.columns[3].x, 
                          -0.1 - 3 * frame.camera.transform.columns[3].y, 
                          -2 - 3 * frame.camera.transform.columns[3].z)];
}
複製代碼

小結 這樣咱們就完成了一個經過ARKit+SceneKit實現將太陽系裝進iPhone的夢想了,女友說我想要天上的星星,因而我打開了ARSolarPlay抓住了Solar,你看整個太陽系盡在個人掌中,說吧,你想要哪顆?簡直撩妹/漢神器有木有。

代碼見同性交友網站:
AR太陽系(Swift實現)
AR太陽系(OC實現)


若是您以爲有價值,請在github賞個star,不勝感激。 若是有什麼想交流的,歡迎私信。

相關文章
相關標籤/搜索