Create WebXR Experiences

Fivos Doganis
fivos.doganis@gmail.com

Focus on AR

Setup: testing on your mobile

  • Check that your smartphone can read QR codes
  • iOS
    • default Camera app
  • Android
    • use Google Chrome + scan button
    • or install a trustworthy QR code scanning app like Trend Micro
  • Other 100% web based alternatives
    • webqr.com
    • qrcodescan.in

Native AR

ARCore 🤖
ARKit 🍎

ModelViewer

Check if your device supports Native AR

  • on your mobile, open

https://modelviewer.dev/

  • click on the AR icon
  • see the astronaut in AR 🎉
    • uses SceneViewer (Android)
    • or QuickLook (iOS)

Android 🤖

http://storage.googleapis.com/ar-answers-in-search-models/static/mandalorian/grogu/grogu.glb

iOS 🍎

http://storage.googleapis.com/ar-answers-in-search-models/static/mandalorian/grogu/grogu.usdz

  • USDZ file format
  • Native AR QuickLook Preview
    • uses Apple's ARKit
    • realistic lighting and occlusions

More examples

https://sketchfab.com/

WebAR

Why use the Web for AR?

  • mobile experiences
  • open technologies
    • cross-platform
    • non-propietary (unlike Unity or Unreal)
    • free
    • distribute by sharing URLs : no installation, no app store
  • easy integration with many existing Web APIs
    • anchors the the web to the real world
    • advanced interactions

Web

HTML, JS, CSS
Canvas API

Reminder: Minimalistic web page

<!DOCTYPE html>
<html> 

<head>
    <meta charset='utf-8'>
</head>

<body>    
</body>

</html>

Reminder: Minimalistic drawing program using the Canvas API

<!DOCTYPE html>
<html> 

<head>
    <meta charset='utf-8'>
</head>
<body>
    <canvas width='640' height='480'></canvas>
    <script>
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d'); // get 2D rendering context, on which we'll draw
        ctx.fillStyle = 'rgba(255, 0, 0, 1)'; // a.k.a. 'red': opaque red
        ctx.fillRect(0, 0, canvas.width, canvas.height); // uses current color
    </script>
</body>

</html>

"There Is Also Canvas"

Bruno Imbrizi

WebGL

Intro
Advanced

WebGL Intro

History
Stack

Prehistory (1983 - 1993)

  • Silicon Graphics (SGI) hardware only
    • IRIX OS
  • IRIS GL (1983)
    • API close to hardware
  • IRIS Inventor (1988)
  • OpenGL 1.0 (1993)
    • Open API, Multi-OS

Fixed Pipeline (1993 - 2004)

  • 3dfx Glide API (1996)
    • Voodoo: "hardware 3D acceleration" for all
  • Microsoft Direct X API (1997)
    • Windows-only ☹️
  • OpenGL ES (2004)
  • OpenGL 2.0 (2004)

Shaders, Mobile, Web (2004+)

  • OpenGL ES 2.0 (2007)
    • Mobile subset with shaders
  • Canvas 3D (2007), WebGL ancestor
  • WebGL 1.0 (2011) ⭐ 🎉
    • OpenGL ES 2.0 functionality for the Web!
  • OpenGL ES 3.0 (2012), 3.1 (2014): not for Apple 😢
  • WebGL 2.0 (2017)
    • OpenGL ES 3.0 exposed to the Web

WebGL architecture: software stack

  • Code: HTML + CSS + JS
    • JS code inside the web page makes WebGL API calls
  • Browser:
    • browser interprets JS code (using JS Engine)
    • turns WebGL calls into OpenGL calls (binding)
  • OS + Driver: converts OpenGL calls to
    • DirectX calls on Windows, Metal on Apple (using ANGLE)
    • OpenGL or OpenGL ES calls on other OSes
  • CPU + GPU: run the hardware accelerated code!

Binding example: from JS to C++

