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

轉(zhuǎn)載請注明原作者

上篇文章我們介紹如何創(chuàng)建一個(gè)ARKit項(xiàng)目,并且創(chuàng)建太陽、地球這些球體,接下來我們來談一談如何讓它們動(dòng)起來。

演示視頻

ARSolarPlay.gif


天文科普

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

圣斗士星矢.jpg

3D模型創(chuàng)建--SceneKit

AR工程中有一個(gè)ARSCNView,它用來加載3D模型的AR視圖的,它繼承于SCNView,相對的加載2D視圖的就是ARSKView,視圖中的那些模型的創(chuàng)建運(yùn)動(dòng)就需要用到本章所說的SceneKit和SpriteKit。它們是iOS中用來開發(fā)3D模型和2D模型的引擎,由于沒用過Unity3D開發(fā),所以此處不介紹。

Sprite是用來創(chuàng)建2D模型,在游戲開發(fā)中,指的是以圖像方式呈現(xiàn)在屏幕上的一個(gè)圖像。這個(gè)圖像也許可以移動(dòng),用戶可以與其交互,也有可能僅只是游戲的一個(gè)靜止的背景圖。而在AR中,2D模型會(huì)隨著手機(jī)的遠(yuǎn)近放大縮小,而不能像3D模型那樣可以從側(cè)面觀察。

SceneKit 建立在 OpenGL 的基礎(chǔ)上,包含了如光照、模型、材質(zhì)、攝像機(jī)等高級引擎特性,我們可以基于它做出很多逼真的3D物理模型。

SCeneKit結(jié)構(gòu)圖.jpg
SCNScene & SCNNode

每個(gè)ARSCNView中都帶有一個(gè)場景SCNScene,它用來承載那些帶有幾何結(jié)構(gòu)、光度、相機(jī)以及其他屬性的節(jié)點(diǎn)SCNNode,一個(gè)完整的3D場景就這么展現(xiàn)出來了。一個(gè)SCNScene可以包含多個(gè)SCNNode子節(jié)點(diǎn),它們一般都是呈樹狀結(jié)構(gòu),一個(gè)子節(jié)點(diǎn)SCNNode可以有多個(gè)childNode,而SCNNode只有一個(gè)parentNode,rootNode作為根節(jié)點(diǎn),我們通過rootNode添加自己的子節(jié)點(diǎn)SCNNode。
SCNNode的常用方法:

addChildNode(_:)
insertChildNode(_: atIndex:)
removeFromParentNode()

接下來介紹下SCNNode的幾種常用的屬性對象
** 1. SCNGeometry **
??SceneNode提供幾種幾何模型,例如六面體(SCNBox)、平面(SCNPlane,只有一面)、無限平面(SCNFloor,沿著x-z平面無限延伸)、球體(SCNSphere)等等。
例如我們創(chuàng)建一個(gè)半徑為0.25的球體

SCNNode *sunNode = [SCNNode new];
sunNode.geometry = [SCNSphere sphereWithRadius:0.25];

為了突出行星運(yùn)動(dòng)軌跡,我們給每顆星星添加了軌道,一開始我使用的是SCNPlane后來發(fā)現(xiàn)它只有一個(gè)平面,你從反面是看不到的,于是我使用的是SCNBox

SCNNode *mercuryOrbit = [SCNNode node];
//設(shè)置不透明度
mercuryOrbit.opacity = 0.4;
//設(shè)置軌道的結(jié)構(gòu)體,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];

補(bǔ)充一下紋理濾波這個(gè)屬性有什么用?
當(dāng)材料表面的部分出現(xiàn)較大或小于原來的紋理圖像時(shí),紋理過濾決定了材料屬性的內(nèi)容的外觀

@property(nonatomic) SCNFilterMode minificationFilter
可選項(xiàng)
typedef enum : NSInteger { 
SCNFilterModeNone = 0, // 當(dāng)這個(gè)位置沒有紋理顏色時(shí),會(huì)采樣離他最近的顏色值 
SCNFilterModeNearest = 1, //當(dāng)這個(gè)位置沒有紋理顏色時(shí),線性插值顏色作為自己的顏色
SCNFilterModeLinear = 2, } SCNFilterMode;
默認(rèn)值為 SCNFilterModeLinear

** 2. SCNMaterial **
SceneNode提供8種屬性用來設(shè)置模型材質(zhì)

  • Diffuse 漫發(fā)射屬性表示光和顏色在各個(gè)方向上的反射量
  • Ambient 環(huán)境光以固定的強(qiáng)度和固定的顏色從表面上的所有點(diǎn)反射出來。如果場景中沒有環(huán)境光對象,這個(gè)屬性對節(jié)點(diǎn)沒有影響
  • Specular 鏡面反射是直接反射到使用者身上的光線,類似于鏡子反射光線的方式。此屬性默認(rèn)為黑色,這將導(dǎo)致材料顯得呆滯
  • Normal 正常照明是一種用于制造材料表面光反射的技術(shù),基本上,它試圖找出材料的顛簸和凹痕,以提供更現(xiàn)實(shí)發(fā)光效果
  • Reflective 反射光屬性是一個(gè)鏡像表面反射環(huán)境。表面不會(huì)真實(shí)地反映場景中的其他物體
  • Emission 該屬性是由模型表面發(fā)出的顏色。默認(rèn)情況下,此屬性設(shè)置為黑色。如果你提供了一個(gè)顏色,這個(gè)顏色就會(huì)體現(xiàn)出來,你可以提供一個(gè)圖像。SceneKit將使用此圖像提供“基于材料的發(fā)光效應(yīng)”。
  • Transparent 用來設(shè)置材質(zhì)的透明度
  • Multiply 通過計(jì)算其他所有屬性的因素生成最終的合成的顏色
