ARKit識別平面-Objective-C

簡單的ARKit的Demo,網上很多,包括Xcode自建的AR項目也可以直接實現(xiàn)一個飛機效果的Demo,不在贅述基本實現(xiàn)。
本文主要講述如何一步一步實現(xiàn)ARKit對平面的識別,以及添加模型到該平面的節(jié)點上。

基礎設置

首先,新建一個類,并且寫好基本的代碼設置
//RecognitionPlaneViewController

#import "RecognitionPlaneViewController.h"
#import <ARKit/ARKit.h>
#import <SceneKit/SceneKit.h>

@interface RecognitionPlaneViewController ()<ARSCNViewDelegate,ARSessionDelegate>
@property (nonatomic, strong)ARSCNView *arSCNView;

@property (nonatomic, strong)ARSession *arSession;

@property (nonatomic, strong)ARConfiguration *arConfiguration;
@end

@implementation RecognitionPlaneViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    
    [self.view addSubview:self.arSCNView];
    
    [self.arSession runWithConfiguration:self.arConfiguration];
}

#pragma mark - ARSCNViewDelegate
#pragma mark - ARSessionDelegate
#pragma mark - lazyLoading
- (ARSCNView *)arSCNView{
    if (!_arSCNView) {
        _arSCNView = [[ARSCNView alloc] initWithFrame:self.view.bounds];
        _arSCNView.delegate = self;
        _arSCNView.session = self.arSession;
    }
    return _arSCNView;
}

- (ARSession *)arSession{
    if (!_arSession) {
        _arSession = [[ARSession alloc] init];
        _arSession.delegate = self;
    }
    return _arSession;
}

- (ARConfiguration *)arConfiguration{
    if (!_arConfiguration) {
        ARWorldTrackingConfiguration *arWTConfiguration = [[ARWorldTrackingConfiguration alloc] init];
        arWTConfiguration.planeDetection = ARPlaneDetectionHorizontal;
        arWTConfiguration.lightEstimationEnabled = YES;
        _arConfiguration = arWTConfiguration;
    }
    return _arConfiguration;
}
@end

識別平面

通過幫助文檔查看ARWorldTrackingConfiguration的planeDetection屬性可以得知,設置好識別平面之后,識別到的平面會被當做一個ARPlaneAnchor的對象添加到正在運行的session中。利用這點,可以利用ARSCNView的代理方法 -renderer:didAddNode:forAnchor:來監(jiān)聽捕捉到的平面。

/**
 當新的節(jié)點映射到指定錨點時調用

 @param renderer 渲染畫面的渲染器
 @param node 新添加的節(jié)點
 @param anchor 節(jié)點映射到的錨點
 */
- (void)renderer:(id<SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    //過濾平面錨點
    if ([anchor isKindOfClass:[ARPlaneAnchor class]]) {
       //平面錨點:anchor
    }
}

這樣我們就可以獲取每次捕捉到的平面錨點了。

畫面反饋

很顯然我們在手機屏幕上并不能直觀的獲得這一反饋,我們需要在屏幕上加點什么東西來讓使用者能夠知道,識別到了平面,和這塊平面的范圍。
此時,我們需要通過代碼構建一個模型,將其添加到一個節(jié)點上,再將該節(jié)點添加到我們的sence中。
SCNNode有三種實例化方法:

  • node
  • nodeWithGeometry:
  • nodeWithMDLObject:
    我們采用第二種方法,nodeWithGeometry:,可以通過實例化一個SCNGeometry對象來產生節(jié)點。而我們可以輕易的通過代碼實例化一個SCNGeometry對象。
    在幫助文檔中, 可以看到SCNGeometry具有諸多子類,我們這里識別平面,可以通過實例化一個SCNPlane對象來反饋用戶。
    實例化SCNPlane時,它的Width與Height可以通過代理方法中獲取的錨點來取得。
if ([anchor isKindOfClass:[ARPlaneAnchor class]]) {
        //實例化為子類
        ARPlaneAnchor *arPlaneAnchor = (ARPlaneAnchor *)anchor;  
    }

ARPlaneAnchor中有三個屬性:

  • alignment一個枚舉值,用來顯示該平面相對于重力的方向,目前只有水平方向這一個枚舉值
  • center平面的中心點相對于錨點的位置
  • extent在錨的坐標空間中平面的范圍
    (對于center、extent這兩個屬性,都為vector_float3類型,文檔中解釋為(x,y,z)的值,但是實際打印出來是(x,y,z,?),最后一個?的值有時為1有時為0,查閱到vector_float3的類型為simd_float3,而simd_float3的解釋為最后一個值是為了與simd_float4的矩陣保持一致而多添加的......使用時應該可以忽略)
    有了extent屬性,我們就可以初始化SCNPlane了
