JavaScript en 3D: une introduction à Three.js

Bonjour, Habr! Je vous présente la traduction de l' article "JavaScript en 3D: une introduction à Three.js" par Bret Cameron.

Présentation


Three.js est un outil puissant. Il aide à utiliser la conception 3D dans un navigateur avec des performances acceptables. Mise en route Three.js peut être délicat, surtout si vous n'avez jamais plongé dans le monde de la programmation 3D auparavant.

J'ai une certaine expérience de base avec le moteur de jeu Unity et C #, mais bon nombre des concepts sont nouveaux pour moi. J'en suis venu à la conclusion que maintenant il y a très peu de ressources pour les développeurs débutants, j'ai donc décidé d'écrire cet article. Dans ce document, nous examinerons les éléments de base des scènes Three.js, des maillages polygonaux et des matériaux à la géométrie, aux chargeurs et bien plus encore.

À la fin de cet article, vous aurez une solide compréhension des aspects de base nécessaires pour ajouter une dimension supplémentaire à votre futur projet Web.

Exemples Three.js de Ben Houston , Thomas Diewald et StrykerDoesAnimation .

Vecteurs et conteneurs - blocs de construction de base


Il y a souvent deux classes principales dans Three.js - Vector3 et Box3 . Si vous êtes nouveau dans la 3D, cela peut sembler un peu abstrait, mais vous les rencontrerez beaucoup plus de fois.

Vector3


La classe 3D la plus basique contenant trois nombres: x, y et z . Les nombres sont les coordonnées d'un point dans l'espace 3D ou la direction et la longueur. Par exemple:

const vect = new THREE.Vector3(1, 1, 1); 

La plupart des constructeurs de Three.js acceptent des objets de type Vector3 comme arguments d'entrée, par exemple Box3

Box3


Cette classe représente cuboïde (conteneur 3D). Sa tâche principale est de créer un conteneur autour d'autres objets - et c'est tout, le plus petit cuboïde dans lequel un objet 3D s'insérera. Chaque Box3 est aligné autour des axes x, y et z. Un exemple de création d'un conteneur à l'aide de Vector3 :

 const vect = new THREE.Vector3(1, 1, 1); const box = new THREE.Box3(vect); 

Un exemple de création d'un conteneur autour d'un objet 3D existant:

 const box = new THREE.Box3(); box.setFromObject(object); 

Vous pouvez créer des grilles sans cette connaissance approfondie, mais dès que vous commencerez à concevoir ou à modifier vos modèles, ces classes seront certainement utiles. Nous allons maintenant nous éloigner des abstractions pour des choses plus visibles.

Maillage polygonal


Dans Three.js, l'élément visuel principal de la scène est Mesh . Il s'agit d'un objet 3D composé de rectangles triangulaires (maillage polygonal). Il est construit à l'aide de deux objets:
La géométrie - détermine sa forme, le matériau - détermine l'apparence.

Leurs définitions peuvent sembler un peu déroutantes (par exemple, la classe Geometry peut contenir des informations sur la couleur), mais la principale différence est exactement cela.

Géométrie


En fonction de la tâche que vous souhaitez réaliser, vous pouvez définir la géométrie à l'intérieur de Three.js ou en importer une autre à partir d'un fichier.

En utilisant des fonctions comme THREE.TorusKnotGeometry , nous pouvons créer des objets complexes avec une seule ligne de code. Nous y reviendrons bientôt, mais considérons d'abord des formulaires plus simples.
La figure 3D la plus simple, cuboïde ou contenant, peut être spécifiée par les paramètres de largeur , hauteur et profondeur .

 const geometry = new THREE.BoxGeometry( 20, 20, 20 ); 


Pour une sphère, la valeur des paramètres radius , widthSegments et heightSegments est minimale . Les deux dernières variables indiquent le nombre de triangles que le modèle doit utiliser pour représenter la sphère: plus le nombre est grand, plus il sera lisse.

 const geometry = new THREE.SphereGeometry( 20, 64, 64 ); 


Si nous voulons créer des formes nettes ou triangulaires, nous pouvons utiliser un cône. Ses arguments sont une combinaison des arguments des deux figures précédentes. Ci-dessous, nous prescrivons radius , widthSegments et radialSegments .

  const geometry = new THREE.ConeBufferGeometry( 5, 20, 32 ); 


