Ceci est le deuxième article d'une série d'articles sur three.js. Le premier article traitait des fondamentaux. Si vous ne l'avez pas encore lu, vous voudrez peut-être commencer par là.
Cet article explique comment rendre votre application three.js réactive à toute situation. Rendre une page web réactive fait généralement référence à la capacité de la page à s'afficher correctement sur des écrans de différentes tailles, des ordinateurs de bureau aux tablettes et téléphones.
Pour three.js, il y a encore plus de situations à considérer. Par exemple, un éditeur 3D avec des contrôles à gauche, à droite, en haut ou en bas est quelque chose que nous pourrions vouloir gérer. Un diagramme interactif au milieu d'un document est un autre exemple.
Le dernier exemple que nous avions utilisait un simple canevas sans CSS et sans taille
<canvas id="c"></canvas>
Ce canevas a par défaut une taille de 300x150 pixels CSS.
Dans la plateforme web, la méthode recommandée pour définir la taille d'un élément est d'utiliser CSS.
Faisons en sorte que le canevas remplisse la page en ajoutant du CSS
<style> html, body { margin: 0; height: 100%; } #c { width: 100%; height: 100%; display: block; } </style>
En HTML, le corps (body) a une marge de 5 pixels par défaut, donc définir la marge à 0 supprime cette marge. Définir la hauteur de html et body à 100 % leur permet de remplir la fenêtre. Sinon, ils ne sont que de la taille du contenu qui les remplit.
Ensuite, nous disons à l'élément id=c
de prendre
100 % de la taille de son conteneur, qui est ici le corps du
document.
Enfin, nous définissons son mode display
à block
. Le mode d'affichage par défaut d'un canevas est inline
. Les éléments inline
peuvent finir par ajouter des espaces blancs à l'affichage. En
définissant le canevas à block
, ce problème disparaît.
Voici le résultat
Vous pouvez voir que le canevas remplit maintenant la page, mais il y a 2 problèmes. Premièrement, nos cubes sont étirés. Ce ne sont pas des cubes, mais plutôt des boîtes. Trop hauts ou trop larges. Ouvrez l'exemple dans sa propre fenêtre et redimensionnez-le. Vous verrez comment les cubes s'étirent en largeur et en hauteur.
Le deuxième problème est qu'ils semblent avoir une faible résolution ou être pixellisés et flous. Élargissez beaucoup la fenêtre et vous verrez vraiment le problème.
Résolvons d'abord le problème de l'étirement. Pour ce faire, nous devons
définir l'aspect de la caméra sur l'aspect de la taille d'affichage du canevas.
Nous pouvons le faire en examinant les propriétés clientWidth
et clientHeight
du canevas.
Nous allons mettre à jour notre boucle de rendu comme ceci
function render(time) { time *= 0.001; + const canvas = renderer.domElement; + camera.aspect = canvas.clientWidth / canvas.clientHeight; + camera.updateProjectionMatrix(); ...
Maintenant, les cubes ne devraient plus être déformés.
Ouvrez l'exemple dans une fenêtre séparée et redimensionnez la fenêtre. Vous devriez voir que les cubes ne sont plus étirés en hauteur ou en largeur. Ils conservent le bon aspect quelle que soit la taille de la fenêtre.
Maintenant, résolvons le problème de la pixellisation.
Les éléments Canvas ont 2 tailles. Une taille est la taille à laquelle le canevas est affiché sur la page. C'est ce que nous définissons avec CSS. L'autre taille est le nombre de pixels dans le canevas lui-même. Ce n'est pas différent d'une image. Par exemple, nous pourrions avoir une image de 128x64 pixels et l'afficher en 400x200 pixels en utilisant CSS.
<img src="some128x64image.jpg" style="width:400px; height:200px">
La taille interne d'un canevas, sa résolution, est souvent appelée sa taille de drawingbuffer.
Dans three.js, nous pouvons définir la taille du drawingbuffer du canevas en appelant renderer.setSize
.
Quelle taille devrions-nous choisir ? La réponse la plus évidente est « la même taille que celle affichée par le canevas ».
Encore une fois, pour ce faire, nous pouvons examiner les propriétés clientWidth
et clientHeight
du canevas.
Écrivons une fonction qui vérifie si le canevas du renderer n'a pas déjà la taille à laquelle il est affiché et, si ce n'est pas le cas, définit sa taille.
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; }
Notez que nous vérifions si le canevas a réellement besoin d'être redimensionné. Le redimensionnement du canevas est une partie intéressante de la spécification du canevas, et il est préférable de ne pas définir la même taille si elle est déjà celle que nous souhaitons.
Une fois que nous savons si nous devons redimensionner ou non, nous appelons alors renderer.setSize
et
passons la nouvelle largeur et hauteur. Il est important de passer false
à la fin.
renderer.setSize
définit par défaut la taille CSS du canevas, mais ce n'est pas
ce que nous voulons. Nous voulons que le navigateur continue à fonctionner comme il le fait pour tous les autres
éléments, c'est-à-dire en utilisant CSS pour déterminer la taille d'affichage de l'élément. Nous ne
voulons pas que les canevas utilisés par three soient différents des autres éléments.
Notez que notre fonction retourne true si le canevas a été redimensionné. Nous pouvons l'utiliser pour vérifier s'il y a d'autres éléments que nous devrions mettre à jour. Modifions notre boucle de rendu pour utiliser la nouvelle fonction
function render(time) { time *= 0.001; + if (resizeRendererToDisplaySize(renderer)) { + const canvas = renderer.domElement; + camera.aspect = canvas.clientWidth / canvas.clientHeight; + camera.updateProjectionMatrix(); + } ...
Comme l'aspect ne changera que si la taille d'affichage du canevas a changé,
nous ne définissons l'aspect de la caméra que si resizeRendererToDisplaySize
retourne true
.
Il devrait maintenant rendre avec une résolution qui correspond à la taille d'affichage du canevas.
Pour illustrer le fait de laisser CSS gérer le redimensionnement, prenons
notre code et mettons-le dans un fichier .js
séparé.
Voici ensuite quelques exemples supplémentaires où nous laissons CSS choisir la taille, et notez
que nous n'avons eu à modifier aucun code pour qu'ils fonctionnent.
Mettons nos cubes au milieu d'un paragraphe de texte.
et voici notre même code utilisé dans une disposition de style éditeur où la zone de contrôle à droite peut être redimensionnée.
Le point important à noter est qu'aucun code n'a été modifié. Seuls notre HTML et notre CSS ont changé.
HD-DPI signifie écrans à haute densité de points par pouce (high-density dot per inch displays). C'est le cas de la plupart des Macs actuels et de nombreuses machines Windows, ainsi que de la quasi-totalité des smartphones.
La façon dont cela fonctionne dans le navigateur est qu'ils utilisent des pixels CSS pour définir les tailles, qui sont censées être les mêmes quelle que soit la résolution de l'écran. Le navigateur se contentera de rendre le texte avec plus de détails, mais avec la même taille physique.
Il existe différentes façons de gérer les écrans HD-DPI avec three.js.
La première consiste simplement à ne rien faire de spécial. C'est sans doute la méthode la plus courante. Le rendu graphique 3D nécessite beaucoup de puissance de traitement GPU. Les GPU mobiles ont moins de puissance que les ordinateurs de bureau, du moins en 2018, et pourtant les téléphones portables ont souvent des écrans à très haute résolution. Les téléphones haut de gamme actuels ont un rapport HD-DPI de 3x, ce qui signifie que pour chaque pixel d'un écran non HD-DPI, ces téléphones ont 9 pixels. Cela signifie qu'ils doivent effectuer 9 fois le rendu.
Calculer 9 fois les pixels représente beaucoup de travail, donc si nous laissons le code tel quel, nous calculerons 1x les pixels et le navigateur se contentera de l'afficher à 3x la taille (3x par 3x = 9x pixels).
Pour toute application three.js lourde, c'est probablement ce que vous voudrez, sinon vous risquez d'avoir une fréquence d'images lente.
Cela dit, si vous souhaitez réellement rendre à la résolution de l'appareil, il existe plusieurs façons de le faire dans three.js.
L'une consiste à indiquer à three.js un multiplicateur de résolution en utilisant renderer.setPixelRatio
.
Vous demandez au navigateur quel est le multiplicateur entre les pixels CSS et les pixels de l'appareil,
et vous le passez à three.js
renderer.setPixelRatio(window.devicePixelRatio);
Après cela, tout appel à renderer.setSize
utilisera magiquement
la taille demandée multipliée par le rapport de pixels
que vous avez passé. Ceci est fortement DÉCONSEILLÉ. Voir ci-dessous
L'autre méthode consiste à le faire vous-même lorsque vous redimensionnez le canevas.
function resizeRendererToDisplaySize(renderer) { const canvas = renderer.domElement; const pixelRatio = window.devicePixelRatio; const width = Math.floor( canvas.clientWidth * pixelRatio ); const height = Math.floor( canvas.clientHeight * pixelRatio ); const needResize = canvas.width !== width || canvas.height !== height; if (needResize) { renderer.setSize(width, height, false); } return needResize; }
Cette seconde méthode est objectivement meilleure. Pourquoi ? Parce que cela signifie que j'obtiens ce que je demande.
Il existe de nombreux cas lors de l'utilisation de three.js où nous devons connaître la taille réelle
du drawingBuffer du canevas. Par exemple, lors de la création d'un filtre de post-traitement,
ou si nous créons un shader qui accède à gl_FragCoord
, si nous faisons
une capture d'écran, ou lisons des pixels pour la sélection GPU, pour dessiner dans un canevas 2D,
etc... Il existe de nombreux cas où si nous utilisons setPixelRatio
, notre taille réelle sera différente
de la taille demandée, et nous devrons deviner quand utiliser la taille
que nous avons demandée et quand utiliser la taille que three.js utilise réellement.
En le faisant nous-mêmes, nous savons toujours que la taille utilisée est la taille que nous avons demandée.
Il n'y a pas de cas particulier où de la magie opère en coulisses.
Voici un exemple utilisant le code ci-dessus.
Il peut être difficile de voir la différence, mais si vous avez un écran HD-DPI et que vous comparez cet exemple à ceux ci-dessus, vous devriez remarquer que les bords sont plus nets.
Cet article a couvert un sujet très basique mais fondamental. Ensuite, passons rapidement en revue les primitives de base que three.js fournit.