【iOS ARKit】协作 Session 实例

协作 Session 使用注意事项

      协作 Session 是在 ARWorldMap 基础上发展起来的技术,ARWorldMap 包含了一系列的地标、ARAnchor 及在观察这些地标和 ARAnchor 时摄像机的视场(View)。如果用户在某一个位置新创建了一个 ARAnchor,这时这个 ARAnchor位置并不是相对于公共世界坐标系的(实际上此时用户根本就不知道是否还有其他参与者),而是被存储成离这个 ARAnchor 最近视场的相对坐标,这些信息也会一并存入到用户的ARWorldMap 中并被发送到其他用户。

      由于 ARAnchor 是相对于 View 的坐标,而这些 View 会分组存储到 ARWorldMap 中,亦即是说,ARAnchor 与任何设备的世界坐标系都没有关系,不管这些ARAnchor 是被本机设备解析到本机场景中,还是通过网络发送到其他设备而被解析到其他用户的场景中,都不会改变 ARAnchor 与 View 之间的相互关系。因此,即使其他用户使用了不同的世界坐标系,他们也能在相同的真实环境位置中看到这个 ARAnchor。

       从以上原理可以看到,ARAnchor 对共享AR体验起到了非常关键的作用,所以为了更好地共享AR体金,开发人员应当在开发时注意以下几点:

     (1)跟踪 ARAnchor 的更新。在ARKit探索环境时,随着采集的特征点信息越来越多,对环境的理解会越来越精准,ARKit会通过对之前的摄像机视场(View)进行微调来优化与调整地标信息,因此,与某一摄像机视场(View)相关联的 ARAnchor 姿态也会随之发生调整,所以应当保持对 ARAnchor 的跟踪以确保ARAnchor 发生更新时能及时反映到当前用户场景中。

    (2)虚拟物体应靠近 ARAnchor。在 ARAnchor 发生更新时,连接到其上的虚拟物体也会发生更新,离ARAnchor 远的虚拟物体在更新时可能会出现误差而导致偏离真实位置。所以连接到ARAnchor 的虚拟对象应当靠近对应的 ARAnchor 以减少误差带来的影响。

    (3)处理好 ARAnchor 与虚拟物体的关系。独立的虚拟物体应当使用独立的ARAnchor,这样每一个独立虚拟物体都可以尽量靠近 ARAnchor,并且在存储时可以存储到 ARWorldMap 相同分组中。对若干个距离较近并且希望保持相互之间位置关系的虚拟物体应当使用同一个 ARAnchor,因为在 ARAnchor 更新时,这些虚拟物体会得到相同的更新矩阵,从而保持相互间的位置关系不发生任何变化。

    (4)使用协作 Session 必须要将 isCollaborationEnabled设置为true,只有设置为 true, ARKit 才会周期性的调用 session(_:didOutputCollaborationData:)方法,也才能将 Collaboration Data 数据发送给所有参与方。

    (5)为更高效可靠地传输 AR 进程数据,ARKit 对 Collaboration Data 数据进行了优先级区分,由ARSession. CollaborationData. Priority 枚举表示,分为两种类型:Critical(关键)和 Optional(可选)。Critical 数据定期更新,对同步 AR体验非常关键,应当被可靠地发送到所有参与设备;Optional 数据产生频率高,几乎每帧产生,重要性不及 Critical 数据,因此有所丢失也不会有太大影响。标记为 Optional 的数据包括设备位置数据。区分优先级可以允许我们对不同的Collaboration Data 数据采取不同的处理策略,提高同步的性能。

    (6) 在使用协作 Session 时,有时我们需要知道某个 ARAnchor 是不是由本机设备生成,ARAnchor 的创建者属于哪个设备,如在某个场合需要在某个参与者退出后清除所有该参与者创建的虚拟物体。

      在ARKit 中,每个 ARSession 在运行时会都会生成一个 UUID(Universally Unique Identifier,全局唯一 ID)用于唯一标识该 Session,同时,在协作 Session 中,每个 ARParticipantAnchor 也都有一个独立且唯一的 Identifier 值标识该参与者,ARParticipantAnchor 与 ARAnchor 都有一个 sessionldentifier 属性,这个sessionldentifier 值与所在设备的ARSession UUID 值相同。因此,利用这些信息我们就可以判断ARAnchor 的创建者,并依据结果进行后续处理,典型的示例代码所示。

   (7)协作 Session 同步从有参与者参与开始,但地图的真正融合开始于参与者物理特征值的匹配,即参与者探索过的物理环境有重叠的部分,一旦地图融合后,每个参与用户都会获得其他参与者探索过的地图,同时会同步所有ARAnchor,所以为了便于 ARKit 更快地融合地图,参与者应当在相同的物理环境中扫描相同的物理区域。

