ARKit從入門到精通(9)-ARKit讓飛機(jī)跟著鏡頭飛起來(lái)

  • 廢話不多說(shuō),先看效果
1001.gif

<h2 id="1.1">1.1-ARKit物體跟隨相機(jī)移動(dòng)流程介紹</h2>

  • 1.點(diǎn)擊屏幕添加物體酵紫,已經(jīng)在第三小節(jié)ARKit從入門到精通(3)-ARKit自定義實(shí)現(xiàn)中介紹

  • 2.監(jiān)聽(tīng)ARSession的代理

    • 相機(jī)的移動(dòng)是由AR會(huì)話來(lái)監(jiān)聽(tīng)的
  • 3.在ARSession的相機(jī)移動(dòng)代理中獲取相機(jī)的當(dāng)前位置适篙,修改物體的位置與相機(jī)位置一致楣富,即可實(shí)現(xiàn)物體跟隨相機(jī)移動(dòng)而移動(dòng)

  • 核心代碼介紹

#pragma mark -ARSessionDelegate

//會(huì)話位置更新(監(jiān)聽(tīng)相機(jī)的移動(dòng))占卧,此代理方法會(huì)調(diào)用非常頻繁涣觉,只要相機(jī)移動(dòng)就會(huì)調(diào)用贵涵,如果相機(jī)移動(dòng)過(guò)快列肢,會(huì)有一定的誤差,具體的需要強(qiáng)大的算法去優(yōu)化宾茂,筆者這里就不深入了
- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
    NSLog(@"相機(jī)移動(dòng)");
    if (self.arType != ARTypeMove) {
        return;
    }
    //移動(dòng)飛機(jī)
    if (self.planeNode) {
        
        //捕捉相機(jī)的位置瓷马,讓節(jié)點(diǎn)隨著相機(jī)移動(dòng)而移動(dòng)
        //根據(jù)官方文檔記錄,相機(jī)的位置參數(shù)在4X4矩陣的第三列
        self.planeNode.position =SCNVector3Make(frame.camera.transform.columns[3].x,frame.camera.transform.columns[3].y,frame.camera.transform.columns[3].z);
    }
    
}

<h2 id="1.2">1.2-完整代碼</h2>


#import "ARSCNViewViewController.h"

//3D游戲框架
#import <SceneKit/SceneKit.h>
//ARKit框架
#import <ARKit/ARKit.h>

@interface ARSCNViewViewController ()<ARSCNViewDelegate,ARSessionDelegate>

//AR視圖:展示3D界面
@property(nonatomic,strong)ARSCNView *arSCNView;

//AR會(huì)話跨晴,負(fù)責(zé)管理相機(jī)追蹤配置及3D相機(jī)坐標(biāo)
@property(nonatomic,strong)ARSession *arSession;

//會(huì)話追蹤配置:負(fù)責(zé)追蹤相機(jī)的運(yùn)動(dòng)
@property(nonatomic,strong)ARSessionConfiguration *arSessionConfiguration;

//飛機(jī)3D模型(本小節(jié)加載多個(gè)模型)
@property(nonatomic,strong)SCNNode *planeNode;

@end

@implementation ARSCNViewViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    
    // Do any additional setup after loading the view.
}

- (void)back:(UIButton *)btn
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    //1.將AR視圖添加到當(dāng)前視圖
    [self.view addSubview:self.arSCNView];
    //2.開(kāi)啟AR會(huì)話(此時(shí)相機(jī)開(kāi)始工作)
    [self.arSession runWithConfiguration:self.arSessionConfiguration];
    
    
    //添加返回按鈕
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setTitle:@"返回" forState:UIControlStateNormal];
    btn.frame = CGRectMake(self.view.bounds.size.width/2-50, self.view.bounds.size.height-100, 100, 50);
    btn.backgroundColor = [UIColor greenColor];
    [btn addTarget:self action:@selector(back:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
    
}

#pragma mark- 點(diǎn)擊屏幕添加飛機(jī)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (self.arType == ARTypePlane || self.planeNode != nil) {
        return;
    }
    
    //1.使用場(chǎng)景加載scn文件(scn格式文件是一個(gè)基于3D建模的文件欧聘,使用3DMax軟件可以創(chuàng)建,這里系統(tǒng)有一個(gè)默認(rèn)的3D飛機(jī))--------在右側(cè)我添加了許多3D模型端盆,只需要替換文件名即可
    SCNScene *scene = [SCNScene sceneNamed:@"Models.scnassets/ship.scn"];
    //2.獲取飛機(jī)節(jié)點(diǎn)(一個(gè)場(chǎng)景會(huì)有多個(gè)節(jié)點(diǎn)怀骤,此處我們只寫(xiě),飛機(jī)節(jié)點(diǎn)則默認(rèn)是場(chǎng)景子節(jié)點(diǎn)的第一個(gè))
    //所有的場(chǎng)景有且只有一個(gè)根節(jié)點(diǎn)焕妙,其他所有節(jié)點(diǎn)都是根節(jié)點(diǎn)的子節(jié)點(diǎn)
    
    SCNNode *shipNode = scene.rootNode.childNodes[0];
    
    self.planeNode = shipNode;
    
    //飛機(jī)比較大蒋伦,釋放縮放一下并且調(diào)整位置讓其在屏幕中間
    shipNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
    shipNode.position = SCNVector3Make(0, -15,-15);
   ;
    //一個(gè)飛機(jī)的3D建模不是一氣呵成的,可能會(huì)有很多個(gè)子節(jié)點(diǎn)拼接访敌,所以里面的子節(jié)點(diǎn)也要一起改凉敲,否則上面的修改會(huì)無(wú)效
    for (SCNNode *node in shipNode.childNodes) {
        node.scale = SCNVector3Make(0.5, 0.5, 0.5);
        node.position = SCNVector3Make(0, -15,-15);
        
    }
    
    //3.將飛機(jī)節(jié)點(diǎn)添加到當(dāng)前屏幕中
    [self.arSCNView.scene.rootNode addChildNode:shipNode];
    
    
}