gl.drawElements(primitiveType, count, indexType, offset);
JSValue JSCanvasRenderingContext3D::glDrawElements(JSC::ExecState* exec, JSC::ArgList const& args)
{
    unsigned mode = args.at(0).toInt32(exec);
    unsigned type = args.at(1).toInt32(exec);

    unsigned int count = 0;

    // If the third param is not an object, it is a number, which is the count.
    // In this case if there is a 4th param, it is the offset. If there is no
    // 4th param, the offset is 0
    if (!args.at(2).isObject()) {
        count = args.at(2).toInt32(exec);
        unsigned int offset = (args.size() > 3) ? args.at(3).toInt32(exec) : 0;
        impl()->glDrawElements(mode, count, type, (void*) offset);
    } else {

Minimalistic WebGL program: to ensure that everything works fine
⚠️ no error checks, for clarity reasons (don't do this at home! 😉)

<!DOCTYPE html>
<html> 
<head>
    <meta charset='utf-8'>
</head>
<body>
    <canvas width='640' height='480'></canvas>
    <script>
        const canvas = document.querySelector('canvas');
        /** @type {WebGLRenderingContext} */
        const gl = canvas.getContext('webgl'); // instead of '2d'
        gl.clearColor(1., 0., 0., 1.); // RGBA: opaque red
        gl.clear(gl.COLOR_BUFFER_BIT); // uses current color (state machine)
    </script>
</body>
</html>

States

WebGL Textured Cube (HTML)

<canvas id="canvas"></canvas>
<!-- vertex shader -->
<script  id="vertex-shader-3d" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec2 a_texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
  // Multiply the position by the matrix.
  gl_Position = u_matrix * a_position;

  // Pass the texcoord to the fragment shader.
  v_texcoord = a_texcoord;
}
</script>
<!-- fragment shader -->
<script  id="fragment-shader-3d" type="x-shader/x-fragment">
precision mediump float;

// Passed in from the vertex shader.
varying vec2 v_texcoord;

// The texture.
uniform sampler2D u_texture;

void main() {
   gl_FragColor = texture2D(u_texture, v_texcoord);
}
</script><!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>

WebGL Textured Cube (JS, >250 lines)

"use strict";

function main() {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.querySelector("#canvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }

  // setup GLSL program
  var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);

  // look up where the vertex data needs to go.
  var positionLocation = gl.getAttribLocation(program, "a_position");
  var texcoordLocation = gl.getAttribLocation(program, "a_texcoord");

  // lookup uniforms
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");
  var textureLocation = gl.getUniformLocation(program, "u_texture");

  // Create a buffer for positions
  var positionBuffer = gl.createBuffer();
  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  // Put the positions in the buffer
  setGeometry(gl);

  // provide texture coordinates for the rectangle.
  var texcoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  // Set Texcoords.
  setTexcoords(gl);

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  // Fill the texture with a 4x4 LUMINANCE pixel.
  const level = 0;
  const format = gl.LUMINANCE;
  const type = gl.UNSIGNED_BYTE;
  const border = 0;
  const width = 4;
  const height = 4;
  const pixels = new Uint8Array([
    255, 128, 255, 128,
    128, 255, 128, 255,
    255, 128, 255, 128,
    128, 255, 128, 255,
  ]);
  gl.texImage2D(gl.TEXTURE_2D, level, format, width, height, border,
                format, type, pixels);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.generateMipmap(gl.TEXTURE_2D);

  function radToDeg(r) {
    return r * 180 / Math.PI;
  }

  function degToRad(d) {
    return d * Math.PI / 180;
  }

  var fieldOfViewRadians = degToRad(60);
  var modelXRotationRadians = degToRad(0);
  var modelYRotationRadians = degToRad(0);

  // Get the starting time.
  var then = 0;

  requestAnimationFrame(drawScene);

  // Draw the scene.
  function drawScene(time) {
    // convert to seconds
    time *= 0.001;
    // Subtract the previous time from the current time
    var deltaTime = time - then;
    // Remember the current time for the next frame.
    then = time;

    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);

    // Animate the rotation
    modelYRotationRadians += -0.7 * deltaTime;
    modelXRotationRadians += -0.4 * deltaTime;

    // Clear the canvas AND the depth buffer.
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Tell it to use our program (pair of shaders)
    gl.useProgram(program);

    // Turn on the position attribute
    gl.enableVertexAttribArray(positionLocation);

    // Bind the position buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
    var size = 3;          // 3 components per iteration
    var type = gl.FLOAT;   // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        positionLocation, size, type, normalize, stride, offset);

    // Turn on the texcoord attribute
    gl.enableVertexAttribArray(texcoordLocation);

    // bind the texcoord buffer.
    gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);

    // Tell the texcoord attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)
    var size = 2;          // 2 components per iteration
    var type = gl.FLOAT;   // the data is 32bit floats
    var normalize = false; // don't normalize the data
    var stride = 0;        // 0 = move forward size * sizeof(type) each iteration to get the next position
    var offset = 0;        // start at the beginning of the buffer
    gl.vertexAttribPointer(
        texcoordLocation, size, type, normalize, stride, offset);

    // Compute the projection matrix
    var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    var projectionMatrix =
        m4.perspective(fieldOfViewRadians, aspect, 1, 2000);

    var cameraPosition = [0, 0, 2];
    var up = [0, 1, 0];
    var target = [0, 0, 0];

    // Compute the camera's matrix using look at.
    var cameraMatrix = m4.lookAt(cameraPosition, target, up);

    // Make a view matrix from the camera matrix.
    var viewMatrix = m4.inverse(cameraMatrix);

    var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);

    var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
    matrix = m4.yRotate(matrix, modelYRotationRadians);

    // Set the matrix.
    gl.uniformMatrix4fv(matrixLocation, false, matrix);

    // Tell the shader to use texture unit 0 for u_texture
    gl.uniform1i(textureLocation, 0);

    // Draw the geometry.
    gl.drawArrays(gl.TRIANGLES, 0, 6 * 6);

    requestAnimationFrame(drawScene);
  }
}

