轉(zhuǎn)載請注明原作者
上篇文章我們介紹如何創(chuàng)建一個(gè)ARKit項(xiàng)目,并且創(chuàng)建太陽、地球這些球體,接下來我們來談一談如何讓它們動(dòng)起來。
演示視頻:

天文科普
首先科普下太陽系的結(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)一步完善。

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物理模型。

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