写点什么

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

作者:HarmonyOS SDK
  • 2024-09-26
    中国香港
  • 本文字数:6187 字

    阅读完需:约 20 分钟

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


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



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

功能演示


业务流程


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


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


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


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

开发步骤

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



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 onLoad ' + 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开发指导文档

用户头像

HarmonyOS SDK

关注

HarmonyOS SDK 2022-06-16 加入

HarmonyOS SDK通过将HarmonyOS系统级能力对外开放,支撑开发者高效打造更纯净、更智能、更精致、更易用的鸿蒙原生应用,和开发者共同成长。

评论

发布
暂无评论
仅需6步,实现虚拟物体在现实世界的精准放置_HarmonyOS_HarmonyOS SDK_InfoQ写作社区