协作 Session 实例

      在 ARKit 中,使用协作 Session 主要利用 session(_:didOutputCollaborationData:)方法跟踪同步所有ARAnchor,其中通过网络收发 Collaboration Data 需要开发人员自行处理,完整代码如下所示。

//
//  CooperationSession.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/2/27.
//import SwiftUI
import ARKit
import RealityKit
import MultipeerConnectivitystruct CooperationSession: View {static var arView:ARView?static var multipeerSession: MultipeerSession?var body: some View {CooperationSessionContent().onDisappear(perform: {CooperationSession.arView?.session.delegate = nilCooperationSession.arView?.session.pause()CooperationSession.arView = nilCooperationSession.multipeerSession?.endConnect()CooperationSession.multipeerSession = nilprint("CooperationSession onDisappear")}).edgesIgnoringSafeArea(.all).navigationTitle("协作Session")}
}struct CooperationSessionContent:UIViewRepresentable {func makeUIView(context: Context) -> some ARView {let arView = ARView(frame: .zero)let config = ARWorldTrackingConfiguration()config.isCollaborationEnabled = trueconfig.planeDetection = .horizontalarView.session.run(config,options: [.resetTracking,.removeExistingAnchors])arView.session.delegate = context.coordinatorCooperationSession.arView = arViewcontext.coordinator.createPlane()context.coordinator.addGesture()return arView}func updateUIView(_ uiView: UIViewType, context: Context) {}func makeCoordinator() -> Coordinator {Coordinator()}class Coordinator: NSObject, ARSessionDelegate {var multipeerSession: MultipeerSession?{return CooperationSession.multipeerSession}var planeEntity : ModelEntity? = nilvar raycastResult : ARRaycastResult?var arView: ARView? {return CooperationSession.arView}func createPlane(){CooperationSession.multipeerSession = MultipeerSession(serviceType: "cooper-session", receivedDataHandler: receiveData(data:from:), peerJoinedHandler: peerJoined(_:), peerLeftHandler: peerLeft(_:), peerDiscoveredHandler: peerDiscovered(_:))let planeMesh = MeshResource.generatePlane(width: 0.15, depth: 0.15)let planeMaterial = SimpleMaterial(color:.white,isMetallic: false)planeEntity = ModelEntity(mesh: planeMesh, materials: [planeMaterial])let planeAnchor = AnchorEntity(plane: .horizontal)do {let planeMesh = MeshResource.generatePlane(width: 0.15, depth: 0.15)var planeMaterial = SimpleMaterial(color: SimpleMaterial.Color.red, isMetallic: false)planeMaterial.color =  try SimpleMaterial.BaseColor(tint:UIColor.yellow.withAlphaComponent(0.9999), texture: MaterialParameters.Texture(TextureResource.load(named: "AR_Placement_Indicator")))planeEntity = ModelEntity(mesh: planeMesh, materials: [planeMaterial])planeAnchor.addChild(planeEntity!)arView?.scene.addAnchor(planeAnchor)} catch let error {print("加载文件失败:\(error)")}}func addGesture(){let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))arView?.addGestureRecognizer(tap)}@objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {guard let raycastResult = raycastResult else {print("还未检测到平面")return}let anchor = ARAnchor(name: "objectAnchor", transform: raycastResult.worldTransform)arView?.session.add(anchor: anchor)}//ARSessionDelegatefunc session(_ session: ARSession, didUpdate frame: ARFrame) {guard let arView = arView,  let result = arView.raycast(from: arView.center, allowing: .estimatedPlane, alignment: .horizontal).first else{return}raycastResult = resultplaneEntity?.setTransformMatrix(result.worldTransform, relativeTo: nil)}func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {guard let arView = arView else {return}for anchor in anchors {if anchor.name == "objectAnchor"{let box = ModelEntity(mesh: MeshResource.generateBox(size: 0.1), materials: [SimpleMaterial.init(color: .green, isMetallic: false)])box.position = [0,0.05,0]let anchorEntity = AnchorEntity(anchor: anchor)anchorEntity.addChild(box)arView.scene.addAnchor(anchorEntity)}}}func session(_ session: ARSession, didOutputCollaborationData data: ARSession.CollaborationData) {guard let multipeerSession = multipeerSession else {return}if !multipeerSession.connectedPeers.isEmpty {do {let encodeData = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true)multipeerSession.sendToAllPeers(encodeData, reliably: data.priority == .critical)} catch  {print("encode data faile")}}}func receiveData(data:Data,from peer: MCPeerID){if let data = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ARSession.CollaborationData.self, from: data){if data.priority == .critical {arView?.session.update(with: data)print(" data updated")}}}func peerDiscovered(_ peer: MCPeerID) -> Bool {guard let multipeerSession = multipeerSession else {return false}if multipeerSession.connectedPeers.count > 3 {return false}else{return true}}func peerJoined(_ peer: MCPeerID) {}func peerLeft(_ peer: MCPeerID) {}}
}#Preview {CooperationSession()
}

