ARKit识别平面-Objective-C

简单的ARKit的Demo,网上很多,包括Xcode自建的AR项目也可以直接实现一个飞机效果的Demo,不在赘述基本实现。
本文主要讲述如何一步一步实现ARKit对平面的识别,以及添加模型到该平面的节点上。

基础设置

首先,新建一个类,并且写好基本的代码设置
//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:来监听捕捉到的平面。

/**
 当新的节点映射到指定锚点时调用

 @param renderer 渲染画面的渲染器
 @param node 新添加的节点
 @param anchor 节点映射到的锚点
 */
- (void)renderer:(id<SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    //过滤平面锚点
    if ([anchor isKindOfClass:[ARPlaneAnchor class]]) {
       //平面锚点:anchor
    }
}

这样我们就可以获取每次捕捉到的平面锚点了。

画面反馈

很显然我们在手机屏幕上并不能直观的获得这一反馈,我们需要在屏幕上加点什么东西来让使用者能够知道,识别到了平面,和这块平面的范围。
此时,我们需要通过代码构建一个模型,将其添加到一个节点上,再将该节点添加到我们的sence中。
SCNNode有三种实例化方法:

  • node
  • nodeWithGeometry:
  • nodeWithMDLObject:
    我们采用第二种方法,nodeWithGeometry:,可以通过实例化一个SCNGeometry对象来产生节点。而我们可以轻易的通过代码实例化一个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对象定义表面外观属性,规定对象表面如何着色或纹理以及如何反应灯光所用)

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

接下来我们将这个平面添加到平面锚点的节点上就行了,添加时需要注意的是,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的属性注释中提到,当两个平面合并的事件发生时,新的平面将会被移除。这个解释光从字面上看,有些难以理解。

我们建立两个可变字典来保存每次添加的节点和平面,以锚点的标识为key

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

然后实现更新节点的代理方法与移除节点的代理方法

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

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

