Immersive Web Emulator Runtime
IWER is a developing tool for webXR. I am doing a school project, which uses webXR to make a campus tour.
I used MeshLab to cut the 3DGS model, and Google Camera to take panorama environment.
I tried three.js, babylon.js and R3F to test which one is suitable.
Panorama
Babylon.js has its own panorama component EquiRectangularCubeTexture
. Generating this uses a lot resources, make sure to use assetManager to load it the page will show a loading state.
const textureP = new Promise<EquiRectangularCubeTexture>(res => {
const textureTask = assetManager.addEquiRectangularCubeTextureAssetTask(
"sky",
"./images/sky.jpg",
2048,
);
textureTask.onSuccess = task => {
task.texture.coordinatesMode = Texture.SKYBOX_MODE;
res(task.texture)
}
})
const eqTexture = await textureP;
const hdrSkybox = MeshBuilder.CreateBox("hdrSkyBox", { size: 2000 }, scene);
const hdrSkyboxMaterial = new PBRMaterial("skyBox", scene);
hdrSkyboxMaterial.backFaceCulling = false;
hdrSkyboxMaterial.reflectionTexture = eqTexture.clone();
hdrSkyboxMaterial.reflectionTexture.coordinatesMode = Texture.SKYBOX_MODE;
hdrSkyboxMaterial.microSurface = 1.0;
hdrSkyboxMaterial.disableLighting = false;
hdrSkybox.material = hdrSkyboxMaterial;
hdrSkybox.infiniteDistance = true;
While three.js and R3F do not have panorama support, a sphere geometry can be used, but the building will be tilted.
3DGS
Babylon.js also have its official support for guassian splatting files PLY
. But it has less customization options.
import { registerBuiltInLoaders } from "@babylonjs/loaders/dynamic"
is needed to be imported.
const meshP = new Promise<AbstractMesh[]>(res => {
const meshTask = assetManager.addMeshTask(
"logo",
"",
"./models/",
"logo.ply"
)
meshTask.onSuccess = task => {
res(task.loadedMeshes)
}
})
const result = await meshP;
const mesh = result[0];
mesh.position.y = -50.0;
mesh.rotation.z = Math.PI;
mesh.rotation.y = -280/360 * Math.PI;
mesh.scaling.x = 100;
mesh.scaling.y = 100;
mesh.scaling.z = 100;
Three.js and R3F have no support for 3DGS. useLoader
will load a weird Mesh. But you can make a point cloud with more customization.
export default function GaussianPointCloud({ url }) {
const geometry = useLoader(PLYLoader, url)
const material = new THREE.PointsMaterial({
size: 0.01,
vertexColors: true,
})
return <points geometry={geometry} material={material} />
}
Inspector
There is an inspector support in Babylon.js. It is much easier to visually put item in the scenes. import "@babylonjs/inspector";
is needed.
While three.js and r3f’s inspector is archived. There is leva can be used as a dat.gui in React.
Request XR
In babylon, just config every thing in the scene setup. However, the getEnabledFeature
can not correctly return the right type. And you do not need to care about others, the VR button will automatically added on screen.
const experience = await scene.createDefaultXRExperienceAsync({
uiOptions: {
sessionMode: "immersive-vr",
},
optionalFeatures: true,
floorMeshes: [ground],
});
experience.teleportation.addFloorMesh(ground);
const teleportation = experience.baseExperience.featuresManager.getEnabledFeature(
WebXRFeatureName.TELEPORTATION
) as WebXRMotionControllerTeleportation
teleportation.onAfterCameraTeleport.add((targetPosition) => {
console.log("Teleported to:", targetPosition);
});
There is an XR component that provides XR components for R3F. And To add the VRButton, you need to place the VRButton
component in page, also in three.js.
Three.js also needs gamepad-wrapper
from npm to add gamepad support.
Text Render
troika-three-text
can be used in three.js.
earcut
is used as an injection in babylon.js.
On Device Testing
Start a https server, if using vite, use mkcert plugin.
export default defineConfig({
plugins: [
mkcert()
],
})
Also, there are many examples in babylonjs’s playground, using Caddy is a good option.
# Caddyfile
localhost:5000 {
root * ./
file_server
}
Using Meta Quest Link sometimes work. But it is better to use port-forwarding in chrome.
PC Testing
This time, IWER is in use. There are some errors in the latest version, "@iwer/devui": "^0.1.1"
,"iwer": "^1.0.4"
is a workable version. But the movement is slow.
import { XRDevice, metaQuest3 } from 'iwer';
import { DevUI } from '@iwer/devui';
let nativeWebXRSupport = false;
if (navigator.xr) {
nativeWebXRSupport = await navigator.xr.isSessionSupported('immersive-vr');
}
if (!nativeWebXRSupport) {
const xrDevice = new XRDevice(metaQuest3);
xrDevice.installRuntime();
xrDevice.fovy = (75 / 180) * Math.PI;
xrDevice.ipd = 0;
xrDevice.controllers.right?.position.set(0.15649, 1.43474, -0.38368);
xrDevice.controllers.right?.quaternion.set(
0.14766305685043335,
0.02471366710960865,
-0.0037767395842820406,
0.9887216687202454,
);
xrDevice.controllers.left?.position.set(-0.15649, 1.43474, -0.38368);
xrDevice.controllers.left?.quaternion.set(
0.14766305685043335,
0.02471366710960865,
-0.0037767395842820406,
0.9887216687202454,
);
new DevUI(xrDevice);
}