En 1994, sur Mega Drive et Super Nintendo, le jeu de combat Ballz alignait des combattants tout en sphères. L’année suivante, son studio, PF.Magic, recyclait ce moteur pour Dogz, un chiot virtuel qui gambadait sur l’écran des PC. Une trentaine d’années plus tard, ces boules plates ont un héritier dans le navigateur : Zdog.

Son auteur le dit « directement inspiré » de Dogz. Et il a rappelé en mars 2019 que ce moteur avait d’abord servi à Ballz.

Zdog dessine de la 3D en formes plates, en Canvas ou en SVG, avec 7 Kio de code compressé. Sa dernière version date de janvier 2022, et le projet se présente toujours comme une bêta. Elle tourne pourtant sans broncher dans un navigateur de 2026.

Voici ce qu’il sait faire, ce qui lui manque et une première scène en quelques lignes. Puis notre banc d’essai face à three.js.

 Illustration en Zdog d'un bol de ramen bleu avec œuf, chashu, naruto et baguettes, à côté d'une assiette de dango
: Hémisphère, ellipses, rectangles et traits épais : notre bol de ramen ne contient rien d’autre. Rendu Canvas.

Zdog, c’est quoi ?

Zdog n’est pas un logiciel de 3D : c’est une bibliothèque JavaScript, libre sous licence MIT. On la doit à David DeSandro, l’auteur d’Isotope et de Flickity. Il voulait créer un jeu vidéo et cherchait un moteur 3D léger. Il a fini par écrire le sien, avec un but : apporter à la 3D la simplicité de l’illustration vectorielle.

Zdog se prononce « zi-dog » à l’américaine, « zèd-dog » à la britannique. Il dessine des cercles, des rectangles, des polygones, des hémisphères, des cônes, des cylindres et des boîtes. Pour une sphère, on trace un seul point avec un trait très épais.

Selon son auteur, la bibliothèque tient en 2 100 lignes et 28 Ko minifiés. C’est presque juste : la version 1.1.3 en pèse 29 Kio. Compressée en gzip, elle tombe à 7 Kio. La version 1.0.0 a été publiée sur npm le 21 mai 2019. Son dépôt GitHub affiche aujourd’hui 10 700 étoiles.

Comment Zdog fait-il de la 3D avec des formes plates ?

Chaque forme existe dans un espace en trois dimensions. À chaque image, Zdog projette les points de chaque forme sur l’écran. Puis il la dessine comme un simple tracé 2D, aux bords arrondis. Le tout passe par le Canvas 2D ou le SVG, jamais par WebGL. Pas de shaders : rien que les outils de dessin les plus simples du navigateur.

Ce choix a un prix. D’abord, pas de perspective : une boule au loin garde la taille d’une boule toute proche. On l’a vérifié avec deux sphères placées à 300 unités d’écart, rendues au même diamètre.

Ensuite, pas de lumière ni d’ombre. L’API ne connaît que des couleurs unies. Le relief, c’est toi qui le peins. Une teinte plus sombre pour la base d’un hémisphère, une autre pour la face arrière d’un cylindre.

Une première scène en 25 lignes

Un dango, trois boules sur une brochette, suffit à faire le tour des bases. Colle ce code dans un fichier .html et ouvre-le dans ton navigateur.

<canvas class="dango" width="320" height="320"></canvas>
<script src="https://unpkg.com/zdog@1/dist/zdog.dist.min.js"></script>
<script>
  const illo = new Zdog.Illustration({ element: '.dango', dragRotate: true });

  // la brochette, penchée à 45 degrés
  const brochette = new Zdog.Anchor({ addTo: illo, rotate: { z: -Zdog.TAU / 8 } });

  // trois boules : un seul point, un trait très épais
  ['#F4A6B8', '#FBF8F2', '#8DBA6A'].forEach((color, i) => {
    new Zdog.Shape({ addTo: brochette, translate: { y: (i - 1) * 58 }, stroke: 56, color });
  });

  // le bâton, en deux bouts qui partent de la surface des boules
  [[-86, -125], [86, 125]].forEach(([from, to]) => {
    new Zdog.Shape({ addTo: brochette, path: [{ y: from }, { y: to }], stroke: 6, color: '#B0844C' });
  });

  function animate() {
    illo.rotate.y += 0.02;
    illo.updateRenderGraph();
    requestAnimationFrame(animate);
  }
  animate();
</script>

Illustration désigne la zone de dessin. Anchor est un point d’attache invisible, penché ici à 45 degrés. Chaque boule est un point unique, avec un trait de 56 pixels. updateRenderGraph recalcule et redessine la scène à chaque passage de requestAnimationFrame. Et dragRotate laisse la souris faire tourner le tout.

Pour passer en SVG, une seule balise change : <svg class="dango" width="320" height="320"></svg> à la place du <canvas>. Le reste du code ne bouge pas.

Deux rendus identiques du dango en Zdog, trois boules rose, blanche et verte sur une brochette, l'un en Canvas, l'autre en SVG
Le même code, la même image : seule la balise de départ change.

Ce que Zdog ne sait pas faire

Zdog ne calcule pas les collisions entre formes. Il trie chacune selon la moyenne de ses points, puis les dessine de l’arrière vers l’avant. Tant que les formes restent séparées, tout va bien. Dès qu’elles se traversent, le tri déraille.

Notre dango en est la preuve. Un bâton d’un seul tenant est trié sur son centre, à hauteur de la boule du milieu. Selon l’angle, il passe alors devant les boules. Coupé en deux bouts qui partent de la surface des boules, chaque morceau retrouve sa place.

