Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

Web,
meet VR

Dominique Hazael‑Massieux

dom@w3.org

@dontcallmeDOM

PWA FTW!

A dystopian view of VR
Using VR to help shopping
VR reporting on Syria war at the New York Times
Some of the VR headsets on the market

VR Platform

WebGL
WebVR
Web Audio API
Gamepad API

      (function () {
      "use strict";
      var PLAYER_HEIGHT = 1.65;
      var vrDisplay = null;
      var projectionMat = mat4.create();
      var viewMat = mat4.create();
      var poseMat = mat4.create();
      var gamepadMat = mat4.create();
      var gamepadColor = vec4.create();
      var standingPosition = vec3.create();
      var vrPresentButton = null;
      var orientation = [0, 0, 0, 1];
      var position = [0, 0, 0];
      // ===================================================
      // WebGL scene setup. This code is not WebVR specific.
      // ===================================================
      // WebGL setup.
      var webglCanvas = document.getElementById("webgl-canvas");
      var gl = null;
      var cubeIsland = null;
      var stats = null;
      var debugGeom = null;
      function initWebGL (preserveDrawingBuffer) {
        var glAttribs = {
          alpha: false,
          antialias: !VRSamplesUtil.isMobile(),
          preserveDrawingBuffer: preserveDrawingBuffer
        };
        gl = webglCanvas.getContext("webgl", glAttribs);
        gl.clearColor(0.1, 0.2, 0.3, 1.0);
        gl.enable(gl.DEPTH_TEST);
        gl.enable(gl.CULL_FACE);
        var textureLoader = new WGLUTextureLoader(gl);
        var texture = textureLoader.loadTexture("media/textures/cube-sea.png");
        cubeIsland = new VRCubeIsland(gl, texture, 2, 2);
        stats = new WGLUStats(gl);
        debugGeom = new WGLUDebugGeometry(gl);
        // Wait until we have a WebGL context to resize and start rendering.
        window.addEventListener("resize", onResize, false);
        onResize();
        window.requestAnimationFrame(onAnimationFrame);
      }
      // ================================
      // WebVR-specific code begins here.
      // ================================
      function onVRRequestPresent () {
        vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () {
        }, function () {
          VRSamplesUtil.addError("requestPresent failed.", 2000);
        });
      }
      function onVRExitPresent () {
        if (!vrDisplay.isPresenting)
          return;
        vrDisplay.exitPresent().then(function () {
        }, function () {
          VRSamplesUtil.addError("exitPresent failed.", 2000);
        });
      }
      function onVRPresentChange () {
        onResize();
        if (vrDisplay.isPresenting) {
          if (vrDisplay.capabilities.hasExternalDisplay) {
            VRSamplesUtil.removeButton(vrPresentButton);
            vrPresentButton = VRSamplesUtil.addButton("Exit VR", "E", "media/icons/cardboard64.png", onVRExitPresent);
          }
        } else {
          if (vrDisplay.capabilities.hasExternalDisplay) {
            VRSamplesUtil.removeButton(vrPresentButton);
            vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);
          }
        }
      }
      if (navigator.getVRDisplays) {
        navigator.getVRDisplays().then(function (displays) {
          if (displays.length > 0) {
            vrDisplay = displays[0];
            vrDisplay.depthNear = 0.1;
            vrDisplay.depthFar = 1024.0;
            initWebGL(true);
            if (vrDisplay.stageParameters &&
                vrDisplay.stageParameters.sizeX > 0 &&
                vrDisplay.stageParameters.sizeZ > 0) {
              cubeIsland.resize(vrDisplay.stageParameters.sizeX, vrDisplay.stageParameters.sizeZ);
            }
            VRSamplesUtil.addButton("Reset Pose", "R", null, function () { vrDisplay.resetPose(); });
            if (vrDisplay.capabilities.canPresent)
              vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);
            window.addEventListener('vrdisplaypresentchange', onVRPresentChange, false);
            window.addEventListener('vrdisplayactivate', onVRRequestPresent, false);
            window.addEventListener('vrdisplaydeactivate', onVRExitPresent, false);
          } else {
            initWebGL(false);
            VRSamplesUtil.addInfo("WebVR supported, but no VRDisplays found.", 3000);
          }
        });
      } else if (navigator.getVRDevices) {
        initWebGL(false);
        VRSamplesUtil.addError("Your browser supports WebVR but not the latest version. See <a href='http://webvr.info'>webvr.info</a> for more info.");
      } else {
        initWebGL(false);
        VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance.");
      }
      function onResize () {
        if (vrDisplay && vrDisplay.isPresenting) {
          var leftEye = vrDisplay.getEyeParameters("left");
          var rightEye = vrDisplay.getEyeParameters("right");
          webglCanvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2;
          webglCanvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight);
        } else {
          webglCanvas.width = webglCanvas.offsetWidth * window.devicePixelRatio;
          webglCanvas.height = webglCanvas.offsetHeight * window.devicePixelRatio;
        }
      }
      function getStandingViewMatrix (out, view) {
        if (vrDisplay.stageParameters) {
          mat4.invert(out, vrDisplay.stageParameters.sittingToStandingTransform);
          mat4.multiply(out, view, out);
        } else {
          mat4.identity(out);
          mat4.translate(out, out, [0, PLAYER_HEIGHT, 0]);
          mat4.invert(out, out);
          mat4.multiply(out, view, out);
        }
      }
      function getPoseMatrix (out, pose, isGamepad) {
        orientation = pose.orientation;
        position = pose.position;
        if (!orientation) { orientation = [0, 0, 0, 1]; }
        if (!position) {
          // If this is a gamepad without a pose set it out in front of us so
          // we can see it.
          position = isGamepad ? [0.1, -0.1, -0.5] : [0, 0, 0];
        }
        if (vrDisplay.stageParameters) {
          mat4.fromRotationTranslation(out, orientation, position);
          mat4.multiply(out, vrDisplay.stageParameters.sittingToStandingTransform, out);
        } else {
          vec3.add(standingPosition, position, [0, PLAYER_HEIGHT, 0]);
          mat4.fromRotationTranslation(out, orientation, standingPosition);
        }
      }
      function renderSceneView (projection, view, gamepads) {
        cubeIsland.render(projection, view, stats);
        debugGeom.bind(projection, view);
        // Render every gamepad with a pose we found
        for (var i = 0; i < gamepads.length; ++i) {
          var gamepad = gamepads[i];
          // Because this sample is done in standing space we need to apply
          // the same transformation to the gamepad pose as we did the
          // VRDisplay's pose.
          getPoseMatrix(gamepadMat, gamepad.pose, true);
          // Scaled down to from 1 meter to be something closer to the size of
          // a hand.
          mat4.scale(gamepadMat, gamepadMat, [0.1, 0.1, 0.1]);
          // Loop through all the gamepad's axes and rotate the cube by their
          // value.
          for (var j = 0; j < gamepad.axes.length; ++j) {
            switch (j%3) {
              case 0:
                mat4.rotateX(gamepadMat, gamepadMat, gamepad.axes[j] * Math.PI);
                break;
              case 1:
                mat4.rotateY(gamepadMat, gamepadMat, gamepad.axes[j] * Math.PI);
                break;
              case 2:
                mat4.rotateZ(gamepadMat, gamepadMat, gamepad.axes[j] * Math.PI);
                break;
            }
          }
          // Show the gamepad's cube as red if any buttons are pressed, blue
          // otherwise.
          vec4.set(gamepadColor, 0, 0, 1, 1);
          for (var j = 0; j < gamepad.buttons.length; ++j) {
            if (gamepad.buttons[j].pressed) {
              vec4.set(gamepadColor, gamepad.buttons[j].value, 0, 0, 1);
              break;
            }
          }
          debugGeom.drawBoxWithMatrix(gamepadMat, gamepadColor);
        }
      }
      var frameData = new VRFrameData();
      function onAnimationFrame (t) {
        stats.begin();
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        if (vrDisplay) {
          vrDisplay.requestAnimationFrame(onAnimationFrame);
          // Loop over every gamepad and if we find any that have a pose use it.
          var vrGamepads = [];
          var gamepads = navigator.getGamepads();
          for (var i = 0; i < gamepads.length; ++i) {
            var gamepad = gamepads[i];
            // The array may contain undefined gamepads, so check for that as
            // well as a non-null pose.
            if (gamepad) {
              if (gamepad.pose)
                vrGamepads.push(gamepad);
              if ("haptics" in gamepad && gamepad.haptics.length > 0) {
                for (var j = 0; j < gamepad.buttons.length; ++j) {
                  if (gamepad.buttons[j].pressed) {
                    // Vibrate the gamepad using to the value of the button as
                    // the vibration intensity.
                    gamepad.haptics[0].vibrate(gamepad.buttons[j].value, 100);
                    break;
                  }
                }
              }
            }
          }
          vrDisplay.getFrameData(frameData);
          if (vrDisplay.isPresenting) {
            gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height);
            getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
            renderSceneView(frameData.leftProjectionMatrix, viewMat, vrGamepads);
            gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height);
            getStandingViewMatrix(viewMat, frameData.rightViewMatrix);
            renderSceneView(frameData.rightProjectionMatrix, viewMat, vrGamepads);
            vrDisplay.submitFrame();
          } else {
            gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
            mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
            getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
            renderSceneView(projectionMat, viewMat, frameData.pose);
            stats.renderOrtho();
          }
        } else {
          window.requestAnimationFrame(onAnimationFrame);
          // No VRDisplay found.
          gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
          mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
          mat4.identity(viewMat);
          mat4.translate(viewMat, viewMat, [0, -PLAYER_HEIGHT, 0]);
          cubeIsland.render(projectionMat, viewMat, stats);
          stats.renderOrtho();
        }
        stats.end();
      }
      })();
// https://github.com/toji/webvr-samples/blob/master/XX-vr-controllers.html

A-Frame


  <a-scene>
     <a-sphere position="0 1.25 -1"
                  radius="1.25" color="#EF2D5E">
     </a-sphere>
     <a-plane rotation="-90 0 0" width="4" height="4" 
                 color="#7BC8A4"></a-plane>
     <a-sky color="#ECECEC"></a-sky>
  </a-scene>
      
Example of an AR frame scene
3D camera & computer vision

More…

  • 360° HTML media integration
  • 3D real-time streaming
  • Performances
  • AR…

aframe.io

WebVR Community

w3.org/community/webvr

Web & VR workshop

19-20 October, San Jose, CA - register by Sep 23

Get involved!

W3C is a complex organization

HELP!

Help me
help you
help us
help you

Thanks - @dontcallmeDOM dom@w3.org

Use a spacebar or arrow keys to navigate