Ce n'est qu'une partie des chiffres les plus courants. Three.js a beaucoup de formes à l'intérieur de la boîte, qui peuvent être trouvées dans la documentation. Dans cet article, nous examinerons des formes plus intéressantes construites sur la base de la méthode TorusKnotGeometry .

Pourquoi ces formes ressemblent-elles exactement à leur apparence? Cette question dépasse le cadre de cet article, mais je vous invite à expérimenter avec les valeurs des paramètres, car vous pouvez obtenir des formes très intéressantes avec une seule ligne de code!

 const geometry = new THREE.TorusKnotGeometry(10, 1.3, 500, 6, 6, 20); 

https://codepen.io/BretCameron/pen/gOYqORg

Matériaux


La géométrie définit la forme de nos objets 3D, mais pas leur apparence. Pour résoudre ce problème, nous avons besoin de matériaux.

Three.js propose 10 matériaux prêts à l'emploi, chacun ayant ses propres avantages et paramètres personnalisables. Nous ne considérerons qu'une partie des plus utiles.



MeshNormalMaterial


Utile pour un démarrage et un démarrage rapides.

Nous allons commencer avec MeshNormalMaterial , le matériau multicolore que nous avons utilisé dans les exemples ci-dessus. Il correspond aux vecteurs normaux dans le panneau RVB, en d'autres termes, les couleurs sont utilisées pour déterminer la position du vecteur dans l'espace 3D.

 const material = new THREE.MeshNormalMaterial(); 

Notez que si vous souhaitez changer la couleur du matériau, vous pouvez utiliser le filtre CSS et changer la saturation:
  filter: hue-rotate(90deg) . 


D'après mon expérience, ce matériel est plus utile pour une stratification et un lancement rapides. Pour mieux contrôler vos objets, il vaut mieux utiliser autre chose.

Meshbasicmaterial


Utile pour afficher uniquement le squelette.

Si vous souhaitez donner à la figure une seule couleur, vous ne pouvez utiliser MeshBasicMaterial que si l'éclairage n'est pas appliqué. J'ai trouvé utile d'utiliser ce matériau pour rendre le squelette du modèle. Pour dessiner uniquement le squelette, vous devez passer {wireframe: true} comme paramètre.

 const material = new THREE.MeshBasicMaterial({ wireframe: true, color: 0xdaa520 }); 

Le principal inconvénient de ce matériau est que les informations sur la profondeur du matériau sont complètement perdues. Chaque matériau a la possibilité d'afficher uniquement le squelette, mais un seul matériau résout le problème du manque de profondeur - MeshDepthMaterial


Meshhambertmaterial


Utile pour des performances élevées mais une faible précision.

C'est le premier matériau qui prend en compte la lumière, vous devez donc ajouter un peu de lumière à notre scène. Dans le code ci-dessous, nous ajoutons des spots avec une teinte jaune pour créer un effet plus chaud.

 const scene = new THREE.Scene(); const frontSpot = new THREE.SpotLight(0xeeeece); frontSpot.position.set(1000, 1000, 1000); scene.add(frontSpot); const frontSpot2 = new THREE.SpotLight(0xddddce); frontSpot2.position.set(-500, -500, -500); scene.add(frontSpot2); 

Maintenant, ajoutez du matériel pour notre figure. Puisque notre silhouette est comme une décoration, je suggère d'ajouter une couleur plus dorée. Un autre paramètre, émissif , est la couleur de l'objet provenant de l'objet lui-même (sans source lumineuse). Souvent, cela fonctionne mieux comme une couleur sombre - par exemple, comme des nuances de gris foncées, comme dans l'exemple ci-dessous

 const material = new THREE.MeshLambertMaterial({ color: 0xdaa520, emissive: 0x111111, }); 


Comme vous pouvez le voir dans l'exemple ci-dessous, la couleur est plus ou moins correcte, mais la façon dont elle interagit avec la lumière n'ajoute pas de réalisme. Pour résoudre ce problème, nous devons utiliser MeshPhongMaterial ou MeshStandardMaterial.

MeshPhongMaterial


Utile pour des performances moyennes et une précision moyenne.

Ce matériau offre un compromis entre performances et précision de rendu, ce matériau est donc une bonne option pour une application qui doit être productive avec un rendu plus précis qu'avec MeshLambertMaterial.

