Cet article fait partie d'une série d'articles sur three.js. Le premier article est les bases de three.js. Si vous ne l'avez pas encore lu et que vous débutez avec three.js, vous pourriez envisager de commencer par là, ainsi que l'article sur la configuration de votre environnement. Le l'article précédent portait sur les textures.
Voyons comment utiliser les différents types de lumières dans three.js.
En partant d'un de nos exemples précédents, mettons à jour la caméra. Nous définirons le champ de vision à 45 degrés, le plan lointain à 100 unités, et nous déplacerons la caméra de 10 unités vers le haut et de 20 unités vers l'arrière par rapport à l'origine
*const fov = 45; const aspect = 2; // the canvas default const near = 0.1; *const far = 100; const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); +camera.position.set(0, 10, 20);
Ajoutons ensuite OrbitControls
. Les OrbitControls
permettent à l'utilisateur de faire tourner ou d'orbiter la caméra autour d'un point. Les OrbitControls
sont une fonctionnalité optionnelle de three.js, nous devons donc d'abord les inclure
dans notre page
import * as THREE from 'three'; +import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
Ensuite, nous pouvons les utiliser. Nous passons aux OrbitControls
une caméra à
contrôler et l'élément DOM à utiliser pour obtenir les événements d'entrée
const controls = new OrbitControls(camera, canvas); controls.target.set(0, 5, 0); controls.update();
Nous définissons également la cible d'orbite à 5 unités au-dessus de l'origine
et appelons ensuite controls.update
pour que les contrôles utilisent la nouvelle
cible.
Voyons ensuite comment créer des éléments à éclairer. D'abord, nous allons créer un plan au sol. Nous appliquerons une petite texture en damier de 2x2 pixels qui ressemble à ceci :
Nous chargeons d'abord la texture, la définissons en mode répétition, définissons le filtrage au plus proche, et définissons le nombre de fois que nous voulons qu'elle se répète. Étant donné que la texture est un damier de 2x2 pixels, en la répétant et en définissant la répétition à la moitié de la taille du plan, chaque case du damier aura exactement 1 unité de taille ;
const planeSize = 40; const loader = new THREE.TextureLoader(); const texture = loader.load('resources/images/checker.png'); texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; texture.magFilter = THREE.NearestFilter; texture.colorSpace = THREE.SRGBColorSpace; const repeats = planeSize / 2; texture.repeat.set(repeats, repeats);
Nous créons ensuite une géométrie de plan, un matériau pour le plan et un maillage pour l'insérer dans la scène. Les plans sont par défaut dans le plan XY, mais le sol est dans le plan XZ, nous le faisons donc pivoter.
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize); const planeMat = new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide, }); const mesh = new THREE.Mesh(planeGeo, planeMat); mesh.rotation.x = Math.PI * -.5; scene.add(mesh);
Ajoutons un cube et une sphère pour avoir 3 éléments à éclairer, y compris le plan.
{ const cubeSize = 4; const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); const cubeMat = new THREE.MeshPhongMaterial({color: '#8AC'}); const mesh = new THREE.Mesh(cubeGeo, cubeMat); mesh.position.set(cubeSize + 1, cubeSize / 2, 0); scene.add(mesh); } { const sphereRadius = 3; const sphereWidthDivisions = 32; const sphereHeightDivisions = 16; const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions); const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'}); const mesh = new THREE.Mesh(sphereGeo, sphereMat); mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0); scene.add(mesh); }
Maintenant que nous avons une scène à éclairer, ajoutons des lumières !
AmbientLight
Commençons par créer une Lumière Ambiante
const color = 0xFFFFFF; const intensity = 1; const light = new THREE.AmbientLight(color, intensity); scene.add(light);
Faisons en sorte de pouvoir également ajuster les paramètres de la lumière.
Nous utiliserons de nouveau lil-gui.
Pour pouvoir ajuster la couleur via lil-gui, nous avons besoin d'un petit assistant
qui présente une propriété à lil-gui qui ressemble à une chaîne de couleur hexadécimale CSS
(par ex. : #FF8844
). Notre assistant obtiendra la couleur d'une propriété nommée,
la convertira en chaîne hexadécimale pour l'offrir à lil-gui.
Lorsque lil-gui essaiera de définir la propriété de l'assistant, nous assignerons le résultat à la
couleur de la lumière.
Voici l'assistant :
class ColorGUIHelper { constructor(object, prop) { this.object = object; this.prop = prop; } get value() { return `#${this.object[this.prop].getHexString()}`; } set value(hexString) { this.object[this.prop].set(hexString); } }
Et voici notre code de configuration de lil-gui
const gui = new GUI(); gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('couleur'); gui.add(light, 'intensity', 0, 5, 0.01);
Et voici le résultat
Cliquez et faites glisser dans la scène pour faire orbiter la caméra.
Remarquez qu'il n'y a pas de définition. Les formes sont plates. La Lumière Ambiante
multiplie simplement la couleur du matériau par la couleur de la lumière multipliée par l'
intensité.
color = materialColor * light.color * light.intensity;
C'est tout. Elle n'a pas de direction. Ce style d'éclairage ambiant n'est pas très utile en tant qu'éclairage car il est uniformément réparti, donc à part changer la couleur de tout dans la scène, il ne ressemble pas beaucoup à un éclairage. Ce qui aide, c'est qu'il rend les zones sombres moins sombres.
HemisphereLight
Passons au code pour une Lumière Hémisphérique
. Une Lumière Hémisphérique
prend une couleur de ciel et une couleur de sol et multiplie simplement la
couleur du matériau entre ces 2 couleurs — la couleur du ciel si la
surface de l'objet pointe vers le haut et la couleur du sol si
la surface de l'objet pointe vers le bas.
Voici le nouveau code
-const color = 0xFFFFFF; +const skyColor = 0xB1E1FF; // light blue +const groundColor = 0xB97A20; // brownish orange const intensity = 1; -const light = new THREE.AmbientLight(color, intensity); +const light = new THREE.HemisphereLight(skyColor, groundColor, intensity); scene.add(light);
Mettons également à jour le code lil-gui pour éditer les deux couleurs
const gui = new GUI(); -gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color'); +gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('couleur du ciel'); +gui.addColor(new ColorGUIHelper(light, 'groundColor'), 'value').name('couleur du sol'); gui.add(light, 'intensity', 0, 5, 0.01);
Le résultat :
Remarquez de nouveau qu'il n'y a presque pas de définition, tout semble un peu
plat. La Lumière Hémisphérique
utilisée en combinaison avec une autre lumière
peut aider à donner une belle influence de la couleur du
ciel et du sol. De cette façon, elle est mieux utilisée en combinaison avec une
autre lumière ou en substitut d'une Lumière Ambiante
.
DirectionalLight
Passons au code pour une Lumière Directionnelle
.
Une Lumière Directionnelle
est souvent utilisée pour représenter le soleil.
const color = 0xFFFFFF; const intensity = 1; const light = new THREE.DirectionalLight(color, intensity); light.position.set(0, 10, 0); light.target.position.set(-5, 0, 0); scene.add(light); scene.add(light.target);
Remarquez que nous avons dû ajouter la light
et la light.target
à la scène. Une Lumière Directionnelle
three.js
brillera dans la direction de sa cible.
Faisons en sorte de pouvoir déplacer la cible en l'ajoutant à notre interface GUI.
const gui = new GUI(); gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('couleur'); gui.add(light, 'intensity', 0, 5, 0.01); gui.add(light.target.position, 'x', -10, 10); gui.add(light.target.position, 'z', -10, 10); gui.add(light.target.position, 'y', 0, 10);
Il est un peu difficile de voir ce qui se passe. Three.js dispose d'un ensemble
d'objets d'aide que nous pouvons ajouter à notre scène pour aider à visualiser
les parties invisibles d'une scène. Dans ce cas, nous utiliserons le
Helper de Lumière Directionnelle
qui dessinera un plan, pour représenter
la lumière, et une ligne de la lumière à la cible. Nous lui
passons simplement la lumière et l'ajoutons à la scène.
const helper = new THREE.DirectionalLightHelper(light); scene.add(helper);
Pendant que nous y sommes, faisons en sorte de pouvoir définir à la fois la position
de la lumière et la cible. Pour ce faire, nous allons créer une fonction
qui, étant donné un Vector3
, ajustera ses propriétés x
, y
, et z
en utilisant lil-gui
.
function makeXYZGUI(gui, vector3, name, onChangeFn) { const folder = gui.addFolder(name); folder.add(vector3, 'x', -10, 10).onChange(onChangeFn); folder.add(vector3, 'y', 0, 10).onChange(onChangeFn); folder.add(vector3, 'z', -10, 10).onChange(onChangeFn); folder.open(); }
Notez que nous devons appeler la fonction update
de l'assistant
chaque fois que nous changeons quelque chose afin que l'assistant sache qu'il doit se mettre à
jour. Ainsi, nous passons une fonction onChangeFn
qui sera
appelée chaque fois que lil-gui met à jour une valeur.
Ensuite, nous pouvons l'utiliser à la fois pour la position de la lumière et pour la position de la cible, comme ceci
+function updateLight() { + light.target.updateMatrixWorld(); + helper.update(); +} +updateLight(); const gui = new GUI(); gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('couleur'); gui.add(light, 'intensity', 0, 5, 0.01); +makeXYZGUI(gui, light.position, 'position', updateLight); +makeXYZGUI(gui, light.target.position, 'cible', updateLight);
Nous pouvons maintenant déplacer la lumière, et sa cible
Faites orbiter la caméra et il devient plus facile de voir. Le plan
représente une Lumière Directionnelle
car une lumière directionnelle
calcule la lumière venant dans une seule direction. Il n'y a pas de
point d'où la lumière provient, c'est un plan infini de lumière
émettant des rayons parallèles.
PointLight
Une Lumière Ponctuelle
est une lumière qui se situe à un point et projette de la lumière
dans toutes les directions à partir de ce point. Modifions le code.
const color = 0xFFFFFF; -const intensity = 1; +const intensity = 150; -const light = new THREE.DirectionalLight(color, intensity); +const light = new THREE.PointLight(color, intensity); light.position.set(0, 10, 0); -light.target.position.set(-5, 0, 0); scene.add(light); -scene.add(light.target);
Passons également à un Helper de Lumière Ponctuelle
-const helper = new THREE.DirectionalLightHelper(light); +const helper = new THREE.PointLightHelper(light); scene.add(helper);
et comme il n'y a pas de cible, la fonction onChange
peut être plus simple.
function updateLight() { - light.target.updateMatrixWorld(); helper.update(); } -updateLight();
Notez qu'à un certain niveau, un Helper de Lumière Ponctuelle
n'a pas de... point.
Il dessine simplement un petit losange en fil de fer. Cela pourrait tout aussi facilement
être n'importe quelle forme que vous souhaitez, il suffit d'ajouter un maillage à la lumière elle-même.
Une Lumière Ponctuelle
a la propriété supplémentaire de distance
.
Si la distance
est 0, alors la Lumière Ponctuelle
brille à
l'infini. Si la distance
est supérieure à 0, alors la lumière brille
à pleine intensité au niveau de la lumière et s'estompe jusqu'à ne plus avoir d'influence à
distance
unités de distance de la lumière.
Configurons l'interface GUI pour que nous puissions ajuster la distance.
const gui = new GUI(); gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('couleur'); gui.add(light, 'intensity', 0, 250, 1); +gui.add(light, 'distance', 0, 40).onChange(updateLight); makeXYZGUI(gui, light.position, 'position', updateLight); -makeXYZGUI(gui, light.target.position, 'target', updateLight);
Et maintenant, essayez.
Remarquez quand distance
est > 0 comment la lumière s'estompe.
SpotLight
Les projecteurs sont effectivement une lumière ponctuelle avec un cône attaché où la lumière ne brille qu'à l'intérieur du cône. Il y a en fait 2 cônes. Un cône extérieur et un cône intérieur. Entre le cône intérieur et le cône extérieur, la lumière s'estompe de la pleine intensité à zéro.
Pour utiliser une Projecteur
, nous avons besoin d'une cible, tout comme
pour la lumière directionnelle. Le cône de la lumière s'ouvrira
vers la cible.
En modifiant notre Lumière Directionnelle
avec l'assistant d'en haut
const color = 0xFFFFFF; -const intensity = 1; +const intensity = 150; -const light = new THREE.DirectionalLight(color, intensity); +const light = new THREE.SpotLight(color, intensity); scene.add(light); scene.add(light.target); -const helper = new THREE.DirectionalLightHelper(light); +const helper = new THREE.SpotLightHelper(light); scene.add(helper);
L'angle du cône du projecteur est défini avec la propriété angle
en radians. Nous utiliserons notre DegRadHelper
de l'article sur les textures
pour présenter une interface utilisateur en
degrés.
gui.add(new DegRadHelper(light, 'angle'), 'value', 0, 90).name('angle').onChange(updateLight);
Le cône intérieur est défini en réglant la propriété pénombre
comme un pourcentage à partir du cône extérieur. En d'autres termes, quand penumbra
est 0, alors le
cône intérieur a la même taille (0 = aucune différence) que le cône extérieur. Quand la
penumbra
est 1, alors la lumière s'estompe en partant du centre du cône jusqu'au
cône extérieur. Quand penumbra
est 0,5, alors la lumière s'estompe en partant de 50 % entre
le centre du cône extérieur.
gui.add(light, 'penumbra', 0, 1, 0.01);
Remarquez qu'avec la penumbra
par défaut de 0, le projecteur a un bord très net,
tandis que lorsque vous ajustez la penumbra
vers 1, le bord devient flou.
Il peut être difficile de voir le cône du projecteur. La raison est qu'il est en dessous du sol. Raccourcissez la distance à environ 5 et vous verrez l'extrémité ouverte du cône.
RectAreaLight
Il existe un autre type de lumière, la Lumière Rectangulaire
, qui représente
exactement ce à quoi cela ressemble : une zone rectangulaire de lumière, comme un long
néon fluorescent ou peut-être une lucarne dépolie dans un plafond.
La Lumière Rectangulaire
ne fonctionne qu'avec les matériaux MeshStandardMaterial
et
MeshPhysicalMaterial
, nous allons donc changer tous nos matériaux en MeshStandardMaterial
... const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize); - const planeMat = new THREE.MeshPhongMaterial({ + const planeMat = new THREE.MeshStandardMaterial({ map: texture, side: THREE.DoubleSide, }); const mesh = new THREE.Mesh(planeGeo, planeMat); mesh.rotation.x = Math.PI * -.5; scene.add(mesh); } { const cubeSize = 4; const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); - const cubeMat = new THREE.MeshPhongMaterial({color: '#8AC'}); + const cubeMat = new THREE.MeshStandardMaterial({color: '#8AC'}); const mesh = new THREE.Mesh(cubeGeo, cubeMat); mesh.position.set(cubeSize + 1, cubeSize / 2, 0); scene.add(mesh); } { const sphereRadius = 3; const sphereWidthDivisions = 32; const sphereHeightDivisions = 16; const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions); - const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'}); + const sphereMat = new THREE.MeshStandardMaterial({color: '#CA8'}); const mesh = new THREE.Mesh(sphereGeo, sphereMat); mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0); scene.add(mesh); }
Pour utiliser la Lumière Rectangulaire
, nous devons inclure des données optionnelles supplémentaires de three.js et nous inclurons le
Helper de Lumière Rectangulaire
pour nous aider à visualiser la lumière
import * as THREE from 'three'; +import {RectAreaLightUniformsLib} from 'three/addons/lights/RectAreaLightUniformsLib.js'; +import {RectAreaLightHelper} from 'three/addons/helpers/RectAreaLightHelper.js';
et nous devons appeler RectAreaLightUniformsLib.init
function main() { const canvas = document.querySelector('#c'); const renderer = new THREE.WebGLRenderer({antialias: true, canvas}); + RectAreaLightUniformsLib.init();
Si vous oubliez les données, la lumière fonctionnera toujours mais elle aura un aspect étrange, alors n'oubliez pas d'inclure les données supplémentaires.
Nous pouvons maintenant créer la lumière
const color = 0xFFFFFF; *const intensity = 5; +const width = 12; +const height = 4; *const light = new THREE.RectAreaLight(color, intensity, width, height); light.position.set(0, 10, 0); +light.rotation.x = THREE.MathUtils.degToRad(-90); scene.add(light); *const helper = new RectAreaLightHelper(light); *light.add(helper);
Une chose à noter est que, contrairement à la Lumière Directionnelle
et au Projecteur
, la
Lumière Rectangulaire
n'utilise pas de cible. Elle utilise simplement sa rotation. Une autre chose
à noter est que l'assistant doit être un enfant de la lumière. Il n'est pas un enfant de la
scène comme les autres assistants.
Ajustons également l'interface GUI. Nous allons faire en sorte de pouvoir faire pivoter la lumière et ajuster
sa width
et sa height
const gui = new GUI(); gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('couleur'); gui.add(light, 'intensity', 0, 10, 0.01); gui.add(light, 'width', 0, 20); gui.add(light, 'height', 0, 20); gui.add(new DegRadHelper(light.rotation, 'x'), 'value', -180, 180).name('rotation x'); gui.add(new DegRadHelper(light.rotation, 'y'), 'value', -180, 180).name('rotation y'); gui.add(new DegRadHelper(light.rotation, 'z'), 'value', -180, 180).name('rotation z'); makeXYZGUI(gui, light.position, 'position');
Et voici cela.
Il est important de noter que chaque lumière que vous ajoutez à la scène ralentit la vitesse de rendu de la scène par three.js, vous devriez donc toujours essayer d'en utiliser le moins possible pour atteindre vos objectifs.
Ensuite, passons à la gestion des caméras.