WebGL et Three.js avec Blender - La 3D du navigateur

Dans cet article, je vais vous expliquer comme afficher du contenu 3D dans votre navigateur. Par contenu 3D, je veux parler d’un ensemble d’objets dans une scène : les meshs. Ci -contre un exemple de 3D généré par webGL: vous pouvez tourner, zoomer, vous deplacer: Testez!

Stack technique

webGL et three.js

Etape 1 : La 3D du navigateur

Il y a différentes façons d’afficher ce que vous voulez en 3D dans un navigateur. La plus basique de ces façons était d’utiliser une image ou une vidéo pour afficher une scène en trois dimensions. Les inconvénients de cette méthode sont :

  • Le manque d’interaction de l’utilisateur avec le contenu affiché : par exemple, impossible de faire tourner une planète sur une photo du système solaire.
  • Lourdeur de chargement : le contenu en entier est chargé depuis le serveur. Rien n'est interprété coté Client ( comme on peut s'y attendre du WebGL)

Les navigateurs offrent depuis un moment déjà des méthodes afin d’afficher des contenus 3D dans le navigateur. Flash est l’une de ces plus anciennes méthodes. Cette technologie présente l’inconvénient de devoir installer au préalable le composant sur son ordinateur avant d’être utilisé. De plus, elle est prohibée par Apple. D’autres technologies propriétaires offrent aussi des moyens convaincants de faire de la 3D dans un navigateur: c’est le cas d’Unity 3D. Malheureusement, la communauté d’Unity 3D est restreinte du fait d’une faible couverture du marché. Enfin, plus récemment, la technologie WebGL a fait son apparition sur le Web : son utilisation progresse à grand pas. WebGL pourrait rapidement devenir un standard du marché. C’est en tout cas dans ce sens que les acteurs du Web vont.

Dans cet article, je ne ferai pas l’apologie de WebGL mais vous expliquerai comment user de cette technologie au travers d’un moteur JavaScript nommé Three.js. Je vous conseille de vous rendre sur leur site afin d’y voir leurs exemples d’utilisation du WebGL: ça pourrait vous convaincre !

Passons donc à la pratique:

Etape 2 : Se procurer tout ce qui est nécessaire

Le navigateur

Avant de l’utiliser WebGL, il faut vérifier la version du navigateur que vous utilisez: liste des navigateurs compatibles et Savoir si votre navigateur est compatible.

Three.js : Moteur JavaScript pour WebGL

Pour utiliser WebGL avec Three.js, vous devez télécharger les fichiers ici.

Un éditeur de texte correct pour développer en JavaScript

Perso, j’utilise Sublime Text 2 que je trouve très efficace (notamment pour sa gestion de recherche et de remplacement de masse), mais je sais que beaucoup préféreront NotePad++ ou PsPad.

Blender

Il est aisé d’afficher des meshs (objets 3D) avec Three.js mais c’est une tout autre mission de modéliser ces meshs que l’on veut afficher. Le meilleur moyen pour construire vos meshs est donc d’utiliser un éditeur dédié. Vous pourrez télécharger Blender. Si vous avez besoin des connaissances rudimentaires à la manipulation de cet outils, je vous conseille de faire les tutoriaux d’OpenClassRooms sur Blender. En complément du logiciel Blender, un plugin très utile vous permettra d’exporter vos meshs sous forme de fichiers JavaScript exploitable par Three.js. Ce plugin est fourni par Three.js et se trouve à cet emplacement : three.js-master\utils\exporters\blender\2.65\scripts\addons. Déplacer le dossier iomeshthreejs qui s’y trouve pour le mettre dans votre dossier Blender à cet emplacement : Blender\2.70\scripts\addons.

Etape 3 : Modéliser un objet 3D

On commence les choses sérieuses : ouvrez Blender et modélisez ce qui vous fait plaisir. Pour l’exemple, et à des fins pédagogique, j’ai modélisé cinq cercles autour d’une sphère:

Damien-Leroux-webgl-Blender1.jpg

Ensuite, il vous faudra utiliser le plugin qui permet d’exporter ce que vous avez produit sous Blender en fichier JSON contenant les données de votre modélisation (vertices, couleurs, position, textures,…)

Damien-Leroux-webgl-Blender2.jpg

Sélectionnez ainsi les options du plugin avant export.

