@fischaelameergeildanke.com #JSCamp Writing WebXR Apps 
 with Web Technology
React 360
WebXR Device API React 360
UX Design for WebXR WebXR Device API React 360
WebXR
WebXR WebVR
WebXR WebVR WebAR
WebXR WebVR WebAR
Created by Laura Hernández from the Noun Project
Created by Laura Hernández from the Noun Project
Completely DigitalVirtual Reality
Virtual Overlay Augmented Reality:
Virtual and Real World Coincide Mixed Reality
WebXR WebVR WebAR
WebXR Device API Detect AR/VR Devices !11
WebXR Device API Detect AR/VR Devices !11 Get Device’s Capabilities
WebXR Device API Detect AR/VR Devices !11 Get Device’s Capabilities Get Device’s Orientation/Position
WebXR Device API Detect AR/VR Devices !11 Get Device’s Capabilities Get Device’s Orientation/Position Display Images With A Fitting Frame Rate
WebVR API
WebVR API WebXR Device API
WebXR Device API Supports Augmented Reality !13
WebXR Device API Supports Augmented Reality !13 Clean, Consistent, Predictable
WebXR Device API Supports Augmented Reality !13 Clean, Consistent, Predictable Better Browser Optimizations
WebXR Device API Supports Augmented Reality !13 Clean, Consistent, Predictable Better Browser Optimizations Unified Input Model
Lifetime of a WebXR App Request XR Device
Lifetime of a WebXR App Request XR Device Reveal XR Functionality
Lifetime of a WebXR App Request XR Device Reveal XR Functionality Request XR Session
Lifetime of a WebXR App Request XR Device Reveal XR Functionality Request XR Session Run Render Loop
Request a XR Devices navigator.xr.requestDevice().then(device => { if (device) { handleXRAvailable(device); } }).catch(error => console.error('Unable to request an XR device: ', error); });
Request a XR Devices navigator.xr.requestDevice().then(device => { if (device) { handleXRAvailable(device); } }).catch(error => console.error('Unable to request an XR device: ', error); });
Check XR Session Support let xrDevice = null; function handleXRAvailable(device) { xrDevice = device; xrDevice.supportsSession({exclusive: true}).then(() => { addWebXRButtonToPage(); }).catch((error) => { console.log('Session not supported: ' + error); }); }
Check XR Session Support let xrDevice = null; function handleXRAvailable(device) { xrDevice = device; xrDevice.supportsSession({exclusive: true}).then(() => { addWebXRButtonToPage(); }).catch((error) => { console.log('Session not supported: ' + error); }); }
Request a XR Session function beginXRSession() { let canvas = document.createElement('canvas'); let context = canvas.getContext('xrpresent'); document.body.appendChild(canvas); xrDevice.requestSession({exclusive: true, outputContext: context}) .then(onSessionStarted) .catch((error) => {console.log('requestSession failed: ' + error);}); }
Start a XR Session let xrSession = null; let xrFrameOfReference = null; function onSessionStarted(session) { xrSession = session; xrSession.requestFrameOfReference('eyeLevel') .then((frameOfReference) => {xrFrameOfReference = frameOfReference;}) .then(setupWebGLLayer) .then(() => {xrSession.requestAnimationFrame(onRenderFrame);}); }
Start a XR Session let xrSession = null; let xrFrameOfReference = null; function onSessionStarted(session) { xrSession = session; xrSession.requestFrameOfReference('eyeLevel') .then((frameOfReference) => {xrFrameOfReference = frameOfReference;}) .then(setupWebGLLayer) .then(() => {xrSession.requestAnimationFrame(onRenderFrame);}); }
Start a XR Session let xrSession = null; let xrFrameOfReference = null; function onSessionStarted(session) { xrSession = session; xrSession.requestFrameOfReference('eyeLevel') .then((frameOfReference) => {xrFrameOfReference = frameOfReference;}) .then(setupWebGLLayer) .then(() => {xrSession.requestAnimationFrame(onRenderFrame);}); }
Setup an XRLayer let glCanvas = document.createElement('canvas'); let glContext = glCanvas.getContext('webgl'); function setupWebGLLayer() { return glContext.setCompatibleXRDevice(xrDevice).then(() => { xrSession.baseLayer = new XRWebGLLayer(xrSession, glContext); }); }
Setup an XRLayer let glCanvas = document.createElement('canvas'); let glContext = glCanvas.getContext('webgl'); function setupWebGLLayer() { return glContext.setCompatibleXRDevice(xrDevice).then(() => { xrSession.baseLayer = new XRWebGLLayer(xrSession, glContext); }); }
Start a XR Session let xrSession = null; let xrFrameOfReference = null; function onSessionStarted(session) { xrSession = session; xrSession.requestFrameOfReference('eyeLevel') .then((frameOfReference) => {xrFrameOfReference = frameOfReference;}) .then(setupWebGLLayer) .then(() => {xrSession.requestAnimationFrame(onRenderFrame);}); }
Start the Render Loop function onRenderFrame(timestamp, xrFrame) { let pose = xrFrame.getDevicePose(xrFrameOfReference); if (pose) { for (let view of xrFrame.views) { // Draw something } } // Input device code xrSession.requestAnimationFrame(onRenderFrame); }
Start the Render Loop function onRenderFrame(timestamp, xrFrame) { let pose = xrFrame.getDevicePose(xrFrameOfReference); if (pose) { for (let view of xrFrame.views) { // Draw something } } // Input device code xrSession.requestAnimationFrame(onRenderFrame); }
Start the Render Loop function onRenderFrame(timestamp, xrFrame) { let pose = xrFrame.getDevicePose(xrFrameOfReference); if (pose) { for (let view of xrFrame.views) { // Draw something } } // Input device code xrSession.requestAnimationFrame(onRenderFrame); }
Start the Render Loop function onRenderFrame(timestamp, xrFrame) { let pose = xrFrame.getDevicePose(xrFrameOfReference); if (pose) { for (let view of xrFrame.views) { // Draw something } } // Input device code xrSession.requestAnimationFrame(onRenderFrame); }
Exit the WebXR Session function endXRSession() { if (xrSession) {xrSession.end().then(onSessionEnd);} } function onSessionEnd() { xrSession = null; window.requestAnimationFrame(onDrawFrame); }
Exit the WebXR Session function endXRSession() { if (xrSession) {xrSession.end().then(onSessionEnd);} } function onSessionEnd() { xrSession = null; window.requestAnimationFrame(onDrawFrame); }
Fallback: Magic Window let mwCanvas = document.createElement('canvas'); let mwContext = mwCanvas.getContext('xrpresent'); document.body.appendChild(mwCanvas); function beginMagicWindowXRSession() { xrDevice.requestSession({exclusive: false, outputContext: mwContext}) .then(OnSessionStarted) .catch((error) => {console.log('requestSession failed: ' + error);}); }
Fallback: Magic Window let mwCanvas = document.createElement('canvas'); let mwContext = mwCanvas.getContext('xrpresent'); document.body.appendChild(mwCanvas); function beginMagicWindowXRSession() { xrDevice.requestSession({exclusive: false, outputContext: mwContext}) .then(OnSessionStarted) .catch((error) => {console.log('requestSession failed: ' + error);}); }
On Page Load: Magic Window
On Page Load: Magic Window exclusive sessions are supported
On Page Load: Magic Window exclusive sessions are supported Render a "Start VR" Button
6DOF VR Headset Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
6DOF VR Headset AR-ready Smartphone Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
6DOF VR Headset AR-ready Smartphone 3DOF VR Headset Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
Progressive Enhancement 6DOF VR Headset AR-ready Smartphone 3DOF VR Headset Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
Progressive Enhancement 6DOF VR Headset AR-ready Smartphone 3DOF VR Headset WebXR Polyfill Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
Progressive Enhancement 6DOF VR Headset AR-ready Smartphone 3DOF VR Headset Magic Window WebXR Polyfill Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
Progressive Enhancement 6DOF VR Headset AR-ready Smartphone 3DOF VR Headset Magic Window Gyroscope WebXR Polyfill Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
Progressive Enhancement 6DOF VR Headset AR-ready Smartphone 3DOF VR Headset Magic Window Gyroscope WebXR Polyfill Static Image Created by Hans Gerhard Meier, Bence Bezeredy, Laura Hernández, Anil, Sachin Modgekar, Ben Davis from the Noun Project
The Future: Augmented Reality AR Session
The Future: Augmented Reality AR Session Hit Test
The Future: Augmented Reality AR Session Hit Test AR Anchors
React 360 https://facebook.github.io/react-360/
React 360 https://aframe.io/ A-Frame
React 360 Full-Sphere Gallery
$ npm install -g react-360-cli React 360 Full-Sphere Gallery
$ npm install -g react-360-cli $ react-360 init HelloJSCampBarcelona React 360 Full-Sphere Gallery
$ npm install -g react-360-cli $ react-360 init HelloJSCampBarcelona $ cd HelloJSCampBarcelona React 360 Full-Sphere Gallery
$ npm install -g react-360-cli $ react-360 init HelloJSCampBarcelona $ cd HelloJSCampBarcelona $ npm start React 360 Full-Sphere Gallery
$ npm install -g react-360-cli $ react-360 init HelloJSCampBarcelona $ cd HelloJSCampBarcelona $ npm start React 360 Full-Sphere Gallery http://localhost:8081/index.html
React 360 Application
React 360 Application React 360 Runtime
React 360 Application React 360 Runtime Renders 3D Objects Keeps A High Frame Rate Web Worker Environment
. ├── __tests__ ├── client.js ├── index.html ├── index.js ├── node_modules ├── package.json ├── rn-cli.config.js └── static_assets React 360 Full-Sphere Gallery
. ├── __tests__ ├── client.js ├── index.html ├── index.js ├── node_modules ├── package.json ├── rn-cli.config.js └── static_assets React 360 Full-Sphere Gallery
. ├── __tests__ ├── client.js ├── index.html ├── index.js ├── node_modules ├── package.json ├── rn-cli.config.js └── static_assets React 360 Full-Sphere Gallery
. ├── __tests__ ├── client.js ├── index.html ├── index.js ├── node_modules ├── package.json ├── rn-cli.config.js └── static_assets React 360 Full-Sphere Gallery
<body> <div id="container"></div> <script src="./client.bundle?platform=vr"></script> <script> React360.init( 'index.bundle?platform=vr&dev=true', document.getElementById('container') ); </script> </body> index.html
<body> <div id="container"></div> <script src="./client.bundle?platform=vr"></script> <script> React360.init( 'index.bundle?platform=vr&dev=true', document.getElementById('container') ); </script> </body> index.html
<body> <div id="container"></div> <script src="./client.bundle?platform=vr"></script> <script> React360.init( 'index.bundle?platform=vr&dev=true', document.getElementById('container') ); </script> </body> index.html
client.js import { ReactInstance } from 'react-360-web' function init(bundle, parent, options = {}) { const r360 = new ReactInstance(bundle, parent, 
 { fullScreen: true, …options }) r360.renderToSurface( r360.createRoot('HelloJSCampBarcelona', {}), r360.getDefaultSurface() ) r360.compositor.setBackground(r360.getAssetURL(‘360_world.jpg')) } window.React360 = { init }
client.js import { ReactInstance } from 'react-360-web' function init(bundle, parent, options = {}) { const r360 = new ReactInstance(bundle, parent, 
 { fullScreen: true, …options }) r360.renderToSurface( r360.createRoot('HelloJSCampBarcelona', {}), r360.getDefaultSurface() ) r360.compositor.setBackground(r360.getAssetURL(‘360_world.jpg')) } window.React360 = { init }
client.js import { ReactInstance } from 'react-360-web' function init(bundle, parent, options = {}) { const r360 = new ReactInstance(bundle, parent, 
 { fullScreen: true, …options }) r360.renderToSurface( r360.createRoot('HelloJSCampBarcelona', {}), r360.getDefaultSurface() ) r360.compositor.setBackground(r360.getAssetURL(‘360_world.jpg')) } window.React360 = { init }
client.js import { ReactInstance } from 'react-360-web' function init(bundle, parent, options = {}) { const r360 = new ReactInstance(bundle, parent, 
 { fullScreen: true, …options }) r360.renderToSurface( r360.createRoot('HelloJSCampBarcelona', {}), r360.getDefaultSurface() ) r360.compositor.setBackground(r360.getAssetURL(‘360_world.jpg')) } window.React360 = { init }
client.js import { ReactInstance } from 'react-360-web' function init(bundle, parent, options = {}) { const r360 = new ReactInstance(bundle, parent, 
 { fullScreen: true, …options }) r360.renderToSurface( r360.createRoot('HelloJSCampBarcelona', {}), r360.getDefaultSurface() ) r360.compositor.setBackground(r360.getAssetURL(‘360_world.jpg')) } window.React360 = { init }
index.js export default class HelloJSCampBarcelona extends React.Component { render() { return ( <View style={styles.panel}> <View style={styles.greetingBox}> <Text style={styles.greeting}>Welcome to React 360</Text> </View> </View> ) } } AppRegistry.registerComponent('HelloJSCampBarcelona', () => HelloChainReactPortland)
client.js … r360.renderToSurface( r360.createRoot('HelloJSCampBarcelona', { photos: [ { uri: './static_assets/1.jpg', title: 'Boat', format: '2D' }, { uri: './static_assets/2.jpg', title: 'Beach', format: '2D' }, { uri: './static_assets/3.jpg', title: 'OnsieJS', format: '2D' }, ], }), r360.getDefaultSurface(), ) …
index.js <View style={styles.wrapper}> <Background uri={current.uri} format={current.format} /> <View style={styles.controls}> <VrButton onClick={this._prevPhoto} style={styles.button}> <Text style={styles.buttonText}>{'<'}</Text> </VrButton> <View><Text style={styles.title}>{current.title}</Text></View> <VrButton onClick={this._nextPhoto} style={styles.button}> <Text style={styles.buttonText}>{'>'}</Text> </VrButton> </View> </View>
index.js <View style={styles.wrapper}> <Background uri={current.uri} format={current.format} /> <View style={styles.controls}> <VrButton onClick={this._prevPhoto} style={styles.button}> <Text style={styles.buttonText}>{'<'}</Text> </VrButton> <View><Text style={styles.title}>{current.title}</Text></View> <VrButton onClick={this._nextPhoto} style={styles.button}> <Text style={styles.buttonText}>{'>'}</Text> </VrButton> </View> </View>
index.js class Background extends React.Component { constructor(props) { super() Environment.setBackgroundImage(props.uri, {format: props.format}) } componentWillReceiveProps(nxtPrps) { if (nxtPrps.uri !== this.props.uri || nxtPrps.format !== this.props.format) { Environment.setBackgroundImage(nxtPrps.uri, {format: nxtPrps.format}) } } render() { return null } }
index.js state = { index: 0 } _prevPhoto = () => { let next = this.state.index - 1; if (next < 0) { next += this.props.photos.length } this.setState({ index: next }) } _nextPhoto = () => { this.setState({ index: this.state.index + 1 }) }
index.js render() { const current = this.props.photos[ this.state.index % this.props.photos.length ] return ( <View style={styles.wrapper}> <Background uri={current.uri} format={current.format} /> … ) }
UX Design for WebXR Apps
Do Not Apply 2D Patterns
No Established Patterns Yet
UX Design Best-Practises Add Feedback, Respond Immediately
Add Feedback
Add Feedback
Add Feedback
Add Feedback
It was the pioneer days; people had to make their own interrogation rooms. Out of cornmeal. These endless days are finally ending in a blaze. When I say, 'I love you,' it's not because I want you or because I can't have you. It's my estimation that every man ever got a statue made of him was one kind of sommbitch or another. Oh my god you will never believe what happened at school today. From beneath you, it devours. I am never gonna see a merman, ever. It was supposed to confuse him, but it just made him peppy. It was like the Heimlich, with stripes! How did your brain even learn human speech? I'm just so curious. Apocalypse, we've all been there; the same old trips, why should we care? Frankly, it's ludicrous to have these interlocking bodies and not...interlock. I just don't see why everyone's always picking on Marie-Antoinette. You're the one freaky thing in my freaky world that still makes sense to me. You are talking crazy-person talk.
It was the pioneer days; people had to make their own interrogation rooms. Out of cornmeal. These endless days are finally ending in a blaze. When I say, 'I love you,' it's not because I want you or because I can't have you. It's my estimation that every man ever got a statue made of him was one kind of sommbitch or another. Oh my god you will never believe what happened at school today. From beneath you, it devours. I am never gonna see a merman, ever. It was supposed to confuse him, but it just made him peppy. It was like the Heimlich, with stripes! How did your brain even learn human speech? I'm just so curious. Apocalypse, we've all been there; the same old trips, why should we care? Frankly, it's ludicrous to have these interlocking bodies and not...interlock. I just don't see why everyone's always picking on Marie-Antoinette. You're the one freaky thing in my freaky world that still makes sense to me. You are talking crazy-person talk.
It was the pioneer days; people had to make their own interrogation rooms. Out of cornmeal. These endless days are finally ending in a blaze. When I say, 'I love you,' it's not because I want you or because I can't have you. It's my estimation that every man ever got a statue made of him was one kind of sommbitch or another. Oh my god you will never believe what happened at school today. From beneath you, it devours. I am never gonna see a merman, ever. It was supposed to confuse him, but it just made him peppy. It was like the Heimlich, with stripes! How did your brain even learn human speech? I'm just so curious. Apocalypse, we've all been there; the same old trips, why should we care? Frankly, it's ludicrous to have these interlocking bodies and not...interlock. I just don't see why everyone's always picking on Marie-Antoinette. You're the one freaky thing in my freaky world that still makes sense to me. You are talking crazy-person talk.
UX Design Best-Practises Add Feedback, Respond Immediately Guide Users with Gaze Cues
Add Gaze Cues
Add Gaze Cues
Add Gaze Cues
Add Gaze Cues
UX Design Best-Practises Add Feedback, Respond Immediately Guide Users with Gaze Cues Provide Information in Context
DoDon’t
Interpretability Usefulness Delight Beau Cronin https://medium.com/@beaucronin/the-hierarchy-of-needs-in-virtual-reality-development-4333a4833acc Comfort
Presence Interpretability Usefulness Delight Beau Cronin https://medium.com/@beaucronin/the-hierarchy-of-needs-in-virtual-reality-development-4333a4833acc Comfort
Presence Interpretability Usefulness Delight Beau Cronin Comfort & Safety https://medium.com/@beaucronin/the-hierarchy-of-needs-in-virtual-reality-development-4333a4833acc
Comfort and Safety in AR There is No Optimal AR Environment
Comfort and Safety in AR There is No Optimal AR Environment Consider a User’s Movement
https://giphy.com/gifs/hyperrpg-vr-ouch-3oKIPAKenYskqXzYnC
https://giphy.com/gifs/hyperrpg-vr-ouch-3oKIPAKenYskqXzYnC
Comfort and Safety in AR There is No Optimal AR Environment Consider a User’s Movement Avoid Fatiguing your Users
Comfort and Safety in VR Do Not Trigger Phobias
Comfort and Safety in VR Do Not Trigger Phobias Do Not Move Things Fast Towards the Camera
Comfort and Safety in VR Do Not Trigger Phobias Do Not Move Things Fast Towards the Camera Respect a User’s Safe Space
https://www.reddit.com/r/VRFail/comments/4s7nc1/friend_loses_his_vrginity_and_then_some_crappy/
https://www.reddit.com/r/VRFail/comments/4s7nc1/friend_loses_his_vrginity_and_then_some_crappy/
https://www.techradar.com/
https://www.reddit.com/r/VRFail/comments/4p9zgj/pool_shot/
https://www.reddit.com/r/VRFail/comments/4p9zgj/pool_shot/
Prevent Simulation Sickness Do Not Move the Horizon or the Camera
Prevent Simulation Sickness Do Not Move the Horizon or the Camera Do Not Use Acceleration
https://web.colby.edu/cogblog/2016/05/09/2451/
https://web.colby.edu/cogblog/2016/05/09/2451/
Prevent Simulation Sickness Do Not Move the Horizon or the Camera Do Not Use Acceleration Avoid Flicker and Blur
Prevent Simulation Sickness Do Not Move the Horizon or the Camera Do Not Use Acceleration Avoid Flicker and Blur Add a Stable Focus Point
@fischaelameergeildanke.com #JSCamp Respect Your Users! Test Your Product on a Diverse Audience.
@fischaelameergeildanke.com #JSCamp Get Involved! https://github.com/immersive-web/webxr https://github.com/facebook/react-360 https://github.com/mozilla/aframe-xr

WebXR: A New Dimension For The Web Writing Virtual and Augmented Reality Apps With Web Technology