|
@@ -0,0 +1,291 @@
|
|
|
+Title: Design réactif et Three.js
|
|
|
+Description: Comment rendre three.js adaptable à des affichages de taille différente.
|
|
|
+TOC: Design réactif
|
|
|
+
|
|
|
+Ceci est le second article dans une série à propos de three.js.
|
|
|
+Le premier était [à propos des principes de base](threejs-fundamentals.html).
|
|
|
+Si vous ne l'avez pas encore lu, vous voudrez peut-être commencer par là.
|
|
|
+
|
|
|
+Cet article indique comment rendre votre application three.js adaptable
|
|
|
+à n'importe quelle situation. Rendre une page web adaptable (*responsive*)
|
|
|
+se réfère généralement à faire en sorte que la page s'affiche de manière
|
|
|
+appropriée sur des affichages de taille différente, des ordinateurs de bureau
|
|
|
+aux *smart-phones*, en passant par les tablettes.
|
|
|
+
|
|
|
+Concernant three.js, il y a d'ailleurs davantage de situations à traiter.
|
|
|
+Par exemple, un éditeur 3D avec des contrôles à gauche, droite, en haut ou
|
|
|
+en bas est quelque chose que nous voudrions gérer. Un schéma interactif
|
|
|
+au milieu d'un document en est un autre exemple.
|
|
|
+
|
|
|
+Le dernier exemple que nous avions utilisé est un canevas sans CSS et
|
|
|
+sans taille :
|
|
|
+
|
|
|
+```html
|
|
|
+<canvas id="c"></canvas>
|
|
|
+```
|
|
|
+
|
|
|
+Ce canevas a par défaut une taille de 300x150 pixels CSS.
|
|
|
+Dans le navigateur, la manière recommandée de fixer la taille
|
|
|
+de quelque chose est d'utiliser CSS.
|
|
|
+
|
|
|
+Paramétrons le canevas pour occuper complètement la page en ajoutant
|
|
|
+du CSS
|
|
|
+
|
|
|
+```html
|
|
|
+<style>
|
|
|
+html, body {
|
|
|
+ margin: 0;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+#c {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+</style>
|
|
|
+```
|
|
|
+
|
|
|
+En HTML, la balise *body* a une marge fixée à 5 pixels par défaut donc
|
|
|
+la changer à 0 la retire. Modifier la hauteur de *html* et *body* à 100%
|
|
|
+leur fait occuper toute la fenêtre. Sinon, ils ne sont seulement aussi large
|
|
|
+que leur contenu.
|
|
|
+
|
|
|
+Ensuite, nous faisons en sort que l'élément `id=c` fasse
|
|
|
+100% de la taille de son conteneur qui, dans ce cas, est le corps du document.
|
|
|
+
|
|
|
+Finalement, nous le passons du mode `display` à celui de `block`.
|
|
|
+Le mode par défaut d'affichage d'un canevas est `inline`, ce qui implique
|
|
|
+que des espaces peuvent y être ajoutés à l'affichage.
|
|
|
+En passant le canevas à `block`, ce problème est supprimé.
|
|
|
+
|
|
|
+Voici le résultat
|
|
|
+
|
|
|
+{{{example url="../threejs-responsive-no-resize.html" }}}
|
|
|
+
|
|
|
+Le canevas, comme nous le voyons, remplit maintenant la page mais il y a 2
|
|
|
+problèmes. Tout d'abord, nos cubes sont étirés et ressemblent à des boîtes, trop
|
|
|
+hautes et trop larges. Ouvrez l'exemple dans sa propre fenêtre et
|
|
|
+redimensionnez la, vous verrez comment les cubes se retrouvent déformés
|
|
|
+en hauteur et en largeur.
|
|
|
+
|
|
|
+<img src="resources/images/resize-incorrect-aspect.png" width="407" class="threejs_center nobg">
|
|
|
+
|
|
|
+Le second problème est qu'ils semblent affichés en basse résolution ou
|
|
|
+à la fois flous et pixellisés. Si vous étirez beaucoup la fenêtre, vous verrez
|
|
|
+pleinement le problème.
|
|
|
+
|
|
|
+<img src="resources/images/resize-low-res.png" class="threejs_center nobg">
|
|
|
+
|
|
|
+Tout d'abord, nous allons résoudre le problème d'étirement.
|
|
|
+Pour cela, nous devons calquer l'aspect de la caméra sur celui
|
|
|
+de la taille d'affichage du canevas. Nous pouvons le faire
|
|
|
+en utilisant les propriétés `clientWidth` et `clientHeight` du canevas.
|
|
|
+
|
|
|
+Nous mettons alors notre boucle de rendu comme cela :
|
|
|
+
|
|
|
+```js
|
|
|
+function render(time) {
|
|
|
+ time *= 0.001;
|
|
|
+
|
|
|
++ const canvas = renderer.domElement;
|
|
|
++ camera.aspect = canvas.clientWidth / canvas.clientHeight;
|
|
|
++ camera.updateProjectionMatrix();
|
|
|
+
|
|
|
+ ...
|
|
|
+```
|
|
|
+
|
|
|
+A présent les cubes ne devraient plus être déformés.
|
|
|
+
|
|
|
+{{{example url="../threejs-responsive-update-camera.html" }}}
|
|
|
+
|
|
|
+Ouvrez l'exemple dans une fenêtre séparée et redimensionnez là.
|
|
|
+Vous devriez voir que les cubes ne sont plus étirés, que ce soit
|
|
|
+en hauteur ou en largeur.
|
|
|
+Ils restent corrects quelque soit l'aspect de la taille de la fenêtre.
|
|
|
+
|
|
|
+<img src="resources/images/resize-correct-aspect.png" width="407" class="threejs_center nobg">
|
|
|
+
|
|
|
+Maintenant résolvons le problème de la pixellisation.
|
|
|
+
|
|
|
+Les éléments de type *canvas* ont deux tailles. La première
|
|
|
+est celle du canevas affiché dans la page. C'est ce que nous paramétrons avec le CSS.
|
|
|
+L'autre taille est le nombre de pixels dont est constitué le canevas lui-même.
|
|
|
+Ceci n'est pas différent d'une image.
|
|
|
+Par exemple, nous pouvons avoir une image de taille 128x64 et, en utilisant le CSS,
|
|
|
+nous pouvons l'afficher avec une taille de 400x200.
|
|
|
+
|
|
|
+```html
|
|
|
+<img src="some128x64image.jpg" style="width:400px; height:200px">
|
|
|
+```
|
|
|
+
|
|
|
+La taille interne d'un canevas, sa résolution, est souvent appelé sa taille de tampon
|
|
|
+de dessin (*drawingbuffer*). Dans three.js, nous pouvons ajuster cette taille
|
|
|
+de canevas en appelant `renderer.setSize`.
|
|
|
+Quelle taille devons nous choisir ? La réponse la plus évident est "la même taille que
|
|
|
+celle d'affichage du canevas". A nouveau, pour le faire, nous pouvons avoir recours
|
|
|
+au propriétés `clientWidth` et `clientHeight`.
|
|
|
+
|
|
|
+Ecrivons une fonction qui vérifie si le canevas du *renderer* est ou non à la taille
|
|
|
+qui est affiché et l'ajuste en conséquence.
|
|
|
+
|
|
|
+```js
|
|
|
+function resizeRendererToDisplaySize(renderer) {
|
|
|
+ const canvas = renderer.domElement;
|
|
|
+ const width = canvas.clientWidth;
|
|
|
+ const height = canvas.clientHeight;
|
|
|
+ const needResize = canvas.width !== width || canvas.height !== height;
|
|
|
+ if (needResize) {
|
|
|
+ renderer.setSize(width, height, false);
|
|
|
+ }
|
|
|
+ return needResize;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Remarquez que nous vérifions sur le canevas a réellement besoin d'être redimensionné.
|
|
|
+Le redimensionnement est une partie intéressante de la spécification du canevas
|
|
|
+et il est mieux de ne pas lui donner à nouveau la même taille s'il est déjà
|
|
|
+à la dimension que nous voulons.
|
|
|
+
|
|
|
+Une fois que nous savons si le redimensionnement est nécessaire ou non, nous
|
|
|
+appelons `renderer.setSize` et lui passons les nouvelles largeur et hauteur.
|
|
|
+Il est important de passer `false` en troisième.
|
|
|
+`render.setSize` modifie par défaut la taille du canevas CSS, mais ce n'est
|
|
|
+pas ce que nous voulons. Nous souhaitons que le navigateur continue à fonctionner
|
|
|
+comme pour les autres éléments, qui est d'utiliser CSS pour déterminer la
|
|
|
+taille d'affichage d'un élément. Nous ne voulons pas que les canevas utilisés
|
|
|
+par three aient un comportement différent des autres éléments.
|
|
|
+
|
|
|
+Remarquez que notre fonction renvoie *true* si le canevas a été redimensionné.
|
|
|
+Nous pouvons l'utiliser pour vérifier si d'autre choses doivent être mises à jour.
|
|
|
+Modifions à présent notre boucle de rendu pour utiliser la nouvelle fonction :
|
|
|
+
|
|
|
+```js
|
|
|
+function render(time) {
|
|
|
+ time *= 0.001;
|
|
|
+
|
|
|
++ if (resizeRendererToDisplaySize(renderer)) {
|
|
|
++ const canvas = renderer.domElement;
|
|
|
++ camera.aspect = canvas.clientWidth / canvas.clientHeight;
|
|
|
++ camera.updateProjectionMatrix();
|
|
|
++ }
|
|
|
+
|
|
|
+ ...
|
|
|
+```
|
|
|
+
|
|
|
+Puisque l'aspect ne change que si la taille d'affichage du canevas change,
|
|
|
+nous ne modifions l'aspect de la caméra que si `resizeRendererToDisplaySize`
|
|
|
+retourne `true`.
|
|
|
+
|
|
|
+{{{example url="../threejs-responsive.html" }}}
|
|
|
+
|
|
|
+Le rendu devrait à présent être avec une résolution qui correspond à
|
|
|
+la taille d'affichage du canevas.
|
|
|
+
|
|
|
+Afin de comprendre pourquoi il faut laisser CSS gérer le redimensionnement,
|
|
|
+prenons notre code et mettons le dans un [fichier `.js` séparé](../threejs-responsive.js).
|
|
|
+
|
|
|
+Voici donc quelques autres exemples où nous avons laissé CSS choisir la taille et remarquez que nous n'avons
|
|
|
+eu aucun code à modifier pour qu'ils fonctionnent.
|
|
|
+
|
|
|
+Mettons nos cubes au milieu d'un paragraphe de texte.
|
|
|
+
|
|
|
+{{{example url="../threejs-responsive-paragraph.html" startPane="html" }}}
|
|
|
+
|
|
|
+et voici notre même code utilisé dans un éditeur
|
|
|
+où la zone de contrôle à droite peut être redimensionnée.
|
|
|
+
|
|
|
+{{{example url="../threejs-responsive-editor.html" startPane="html" }}}
|
|
|
+
|
|
|
+Le point important à remarquer est que le code n'est pas modifié, seulement
|
|
|
+le HTML et le CSS.
|
|
|
+
|
|
|
+## Gérer les affichages HD-DPI
|
|
|
+
|
|
|
+HD-DPI est l'acronyme pour *high-density dot per inch*,
|
|
|
+autrement dit, les écrans à haute densité d'affichage.
|
|
|
+C'est le cas de la plupart des Macs, des machines sous Windows
|
|
|
+ainsi que des smartphones.
|
|
|
+
|
|
|
+La façon dont cela fonctionne dans le navigateur est
|
|
|
+qu'il utilise les pixels CSS pour mettre à jour la taille
|
|
|
+qui est supposée être la même quelque soit la résolution de
|
|
|
+l'affichage. Le navigateur effectue le rendu du texte avec davantage
|
|
|
+de détails mais la même taille physique.
|
|
|
+
|
|
|
+Il y a plusieurs façons de gérer les HD-DPI avec three.js.
|
|
|
+
|
|
|
+La première façon est de ne rien faire de spécial. Cela
|
|
|
+est, de manière discutable, le plus commun. Effectuer le
|
|
|
+rendu de graphismes 3D prend beaucoup de puissance de calcul GPU
|
|
|
+(*Graphics Processing Units*, les processeurs dédiés de carte graphique).
|
|
|
+Les GPUs mobiles ont moins de puissance que les ordinateurs de bureau,
|
|
|
+du moins en 2018, et pourtant les téléphones mobules ont des affichages
|
|
|
+haute résolution. Le haut de gamme actuel pour les smartphones a un ratio
|
|
|
+HD-DPI de 3x, ce qui signifie que pour chaque pixel d'un affichage non HD-DPI,
|
|
|
+ces téléphones ont 9 pixels. Il y a donc 9 fois plus de travail
|
|
|
+pour le rendu.
|
|
|
+
|
|
|
+Calculer pour 9 pixels nécessite des ressources donc, si
|
|
|
+nous laissons le code comme cela, nous calculerons pour 1 pixel
|
|
|
+et le navigateur le dessinera avec 3 fois sa taille (3 x 3 = 9 pixels).
|
|
|
+
|
|
|
+Pour toute application three.js lourde, c'est probablement ce que vous
|
|
|
+voulez sinon vous risquez d'avoir un taux de rafraîssement faible (*framerate*).
|
|
|
+
|
|
|
+Ceci étant dit, si vous préférez effectuer le rendu à la résolution de l'appareil,
|
|
|
+voici quelques façons de le faire en three.js.
|
|
|
+
|
|
|
+La première est d'indiquer à three.js le facteur de multiplication de la résolution
|
|
|
+en utilisant `renderer.setPixelRatio`. Nous pouvons demander au navigateur ce
|
|
|
+facteur entre les pixels CSS et les pixels du périphérique et les passer à three.js
|
|
|
+
|
|
|
+ renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
+
|
|
|
+Après cela, tout appel à `renderer.setSize` va automatiquement
|
|
|
+utiliser la taille que vous avez demandée, multipliée par le
|
|
|
+ratio que vous avez demandé.
|
|
|
+**Ceci est fortement DÉCONSEILLÉ**. Voir ci-dessous.
|
|
|
+
|
|
|
+L'autre façon est de le faire par soi-même quand on redimensionne le canevas.
|
|
|
+
|
|
|
+```js
|
|
|
+ function resizeRendererToDisplaySize(renderer) {
|
|
|
+ const canvas = renderer.domElement;
|
|
|
+ const pixelRatio = window.devicePixelRatio;
|
|
|
+ const width = canvas.clientWidth * pixelRatio | 0;
|
|
|
+ const height = canvas.clientHeight * pixelRatio | 0;
|
|
|
+ const needResize = canvas.width !== width || canvas.height !== height;
|
|
|
+ if (needResize) {
|
|
|
+ renderer.setSize(width, height, false);
|
|
|
+ }
|
|
|
+ return needResize;
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+Cette seconde façon est objectivement meilleure. Pourquoi ? Parce que cela signifie
|
|
|
+que nous avons ce que nous avons demandé. Il y a plusieurs cas où,
|
|
|
+quand on utilise three.js, nous avons besoin de savoir la taille effective
|
|
|
+du tampon d'affichage du canevas. Par exemple, quand on réalise un filtre de
|
|
|
+post-processing, ou si nous faisons un *shader* qui accède à `gl_FragCoord`,
|
|
|
+si nous sommes en train de faire une capture d'écran, ou en train de lire les pixels
|
|
|
+pour une sélection par GPU, pour dessiner dans un canevas 2D, etc...
|
|
|
+Il y a plusieurs cas où, si nous utilisons `setPixelRatio` alors notre
|
|
|
+taille effective est différente de la taille que nous avons demandé et nous
|
|
|
+aurons alors à deviner quand utiliser la taille demandée ou la taille utilisée
|
|
|
+par three.js.
|
|
|
+En le faisant par soi-même, nous savons toujours que la taille utilisée
|
|
|
+est celle que nous avons demandé. Il n'y a aucun cas où cela se fait tout
|
|
|
+seul autrement.
|
|
|
+
|
|
|
+Voici un exemple utilisant le code vu plus haut.
|
|
|
+
|
|
|
+{{{example url="../threejs-responsive-hd-dpi.html" }}}
|
|
|
+
|
|
|
+Cela devrait être difficile de voir la différence, mais si vous avez
|
|
|
+un affichage HD-DPI et que vous comparez cet exemple aux autres plus
|
|
|
+haut, vous devriez remarquer que les arêtes sont plus vives.
|
|
|
+
|
|
|
+Cet article a couvert un sujet très basique mais fondamental.
|
|
|
+Ensuite, nous allons rapidement
|
|
|
+[passer en revue les primitives de base proposées par three.js](threejs-primitives.html).
|
|
|
+
|