Objets et scène

Dans ce chapitre, nous verrons comment organiser et animer les objets dans une scène 3D.

Repère de coordonnées

three.js utilise le même repère de coordonnées que WebGL (qui l’hérite de OpenGL). Dans ce repère, l’axe Y pointe vers le haut. Si on cale les axes X et Y sur un écran, alors le centre se situe en bas à gauche de l’écran et l’axe Z pointe vers l’extérieur de l’écran. Ce système de coordonnées est dit à main droite.

../_images/opengl_coordinate_system.png

Note

Par défaut, lorsqu’on crée une caméra, celle-ci est placée à l’origine de la scène (0, 0, 0) et pointe vers l’axe -Z.

../_images/opengl_coordinate_system_with_camera.png

Mesh et Geometry

Object3D est la classe de base qui définit un objet qui peut être ajouté dans une scène. Un objet 3D possède notamment une position dans l’espace de la scène. Il existe beaucoup de classes qui héritent de Object3D (dont Camera). Parmi elles, la classe Mesh. Un mesh (ou maillage) représente une figure géométrique construite à partir de triangles. Un mesh est l’association de données géométriques et d’information de rendu (le matériau).

Une figure géométrique est construite à partir de vertices (au singulier vertex). Un vertex est un point défini par ses coordonnées dans l’espace (x, y, z). Une géométrie 3D est définit ensuite par ses faces, chacune d’elle étant composée de trois vertices. La classe Geometry permet de représenter une figure dans three.js en définissant des vertices et leur association pour créer des faces.

var geometry = new THREE.Geometry();
geometry.vertices.push(
          new THREE.Vector3(-1, -1, -3),
          new THREE.Vector3( 1,  1, -3),
          new THREE.Vector3(-1,  1, -3),
          new THREE.Vector3( 1, -1, -3)
);

geometry.faces.push(
          new THREE.Face3(0, 1, 2),
          new THREE.Face3(0, 3, 1)
);

Dans le code ci-dessus, on crée un carré qui est en fait l’association de deux triangles. Les faces sont définies par des objets de type Face3 qui contiennent les indices des vertices.

On peut ensuite passer cet objet de type Geometry en paramètre du constructeur de Mesh. Comme ce dernier hérite de Object3D, il est possible de l’ajouter à la scène :

<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>three.js app</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="https://threejs.org/build/three.min.js"></script>
    <script>
      // création du renderer
      var renderer = new THREE.WebGLRenderer();
      document.body.appendChild(renderer.domElement);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // création de la caméra
      var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.5, 1000);

      // création de la scène
      var scene = new THREE.Scene();

      // couleur de fond noire
      scene.background = new THREE.Color(0, 0, 0);

      // création d'une géométrie
      var geometry = new THREE.Geometry();
      geometry.vertices.push(
                new THREE.Vector3(-1, -1, -3),
                new THREE.Vector3(1,  1, -3),
                new THREE.Vector3(-1,  1, -3),
                new THREE.Vector3(1, -1, -3)
      );

      geometry.faces.push(
                new THREE.Face3(0, 1, 2),
                new THREE.Face3(0, 3, 1)
      );

      // création du mesh et ajout dans la scène
      scene.add(new THREE.Mesh(geometry));

      // rendu
      renderer.render(scene, camera);
    </script>
  </body>
</html>

Vous pouvez télécharger ce fichier directement.

Créer manuellement les informations pour une Geometry est assez compliqué. Heureusement, three.js fournit un ensemble de classes qui héritent toutes de Geometry et qui s’occupent de créer les vertices et les faces pour des figures élémentaires tels que cube, sphère, icosaèdre

Les transformations dans l’espace

Une instance de Object3D possède des propriétés qui définissent son positionnement dans l’espace local de coordonnées :

position

indique la position dans l’espace. Par défaut : (0, 0, 0)

rotation

indique la rotation en radians autour des axes X, Y, Z (rotation d’Euler)

Note

Pour passer d’une valeur en degrés à une valeur en radians, vous pouvez utiliser la méthode :

THREE.Math.degToRad(degrees : Float)

Pour passer d’une valeur en radians à une valeur en degrés, vous pouvez utiliser la méthode :

THREE.Math.radToDeg(radians : Float)
scale

indique la mise à l’échelle suivant les axes X, Y, Z. Par défaut : (1, 1, 1)

Important

three.js applique toujours ces propriétés dans le même ordre :

  1. mise à l’échelle (scale)

  2. rotation

  3. position

La méthode lookAt est également très pratique (surtout avec la caméra) puisqu’elle modifie la rotation d’un objet de manière à ce que son axe local -Z (0, 0, -1) pointe vers le Vector3 passé en paramètre de la méthode.

Nous pouvons ainsi placer les objets dans la scène selon ces trois propriétés fondamentales (y compris la caméra).