代码清单8-6 中代码实现的功能如下:

     (1)进行平面检测,在检测到可用平面时实例化一个指示图标用于指示放置位置。

     (2)添加屏幕单击手势,在平面可用时单击屏幕会在指示图标位置放置一个 ARAnchor,注意这个ARAnchor 的名字(name 属性),稍后会详细说明。

    (3)检查所有添加到场景中的 ARAnchor,当ARAnchor 名字(name 属性)为指定值时在 ARAnchor 位置生成一个立方体。

    (4)周期性地向所有参与设备发送本设备的 AR 进程数据(Collaboration Data)。

    (5)接收来自其他设备的Collaboration Data 数据并更新到本设备的 ARSession 中。

      在第(2)项功能中,即 handleTap()方法中的代码,利用命中点的坐标生成了一个 ARAnchor,并将其添加到 ARSession 中,这里 ARAnchor 的name 属性很重要,因为我们后续需要利用该 ARAnchor 的名字来恢复虚拟元素。

     在第(3)项功能中,即 session(:didAdd:)方法中代码,遍历所有添加的 ARAnchor,这里的 ARAnchor既包括本设备的ARAnchor,也包括从其他设备同步过来的 ARAnchor,当 ARAnchor 名字(name 属性)为功能2中指定值时在 ARAnchor 位置生成一个立方体。利用该方法既会生成本设备自身的立方体,也会生成其他设备共享的立方体,即实现了操作同步。

     在第(4)项功能中,即 session(_:didOutputCollaborationData:)方法中代码,首先确保通信可用且有参与者,然后利用 let collaborationData = try? NSKeyedUnarchiver. unarchivedObject (ofClass: ARSession.CollaborationData. self, from: data) 语句获取 Collaboration Data 数据并序列化之,设置数据通信优先级后将数据发送到所有参与者。

     在第(5)项功能中,即 receivedData()方法中代码,利用 let collaborationData = try?NSKeyedUnarchiver.unarchivedObject(ofClass: ARSession. CollaborationData. self, from: data) 语何获取 Collaboration Data数据并反序列化之,然后将其更新到本设备的 ARSession中。

     与 ARWorldMap一样,Collaboration Data 数据也不包含虚拟元素本身,因此,需要人工恢复虚拟元素,因为虚拟元素总是与ARAnchor 关联,利用 ARAnchor 的名字(name)属性我们就可以恢复关联的虚拟元素,通过这种方式,可以逐一地恢复所有的虚拟元素,从而恢复整个场景,达到所有参与方看到完全相同 AR场景的效果,即实现了 AR体验的同步。

      在两台设备A 和B上同时运行本案例(确保两台设备连接到同一个 WiFi网络或者都打开蓝牙),在A设备检测到的平面上单击添加立方体,在AB连接顺畅的情况下可以看到B设备也会同步出现该立方体,并且立方体所在物理世界中的位置与A设备中的一致,反之亦然,在B设备检测到的平面上单击添加立方体,A设备也会同步出现该立方体,并且立方体所在物理世界中的位置与B设备中的一致,效果如上图 所示。

