WebXR is a set of enabling technologies that allow developers to create virtual reality and augmented reality experiences hosted in a browser. In this sense, it supercedes the older WebVR API.
There are a number of things that come together to build WebXR experiences.
Unless you're planning on building your own rendering engine, you're better off looking at an existing framework to get going.
There are a number of frameworks you can use to build WebXR experiences. Here are some that I've used or I've looked into in the past.
I've run into BabylonJS back in my Windows Mixed Reality development days, so that's what I'm going with today.
Let's put together a hello world! kind of page.
Let's start with a simple skeleton HTML5 page.
<!DOCTYPE html>
<html lang='en-US'>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hello World XR!</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
OK, a simple Hello World page, nothing much to see here.
Now let's import IWER so we can test this even without a headset.
<body>
<script type="importmap">
{
"imports": {
"iwer": "https://unpkg.com/iwer@1.0.0/build/iwer.module.js"
}
}
</script>
<script type='text/javascript'>
async function runAppAsync() {
const iwer = await import("iwer");
const xrDevice = new iwer.XRDevice(iwer.metaQuest2);
xrDevice.installRuntime();
}
runAppAsync();
</script>
</body>
At this point, you should be able to refresh your page and see it run without any errors, but there's not much going on yet.
Let's fix that.
To import BabylonJS, we'll add the core library and the loaders.
<!-- add these lines after the importmap script -->
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
Again, refreshing now won't do much, but it's good to see we haven't broken anything yet.
Let's add a rendering canvas and revisit our script block.
<canvas id="renderCanvas"></canvas>
<script type='text/javascript'>
let gameEngine = null;
let gameScene = null;
let gameXrDevice = null;
async function createSphereSceneAsync(engine, canvas) {
// Create a scene that can be rendered.
const scene = new BABYLON.Scene(engine);
// Create a free camera
const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
camera.setTarget(BABYLON.Vector3.Zero());
camera.attachControl(canvas, true);
// Create a light in the scene.
const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.7;
// Create a 2m sphere.
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere1", { segments: 16, diameter: 2 }, scene);
sphere.position.y = 1;
// Create the default environment.
const env = scene.createDefaultEnvironment();
// here we add XR support
const xr = await scene.createDefaultXRExperienceAsync({
floorMeshes: [env.ground],
});
return scene;
};
async function runAppAsync() {
const iwer = await import("iwer");
const xrDevice = new iwer.XRDevice(iwer.metaQuest2);
xrDevice.installRuntime();
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const scene = await createSphereSceneAsync(engine, canvas);
// These globals make it easy to tweak things from the browser console.
gameEngine = engine;
gameScene = scene;
gameXrDevice = xrDevice;
// Register a render loop to repeatedly render the scene
engine.runRenderLoop(function () {
scene.render();
});
};
runAppAsync();
</script>
OK, a lot of things are going to be happening this time around.
First, when you refresh the page, you'll see a sphere in the top-left corner of the page, and a button with VR goggles on the bottom-right. That's because Babylon has detected VR support, courtesy of IWER.
When you click on the goggles, you'll see the viewport grow larger and you'll find a pair of controllers. However, the sphere is gone. It's a classic - we're inside the sphere, so half of it is behind us, and everything else is removed by a backface cull (the sphere triangels are facing towards the outside).
We can correct this in the browser by adjusting our camera position with the globals we've conveniently set up for ourselves. Evaluate gameXrDevice.position.z = 6
and the sphere will be back in view.
Once we're ready to test on headset, we'll want to make sure IWER doesn't get in the way.
Let's go back to our runAppAsync
function and adjust the preamble there.
async function runAppAsync() {
const nativeXr = navigator.xr &&
await navigator.xr.isSessionSupported('immersive-vr');
if (!nativeXr) {
console.log('Bootstrapping IWER ...');
const iwer = await import("iwer");
const xrDevice = new iwer.XRDevice(iwer.metaQuest2);
xrDevice.installRuntime();
gameXrDevice = xrDevice; // let's move this up here
}
And with this, you're ready to walk around your beautiful sphere.
You can also speed up your iteration cycle by setting up port forwarding via adb
to connect to a server on your development host.
The end result is here.
OK, what are some things you'll need to add to your scene at this point?
You're now ready to fall down the rabbit hole of decorating your environment, making it interactive, acquiring or building assets, and so much more!
Happy reality crafting!