SCNPlane *scnPlane = [SCNPlane planeWithWidth:arPlaneAnchor.extent.x height:arPlaneAnchor.extent.z];

我們還可以設置這個平面模型的顏色,這里設置為綠色。(查到SCNMaterial類是給SCNGeometry對象定義表面外觀屬性,規(guī)定對象表面如何著色或紋理以及如何反應燈光所用)

//調整顏色
scnPlane.firstMaterial.diffuse.contents = [UIColor greenColor];
//調整透明度
scnPlane.firstMaterial.transparency = 0.5;

接下來我們將這個平面添加到平面錨點的節(jié)點上就行了,添加時需要注意的是,SCNPlane的圖形是單面的,初始化后是平行于重力方向的,所以我們需要將其反轉90°也就是-π/2(如果是π/2的話會看不到,因為是單面的)

        SCNNode *planeNode = [SCNNode nodeWithGeometry:scnPlane];
        [planeNode setPosition:SCNVector3Make(arPlaneAnchor.center.x, 0, arPlaneAnchor.center.z)];
        planeNode.transform = SCNMatrix4MakeRotation(- M_PI_2, 1, 0, 0);
        [node addChildNode:planeNode];

這樣我們就可以直觀的接收到平面識別反饋了:


平面反饋

更新所識別的平面

之前提到了ARWorldTrackingConfigurationplaneDetection這個屬性,Xcode的屬性注釋中提到,當兩個平面合并的事件發(fā)生時,新的平面將會被移除。這個解釋光從字面上看,有些難以理解。

我們建立兩個可變字典來保存每次添加的節(jié)點和平面,以錨點的標識為key

        [self.planeDictionary setObject:scnPlane forKey:arPlaneAnchor.identifier];
        [self.nodeDictionary setObject:planeNode forKey:arPlaneAnchor.identifier];

然后實現(xiàn)更新節(jié)點的代理方法與移除節(jié)點的代理方法