// Fill the buffer with the values that define a cube.
function setGeometry(gl) {
  var positions = new Float32Array(
    [
    -0.5, -0.5,  -0.5,
    -0.5,  0.5,  -0.5,
     0.5, -0.5,  -0.5,
    -0.5,  0.5,  -0.5,
     0.5,  0.5,  -0.5,
     0.5, -0.5,  -0.5,

    -0.5, -0.5,   0.5,
     0.5, -0.5,   0.5,
    -0.5,  0.5,   0.5,
    -0.5,  0.5,   0.5,
     0.5, -0.5,   0.5,
     0.5,  0.5,   0.5,

    -0.5,   0.5, -0.5,
    -0.5,   0.5,  0.5,
     0.5,   0.5, -0.5,
    -0.5,   0.5,  0.5,
     0.5,   0.5,  0.5,
     0.5,   0.5, -0.5,

    -0.5,  -0.5, -0.5,
     0.5,  -0.5, -0.5,
    -0.5,  -0.5,  0.5,
    -0.5,  -0.5,  0.5,
     0.5,  -0.5, -0.5,
     0.5,  -0.5,  0.5,

    -0.5,  -0.5, -0.5,
    -0.5,  -0.5,  0.5,
    -0.5,   0.5, -0.5,
    -0.5,  -0.5,  0.5,
    -0.5,   0.5,  0.5,
    -0.5,   0.5, -0.5,

     0.5,  -0.5, -0.5,
     0.5,   0.5, -0.5,
     0.5,  -0.5,  0.5,
     0.5,  -0.5,  0.5,
     0.5,   0.5, -0.5,
     0.5,   0.5,  0.5,

    ]);
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}

