Loupe

Oculus Rift et Babylon JS

Il y’a quelques semaines, l’ami David Catuhe m’a posé un chalenge technique assez inhabituel, et à dire vrai ultra-intéressant : finaliser le support d’Oculus Rift dans Babylon JS.

En effet, l’équipe Microsoft DPE France (et plus particulièrement Eric Vernié et David Rousset) avait déjà prototype un hack fonctionnel permettant de faire communiquer Babylon JS avec un device Oculus Rift. Le prototype prouvait que la chose était possible : je suis donc parti du code existant, l’ai optimisé aux petits oignions, et ai finalisé le tout pour obtenir le résultat disponible dans la dernière build de Babylon JS.

Le hack de base

Le hack sur lequel repose le projet est tout bête, et repose sur des fondements de Windows 8 et IE11. En effet, si on regarde la nature des informations fournies par l’Oculus Rift pour le positionnement dans l’espace, on s’aperçoit qu’il s’agit ni plus ni moins que d’une orientation (exprimée sous la forme d’une matrice, d’un quaternion, ou sous un vecteur 3D “yaw/pitch/roll”), et de manière optionnelle, d’une vélocité. Si l’on regarde dans les standards HTML5, il existe un évènement qui expose des données exactement sous la même forme : il s’agit de l’évènement “DeviceOrientation”. IE11 supporte cet évènement à partir du moment où l’ordinateur l’exécutant possède un device Sensor dit de “fusion” (il s’agit d’un sensor normalement généré par Windows, agrégeant les données des gyroscopes, accéléromètres etc. présents dans nos PCs et tablettes pour fournir une données très précise sur l’orientation du device). Ce que nous avons donc fait, c’est un driver Windows de type Sensor “Fusion” exposant un device virtuel exposant les données fournies par l’Oculus Rift. Ce driver utilise le SDK Oculus, extrait les données 200 fois par seconde, et les transforme dans le format standard définit dans la doc Sensor and Location Platform. Grâce à ce hack, nous pouvons désormais obtenir l’orientation de la tête de l’utilisateur et piloter une caméra Babylon JS (et on peut aussi récupérer ces informations dans les apps WinRT !).

Stéréoscopie

La deuxième capacité très importante d’Oculus Rift, est son rendu stéréoscopique particulièrement immersif. En effet, l’écran présent dans le casque est séparé en 2, de manière à ce que chaque œil reçoive une image différente. De plus, les lentilles présentes entre les yeux et l’écran permette d’étendre le champs de vision en déformant l’image (ce qui donne un côté particulièrement immersif, car le champ de vision perçu quand on a le casque sur la tête est quasiment équivalent à notre champ de vision naturel). Pour effectuer ce rendu stéréoscopique, nous avons besoin non pas d’une, mais de deux caméras correspondant aux 2 demi-images. Côté Babylon, nous avons donc créé un type de camera particulier qui ne va projeter son contenu non pas sur l’intégralité du Framebuffer, mais sur la moitié. Dans une scène compatible Oculus, on créera donc une camera pour chaque œil.

Chacune de ces cameras est paramétrée d’une manière spéciale : sa matrice de vue est décalée (vers la gauche pour l’œil gauche, vers la droite pour l’œil droit), et sa matrice de projection doit-elle aussi être manipulée afin de prendre en compte la position de l’écran par rapport à l’œil de l’utilisateur ainsi que la position de la lentille. Ces informations (positions des yeux par rapport à l’écran, distance entre les lentilles et l’écran, distance entre chaque lentille, alignement des yeux / lentilles par rapport au centre du demi-écran…) sont exposés par le firmware de l’Oculus Rift, et le sdk Oculus VR fournit du coup des fonctions C++ très simples pour créer les matrices vues et projections à utiliser dans nos scène.

En JavaScript nous avons donc eu à faire face à 2 obstacles : nous n’avons pas accès aux informations métriques exposées par le firmware, et nous ne pouvons pas appeler les fonctions de créations de matrices. La solution que nous avons donc mis en place pour remplacer les appels aux informations du device, est la création de “profils métriques”. Pour le moment l’unique profil que nous avons est celui correspondant au devkit de 2013, et est accessible via la propriété statique “BABYLON.OculusController.CameraSettings_OculusRiftDevKit2013_Metric”. Cet objet a été construit à partir d’un petit outil écrit en C++ qui extrait les informations provenant du casque vers un fichier json. Lorsqu’un nouveau casque sortira, nous créerons un nouveau profil lui correspondant. En ce qui concerne les créations de matrices, fort heureusement, Oculus VR a documenté les opérations mathématiques à effectuer pour modifier des matrices de vues et de projection classique et en faire des matrices compatibles avec la vision stéréoscopique d’Oculus Rift, en fonction des informations métriques fournies par le casque. Nous avons donc implémenté ces opérations en JavaScript. Comme vous pouvez l’imaginer, c’est un des sujets qui m’a pris le plus de temps dans la réalisation du projet ! Mais à partir de cette étape là, on commence à voir le bout du chemin : on a un point de vue qui pivote suivant les mouvement de la tête et qui produit un rendu stéréoscopique