<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>three.js app</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="https://threejs.org/build/three.min.js"></script>
    <script>
      // création du renderer
      var renderer = new THREE.WebGLRenderer();
      document.body.appendChild(renderer.domElement);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // création de la caméra
      var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.5, 1000);

      // création de la scène
      var scene = new THREE.Scene();

      // couleur de fond noire
      scene.background = new THREE.Color(0, 0, 0);

      // création du mesh et ajout dans la scène
      var mesh = new THREE.Mesh(new THREE.TorusGeometry(2, .6, 16, 50));
      scene.add(mesh);
      scene.add(camera);

      // positionnement du mesh
      mesh.position.z = -3;
      mesh.rotation.x = -.5;
      mesh.rotation.y = 2.5;

      // positionnement de la caméra
      camera.position.z = 5;
      camera.position.y = 4;
      camera.lookAt(mesh.position);

      // rendu
      renderer.render(scene, camera);
    </script>
  </body>
</html>

Vous pouvez télécharger ce fichier directement.

Il existe également des méthodes utilitaires dans la classe Object3D permettant de réaliser des transformations de coordonnées :

Important

L’ordre dans lequel vous appliquez les transformations (c’est-à-dire l’ordre d’appel de ces méthodes) est important. Les opérations seront réalisées dans l’ordre inverse. Ainsi si vous appelez :

obj.rotateY(Math.PI);
obj.translateX(2);

Pour un objet situé initialement à l’origine de la scène (0, 0, 0). D’abord three.js effectue une translation de l’objet sur son axe local X puis three.js effectue une rotation de 180° sur l’axe Y. Donc, l’objet se trouvera au final en position (-2, 0, 0) !

Il y a donc une différence importante lorsque l’on utilise dans un programme les propriétés (position, rotation, scale) ou les méthodes de transformations. Pour les premières, on se contente de décrire la situation finale de l’objet, ce qui est très pratique pour placer l’objet dans la scène. Avec les secondes, on décrit une succession d’opérations à appliquer sur un objet, ce qui est très pratique pour réaliser des animations.

Exercice

Contrôler les transformations d’un objet

Créez une page HTML avec un canevas faisant le rendu 3D d’un cube (BoxGeometry). Ajoutez un formulaire HTML permettant de contrôler la position (sur les axes X, Y et Z), la rotation (sur les axes X, Y et Z) et la mise à l’échelle du cube.

Pour le formulaire, vous pouvez utiliser des <input> de type range. Si vous le souhaitez, vous pouvez aussi utiliser la bibliothèque JavaScript dat.GUI qui permet d’ajouter assez facilement des widgets pour contrôler le contenu d’une page.

Une animation simple

Pour animer de manière assez simpliste les éléments dans la scène, il suffit de rappeler régulièrement la méthode render après avoir mis à jour la position des éléments.

Pour cela nous pouvons utiliser la méthode JavaScript standard requestAnimationFrame. Cette méthode prend en paramètre une fonction qui sera appelée dès que l’animation doit être rafraîchie (généralement la méthode est appelée au bout de 1/60 ième de seconde).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>three.js app</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="https://threejs.org/build/three.min.js"></script>
    <script>
      // création du renderer
      var renderer = new THREE.WebGLRenderer();
      document.body.appendChild(renderer.domElement);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // création de la caméra
      var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.5, 1000);

      // création de la scène
      var scene = new THREE.Scene();

      // couleur de fond noire
      scene.background = new THREE.Color(0, 0, 0);

      // création du mesh et ajout dans la scène
      var torus = new THREE.Mesh(new THREE.TorusGeometry(2, .6, 16, 50));
      var sphere = new THREE.Mesh(new THREE.SphereGeometry());
      scene.add(torus);
      scene.add(sphere);
      scene.add(camera);

      // positionnement de la caméra
      camera.position.z = 15;

      function animate() {
        torus.rotateY(.1);

        sphere.position.set(0, 0, 0);
        sphere.rotateZ(.1);
        sphere.translateX(4);

        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      }

      animate();
    </script>
  </body>
</html>

Vous pouvez télécharger ce fichier directement.

La fonction animate() est appelée à la ligne 51. Elle se passe elle-même en paramètre de requestAnimationFrame (ligne 47) avant d’appeler le renderer pour faire un rendu de l’image.

Important

Il faut garder à l’esprit que la fonction passée en paramètre de requestAnimationFrame devrait être appelée environ 60 fois par seconde. Il est donc important qu’elle s’exécute aussi rapidement que possible. Il faut donc penser à réaliser le plus de traitement avant de démarrer l’animation. Il faut également éviter de créer trop d’objets lors de son exécution. À ce titre, three.js fournit beaucoup de méthodes qui, soit retournent de nouveaux objets, soit acceptent un paramètre de sortie qui sera mis à jour afin d’éviter de créer inutilement de nouveaux objets.

Si vous voulez monitorer les performances de votre scène, vous pouvez utiliser la bibliothèque stats.js pour afficher des informations de débogage (notamment le FPS).

Hiérarchie d’objets

Une instance de Object3D possède la propriété children qui est un tableau des instances de Object3D filles. Il est ainsi possible de créer une hiérarchie d’objets afin de représenter la scène sous la forme d’un arbre.

Les transformations (translation, rotation, mise à l’échelle) effectuées sur un objet de la scène, sont également appliquées à tous les objets fils de cet objet. On crée ainsi une chaîne de transformation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>three.js app</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="https://threejs.org/build/three.min.js"></script>
    <script>
      // création du renderer
      var renderer = new THREE.WebGLRenderer();
      document.body.appendChild(renderer.domElement);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // création de la caméra
      var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.5, 1000);

      // création de la scène
      var scene = new THREE.Scene();

      // couleur de fond noire
      scene.background = new THREE.Color(0, 0, 0);

      // création du mesh et ajout dans la scène
      var sphere = new THREE.Mesh(new THREE.SphereGeometry());
      sphere.position.x = 2;
      scene.add(sphere);

      // création du mesh et ajout comme fils du premier mesh
      var cubeFils = new THREE.Mesh(new THREE.BoxGeometry());
      sphere.add(cubeFils);
      cubeFils.position.y = 3;

      // positionnement de la caméra
      camera.position.z = 10;

      renderer.render(scene, camera);
    </script>
  </body>
</html>

Vous pouvez télécharger ce fichier directement.

Dans l’exemple ci-dessus on crée une sphère (par défaut au centre de la scène) et on positionne cette sphère à (2,0,0) à la ligne 32. Puis, on crée un cube (BoxGeometry) que l’on ajoute comme fils à la sphère et on positionne ce cube à (0,3,0) à la ligne 38. Comme le cube est fils de la sphère, il hérite de sa translation le long de l’axe X. La position finale du cube dans la scène est donc (2,3,0).

Les transformations appliquées à un objet le sont toujours dans son espace de coordonnées local (local space) ou espace de l’objet, c’est-à-dire avant que les transformations héritées de son parent ne soient appliquées. Lorsque toute la hiérarchie des transformations est appliquée, on obtient la position, la rotation et la mise à échelle de l’objet dans l’espace de coordonnées du monde (world space).

Pour réaliser des transformations complexes, il est possible de créer des instances de Group. Ces objets n’ont aucune géométrie mais ils permettent d’ajouter un niveau dans la hiérarchie d’objets et de contrôler plus facilement les transformations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>three.js app</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="https://threejs.org/build/three.min.js"></script>
    <script>
      // création du renderer
      var renderer = new THREE.WebGLRenderer();
      document.body.appendChild(renderer.domElement);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // création de la caméra
      var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.5, 1000);

      // création de la scène
      var scene = new THREE.Scene();

      // couleur de fond noire
      scene.background = new THREE.Color(0, 0, 0);

      // création du groupe
      var group = new THREE.Group();
      scene.add(group);

      // création du premier cube et ajout dans le groupe
      var cube1 = new THREE.Mesh(new THREE.BoxGeometry());
      cube1.position.x = 2;
      group.add(cube1);

      // création du second cube et ajout dans le groupe
      var cube2 = new THREE.Mesh(new THREE.BoxGeometry());
      cube2.position.x = -2;
      group.add(cube2);

      // positionnement de la caméra
      camera.position.z = 10;

      function animate() {
        cube1.rotateX(.1);
        cube2.rotateX(.1);
        group.rotateZ(.05);
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      }

      animate();

    </script>
  </body>
</html>

Vous pouvez télécharger ce fichier directement.

Dans l’exemple ci-dessus, chaque cube tourne autour de son axe X dans son repère de coordonnées local et le groupe auquel ils appartiennent tourne autour de son axe Z

Note

Pour vous aider à visualiser les transformations, vous pouvez faire apparaître les axes X, Y, Z grâce à l’objet 3D AxesHelper qui prend en paramètre de construction la longueur des axes à afficher.

var cube1 = new THREE.Mesh(new THREE.BoxGeometry());
cube1.add(new THREE.AxesHelper(1.3));

Vous pouvez télécharger la même scène avec l'affichage des axes.

Excercice

Un premier système planétaire

Créez une scène affichant deux sphères. La première représentera la Terre et la seconde la Lune. Les deux sphères doivent tourner sur elles-mêmes et la Lune doit également tourner autour de la Terre. De plus, la rotation de la Lune sur elle-même est telle que l’on voit toujours la même face depuis la Terre.

Pensez à utiliser des objets de type Group pour contrôler plus facilement les différentes rotations.

Astuce

Pour info, le rayon de la Terre est approximativement de 6 378 km et le rayon de la Lune est approximativement de 1 737 km. Cela signifie que la Lune à un rayon 0.2723 plus petite que celui de la Terre.