Virtual Reality has come to level where it does not need any introduction, at least in the developer circles. However, how can one develop VR apps is still a mystery for web developers which essentially forms a major portion of all software developers. This is mainly because most of the existing VR ecosystems are closed which prevents developers from freely exploring the existing VR apps and play around with the code. Even if it weren’t closed sourced, the tech stack required for building traditional VR apps is pretty complex and a web developer would rather not venture into that direction.

In this tutorial, we’ll learn about a breaking new API that lets you build VR with a web developer’s existing tech stack. Let’s dive right in!

Please note that deepstreamHub is no longer operating, hence this tutorial no longer works, check an alternative tutorial I wrote using Ably, here.

What will we build?

By the end of this tutorial, you will have a VR app similar to the one seen in the above picture. It will have a basic VR scene where multiple users can connect to your app from their mobile phones by simply hitting a URL on their mobile phone’s browsers. We’ll use only open source technologies and frameworks for implementing this application, thus at no point you are expected to pay and you will be able to find numerous resources for extended research/learning.

For every user who joins your application, a new avatar will appear in your VR scene which will rotate/move in realtime according to the movement of the user’s phone in real life. This app was made for a talk at Front-end connect conference but I later realized that it would serve as a great getting-started guide for both VR and realtime, separately as technologies as well as implemented together like in this one.

What will we use?

As mentioned before, we wish to access VR directly in the browsers, without having to download anything. We shall use WebVR to achieve this. WebVR is a web framework allows us to build Virtual Reality applications that are accessible directly on the web, thus completely eliminating heavy downloads and installs as well making Virtual Reality device independent.

However, building applications directly in WebVR is a bit complex since it requires the knowledge of WebGL, etc and since we brought VR to the web, there should be a way for Web developers to be able to build VR apps without much complexity. Hence, Mozilla’s VR team built a framework on top of WebVR, called A-Frame. A-Frame completely eliminates WebVR’s boilerplate code and allows developers to build VR apps with something as simple as HTML.

Further, we’ll use deepstream.io to implement all the realtime functionality in the application. deepstream.io is an open realtime server that provides features like PubSub and data-sync. Out of it’s five major features — records, RPCs, events, presence and listening- we shall extensively make use presence and records in this tutorial. In my opinion, deepstream provides a very easy way to add realtime functionality to your existing applications. It is also available as a hosted version, called deepstreamHub.

As you know by now that every WebVR application can be accessed from just a browser. This means, we’ll need to host our files in order to be able to access them in a mobile with a URL. Glitch is a very convenient way of doing this. You can simply create a new project and when you are done, the URL is readily available so that you can use it via mobile browsers instantly.

Getting started

Let us start by building the basic VR setup for the application. We’ll use A-Frame’s entity-component (ECS). ECS makes it easy to build any objects in the scene by treating every object as an entity which differs from other entities by the various components (or attributes) that can attached to an entity.

In your HTML file, start of by adding the HTML skeleton code:

The references refer to the following respectively:

1. deepstream’s Javascript client

2. A-Frame’s source file

3. Our local JS file which we’ll use next to add the logic into the app

4. A-frame’s community contributed — text component to conveniently add text to our VR scene.

All the objects we wish to include in our VR scene must go within the `a-scene` tag in our HTML file, as scene above (pun intended)! This is analogous to the `body` tag in regular HTML documents.

Next, we will add all the assets we wish to use within the `a-assets` tag. Adding all the resources under this tag makes sure that all your assets are pre-loaded before your app shows up, thus preventing a shitty first look due to slow loading of resources.



<! — assets at one place for better loading →

<a-assets>

<img id=”pink” src=”

<img src=”

<img src=”

<img id=”sky” src=”



<a-asset-item id=”dawningFont” src=”

<a-asset-item id=”exoFont” src=”

<a-asset-item id=”exoItalicFont” src=”



<a-mixin id=”eye” geometry=”primitive: sphere; radius: 0.2" material=”shader: flat; side: double; color: #FFF”></a-mixin>

<a-mixin id=”pupil” geometry=”primitive: sphere; radius: 0.05" material=”shader: flat; side: double; color: #222"></a-mixin>

<a-mixin id=”arm” geometry=”primitive: box; depth: 0.2; height: 1.5; width: 0.2" material=”color: #222; shader: flat”></a-mixin>

</a-assets>

https://img.gs/bbdkhfbzkk/stretch/http://i.imgur.com/1hyyIUi.jpg " crossorigin=”anonymous” /> https://img.gs/bbdkhfbzkk/stretch/https://i.imgur.com/25P1geh.png " id=”grid” crossorigin=”anonymous”> https://img.gs/bbdkhfbzkk/2048x1024,stretch/http://i.imgur.com/WMNH2OF.jpg " id=”chrome” crossorigin=”anonymous”> https://img.gs/bbdkhfbzkk/2048x2048,stretch/http://i.imgur.com/WqlqEkq.jpg " crossorigin=”anonymous” /> https://cdn.glitch.com/c719c986-c0c5-48b8-967c-3cd8b8aa17f3%2FdawningOfANewDayRegular.typeface.json?1490305922844 "> https://cdn.glitch.com/c719c986-c0c5-48b8-967c-3cd8b8aa17f3%2Fexo2Black.typeface.json?1490305922150 "> https://cdn.glitch.com/c719c986-c0c5-48b8-967c-3cd8b8aa17f3%2Fexo2BlackItalic.typeface.json?1490305922725 ">

Feel free to use your own resources to give the app a customized feel!

You will observe that we added two new tags in the above code snippet, let’s crack them down:

a-asset-item — invokes the three.js file loader. You can use this to load all file types.

a-mixin — is a very useful tag that allows code reuse by letting you specify a set of properties(components) to be applied to a single entity. You can give it an id and reference it multiple times as we’ll see. We will have three mixins, each specifying certain attributes of the avatar that we intend to create- the eyes, pupils and arms.

Now, let us add all the static visual elements in our VR scene.

<! — adds the torusKont →

<a-entity scale=”2 2 2" geometry=”primitive: torusKnot” position=”0 6 -10" material=”color: magenta; metalness:1; roughness: 0.1; sphericalEnvMap: #sky;”>

<a-animation easing=”linear” attribute=”rotation” dur=”10000" to=”0 0 360" repeat=”indefinite”></a-animation>

</a-entity>

<! — adds the text with custom font →

<a-entity position=”-3 1 -6" rotation=”5 0 0">

<a-entity

rotation=”0 0 5"

position=”0 2 0.2"

text-geometry=”value: Frontend-Connect; font: #dawningFont; bevelEnabled: true; bevelSize: 0.05; bevelThickness: 0.05; curveSegments: 12; size: 1; height: 0;”

material=”color:lavenderblush; metalness:1; roughness: 0; sphericalEnvMap: #pink;”>

</a-entity>

<! — adds the text with custom font →

<a-entity position=”-0.5 0.5 -0.5" scale=”0.6 1.2 1" text-geometry=”value: VR on the web; font: #exoFont; bevelEnabled: true; bevelSize: 0.1; bevelThickness: 0.1; curveSegments: 1; size: 1.5; height: 0.5;” material=”color:pink; metalness:0.9; roughness: 0.05; sphericalEnvMap: #chrome;”></a-entity>

<! — adds the text with custom font →

<a-entity position=”1 0 0.3" text-geometry=”value: 2017; font: #exoItalicFont; style: italic; size: 0.8; weight: bold; height: 0;”

material=”shader: flat; color: white”>

</a-entity>

<! — adds the text with custom font →

<a-entity position=”1 0 0.3" text-geometry=”value: 2017; font: #exoItalicFont; style: italic; size: 0.8; weight: bold; height: 0; bevelEnabled: true; bevelSize: 0.04; bevelThickness: 0.04; curveSegments: 1"

material=”shader: flat; color: white; transparent: true; opacity: 0.4">

</a-entity>

</a-entity>

<! — adds the grid to give a perspective of distance →

<a-entity

geometry=”primitive: plane; width: 10000; height: 10000;” rotation=”-90 0 0"

material=”src: #grid; repeat: 10000 10000; transparent: true;metalness:0.6; roughness: 0.4; sphericalEnvMap: #sky;”>

</a-entity>

<! — adds the lighting to make the scene more realistic →

<a-entity light=”color: #ccccff; intensity: 1; type: ambient;” visible=””></a-entity>

<a-entity light=”color: ffaaff; intensity: 1.5" position=”5 5 5"></a-entity>

<a-entity light=”color: white; intensity: 0.5" position=”-5 5 15"></a-entity>

<a-entity light=”color: white; type: ambient;”></a-entity>

<! — adds the sky →

<a-sky src=”#sky” rotation=”0 -90 0"></a-sky>

<a-entity position=”0 -5 0"

geometry=”primitive: plane; width: 10000; height: 10000;” rotation=”-90 0 0"

material=”src: #grid; repeat: 10000 10000; transparent: true;metalness:0.6; roughness: 0.4; sphericalEnvMap: #sky;”>

</a-entity>



As you can see, we have implemented the complete app using ECS. However, this is not the only way that you can add objects to the scene. A-Frame comes with a few custom entities such as box, sphere etc. These custom entities are called primitives.

As you can see, the code contains very easy to follow comments that explain what each of the entity-component set is trying to implement in our app. For VR newbies, here’s something interesting- a sky is like a layer that covers your 360 deg sphere inside which you assume yourself to be standing while experiencing a VR app. It is generally analogous to the sky in real-life which can be seen on the top and appears to drop down near the horizon. We use `a-sky` in A-Frame to add a sky and the resource to be used can be either a 360 deg image or just a solid colour.

Now comes the interesting part. We require a camera entity. This is a special entity offered by A-Frame that intrinsically grabs the continously changing position as well as rotation values of your mobile phone while you are using an A-Frame powered VR app in the browser. It takes advantage of the various gyro sensors in your phone to achieve this under-the-hood.

Here’s how we can add a camera entity. We can optionally give it a shape and animation, which helps us track it’s movement by serving as a cursor.



<! — camera entity →

<a-entity>