具体代码地址:GitHub - duzhaoquan/ARkitDemo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/710532.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

禅道安装与使用

文章目录 1.下载2.安装 1.下载 进入禅道官网下载 2.安装 登录后

uniapp生成app包引导用户开启通知权限和热更新

uniapp生成app包引导用户开启通知权限和热更新 引导用户开启通知权限 export function setPermissions() {// #ifdef APP-PLUS if (plus.os.name Android) {var main plus.android.runtimeMainActivity();var pkName main.getPackageName();var uid main.getApplicationI…

【免费】两阶段鲁棒优化matlab实现——CCG和benders

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序采用matlab复现经典论文《Solving two-stage robust optimization problems using a column-and-constraint generation method》算例,实现了C&CG和benders算法两部分内容,通过…

1.3 vue ui框架-element-ui框架

1 前言 ElementUI是一套基于VUE2.0的桌面端组件库,ElementUI提供了丰富的组件帮助开发人员快速构建功能强大、风格统一的页面。 ElementUI官网 https://element.eleme.io 2 安装 运行命令 cnpm i element-ui -S -S表示只在该项目下安装,不是全局安…

基于YOLOv8深度学习的复杂场景下船舶目标检测系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测

《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…

GL绘制自定义线条4_使用OpenGL ES实现钢笔效果

在以前的文章里http://t.csdnimg.cn/TgCtl,我简述了如何使用OpenGL ES实现光滑的粗线条的绘制效果,在闲暇时间我把它再进一步进化,实现了端点长度按照压感大小实现伸缩的逻辑,从而实现了如下的笔锋效果: 书写过程中的效…

包管理工具之npm也慌了?

起因 因为npm的种种问题,我很早就换成了pnpm和yarn(但是其实npm也在使用),已经很久没有关注npm的功能更新了。最近无意间进入Node18版本的安装目录,发现其除了常规的node,npm等默认安装了一个新的包corepack,这个就是今天我要分享的东西了。 注: 我因为18版本的node上…

【STM32】STM32学习笔记-FLASH闪存(48)

00. 目录 文章目录 00. 目录01. FLASH简介02. 闪存模块组织03. FLASH基本结构04. FLASH解锁05. 使用指针访问存储器06. 程序存储器编程07. 选项字节08. 选项字节编程09. 选项字节擦除10. 器件电子签名11. 附录 01. FLASH简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选…

灰度负载均衡和普通负载均衡有什么区别

灰度负载均衡(Gray Load Balancing)与普通负载均衡的主要区别在于它们服务发布和流量管理的方式。 灰度负载均衡 目的:主要用于灰度发布,即逐步向用户发布新版本的服务,以减少新版本可能带来的风险。工作方式&#x…

【软考】UML中的图之通信图

目录 1. 说明2. 图示3. 特性4. 例题4.1 例题1 1. 说明 1.通信图强调收发消息的对象的结构组织2.早期版本叫做协作图3.通信图强调参加交互的对象和组织4.首先将参加交互的对象作为图的顶点,然后把连接这些对象的链表示为图的弧,最后用对象发送和接收的消…

Google发布Genie硬杠Sora:通过大量无监督视频训练最终生成可交互虚拟世界

前言 Sora 问世才不到两个星期,谷歌的世界模型也来了,能力看似更强大(嗯,看似):它生成的虚拟世界自主可控 第一部分 首个基础世界模型Genie 1.1 Genie是什么 Genie是第一个以无监督方式从未标记的互联网视频中训练的生成式交互…

vue-electron 项目创建记录及注意事项

vue-electron 项目创建记录及注意事项 1、使用vue ui或者命令行创建vue项目 2、添加electron插件 3、安装element-plus: npm install --save element-plus 4、修改配置文件如下图: vue.config.js增加配置: pluginOptions:{ electronOutput: { contextIsolation…

【Redis】深入理解 Redis 常用数据类型源码及底层实现(6.详解Set和ZSet数据结构)

本文是深入理解 Redis 常用数据类型源码及底层实现系列的第6篇~前5篇可移步( ̄∇ ̄)/ 【Redis】深入理解 Redis 常用数据类型源码及底层实现(1.结构与源码概述)-CSDN博客 【Redis】深入理解 Redis 常用数据类型源码及底…

【Redis:事务】

1 🍑事务概念🍑 Redis 的事务和 MySQL 的事务概念上是类似的,都是把⼀系列操作绑定成⼀组,让这⼀组能够批量执⾏。 但是注意体会 Redis 的事务和 MySQL 事务的区别: 弱化的原⼦性: redis 没有 “回滚机制”. 只能做到这些操作 “…

常用网络协议的学习

TCP/IP TCP/IP的定义 TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/互联网协议)是互联网的基本协议,也是国际互联网络的基础。 TCP/IP 不是指一个协议,也不是 TCP 和 IP 这两个协议的合称…

k8s资源管理之声明式管理方式

1 声明式管理方式 1.1 声明式管理方式支持的格式 JSON 格式:主要用于 api 接口之间消息的传递 YAML 格式:用于配置和管理,YAML 是一种简洁的非标记性语言,内容格式人性化,较易读 1.2 YAML 语法格式: ●…

.net 日志

一、Log4net 1、log4net写入文本 1、nuget引入log4net、Microsoft.Extensions.Logging.Log4Net.AspNetCore这2个 2、引入配置文件,可以直接去官网(log4net官网配置文件)复制下来,放到项目目录下面,设置成始终复制,因为这个文件最终要到我们项目运行目录下面去 3、要在pr…

Vue3+springboot实现简单登录demo

Vue3从0搭建脚手架步骤【默认已安装node.js】 前置条件:默认已安装node.js、yarn 第一步:创建项目 选择任意一个空白文件夹如下: cmd进入该文件夹下的命令窗口模式,然后输入指令创建vue项目:vue create my-project …

智能指针(C++)

目录 一、智能指针是什么 二、为什么需要智能指针 三、智能指针的使用和原理 3.1、RALL 3.2 智能指针的原理 3.3、智能指针的分类 3.3.1、auto_ptr 3.3.2、unique_ptr 3.3.3、shared_ptr 3.2.4、weak_ptr 一、智能指针是什么 在c中,动态内存的管理式通过一…

PYCHARM PYSIDE6 QT 打包异常处理 no qt platform plugin could be initialized

安装有PYSIDE6的电脑 异常错误 … no qt platform plugin could be initialized … 变量名:QT_QPA_PLATFORM_PLUGIN_PATH (一个字都不能改!!) 自己环境变量值:D:\Users\topma\anaconda3\Lib\site-package…