// Fill the buffer with texture coordinates the cube.
function setTexcoords(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array(
        [
          0, 0,
          0, 1,
          1, 0,
          0, 1,
          1, 1,
          1, 0,

          0, 0,
          0, 1,
          1, 0,
          1, 0,
          0, 1,
          1, 1,

          0, 0,
          0, 1,
          1, 0,
          0, 1,
          1, 1,
          1, 0,

          0, 0,
          0, 1,
          1, 0,
          1, 0,
          0, 1,
          1, 1,

          0, 0,
          0, 1,
          1, 0,
          0, 1,
          1, 1,
          1, 0,

          0, 0,
          0, 1,
          1, 0,
          1, 0,
          0, 1,
          1, 1,

      ]),
      gl.STATIC_DRAW);
}

main();


Advanced WebGL

Check these excellent courses:

THREE.js

THREE.js

THREE.js

Interactive overview
by
David Lyons
https://davidlyons.dev/threejs-intro

THREE.js Manual

⭐ BEST Guide ⭐

https://threejs.org/manual/#en/fundamentals

Complete and simple guide

SceneGraph ⭐

  • we handle 3D objects instead of buffers
    • higher level, easier, more intuitive
  • each scene is organized as a hierarchy of objets
    • hence the term "scene graph"
  • allows to combine local transforms into global transforms
    • ex: solar system (see below), wheels of a car
  • rendering API abstraction
    • ex: seamless transition from WebGL to WebGPU
  • scene graph optimizations
    • batching
    • smart update of 3D objects

Solar system example

THREE.js Manual (excerpts)

Setup

How to run the examples locally

Setup

  • THREE.js is a library, NOT a standard API like WebGL
  • THREE.js abstracts WebGL 1, WebGL 2 and WebGPU
  • We need to import its modules before coding:
    • 1️⃣ either using CDN (Content Delivery Network)
      • zero setup: allows quick tests, without installation
    • 2️⃣ or through a full installation (via Node.js)
      • allows complete access to all resources, but introduces a complex toolchain (npm, webpack, rollup etc.)
    • ⚠️ zip download NOT recommended (complex dependencies)

1️⃣ Zero Setup: using a CDN

 <!DOCTYPE html>
 <html lang="en">
    
    <head>
        <title>three.js</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    </head>
    
    <style>
      html, body { margin: 0; padding: 0;  overflow: hidden; }
    </style>
    
    <body>

        <script type="importmap">
          {
            "imports": {
              "three": "https://cdn.jsdelivr.net/npm/three@.169.0/build/three.module.js",
              "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.169.0/examples/jsm/"
            }
          }
        </script>

        <script type="module">

            // Example of hard link to official repo for data, if needed
            const MODEL_PATH = 'https://raw.githubusercontent.com/mrdoob/three.js/r169/examples/models/gltf/LeePerrySmith/LeePerrySmith.glb';

            import * as THREE from 'three'

            import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
            import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

            // INSERT CODE HERE

        </script>
    </body>
