|
|
<!DOCTYPE html><html lang="fr"><head>
|
|
|
<meta charset="utf-8">
|
|
|
<title>Textures</title>
|
|
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
|
<meta name="twitter:site" content="@threejs">
|
|
|
<meta name="twitter:title" content="Three.js – Textures">
|
|
|
<meta property="og:image" content="https://threejs.org/files/share.png">
|
|
|
<link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
|
|
|
<link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
|
|
|
|
|
|
<link rel="stylesheet" href="../resources/lesson.css">
|
|
|
<link rel="stylesheet" href="../resources/lang.css">
|
|
|
<script type="importmap">
|
|
|
{
|
|
|
"imports": {
|
|
|
"three": "../../build/three.module.js"
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="container">
|
|
|
<div class="lesson-title">
|
|
|
<h1>Textures</h1>
|
|
|
</div>
|
|
|
<div class="lesson">
|
|
|
<div class="lesson-main">
|
|
|
<p>Cet article fait partie d'une série d'articles sur three.js.
|
|
|
Le premier article concernait <a href="fundamentals.html">les bases de three.js</a>.
|
|
|
L'<a href="setup.html">article précédent</a> expliquait comment se préparer pour cet article.
|
|
|
Si vous ne l'avez pas encore lu, vous pourriez vouloir commencer par là.</p>
|
|
|
<p>Les textures sont un sujet assez vaste dans Three.js et
|
|
|
je ne suis pas sûr à 100% du niveau auquel les expliquer, mais je vais essayer.
|
|
|
Il y a de nombreux sujets et beaucoup d'entre eux sont interdépendants, il est donc difficile d'expliquer
|
|
|
tout en une seule fois. Voici une table des matières rapide pour cet article.</p>
|
|
|
<ul>
|
|
|
<li><a href="#hello">Bonjour la texture</a></li>
|
|
|
<li><a href="#six">6 textures, une différente sur chaque face d'un cube</a></li>
|
|
|
<li><a href="#loading">Charger des textures</a></li>
|
|
|
<ul>
|
|
|
<li><a href="#easy">La manière simple</a></li>
|
|
|
<li><a href="#wait1">Attendre le chargement d'une texture</a></li>
|
|
|
<li><a href="#waitmany">Attendre le chargement de plusieurs textures</a></li>
|
|
|
<li><a href="#cors">Charger des textures depuis d'autres origines</a></li>
|
|
|
</ul>
|
|
|
<li><a href="#memory">Utilisation de la mémoire</a></li>
|
|
|
<li><a href="#format">JPG vs PNG</a></li>
|
|
|
<li><a href="#filtering-and-mips">Filtrage et mips</a></li>
|
|
|
<li><a href="#uvmanipulation">Répétition, décalage, rotation, habillage</a></li>
|
|
|
</ul>
|
|
|
|
|
|
<h2 id="-a-name-hello-a-hello-texture"><a name="hello"></a> Bonjour la texture</h2>
|
|
|
<p>Les textures sont <em>généralement</em> des images qui sont le plus souvent créées
|
|
|
dans un programme tiers comme Photoshop ou GIMP. Par exemple, mettons
|
|
|
cette image sur un cube.</p>
|
|
|
<div class="threejs_center">
|
|
|
<img src="../examples/resources/images/wall.jpg" style="width: 600px;" class="border">
|
|
|
</div>
|
|
|
|
|
|
<p>Nous allons modifier un de nos premiers exemples. Tout ce que nous avons à faire est de créer un <a href="/docs/#api/en/loaders/TextureLoader"><code class="notranslate" translate="no">TextureLoader</code></a>. Appelez sa
|
|
|
méthode <a href="/docs/#api/en/loaders/TextureLoader#load"><code class="notranslate" translate="no">load</code></a> avec l'URL d'une
|
|
|
image et définissez la propriété <code class="notranslate" translate="no">map</code> du matériau sur le résultat au lieu de définir sa <code class="notranslate" translate="no">color</code>.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const loader = new THREE.TextureLoader();
|
|
|
+const texture = loader.load( 'resources/images/wall.jpg' );
|
|
|
+texture.colorSpace = THREE.SRGBColorSpace;
|
|
|
|
|
|
const material = new THREE.MeshBasicMaterial({
|
|
|
- color: 0xFF8844,
|
|
|
+ map: texture,
|
|
|
});
|
|
|
</pre>
|
|
|
<p>Notez que nous utilisons <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>, donc pas besoin de lumières.</p>
|
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/textured-cube.html"></iframe></div>
|
|
|
<a class="threejs_center" href="/manual/examples/textured-cube.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
|
|
|
</div>
|
|
|
|
|
|
<p></p>
|
|
|
<h2 id="-a-name-six-a-6-textures-a-different-one-on-each-face-of-a-cube"><a name="six"></a> 6 textures, une différente sur chaque face d'un cube</h2>
|
|
|
<p>Que diriez-vous de 6 textures, une sur chaque face d'un cube ?</p>
|
|
|
<div class="threejs_center">
|
|
|
<div>
|
|
|
<img src="../examples/resources/images/flower-1.jpg" style="width: 100px;" class="border">
|
|
|
<img src="../examples/resources/images/flower-2.jpg" style="width: 100px;" class="border">
|
|
|
<img src="../examples/resources/images/flower-3.jpg" style="width: 100px;" class="border">
|
|
|
</div>
|
|
|
<div>
|
|
|
<img src="../examples/resources/images/flower-4.jpg" style="width: 100px;" class="border">
|
|
|
<img src="../examples/resources/images/flower-5.jpg" style="width: 100px;" class="border">
|
|
|
<img src="../examples/resources/images/flower-6.jpg" style="width: 100px;" class="border">
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<p>Nous créons simplement 6 matériaux et les passons sous forme de tableau lorsque nous créons le <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a></p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loader = new THREE.TextureLoader();
|
|
|
-const texture = loader.load( 'resources/images/wall.jpg' );
|
|
|
-texture.colorSpace = THREE.SRGBColorSpace;
|
|
|
|
|
|
-const material = new THREE.MeshBasicMaterial({
|
|
|
- map: texture,
|
|
|
-});
|
|
|
+const materials = [
|
|
|
+ new THREE.MeshBasicMaterial({map: loadColorTexture('resources/images/flower-1.jpg')}),
|
|
|
+ new THREE.MeshBasicMaterial({map: loadColorTexture('resources/images/flower-2.jpg')}),
|
|
|
+ new THREE.MeshBasicMaterial({map: loadColorTexture('resources/images/flower-3.jpg')}),
|
|
|
+ new THREE.MeshBasicMaterial({map: loadColorTexture('resources/images/flower-4.jpg')}),
|
|
|
+ new THREE.MeshBasicMaterial({map: loadColorTexture('resources/images/flower-5.jpg')}),
|
|
|
+ new THREE.MeshBasicMaterial({map: loadColorTexture('resources/images/flower-6.jpg')}),
|
|
|
+];
|
|
|
-const cube = new THREE.Mesh(geometry, material);
|
|
|
+const cube = new THREE.Mesh(geometry, materials);
|
|
|
|
|
|
+function loadColorTexture( path ) {
|
|
|
+ const texture = loader.load( path );
|
|
|
+ texture.colorSpace = THREE.SRGBColorSpace;
|
|
|
+ return texture;
|
|
|
+}
|
|
|
</pre>
|
|
|
<p>Ça marche !</p>
|
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/textured-cube-6-textures.html"></iframe></div>
|
|
|
<a class="threejs_center" href="/manual/examples/textured-cube-6-textures.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
|
|
|
</div>
|
|
|
|
|
|
<p></p>
|
|
|
<p>Il convient de noter cependant que tous les types de géométrie ne supportent pas plusieurs
|
|
|
matériaux. <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a> peut utiliser 6 matériaux, un pour chaque face.
|
|
|
<a href="/docs/#api/en/geometries/ConeGeometry"><code class="notranslate" translate="no">ConeGeometry</code></a> peut utiliser 2 matériaux, un pour le fond et un pour le cône.
|
|
|
<a href="/docs/#api/en/geometries/CylinderGeometry"><code class="notranslate" translate="no">CylinderGeometry</code></a> peut utiliser 3 matériaux : fond, haut et côté.
|
|
|
Dans d'autres cas, vous devrez construire ou charger une géométrie personnalisée et/ou modifier les coordonnées de texture.</p>
|
|
|
<p>Il est beaucoup plus courant dans d'autres moteurs 3D et beaucoup plus performant d'utiliser un
|
|
|
<a href="https://en.wikipedia.org/wiki/Texture_atlas">atlas de textures</a>
|
|
|
si vous souhaitez autoriser plusieurs images sur une seule géométrie. Un atlas de textures
|
|
|
est un endroit où vous placez plusieurs images dans une seule texture et utilisez ensuite les coordonnées de texture
|
|
|
sur les sommets de votre géométrie pour sélectionner quelles parties d'une texture sont utilisées
|
|
|
sur chaque triangle de votre géométrie.</p>
|
|
|
<p>Que sont les coordonnées de texture ? Ce sont des données ajoutées à chaque sommet d'une pièce de géométrie
|
|
|
qui spécifient quelle partie de la texture correspond à ce sommet spécifique.
|
|
|
Nous les aborderons lorsque nous commencerons à <a href="custom-buffergeometry.html">construire une géométrie personnalisée</a>.</p>
|
|
|
<h2 id="-a-name-loading-a-loading-textures"><a name="loading"></a> Chargement des textures</h2>
|
|
|
<h3 id="-a-name-easy-a-the-easy-way"><a name="easy"></a> La manière simple</h3>
|
|
|
<p>La plupart du code sur ce site utilise la méthode la plus simple pour charger des textures.
|
|
|
Nous créons un <a href="/docs/#api/en/loaders/TextureLoader"><code class="notranslate" translate="no">TextureLoader</code></a>, puis appelons sa méthode <a href="/docs/#api/en/loaders/TextureLoader#load"><code class="notranslate" translate="no">load</code></a>.
|
|
|
Cela renvoie un objet <a href="/docs/#api/en/textures/Texture"><code class="notranslate" translate="no">Texture</code></a>.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const texture = loader.load('resources/images/flower-1.jpg');
|
|
|
</pre>
|
|
|
<p>Il est important de noter qu'en utilisant cette méthode, notre texture sera transparente jusqu'à
|
|
|
ce que l'image soit chargée de manière asynchrone par three.js, moment auquel elle mettra à jour la texture
|
|
|
avec l'image téléchargée.</p>
|
|
|
<p>Cela présente le grand avantage de ne pas avoir à attendre le chargement de la texture et notre
|
|
|
page commencera à s'afficher immédiatement. C'est probablement acceptable pour un grand nombre de cas d'utilisation
|
|
|
mais si nous le souhaitons, nous pouvons demander à three.js de nous informer lorsque la texture a fini de se télécharger.</p>
|
|
|
<h3 id="-a-name-wait1-a-waiting-for-a-texture-to-load"><a name="wait1"></a> Attendre le chargement d'une texture</h3>
|
|
|
<p>Pour attendre le chargement d'une texture, la méthode <code class="notranslate" translate="no">load</code> du chargeur de textures prend un rappel
|
|
|
qui sera appelé lorsque la texture aura fini de se charger. En reprenant notre premier exemple,
|
|
|
nous pouvons attendre le chargement de la texture avant de créer notre <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> et de l'ajouter à la scène
|
|
|
comme ceci</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loader = new THREE.TextureLoader();
|
|
|
loader.load('resources/images/wall.jpg', (texture) => {
|
|
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
|
const material = new THREE.MeshBasicMaterial({
|
|
|
map: texture,
|
|
|
});
|
|
|
const cube = new THREE.Mesh(geometry, material);
|
|
|
scene.add(cube);
|
|
|
cubes.push(cube); // add to our list of cubes to rotate
|
|
|
});
|
|
|
</pre>
|
|
|
<p>À moins que vous ne vidiez le cache de votre navigateur et que vous ayez une connexion lente, il est peu probable
|
|
|
que vous voyiez une différence, mais soyez assuré qu'elle attend le chargement de la texture.</p>
|
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/textured-cube-wait-for-texture.html"></iframe></div>
|
|
|
<a class="threejs_center" href="/manual/examples/textured-cube-wait-for-texture.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
|
|
|
</div>
|
|
|
|
|
|
<p></p>
|
|
|
<h3 id="-a-name-waitmany-a-waiting-for-multiple-textures-to-load"><a name="waitmany"></a> Attendre le chargement de plusieurs textures</h3>
|
|
|
<p>Pour attendre que toutes les textures soient chargées, vous pouvez utiliser un <a href="/docs/#api/en/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a>. Créez-en un
|
|
|
et passez-le au <a href="/docs/#api/en/loaders/TextureLoader"><code class="notranslate" translate="no">TextureLoader</code></a>, puis définissez sa propriété <a href="/docs/#api/en/loaders/managers/LoadingManager#onLoad"><code class="notranslate" translate="no">onLoad</code></a>
|
|
|
sur un rappel.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const loadManager = new THREE.LoadingManager();
|
|
|
*const loader = new THREE.TextureLoader(loadManager);
|
|
|
|
|
|
const materials = [
|
|
|
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
|
|
|
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
|
|
|
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
|
|
|
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
|
|
|
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
|
|
|
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
|
|
|
];
|
|
|
|
|
|
+loadManager.onLoad = () => {
|
|
|
+ const cube = new THREE.Mesh(geometry, materials);
|
|
|
+ scene.add(cube);
|
|
|
+ cubes.push(cube); // add to our list of cubes to rotate
|
|
|
+};
|
|
|
</pre>
|
|
|
<p>Le <a href="/docs/#api/en/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a> a également une propriété <a href="/docs/#api/en/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a>
|
|
|
que nous pouvons définir sur un autre rappel pour afficher un indicateur de progression.</p>
|
|
|
<p>Nous allons d'abord ajouter une barre de progression en HTML</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-html" translate="no"><body>
|
|
|
<canvas id="c"></canvas>
|
|
|
+ <div id="loading">
|
|
|
+ <div class="progress"><div class="progressbar"></div></div>
|
|
|
+ </div>
|
|
|
</body>
|
|
|
</pre>
|
|
|
<p>et le CSS associé</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-css" translate="no">#loading {
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
}
|
|
|
#loading .progress {
|
|
|
margin: 1.5em;
|
|
|
border: 1px solid white;
|
|
|
width: 50vw;
|
|
|
}
|
|
|
#loading .progressbar {
|
|
|
margin: 2px;
|
|
|
background: white;
|
|
|
height: 1em;
|
|
|
transform-origin: top left;
|
|
|
transform: scaleX(0);
|
|
|
}
|
|
|
</pre>
|
|
|
<p>Ensuite, dans le code, nous mettrons à jour l'échelle de la <code class="notranslate" translate="no">progressbar</code> dans notre rappel <code class="notranslate" translate="no">onProgress</code>. Il est
|
|
|
appelé avec l'URL du dernier élément chargé, le nombre d'éléments chargés jusqu'à présent et le nombre total
|
|
|
d'éléments à charger.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const loadingElem = document.querySelector('#loading');
|
|
|
+const progressBarElem = loadingElem.querySelector('.progressbar');
|
|
|
|
|
|
loadManager.onLoad = () => {
|
|
|
+ loadingElem.style.display = 'none';
|
|
|
const cube = new THREE.Mesh(geometry, materials);
|
|
|
scene.add(cube);
|
|
|
cubes.push(cube); // add to our list of cubes to rotate
|
|
|
};
|
|
|
|
|
|
+loadManager.onProgress = (urlOfLastItemLoaded, itemsLoaded, itemsTotal) => {
|
|
|
+ const progress = itemsLoaded / itemsTotal;
|
|
|
+ progressBarElem.style.transform = `scaleX(${progress})`;
|
|
|
+};
|
|
|
</pre>
|
|
|
<p>À moins que vous ne vidiez votre cache et que vous ayez une connexion lente, il est possible que vous ne voyiez
|
|
|
pas la barre de chargement.</p>
|
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/textured-cube-wait-for-all-textures.html"></iframe></div>
|
|
|
<a class="threejs_center" href="/manual/examples/textured-cube-wait-for-all-textures.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
|
|
|
</div>
|
|
|
|
|
|
<p></p>
|
|
|
<h2 id="-a-name-cors-a-loading-textures-from-other-origins"><a name="cors"></a> Charger des textures depuis d'autres origines</h2>
|
|
|
<p>Pour utiliser des images provenant d'autres serveurs, ces serveurs doivent envoyer les en-têtes corrects.
|
|
|
S'ils ne le font pas, vous ne pouvez pas utiliser les images dans three.js et vous obtiendrez une erreur.
|
|
|
Si vous gérez le serveur fournissant les images, assurez-vous qu'il
|
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">envoie les en-têtes corrects</a>.
|
|
|
Si vous ne contrôlez pas le serveur hébergeant les images et qu'il n'envoie pas les
|
|
|
en-têtes d'autorisation, vous ne pouvez pas utiliser les images de ce serveur.</p>
|
|
|
<p>Par exemple, <a href="https://imgur.com">imgur</a>, <a href="https://flickr.com">flickr</a> et
|
|
|
<a href="https://github.com">github</a> envoient tous des en-têtes vous permettant d'utiliser les images
|
|
|
hébergées sur leurs serveurs dans three.js. La plupart des autres sites web ne le font pas.</p>
|
|
|
<h2 id="-a-name-memory-a-memory-usage"><a name="memory"></a> Utilisation de la mémoire</h2>
|
|
|
<p>Les textures sont souvent la partie d'une application three.js qui utilise le plus de mémoire. Il est important de comprendre
|
|
|
qu'<em>en général</em>, les textures prennent <code class="notranslate" translate="no">largeur * hauteur * 4 * 1.33</code> octets de mémoire.</p>
|
|
|
<p>Notez que cela ne dit rien sur la compression. Je peux créer une image .jpg et régler sa compression très élevée.
|
|
|
Par exemple, disons que je créais une scène d'une maison. À l'intérieur de la maison, il y a une table
|
|
|
et je décide de mettre cette texture de bois sur la surface supérieure de la table</p>
|
|
|
<div class="threejs_center"><img class="border" src="../resources/images/compressed-but-large-wood-texture.jpg" align="center" style="width: 300px"></div>
|
|
|
|
|
|
<p>Cette image ne fait que 157k, elle se téléchargera donc relativement rapidement, mais <a href="resources/images/compressed-but-large-wood-texture.jpg">sa taille est en réalité
|
|
|
de 3024 x 3761 pixels</a>.
|
|
|
En suivant l'équation ci-dessus, cela donne</p>
|
|
|
<pre class="prettyprint showlinemods notranslate notranslate" translate="no">3024 * 3761 * 4 * 1.33 = 60505764.5
|
|
|
</pre><p>Cette image prendra <strong>60 MÉGAOCTETS DE MÉMOIRE !</strong> dans three.js.
|
|
|
Quelques textures comme celle-là et vous serez à court de mémoire.</p>
|
|
|
<p>Je soulève ce point car il est important de savoir que l'utilisation des textures a un coût caché.
|
|
|
Pour que three.js puisse utiliser la texture, il doit la transmettre au GPU, et le
|
|
|
GPU <em>en général</em> nécessite que les données de la texture soient décompressées.</p>
|
|
|
<p>La morale de l'histoire est de rendre vos textures petites en dimensions, pas seulement petites
|
|
|
en taille de fichier. Petite taille de fichier = téléchargement rapide. Petites dimensions = prend
|
|
|
moins de mémoire. Quelle taille devraient-elles avoir ?
|
|
|
Aussi petites que possible tout en conservant l'apparence dont vous avez besoin.</p>
|
|
|
<h2 id="-a-name-format-a-jpg-vs-png"><a name="format"></a> JPG vs PNG</h2>
|
|
|
<p>C'est à peu près la même chose qu'en HTML classique : les JPG ont une compression avec perte,
|
|
|
les PNG ont une compression sans perte, donc les PNG sont généralement plus lents à télécharger.
|
|
|
Mais les PNG supportent la transparence. Les PNG sont également probablement le format
|
|
|
approprié pour les données non-image comme les normal maps et d'autres types de maps non-image que nous verrons plus tard.</p>
|
|
|
<p>Il est important de se rappeler qu'un JPG n'utilise
|
|
|
pas moins de mémoire qu'un PNG dans WebGL. Voir ci-dessus.</p>
|
|
|
<h2 id="-a-name-filtering-and-mips-a-filtering-and-mips"><a name="filtering-and-mips"></a> Filtrage et Mips</h2>
|
|
|
<p>Appliquons cette texture 16x16</p>
|
|
|
<div class="threejs_center"><img src="../resources/images/mip-low-res-enlarged.png" class="nobg" align="center"></div>
|
|
|
|
|
|
<p>À un cube</p>
|
|
|
<div class="spread"><div data-diagram="filterCube"></div></div>
|
|
|
|
|
|
<p>Dessinons ce cube très petit</p>
|
|
|
<div class="spread"><div data-diagram="filterCubeSmall"></div></div>
|
|
|
|
|
|
<p>Hmmm, je suppose que c'est difficile à voir. Agrandissons ce tout petit cube</p>
|
|
|
<div class="spread"><div data-diagram="filterCubeSmallLowRes"></div></div>
|
|
|
|
|
|
<p>Comment le GPU sait-il quelles couleurs donner à chaque pixel qu'il dessine pour le petit cube ?
|
|
|
Que se passerait-il si le cube était si petit qu'il ne fasse qu'un ou deux pixels ?</p>
|
|
|
<p>C'est à cela que sert le filtrage.</p>
|
|
|
<p>Si c'était Photoshop, Photoshop ferait la moyenne de presque tous les pixels pour déterminer la couleur
|
|
|
à donner à ces 1 ou 2 pixels. Ce serait une opération très lente. Les GPU résolvent ce problème
|
|
|
en utilisant les mipmaps.</p>
|
|
|
<p>Les mips sont des copies de la texture, chacune faisant la moitié de la largeur et la moitié de la hauteur du mip précédent,
|
|
|
où les pixels ont été mélangés pour créer le mip suivant plus petit. Les mips sont créés
|
|
|
jusqu'à ce que l'on arrive à un mip de 1x1 pixel. Pour l'image ci-dessus, tous les mips ressembleraient
|
|
|
à ceci</p>
|
|
|
<div class="threejs_center"><img src="../resources/images/mipmap-low-res-enlarged.png" class="nobg" align="center"></div>
|
|
|
|
|
|
<p>Maintenant, lorsque le cube est dessiné si petit qu'il ne fait qu'un ou deux pixels, le GPU peut choisir
|
|
|
d'utiliser uniquement le mip le plus petit ou le mip juste avant le plus petit pour décider de la couleur
|
|
|
à donner au petit cube.</p>
|
|
|
<p>Dans three.js, vous pouvez choisir ce qui se passe à la fois lorsque la texture est dessinée
|
|
|
plus grande que sa taille d'origine et ce qui se passe lorsqu'elle est dessinée plus petite que sa
|
|
|
taille d'origine.</p>
|
|
|
<p>Pour définir le filtre lorsque la texture est dessinée plus grande que sa taille d'origine,
|
|
|
vous définissez la propriété <a href="/docs/#api/en/textures/Texture#magFilter"><code class="notranslate" translate="no">texture.magFilter</code></a> sur <code class="notranslate" translate="no">THREE.NearestFilter</code> ou
|
|
|
<code class="notranslate" translate="no">THREE.LinearFilter</code>. <code class="notranslate" translate="no">NearestFilter</code> signifie
|
|
|
simplement choisir le pixel unique le plus proche de la texture d'origine. Avec une texture
|
|
|
basse résolution, cela donne un aspect très pixélisé comme dans Minecraft.</p>
|
|
|
<p><code class="notranslate" translate="no">LinearFilter</code> signifie choisir les 4 pixels de la texture qui sont les plus proches
|
|
|
de l'endroit où nous devrions choisir une couleur et les mélanger dans les
|
|
|
proportions appropriées par rapport à la distance entre le point réel et
|
|
|
chacun des 4 pixels.</p>
|
|
|
<div class="spread">
|
|
|
<div>
|
|
|
<div data-diagram="filterCubeMagNearest" style="height: 250px;"></div>
|
|
|
<div class="code">Plus proche</div>
|
|
|
</div>
|
|
|
<div>
|
|
|
<div data-diagram="filterCubeMagLinear" style="height: 250px;"></div>
|
|
|
<div class="code">Linéaire</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<p>Pour définir le filtre lorsque la texture est dessinée plus petite que sa taille d'origine,
|
|
|
vous définissez la propriété <a href="/docs/#api/en/textures/Texture#minFilter"><code class="notranslate" translate="no">texture.minFilter</code></a> sur l'une des 6 valeurs suivantes.</p>
|
|
|
<ul>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.NearestFilter</code></p>
|
|
|
<p> identique à ci-dessus, choisir le pixel le plus proche dans la texture</p>
|
|
|
</li>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.LinearFilter</code></p>
|
|
|
<p> identique à ci-dessus, choisir 4 pixels de la texture et les mélanger</p>
|
|
|
</li>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.NearestMipmapNearestFilter</code></p>
|
|
|
<p> choisir le mip approprié puis choisir un pixel</p>
|
|
|
</li>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.NearestMipmapLinearFilter</code></p>
|
|
|
<p> choisir 2 mips, choisir un pixel de chaque, mélanger les 2 pixels</p>
|
|
|
</li>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.LinearMipmapNearestFilter</code></p>
|
|
|
<p> choisir le mip approprié puis choisir 4 pixels et les mélanger</p>
|
|
|
</li>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.LinearMipmapLinearFilter</code></p>
|
|
|
<p>choisir 2 mips, choisir 4 pixels de chaque et mélanger les 8 en 1 pixel</p>
|
|
|
</li>
|
|
|
</ul>
|
|
|
<p>Voici un exemple montrant les 6 paramètres</p>
|
|
|
<div class="spread">
|
|
|
<div data-diagram="filterModes" style="
|
|
|
height: 450px;
|
|
|
position: relative;
|
|
|
">
|
|
|
<div style="
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: flex-start;
|
|
|
">
|
|
|
<div style="
|
|
|
background: rgba(255,0,0,.8);
|
|
|
color: white;
|
|
|
padding: .5em;
|
|
|
margin: 1em;
|
|
|
font-size: small;
|
|
|
border-radius: .5em;
|
|
|
line-height: 1.2;
|
|
|
user-select: none;">cliquer pour<br>changer la<br>texture</div>
|
|
|
</div>
|
|
|
<div class="filter-caption" style="left: 0.5em; top: 0.5em;">plus proche</div>
|
|
|
<div class="filter-caption" style="width: 100%; text-align: center; top: 0.5em;">linéaire</div>
|
|
|
<div class="filter-caption" style="right: 0.5em; text-align: right; top: 0.5em;">plus proche<br>mipmap<br>plus proche</div>
|
|
|
<div class="filter-caption" style="left: 0.5em; text-align: left; bottom: 0.5em;">plus proche<br>mipmap<br>linéaire</div>
|
|
|
<div class="filter-caption" style="width: 100%; text-align: center; bottom: 0.5em;">linéaire<br>mipmap<br>plus proche</div>
|
|
|
<div class="filter-caption" style="right: 0.5em; text-align: right; bottom: 0.5em;">linéaire<br>mipmap<br>linéaire</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<p>Une chose à remarquer est que le coin supérieur gauche et le milieu supérieur utilisant <code class="notranslate" translate="no">NearestFilter</code> et <code class="notranslate" translate="no">LinearFilter</code>
|
|
|
n'utilisent pas les mips. De ce fait, ils scintillent au loin car le GPU sélectionne
|
|
|
des pixels de la texture d'origine. À gauche, un seul pixel est choisi et
|
|
|
au milieu, 4 sont choisis et mélangés, mais ce n'est pas suffisant pour obtenir une bonne
|
|
|
couleur représentative. Les 4 autres bandes s'en sortent mieux,
|
|
|
celle en bas à droite, <code class="notranslate" translate="no">LinearMipmapLinearFilter</code>, étant la meilleure.</p>
|
|
|
<p>Si vous cliquez sur l'image ci-dessus, elle basculera entre la texture que nous avons utilisée ci-dessus
|
|
|
et une texture où chaque niveau de mip est d'une couleur différente.</p>
|
|
|
<div class="threejs_center">
|
|
|
<div data-texture-diagram="differentColoredMips"></div>
|
|
|
</div>
|
|
|
|
|
|
<p>Cela rend plus clair
|
|
|
ce qui se passe. Vous pouvez voir en haut à gauche et au milieu supérieur que le premier mip est utilisé jusqu'au loin.
|
|
|
En haut à droite et au milieu inférieur, vous pouvez clairement voir où un mip différent
|
|
|
est utilisé.</p>
|
|
|
<p>En revenant à la texture d'origine, vous pouvez voir que celle en bas à droite est la plus lisse,
|
|
|
de la plus haute qualité. Vous pourriez vous demander pourquoi ne pas toujours utiliser ce mode. La raison
|
|
|
la plus évidente est que parfois vous voulez que les choses soient pixélisées pour un look rétro ou pour une autre raison.
|
|
|
La raison suivante la plus courante est que lire 8 pixels et les mélanger est plus lent
|
|
|
que de lire 1 pixel et de le mélanger. Bien qu'il soit peu probable qu'une seule texture
|
|
|
fasse la différence entre rapide et lent, à mesure que nous progresserons dans ces articles,
|
|
|
nous aurons finalement des matériaux qui utilisent 4 ou 5 textures en même temps.
|
|
|
4 textures * 8 pixels par texture, c'est rechercher 32 pixels pour chaque pixel rendu.
|
|
|
Cela peut être particulièrement important à considérer sur les appareils mobiles.</p>
|
|
|
<h2 id="-a-name-uvmanipulation-a-repeating-offseting-rotating-wrapping-a-texture"><a name="uvmanipulation"></a> Répétition, décalage, rotation, habillage d'une texture</h2>
|
|
|
<p>Les textures ont des paramètres pour la répétition, le décalage et la rotation d'une texture.</p>
|
|
|
<p>Par défaut, les textures dans three.js ne se répètent pas. Pour définir si une
|
|
|
texture se répète ou non, il existe 2 propriétés : <a href="/docs/#api/en/textures/Texture#wrapS"><code class="notranslate" translate="no">wrapS</code></a> pour l'habillage horizontal
|
|
|
et <a href="/docs/#api/en/textures/Texture#wrapT"><code class="notranslate" translate="no">wrapT</code></a> pour l'habillage vertical.</p>
|
|
|
<p>Ils peuvent être définis sur l'une des valeurs suivantes :</p>
|
|
|
<ul>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.ClampToEdgeWrapping</code></p>
|
|
|
<p> le dernier pixel sur chaque bord est répété indéfiniment</p>
|
|
|
</li>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.RepeatWrapping</code></p>
|
|
|
<p> la texture est répétée</p>
|
|
|
</li>
|
|
|
<li><p><code class="notranslate" translate="no">THREE.MirroredRepeatWrapping</code></p>
|
|
|
<p> la texture est mise en miroir et répétée</p>
|
|
|
</li>
|
|
|
</ul>
|
|
|
<p>Par exemple, pour activer l'habillage dans les deux directions :</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">someTexture.wrapS = THREE.RepeatWrapping;
|
|
|
someTexture.wrapT = THREE.RepeatWrapping;
|
|
|
</pre>
|
|
|
<p>La répétition est définie avec la propriété [repeat] repeat.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const timesToRepeatHorizontally = 4;
|
|
|
const timesToRepeatVertically = 2;
|
|
|
someTexture.repeat.set(timesToRepeatHorizontally, timesToRepeatVertically);
|
|
|
</pre>
|
|
|
<p>Le décalage de la texture peut être effectué en définissant la propriété <code class="notranslate" translate="no">offset</code>. Les textures
|
|
|
sont décalées avec des unités où 1 unité = 1 taille de texture. Autrement dit, 0 = pas de décalage
|
|
|
et 1 = décalage d'une quantité de texture complète.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const xOffset = .5; // offset by half the texture
|
|
|
const yOffset = .25; // offset by 1/4 the texture
|
|
|
someTexture.offset.set(xOffset, yOffset);
|
|
|
</pre>
|
|
|
<p>La rotation de la texture peut être définie en définissant la propriété <code class="notranslate" translate="no">rotation</code> en radians
|
|
|
ainsi que la propriété <code class="notranslate" translate="no">center</code> pour choisir le centre de rotation.
|
|
|
Elle est par défaut à 0,0, ce qui correspond à une rotation depuis le coin inférieur gauche. Comme pour le décalage,
|
|
|
ces unités sont en taille de texture, donc les définir à <code class="notranslate" translate="no">.5, .5</code> effectuerait une rotation
|
|
|
autour du centre de la texture.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">someTexture.center.set(.5, .5);
|
|
|
someTexture.rotation = THREE.MathUtils.degToRad(45);
|
|
|
</pre>
|
|
|
<p>Modifions l'exemple du haut ci-dessus pour jouer avec ces valeurs</p>
|
|
|
<p>Tout d'abord, nous allons conserver une référence à la texture afin de pouvoir la manipuler</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const texture = loader.load('resources/images/wall.jpg');
|
|
|
const material = new THREE.MeshBasicMaterial({
|
|
|
- map: loader.load('resources/images/wall.jpg');
|
|
|
+ map: texture,
|
|
|
});
|
|
|
</pre>
|
|
|
<p>Ensuite, nous utiliserons à nouveau <a href="https://github.com/georgealways/lil-gui">lil-gui</a> pour fournir une interface simple.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
|
|
|
</pre>
|
|
|
<p>Comme nous l'avons fait dans les exemples précédents avec lil-gui, nous utiliserons une classe simple pour
|
|
|
donner à lil-gui un objet qu'il peut manipuler en degrés
|
|
|
mais qui définira une propriété en radians.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class DegRadHelper {
|
|
|
constructor(obj, prop) {
|
|
|
this.obj = obj;
|
|
|
this.prop = prop;
|
|
|
}
|
|
|
get value() {
|
|
|
return THREE.MathUtils.radToDeg(this.obj[this.prop]);
|
|
|
}
|
|
|
set value(v) {
|
|
|
this.obj[this.prop] = THREE.MathUtils.degToRad(v);
|
|
|
}
|
|
|
}
|
|
|
</pre>
|
|
|
<p>Nous avons également besoin d'une classe qui convertira une chaîne de caractères comme <code class="notranslate" translate="no">"123"</code> en un
|
|
|
nombre comme <code class="notranslate" translate="no">123</code>, car three.js nécessite des nombres pour les paramètres d'énumération
|
|
|
comme <code class="notranslate" translate="no">wrapS</code> et <code class="notranslate" translate="no">wrapT</code>, mais lil-gui n'utilise que des chaînes de caractères pour les énumérations.</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class StringToNumberHelper {
|
|
|
constructor(obj, prop) {
|
|
|
this.obj = obj;
|
|
|
this.prop = prop;
|
|
|
}
|
|
|
get value() {
|
|
|
return this.obj[this.prop];
|
|
|
}
|
|
|
set value(v) {
|
|
|
this.obj[this.prop] = parseFloat(v);
|
|
|
}
|
|
|
}
|
|
|
</pre>
|
|
|
<p>En utilisant ces classes, nous pouvons configurer une interface graphique simple pour les paramètres ci-dessus</p>
|
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const wrapModes = {
|
|
|
'ClampToEdgeWrapping': THREE.ClampToEdgeWrapping,
|
|
|
'RepeatWrapping': THREE.RepeatWrapping,
|
|
|
'MirroredRepeatWrapping': THREE.MirroredRepeatWrapping,
|
|
|
};
|
|
|
|
|
|
function updateTexture() {
|
|
|
texture.needsUpdate = true;
|
|
|
}
|
|
|
|
|
|
const gui = new GUI();
|
|
|
gui.add(new StringToNumberHelper(texture, 'wrapS'), 'value', wrapModes)
|
|
|
.name('texture.wrapS')
|
|
|
.onChange(updateTexture);
|
|
|
gui.add(new StringToNumberHelper(texture, 'wrapT'), 'value', wrapModes)
|
|
|
.name('texture.wrapT')
|
|
|
.onChange(updateTexture);
|
|
|
gui.add(texture.repeat, 'x', 0, 5, .01).name('texture.repeat.x');
|
|
|
gui.add(texture.repeat, 'y', 0, 5, .01).name('texture.repeat.y');
|
|
|
gui.add(texture.offset, 'x', -2, 2, .01).name('texture.offset.x');
|
|
|
gui.add(texture.offset, 'y', -2, 2, .01).name('texture.offset.y');
|
|
|
gui.add(texture.center, 'x', -.5, 1.5, .01).name('texture.center.x');
|
|
|
gui.add(texture.center, 'y', -.5, 1.5, .01).name('texture.center.y');
|
|
|
gui.add(new DegRadHelper(texture, 'rotation'), 'value', -360, 360)
|
|
|
.name('texture.rotation');
|
|
|
</pre>
|
|
|
<p>La dernière chose à noter à propos de l'exemple est que si vous changez <code class="notranslate" translate="no">wrapS</code> ou
|
|
|
<code class="notranslate" translate="no">wrapT</code> sur la texture, vous devez également définir <a href="/docs/#api/en/textures/Texture#needsUpdate"><code class="notranslate" translate="no">texture.needsUpdate</code></a>
|
|
|
afin que three.js sache qu'il doit appliquer ces paramètres. Les autres paramètres sont appliqués automatiquement.</p>
|
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/textured-cube-adjust.html"></iframe></div>
|
|
|
<a class="threejs_center" href="/manual/examples/textured-cube-adjust.html" target="_blank">cliquez ici pour ouvrir dans une fenêtre séparée</a>
|
|
|
</div>
|
|
|
|
|
|
<p></p>
|
|
|
<p>Ce n'est qu'une étape dans le sujet des textures. À un moment donné, nous aborderons
|
|
|
les coordonnées de texture ainsi que 9 autres types de textures qui peuvent être appliqués
|
|
|
aux matériaux.</p>
|
|
|
<p>Pour l'instant, passons aux <a href="lights.html">lumières</a>.</p>
|
|
|
<!-- alpha ao env light specular bumpmap ? normalmap ? metalness roughness -->
|
|
|
<p><link rel="stylesheet" href="../resources/threejs-textures.css"></p>
|
|
|
<script type="module" src="../resources/threejs-textures.js"></script>
|
|
|
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script src="../resources/prettify.js"></script>
|
|
|
<script src="../resources/lesson.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</body></html> |