Déformation de l’image

J’ai évoqué le fait que les lentilles présentes dans le casque permettait d’éclater l’image et de donner une impression d’un champ de vision étendu. Ce procédé a un effet de bord important : il provoque une distorsion de l’image sous forme de “coussin”. Pour compenser cette distorsion, il faut en introduire une autre complètement inverse, en forme de “tonneau”. En effet, pour obtenir un rendu correct pour Oculus rift, il produire une image ressemblant plus ou moins à cela:
sample coulus

 

 

Pour cela, nous avons du écrire un shader de Post-processing déformant l’image originale, en prenant comme paramètre des information provenant du profil du device (en gros des indices de déformation spécifiques au device). Là-encore, ca n’a pas été particulièrement simple à faire. Cette dernière étape a marqué la fin du travail effectué sur le Rift à proprement parler : le rendu stéréoscopique conserve désormais les proportions entre les éléments de la scène, même en bordure d’écran.

Autres sujets

Avant ce travail effectué avec Oculus Rift, les caméras Babylon étaient responsable à la fois de produire les matrices vues / projection correspondant à la position de l’observateur, mais aussi de gérer les déplacements etc. Hors avec Oculus, on a 2 caméras, mais qui doivent être pilotées par un contrôleur commun (afin que la position et l’orientation de chaque caméra reste cohérente). Une des tâches les plus importantes a donc été d’effectuer un refactoring du système d’input pour être capable d’isoler la partie “caméra”, et la partie “pilotage du joueur”. Il a fallu aussi faire en sorte de pouvoir merger les inputs provenant des évènements Oculus, et ceux provenant du clavier pour être capable de se déplacer au clavier, et de pivoter en tournant la tête.

A l’utilisation

Le nombre d’action à faire pour initialiser les 2 caméras, le lier à un input controller etc. étant assez important, nous avons introduit une fonction helper créant tous les objets dans le bon ordre, et les initialisant.

Par exemple, voici le code utilisé pour remplacer la camera d’origine d’une scène par une double caméra Oculus :

var originCamera = scene.activeCamera;
scene.activeCamera = null;
scene.activeCameras = [];
scene.autoClear = true;
BABYLON.OculusOrientedCamera.BuildOculusStereoCamera(     scene,     "Oculus",     originCamera.minZ,     originCamera.maxZ,     originCamera.position,     { yaw: 3, pitch: 0, roll: 0 }, // initial orientation     true, // use fxaa     false, // don't disable collisions     false, // don't disable gravity     originCamera.ellipsoid); // shape of the camera

Et les autres navigateurs ?

Pour le moment seul IE11 est supporté pour Oculus + BabylonJS. La raison est simple : c’est le seul navigateur sur lequel la latence est acceptable. En observant le code de Chrome et Firefox on se rend compte assez facilement que l’intervalle de questionnement du Sensor est de 100ms. C’est beaucoup trop pour obtenir un comportement fluide et “humainement supportable” avec Oculus Rift.

De plus, Chrome comme Firefox ne font pas appel au Sensor “Fusion”, mais à un type de Sensor “Inclinometer”. Il Aurait donc fallu que j’écrive un driver différent pour supporter ces navigateurs.

Enfin, un problème encore plus important, est que les différents navigateurs ne sont pas forcément d’éccord sur le sens de rotation exprimées par les valeurs alpha, beta et gamma de l’évènement d’orientation…

Pour le moment donc, pas de support d’Oculus Rift sur ces navigateurs

Next steps ?

Les prochaines étapes que j’ai en tête pour le projet, sont:

  • Soumettre des patchs à Firefox et Chromium pour améliorer leur support des Sensors sous Windows (et supporter le driver Oculus Rift courant).
  • Optimiser les effets de PostProcessing quand la caméra ne couvre pas tout l’écran (pour le moment l’impact sur les performances est trop important à mon gout
  • Ajouter la correction des aberrations chromatiques produites par le Rift
  • Supporter les contenus 2D et les caméras orthographiques (pour l’affichage d’un HUD 2D par exemple)

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus