Le titre de cette veille disait : « dessiner le mouvement dans un shader au lieu de déplacer un objet ». Après lecture du tutoriel de Maxime Heckel, il faut presque l’inverser.
Les objets bougent déjà. Le shader essaie de comprendre où ce mouvement a laissé une trace.
Dans Shading Motion, publié le 18 août 2026, Heckel part d’une question moins habituelle que « comment animer cette forme ? » : que se passe-t-il si le mouvement lui-même devient une donnée graphique, au même titre que la couleur ou la profondeur ?1
La réponse passe par WebGPU, des compute shaders, deux frames successives et une série de textures intermédiaires. Une fois le mouvement transformé en nombres, il peut devenir chaleur, traînée, blobs, flèches ou flou.

Deux images
La première méthode est presque embarrassante de simplicité.
Pour chaque pixel, le compute shader charge la couleur courante, la transforme en luminance, récupère la luminance enregistrée à la frame précédente, puis prend la valeur absolue de la différence.1
En notation grossière :
mouvement = |luma(frame N) - luma(frame N-1)|
Un smoothstep() élimine ensuite les petites variations sous un seuil. The Book of Shaders décrit précisément cette fonction comme une interpolation d’Hermite entre deux bornes, utile lorsqu’on veut transformer une transition brutale en seuil progressif.2
Le résultat ne connaît ni roue, ni visage, ni sphère. Il connaît seulement les endroits où la luminance a changé.

C’est ce qui rend la technique à la fois puissante et fragile. Une ombre qui change, une variation de lumière ou du bruit vidéo peuvent produire un signal sans qu’un objet se soit déplacé comme on l’imagine. Heckel le présente d’ailleurs comme l’une des façons les plus simples d’estimer le mouvement, suffisante pour une matière créative mais pas comme une vérité géométrique.1
Faire durer
Le masque brut clignote. Un pixel bouge, puis disparaît du signal dès que la frame suivante ressemble à la précédente.
Heckel ajoute donc un état persistant. La traînée précédente est multipliée par un coefficient de decay, diminuée légèrement puis combinée avec le mouvement nouvellement détecté.1
Ce détail change la perception beaucoup plus que la détection elle-même. Le GPU ne découvre pas soudain un déplacement continu ; on lui demande de conserver une mémoire visuelle des différences passées.
Un decay faible efface vite. Un decay proche de 1 laisse une longue queue. La valeur devient donc un réglage de direction artistique : la même détection peut finir en flash sec ou en trace presque calligraphique.1
C’est une bonne leçon de shader : une illusion de mouvement convaincante vient souvent moins d’un modèle physique complet que d’une fonction temporelle choisie avec soin.
Donner direction
Le masque dit où l’image change, mais pas vers où elle va.
Heckel ajoute alors une approximation de flux optique. Pour chaque pixel courant, le shader compare sa luminance avec quatre voisins de l’état précédent : gauche, droite, haut, bas. Les différences opposées deviennent deux composantes d’un vecteur.1
x = matchDroite - matchGauche
y = matchBas - matchHaut
Le résultat est normalisé, mélangé avec la direction précédente et stocké dans une texture : rouge pour la luminance, vert et bleu pour les composantes du mouvement, alpha pour la persistance.1

Cette prudence est importante. L’auteur recommande lui-même OpenCV lorsqu’on cherche de la précision. Ici, le but est d’obtenir une nouvelle entrée pour dessiner, pas une mesure scientifique du déplacement.1
Une grille de flèches peut ensuite échantillonner cette texture. Chaque cellule récupère une direction, calcule son orientation et dessine une flèche avec des fonctions de distance signée. The Book of Shaders décrit cette logique : un cercle, par exemple, peut être défini en demandant à chaque fragment sa distance au centre plutôt qu’en déplaçant une géométrie circulaire.3
Dessiner après
Voilà le véritable déplacement conceptuel du tutoriel.
Une fois la motion map produite, les effets deviennent presque indépendants de la scène originale. Un pixel clair peut être coloré comme une heatmap. Une zone persistante peut devenir masque. Des clusters deviennent boîtes et segments. Une direction devient flèche. Une vélocité devient rayon de flou.1
Le mouvement a été compressé en représentation intermédiaire.
Cette séparation est très transférable. Une fois un phénomène transformé en texture, les shaders suivants n’ont plus besoin de savoir comment il a été obtenu. On peut remplacer une webcam par une scène 3D ou changer la visualisation sans réécrire toute la détection.
Le coût
« C’est sur le GPU » ne veut évidemment pas dire « c’est gratuit ». Khronos rappelle qu’un fragment shader s’exécute pour les fragments produits par la rasterisation, avec des invocations supplémentaires possibles notamment pour les dérivées ou le multisampling.4 Ses recommandations historiques donnent le bon ordre de grandeur : une scène peut avoir quelques milliers de sommets face à des millions de pixels à traiter.5
Le compute pass de Heckel est encore plus explicite : une invocation traite un pixel et la dispatch couvre detectionWidth × detectionHeight à chaque frame.1
Nous avons donc compté uniquement les accès texture visibles dans le code, sans prétendre mesurer un GPU puisque notre environnement local n’expose ni profiler shader ni instrumentation du matériel.
Pour le frame differencing de base, chaque pixel entraîne au minimum :
- une lecture de l’image courante ;
- une lecture de l’état précédent ;
- une écriture du nouvel état.
Soit 3 opérations logiques de lecture/écriture texture par position. La variante de flux ajoute quatre lectures des voisins, donc au moins 7.1