Damien-Leroux-webgl-Blender3.png

Vous devriez obtenir une série de fichiers JavaScript dans votre dossier d’export :

  • Un fichier principal contenant les données de la scène : nom des mesh, position, rotation, texture, etc...
  • Des fichiers pour chaque mesh : nom du mesh, vertices, normales, couleurs, etc...

Damien-Leroux-webgl-Blender4.png

Ces fichiers contenant du JSON seront alors exploités par three.js: Vous pouvez Téléchargez les fichiers ici si jamais vous voulez tester.

Etape 4 : Une phase d’initialisation - charger et préparer la scène 3D

Voici la partie de code relative à l'initialisation de la scène 3D WebGL:

function createScene() {
    scene =  new THREE.Scene(),

    /*Camera*/
    camera = new THREE.PerspectiveCamera( 65, container.clientWidth / container.clientHeight, 1, 1500 )
    camera.position.y = 4;
    camera.position.x = 5;
    camera.position.z = 0;
    var camTarget = new THREE.Vector3(0,0,0);
    camera.lookAt( camTarget );
    scene.add( camera );
}

function loadScene() {
    var callbackFinished = function ( result ) {
        result.scene.traverse( function ( object ) {
        if ( object instanceof THREE.Mesh ) {
            meshs.push(object);
        }
        });
        for ( meshsId in meshs) {
            scene.add(meshs[meshsId]);
        }
    };

    /*load scene*/
    var loader = new THREE.SceneLoader();
    loader.load('./exportBlender/WebGLExample.js', callbackFinished);
}

function initRenderer(){
    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setSize(container.clientWidth, container.clientHeight);
    renderer.domElement.style.position = "relative";
    renderer.setClearColor(new THREE.Color(0xffffff));

    container.appendChild( renderer.domElement );
    container.addEventListener( 'resize', onWindowResize, false );
}

function init() {
    //create the scene to display on screen
    createScene();
    //load the scene from JSON Files
    loadScene();
    //init the renderer to display the created scene
    initRenderer();
}

createScene()

En premier lieu, il faut créer une scène. On va aussi en profiter pour créer une caméra. Si vous avez des bases en 3D, vous savez que sans camera, il n’y a nulle part ou regarder !

loadScene()

Ensuite, nous allons charger les éléments à afficher dans la scène grâce aux fichiers JSON créés plus tôt. Pour ce faire, on créait une fonction qui va être appelée une fois le chargement des fichiers terminés. Comme elle sera automatiquement appelé une fois les fichiers chargés, c’est une fonction de callback. Cette fonction va parcourir tous les objets chargés pour les incorporer dans notre scène. Il faut ensuite charger les fichiers JSON avec la fonction loader.load('/static/WebGLExample.js', callbackFinished) à qui on donne notre fonction de callback et le fichier JSON principal se trouve. En chargeant ce fichier, on charge tous les autres fichiers issus de l’export Blender.

initRenderer()

Une fois notre scène chargée, on va ouvrir une fenêtre pour l’y afficher. Cette fenêtre est le « renderer ». Elle est initialisée avec une taille fixe et d’autres paramètres réglés pour l’occasion : couleur, position, etc... La liste est longue. Lorsque notre renderer est prêt, on l’incorpore dans l’emplacement HTML qui est prévu à cet effet:

var container = document.getElementById( 'renderWebGL1' );
container.appendChild( renderer.domElement );

Etape 5 : Une phase de rendu

Damien Leroux WebGL1

Lorsque la fenêtre de rendu et la scène sont prêtes, on active le rendu avec la fonction animate(). Cette fonction va demander le rafraîchissement automatique de notre rendu avec la fonction requestAnimationFrame(animate). animate() sera continuellement appelée. En plus de cette fonction qui nous assure un rafraîchissement perpétuel, la fonction render() va affiché notre scène. Enfin, on affiche la scène et la caméra dans notre rendu : renderer.render( scene, camera );. Le résultat est simple mais manque de finitions : couleur, lumière, mouvement… Télécharger le fichier correspondant ici

Etape 6 : Lumières et couleurs

Damien Leroux WebGL2