</html>
const scene = new THREE.Scene();
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.PerspectiveCamera( 75, aspect, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshNormalMaterial();
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
camera.position.z = 5;

const render = () => {
  requestAnimationFrame( render );
  cube.rotation.x += 0.1;
  cube.rotation.y += 0.1;
  renderer.render( scene, camera );
};

render();

2️⃣ Full setup using NPM

sudo apt install nodejs
curl -L https://npmjs.org/install.sh | sudo sh 

See below 👇

Bundlers

Automatic installation

THREE.js with "batteries included" 🎉

THREE Vite boilerplate + = ❤️

Preconfigured environment (allows to test all official examples)

https://github.com/fdoganis/three_vite ⭐

git clone https://github.com/fdoganis/three_vite.git

cd three_vite

npm install

Run with npm run dev or use F5 in VS Code

Open http://localhost:5173 in your browser

Let's code!

Tools

  • Web development

    • Web browser (Firefox, Chrome, Safari Mobile)
    • Git
    • Code Editor (VSCode)
  • Technologies

    • HTML, JS, CSS
    • WebGL, THREE.js
    • WebXR

Install a browser (desktop)

  • Firefox installed by default
    • should be enough!
  • Chrome
    • to test compatibility and some features
    • alternative: install Chromium on Linux
      • open-source version without proprietary services
sudo apt-get install chromium-browser

Install Git

sudo apt-get install git

git config --global user.name "myusername"
git config --global user.email myname@mymailprovider.com

Install VSCode

sudo apt update
sudo apt install software-properties-common apt-transport-https wget

wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add -

sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main"

sudo apt install code

Remove GPG warnings

sudo gpgconf --kill dirmngr
sudo chown -R $USER:$USER ~/.gnupg

Customize VS Code

  • Avoid UI blinking by changing the settings:
set window.titleBarStyle to custom
  • File > Settings > Format on Save ⭐

Local development caveats

➡️ need to run a server, like Live Server, or using Python:

$ cd /home/somedir
$ python -m SimpleHTTPServer

$ python3 -m http.server

Then open http://localhost:8000 in your browser

Basic THREE.js concepts

Let's build a solar system

  • full tutorial here ⬅️
  • create a webpage with a canvas
  • create a scene with lights and meshes
    • understand object hierarchy
      • add vs attach
  • render the scene using a camera and renderer
  • animate the scene using requestAnimationFrame
    • or setAnimationLoop
  • see below 👇
0.15

THREE.js Editor

  • https://threejs.org/editor/ ⭐

    • basic 3D model edition
    • basic material edition
    • simple 3D format conversion
  • ➡️ use THREE.js editor to create a hierarchy

    • sun ☀️
    • earth 🌍
    • moon 🌔

WebXR

Setup

Install WebXR browser (mobile)

  • Android 🤖
    • install the latest mobile Chrome version (129+)
  • Meta Quest ♾🥽
    • use default Browser app
  • Apple Vision Pro 🍎 🥽
    • VR only, no AR 😢
  • iOS 🍎 📱
    • no official WebXR support 😭
    • alternatives below 👇 and here 💀

Install iOS WebXR browser

"caniuse"

WebXR
Report
⭐

Desktop WebXR Emulators

Mozilla WebXR Emulator

  • uses WebXR Polyfill
  • fake mobile AR device
  • very convenient when you don't have an AR device or for debugging
  • hand tracking (WIP)
  • NO LONGER ACTIVE forked by Meta 👇

Meta WebXR Emulator

source code

WebXR in Safari inside Apple Vision Pro Emulator

Demo

WebXR Concepts

WebXR Basic Concepts XR + AR ⭐

  • Tracking (spaces) and geometry of the real scene:
    • detect planes and geometry (point cloud or mesh) using SLAM, or similar technologies
  • XR Frame: RGB image + camera info (pose, focal, tracking, light)
  • Hit test intersection between a virtual ray and the real scene
    • frequent constraint: RGB camera + depth estimation
  • Anchors and worldmap :
    • points of interest placed by the used
    • updated continuously as the real world gets reconstructed

Advanced WebXR Concepts

  • Occlusion handling
    • human occlusion (ARKit)
    • real world occlusion (ARKit + LiDAR, ARCore)
  • Perception
    • of the environment (Vision, IA, LiDAR)
      • reconstruction + classification floor, wall, table
    • of the user
      • hand gestures, gaze, intentions

Standard APIs

Code examples : WebXR needs 3D

WebXR AR Module

API overview using pseudo-code

Security constraints 🚨

  • permissions
    • camera, location, movement
  • ⚠️ https mandatory
  • requires user action to start
    • AR / VR / XR Button to switch to AR

AR Initialization

isSessionSupported(‘immersive-ar’);

// RequestSession on Button press
navigator.xr.requestSession

// Add listener for ARButton Press

// Request reference spaces
localReferenceSpace = await session.requestReferenceSpace(‘local');
viewerReferenceSpace = await session.requestReferenceSpace(‘viewer');

// Request hitTest

session.requestHitTestSource

// RequestAnimationFrame
// NOTE: THREE.js must use
 renderer.setAnimationLoop 
 // instead of window.requestAnimationFrame
// Or else use session.requestAnimationFrame(render)

Draw

// On each Draw
// Callback on every draw, with an XRFrame

const render = (t, frame) => {

    const pose = frame.getViewerPose(localReferenceSpace);

    frame.getPose(localReferenceSpace, viewerReferenceSpace).transform.matrix

    const hitTestResults = frame.getHitTestResults( hitTestSource );
    const hit = hitTestResults[ 0 ];
    reticle.matrix.fromArray( hit.getPose(viewerReferenceSpace ).transform.matrix );

}

Selection (onTouch)

Example: https://github.com/mrdoob/three.js/blob/master/examples/webxr_ar_cones.html

// Get hand, controller, or phone
controller = renderer.xr.getController( 0 ); 

// See also selectstart, selectend, squeeze etc.
controller.addEventListener( 'select', onSelect );

scene.add( controller );

// Before rendering, update the controller, and apply position to mesh (in meters)
mesh.position.set( 0, 0, - 0.3 ).applyMatrix4( controller.matrixWorld );

Let's Code!

WebXR + THREE.js ⭐

Creating your own QR code

Build: reminders

Replace the cone with another model

Add XR to your THREE.js project! ⭐

Port your THREE.js project to use the real world!

  • start with THREE.js' webxr_ar_cones example above
  • replace the cone creation with your solar system
    • create the solar system only once
    • change its position if it has already been created

Challenges 💪

The End!

Extras

Alternative AR Libraries

Vision

Courses

https://medium.com/sopra-steria-norge/get-started-with-augmented-reality-on-the-web-using-three-js-and-webxr-part-1-8b07757fc23a ⭐

https://codelabs.developers.google.com/ar-with-webxr#0

https://medium.com/arjs/webar-playground-ar-in-a-few-clicks-67a08cfb1534

https://blog.halolabs.io/building-ar-vr-with-javascript-and-html-97af4434bcf6

Guidesx

https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/augmented-reality/

Code links : Browser-based AR and VR

http://webglworkshop.com/presentations/Workshop31-ar-vr.html#/21

https://webxr.io/webar-playground/

http://learningthreejs.com/blog/2015/07/16/hatsune-miku-dancing-in-augmented-reality/

http://studioknol.com/phase-two-building-with-virtuality/

https://github.com/rodrigocam/ar-gif
https://github.com/XingMeansOK/slamjs_samples (RGBD)

Mozilla's (deprecated) XR Viewer (1)

Mozilla's (deprecated) XR Viewer (2)

  • DO NOT use URLs with iframes!

For example:

https://threejs.org/examples/?q=cones#webxr_ar_cones

will NOT work (you will get a confusing "WebXR not available" message on your XR Button)

Use this URL instead:

https://threejs.org/examples/webxr_ar_cones

Mozilla's (deprecated) XR Viewer (3)

  • DO NOT use Dark Mode, otherwise all the colors in your 3D graphics will look inverted
    • you can force XRViewer to always use the light theme
      • check the in-app settings

Mozilla's (deprecated) XR Viewer (4)

  • Remember to clear your cache frequently if you are developing an app and you are not seeing your code changes
    • check Data Management from the in-app settings

Mozilla's (deprecated) XR Viewer (5)

Settings / XRViewer / WebXR Polyfill URL : https://arenaxr.org/webxrios.js

URL copied from Anthony Rowes' "XR Browser" (maintained!) app

**************************

See Readme / TODO

**************************

WebGL stack

![bg right](https://threejs.org/manual/resources/images/threejs-structure.svg)

# Source code https://aframe.io/blog/webxr-ar-module/ https://glitch.com/edit/#!/xr-spinosaurus ``` <a-scene> <a-assets timeout="30000"> <a-asset-item id="spinosaurus" src="dino-model.glb"></a-asset-item> </a-assets> <a-camera position="0 1.2 0"></a-camera> <a-entity id="dino" position="-1 0 -3" scale="0.5 0.5 0.5"> <a-entity position="0 2.15 0" rotation="0 55 0" gltf-model="#spinosaurus" animation-mixer shadow="cast: true; receive: false"></a-entity> </a-entity> </a-scene> ``` ---