Utiliser WebGL sur iOS avec Ejecta

Difficulté : | 15' Publié il y a 4 ans
La technologie WebGL est de plus en plus présente, même sur les terminaux mobiles, malheureusement pour les possesseurs d'appareils sous iOS, celle-ci n'est pas disponible. Nous avons trouvé une solution pour utiliser WebGL sur iOS, vous allez voir c'est très simple à utiliser.

Notre outils de conception de plan de maison Wanaplan est en ligne depuis quelques mois maintenant, celui-ci est accessible directement dans votre navigateur comme utilisé chez notre partenaire kozikaza.com, sans aucun plugin. Une des prochaines étapes est de réaliser des applications mobiles et pour cela nous nous sommes confrontés à plusieurs problématiques. La plus grande a été de savoir s'il serait possible de conserver du code métier ou si une réécriture totale était obligatoire.

Aujourd'hui la technologie WebGL est disponible dans presque tous les navigateurs web, y compris sur mobile. Malheureusement, Apple, qui a de fortes parts de marché sur les terminaux mobiles, n'a pas encore activé cette fonctionnalité sur son système iOS. Pourtant ces appareils, une fois débloqués via un Jailbreak, permettent d'utiliser WebGL et les résultats sont très bons.

webgl sur ios avec ejecta

Comment faire pour porter une application 3D WebGL sur un appareil iOS ?

En utilisant Ejecta ! Sur la capture de gauche vous pouvez voir Ejecta en action sur un iPad mini ! Sur PC nous avons la version web de Wanaplan et sur iPad le même plan exécuté par une modification de notre moteur JavaScript ! Oui nous utilisons WebGL sur iOS et celui-ci n'est pas jailbreaké :)

Utiliser WebGL sur iOS grâce Ejecta

Ejecta est une implémentation open source des éléments HTML Canvas et Audio en Objective-C pour iOS. Vous pouvez donc écrire du code JavaScript et l'exécuter par votre application native. Avec quelques adaptations de code il sera alors possible d'utiliser Three.js par exemple et utiliser WebGL sur iOS. Un projet Ejecta est très facile à mettre en place, il suffit de télécharger le projet sur le site ou de cloner le dépôt via github et l'ouvrir dans Xcode.

Cette solution va fonctionner un peu comme une WebView qui n'affichera qu'une balise Canvas et rien d'autre ! Il n'est effectivement pas possible d'utiliser du code HTML. Les interfaces graphiques seront réalisées en Objective-C avec une classe dérivant de EJAppViewController (ou directement sur AppDelegate si vous voulez tester rapidement). De là il sera possible de récupérer un pointeur de l'instance EJJavaScriptView et utiliser sa méthode evaluateString pour lancer une fonction JavaScript.

Voici par exemple deux méthodes qui permettent d'exécuter une fonction JavaScript depuis une application Objective-C.

- (void)executeJavaScript:(NSString *)evalString
{
    EJJavaScriptView *jsView = (EJJavaScriptView *)self.view;
    [jsView evaluateScript:evalString];
    [jsView release];
}
- (void)onClickHandle:(UIButton *)button
{       
    [self executeJavaScript:@"addCubes()"];
}

