Voici une liste d’anti-patterns pour WebGL. Les anti-patterns sont des choses que vous devriez éviter de faire.
Mettre viewportWidth et viewportHeight sur le WebGLRenderingContext
Certains codes ajoutent des propriétés pour leur largeur et hauteur de viewport
et les collent sur le WebGLRenderingContext comme ceci
gl = canvas.getContext("webgl2");
gl.viewportWidth = canvas.width; // MAUVAIS!!!
gl.viewportHeight = canvas.height; // MAUVAIS!!!
Puis plus tard ils pourraient faire quelque chose comme ça
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
Pourquoi c’est mauvais :
C’est objectivement mauvais parce que vous avez maintenant 2 propriétés qui doivent être mises à jour
à chaque fois que vous changez la taille du canvas. Par exemple, si vous changez la taille
du canvas quand l’utilisateur redimensionne la fenêtre, gl.viewportWidth et gl.viewportHeight
seront incorrects sauf si vous les redéfinissez.
C’est subjectivement mauvais car tout nouveau programmeur WebGL jettera un œil à votre code
et pensera probablement que gl.viewportWidth et gl.viewportHeight font partie de la
spécification WebGL, les confondant pendant des mois.
Que faire à la place :
Pourquoi se créer plus de travail ? Le contexte WebGL a son canvas disponible et celui-ci a une taille.
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
Le contexte a aussi sa largeur et sa hauteur directement dessus.
// Quand vous avez besoin de définir le viewport pour correspondre à la taille du
// drawingBuffer du canvas, ceci sera toujours correct
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
C’est encore mieux car ça gérera les cas extrêmes alors qu’utiliser gl.canvas.width
et gl.canvas.height ne le fera pas. Pour savoir pourquoi, voir ici.
Utiliser canvas.width et canvas.height pour le ratio d’aspect
Souvent, le code utilise canvas.width et canvas.height pour le ratio d’aspect comme ça
var aspect = canvas.width / canvas.height;
perspective(fieldOfView, aspect, zNear, zFar);
Pourquoi c’est mauvais :
La largeur et la hauteur du canvas n’ont rien à voir avec la taille à laquelle le canvas est affiché. CSS contrôle la taille à laquelle le canvas est affiché.
Que faire à la place :
Utilisez canvas.clientWidth et canvas.clientHeight. Ces valeurs vous indiquent quelle
taille votre canvas est réellement affiché à l’écran. En utilisant ces valeurs,
vous obtiendrez toujours le bon ratio d’aspect quelle que soit vos paramètres CSS.
var aspect = canvas.clientWidth / canvas.clientHeight;
perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
Voici des exemples d’un canvas dont les drawingbuffers ont la même taille (width="400" height="300")
mais en utilisant CSS, nous avons dit au navigateur d’afficher le canvas à une taille différente.
Remarquez que les deux exemples affichent le ‘F’ avec le bon ratio d’aspect.
Si nous avions utilisé canvas.width et canvas.height, ce ne serait pas le cas.
Utiliser window.innerWidth et window.innerHeight pour calculer quoi que ce soit
De nombreux programmes WebGL utilisent window.innerWidth et window.innerHeight dans de nombreux endroits.
Par exemple :
canvas.width = window.innerWidth; // MAUVAIS!!
canvas.height = window.innerHeight; // MAUVAIS!!
Pourquoi c’est mauvais :
Ce n’est pas portable. Oui, ça peut fonctionner pour les pages WebGL où vous voulez que le canvas remplisse l’écran. Le problème vient quand ce n’est pas le cas. Peut-être décidez-vous de faire un article comme ces tutoriels où votre canvas est juste un petit diagramme dans une page plus grande. Ou peut-être avez-vous besoin d’un éditeur de propriétés sur le côté ou d’un score pour un jeu. Bien sûr, vous pouvez corriger votre code pour gérer ces cas, mais pourquoi ne pas simplement l’écrire pour qu’il fonctionne dans ces cas dès le départ ? Alors vous n’aurez pas à modifier le code quand vous le copiez dans un nouveau projet ou utilisez un ancien projet d’une nouvelle façon.
Que faire à la place :
Au lieu de combattre la plateforme Web, utilisez la plateforme Web comme elle a été conçue pour être utilisée.
Utilisez CSS et clientWidth et clientHeight.
var width = gl.canvas.clientWidth;
var height = gl.canvas.clientHeight;
gl.canvas.width = width;
gl.canvas.height = height;
Voici 9 cas. Ils utilisent tous exactement le même code. Remarquez qu’aucun d’eux
ne fait référence à window.innerWidth ni à window.innerHeight.
Une page avec rien qu’un canvas utilisant CSS pour le mettre en plein écran
Une page avec un canvas intégré dans un paragraphe
Une page avec un canvas intégré dans un paragraphe en utilisant box-sizing: border-box;
box-sizing: border-box; fait que les bordures et le rembourrage prennent de l’espace à l’élément sur lequel ils sont définis plutôt qu’à l’extérieur. En d’autres termes, dans
le mode normal box-sizing, un élément de 400x300 pixels avec une bordure de 15 pixels a un espace de contenu de 400x300 pixels entouré d’une bordure de 15 pixels, faisant que sa taille totale est
430x330 pixels. En mode box-sizing: border-box, la bordure va à l’intérieur de sorte que le même élément resterait à 400x300 pixels, le contenu se retrouverait
à 370x270. C’est encore une autre raison pour laquelle l’utilisation de clientWidth et clientHeight est si importante. Si vous définissez la bordure à 1em, vous n’auriez
aucun moyen de savoir quelle taille aura votre canvas. Ce serait différent avec différentes polices sur différentes machines ou différents navigateurs.
Une page avec un conteneur intégré dans un paragraphe dans lequel le code insérera un canvas
Encore une fois, l’essentiel est que si vous adoptez le web et écrivez votre code en utilisant les techniques ci-dessus, vous n’aurez pas à changer de code quand vous rencontrerez différents cas d’utilisation.
Utiliser l’événement 'resize' pour changer la taille de votre canvas.
Certaines applications vérifient l’événement 'resize' de la fenêtre comme ça pour redimensionner leur canvas.
window.addEventListener('resize', resizeTheCanvas);
ou cela
window.onresize = resizeTheCanvas;
Pourquoi c’est mauvais :
Ce n’est pas mauvais en soi, plutôt, pour la plupart des programmes WebGL, ça couvre moins de cas d’utilisation.
Spécifiquement, 'resize' ne fonctionne que lorsque la fenêtre est redimensionnée. Il ne fonctionne pas
si le canvas est redimensionné pour une autre raison. Par exemple, imaginons que vous faites
un éditeur 3D. Vous avez votre canvas à gauche et vos paramètres à droite. Vous avez
fait en sorte qu’il y a une barre déplaçable séparant les 2 parties et vous pouvez faire glisser cette barre
pour agrandir ou réduire la zone des paramètres. Dans ce cas, vous ne recevrez aucun événement 'resize'.
De même, si vous avez une page où d’autres contenus sont ajoutés ou supprimés et
que le canvas change de taille pendant que le navigateur refait la mise en page, vous ne recevrez pas d’événement de redimensionnement.
Que faire à la place :
Comme pour beaucoup des solutions aux anti-patterns ci-dessus, il y a une façon d’écrire votre code pour qu’il fonctionne simplement dans la plupart des cas. Pour les applications WebGL qui dessinent continuellement chaque frame, la solution est de vérifier si vous devez redimensionner à chaque fois que vous dessinez comme ça
function resizeCanvasToDisplaySize() {
var width = gl.canvas.clientWidth;
var height = gl.canvas.clientHeight;
if (gl.canvas.width != width ||
gl.canvas.height != height) {
gl.canvas.width = width;
gl.canvas.height = height;
}
}
function render() {
resizeCanvasToDisplaySize();
drawStuff();
requestAnimationFrame(render);
}
render();
Maintenant dans n’importe lequel de ces cas, votre canvas s’adaptera à la bonne taille. Pas besoin de changer de code pour différents cas. Par exemple, en utilisant le même code du #3 ci-dessus, voici un éditeur avec une zone d’édition de taille variable.
Il n’y aurait pas d’événements de redimensionnement pour ce cas ni pour tout autre où le canvas est redimensionné en fonction de la taille d’autres éléments dynamiques sur la page.
Pour les applications WebGL qui ne redessinent pas chaque frame, le code ci-dessus est toujours correct, vous aurez juste besoin
de déclencher un redessin dans chaque cas où le canvas peut potentiellement être redimensionné. Une façon facile est d’utiliser un ResizeObserver
const resizeObserver = new ResizeObserver(render);
resizeObserver.observe(gl.canvas, {box: 'content-box'});
Ajouter des propriétés aux WebGLObjects
Les WebGLObjects sont les différents types de ressources dans WebGL comme un WebGLBuffer
ou WebGLTexture. Certaines applications ajoutent des propriétés à ces objets. Par exemple du code comme ça :
var buffer = gl.createBuffer();
buffer.itemSize = 3; // MAUVAIS!!
buffer.numComponents = 75; // MAUVAIS!!
var program = gl.createProgram();
...
program.u_matrixLoc = gl.getUniformLocation(program, "u_matrix"); // MAUVAIS!!
Pourquoi c’est mauvais :
La raison pour laquelle c’est mauvais est que WebGL peut “perdre le contexte”. Cela peut arriver pour n’importe quelle
raison, mais la raison la plus courante est que si le navigateur décide que trop de ressources GPU sont utilisées,
il pourrait intentionnellement perdre le contexte sur certains WebGLRenderingContext pour libérer de l’espace.
Les programmes WebGL qui veulent toujours fonctionner doivent gérer ça. Google Maps le gère par exemple.
Le problème avec le code ci-dessus est que quand le contexte est perdu, les fonctions de création WebGL comme
gl.createBuffer() ci-dessus retourneront null. Cela fait effectivement du code ça
var buffer = null;
buffer.itemSize = 3; // ERREUR!
buffer.numComponents = 75; // ERREUR!
Cela tuera probablement votre application avec une erreur comme
TypeError: Cannot set property 'itemSize' of null
Bien que de nombreuses applications ne se soucient pas de mourir quand le contexte est perdu, il semble que ce soit une mauvaise idée d’écrire du code qui devra être corrigé plus tard si les développeurs décident un jour de mettre à jour leur application pour gérer les événements de perte de contexte.
Que faire à la place :
Si vous voulez garder les WebGLObjects et certaines infos à leur sujet ensemble, une façon serait
d’utiliser des objets JavaScript. Par exemple :
var bufferInfo = {
id: gl.createBuffer(),
itemSize: 3,
numComponents: 75,
};
var programInfo = {
id: program,
u_matrixLoc: gl.getUniformLocation(program, "u_matrix"),
};
Personnellement, je suggère d’utiliser quelques helpers simples qui rendent l’écriture WebGL beaucoup plus simple.
Ce sont quelques-uns de ce que je considère comme des Anti-Patterns WebGL dans du code que j’ai vu sur le net. J’espère avoir montré pourquoi les éviter et avoir donné des solutions faciles et utiles.