<a-entity camera look-controls wasd-controls id=”user-cam”> <a-entity position=”0 0 -3" scale=”0.2 0.2 0.2" geometry=”primitive: ring; radiusOuter: 0.20; radiusInner: 0.13;” material=”color: #ADD8E6; shader: flat” cursor=”maxDistance: 30; fuse: true”> <a-animation begin=”click” easing=”ease-in” attribute=”scale” fill=”backwards” from=”0.1 0.1 0.1" to=”1 1 1" dur=”150"></a-animation> <a-animation begin=”fusing” easing=”ease-in” attribute=”scale” fill=”forwards” from=”1 1 1" to=”0.2 0.2 0.2" dur=”1500"></a-animation>

</a-entity>

</a-entity>

</a-entity>



By the end of this section, your VR app should ideally look like as shown below. But only if you haven’t switched the resources with your custom ones, of course!

first look

Voila! we just finished setting up the basic VR scene, it’s now time to add some funtionality to it in order to make the avatars appear, dissappear and move around in realtime as the users log in and out of your application or simply move their phone around.

Adding realtime functionality

It’s time to spin up some magic into our VR scene. Using deepstream is very easy compared to any of the other existing alternatives like Firebase.

Start by connecting your client/user to deepstream:



var client = deepstream(‘<YOUR APP URL>’)

client.login({}, function (success,data) {

if(success){

startApp(data)

}else{

//handle login failed

}

})



We can connect to deepstream’s JS client as shown above. For this tutorial, I have used deepstream’s hosted version called deepstreamHub. For this, navigate to deepstream’s dashboard and create an account for free. Next, add a new application and give it a name. On the home page of this application, you’ll find something called the APP URL. Add this URL in the above code.

deepstream comes with a many authentication mechanisms. For our application, we will simply use the NO AUTH mechanism for the sake of simplicity of this tutorial. This is the reason why the first parameter of the login method is left empty. You can also choose to skip this paramater completely. Login method’s callback has two parameters- `success` is a boolean variable, which if true implies that the login was successful and `data` contains some user specific data such as a unique ID. This data is different for different users. We’ll use this unique ID in order to differentiate between multiple avatars generated by different users that would appear in the scene.



//startup by creating a new record for each user

function startApp(data){

var x = Math.random() * (10 — (-10)) + (-10);

var y = 0;

var z = 0;

var initialPosition = {x: x, y: y, z: z};



var myBoxColor = ‘#222’

var currentUser = client.record.getRecord(‘user/’+ data.id);

currentUser.whenReady(function() {

currentUser.set({

type: ‘a-box’,

attr: {

position: initialPosition,

rotation: “0 0 0”,

color: myBoxColor,

id: data.id,

depth: “1”,

height: “1”,

width: “1”

}

}) var camera = document.getElementById(‘user-cam’);



//update camera position

var networkTick = function() {

var latestPosition = camera.getAttribute(‘position’);

var latestRotation = camera.getAttribute(‘rotation’);

currentUser.set({

attr: {

position: latestPosition,

rotation: latestRotation

}

});

};

setInterval(networkTick, 100);

}) //deepstream’s presence feature

client.presence.getAll(function(ids) {

ids.forEach(subscribeToAvatarChanges)

});



client.presence.subscribe((userId, isOnline) => {

if( isOnline ) {

subscribeToAvatarChanges(userId)

} else{

removeAvatar(userId)

}

});

}



For simplicity, we’ll restrict the avatar’s rotation/movement to x-axis only, while keeping the coordinates on the other two planes as zero. The initial position on x-axis is chosen randomly so that multiple avatars do not clutter at the same point in the scene as soon as they appear.

`a-box` is a primitive in A-Frame that can be conveniently used to create a 3D box with basic attributes such as dimensions, position, rotation, color etc. We shall store these attributes using a record in deepstream and we’ll add all the other parts of the avatar, i.e eyes and arms, with respect to this box’s position (which serves as the head of the avatar).

Records are documents in deepstream’s realtime data-store. We make use of this feature to store the attributes like id, position, rotation, etc of all the users, which will be later implied on their respective avatars.

We retrieve an existing record or create a new one using

client.record.getRecord(<record name>);

We will ensure that it’s a unique record for every user by adding the client’s id to the name(path) of the record. Further, we need to add all the required attribures in this record, including the initial position which is selected at random. As discussed above, we track the movement of the phone by using the camera entity and we update this data every 100 milliseconds as seen above and consequently set this new data in the record each time.

Presence is a common term in the realtime world which basically gives you the information about the user’s online status. In our case, we would create and make the avatar appear in the scene only when a user comes online(hits the URL) and likewise make it disappear as soon as a user quits the app, in other words, closes the browser window on his/her phone. Additionally, deepstream allows you to subscribe to presence, which fires a callback every time a new user logs in or an existing user logs out. This is exactly what we require in our app.

As soon as a user comes online, we will make the user subscribe to the changes in the record. These changes, as you can imagine will be in the attr object due to changing position and rotation. Our goal is to update the avatar as these attributes update.

Before that, we first need to ensure that the avatar is created atleast once, for it to update it’s attributes. We do this by using a simple map of avatars where we store the IDs of all the existing avatars.