À 1080p et 60 fps, cela représente 124,4 millions de positions de pixels par seconde. Le pass minimal expose donc au moins 373,2 millions de lectures/écritures logiques par seconde ; la variante quatre voisins monte à 870,9 millions.
À 4K, les deux nombres sont multipliés par quatre : environ 1,49 milliard et 3,48 milliards.
Ces chiffres ne sont ni une bande passante mesurée ni un nombre d’instructions GPU. Les caches, formats de texture, workgroups, helper invocations, passes supplémentaires et architecture matérielle changent énormément le temps réel. Ils montrent seulement pourquoi réduire la résolution d’analyse est souvent le premier levier avant de micro-optimiser une formule.
Douze blobs
Le tutoriel illustre d’ailleurs une autre stratégie : après avoir payé le coût pixel par pixel pour créer le masque, Heckel compresse les grands mouvements en seulement 12 slots de blobs.1
Chaque slot stocke quatre floats : centre x/y, taille et confiance. Le buffer complet fait donc 12 × 4 × 4 = 192 octets, calcul confirmé dans l’article.1
La détection n’est pas gratuite : chaque slot échantillonne notamment une grille de 5 × 5, donc 25 positions candidates, avec pondération par mouvement, distance et exclusion.1 Mais une fois ce travail fait, dessiner douze rectangles et quelques segments est très différent de transporter un champ complexe complet jusqu’à chaque décision graphique.
C’est une règle utile : extraire dense, résumer compact, dessiner avec le résumé lorsque l’effet le permet.
Objet connu
Une scène 3D offre une alternative que la vidéo n’a pas.
Si l’on connaît l’objet, sa transformation et la caméra, on peut projeter sa position dans l’écran à deux frames successives et obtenir directement un vecteur de vélocité.1 Heckel expérimente cette voie pour obtenir une velocity map plus exacte, notamment afin d’appliquer le motion blur à un objet même lorsque la caméra ne bouge pas.
Mais la précision déplace la complexité ailleurs : il crée une copie de l’objet dans une scène hors écran, synchronise les positions, applique un matériau encodant la vélocité, rend cette scène dans une target séparée puis mélange le résultat avec les autres données de mouvement.1
L’auteur qualifie lui-même ce chemin de convoluted et limite son usage à un ou deux meshes, en suggérant de réduire les vertices ou d’utiliser des instances.1
Le mouvement « connu » évite donc une partie de l’inférence, pas nécessairement le coût de la représentation.
L’illusion casse
Même une velocity map exacte rencontre une frontière temporelle : l’écran n’observe le mouvement qu’à des instants discrets.
Heckel montre le wagon-wheel effect : au-delà d’une certaine vitesse, une rotation continue peut sembler ralentir, s’arrêter ou partir dans le sens inverse parce que les positions échantillonnées d’une frame à l’autre deviennent ambiguës.18

La correction testée consiste à rendre des copies à des positions intermédiaires prévisibles. Cela fonctionne seulement lorsque la trajectoire peut être évaluée analytiquement entre les frames.1
Ici encore, le shader ne découvre pas davantage de réalité. On lui donne un modèle plus riche de ce qui s’est passé entre deux observations.
Déboguer gris
Le meilleur outil de mise au point du tutoriel n’est finalement pas un profiler. C’est sa manière de rendre visibles les données intermédiaires.
Le masque est affiché en niveaux de gris. Le flux encode x/y en couleurs. Les blobs ont une confiance. Les playgrounds exposent seuil et decay, et certains affichent l’effet à côté de l’image d’origine.1
C’est exactement la bonne stratégie pour un shader temporel : avant de déboguer « l’esthétique finale », afficher la quantité que l’on pense calculer.
Pour une implémentation WebGL, Spector.js sait capturer une frame, montrer les shaders, l’état du contexte et les appels de rendu.6 La pipeline de Heckel est en WebGPU ; Chrome ajoute plusieurs fonctions développeur destinées à l’analyse, parmi lesquelles des options de compilation et, sur certains backends, la transmission de labels vers le débogueur de la plateforme.7
Mais aucune capture ne remplace quelques vues volontairement laides :
- figer ou contrôler l’entrée temporelle ;
- afficher la luminance précédente ;
- afficher la différence brute avant seuil ;
- afficher le masque après
smoothstep; - afficher le trail sans effet final ;
- encoder les vecteurs en RGB ;
- seulement ensuite remettre heatmap, SDF et blur.
Le shader devient soudain beaucoup moins mystique.
Mouvement matière
Le vrai intérêt de Shading Motion n’est donc pas « faire bouger une forme avec des maths ». The Book of Shaders enseigne déjà très bien comment sin, cos, smoothstep et les champs de distance permettent de déplacer ou transformer des formes procédurales.23
Heckel va dans l’autre sens : le mouvement arrive d’abord, puis il devient matière à shader.
Deux frames donnent une différence. Une mémoire donne une traînée. Les voisins donnent une direction approximative. Une scène 3D connue peut donner une vélocité. Ensuite seulement, les fonctions de distance, mélanges et seuils construisent l’image.
Ce changement de perspective est minuscule dans le code et immense dans la manière de penser : on ne dessine plus seulement des objets qui bougent.
On dessine ce que leur passage a laissé dans le temps.