- (void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    if ([self.planeDictionary objectForKey:anchor.identifier] && [self.nodeDictionary objectForKey:anchor.identifier]) {
        NSLog(@"更新節(jié)點%@",anchor.identifier);
}

- (void)renderer:(id<SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    NSLog(@"移除節(jié)點%@",anchor.identifier);
    [self.nodeDictionary removeObjectForKey:anchor.identifier];
    [self.planeDictionary removeObjectForKey:anchor.identifier];
}

我們再次運行程序,首先識別一塊地板,然后往沒有識別到的區(qū)域平移,控制臺打印的結果如下(此處將錨點的identifier替換為了錨點A、B、C)

2018-01-24 14:11:51.439594+0800 MHARDemo_2_OC[33797:10928440] 錨點添加至Session
2018-01-24 14:11:51.445644+0800 MHARDemo_2_OC[33797:10928482] center - (-0.017074,0.000000,-0.006556)
2018-01-24 14:11:51.445695+0800 MHARDemo_2_OC[33797:10928482] extent - (0.815687,0.000000,0.418654)
2018-01-24 14:11:51.445748+0800 MHARDemo_2_OC[33797:10928482] 添加平面錨點--------錨點A
2018-01-24 14:11:51.445995+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點--------錨點A
2018-01-24 14:11:51.839800+0800 MHARDemo_2_OC[33797:10928440] 錨點添加至Session
2018-01-24 14:11:51.845624+0800 MHARDemo_2_OC[33797:10928482] center - (0.058377,0.000000,0.022934)
2018-01-24 14:11:51.845675+0800 MHARDemo_2_OC[33797:10928482] extent - (0.829945,0.000000,0.420610)
2018-01-24 14:11:51.845713+0800 MHARDemo_2_OC[33797:10928482] 添加平面錨點------------錨點B
2018-01-24 14:11:51.845896+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點------------錨點B
2018-01-24 14:11:51.945646+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點------------錨點B
2018-01-24 14:11:52.045680+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點------------錨點B
2018-01-24 14:11:52.145672+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點------------錨點B
2018-01-24 14:11:52.245644+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點------------錨點B
2018-01-24 14:11:52.345731+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點------------錨點B
2018-01-24 14:11:52.445655+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點------------錨點B
2018-01-24 14:11:52.545712+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點------------錨點B
2018-01-24 14:11:52.645669+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點------------錨點B
2018-01-24 14:12:01.646132+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點------------錨點B
2018-01-24 14:12:01.746214+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點------------錨點B
2018-01-24 14:12:01.846150+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點------------錨點B
2018-01-24 14:12:01.946155+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點------------錨點B
2018-01-24 14:12:02.046124+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點------------錨點B
2018-01-24 14:12:02.146123+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點------------錨點B
2018-01-24 14:12:02.246198+0800 MHARDemo_2_OC[33797:10928469] 移除節(jié)點------------錨點B
2018-01-24 14:12:02.246509+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:02.346143+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點--------錨點A
2018-01-24 14:12:04.446334+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點--------錨點A
2018-01-24 14:12:04.546234+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點--------錨點A
2018-01-24 14:12:04.646352+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:04.746282+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點--------錨點A
2018-01-24 14:12:04.846371+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:04.946271+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點--------錨點A
2018-01-24 14:12:06.440534+0800 MHARDemo_2_OC[33797:10928440] 錨點添加至Session
2018-01-24 14:12:06.446355+0800 MHARDemo_2_OC[33797:10928480] center - (-0.000406,0.000000,0.008070)
2018-01-24 14:12:06.446400+0800 MHARDemo_2_OC[33797:10928480] extent - (0.402418,0.000000,0.396076)
2018-01-24 14:12:06.446433+0800 MHARDemo_2_OC[33797:10928480] 添加平面錨點----------------錨點C
2018-01-24 14:12:06.446558+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點----------------錨點C
2018-01-24 14:12:06.746361+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點----------------錨點C
2018-01-24 14:12:09.546507+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點--------錨點A
2018-01-24 14:12:09.646517+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:09.746525+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:09.846596+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點--------錨點A
2018-01-24 14:12:09.946547+0800 MHARDemo_2_OC[33797:10928478] 更新節(jié)點--------錨點A
2018-01-24 14:12:10.046569+0800 MHARDemo_2_OC[33797:10928469] 移除節(jié)點----------------錨點C
2018-01-24 14:12:10.046894+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:10.146545+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點--------錨點A
2018-01-24 14:12:10.246552+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:10.346560+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:10.446555+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點--------錨點A
2018-01-24 14:12:11.346620+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:11.446623+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點--------錨點A
2018-01-24 14:12:11.546624+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點--------錨點A
2018-01-24 14:12:11.646631+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:11.746665+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:11.846669+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:11.946644+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:12.046644+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點--------錨點A
2018-01-24 14:12:14.046760+0800 MHARDemo_2_OC[33797:10928469] 更新節(jié)點--------錨點A
2018-01-24 14:12:14.146824+0800 MHARDemo_2_OC[33797:10928480] 更新節(jié)點--------錨點A
2018-01-24 14:12:14.646875+0800 MHARDemo_2_OC[33797:10928482] 更新節(jié)點--------錨點A

我們可以看出來,整個過程如圖:


錨點更新過程

這樣子我們在更新錨點的代理方法中,重新設置錨點對應的節(jié)點的坐標、節(jié)點對應的平面模型的大小,就可以實現(xiàn)更新所識別的平面到手機界面上了

- (void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    if ([self.planeDictionary objectForKey:anchor.identifier] && [self.nodeDictionary objectForKey:anchor.identifier]) {
        NSLog(@"更新節(jié)點%@",anchor.identifier);
        ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
        SCNNode *scnNode = [self.nodeDictionary objectForKey:anchor.identifier];
        SCNPlane *scnPlane = [self.planeDictionary objectForKey:anchor.identifier];
        
        scnPlane.width = planeAnchor.extent.x;
        scnPlane.height = planeAnchor.extent.z;
        
        scnNode.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z);
    }
}

完整項目代碼:https://github.com/wmhzhm/MHARDemo_OC

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

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,641評論 19 139
  • 偶然打開QQ,忽見聯(lián)系人一欄顯示有新朋友,打開來,卻驀地怔在了那里:推薦好友是——“錢驢”,一個已經死去半...
    魚落忘川閱讀 739評論 0 5
  • 如果可以 請把此時此刻以后 的每一次告別 都當做是 永別 用力一點 請你再用力一點 多說一句 可能是最后一句 多看...
    幻夢邪魂閱讀 285評論 0 3
  • 今天準備在CentOS上安裝Python3(因為我發(fā)現(xiàn)我的系統(tǒng)上只有2并沒有3版本),上網發(fā)現(xiàn)都是先在網站上下載一...
    射手再見藍天575閱讀 1,201評論 0 1
  • 近日由日本推民(Twitter使用者簡稱)們嚴選出的“2014動畫歌曲排行Top8”新鮮出爐!你的神曲是否在榜? ...
    animesama閱讀 543評論 0 11

友情鏈接更多精彩內容