WebXR
前言
W3C出了一套包括但不限於WebVR/WebAR的規范。這個W3C組織名叫Immersive Web,即沉浸式網絡,非常確切的概括了WebXR想要實現的效果。組織的目標是通過API將高性能虛擬現實(VR)和增強現實(AR)(統稱為XR)引入開放式Web,以便與Web中的傳感器和XR設備進行交互。
當前狀態
WebXR W3C提案目前處於草案階段。 然而,它已經在 Chrome 中實現了(查看 caniuse.com 可了解其他瀏覽器的WebXR實現情況)。 從Chrome的79版本開始,WebVR已被棄用,默認情況下啟用WebXR。 早期的Chrome瀏覽器版本在配置選項里有WebXR選項,可選擇打開。 其目標之一是棄用WebVR和其他AR實現,並提供單一的VR和AR API。
由於WebXR API還在不斷變化和更新中,所以babylon.js很難跟上其功能和特性的變化。最新的chrome瀏覽器金絲雀版本(canary)是目前XR功能最完整的瀏覽器,而且谷歌也在不斷持續的更新WebXR新功能。 這是我們引入功能管理器(Features Manager)的主要原因,它允許Babylon.js通過內部版本控制功能去實現WebXR最新版本的官方功能,而不會破壞向后兼容性。
請注意,大多數時候當我們說WebXR時,我們實際上是指VR沉浸式模式(VR immersive mode)下的WebXR。 這是目前WebXR最常用的模式。
設備和瀏覽器支持
PC
所有微軟的混合現實設備使用的windows系統的Chrome 79版本瀏覽器都官方支持WebXR。 非官方的,WebXR 與 oculus SDK(Rift、Rift S 和 Quest with Link)配合良好。 在撰寫本文時,對Oculus的官方支持仍未公布。
手機和Quest
Google Daydream設備使用Chrome瀏覽器可支持WebXR。
Android 的 Chrome 瀏覽器(Stable 和 Canary版本)上的 WebXR AR 功能可以在 chrome://flags 打開相應的功能配置標志后啟用,包括平面檢測、命中測試和錨點等 AR 功能。 請注意,AR功能的架構在不斷變化,因此預計不同版本的結果會有所不同。
Oculus Quest設備在最新的oculus瀏覽器中支持 WebXR(在 VR 模式下)。 Babylon.js的規范實現在quest設備中運行良好。
目前沒有正式的 iOS/iPhone 支持計划。 Mozilla 已經構建了一個在IOS系統能夠運行的WebXR瀏覽器(WebXR iOS Viewer),這是一個(非常)受限的面向 AR 的瀏覽器。
Polyfill
對於支持 WebVR 但不支持 WebXR 的舊瀏覽器,您可以使用 WebXR Polyfill,它是使用WebXR API接口去實現了WebVR的功能 。 某些功能將無法運行(或將直接返回而不進行任何操作),但基本功能運行OK。
Babylon.js不打算將 polyfill 集成到框架本身或Playground中。 我們鼓勵開發人員向那些不使用支持 WebXR 的瀏覽器的用戶提供 polyfill。
要在 Playground 中使用 polyfill,請將以下內容添加到您的 Playground中(在 'createScene'函數之前):
1 const xrPolyfillPromise = new Promise((resolve) => { 2 if (navigator.xr) { 3 return resolve(); 4 } 5 define("polyfill", ["https://cdn.jsdelivr.net/npm/webxr-polyfill@latest/build/webxr-polyfill.js"], (polyfill) => { 6 new polyfill(); 7 resolve(); 8 }); 9 });
然后,確保在初始化 WebXR 之前等待該函數執行完成:
const xrPolyfillPromise = new Promise((resolve) => { if (navigator.xr) { return resolve(); } define("polyfill", ["https://cdn.jsdelivr.net/npm/webxr-polyfill@latest/build/webxr-polyfill.js"], (polyfill) => { new polyfill(); resolve(); }); }); var createScene = async function () { // wait for the polyfill to kick in await xrPolyfillPromise; console.log(navigator.xr); // should be there! console.log(await BABYLON.WebXRSessionManager.IsSessionSupportedAsync("immersive-vr")); // should be true // create your scene var scene = new BABYLON.Scene(engine); var camera = new BABYLON.DeviceOrientationCamera("DevOr_camera", new BABYLON.Vector3(-30, -30, -30), scene); camera.setTarget(BABYLON.Vector3.Zero()); camera.attachControl(canvas, true); var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 0, 0), scene); scale = 100; // initialize XR var xr = await scene.createDefaultXRExperienceAsync(); return scene; };
如果在使用 polyfill 時遇到低分辨率,需要將畫布大小調整為更高的分辨率。 這是WebVR的限制(需要調整畫布大小),我們集成的WebXR沒有這個限制 。
WebXR模擬器
如果您正在開發並且不想經常在真實設備上進行測試,請使用 mozilla 的 WebXR 模擬器,它適用於 chrome 和 firefox瀏覽器。 我們支持它並在開發過程中實際使用了它。 強烈推薦!
快速入門
最簡單的入門方法是使用支持 WebXR 的瀏覽器並將一行代碼添加到您的場景中:
const xr = scene.createDefaultXRExperienceAsync();
這將在 VR 沉浸式模式下(VR immersive mode)啟用 WebXR,包括會話初始化、輸入源、相機、隱形傳態和場景交互。 全部使用我們的 WebXR 默認體驗助手(WebXR Default Experience Helper)。
請注意, xr 變量是一個 Promise。 使用 async/await 模式會更簡單、更直觀。 定義地板網格(floor meshes)也很有意義,這樣我們就可以定義我們的地面並在上面移動。 這是 XR 中的一個球體:
var createScene = async function () { var scene = new BABYLON.Scene(engine); var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene); camera.setTarget(BABYLON.Vector3.Zero()); camera.attachControl(canvas, true); var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); light.intensity = 0.7; var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene); sphere.position.y = 1; const env = scene.createDefaultEnvironment(); // here we add XR support const xr = await scene.createDefaultXRExperienceAsync({ floorMeshes: [env.ground], }); return scene; };
這樣就可以了,是不是很簡單?
請務必閱讀有關WebXR Experience Helper的更多信息以獲取更多提示和技巧,並查看我們的演示和示例。
從WebVR遷移到WebXR
WebVR 已被棄用,並且將很快在大多數瀏覽器中結束其生命周期。 強烈建議將所有 WebVR 實現移植到 WebXR。
從VR Experience helper遷移
如果您使用了我們的 VR 體驗助手(WebVR experience helper),請移除 VR 初始化程序並添加 XR 體驗助手(WebXR experience helper)。 所以代碼由:
var scene = new BABYLON.Scene(engine); var vrHelper = scene.createDefaultVRExperience();
轉變成:
var scene = new BABYLON.Scene(engine); var xrHelper = scene.createDefaultXRExperienceAsync();
默認情況下,WebXR助手具有完整的控制器支持,包括與場景網格、指針事件等的交互。 閱讀有關WebXR體驗助手的更多信息。
遷移控制器支持
由於 WebXR 控制器不再被視為游戲手柄,因此架構略有不同。
添加的最重要的功能是對控制器的完整指針事件支持。 控制器支持所有指針事件,因此您可以使用指針交互(Pointer interactions),就像在場景中控制鼠標交互一樣。
同樣需要注意的是,現在可以查詢控制器具有哪些功能及其相關的操作。
以下是 VR 控制器最常用的功能,以及如何讓它們在 XR 中工作:
1 // On new controller attached: 2 3 // WebVR: 4 webvrCamera.onControllersAttached = (vrController) => { 5 // fun with the new controller, which is a gamepad! 6 }; 7 8 // WebXR: 9 const webXRInput = xr.input; // if using the experience helper, otherwise, an instance of WebXRInput 10 input.onControllerAddedObservable.add((xrController /* WebXRInputSource instance */) => { 11 // more fun with the new controller, since we are in XR! 12 inputSource.onMotionControllerInitObservable.add((motionController) => { 13 // get the motionController, which is similar to but NOT a gamepad: 14 }); 15 // xr supports all types of inputs, so some won't have a motion controller 16 if (!xrController.gamepad) { 17 // using touch, hands, gaze, something else? 18 } 19 }); 20 21 // From this point we assume we have two variables: vrController and xrController. 22 // We also assume motionController is present! 23 24 // main button 25 26 // WebVR: 27 controller.onMainButtonStateChangedObservable.add((newState /* ExtendedGamepadButton */) => { 28 // is the button pressed? 29 if (newState.pressed) { 30 // Do something 31 } 32 }); 33 34 // WebXR: 35 // get the main component (decided by the controller's vendor!) 36 const mainComponent = xrController.motionController.getMainComponent(); 37 // or get the trigger component, if present: 38 const mainTrigger = xrController.motionController.getComponent(WebXRControllerComponent.TRIGGER); 39 mainComponent.onButtonStateChanged.add((component /* WebXRControllerComponent */) => { 40 // check for changes: 41 // pressed changed? 42 if (component.changes.pressed) { 43 // is it pressed? 44 if (component.changes.pressed.current === true) { 45 // pressed 46 } 47 // or a different way: 48 if (component.pressed) { 49 // component is pressed. 50 } 51 } 52 }); 53 54 // thumbpad / touchpad 55 56 // in WebVR - you had to check what controller is being used, but in general this would work: 57 vrController.onPadValuesChangedObservable.add(function (stateObject) { 58 console.log(stateObject); // {x: 0.1, y: -0.3} 59 }); 60 61 // in webXR you can check if it is present and work accordingly: 62 const thumbstick = xrController.motionController.getComponent(WebXRControllerComponent.THUMBSTICK); 63 if (thumbstick) { 64 // Huzza! we have a thumbstick: 65 thumbstick.onButtonStateChanged; // will trigger when the thumbstick is PRESSED or touched! 66 67 thumbstick.onAxisValueChanged; // will trigger when axes of the thumbstick changed 68 } 69 70 // touchpad 71 72 // in WebVR we had "pad" concept which was for both thumbstick and touchpad 73 controller.onPadValuesChangedObservable.add(function (stateObject) { 74 console.log(stateObject); // {x: 0.1, y: -0.3} 75 }); 76 77 // in WebXR, it is much much better: 78 const touchpad = xrController.motionController.getComponent(WebXRControllerComponent.TOUCHPAD); 79 if (touchpad) { 80 // Finally, a controller with a touchpad 81 touchpad.onButtonStateChanged; // will trigger when the touchpad is touched or pressed 82 83 touchpad.onAxisValueChanged; // will trigger when axes of the touchpad changed 84 }
閱讀有關XR 控制器系統的更多信息。
兼容性支持
我們始終鼓勵向后兼容,我們建議直接使用 WebXR 並停止使用 WebVR 體驗助手(WebVR experience helper)。
當然:
最新的 WebVR 體驗助手在其初始化選項中有一個新標志 - useXR。 這將檢查是否支持WebXR,如果支持,則會在WebXR中啟動VR會話。
一個工作示例參見WebVR demo。
源代碼如下:
1 var createScene = function () { 2 // Create scene 3 var scene = new BABYLON.Scene(engine); 4 5 // Create simple sphere 6 var sphere = BABYLON.Mesh.CreateIcoSphere( 7 "sphere", 8 { 9 radius: 0.2, 10 flat: true, 11 subdivisions: 1, 12 }, 13 scene, 14 ); 15 sphere.position.y = 3; 16 sphere.material = new BABYLON.StandardMaterial("sphere material", scene); 17 18 // Lights and camera 19 var light = new BABYLON.DirectionalLight("light", new BABYLON.Vector3(0, -0.5, 1.0), scene); 20 light.position = new BABYLON.Vector3(0, 5, -2); 21 var camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 4, 3, new BABYLON.Vector3(0, 3, 0), scene); 22 camera.attachControl(canvas, true); 23 scene.activeCamera.beta += 0.8; 24 25 // Default Environment 26 var environment = scene.createDefaultEnvironment({ 27 enableGroundShadow: true, 28 groundYBias: 2.8, 29 }); 30 environment.setMainColor(BABYLON.Color3.FromHexString("#74b9ff")); 31 32 // Shadows 33 var shadowGenerator = new BABYLON.ShadowGenerator(1024, light); 34 shadowGenerator.useBlurExponentialShadowMap = true; 35 shadowGenerator.blurKernel = 32; 36 shadowGenerator.addShadowCaster(sphere, true); 37 38 // Enable VR, use XR when possible 39 var vrHelper = scene.createDefaultVRExperience({ 40 createDeviceOrientationCamera: false, 41 useXR: true, // This will enable XR if supported 42 floorMeshes: [environment.ground], 43 }); 44 45 // Runs every frame to rotate the sphere 46 scene.onBeforeRenderObservable.add(() => { 47 sphere.rotation.y += 0.0001 * scene.getEngine().getDeltaTime(); 48 sphere.rotation.x += 0.0001 * scene.getEngine().getDeltaTime(); 49 }); 50 51 // GUI 52 var plane = BABYLON.Mesh.CreatePlane("plane", 1); 53 plane.position = new BABYLON.Vector3(0.4, 4, 0.4); 54 var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(plane); 55 var panel = new BABYLON.GUI.StackPanel(); 56 advancedTexture.addControl(panel); 57 var header = new BABYLON.GUI.TextBlock(); 58 header.text = "Color GUI"; 59 header.height = "100px"; 60 header.color = "white"; 61 header.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER; 62 header.fontSize = "120"; 63 panel.addControl(header); 64 var picker = new BABYLON.GUI.ColorPicker(); 65 picker.value = sphere.material.diffuseColor; 66 picker.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER; 67 picker.height = "350px"; 68 picker.width = "350px"; 69 // This will work in XR, since we are using native pointer events! 70 picker.onValueChangedObservable.add(function (value) { 71 sphere.material.diffuseColor.copyFrom(value); 72 }); 73 panel.addControl(picker); 74 75 vrHelper.onAfterEnteringVRObservable.add(() => { 76 // This callback will still work! Would be better to use the XR native observables. 77 }); 78 79 return scene; 80 };
顏色選擇器(The color picker)使用指針架構(The pointer architecture)來工作,用戶點擊(point)不同顏色區域,可以選擇不用的顏色。 如果存在 XR,則將使用 XR。 否則,它將使用 WebVR 作為后備。
請注意,WebVR的某些功能將無法正常工作或根本無法工作。 例如,相機凝視(camera gaze)功能根本不起作用。 控制器可以工作,但由於交互架構不同,您很可能需要調整一些觀察者(observers)才能使其工作,特別是專門針對VR的控制器回調。
我們建議在不支持WebXR的瀏覽器中,使用WebXR polyfill代替WebVR 體驗助手(WebVR experience helper)進行WebVR開發。
PS:
1)本文翻譯自babylon.js官方WebXR文檔,翻譯用於自學和鞏固知識,也希望能給有需要的人一點幫助;
2)閱讀本文,需要具備一定的Web JavaScript,babylon.js,以及WebVR開發基礎。
翻譯若有不當之處,請指正。