Maintenant, nous pouvons changer la propriété spéculaire qui affecte la luminosité et la couleur de la réflexion de la surface. Si la propriété émissive est généralement sombre, le spéculaire fonctionne mieux pour les couleurs claires. Ci-dessous, nous utilisons du gris clair.

 const material = new THREE.MeshPhongMaterial({ color: 0xdaa520, emissive: 0x000000, specular: 0xbcbcbc, }); 


Visuellement, l'image d'en haut reflète la lumière de manière plus convaincante, mais toujours pas parfaite. La lumière blanche est trop brillante et le matériau semble plus nervuré que métallique (et nous nous efforçons de le faire). Nous pouvons obtenir un meilleur résultat en utilisant MeshStandardMaterial.

MeshStandartMaterial


Utile pour une grande précision mais une faible productivité.

C'est le matériau le plus précis de tous, bien que son utilisation entraîne des coûts d'utilisation de plus d'énergie. MeshStandartMaterial est utilisé avec des paramètres de métal et de rugosité supplémentaires, chacun prenant une valeur comprise entre 0 et 1.

Le paramètre de métal affecte la façon dont l'objet se reflète, se rapprochant de la nature du métal. En effet, les matériaux conducteurs comme les métaux ont des propriétés réfléchissantes différentes, contrairement aux diélectriques tels que la céramique.

La rugosité ajoute une couche supplémentaire pour la personnalisation. Vous pouvez l'imaginer comme l'opposé du brillant: 0 - très brillant, 1 - très mat.

 const material = new THREE.MeshStandardMaterial({ color: 0xfcc742, emissive: 0x111111, specular: 0xffffff, metalness: 1, roughness: 0.55, }); 


C'est le matériel le plus réaliste de tous présenté dans Three.js, mais aussi le plus consommateur de ressources

Les matériaux dont nous avons discuté ci-dessous sont ceux que j'ai rencontrés le plus souvent et vous pouvez voir toutes les options dans le dock.

Chargeurs


Comme nous l'avons vu ci-dessus, vous pouvez définir manuellement la géométrie et les maillages polygonaux. En pratique, les gens chargent souvent leurs géométries à partir de fichiers. Heureusement, Three.js a peu de téléchargeurs pris en charge qui prennent en charge de nombreux formats 3D.

L' ObjectLoader principal charge le fichier JSON en utilisant le format d'objet / scène JSON . La plupart des chargeurs de démarrage doivent être importés manuellement. Vous pouvez trouver une liste complète des chargeurs de démarrage pris en charge ici et les importer. Vous trouverez ci-dessous une petite liste de ce que vous pouvez importer.

 // GLTF import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; // OBJ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; // STL import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'; // FBX import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'; // 3MF import { 3MFLoader } from 'three/examples/jsm/loaders/3MFLoader.js'; 

Le format recommandé pour la visualisation en ligne est GLTF, car le format est «destiné à fournir des actifs en runtime, compact pour le transfert et rapide pour le téléchargement».

Bien sûr, il peut y avoir de nombreuses raisons de préférer un certain type de fichier (par exemple, si la qualité est une priorité ou si la précision est nécessaire pour l'impression 3D). Les meilleures performances en ligne seront lors de l'importation de GLTF.

 import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import model from '../models/sample.gltf'; let loader = new GLTFLoader(); loader.load(model, function (geometry) { // if the model is loaded successfully, add it to your scene here }, undefined, function (err) { console.error(err); }); 

Tout mettre ensemble


L'une des raisons pour lesquelles Three.js peut sembler intimidante est que vous pouvez créer quelque chose à partir de zéro avec seulement quelques lignes de code. Dans chaque exemple ci-dessus, nous devions créer une scène et une caméra. Pour simplifier, j'ai gardé ce code en dehors de la portée de la revue, mais pour l'instant nous allons voir à quoi il ressemblera.

La façon dont vous organisez votre code dépend de vous. Dans des exemples plus simples, comme dans cet article, il est logique d'écrire tout le code en un seul endroit. Mais en pratique, il est utile de séparer les éléments individuels pour la possibilité d'étendre la base de code et sa gestion.

Par souci de simplicité, nous considérerons les éléments qui sont dessinés comme un seul objet, nous allons donc mettre tout le code dans un seul fichier.

 // Import dependencies import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; //   const scene = new THREE.Scene(); scene.background = new THREE.Color(0x282c34); //  ,       const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); camera.position.z = 5; //  ""      const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); //   DOM   renderer.domElement   document.getElementById('threejs').appendChild(renderer.domElement); //  ,      DOM  let controls = new OrbitControls(camera, document.getElementById('threejs')); controls.target.set(0, 0, 0); controls.rotateSpeed = 0.5; controls.update(); //  ( )   const geometry = new THREE.TorusKnotGeometry(10, 1.3, 500, 6, 6, 20); //    const material = new THREE.MeshStandardMaterial({ color: 0xfcc742, emissive: 0x111111, specular: 0xffffff, metalness: 1, roughness: 0.55, }); //   ,       const mesh = new THREE.Mesh(geometry, material); mesh.scale.x = 0.1; mesh.scale.y = 0.1; mesh.scale.z = 0.1; scene.add(mesh); //  ,       const frontSpot = new THREE.SpotLight(0xeeeece); const frontSpot2 = new THREE.SpotLight(0xddddce); frontSpot.position.set(1000, 1000, 1000); frontSpot2.position.set(-500, -500, -500); scene.add(frontSpot); scene.add(frontSpot2); //   ,           const animate = function () { requestAnimationFrame(animate); mesh.rotation.x += 0.005; mesh.rotation.y += 0.005; mesh.rotation.z += 0.005; renderer.render(scene, camera); }; //    animate(); 

Dois-je utiliser un framework?


Enfin, il est temps de discuter de l'opportunité d'utiliser Three.js avec votre framework préféré? À l'heure actuelle, il existe un bon package de réactif à trois fibres pour React. Pour les utilisateurs de React, l'utilisation d'un package comme celui-ci présente des avantages évidents: vous conservez une structure de travail avec des composants qui vous permet de réutiliser du code.

Pour les débutants, je vous conseille de commencer avec le Vanila JS habituel, car la plupart des documents en ligne écrits sur Three.js concernent Three.js sur Vanila JS. Sur la base de mon expérience d'apprentissage, cela peut être déroutant et difficile à apprendre via un package - par exemple, vous devrez traduire des objets et des méthodes Three.js en composants et accessoires. (dès que vous apprenez Three.js, vous pouvez utiliser n'importe quel package).

Comment ajouter Three.js au framework


Three.js fournit un objet HTML (le plus souvent appelé renderer.domElement ) qui peut être ajouté à n'importe quel objet HTML de votre application. Par exemple, si vous avez un div avec id = "threejs", vous pouvez simplement inclure le code suivant dans votre code Three.js:

 document.getElementById('threejs').appendChild(renderer.domElement); 

Certains frameworks ont des chemins préférés pour accéder aux nœuds DOM de l'arborescence. Par exemple, ref dans React, $ ref dans Vue ou ngRef dans Angular et cela ressemble à un énorme avantage sur fond d'accès direct aux éléments DOM. À titre d'exemple, regardons une implémentation rapide de React.

Stratégie pour React


Si vous utilisez React, il existe un moyen d'incorporer des fichiers Three.js dans l'un de vos composants. Dans le fichier ThreeEntryPoint.js, nous écrirons le code suivant:

 export default function ThreeEntryPoint(sceneRef) { let renderer = new THREE.WebGLRenderer(); // ... sceneRef.appendChild(renderer.domElement); } 

Nous l'exportons en tant que fonction qui prend un argument: une référence à un élément de notre composant. Nous pouvons maintenant créer notre composant

 import React, { Component } from 'react'; import ThreeEntryPoint from './threejs/ThreeEntryPoint'; export default class ThreeContainer extends Component { componentDidMount() { ThreeEntryPoint(this.scene); } render() { return ( <> <div ref={element => this.scene = element} /> </> ); } } 

La fonction ThreeEntryPoint importée doit être appelée dans la méthode componentDidMount et passer le nouveau div comme argument en utilisant des références
Comme exemple de cette approche en action, vous pouvez cloner le référentiel et l'essayer vous-même: https://github.com/BretCameron/three-js-sample .

Conclusion


Il y a encore beaucoup de choses que je peux parler de Three.js, mais j'espère que cet article vous a donné suffisamment d'informations pour commencer à utiliser cette technologie puissante. Quand j'ai commencé à apprendre Three.js, je n'ai pas pu trouver une seule ressource comme cet article, j'espère donc avoir aidé à rendre cette technologie plus accessible pour les débutants.

Source: https://habr.com/ru/post/fr477956/


All Articles