#pragma mark -搭建ARKit環(huán)境


//懶加載會(huì)話追蹤配置
- (ARSessionConfiguration *)arSessionConfiguration
{
    if (_arSessionConfiguration != nil) {
        return _arSessionConfiguration;
    }
    
    //1.創(chuàng)建世界追蹤會(huì)話配置(使用ARWorldTrackingSessionConfiguration效果更加好),需要A9芯片支持
    ARWorldTrackingSessionConfiguration *configuration = [[ARWorldTrackingSessionConfiguration alloc] init];
    //2.設(shè)置追蹤方向(追蹤平面寺旺,后面會(huì)用到)
    configuration.planeDetection = ARPlaneDetectionHorizontal;
    _arSessionConfiguration = configuration;
    //3.自適應(yīng)燈光(相機(jī)從暗到強(qiáng)光快速過(guò)渡效果會(huì)平緩一些)
    _arSessionConfiguration.lightEstimationEnabled = YES;
    
    return _arSessionConfiguration;
    
}

//懶加載拍攝會(huì)話
- (ARSession *)arSession
{
    if(_arSession != nil)
    {
        return _arSession;
    }
    //1.創(chuàng)建會(huì)話
    _arSession = [[ARSession alloc] init];
    _arSession.delegate = self;
    //2返回會(huì)話
    return _arSession;
}

//創(chuàng)建AR視圖
- (ARSCNView *)arSCNView
{
    if (_arSCNView != nil) {
        return _arSCNView;
    }
    //1.創(chuàng)建AR視圖
    _arSCNView = [[ARSCNView alloc] initWithFrame:self.view.bounds];
    
    //2.設(shè)置代理  捕捉到平地會(huì)在代理回調(diào)中返回
    _arSCNView.delegate = self;
    
    //2.設(shè)置視圖會(huì)話
    _arSCNView.session = self.arSession;
    //3.自動(dòng)刷新燈光(3D游戲用到爷抓,此處可忽略)
    _arSCNView.automaticallyUpdatesLighting = YES;
    
    return _arSCNView;
}

#pragma mark -- ARSCNViewDelegate



//添加節(jié)點(diǎn)時(shí)候調(diào)用(當(dāng)開(kāi)啟平地捕捉模式之后,如果捕捉到平地阻塑,ARKit會(huì)自動(dòng)添加一個(gè)平地節(jié)點(diǎn))
- (void)renderer:(id <SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    
    if(self.arType != ARTypePlane)
    {
        return;
    }
    
    if ([anchor isMemberOfClass:[ARPlaneAnchor class]]) {
        NSLog(@"捕捉到平地");
        
        //添加一個(gè)3D平面模型蓝撇,ARKit只有捕捉能力,錨點(diǎn)只是一個(gè)空間位置陈莽,要想更加清楚看到這個(gè)空間渤昌,我們需要給空間添加一個(gè)平地的3D模型來(lái)渲染他
        
        //1.獲取捕捉到的平地錨點(diǎn)
        ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
        //2.創(chuàng)建一個(gè)3D物體模型    (系統(tǒng)捕捉到的平地是一個(gè)不規(guī)則大小的長(zhǎng)方形,這里筆者將其變成一個(gè)長(zhǎng)方形走搁,并且是否對(duì)平地做了一個(gè)縮放效果)
        //參數(shù)分別是長(zhǎng)寬高和圓角
        SCNBox *plane = [SCNBox boxWithWidth:planeAnchor.extent.x*0.3 height:0 length:planeAnchor.extent.x*0.3 chamferRadius:0];
        //3.使用Material渲染3D模型(默認(rèn)模型是白色的独柑,這里筆者改成紅色)
        plane.firstMaterial.diffuse.contents = [UIColor redColor];
        
        //4.創(chuàng)建一個(gè)基于3D物體模型的節(jié)點(diǎn)
        SCNNode *planeNode = [SCNNode nodeWithGeometry:plane];
        //5.設(shè)置節(jié)點(diǎn)的位置為捕捉到的平地的錨點(diǎn)的中心位置  SceneKit框架中節(jié)點(diǎn)的位置position是一個(gè)基于3D坐標(biāo)系的矢量坐標(biāo)SCNVector3Make
        planeNode.position =SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z);
        
        //self.planeNode = planeNode;
        [node addChildNode:planeNode];
        
        
        //2.當(dāng)捕捉到平地時(shí),2s之后開(kāi)始在平地上添加一個(gè)3D模型
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //1.創(chuàng)建一個(gè)花瓶場(chǎng)景
            SCNScene *scene = [SCNScene sceneNamed:@"Models.scnassets/vase/vase.scn"];
            //2.獲取花瓶節(jié)點(diǎn)(一個(gè)場(chǎng)景會(huì)有多個(gè)節(jié)點(diǎn)私植,此處我們只寫(xiě)忌栅,花瓶節(jié)點(diǎn)則默認(rèn)是場(chǎng)景子節(jié)點(diǎn)的第一個(gè))
            //所有的場(chǎng)景有且只有一個(gè)根節(jié)點(diǎn),其他所有節(jié)點(diǎn)都是根節(jié)點(diǎn)的子節(jié)點(diǎn)
            SCNNode *vaseNode = scene.rootNode.childNodes[0];
            
            //4.設(shè)置花瓶節(jié)點(diǎn)的位置為捕捉到的平地的位置曲稼,如果不設(shè)置索绪,則默認(rèn)為原點(diǎn)位置湖员,也就是相機(jī)位置
            vaseNode.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z);
            
            //5.將花瓶節(jié)點(diǎn)添加到當(dāng)前屏幕中
            //!!!此處一定要注意:花瓶節(jié)點(diǎn)是添加到代理捕捉到的節(jié)點(diǎn)中,而不是AR試圖的根節(jié)點(diǎn)瑞驱。因?yàn)椴蹲降降钠降劐^點(diǎn)是一個(gè)本地坐標(biāo)系娘摔,而不是世界坐標(biāo)系
            [node addChildNode:vaseNode];
        });
    }
}

