TL;DR: One of the greatest moments for the web is when an API emerges and brings the ability to create an application on the web (with web technologies) that was only possible to develop natively. The emergence of the WebVR API is one of such fireworks moment that opens a whole new world of exciting possibilities. The WebVR API empowers web developers to build virtual-reality experiences with web technologies and also enable users to have that fully immersed experience using just their browser.

This article takes you through a small project built with WebVR. Although you won't be going into details on the basics, it will help you to understand all the moving parts and how everything fits together to create amazing virtual reality scenes.

You can find the whole code for this application in this GitHub repository and a live sample over here.

Name: Any name to identify your application. Something like "WebVR Tutorial" will do.

Choose an application type: Select Single Page Web Applications here.

After filling in this form just hit the Create button. When done, Auth0 will redirect you to the Quick Start section of your new Auth0 Application. From there, click on the Settings tab. This will open a form with a bunch of fields. At this moment, you will only need to do one thing: add http://localhost:8080/ to the Allowed Callback URLs. Just don't forget to hit the Save button after making this change (you can also hit Ctrl + S or ⌘ + S).

Creating the WebVR Application

Now that you already have your Auth0 account properly configured, it is time to start creating your app. So, the first thing you will do is to create a new directory called webvr-tutorial in your computer (from now on, this directory will be referenced as the project root).

Note: You will have to replace <AUTH0-CLIENT-ID> and <AUTH0-DOMAIN> with your own Auth0 properties. That is, you will have to replace <AUTH0-CLIENT-ID> with the Client ID property found in the Settings tab of the Auth0 Application that you created in the previous section and you will have to replace <AUTH0-DOMAIN> with the domain of your Auth0 tenant (something like: bk-samples.auth0.com). You can also find this information in the Settings tab of your Auth0 Application.

In the <script> section of the code above, you first set up Auth0 Lock using your Auth0 Application's Client Id and Domain then you instructed it to use the Google login by entering google-oauth2 into the allowedConnections array.

After that, you set up a listener on the authenticated event. This event is then handled by calling the Lock's getUserInfo function and passing the access token returned from the authentication results. The callback to this function will save the access token and user profile in the browser's local storage to access later.

After the authentication process is fulfilled, you redirected your users to a page called stage.html. You will create your WebVR world in this page in no time.

Also, notice that you made your index.html call Lock's show method to instantly display Auth0's login box when the page loads.

Building the Scene with WebVR

Now to the main action. You are going to build a 3D scene using a skybox, add users' name from the saved profile, and make it rotate in space right before the user's eyes.

You will make this viewable using a Head Mounted Device like Google Cardboard or a simple web browser.

Adding the Libraries in the WebVR App

To begin, you first need to add the required libraries to build your 3D scene and use the WebVR API.

<!DOCTYPE html><htmllang="en"><head><title>WebVR Demo</title><metacharset="utf-8"><metaname="viewport"content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no"><metaname="mobile-web-app-capable"content="yes"><metaname="apple-mobile-web-app-capable"content="yes"/><metaname="apple-mobile-web-app-status-bar-style"content="black-translucent"/><style>body{width:100%;height:100%;background-color:#000;color:#fff;margin:0px;padding:0;overflow:hidden;}/* Position the button at the bottom of the page. */#ui{position:absolute;bottom:10px;left:50%;transform:translate(-50%,-50%);text-align:center;font-family:'Karla',sans-serif;z-index:1;}a#magic-window{display:block;color:white;margin-top:1em;}</style></head><body><divid="ui"><divid="vr-button"></div><aid="magic-window"href="#">Try it without a headset</a></div></body><script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.min.js"></script><script src="http://threejs.org/build/three.js"></script><script src="http://threejs.org/examples/js/controls/VRControls.js"></script><script src="http://threejs.org/examples/js/effects/VREffect.js"></script><script src='https://cdn.jsdelivr.net/npm/webvr-polyfill@0.9.41/build/webvr-polyfill.js'></script><script src="https://googlevr.github.io/webvr-ui/build/webvr-ui.min.js"></script></html>

The code above can be described as the boilerplate markup for WebVR projects. In the <head> section, meta tags are used to set defaults that optimize and make the mobile experience responsive across supported devices.

Then, you have some basic styling for page style normalization and for styling the WebVR user interface (UI).

After that, in the <body> section of the page, you have the markup that holds your WebVR UI widgets. Then, after the <body> tag, you are including all the required libraries and polyfills.

Setting Up 3D Scenes in WebVR

To set up your scene, you need to setup some global variables which will represent all the elements needed in your scene that needs to be accessible globally.

You will also need to declare three functions:

A function that runs immediately when the page is loaded and sets up the entire scene.

A function to handle rendering of the scene and animations.

A function to handle responsiveness of the application to changes in the window size.

Below is the complete code with the variable declaration and the three functions, followed by the event handler that handles the page load event. Place this code right after all the library that you included in the stage.html file:

<script>// Last time the scene was rendered.letlastRenderTime=0;// Currently active VRDisplay.letvrDisplay;// How big of a box to render. **Skybox: size skyboxconstboxSize=5;// Various global THREE.Objects.letscene;letcube;lettextMesh;letcontrols;leteffect;letcamera;// EnterVRButton for rendering enter/exit UI.letvrButton;//User Profileconstprofile=JSON.parse(localStorage.getItem("profile"));functiononLoad(){/* Threejs Section */// configuring crossOriginTHREE.TextureLoader.prototype.crossOrigin='';// Setup three.js WebGL renderer. Note: Antialiasing is a big performance hit.// Only enable it if you actually need to.constrenderer=newTHREE.WebGLRenderer({antialias:true});renderer.setPixelRatio(window.devicePixelRatio);// Append the canvas element created by the renderer to document body element.document.body.appendChild(renderer.domElement);// Create a three.js scene.scene=newTHREE.Scene();// Create a three.js camera.constaspect=window.innerWidth/window.innerHeight;camera=newTHREE.PerspectiveCamera(75,aspect,0.1,10000);controls=newTHREE.VRControls(camera);//*Set standing to 'true' to indicate that the user is standingcontrols.standing=true;//*Set the camera vertical position to the eye level of usercamera.position.y=controls.userHeight;// Apply VR stereo rendering to renderer.effect=newTHREE.VREffect(renderer);effect.setSize(window.innerWidth,window.innerHeight);//Add SkyboxconstdesertGeometry=newTHREE.CubeGeometry(10000,10000,10000);constdesertMaterials=[newTHREE.MeshBasicMaterial({map:newTHREE.TextureLoader().load("https://cdn.auth0.com/blog/webvr/desertsky_front.png"),side:THREE.DoubleSide}),newTHREE.MeshBasicMaterial({map:newTHREE.TextureLoader().load("https://cdn.auth0.com/blog/webvr/desertsky_back.png"),side:THREE.DoubleSide}),newTHREE.MeshBasicMaterial({map:newTHREE.TextureLoader().load("https://cdn.auth0.com/blog/webvr/desertsky_up.png"),side:THREE.DoubleSide}),newTHREE.MeshBasicMaterial({map:newTHREE.TextureLoader().load("https://cdn.auth0.com/blog/webvr/desertsky_down.png"),side:THREE.DoubleSide}),newTHREE.MeshBasicMaterial({map:newTHREE.TextureLoader().load("https://cdn.auth0.com/blog/webvr/desertsky_right.png"),side:THREE.DoubleSide}),newTHREE.MeshBasicMaterial({map:newTHREE.TextureLoader().load("https://cdn.auth0.com/blog/webvr/desertsky_left.png"),side:THREE.DoubleSide})];constdesertMaterial=newTHREE.MultiMaterial(desertMaterials);constdesert=newTHREE.Mesh(desertGeometry,desertMaterial);console.log(desert);scene.add(desert);constambientLight=newTHREE.AmbientLight(0xffffff);scene.add(ambientLight);/* Create 3D objects. *///Add name textconstloader=newTHREE.FontLoader();loader.load('https://cdn.auth0.com/blog/webvr/helvetiker_regular.typeface.json',function(font){consttextGeometry=newTHREE.TextGeometry(profile.name,{font:font,size:80,height:5,curveSegments:12,bevelEnabled:true,bevelThickness:3,bevelSize:2,bevelSegments:5});consttextMaterial=newTHREE.MeshBasicMaterial();textMesh=newTHREE.Mesh(textGeometry,textMaterial);textMesh.position.set(-400,controls.userHeight,-550.5);console.log(textMesh);scene.add(textMesh);});/* Screen and VR Setup */window.addEventListener('resize',onResize,true);window.addEventListener('vrdisplaypresentchange',onResize,true);// Initialize the WebVR UI.constuiOptions={color:'black',background:'white',corners:'square'};vrButton=newwebvrui.EnterVRButton(renderer.domElement,uiOptions);vrButton.on('exit',function(){camera.quaternion.set(0,0,0,1);camera.position.set(0,controls.userHeight,0);});vrButton.on('hide',function(){document.getElementById('ui').style.display='none';});vrButton.on('show',function(){document.getElementById('ui').style.display='inherit';});document.getElementById('vr-button').appendChild(vrButton.domElement);document.getElementById('magic-window').addEventListener('click',function(){vrButton.requestEnterFullscreen();});navigator.getVRDisplays().then(function(displays){if(displays.length>0){vrDisplay=displays[0];vrDisplay.requestAnimationFrame(animate);}});}// Request animation frame loop functionfunctionanimate(timestamp){constdelta=Math.min(timestamp-lastRenderTime,500);lastRenderTime=timestamp;// Apply rotation to cube meshif(cube){cube.rotation.y+=delta*0.0006;}if(textMesh){textMesh.rotation.x+=delta*0.0003;textMesh.rotation.y+=delta*0.0003;textMesh.rotation.z+=delta*0.0003;}// Only update controls if we're presenting.if(vrButton.isPresenting()){controls.update();}// Render the scene.effect.render(scene,camera);vrDisplay.requestAnimationFrame(animate);}functiononResize(e){effect.setSize(window.innerWidth,window.innerHeight);camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();}window.addEventListener('load',onLoad);</script>

To have a better understanding of what is going on, the following section will provide a better explanation of different parts of this code.

The onLoad Function

A lot is going on in this function because it is the function that sets up your 3D scene and the WebVR UI.

The first this is that you do is to create a WebGL renderer and attach it to the page body. Thus your scene will fill up the whole page.

This function handles the rendering and re-rendering of the scene and also animations on the page. Anytime the page renders, you update the controls, change the position properties of the Text Mesh to create an animated effect and then call your render function to render or re-render the scene.
You use requestAnimationFrame to ensure that your animations are optimized.

The onResize function

This simple function simply re-calibrates the camera position settings and page dimensions anytime the display is resized.

Done! That's all the code you need for the project. Now you can proceed to test it in a web browser.

Testing the WebVR App

To test your WebVR application, you can take advantage of tools like http-server. If you do have Node.js and NPM installed in your machine, you can install http-server globally with this command:

npm install http-server -g

Then, you can go to your project root and type:

http-server

This will run a lightweight HTTP server that you can access in web browser heading to http://localhost:8080/. Accessing this page will bring the login form of Auth0 where you will be able to sign in with your Google account.

After the authentication process, you will see a screen like this one:

Then, if you click on the Try it without a headset link, you will be able to look around by clicking and moving your mouse.

Conclusion

The future of virtual reality is on the web. The web offers a simple and quick way for the user to easily get immersed in a virtual reality. As the WebVR API continues to evolve, the future is exciting for developers developing virtual experiences on the web.