【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.hqwc.cn/news/499696.html

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

相关文章

DataGrip2023配置连接Mssqlserver、Mysql、Oracle若干问题解决方案

1、Mssqlserver连接 本人连的是Sql2008,默认添加时,地址、端口、实例、账号、密码后,测试连接出现错误。 Use SSL:不要勾选 VM option:填写,"-Djdk.tls.disabledAlgorithmsSSLv3, RC4, DES, MD5withR…

人工智能如何改变我们与世界的沟通的方式?

腾讯研究院 作为一名设计师、艺术家、科技研究人员,人工智能如何改变我们与世界的沟通的方式? 1.《音外话》 它是一款语音至视频生成人工智能的系统,它把我们的话语转化为充满情感的视觉影像。这个系统不仅可以捕捉语言的字面意义,还能理解…

小红书关键词爬虫

标题 1 统计要收集的关键词,制作一个文件夹2 爬取每一页的内容3 爬取标题和内容4 如果内容可以被查看,爬取评论内容5 将结果进行汇总,并且每个帖子保存为一个json文件,具体内容6 总结 1 统计要收集的关键词,制作一个文…

ts的重载

官网示例 TypeScript: Documentation - Template Literal Types 这里大概理解是 T 继承了Number|sting 加上&#xff1f;条件判断就是 T继承Number|sting 部分为true 没有继承部分为false&#xff0c; 就是输入string, 为true, 输入 null 则为false, type Exclude<T, U&…

八股文打卡day24——数据库(1)

面试题&#xff1a;左连接和右连接的区别&#xff1f; 我的回答&#xff1a; 左连接的SQL语句是&#xff1a;左表 left join 右表 on 连接条件&#xff0c;表示以左表为基础&#xff0c;将左表的的所有记录与右表进行连接。即使右表中没有与左表匹配的记录&#xff0c;左连接…

类和对象(2)——距离C++又近了一步

目录 一、构造函数 1.1声明和定义构造函数 1.2成员名和参数名 1.3构造函数的使用 1.4初始化列表 二、析构函数 2.1析构函数的概念 2.2析构函数的性质 三、拷贝构造函数 四、赋值运算符重载 4.1运算符重载 4.2赋值运算符重载 一、构造函数 我们知道&#xff0c;C中…

网络编程作业day2

1.将TPC和UDP通信模型各敲两遍 &#xff08;1&#xff09;TPC通信模型&#xff1a; 服务器代码&#xff1a; #include <myhead.h> #define SERVER_IP "192.168.125.136" #define SERVER_PORT 1314 int main(int argc, const char *argv[]) {//1、创建用于监…

Rocky Linux 运维工具 ls

一、ls 的简介 ​​ls​ 用于列出当前目录下的文件和目录&#xff0c;以及它们的属性信息。通过 ​ls​命令可以查看文件名、文件大小、创建时间等信息&#xff0c;并方便用户浏览和管理文件。 二、ls 的参数说明 序号参数描述1-a显示所有文件&#xff0c;包括以 ​.​开头的…

neo4j创建新数据库

根据网上提供的教程&#xff0c;neo4j并没有提供创建数据库的命令&#xff0c;其只有一个默认数据库graph.db&#xff0c;该数据库中的所有数据将存储在neo4j安装路径下的data/databases/graph.db目录中。 因此&#xff0c;我们猜想&#xff0c;如果我们将默认数据库的名字修改…

YOLOv8改进 | 独家创新篇 | 结合SOTA思想利用双主干网络改进YOLOv8(全网独家创新,最重磅的更新)

一、本文介绍 本文给大家带来的改进机制是结合目前SOTAYOLOv9的思想利用双主干网络来改进YOLOv8(本专栏目前发布以来改进最大的内容,同时本文内容为我个人一手整理全网独家首发 | 就连V9官方不支持的模型宽度和深度修改我都均已提供,本文内容支持YOLOv8全系列模型从n到x均可…

ardupilot 及PX4姿态误差计算算法对比分析

目录 文章目录 目录摘要1.APM姿态误差计算算法2.PX4姿态误差计算算法3.结论摘要 本节主要记录ardupilot 及PX4姿态误差计算算法差异对比过程,欢迎批评指正。 备注: 1.创作不易,有问题急时反馈 2.需要理解四元物理含义、叉乘及点乘含义、方向余弦矩阵含义、四元数乘法物理含…

C++ 网络编程学习三

C 网络编程学习三 用智能指针延长session的生命周期处理粘包问题 用智能指针延长session的生命周期 问题&#xff1a; 客户端断开后&#xff1a;会触发服务器对应session的写或读事件&#xff0c;由于是异步编程&#xff0c;需要在回调中对读写事件进行处理。客户端断开&#…

(libusb) usb口自动刷新

文章目录 libusb自动刷新程序Code目录结构Code项目文件usb包code包 效果描述重置reset热拔插使用 END libusb 在操作USB相关内容时&#xff0c;有一个比较著名的库就是libusb。 官方网址&#xff1a;libusb 下载&#xff1a; 下载源码官方编好的库github&#xff1a;Release…

@Slf4j 变量log找不到符号,可能是 Gradle 配置文件写得有问题

Slf4j 变量log找不到符号 鄙人在学习 Java 的 spring boot 项目时, 常常因为 maven 配置文件使用 xml 格式过于复杂, 所以更倾向于使用 gradle 作为构建工具. 然而, 在使用 gradle 作为构建工具时, 又需要引用 Lombok 依赖. 有时忘记在初始化项目时添加上 Lombok 依赖, 所以经…

本届挑战赛亚军方案:基于大模型和多AGENT协同的运维

“轻舟已过万重山团队”荣获本届挑战赛亚军&#xff0c;该团队来自华为集团IT-UniAI 产品和openEuler系统智能团队。 方案介绍 自ChatGPT问世以来&#xff0c;AI迎来了奇点iPhone时刻&#xff0c;这一年来大模型深入影响企业办公&#xff0c;金融&#xff0c;广告&#xff0c;…

SDWAN异地组网难在哪?怎么解决?

SD-WAN作为一种先进的网络技术&#xff0c;为企业提供了更加灵活和高效的网络连接方案。然而&#xff0c;在异地组网的过程中&#xff0c;SD-WAN也面临一些挑战。本文将探讨SD-WAN异地组网所面临的难题&#xff0c;并提供相应的解决方案。 挑战一&#xff1a;网络延迟和不稳定性…

ptython迭代器与生成器

迭代器 Python中的迭代器&#xff08;Iterator&#xff09;是一种强大的工具&#xff0c;用于访问集合元素。它是一种可以记住遍历位置的对象&#xff0c;这意味着迭代器不会一次性生成所有的元素&#xff0c;而是可以等到需要的时候才生成&#xff0c;从而节省了大量的内存资…

智慧城市:打造宜居环境,引领未来可持续发展

随着科技的不断进步与创新&#xff0c;我们的城市正步入一个崭新的时代——智慧城市。智慧城市是指运用信息技术和大数据等现代科技手段&#xff0c;对城市基础设施、公共服务和社会管理进行智能化改造&#xff0c;实现城市各领域的智能化、信息化和高效化。今天&#xff0c;就…

SQL函数学习记录

聚合函数 函数是编程语言的基础之一&#xff0c;在对数字的运算中&#xff0c;我们用的最多的就是聚合函数&#xff0c;本篇接下来就详细阐述下SQL中聚合函数的运用。 什么是聚合函数&#xff08;aggregate function&#xff09;&#xff1f; 聚合函数指的是对一组值执行计算…

Pytorch模型训练后静态量化并加载int8量化模型推理

目录 一、源码包准备1.1 源码包获取1.2 代表性验证集1.3 Pytorch模型1.4 推理测试图片 二、环境准备三、模型转换3.1 参数修改3.2 代码3.3 量化转换结果3.4 量化前后模型大小对比 四、量化模型推理4.1 参数修改4.2 代码4.3 推理结果4.4推理时间 五、总结 一、源码包准备 1.1 源…