ARKit识别平面-Objective-C

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

基础设置

首先,新建一个类,并且写好基本的代码设置
//RecognitionPlaneViewController

#import "RecognitionPlaneViewController.h"
#import 
#import 

@interface RecognitionPlaneViewController ()
@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)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];

这样我们就可以直观的接收到平面识别反馈了:


ARKit识别平面-Objective-C_第1张图片
平面反馈

更新所识别的平面

之前提到了ARWorldTrackingConfigurationplaneDetection这个属性,Xcode的属性注释中提到,当两个平面合并的事件发生时,新的平面将会被移除。这个解释光从字面上看,有些难以理解。

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

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

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

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

- (void)renderer:(id)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

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


ARKit识别平面-Objective-C_第2张图片
锚点更新过程

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

- (void)renderer:(id)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

你可能感兴趣的:(ARKit识别平面-Objective-C)