首页 > 其他分享 >仅需6步,实现虚拟物体在现实世界的精准放置

仅需6步,实现虚拟物体在现实世界的精准放置

时间:2024-09-29 10:48:02浏览次数:1  
标签:AR 放置 AREngine nullptr HMS 虚拟 arSession 平面 精准

仅需6步,实现虚拟物体在现实世界的精准放置

 

增强现实(AR)技术作为一种将数字信息和现实场景融合的创新技术,近年来得到了快速发展,并在多个应用领域展现出其独特的魅力。比如在教育行业,老师可以通过虚拟现实场景生动直观地帮助学生理解抽象概念;在旅游行业,AR技术还能虚拟历史文化场景、虚拟导航等,为游客提供更加沉浸的互动体验。

然而,对于应用来说,AR技术的开发使用绝非易事,这需要高昂的开发成本和专业的技术人才。基于此,HarmonyOS SDKAR引擎服务(AR Engine)为广大应用开发者提供了先进的AR技术,解决了开发成本和技术门槛的难题。

image

在集成AR Engine能力后,开发者只需6个开发步骤,就可以实现将虚拟物体摆放于现实世界的平面上,实现虚拟和现实的融合,该功能可应用于虚拟家具放置、数字展厅布展等场景,为用户提供虚实结合的新体验。

业务流程

image

AR摆放实现的业务流程主要分为打开应用、识别平面并展示和放置虚拟物体三个部分。

第一部分是用户打开应用,应用需要向用户申请相机权限。如果用户未同意授权,则无法使用该功能。

第二部分中,AR Engine识别平面并展示。包括完成AR Engine初始化,更新ARFrame对象、获取平面、绘制平面并显示预览画面等步骤。

第三部分为放置虚拟物体。即用户点击屏幕,通过碰撞检测获取现实环境中的兴趣点,并在兴趣点上创建锚点,最终实现在锚点位置绘制虚拟物体,并将虚拟物体显示在预览画面上。

开发步骤

在实现AR物体摆放的具体开发步骤之前,开发者需要先创建Native C++工程,声明ArkTs接口,并申请以下权限授权。

image

1.创建UI界面

在做好准备工作后,需要创建一个UI界面,用于显示相机预览画面,并定时触发每一帧绘制。

import { Logger } from '../utils/Logger';
import arEngineDemo from 'libentry.so';
import { resourceManager } from '@kit.LocalizationKit';
import { display } from '@kit.ArkUI';

