【iOS ARKit】手动配置环境探头

news2024/11/16 15:56:09

        在上节中我们已经了解了环境探头以及如何使用自动环境探头,这节一起了解如何使用手动配置环境探头。

     在使用自动环境反射时,开发人员无须进行有关环境反射的任何操作,只需要设置自动环境反射即可,其余工作完全由 RealityKit 自动完成,这适用于基本的常见环境反射。但这种环境反射方案是一种普适性的反射,并没有专门针对某特定虚拟元素进行优化,在某些情况下效果并不精细,并且我们也无法进行干预调优,如一辆行驶的赛车对环境的反射就需要更精细的控制,这时就需要手动控制环境探头的生成及更新。

     使用手动控制环境探头时,我们需要将配置中 environmentTexturing 属性设置为 manual,并决定在什么地方、什么时候设置与更新环境探头。通常而言,可以遵循以下流程:

(1) 在场景中某个特定位置创建 AREnvironmentProbeAnchor 锚点。

(2)将创建的 AREnvironmentProbeAnchor 锚点添加到 ARSession 中。

(3)使用 session(_:didUpdate:)代理方法根据需要更新环境探头。

使用手动方式控制环境探头的示例代码如下所示。

//
//  ManualEnvirmentTexturing.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/1/30.
//

import SwiftUI
import ARKit
import RealityKit

struct ManualEnvirmentTexturing: View {
    @State var automatic: Bool = true
    var body: some View {
        ManualEnvirmentTexturingContainer(automatic: $automatic)
            .overlay(content: {
               
                VStack {
                    Spacer()
                    HStack{
                        Text(automatic ? "HDR" : "HDR Off")
                            .background(GeometryReader{ _ in
                                Color.white
                            })
                            .padding(10)
                            .offset(x: 0)
                        Toggle(isOn: $automatic) {}
                            .frame(width: 50)
                            .offset(x: 0)
                    }
                    
                    Spacer().frame(height: 40)
                }
                

            })
            .edgesIgnoringSafeArea(.all)
            .navigationTitle("环境探头")
        
    }
       
}

struct ManualEnvirmentTexturingContainer:UIViewRepresentable {
    @Binding var automatic: Bool
    func makeUIView(context: Context) -> ARView
    {
        let arView = ARView(frame: .zero)
        
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = .horizontal
        
        config.environmentTexturing = .manual
        
        context.coordinator.arView = uiView
        uiView.session.delegate = context.coordinator
        
        if automatic {
           
            config.wantsHDREnvironmentTextures = true
            
        }else{
            config.wantsHDREnvironmentTextures = false
        }
        uiView.session.run(config, options: [])
       
        
        
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject,ARSessionDelegate {
        var manualProbe: ManualProbe?
        var arView: ARView? = nil
        var parent: ManualEnvirmentTexturingContainer
        init(parent: ManualEnvirmentTexturingContainer) {
            self.parent = parent
        }
        func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
            guard let anchor = anchors.first as? ARPlaneAnchor else {
                return
            }
            //球体
            let mesh = MeshResource.generateSphere(radius: 0.05)
            let meterial = SimpleMaterial(color: .blue, isMetallic: true)
            let modelEntity = ModelEntity(mesh: mesh, materials: [meterial])
            
           
            
            let planAnchor = AnchorEntity(anchor: anchor)
            //放在正上方5cm处
            modelEntity.transform.translation = [0, planAnchor.transform.translation.y + 0.05,0]
            manualProbe = ManualProbe(shpereEntity: modelEntity)
            
            updateProbe()
            planAnchor.addChild(manualProbe!.shpereEntity)
            arView?.scene.addAnchor(planAnchor)
            
            //只添加一次
            session.delegate = nil
            session.run(ARWorldTrackingConfiguration())
            
            manualProbe?.isPlaced = true
        }
        func session(_ session: ARSession, didUpdate frame: ARFrame) {
            if let manualProbe = manualProbe,
                manualProbe.requireRefresh,
                Date().timeIntervalSince1970 - manualProbe.lastUpadateTime > 1{
                
                self.manualProbe?.lastUpadateTime = Date().timeIntervalSince1970
                updateProbe()
                
            }
        }
        
        
        func  updateProbe() {
            guard let manualProbe = manualProbe else {
                return
            }
            //移除旧的
            if let probAnchor = manualProbe.objectProbeAnchor {
                self.arView?.session.remove(anchor: probAnchor)
                self.manualProbe?.objectProbeAnchor = nil
            }
            
            var extent = (manualProbe.shpereEntity.model?.mesh.bounds.extents)! * manualProbe.shpereEntity.transform.scale
            extent.x *= 3
            extent.y *= 3
            extent.z *= 2
            
//            let verticalOffset = SIMD3(0, extent.y, 0)
//            var probeTransform = manualProbe.shpereEntity.transform
//            probeTransform.translation += verticalOffset
            
            let  position = simd_float4x4(
               
                SIMD4(1,0,0,0),
                SIMD4(0,1,0,0),
                SIMD4(0,0,1,0),
                SIMD4(manualProbe.shpereEntity.transform.translation,1)
                        
            )
            
            self.manualProbe?.objectProbeAnchor = AREnvironmentProbeAnchor(name: "objectProbe",transform: position, extent:extent)
            self.arView?.session.add(anchor: (self.manualProbe?.objectProbeAnchor)!)
        }
    }
    
    
    
}