我们再次运行程序,首先识别一块地板,然后往没有识别到的区域平移,控制台打印的结果如下(此处将锚点的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] 更新节点--------锚点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] 更新节点------------锚点B
2018-01-24 14:11:51.945646+0800 MHARDemo_2_OC[33797:10928469] 更新节点------------锚点B
2018-01-24 14:11:52.045680+0800 MHARDemo_2_OC[33797:10928482] 更新节点------------锚点B
2018-01-24 14:11:52.145672+0800 MHARDemo_2_OC[33797:10928480] 更新节点------------锚点B
2018-01-24 14:11:52.245644+0800 MHARDemo_2_OC[33797:10928469] 更新节点------------锚点B
2018-01-24 14:11:52.345731+0800 MHARDemo_2_OC[33797:10928482] 更新节点------------锚点B
2018-01-24 14:11:52.445655+0800 MHARDemo_2_OC[33797:10928482] 更新节点------------锚点B
2018-01-24 14:11:52.545712+0800 MHARDemo_2_OC[33797:10928480] 更新节点------------锚点B
2018-01-24 14:11:52.645669+0800 MHARDemo_2_OC[33797:10928469] 更新节点------------锚点B
2018-01-24 14:12:01.646132+0800 MHARDemo_2_OC[33797:10928478] 更新节点------------锚点B
2018-01-24 14:12:01.746214+0800 MHARDemo_2_OC[33797:10928469] 更新节点------------锚点B
2018-01-24 14:12:01.846150+0800 MHARDemo_2_OC[33797:10928478] 更新节点------------锚点B
2018-01-24 14:12:01.946155+0800 MHARDemo_2_OC[33797:10928478] 更新节点------------锚点B
2018-01-24 14:12:02.046124+0800 MHARDemo_2_OC[33797:10928469] 更新节点------------锚点B
2018-01-24 14:12:02.146123+0800 MHARDemo_2_OC[33797:10928469] 更新节点------------锚点B
2018-01-24 14:12:02.246198+0800 MHARDemo_2_OC[33797:10928469] 移除节点------------锚点B
2018-01-24 14:12:02.246509+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:02.346143+0800 MHARDemo_2_OC[33797:10928480] 更新节点--------锚点A
2018-01-24 14:12:04.446334+0800 MHARDemo_2_OC[33797:10928478] 更新节点--------锚点A
2018-01-24 14:12:04.546234+0800 MHARDemo_2_OC[33797:10928480] 更新节点--------锚点A
2018-01-24 14:12:04.646352+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:04.746282+0800 MHARDemo_2_OC[33797:10928480] 更新节点--------锚点A
2018-01-24 14:12:04.846371+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:04.946271+0800 MHARDemo_2_OC[33797:10928478] 更新节点--------锚点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] 更新节点----------------锚点C
2018-01-24 14:12:06.746361+0800 MHARDemo_2_OC[33797:10928478] 更新节点----------------锚点C
2018-01-24 14:12:09.546507+0800 MHARDemo_2_OC[33797:10928478] 更新节点--------锚点A
2018-01-24 14:12:09.646517+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:09.746525+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:09.846596+0800 MHARDemo_2_OC[33797:10928480] 更新节点--------锚点A
2018-01-24 14:12:09.946547+0800 MHARDemo_2_OC[33797:10928478] 更新节点--------锚点A
2018-01-24 14:12:10.046569+0800 MHARDemo_2_OC[33797:10928469] 移除节点----------------锚点C
2018-01-24 14:12:10.046894+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:10.146545+0800 MHARDemo_2_OC[33797:10928482] 更新节点--------锚点A
2018-01-24 14:12:10.246552+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:10.346560+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:10.446555+0800 MHARDemo_2_OC[33797:10928480] 更新节点--------锚点A
2018-01-24 14:12:11.346620+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:11.446623+0800 MHARDemo_2_OC[33797:10928482] 更新节点--------锚点A
2018-01-24 14:12:11.546624+0800 MHARDemo_2_OC[33797:10928480] 更新节点--------锚点A
2018-01-24 14:12:11.646631+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:11.746665+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:11.846669+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:11.946644+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:12.046644+0800 MHARDemo_2_OC[33797:10928482] 更新节点--------锚点A
2018-01-24 14:12:14.046760+0800 MHARDemo_2_OC[33797:10928469] 更新节点--------锚点A
2018-01-24 14:12:14.146824+0800 MHARDemo_2_OC[33797:10928480] 更新节点--------锚点A
2018-01-24 14:12:14.646875+0800 MHARDemo_2_OC[33797:10928482] 更新节点--------锚点A

我们可以看出来,整个过程如图:


锚点更新过程

这样子我们在更新锚点的代理方法中,重新设置锚点对应的节点的坐标、节点对应的平面模型的大小,就可以实现更新所识别的平面到手机界面上了

- (void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    if ([self.planeDictionary objectForKey:anchor.identifier] && [self.nodeDictionary objectForKey:anchor.identifier]) {
        NSLog(@"更新节点%@",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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 偶然打开QQ,忽见联系人一栏显示有新朋友,打开来,却蓦地怔在了那里:推荐好友是——“钱驴”,一个已经死去半...
    鱼落忘川阅读 669评论 0 5
  • 如果可以 请把此时此刻以后 的每一次告别 都当做是 永别 用力一点 请你再用力一点 多说一句 可能是最后一句 多看...
    幻梦邪魂阅读 229评论 0 3
  • 今天准备在CentOS上安装Python3(因为我发现我的系统上只有2并没有3版本),上网发现都是先在网站上下载一...
    射手再见蓝天575阅读 1,170评论 0 1
  • 近日由日本推民(Twitter使用者简称)们严选出的“2014动画歌曲排行Top8”新鲜出炉!你的神曲是否在榜? ...
    animesama阅读 421评论 0 11