[@Entry](https://my.oschina.net/u/4127701)
[@Component](https://my.oschina.net/u/3907912)
struct ArWorld {
  private xcomponentId = 'ArWorld';
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
  private resMgr: resourceManager.ResourceManager = getContext(this).resourceManager;
  private interval: number = -1;
  private isUpdate: boolean = true;

  aboutToAppear() {
    Logger.debug('aboutToAppear ' + this.xcomponentId);
    arEngineDemo.init(this.resMgr);
    arEngineDemo.start(this.xcomponentId);
    display.on("foldStatusChange", (foldStatus: display.FoldStatus) => {
      Logger.info('foldStatusChange display on ' + foldStatus);
      if (foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED
        || foldStatus === display.FoldStatus.FOLD_STATUS_FOLDED) {
        arEngineDemo.stop(this.xcomponentId);
        arEngineDemo.init(this.resMgr);
        // 调用Native的start接口,创建ARSession。
        arEngineDemo.start(this.xcomponentId);
        arEngineDemo.show(this.xcomponentId);
      }
    })
  }

  aboutToDisappear() {
    Logger.debug('aboutToDisappear ' + this.xcomponentId);
    arEngineDemo.stop(this.xcomponentId);
  }

  onPageShow() {
   this.isUpdate = true;
    Logger.debug('onPageShow ' + this.xcomponentId);
    arEngineDemo.show(this.xcomponentId);
  }

  onPageHide() {
    Logger.debug('onPageHide ' + this.xcomponentId);
    this.isUpdate = false;
    arEngineDemo.hide(this.xcomponentId);
  }

  build() {
    Column() {
      XComponent({ id: this.xcomponentId, type: 'surface', libraryname: 'entry' })
        .onLoad(() => {
          Logger.debug('XComponent onl oad ' + this.xcomponentId);
          this.interval = setInterval(() => {
            if (this.isUpdate) {
              // 调用Native的update,更新AR Engine每一帧的计算结果
              arEngineDemo.update(this.xcomponentId);
            }
          }, 33); // 控制帧率为30fps(每33毫秒刷新一帧)。
        })
        .width('100%');
        .height('100%');
        .onDestroy(() => {
          Logger.debug('XComponent onDestroy ' + this.xcomponentId);
          clearInterval(this.interval);
        })
        .backgroundColor(Color.White);
    }
    .justifyContent(FlexAlign.SpaceAround);
    .alignItems(HorizontalAlign.Center);
    .backgroundColor(Color.White);
    .borderRadius(24);
    .width('100%');
    .height('100%');
  }
}

2.引入AR Engine

创建完UI界面后,引入AR Engine头文件,并编写CMakeLists.txt。

#include "ar/ar_engine_core.h" 

find_library(
    # Sets the name of the path variable.
    arengine-lib
    # Specifies the name of the NDK library that
    # you want CMake to locate.
    libarengine_ndk.z.so
)

target_link_libraries(entry PUBLIC
    ${arengine-lib}
)

3.创建AR场景

首先,配置AR会话及预览尺寸。

// 【可选】创建一个拥有合理默认配置的配置对象。
AREngine_ARConfig *arConfig = nullptr;
HMS_AREngine_ARConfig_Create(arSession, &arConfig);
// 【可选】配置AREngine_ARSession会话。
HMS_AREngine_ARSession_Configure(arSession, arConfig);
// 【可选】释放指定的配置对象的内存空间。
HMS_AREngine_ARConfig_Destroy(arConfig);

// 创建一个新的AREngine_ARFrame对象。
HMS_AREngine_ARFrame_Create(arSession, &arFrame);
// 预览区域的实际宽高,如使用xcomponent组件显示,则该宽和高是xcomponent的宽和高,如果不一致,会导致显示相机预览出错。
int32_t width = 1440;
int32_t height = 1080;
// 设置显示的宽和高(以像素为单位)。
HMS_AREngine_ARSession_SetDisplayGeometry(arSession, displayRotation, width, height);

通过openGL接口获取纹理ID。

//通过openGL接口获取纹理ID.
GLuint textureId = 0;
glGenTextures(1, &textureId);

设置openGL纹理,存储相机预览流数据。

// 设置可用于存储相机预览流数据的openGL纹理。
HMS_AREngine_ARSession_SetCameraGLTexture(arSession, textureId );

4.获取平面

调用HMS_AREngine_ARSession_Update函数更新当前AREngine_ARFrame对象。

// 获取帧数据AREngine_ARFrame。
HMS_AREngine_ARSession_Update(arSession, arFrame);

获取相机的视图矩阵和相机的投影矩阵,用于后续绘制。

// 根据AREngine_ARFrame对象可以获取相机对象AREngine_ARCamera。
AREngine_ARCamera *arCamera = nullptr;
HMS_AREngine_ARFrame_AcquireCamera(arSession, arFrame, &arCamera);
// 获取最新帧中相机的视图矩阵。
HMS_AREngine_ARCamera_GetViewMatrix(arSession, arCamera, glm::value_ptr(*viewMat), 16);
// 获取用于在相机图像上层渲染虚拟内容的投影矩阵,可用于相机坐标系到裁剪坐标系转换。Near (0.1) Far (100)。
HMS_AREngine_ARCamera_GetProjectionMatrix(arSession, arCamera, {0.1f, 100.f}, glm::value_ptr(*projectionMat), 16);

调用HMS_AREngine_ARSession_GetAllTrackables函数获取平面列表。

// 获取当前检测到的平面列表。
AREngine_ARTrackableList *planeList = nullptr;
// 创建一个可跟踪对象列表。
HMS_AREngine_ARTrackableList_Create(arSession, &planeList);
// 获取所有指定类型为ARENGINE_TRACKABLE_PLANE的可跟踪对像集合。
AREngine_ARTrackableType planeTrackedType = ARENGINE_TRACKABLE_PLANE;
HMS_AREngine_ARSession_GetAllTrackables(arSession, planeTrackedType, planeList);
int32_t planeListSize = 0;
// 获取此列表中的可跟踪对象的数量。
HMS_AREngine_ARTrackableList_GetSize(arSession, planeList, &planeListSize);
mPlaneCount = planeListSize;
for (int i = 0; i < planeListSize; ++i) {
    AREngine_ARTrackable *arTrackable = nullptr;
    // 从可跟踪列表中获取指定index的对象。
    HMS_AREngine_ARTrackableList_AcquireItem(arSession, planeList, i, &arTrackable);
    AREngine_ARPlane *arPlane = reinterpret_cast<AREngine_ARPlane*>(arTrackable);
    // 获取当前可跟踪对象的跟踪状态。如果状态为:ARENGINE_TRACKING_STATE_TRACKING(可跟踪状态)才进行绘制。
    AREngine_ARTrackingState outTrackingState;
    HMS_AREngine_ARTrackable_GetTrackingState(arSession, arTrackable, &outTrackingState);
    AREngine_ARPlane *subsumePlane = nullptr;
    // 获取平面的父平面(一个平面被另一个平面合并时,会产生父平面),如果无父平面返回为NULL。
     HMS_AREngine_ARPlane_AcquireSubsumedBy(arSession, arPlane, &subsumePlane);
if (subsumePlane != nullptr) {
HMS_AREngine_ARTrackable_Release(reinterpret_cast<AREngine_ARTrackable*>(subsumePlane));
        // 如果当前平面有父平面,则当前平面不进行展示。否则会出现双平面。
        continue;
    }
    // 跟踪状态为:ARENGINE_TRACKING_STATE_TRACKING时才进行绘制。
    if (AREngine_ARTrackingState::ARENGINE_TRACKING_STATE_TRACKING != outTrackingState) {
        continue;
    }
    // 进行平面绘制。
}
HMS_AREngine_ARTrackableList_Destroy(planeList);
planeList = nullptr;

调用HMS_AREngine_ARPlane_GetPolygon函数获取平面的二维顶点坐标数组,用于绘制平面边界。

// 获取检测到平面的二维顶点数组大小。
int32_t polygonLength = 0;
HMS_AREngine_ARPlane_GetPolygonSize(session, plane, &polygonLength);

// 获取检测到平面的二维顶点数组,格式为[x1,z1,x2,z2,...]。
const int32_t verticesSize = polygonLength / 2;
std::vector<glm::vec2> raw_vertices(verticesSize);
HMS_AREngine_ARPlane_GetPolygon(session, plane, glm::value_ptr(raw_vertices.front()), polygonLength);

// 局部坐标系顶点坐标。
for (int32_t i = 0; i < verticesSize; ++i) {
    vertices.emplace_back(raw_vertices[i].x, raw_vertices[i].y, 0.75f);
}

将平面的二维顶点坐标转换到世界坐标系,并绘制平面。

// 获取从平面的局部坐标系到世界坐标系转换的位姿信息。
AREngine_ARPose *scopedArPose = nullptr;
HMS_AREngine_ARPose_Create(session, nullptr, 0, &scopedArPose);
HMS_AREngine_ARPlane_GetCenterPose(session, plane, scopedArPose);

// 将位姿数据转换成4X4的矩阵,outMatrixColMajor4x4为存放数组,其中的数据按照列优先存储.
// 该矩阵与局部坐标系的坐标点做乘法,可以得到局部坐标系到世界坐标系的转换。
HMS_AREngine_ARPose_GetMatrix(session, scopedArPose, glm::value_ptr(modelMat), 16);
HMS_AREngine_ARPose_Destroy(scopedArPose);

// 构筑绘制渲染平面所需的数据。
// 生成三角形。
for (int i = 1; i < verticesSize - 1; ++i) {
    triangles.push_back(0);
    triangles.push_back(i);
    triangles.push_back(i + 1);
}
// 生成平面包围线。
for (int i = 0; i < verticesSize; ++i) {
    lines.push_back(i);
}

5.点击屏幕

用户点击屏幕后,基于点击事件获取屏幕坐标。

// 添加头文件:native_interface_xcomponent.h
#include <ace/xcomponent/native_interface_xcomponent.h>

float pixeLX= 0.0f;
float pixeLY= 0.0f;
int32_t ret = OH_NativeXComponent_GetTouchEvent(component, window, &mTouchEvent);

if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
    if (mTouchEvent.type == OH_NATIVEXCOMPONENT_DOWN) {
        pixeLX= mTouchEvent.touchPoints[0].x;
    pixeLY= mTouchEvent.touchPoints[0].y;
    } else {
    return;
    }
}

调用HMS_AREngine_ARFrame_HitTest函数进行碰撞检测,结果存放在碰撞检测结果列表中。

// 创建一个命中检测结果对象列表,arSession为创建AR场景步骤中创建的会话对象。
AREngine_ARHitResultList *hitResultList = nullptr;
HMS_AREngine_ARHitResultList_Create(arSession, &hitResultList);

// 获取命中检测结果对象列表,arFrame为创建AR场景步骤中创建的帧对象,pixeLX/pixeLY为屏幕点坐标。
HMS_AREngine_ARFrame_HitTest(arSession, arFrame, pixeLX, pixeLY, hitResultList);

6.放置虚拟物体

调用HMS_AREngine_ARHitResultList_GetItem函数遍历碰撞检测结果列表,获取命中的可跟踪对象。

// 创建命中检测结果对象。
AREngine_ARHitResult *arHit = nullptr;
HMS_AREngine_ARHitResult_Create(arSession, &arHit);

// 获取第一个命中检测结果对象。
HMS_AREngine_ARHitResultList_GetItem(arSession, hitResultList, 0, arHit);

// 获取被命中的可追踪对象。
AREngine_ARTrackable *arHitTrackable = nullptr;
HMS_AREngine_ARHitResult_AcquireTrackable(arSession, arHit, &arHitTrackable);

判断碰撞结果是否存在于平面内部。

AREngine_ARTrackableType ar_trackable_type = ARENGINE_TRACKABLE_INVALID;
HMS_AREngine_ARTrackable_GetType(arSession, arTrackable, &ar_trackable_type)
if (ARENGINE_TRACKABLE_PLANE == ar_trackable_type) {
    AREngine_ARPose *arPose = nullptr;
    HMS_AREngine_ARPose_Create(arSession, nullptr, 0, &arPose);
    HMS_AREngine_ARHitResult_GetHitPose(arSession, arHit, arPose);
    // 判断位姿是否位于平面的多边形范围内。0表示不在范围内,非0表示在范围内。
    HMS_AREngine_ARPlane_IsPoseInPolygon(mArSession, arPlane, arPose, &inPolygon)
    HMS_AREngine_ARPose_Destroy(arPose);
    if (!inPolygon) {
    // 不在平面内,就跳过当前平面。
    continue;
    }
}

在碰撞结果位置创建一个新的锚点,并基于此锚点放置虚拟模型。

// 在碰撞命中位置创建一个新的锚点。
AREngine_ARAnchor *anchor = nullptr;
HMS_AREngine_ARHitResult_AcquireNewAnchor(arSession, arHitResult, &anchor)

// 判断锚点的可跟踪状态
AREngine_ARTrackingState trackingState = ARENGINE_TRACKING_STATE_STOPPED;
HMS_AREngine_ARAnchor_GetTrackingState(arSession, anchor, &trackingState)
if (trackingState != ARENGINE_TRACKING_STATE_TRACKING) {
    HMS_AREngine_ARAnchor_Release(anchor);
    return;
}

调用HMS_AREngine_ARAnchor_GetPose函数获取锚点位姿,并基于该位姿绘制虚拟模型。

// 获取锚点的位姿。
AREngine_ARPose *pose = nullptr;
HMS_AREngine_ARPose_Create(arSession, nullptr, 0, &pose);
HMS_AREngine_ARAnchor_GetPose(arSession, anchor, pose);
// 将位姿数据转换成4X4的矩阵modelMat。
HMS_AREngine_ARPose_GetMatrix(arSession, pose, glm::value_ptr(modelMat), 16);
HMS_AREngine_ARPose_Destroy(pose);
// 绘制虚拟模型。

了解更多详情>>

访问AR Engine联盟官网

获取AR Engine开发指导文档

  分类: 技术赋能

标签:AR,放置,AREngine,nullptr,HMS,虚拟,arSession,平面,精准
From: https://www.cnblogs.com/sexintercourse/p/18439112

相关文章

  • 针对VMware的安装遇到的麻烦及解决办法(vmware Authorization Service未启动,蓝屏,卡在下
    注明:该文章仅为个人安装vmware时遇到的问题与解决方法的总结,第一次写文章可能有点粗糙。注意:如要使用vmware需要允许虚拟机相关服务详见第二个文章链接(先开启此项服务尝试问题是否解决再尝试其他办法)一:vmwareAuthorizationService未启动安装好VMware后运行虚拟机时会报错......
  • 无人机之虚拟云台技术篇
    一、概念解释   虚拟云台技术,并非直接安装在无人机上的机械装置,而是通过软件算法和传感器技术,模拟出物理云台的功能,实现对相机或传感器的稳定控制。这种技术通过高精度的算法和实时数据处理,能够在无人机飞行过程中,有效抵消因风力、机体振动等外部因素引起的抖动,从而确保......
  • java计算机毕业设计网络游戏虚拟交易平台(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着网络技术的飞速发展与普及,网络游戏已成为全球范围内广受欢迎的休闲娱乐方式之一。这一趋势不仅催生了庞大的玩家群体,也孕育了繁荣的虚拟经济体系......
  • 抖音快手OBS虚拟摄像头直播被误判录播如何修改注册表
     使用OBS虚拟摄像头容易被检测为录播解决办法参考增加视频内容的动态性:在OBS中使用多个视频源,如实时摄像头画面、屏幕共享、图像幻灯片等,并且定期切换这些源,以模拟真实直播时的操作。使用摄像头捕捉真实画面:将真实摄像头捕捉到的画面作为OBS的一个场景,这样可以......
  • 虚拟环境venv
    使用虚拟环境开发项目的例子1.创建项目目录首先,在你的工作目录下创建一个新项目文件夹:mkdirmyflaskappcdmyflaskapp2.创建虚拟环境在项目文件夹中创建一个虚拟环境:python3-mvenvvenv这将创建一个名为venv的虚拟环境。3.激活虚拟环境接下来,激活虚拟环......
  • 【VMware ESXi】使用 esxtop 杀死 ESXi 主机中卡死和不响应的虚拟机。
    最近在家里的Homelab主机上进行VMwareCloudFoundation相关测试,由于CPU超负荷使用,某个别虚拟机时不时的会出现卡死和不响应等现象,进而导致了测试的失败并影响了相关实验的进度。比如,下图所示的嵌套ESXi虚拟机,本来运行好好的,由于资源不足,该虚拟机便出现了卡死和不响应问题......
  • 从‘盲管’到‘智网’,精准构建排水管网监测方案
    在城市错综复杂的基础设施网络中,排水管网作为城市的“血脉”,其高效、稳定运行直接关系到城市生活的安宁与财产的安全。面对日益频繁的雨季挑战与气候变化的不确定性,传统“盲管”管理模式已难以满足现代城市治理的需求。排水管网监测方案通过集成高精度传感器与低功耗远程采集终端,实......
  • AI数字人直播爆火,数字人虚拟主播成品牌闲时直播最佳选择!
    近年来,随着互联网的普及和发展,电商和直播平台在我国迅速崛起。根据中国网络信息中心的数据显示,我国直播用户7.5亿,使用率已经超过70%,直播已经成为企业重要的营销和销售通道。一、在经历了几年的爆发式增长后,如今的直播行业也面临着一些显著的痛点:1、组建直播团队运营成本过......
  • 阿里云虚拟主机的PbootCMS网站为什么不显示后台登录验证码(pbootcms后台登陆不显示验证
    在使用PBootCMS时,如果遇到后台登录验证码不显示的问题,通常与服务器配置或PHP设置有关。以下是具体原因分析和解决方法:原因分析输出缓冲区未开启PHP的输出缓冲区(OutputBuffering)未开启,导致某些动态内容(如验证码图片)无法正常输出。权限问题文件或目录权限设置不当,导致......
  • 深入 JavaScript 世界:掌握 OOP、虚拟 DOM 等
    踏上激动人心的旅程,探索广阔而动态的javascript世界!getvm提供的免费编程学习资源集合涵盖了广泛的主题,从复杂的面向对象编程(oop)到创建自定义虚拟dom实现。无论您是经验丰富的开发人员还是好奇的初学者,这些教程都将为您提供提升javascript能力的知识和技能。?理......