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

miliPolo發表於2017-12-21

轉載請註明原作者

上篇文章我們介紹如何建立一個ARKit專案,並且建立太陽、地球這些球體,接下來我們來談一談如何讓它們動起來。

演示視訊

ARSolarPlay.gif


天文科普

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

聖鬥士星矢.jpg


3D模型建立--SceneKit

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的常用方法:

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,不勝感激。 如果有什麼想交流的,歡迎私信。

相關文章