WebXR的AR功能特性
关于增强现实(AR)
增强现实(AR)背后的想法很简单——展示真实世界,但可以在真实世界中添加信息。
这点与虚拟现实(VR)不同,虚拟现实让你完全沉浸在不同的场景中,与现实世界没有实际接触,增强现实让你与现实世界互动。
快速入门
WebXR和AR
使用Babylon.js构建增强现实(AR)将大量使用WebXR,所以我建议您首先开始使用WebXR入门指南。
大多数对沉浸式VR会话有效的信息也对沉浸式AR会话有效。这里将解释两者之间的几个主要区别。
支持设备
沉浸式AR会话(目前)在两种类型的设备上得到支持——手机和Hololens上的firefox reality浏览器。
使用android的手机支持chrome(稳定/金丝雀版本)上的沉浸式AR会话。请注意,您将需要安装AR Core,否则这将是一个非常短暂的体验。
Hololens 2在使用Firefox Reality for Hololens时支持WebXR和沉浸式AR会话。
要在桌面上检查场景,可以使用WebXR emulator,它支持AR的一组功能,并允许您在选择移动模式时进入沉浸式AR会话。
沉浸式AR(immersive AR)中的简单场景示例
最简单的沉浸式AR示例如下所示:
1 var createScene = async function () { 2 var scene = new BABYLON.Scene(engine); 3 var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene); 4 camera.setTarget(BABYLON.Vector3.Zero()); 5 camera.attachControl(canvas, true); 6 var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); 7 light.intensity = 0.7; 8 var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene); 9 sphere.position.y = 2; 10 sphere.position.z = 5; 11 12 const xr = await scene.createDefaultXRExperienceAsync({ 13 // ask for an ar-session 14 uiOptions: { 15 sessionMode: "immersive-ar", 16 }, 17 }); 18 19 return scene; 20 };
官方Playground的简单AR示例链接:simple immersive AR scene
我们可以注意到,AR示例中没有创建任何环境。
与沉浸式VR会话相反,AR不需要天空盒或地面。
如果您想在进入AR模式时将已经定义好的地面移除(例如,如果您开发的应用同时支持桌面和AR两种模式),您可以使用本页后面描述的背景移除功能。
AR特性
一些AR的功能特性需要在最新的chrome canary版本中启动相关配置。访问chrome://flags/浏览器配置页面,并启用WebXR incubation配置选项。
然后,您需要在您的代码中让WebXR启用这些功能特性。这可以使用default experience helper中的optionalFeature参数完成:
1 const xr = await scene.createDefaultXRExperienceAsync({ 2 uiOptions: { 3 sessionMode: "immersive-ar", 4 }, 5 optionalFeatures: true, 6 });
上述代码将启用我们支持的所有可选功能。您也可以使用数组来定制添加自己需要的功能特性:
1 const xr = await scene.createDefaultXRExperienceAsync({ 2 uiOptions: { 3 sessionMode: "immersive-ar", 4 }, 5 optionalFeatures: ["hit-test", "anchors"], 6 });
Hit test
Hit-test(命中测试)用于向现实世界发送光线,并接收有关空间相交的信息。您可以在hit test w3c draft中了解它。
想象一条光线从您的手机屏幕向您正在寻找的物体发射。如果设备的AR功能允许,您将知道相交点相对于您的位置和方向。
启用hit-testing,需要在初始化XR后添加此如下代码:
1 // featuresManager from the base webxr experience helper 2 const hitTest = featuresManager.enableFeature(BABYLON.WebXRHitTest, "latest");
在typescript中,您还可以获取正确设置的类型:
1 // featuresManager from the base webxr experience helper 2 const hitTest = featuresManager.enableFeature(BABYLON.WebXRHitTest, ‘latest‘) as BABYLON.WebXRHitTest;
启用hit-testing后的默认行为,即在每一帧上从显示器中心向前发送一条hit-test ray。
如下接口描述了您可以传递的可选参数:
1 export interface IWebXRHitTestOptions { 2 /** 3 * Do not create a permanent hit test. Will usually be used when only 4 * transient inputs are needed. 5 */ 6 disablePermanentHitTest?: boolean; 7 /** 8 * Enable transient (for example touch-based) hit test inspections 9 */ 10 enableTransientHitTest?: boolean; 11 /** 12 * Offset ray for the permanent hit test 13 */ 14 offsetRay?: Vector3; 15 /** 16 * Offset ray for the transient hit test 17 */ 18 transientOffsetRay?: Vector3; 19 /** 20 * Instead of using viewer space for hit tests, use the reference space defined in the session manager 21 */ 22 useReferenceSpace?: boolean; 23 }
disablePermanentHitTest选项将禁用持续hit-testing,而enableTransientHitTest选项将在触摸屏幕时启用hit-testing。offsets几个参数代表偏移,定义光线发射起点的偏移量(相对于设备视图的中心)。
启用hit-testing功能后,您可以注册OnHitTestResultoServable函数以获取hit-testing的更新数据:
1 // a dot to show in the found position 2 const dot = BABYLON.SphereBuilder.CreateSphere( 3 "dot", 4 { 5 diameter: 0.05, 6 }, 7 scene, 8 ); 9 dot.isVisible = false; 10 hitTest.onHitTestResultObservable.add((results) => { 11 if (results.length) { 12 dot.isVisible = true; 13 results[0].transformationMatrix.decompose(dot.scaling, dot.rotationQuaternion, dot.position); 14 } else { 15 dot.isVisible = false; 16 } 17 });
上述代码实现了功能:在hit-test有效时显示圆点,如果无效时,则将隐藏圆点。圆点通过使用系统提供的信息,被投影到现实世界中。
基于Babylon.js的WebXR hit-test的一个简单示例:WebXR Hit-Test Using Babylon.js
用你的AR设备(可能是你的android智能手机)打开它,点击设备中的一个纹理平面(比如你的地板或门)。如果/当系统正确扫描平面时,标记将显示在正确位置。
瞄点(Anchors)
瞄点是空间中的跟踪点,当您扫描环境时,系统将不断更新这些点。点的转换将由底层系统不断更新。
您可以在WebXR anchors module w3c proposal中阅读更多关于瞄点的信息。
使用以下命令启用瞄点系统:
1 // featuresManager from the base webxr experience helper 2 const anchorSystem = featuresManager.enableFeature(BABYLON.WebXRAnchorSystem, "latest");
或对于typescript:
1 // featuresManager from the base webxr experience helper 2 const anchorSystem = featuresManager.enableFeature(BABYLON.WebXRAnchorSystem, ‘latest‘) as BABYLON.WebXRAnchorSystem;
一些选项可以传递给锚点系统:
1 export interface IWebXRAnchorSystemOptions { 2 /** 3 * a node that will be used to convert local to world coordinates 4 */ 5 worldParentNode?: TransformNode; 6 7 /** 8 * If set to true a reference of the created anchors will be kept until the next session starts 9 * If not defined, anchors will be removed from the array when the feature is detached or the session ended. 10 */ 11 doNotRemoveAnchorsOnSessionEnded?: boolean; 12 }
退出XR会话时会默认删除锚点(请注意,只删除锚点,连接到锚点的数据不会被删除),这是推荐的行为,因为在会话之间无法相互引用锚点。
如果要防止这种情况发生,请在初始化锚定系统时设置DonotRemoveAnchorSessionEnded:
const anchorSystem = featuresManager.enableFeature(BABYLON.WebXRAnchorSystem, "latest", { doNotRemoveAnchorsOnSessionEnded: true });
当您要在hit test位置添加锚点时会发现,瞄点系统和hit-test特性匹配的非常完美。使用addAnchorPointUsingHitTestResultAsync函数完成这个任务:
1 const arTestResult = getMeTheResultINeed(); 2 const anchorPromise = anchorSystem.addAnchorPointUsingHitTestResultAsync(lastHitTest);
要在场景中的任何位置和旋转中添加锚点,请使用AddAnchorPositionAndRotationAsync函数:
1 const { position, rotationQuaternion } = anyRandomMesh; 2 const anchorPromise = anchorSystem.addAnchorAtPositionAndRotationAsync(position, rotationQuaternion);
注意到,anchorPromise将在完成时返回一个内部的XRAnchor对象,它将为您提供浏览器返回的内容。
为了使用babylon锚点,我们使用瞄点模块中定义的observables:
1 anchorSystem.onAnchorAddedObservable.add((anchor) => { 2 // ... do what you want with the anchor after it was added 3 }); 4 5 anchorSystem.onAnchorRemovedObservable.add((anchor) => { 6 // ... do what you want with the anchor after it was removed 7 }); 8 9 anchorSystem.onAnchorUpdatedObservable.add((anchor) => { 10 // ... do what you want with the anchor after it was updated 11 });
瞄点类型为IWebXRAnchor:
1 export interface IWebXRAnchor { 2 /** 3 * A babylon-assigned ID for this anchor 4 */ 5 id: number; 6 /** 7 * Transformation matrix to apply to an object attached to this anchor 8 */ 9 transformationMatrix: Matrix; 10 /** 11 * The native anchor object 12 */ 13 xrAnchor: XRAnchor; 14 15 /** 16 * if defined, this object will be constantly updated by the anchor‘s position and rotation 17 */ 18 attachedNode?: TransformNode; 19 }
要将锚点附着到一个节点上(例如,节点为始终位于场景中某位置的模型对象),请使用attachedNode变量。当瞄点更新时,其附加的模型对象的变换也会更新:
1 const mesh = anchorSystem.onAnchorAddedObservable.add((anchor) => { 2 //... 3 anchor.attachedNode = mesh; 4 });
mesh模型对象现在将由系统跟踪,并将放置在指定的位置。
您可能会问自己,为什么要使用带有hit-test结果的瞄点系统,当hit-test结果由系统根据设备定义的一个位置返回。
在hit-test的位置放置mesh模型对象将非常有效。
不同的是,系统可能会更新关于这个位置的信息——可能它发现平面处于不同的变换,也可能它更新了它在空间中的位置。
使用瞄点系统将保持变换更新,即使系统更新了其空间知识。
平面检测
您的设备(通常)能够检测现实世界中的平面几何图形。要了解更多有关平面检测的信息,请访问平面检测说明。
Babylon有一个实验性的平面探测模块,与底层系统一起工作。要启用它,请执行以下操作:
1 // featuresManager from the base webxr experience helper 2 const planeDetector = featuresManager.enableFeature(BABYLON.WebXRPlaneDetector, "latest");
与任何模块一样,您可以使用选项对象(属于以下类型)对其进行配置:
1 export interface IWebXRPlaneDetectorOptions { 2 /** 3 * The node to use to transform the local results to world coordinates 4 */ 5 worldParentNode?: TransformNode; 6 7 /** 8 * If set to true a reference of the created planes will be kept until the next session starts 9 * If not defined, planes will be removed from the array when the feature is detached or the session ended. 10 */ 11 doNotRemovePlanesOnSessionEnded?: boolean; 12 }
与瞄点系统类似,平面不会在会话之间停留。如果要保留本地的XRPlane对象,请将doNotRemovePlanesOnSessionEnded设置为true,Babylon将不会删除它们。
平面探测器是自动工作的,并提供三个observables供您使用:
1 planeDetector.onPlaneAddedObservable.add((plane) => { 2 // ... do what you want with the plane after it was added 3 }); 4 5 planeDetector.onPlaneRemovedObservable.add((plane) => { 6 // ... do what you want with the plane after it was removed 7 }); 8 9 planeDetector.onPlaneUpdatedObservable.add((plane) => { 10 // ... do what you want with the plane after it was updated 11 });
平面对象的类型为IWebXRPlane:
1 export interface IWebXRPlane { 2 /** 3 * a babylon-assigned ID for this polygon 4 */ 5 id: number; 6 /** 7 * an array of vector3 points in babylon space. right/left hand system is taken into account. 8 */ 9 polygonDefinition: Array<Vector3>; 10 /** 11 * A transformation matrix to apply on the mesh that will be built using the polygonDefinition 12 * Local vs. World are decided if worldParentNode was provided or not in the options when constructing the module 13 */ 14 transformationMatrix: Matrix; 15 /** 16 * the native xr-plane object 17 */ 18 xrPlane: XRPlane; 19 }
要根据平面信息创建Babylon多边形,请执行以下操作:
1 const plane = // a reference to an added plane 2 // add the starting point, so the polygon will close 3 plane.polygonDefinition.push(plane.polygonDefinition[0]); 4 // create a polygon mesh builder for the polygons returned from the system 5 var polygon_triangulation = new BABYLON.PolygonMeshBuilder( 6 "name", 7 plane.polygonDefinition.map((p) => new BABYLON.Vector2(p.x, p.z)), 8 scene, 9 ); 10 // build the plane with specific thickness 11 var polygon = polygon_triangulation.build(false, 0.01);
平面的一个简单用例是使用多边形表示平面,在场景中中进行展示。在WebXR平面检测演示中可以找到一个示例:
背景移除
在AR模式下,应避免使用像天空框和地面这样的环境网格对象(除非您的目标是保留它们)。
如果您正在创建一个场景,该场景既能在常规设备模式下,又能在AR模式下工作,则您将希望能够在进入AR模式时禁用某些网格对象,并在离开AR模式时重新启用它们。
此模块正是这样做的。它接收网格对象列表,并在需要时禁用/启用网格对象。
当使用babylon环境助手(babylon environment helper)时,模块可以自动为您完成工作。在这种情况下,如果启用该功能,skybox和ground将自动删除。
要启用它,请使用:
1 const xrBackgroundRemover = featuresManager.enableFeature(BABYLON.WebXRBackgroundRemover);
要自定义模块的工作方式,请使用以下配置对象:
1 export interface IWebXRBackgroundRemoverOptions { 2 /** 3 * Further background meshes to disable when entering AR 4 */ 5 backgroundMeshes?: AbstractMesh[]; 6 /** 7 * flags to configure the removal of the environment helper. 8 * If not set, the entire background will be removed. If set, flags should be set as well. 9 */ 10 environmentHelperRemovalFlags?: { 11 /** 12 * Should the skybox be removed (default false) 13 */ 14 skyBox?: boolean, 15 /** 16 * Should the ground be removed (default false) 17 */ 18 ground?: boolean, 19 }; 20 /** 21 * don‘t disable the environment helper 22 */ 23 ignoreEnvironmentHelper?: boolean; 24 }
例如,如果希望模块仅移除skybox而不移除地面,则在使用环境辅助对象时,请通过以下方式启用该功能:
1 const xrBackgroundRemover = featuresManager.enableFeature(BABYLON.WebXRBackgroundRemover, "latest", { 2 environmentHelperRemovalFlags: { 3 skyBox: true, 4 ground: false, 5 }, 6 });
DOM覆盖
在AR模式下,可能需要显示DOM元素。
启用DOM覆盖时,功能元素是唯一必需的选项,可以是DOM元素或字符串(使用传递到document.querySelector时返回的第一个元素)。
enableFeature的最后一个参数可能对您很重要,可以将此功能设置为可选。
1 const featuresManager = xr.baseExperience.featuresManager; 2 const domOverlayFeature = featuresManager.enableFeature(BABYLON.WebXRDomOverlay, "latest", { element: ".dom-overlay-container" }, undefined, false); 3 4 xr.baseExperience.onStateChangedObservable.add((webXRState) => { 5 switch (webXRState) { 6 case BABYLON.WebXRState.ENTERING_XR: 7 case BABYLON.WebXRState.IN_XR: 8 // domOverlayType will be null when not supported. 9 console.log("overlay type:", domOverlayFeature.domOverlayType); 10 break; 11 } 12 });
当进入AR模式后,可以检查DOM覆盖类型的特征;如果浏览器中支持该功能,domOverlayType将为非空。
最新的选项可以在WebXR DOM overlay功能的源代码中找到。