La grande question est de savoir si cette solution est performante sur une application 3D complexe. Premièrement, replaçons les choses  dans le bon contexte. Nous sommes sur mobile avec un hardware limité, les performances ne seront donc pas aussi bonnes que sur un Desktop et il faudra donc faire des optimisations et des concessions (suppression des normal map, des specular, texture de plus petite résolution, moins d'ombres, etc...).

Premier test d'utilisation de WebGL sur un iPad Air

Nous avons réalisé plusieurs tests en ajoutant successivement plusieurs cubes avec un material de type Phong (une texture de diffuse). Voici la première fonction utilisée pour ajouter des cubes sur la scène :

function createCubes(numberOfCubes) {
    var blockSize = 75;
    var sceneSize = 5000;
    var texture = THREE.ImageUtils.loadTexture("images/box_diffuse.png");
    var cubeGeometry = new THREE.CubeGeometry(blockSize, blockSize, blockSize);
    var cubeMaterial = new THREE.MeshPhongMaterial({ map: texture });

    for (var i = 0; i < numberOfCubes; i++) {
        var mesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
    mesh.position.x = random(-sceneSize / 2 + blockSize, sceneSize / 2 - blockSize);
    mesh.position.y = random(blockSize / 2, sceneSize / 2 - blockSize);
    mesh.position.z = random(-sceneSize / 2 + blockSize, sceneSize / 2 - blockSize);
    scene.add(mesh);
    }
}

Sur cette capture d'écran nous avons une scène relativement simple avec une skysphere, une terrain plat de 5000x5000 et 5000 cubes positionnés aléatoirement de façon à rester sur la scène. Le verdict est sans appel et l'iPad a beaucoup de mal, le déplacement de la caméra est très lent, ce test n'est donc pas concluant.

La fonction qui ajoute des cubes sur la scène n'est pas trop mal écrite car on utilise à chaque fois la même geometry et la même material. Cependant, comme tous les cubes utilisent le même material il serait intéressant de fusionner toutes les geometry et de n'avoir au final qu'un seul mesh au lieu d'avoir 5000 meshes sur la scène.

[caption id="attachment_294" align="alignnone" width="750"]5000 cubes sans optimisations 5000 cubes sans optimisations[/caption]

Optimisation par fusion

Le Framework Three.js met à notre disposition plusieurs classes d'aide dont GeometryHelper qui contient une méthode merge(geometry, mesh) ! Grâce à cette méthode nous pourrons regrouper toutes les geometry en une seule et créer un seul mesh résultant. La fonction de création de cubes va donc changer drastiquement.

function createCubes(numberOfCubes) {
    var blockSize = 75;
    var sceneSize = 5000;
    var texture = THREE.ImageUtils.loadTexture("images/box_diffuse.png");
    var cubeGeometry = new THREE.CubeGeometry(blockSize, blockSize, blockSize);
    var cubeMaterial = new THREE.MeshPhongMaterial({ map: texture });

    var oGeometry = new THREE.Geometry();
    var oMesh = new THREE.Mesh(cubeGeometry, material);
    var mesh = null;

    for (var i = 0; i < numberOfCubes; i++) {
        mesh = oMesh.clone();
    mesh.position.x = random(-sceneSize / 2 + blockSize, sceneSize / 2 - blockSize);
    mesh.position.y = random(blockSize / 2, sceneSize - blockSize);
    mesh.position.z = random(-sceneSize / 2 + blockSize, sceneSize / 2 - blockSize);
    // On réalise notre fusion des geometry ici
        THREE.GeometryUtils.merge(oGeometry, mesh);
    }

    mesh = new THREE.Mesh(oGeometry, cubeMaterial);
    scene.add(mesh);
}

Vous remarquerez que nous en avons profité pour faire une économie avec la déclaration d'une seule variable mesh. En réalité on crée un mesh temporaire et on fusionne ses vertices avec la geometry de base. A la fin de la boucle un mesh résultant est créé et ajouté à la scène.

Le premier test est désormais parfaitement fluide ! Nous avons alors tenté de monter le nombre de cubes à 10 000 puis à 20 000 et tout reste très fluide :) . Alors certes il y a quelques lags quand on se rapproche des cubes mais c'est assez négligeable. L'ensemble est exploitable. Je vous laisse consulter la vidéo ci-dessous.

Il faut néanmoins garder à l'esprit que nous avons pu réaliser cette opération de fusion car tout nos objets partagent la même material. Cette méthode casse aussi toute notion de parenté et n'est donc pas adapté pour les objets dynamiques.

WebGL et iOS grâce à Ejecta, la conclusion

Ejecta est une solution très intéressante qui doit être suivie de près. Sans optimisation il est déjà possible de créer des scènes simple en 3D mais dès que l'on pousse en optimisation, il est possible de faire de grandes choses. Il faudra cependant garder à l'esprit que ce n'est pas une WebView et que toute la partie UI devra être réalisée en Objective-C. Malheureusement le point faible de cette solution est qu'elle ne dispose pas encore de beaucoup de documentation, en réalité il y a une page qui montre quelque exemples de code, mais ça n'est clairement pas suffisant pour démarrer un projet concret. Pour cela, il faudra se plonger dans le code source Objective-C de la bibliothèque et l'étudier. On notera enfin que le Framework Three.js est très bien supporté.

[caption id="attachment_317" align="alignleft" width="225"]Babylonjs sur iOS Babylon.js sur iOS[/caption]

L'utilisation du moteur Babyon.js (que nous avons essayé aussi ici) est aussi possible mais il faudra effectuer quelques changements sur le code afin que celui-ci puisse être utilisé (ne pas charger les shaders depuis les fichiers mais les intégrer directement dans le moteur et ne pas oublier de dimensionner le canvas avec les propriétés width/height clientWidth/clientHeight). Vous pouvez récupérer mon petit wrapper à cette adresse pour vos tests. 

Tags de l'article :

Cet article n'est pas taggué.

Commentaires