struct ManualProbe {
    var objectProbeAnchor: AREnvironmentProbeAnchor?
    var requireRefresh = true
    var lastUpadateTime = Date().timeIntervalSince1970
    var dateTime = Date()
    var shpereEntity: ModelEntity
    var isPlaced = false
}

       在代码中,为更好地组织代码,我们自定义了 ManualProbe 类管理环境探头。在 ARKit 检测到水平平面并放置圆球后,将生成的环境探头放置在園球上方5cm 的地方以更精准地反射圆球的周边环境。在 session(_:didUpdate:)代理方法中我们对环境探头更新率进行了控制,设置为每秒更新一次,与所有的 ARAnchor一样,AREnvironmentProbeAnchor 锚点无法修改,更新时只能移除原锚点信息,创建新的锚点使用。updateProbe()方法负责所有的环境探头更新操作(具体细节稍后详述)。通过及时地更新就能反射用户环境中动态的变化,效果如图所示。

       手动放置环境探头主要是为了获得对特定虚拟对象的最精确环境反射信息,绑定环境探头与虚拟对象位置可以提高反射渲染的质量,因此,手动将反射探头放置在重要的虚拟对象中或其附近会产生为该对象生成最准确的环境反射信息。通常而言,自动放置可以提供对真实环境比较好的宏观环境信息,而手动放置能提供在某个特定点上对周围环境更准确的环境映射从而提升反射的质量。

      反射探头负责捕获环境纹理信息,每个反射探头都有一个比例(Scale)、方向(Orientation)、位置(Position) 和大小(Size)。比例、方向和位置属性定义了反射探头相对于 ARSession 空间的空间信息,大小则定义了反射探头反射的范围,无限大小表示环境纹理可用于全局,而有限大小表示反射探头只能捕获其周围特定区域的环境信息。

在手动放置时,为了使放置的反射探头能更好地发挥作用,通常反射探头的放置位置与大小设置应遵循以下原则:

(1)反射探头的位置应当放在需要反射的虚拟物体顶部中央,高度应该为虚拟物体高的两倍,如下图所示。这可以确保反射探头下部与虚拟物体下部对齐,并捕获到虚拟物体放置平面的环境信息。

(2)反射探头的长与宽应该为虚拟物体长与宽的3倍,确保反射探头能捕获到虚拟物体周边的环境信息。

  • 提示:使用手动处理环境反射时,我们也可以使用 wantsHDREnvironmentTextures 提高反射质量。

性能优化

       在AR 中使用反射探头反射环境可以大大增强虚拟物体的可信度,但由于AR摄像头获取的环境信息不充分,ARKit 只能得到摄像头拍摄的真实环境部分数据,而无法获取摄像头未拍摄部分的环境数据,需要利用人工智能的方式对不足信息进行补充,需要补充的信息计算量大,对资源要求高,这对移动平台的性能与电池续航提出了非常高的要求,因此为更好的扬长避短,在AR 中使用反射探头反射真实环境需要注意以下几点。

1. 避免精确反射

     如上所述,AR中从摄像头获取的信息不足以对周围环境进行精准再现,因此反射体对环境的反射也不能做到非常精准,希望利用反射探头实现对真实环境的镜面反射是不现实的。因为在 AR 中不能获取完全的立方体贴图并且立方体贴图更新也不实时(为降低硬件消耗),通常在小面积上可以使用高反射率而在大面积上使用低反射率,达到既营造反射效果又避免反射不准确而带来的负面作用。

2. 对移动对象的处理

     烘焙的环境贴图不能反映环境的变化,实时的反射探头又会带来过大的性能消耗,对移动对象的反射处理需要特别进行优化。在设置反射探头时可以考虑以下方法:

(1)如果移动物体移动路径可知或可以预测,可以提前在其经过的路径上放置多个反射探头并进行烘焙,这样移动物体可以根据距离的远近对不同的反射探头生成的立方体贴图采样。

(2)创建一个全局的环境反射,如Skybox,这样当移动物体移动出某个反射探头的范围时仍然可以反射而不是突然出现反射中断。

(3)当移动物体移动到一个新的位置后重新创建一个反射探头并销毁原来位置的反射探头。

3. 防止滥用

     在AR中,当用户移动位置或者调整虚拟对象大小时,应用程序都会重新创建反射探头,因此我们需要限制此类更新,如更新频率不应大于1次每秒。

4.避免突然切换

      突然地移除反射探头或者添加新的反射探头会让用户感到不适。在 ARKit 中使用自动放置反射探头的模式下,只要 ARSession启动,就会创建一个全局的类似 Skybox 的大背景以防止反射突然切换。在手动放置时,开发者应该确保反射的自然过渡,确保虚拟物体始终能反射合适的环境,或者使用一个全种环境下都能适应的静态立方体贴图作为过渡手段。

具体代码地址:https://github.com/duzhaoquan/ARkitDemo.git

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

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

相关文章

TRIZ经典矛盾矩阵.exe

TRIZ经典矛盾矩阵.exe 一、概要二、技术细节I.函数open_dialog()和open_version_dialog()II.函数resolvent()III.函数Invention_Principle_Content(&#xff…

Log4j2-24-log4j2 相同的日志打印 2 次

现象 相同的日志打印了两次,且因为日志的配置不同,导致脱敏的情况不一致。 代码与配置 代码 package com.ryo.log4j2.cfg.additivity;import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;public class SimpleDemo…

蓝桥杯省赛无忧 编程14 肖恩的投球游戏加强版

#include <stdio.h> #define MAX_N 1003 int a[MAX_N][MAX_N], d[MAX_N][MAX_N]; // 差分数组的初始化 void init_diff(int n, int m) {for (int i 1; i < n; i) {for (int j 1; j < m; j) {d[i][j] a[i][j] - a[i-1][j] - a[i][j-1] a[i-1][j-1];}} } // 对差…

oracle数仓rac两个节点查询耗时不一致问题处理

问题描述 数据库节点1查询比节点2查询慢。现场操作应用发现发现同一sql语句在节点2上只要2分钟左右&#xff0c;在节点1&#xff0c;该条sql执行要超过30分钟。 处理过程 根据问题&#xff0c;初步判断是由于错误的执行计划&#xff0c;导致性能问题&#xff0c;但实际上对两…

【云原生】docker容器实现https访问

目录 步骤一&#xff1a;在宿主机生成服务端的公钥证书和私钥文件 步骤二&#xff1a;准备Dockerfile文件构建镜像 步骤三&#xff1a;docker run启动容器并将其映射到443端口 步骤一&#xff1a;在宿主机生成服务端的公钥证书和私钥文件 //生成ca证书 &#xff08;1&#x…

0131-2-关于事件捕获和冒泡

关于事件捕获和冒泡 DOM事件流分为三个阶段&#xff1a;捕获阶段、目标阶段、冒泡阶段 点击目标元素后&#xff0c;不会马上触发目标元素&#xff0c;而是先执行事件捕获&#xff0c;从顶部逐步到目标元素&#xff1b;处于目标阶段的时候触发目标元素&#xff1b;最后冒泡阶段…

随机 Transformer

在这篇博客中&#xff0c;我们将通过一个端到端的示例来讲解 Transformer 模型中的数学原理。我们的目标是对模型的工作原理有一个良好的理解。为了使内容易于理解&#xff0c;我们会进行大量简化。我们将减少模型的维度&#xff0c;以便我们可以手动推理模型的计算过程。例如&…

CSS 3D三角彩色锥形旋转动画效果

<template><view class="pyramid-loader"><view class="wrapper"><span class="side side1"></span> <!-- 金字塔的一个面 --><span class="side side2"></span> <!-- 金字塔的…

基于stm32F4卷积神经网络手写数字识别项目

加我微信hezkz17 可以申请加入嵌入式人工智能技术研究开发交流答疑群&#xff0c;赠送企业嵌入式AI 图像理解/音/视频项目核心开发资料 1 采用CNN BP反向传播算法更新权重系数 2 原理解析 3 实现策略 训练与识别分离&#xff0c;先在电脑上训练好CNN BP神经网络的模型&#…

[Python] 什么是PCA降维技术以及scikit-learn中PCA类使用案例(图文教程,含详细代码)

什么是维度&#xff1f; 对于Numpy中数组来说&#xff0c;维度就是功能shape返回的结果&#xff0c;shape中返回了几个数字&#xff0c;就是几维。索引以外的数据&#xff0c;不分行列的叫一维&#xff08;此时shape返回唯一的维度上的数据个数&#xff09;&#xff0c;有行列…

Java数组-初识数组

签名&#xff1a;但行好事&#xff0c;莫问前程。 文章目录 前言一、数组的概述1、数组中的概念2、数组的特点 二、数组的声明与初始化1、先声明后初始化2、声明的同时直接初始化3、静态初始化数组4、动态初始化数组 三、数组的赋值与取值1、给数组的元素赋值2、遍历数组的元素…

【Midjourney】新手指南:命令

1./ask 向Midjourney提问&#xff0c;不过问题和回答都是英文的&#xff0c;例如&#xff1a; 2./blend 将两张图片合并为一张 ​ 3./describe 上传一张图片&#xff0c;Midjourney会生成四组该图片相关的关键词&#xff0c;可以使用这些关键词再生成图片。 ​ 4./turbo …

缓存之SpringCache整合redis(五)

SpringCache整合redis 一、引入pom二、配置1.springCache自动配置了redis2.配置yml3.开启缓存功能4.测试使用缓存5.缓存保存JSON格式 三、springCache的读写模式1.读模式2.写模式&#xff08;缓存和数据库一致性&#xff09; 一、引入pom <!--springCache依赖 --> <d…

在Meteor Lake上测试基于Stable Diffusion的AI应用

上个月刚刚推出的英特尔新一代Meteor Lake CPU&#xff0c;预示着AI PC的新时代到来。AI PC可以不依赖服务器直接在PC端处理AI推理工作负载&#xff0c;例如生成图像或转录音频。这些芯片的正式名称为Intel Core Ultra处理器&#xff0c;是首款配备专门用于处理人工智能任务的 …

Java EE 5 SDK架构

Java EE 5 SDK架构 大型组织每天都要处理大量数据和多用户的相关事务。为管理该组织如此大型而又复杂的系统,开发了企业应用程序。企业应用程序是在服务器上托管的应用程序,通过计算机网络同时向大量用户提供服务。这种应用程序可采用各种技术开发,如Java EE 5。Java EE 5平…

Git--07--GitExtension

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、GitExtension下载GitExtension官网下载教程 二、GitExtension安装三、GitExtension配置四、GitExtension使用 一、GitExtension下载 官网下载&#xff1a; http…

day38 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯

动态规划是前一个状态推导过来的&#xff0c;贪心是局部最优解。 class Solution { public:int fib(int n) {int a0;int b1;int res0;if(n1) return 1;for(int i2;i<n;i){resab;ab;bres;}return res;} }; 可以由前面状态推出后面状态&#xff0c;是动态规划。由于始终只要后…

【正点原子STM32】IWDG 独立看门狗(简介、工作原理、IWDG寄存器配置操作步骤、IWDG溢出时间计算、IWDG配置步骤、独立看门狗流程)

一、IWDG简介 IWDG有什么作用&#xff1f; 二、IWDG工作原理 三、IWDG框图 四、IWDG寄存器 键寄存器&#xff08;IWDG_KR&#xff09;预分频器寄存器 (IWDG_PR)重装载寄存器(IWDG_RLR) 状态寄存器(IWDG_SR) 寄存器配置操作步骤 五、IWDG溢出时间计算 IWDG溢出时间计算公式…

delphi fmxui 做的一些跨平台app

pascal语音显然已经没落&#xff0c;但delphi还在坚挺着&#xff0c;每年都会发布新版本&#xff0c; 主要是做跨平台应用。 如果你觉得qt qml 写android app 比较麻烦&#xff0c;那可以尝试delphi 12&#xff0c;可以用c builder 尝试 android&#xff0c;ios 开发 下面的…

burp靶场--xss下篇【16-30】

burp靶场–xss下篇【16-30】 https://portswigger.net/web-security/all-labs#cross-site-scripting 实验16&#xff1a;允许使用一些 SVG 标记的反射型 XSS ### 实验要求&#xff1a; 该实验室有一个简单的反射型 XSS漏洞。该网站阻止了常见标签&#xff0c;但错过了一些 S…