-
-
[原创]仅需6步,实现虚拟物体在现实世界的精准放置
-
发表于: 2024-9-26 15:53 2953
-
增强现实(AR)技术作为一种将数字信息和现实场景融合的创新技术,近年来得到了快速发展,并在多个应用领域展现出其独特的魅力。比如在教育行业,老师可以通过虚拟现实场景生动直观地帮助学生理解抽象概念;在旅游行业,AR技术还能虚拟历史文化场景、虚拟导航等,为游客提供更加沉浸的互动体验。
然而,对于应用来说,AR技术的开发使用绝非易事,这需要高昂的开发成本和专业的技术人才。基于此,HarmonyOS SDKAR引擎服务(AR Engine)为广大应用开发者提供了先进的AR技术,解决了开发成本和技术门槛的难题。
在集成AR Engine能力后,开发者只需6个开发步骤,就可以实现将虚拟物体摆放于现实世界的平面上,实现虚拟和现实的融合,该功能可应用于虚拟家具放置、数字展厅布展等场景,为用户提供虚实结合的新体验。
功能演示
业务流程
AR摆放实现的业务流程主要分为打开应用、识别平面并展示和放置虚拟物体三个部分。
第一部分是用户打开应用,应用需要向用户申请相机权限。如果用户未同意授权,则无法使用该功能。
第二部分中,AR Engine识别平面并展示。包括完成AR Engine初始化,更新ARFrame对象、获取平面、绘制平面并显示预览画面等步骤。
第三部分为放置虚拟物体。即用户点击屏幕,通过碰撞检测获取现实环境中的兴趣点,并在兴趣点上创建锚点,最终实现在锚点位置绘制虚拟物体,并将虚拟物体显示在预览画面上。
开发步骤
在实现AR物体摆放的具体开发步骤之前,开发者需要先创建Native C++工程,声明ArkTs接口,并申请以下权限授权。
1.创建UI界面
在做好准备工作后,需要创建一个UI界面,用于显示相机预览画面,并定时触发每一帧绘制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 | #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会话及预览尺寸。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | / / 【可选】创建一个拥有合理默认配置的配置对象。 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。
1 2 3 | / / 通过openGL接口获取纹理 ID . GLuint textureId = 0 ; glGenTextures( 1 , &textureId); |
设置openGL纹理,存储相机预览流数据。
1 2 | / / 设置可用于存储相机预览流数据的openGL纹理。 HMS_AREngine_ARSession_SetCameraGLTexture(arSession, textureId ); |
4.获取平面
调用HMS_AREngine_ARSession_Update函数更新当前AREngine_ARFrame对象。
1 2 | / / 获取帧数据AREngine_ARFrame。 HMS_AREngine_ARSession_Update(arSession, arFrame); |
获取相机的视图矩阵和相机的投影矩阵,用于后续绘制。
1 2 3 4 5 6 7 | / / 根据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函数获取平面列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | / / 获取当前检测到的平面列表。 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函数获取平面的二维顶点坐标数组,用于绘制平面边界。
1 2 3 4 5 6 7 8 9 10 11 12 13 | / / 获取检测到平面的二维顶点数组大小。 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 ); } |
将平面的二维顶点坐标转换到世界坐标系,并绘制平面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | / / 获取从平面的局部坐标系到世界坐标系转换的位姿信息。 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.点击屏幕
用户点击屏幕后,基于点击事件获取屏幕坐标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | / / 添加头文件: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函数进行碰撞检测,结果存放在碰撞检测结果列表中。
1 2 3 4 5 6 | / / 创建一个命中检测结果对象列表,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函数遍历碰撞检测结果列表,获取命中的可跟踪对象。
1 2 3 4 5 6 7 8 9 10 | / / 创建命中检测结果对象。 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); |
判断碰撞结果是否存在于平面内部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 ; } } |
在碰撞结果位置创建一个新的锚点,并基于此锚点放置虚拟模型。
1 2 3 4 5 6 7 8 9 10 11 | / / 在碰撞命中位置创建一个新的锚点。 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函数获取锚点位姿,并基于该位姿绘制虚拟模型。
1 2 3 4 5 6 7 8 | / / 获取锚点的位姿。 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); / / 绘制虚拟模型。 |
了解更多详情>>
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)