Deux rendus du dango en Zdog : à gauche, le bâton traverse visiblement les boules ; à droite, il reste caché derrière elles
À gauche, le bâton entier est trié sur son centre et passe devant. À droite, ses deux bouts se trient chacun à leur place.

La documentation donne un nom à ce conflit de profondeur, le z-fighting, et l’assume avec humour. C’est même l’un des « charmes » de Zdog, écrit-elle, avant d’inviter à l’accepter. Elle propose aussi une parade, le Group, qui dessine ses formes dans l’ordre où on les ajoute. C’est ce qu’utilise notre bol de ramen, vu sous un angle fixe.

Le reste de la liste est court mais net. Pas de texture, pas d’import de modèles : tout se construit en code. L’import de tracés SVG, réclamé depuis mai 2019 et soutenu par 125 votes, attend toujours. Et plus les formes s’accumulent, plus le dessin ralentit, comme le montre notre banc d’essai.

Canvas ou SVG ? Notre banc d’essai

On a fait tourner un nuage de sphères, de 50 à 3 200 formes, sur deux tailles d’image. À chaque fois, on a mesuré le temps pour calculer et afficher une image, dans un Chromium sans carte graphique.

Le Canvas l’emporte partout, de 1,5 à 3,3 fois plus vite. La barre à surveiller, c’est 16,7 millisecondes par image, soit 60 images par seconde. Sur une image de 500 pixels de côté, le Canvas reste dessous jusqu’à 3 200 formes. Le SVG la franchit entre 1 600 et 3 200 formes. Sur une image de 1 500 pixels, le Canvas décroche entre 1 600 et 3 200 formes. Le SVG, lui, lâche prise entre 400 et 800.

La documentation conseille d’ailleurs le Canvas pour les scènes chargées, et l’explique par le DOM. En SVG, chaque forme est un élément de la page, à dessiner et à trier. À chaque image, Zdog vide le SVG, puis y réinsère chaque forme dans l’ordre de profondeur. Notre banc chronomètre ce manège. À 3 200 formes, il coûte 14 à 17 millisecondes de JavaScript, environ quatre fois plus qu’en Canvas.

Le Canvas, lui, peint des pixels, et paie la taille de l’image. À 3 200 formes, son temps par image est multiplié par 2,6 entre 500 et 1 500 pixels de côté. Celui du SVG, par 1,4. D’où le conseil de la doc : le SVG pour peu de formes sur une grande image. Chez nous, l’écart se resserre bien, mais le SVG ne passe jamais devant.

Sous la ligne pointillée, l’animation tient 60 images par seconde. Jusqu’à 200 formes, les deux modes restent loin de la limite.

Pour quelques dizaines de formes, la vitesse ne départage rien : moins de 7 millisecondes dans tous les cas. Le SVG garde alors deux atouts. Il reste net à toutes les tailles. Et ses éléments de page, qui le ralentissent en masse, changent de camp. On peut les styler, ou enregistrer le tout en fichier .svg. Rendue en SVG, notre illustration de ramen ne diffère de la version Canvas que sur 0,06 % des pixels. Tous au bord des formes.

Zdog ou three.js ?

Pour la même boule qui tourne, empaquetée et compressée de la même façon, Zdog pèse 7,8 Kio. three.js en demande 129, et Babylon.js 332. Charger la bibliothèque entière creuse l’écart : 181 Kio pour three.js, plus de 1,8 Mio pour Babylon.js.

Ce poids achète de vraies capacités. Lumières, ombres, textures, perspective, modèles glTF exportés de Blender : three.js fait tout ce que Zdog ne fait pas. Il s’appuie pour cela sur WebGL et la carte graphique.

Graphique en barres du poids compressé d'une scène minimale : 7,8 Kio pour Zdog, 129,3 Kio pour three.js, 332 Kio pour Babylon.js
La même boule qui tourne, empaquetée et minifiée avec esbuild, puis compressée : Zdog pèse 17 fois moins que three.js.

Le partage des rôles est donc simple. Zdog pour une illustration, un logo animé, une icône qu’on fait pivoter. Bref, quelques dizaines de formes au rendu doux et rond. three.js dès qu’il faut éclairer, texturer ou charger un modèle. Et pour monter une scène 3D web sans écrire de code, Spline fait le même travail à la souris.

Zdog est-il abandonné ?

Il n’évolue plus, c’est certain. La dernière version, la 1.1.3, a été publiée sur npm le 22 janvier 2022. Le README du projet annonce pourtant toujours « beaucoup de changements pour la v2 ». Cette v2 n’est jamais sortie, et le dépôt compte 46 tickets ouverts.

Mais Zdog ne s’appuie que sur des briques stables du navigateur, le Canvas 2D et le SVG. C’est pour ça qu’il tourne toujours, tel quel, près de cinq ans après sa dernière mise à jour. Sous licence MIT, rien n’empêche de copier le fichier sur ton propre serveur plutôt que de dépendre d’un CDN. Et ses 2 100 lignes se lisent en une soirée, si un jour il faut le corriger soi-même.

Un os à ronger pour se lancer


Copie le dango

Colle le code de cet article dans un fichier .html et ouvre-le. La brochette tourne, et la souris la fait pivoter.


Change de formes

Remplace les boules par Ellipse, Rect ou Hemisphere, et joue sur les réglages stroke et fill.


Bascule en SVG

Change la balise de départ et compare. Au-delà de quelques centaines de formes, reviens au Canvas.


Range ce qui se chevauche

Si une forme passe devant à tort, coupe-la en morceaux ou range-la dans un Group.


La documentation et les démos officielles attendent sur zzz.dog.