//刷新時(shí)調(diào)用
- (void)renderer:(id <SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    NSLog(@"刷新中");
}

//更新節(jié)點(diǎn)時(shí)調(diào)用
- (void)renderer:(id <SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    NSLog(@"節(jié)點(diǎn)更新");
    
}

//移除節(jié)點(diǎn)時(shí)調(diào)用
- (void)renderer:(id <SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
    NSLog(@"節(jié)點(diǎn)移除");
}

#pragma mark -ARSessionDelegate

//會(huì)話位置更新(監(jiān)聽(tīng)相機(jī)的移動(dòng)),此代理方法會(huì)調(diào)用非常頻繁唤反,只要相機(jī)移動(dòng)就會(huì)調(diào)用凳寺,如果相機(jī)移動(dòng)過(guò)快,會(huì)有一定的誤差拴袭,具體的需要強(qiáng)大的算法去優(yōu)化读第,筆者這里就不深入了
- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
    NSLog(@"相機(jī)移動(dòng)");
    if (self.arType != ARTypeMove) {
        return;
    }
    //移動(dòng)飛機(jī)
    if (self.planeNode) {
        
        //捕捉相機(jī)的位置,讓節(jié)點(diǎn)隨著相機(jī)移動(dòng)而移動(dòng)
        //根據(jù)官方文檔記錄拥刻,相機(jī)的位置參數(shù)在4X4矩陣的第三列
        self.planeNode.position =SCNVector3Make(frame.camera.transform.columns[3].x,frame.camera.transform.columns[3].y,frame.camera.transform.columns[3].z);
    }
    
}
- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor*>*)anchors
{
    NSLog(@"添加錨點(diǎn)");
    
}


- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor*>*)anchors
{
    NSLog(@"刷新錨點(diǎn)");
    
}


- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor*>*)anchors
{
    NSLog(@"移除錨點(diǎn)");
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end


<h2 id="1.3">1.3-代碼下載地址</h2>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吴汪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蒸眠,更是在濱河造成了極大的恐慌漾橙,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楞卡,死亡現(xiàn)場(chǎng)離奇詭異霜运,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蒋腮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門淘捡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人池摧,你說(shuō)我怎么就攤上這事焦除。” “怎么了作彤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵膘魄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我竭讳,道長(zhǎng)创葡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任绢慢,我火速辦了婚禮蹈丸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呐芥。我一直安慰自己逻杖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布思瘟。 她就那樣靜靜地躺著荸百,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滨攻。 梳的紋絲不亂的頭發(fā)上够话,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音光绕,去河邊找鬼女嘲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诞帐,可吹牛的內(nèi)容都是我干的欣尼。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼停蕉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼愕鼓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起慧起,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤菇晃,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蚓挤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體磺送,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年灿意,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了估灿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脾歧,死狀恐怖甲捏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞭执,我是刑警寧澤司顿,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站兄纺,受9級(jí)特大地震影響大溜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜估脆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一钦奋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦付材、人聲如沸朦拖。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)璧帝。三九已至,卻和暖如春富寿,著一層夾襖步出監(jiān)牢的瞬間睬隶,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工页徐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苏潜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓变勇,卻偏偏與公主長(zhǎng)得像恤左,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贰锁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容