// 地球貼圖
    _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進(jìn)行copy時(shí),其屬性SCNMaterial并不會(huì)執(zhí)行深拷貝,也就是說被拷貝對象屬性只是對原來屬性的引用而已。
**3. SCNLight **
SceneNode中完全都是動(dòng)態(tài)光照,提供四種類型的光照

  • SCNLightTypeAmbient 環(huán)境光
  • SCNLightTypeOmni 聚光燈
  • SCNLightTypeDirectional 定向光源
  • SCNLightTypeSpot 點(diǎn)光源
    由于太陽作為太陽系的光源,所以我們需要能從各個(gè)角度看到它發(fā)光,所以它的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;

添加動(dòng)畫--CoreAnimation

地球自轉(zhuǎn)動(dòng)畫

//earthNode以y軸不停的旋轉(zhuǎn),每次旋轉(zhuǎn)的周期為1s。
[_earthNoderunAction:[SCNActionrepeatActionForever:[SCNActionrotateByX:0y:2z:0duration:1]]];

月球自轉(zhuǎn)動(dòng)畫

CABasicAnimation*animation = [CABasicAnimationanimationWithKeyPath:@"rotation"];//月球自轉(zhuǎn)
animation.duration=1.5; //自轉(zhuǎn)周期1.5s
animation.toValue= [NSValuevalueWithSCNVector4:SCNVector4Make(0,1,0,M_PI*2)];//此處的意思是圍繞y軸([0,0,0]->[0,1,0])旋轉(zhuǎn)360°
animation.repeatCount=FLT_MAX;//重復(fù)次數(shù),此處無限次
[_moonNode addAnimation:animation forKey:@"moon rotation"];//將動(dòng)畫添加至moonNode節(jié)點(diǎn)

接下來我們來實(shí)現(xiàn)月球隨著地球公轉(zhuǎn)
??moonRotationNode添加moonNode,moonNode由于與原點(diǎn)有偏移,moonRotation自轉(zhuǎn)后就實(shí)現(xiàn)了moonNode圍繞原點(diǎn)公轉(zhuǎn)了,然后再加moonRotationNode添加至earthGroupNode即可。

_moonNode.position=SCNVector3Make(0.1,0,0);//設(shè)置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節(jié)點(diǎn)

如何實(shí)現(xiàn)地球子系統(tǒng)圍繞太陽公轉(zhuǎn)

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"];

同理其他幾顆星體也可以如此,由于土星自帶行星環(huán),需要額外處理一下。

CABasicAnimation*animation = [CABasicAnimationanimationWithKeyPath:@"rotation"];//月球自轉(zhuǎn)
animation.duration=1.5; //自轉(zhuǎn)周期1.5s
animation.toValue= [NSValuevalueWithSCNVector4:SCNVector4Make(0,1,0,M_PI*2)];//此處的意思是圍繞y軸([0,0,0]->[0,1,0])旋轉(zhuǎn)360°
animation.repeatCount=FLT_MAX;//重復(fù)次數(shù),此處無限次
[_moonNode addAnimation:animation forKey:@"moon rotation"];//將動(dòng)畫添加至moonNode節(jié)點(diǎn)

為了讓太陽的效果更佳逼真,我們給它增加了光環(huán)

    // 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;

以上我們就實(shí)現(xiàn)了太陽系的模型創(chuàng)建以及行星的自轉(zhuǎn)并周期的圍繞太陽公轉(zhuǎn),但是如何才能有更好的觀看效果呢,于是我們記起了上章講到的ARKit,通過ARSession的一個(gè)Delegate函數(shù)

//pragma mark -ARSessionDelegate
//會(huì)話位置更新
-- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
    //監(jiān)聽手機(jī)的移動(dòng),實(shí)現(xiàn)近距離查看太陽系細(xì)節(jié),為了凸顯效果變化值*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)];
}

小結(jié)

這樣我們就完成了一個(gè)通過ARKit+SceneKit實(shí)現(xiàn)將太陽系裝進(jìn)iPhone的夢想了,女朋友說我想要天上的星星,于是我打開了ARSolarPlay抓住了Solar,你看整個(gè)太陽系盡在我的掌中,說吧,你想要哪顆?簡直撩妹/漢神器有木有。

代碼見同性交友網(wǎng)站:https://github.com/miliPolo/ARSolarPlay(OC實(shí)現(xiàn))
????????????????????https://github.com/miliPolo/ARSolarPlaySwift(Swift實(shí)現(xiàn))

上一篇文章:如何用ARKit將太陽系裝進(jìn)iPhone(一)


如果您覺得有價(jià)值,請?jiān)趃ithub賞個(gè)star,不勝感激。
如果有什么想交流的,歡迎私信。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容