Pour ajouter des lumières dans votre scène: rien de plus simple ! Il faut créer une lumière et la placer dans votre scène. La seule question que vous devrez vous poser est quel type de lumière mettre dans votre scène : une lumière directionnelle ou une lumière ambiante ? Si vous hésitez entre les deux, je vous conseille le tutoriel sur les bases de l'éclairage directionnel et ambiant qui vous expliquera aussi bien l’intérêt de colorer votre lumière que la manière dont la lumière interagie avec les éléments de votre scène. Avant de chercher à donner une couleur particulière à votre éclairage, il est tout aussi intéressant de colorer en première lieu les objets de votre scène. Il faut savoir à propos de vos objets qu’ils possèdent un matériau qui permet de définir la transparence de votre objet, sa couleur, sa texture, etc…Télécharger le fichier correspondant ici

A titre d’exemple, les objets de ma scène sont colorés au chargement des meshs. La coloration est aléatoire : une valeur entre 0 et 1 pour les composantes rouge, verte et bleu de la propriété de couleur du seule matériau alors présent sur chacun des objets.

var callbackFinished = function ( result ) {for ( meshsId in meshs) {
        …
        meshs[meshsId].material.materials[0].color.b = Math.random();
        meshs[meshsId].material.materials[0].color.g = Math.random();
        meshs[meshsId].material.materials[0].color.r = Math.random();
    }
};

Damien Leroux WebGL3

Un autre moyen de colorer ces objet serait d’appeler la fonction setHex qui permet de donner une couleur à un matériau avec un héxadécimal : meshs[meshsId].material.materials[0].color.setHex(0x0000ff); Téléchargez le fichier correspondant ici

Etape 7 : Mouvement

Damien Leroux WebGL4

On peut tout déplacer dans une scène webGL. Commençons par les objets visibles dans notre scène : une série de rotation permet de faire vivre la scène.

Pour ce qui est de faire tourner un objet, on choisit un axe de rotation et on utilise celui-ci pour modifier les valeurs de de matrice (valeurs à l’origine de l’orientation de l’objet).

function rotate(object, axis, radians) {
    //rotation matrix is calculated
    var rotationMatrix = new THREE.Matrix4();
    rotationMatrix.makeRotationAxis(axis.normalize(), radians);

    //apply rotation on the object matrix ( set the objet direction)
    rotationMatrix.multiply(object.matrix);             
    object.matrix = rotationMatrix;

    //apply rotation on object (set the object inclinaison)
    object.rotation.setFromRotationMatrix(rotationMatrix);
}

Téléchargez le fichier incluant les mouvements des disques ici

Une autre chose qu’il peut être amusant de faire tourner est la camera. Dans cet exemple, j’utilise un trackball pour pouvoir me déplacer autour de mes objets. Pour ce faire, la solution est déjà fournie clé en main par three.js. Il faut charger le ficher JavaScript relatif au trackball, TrackballControls.js, depuis le répertoire three.js-master/examples/js/controls. Une fois ce fichier inclus, on initialise le trackball:

function createScene() {
    scene =  new THREE.Scene(),

    /*Camera*/
    camera = new THREE.PerspectiveCamera( 65, container.clientWidth / container.clientHeight, 1, 1500 )
    camera.position.y = 4;
    camera.position.x = 5;
    camera.position.z = 0;
    var camTarget = new THREE.Vector3(0,0,0);
    camera.lookAt( camTarget );
    scene.add( camera ); 
}

et on demande une mise à jour du trackball lors de l’affichage

function animate() {//update trackball here
    trackball.update();}

Téléchargez le fichier incluant en plus le trackball ici

Etape 8 : Conditionner l’affichage si WebGL n’est pas supporté

On va maintenant modifier ce qui est affiché si WebGL n’est pas supporté par votre navigateur ou si aucun rendu n’a pu être rendu.

if (window.WebGLRenderingContext){
    init();
    animate();

    var canvas = container.getElementsByTagName("canvas");
    if ( canvas != undefined){
        var context = canvas[0].getContext("webgl");
        if (!context) {
            container.innerHTML= 'webGL content not initialized';
        }
    }else{
        container.innerHTML= 'webGL content Not Found';
    }
}else{
    container.innerHTML= 'impossible to display webGL content';
}

Ici, init() et animate() ne seront appelées que si un rendu web GL est possible. Autrement un message indique que webGL n’est pas supporté. Apres la génération webGL, on cherche à s’assurer qu’un nouvel élément HTML au contexte webGL est bien apparu dans le code HTML sinon on affiche un message d’erreur.