3 points par GN⁺ 2024-01-25 | 1 commentaires | Partager sur WhatsApp
  • L’équipe Ctrl-Alt-Test a commencé en novembre 2022 une expérimentation visant à faire tenir dans 8 ko une démo temps réel ressemblant à un court métrage d’animation, puis a publié The Sheep and the Flower en avril 2023
  • Le résultat est un unique .exe qui génère les graphismes et la musique, sans fichiers de ressources ni dépendances externes ; les scènes sont calculées en temps réel sur le GPU avec des shaders GLSL et du raymarching
  • Le mouton, les panneaux, la caméra et la timeline ne reposent pas sur des modèles ou textures stockés, mais sur des SDF, des textures 3D procédurales et des paramètres temporels codés en dur ; environ 25 plans de caméra ont été ajustés à la main
  • La musique a été créée avec OpenMPT et 4klang ; le nombre d’instruments a été réduit de 16 à 13 et les répétitions ont été augmentées pour améliorer l’efficacité de la compression
  • Les 8 ko finaux ont été rendus possibles par la compression Crinkler et les améliorations de Shader Minifier ; les 42 ko de code shader tiennent dans environ 5 ko après minification et compression, laissant de la place à la musique et au code C++

Un exécutable unique tenant dans 8 ko

  • The Sheep and the Flower est une animation temps réel publiée en avril 2023 sous forme d’exécutable Windows
  • Un seul fichier .exe génère tout, sans fichiers de ressources séparés ni dépendances externes
    • Son exécution nécessite Windows et des pilotes récents
  • L’objectif était de faire tenir les graphismes, l’animation, la mise en scène, les mouvements de caméra et la musique dans 8 ko
  • Le code source est publié sur GitHub : ctrl-alt-test/mouton

Les outils utilisés pour les graphismes, la musique et la compression

  • Tous les éléments visuels sont calculés en temps réel sur le GPU
    • Le shader GLSL contient les informations de timeline, les réglages de caméra et la représentation de la scène
    • Le rendu est effectué par raymarching
  • Le shader est minifié avec Shader Minifier
  • La musique est produite avec OpenMPT et le synthétiseur 4klang
    • Les instruments sont décrits de manière procédurale
    • La liste des notes est compressée
  • Le code est écrit en C++ dans Visual Studio 2022
  • Les flags du compilateur et l’initialisation partent du framework Leviathan
  • L’exécutable final est compressé avec Crinkler

Pourquoi 8 ko

  • Dans la demoscene, les catégories avec limites de taille comme 4 ko et 64 ko sont courantes
  • Les travaux précédents visaient surtout 64 ko, mais 8 ko impose des contraintes et des règles totalement différentes
  • Les techniques d’intro 4 ko sont intéressantes, mais jugées trop limitées pour un véritable storytelling
  • La Revision demoparty a ajouté il y a quelques années une catégorie de compétition en 8 ko, ce qui en a fait une bonne occasion d’expérimenter

Un monde dessiné avec deux triangles

  • Comme c’est devenu standard dans la demoscene depuis 2008, un rectangle couvrant tout l’écran est dessiné avec deux triangles
  • Sur ce rectangle, un shader GLSL calcule la couleur de chaque pixel et de chaque frame
  • La fonction du shader prend des coordonnées et le temps en entrée, puis renvoie une couleur
  • Le défi central était de représenter le mouton et l’ensemble de la scène dans cette fonction

Composer la scène avec des SDF

  • Un champ de distance signé (SDF) est une fonction qui calcule la distance entre un point de l’espace et l’objet le plus proche
    • Elle renvoie 0 à la surface
    • Elle renvoie une valeur négative à l’intérieur d’un objet
  • Des primitives comme des sphères ou des boîtes peuvent être exprimées par des formules simples
  • La scène est construite en combinant plusieurs champs de distance
    • L’union de deux objets peut être obtenue avec le minimum des distances
    • Leur intersection peut être obtenue avec le maximum
    • Une union lissée (smooth union) permet de créer des formes plus organiques
  • Le mouton a été créé en assemblant et fusionnant des formes simples comme des cônes et des sphères, tandis que son corps et sa laine sont construits avec une fonction de bruit 3D

Comment le raymarching détermine les pixels

  • Le raymarching est une technique de rendu qui utilise les SDF pour faire avancer progressivement un rayon dans une scène 3D
  • Contrairement au raytracing traditionnel, elle ne calcule pas directement les intersections de manière mathématique
  • Le SDF indique la distance qui peut être parcourue sans collision, puis le rayon est avancé d’autant ; ce processus est répété
  • Lorsque la distance devient nulle ou proche de zéro, l’intersection est considérée comme trouvée
  • Une fois l’intersection trouvée, on sait si le pixel courant correspond au mouton, au ciel ou à un autre objet
  • L’éclairage nécessite une normale de surface, estimée en calculant le gradient autour du point
  • Les ombres sont calculées en envoyant un autre rayon du point d’intersection vers le soleil pour vérifier s’il rencontre un objet sur son trajet

Raconter une histoire avec peu de moyens expressifs

  • La démo ne contient ni voix ni texte, et ne comporte qu’un seul personnage
  • Le mouton peut seulement marcher et bouger la tête et les yeux ; il ne peut même pas tourner
  • Pour transmettre des émotions dans ces contraintes, les éléments sans rapport avec l’histoire ont été supprimés
    • Au départ, un désert, des dunes de sable et un ciel avaient été envisagés, mais ils ont été jugés inutiles pour l’histoire
    • Le décor reste entièrement blanc
    • Les textures ont été réduites, sauf pour les panneaux et les yeux, qui portent du sens
  • En conservant un périmètre réduit, l’équipe s’est concentrée sur les détails, le polissage, le travail de caméra, le montage et la synchronisation musicale
  • Chaque plan a été composé à la main, et l’animation a été ajustée à de nombreuses reprises pour que le déroulé paraisse naturel

Caméra et mise en scène

  • La caméra sert d’outil narratif
  • Les scènes où le mouton erre longtemps utilisent des plans larges pour créer un sentiment de solitude
  • Au moment où il remarque le panneau, un très gros plan sur l’œil est utilisé
  • Quand le mouton regarde le panneau, un lent zoom vers sa tête renforce la concentration
  • Son excitation est transmise en superposant plusieurs effets
    • Des effets 2D de type cartoon
    • Une marche plus rapide
    • Un mouvement de queue accentué
    • Des hochements de tête exagérés
    • Un changement dramatique de la musique

Une méthode de développement pour itérer vite

  • Le code source contient beaucoup de constantes codées en dur
    • La taille des yeux du mouton
    • La vitesse de déplacement de la caméra
    • La durée de chaque plan
    • Des valeurs comme la couleur de la fleur, ajustées à plusieurs reprises
  • Pour obtenir un feedback rapide, le shader est recompilé à l’exécution afin de mettre à jour les graphismes en moins d’une seconde
  • Un lecteur était nécessaire pour ajuster l’animation et la caméra
    • Pause
    • Lecture
    • Contrôle du temps
    • Vérification immédiate après live reload du shader
  • Le support de la musique était également indispensable pour synchroniser précisément la musique et l’animation
  • Le premier prototype a été créé dans Shadertoy, puis déplacé vers KodeLife, avant d’être porté dans un projet C++

La musique créée avec 4klang

  • La musique était un élément important de la narration, avec plusieurs ambiances et des transitions à des moments précis
  • Les mêmes outils que pour les intros 4 ko ont été utilisés, mais davantage d’espace a été alloué pour une composition plus complexe
  • Le synthétiseur 4klang fournit un plugin utilisable dans des logiciels de musique et exporte un fichier assembleur
  • Ce fichier assembleur est compilé et lié avec la démo
  • Lors de l’exécution, le synthé génère procéduralement le son sous forme d’onde dans un thread séparé, puis l’envoie à la carte son
  • La première version de la musique était plus volumineuse que prévu, et plusieurs ajustements ont été effectués pour réduire sa taille
    • Le nombre d’instruments a été réduit de 16 à 13
    • Le thème final a été recomposé pour correspondre au tempo global de la musique
    • Les répétitions ont été augmentées afin de mieux se compresser
    • Le taux de compression a été amélioré grâce à des changements presque imperceptibles à l’oreille, comme la durée des notes de fond
  • La structure globale de la musique a été conservée tout en minimisant la perte de qualité et en économisant de l’espace

Animation et synchronisation temporelle

  • La démo réévalue tout à chaque frame, sans précalcul ni cache
  • C’est défavorable pour les performances, mais avantageux pour l’animation, car n’importe quel élément peut varier en fonction du temps
  • Toute la démo est composée d’environ 25 plans de caméra faits à la main
  • Chaque plan décrit l’évolution de 18 paramètres dans le temps
    • La position de chaque objet
    • L’état du mouton
    • La position de la caméra
    • La mise au point
    • La cible regardée par la caméra, etc.
  • Par exemple, une seule ligne comme camPos = vec3(22., 2., time*0.6-10.); suffit à exprimer le déplacement linéaire de la caméra dans un plan
  • time ne désigne pas le temps absolu depuis le début de la démo, mais le temps écoulé depuis le début du plan
    • Ainsi, insérer, supprimer ou ajuster un plan affecte moins le reste de la démo
  • Une simple interpolation linéaire pouvant paraître mécanique, smoothstep est utilisé dans plusieurs cas
  • Le code de la timeline se trouve dans le vertex shader
  • Les définitions de plans contiennent beaucoup de code répétitif, mais cette répétition favorise la compression

Textures et matériaux

  • Dans un moteur de rendu traditionnel, des textures 2D sont appliquées sur la surface de modèles 3D
  • Avec le raymarching, les coordonnées de texture sont difficiles à calculer facilement ; à la place, des textures 3D sont calculées à la volée
  • Une fois que le raymarcher a trouvé la position 3D à rendre, ces coordonnées 3D sont transmises à une fonction de texture
  • Les panneaux de signalisation dessinent mathématiquement des bordures triangulaires ou rectangulaires, et leur contenu interne est obtenu en combinant plusieurs fonctions
    • Le pictogramme de restaurant est composé de quatre formes elliptiques noires et de deux formes blanches qui créent des évidements
  • Pour la diversité visuelle, les matériaux varient aussi, pas seulement les textures
    • Par exemple, les sabots du mouton utilisent un coefficient de Fresnel pour produire des réflexions différentes

De faux reflets pour donner vie aux yeux

  • Pendant longtemps au cours du développement, les yeux semblaient sans vie et plats
  • Les yeux étaient un élément important du character design et de la narration
  • En observant des images de référence de personnages de dessin animé, l’équipe a constaté que l’iris est fréquent mais pas indispensable, tandis que la pupille tend toujours à être grande
  • Pour obtenir des yeux brillants, il fallait des reflets lumineux dans les yeux
  • Avec les équations d’éclairage existantes, les reflets n’apparaissaient que lorsque le soleil et la caméra se trouvaient dans certaines positions
  • Pour y remédier, le vecteur de normale de surface a été modifié afin d’augmenter la probabilité de reflets du soleil
  • De l’environment mapping a aussi été utilisé
    • Dans les jeux, il sert généralement à optimiser les reflets de l’environnement réel en les simplifiant
    • Ici, même si le monde réel est presque entièrement blanc, une texture ajoute de faux détails
  • Le blanc des yeux et les pupilles contiennent des reflets bien plus complexes qu’un monde vide
    • Plusieurs fausses sources lumineuses
    • Un dégradé imitant un sol sombre et un ciel bleu

Post-traitement et style visuel

  • L’atmosphère finale et la qualité d’image sont affinées par le post-traitement
  • Les traitements utilisés sont les suivants
    • Étalonnage des couleurs
    • Correction gamma
    • Léger vignettage
    • Filtre FXAA en 2 passes pour réduire l’aliasing
  • Certains effets, comme les étoiles dans les yeux ou l’effet de l’écran final, sont également implémentés à l’étape de post-traitement
    • Ces effets sont purement 2D et n’existent pas dans le monde 3D
  • Un style évoquant les vieux dessins animés a un temps été expérimenté
    • Détection de contours pour un rendu proche du dessin à la main
    • Rendu en noir et blanc
    • Grain et bruit
  • Après discussion, cette expérimentation a été abandonnée au profit d’un rendu plus propre et moderne

Compression Crinkler

  • L’idée centrale est de ne pas stocker les données, mais de stocker le code qui les génère
  • La musique stocke la liste des notes à jouer et la liste des instructions de chaque instrument
  • Crinkler est un outil de compression conçu pour la demoscene et les intros de 1 ko à 8 ko
  • L’exécutable doit pouvoir s’auto-décompresser ; Crinkler inclut donc un petit code assembleur capable de décompresser le reste de l’exécutable
  • Crinkler donne la priorité à l’optimisation de la taille
    • L’algorithme de compression prend du temps
    • La décompression est relativement lente
    • Il utilise plusieurs centaines de Mo de RAM
  • Mais Crinkler seul ne suffisait pas, et une minification supplémentaire était nécessaire pour inclure les 42 ko de code source shader dans l’exécutable

Améliorations de Shader Minifier

  • Comme le code source du shader est inclus dans le binaire final, il doit être aussi petit que possible
  • Il serait possible de le minifier à la main, mais cela poserait des problèmes de maintenance et d’itération rapide
  • Shader Minifier est un outil de minification de shaders développé depuis 2011
  • Il supprime les espaces et les commentaires, raccourcit les noms de variables et effectue de nombreuses autres transformations
  • Les intros 8 ko contiennent beaucoup plus de code que les 4 ko, ce qui a fait apparaître de nouveaux problèmes ; le travail sur la démo a été interrompu pendant un mois pour ajouter les fonctionnalités nécessaires
  • Pour obtenir une bonne minification, il fallait non pas un simple minifier, mais un compilateur source-à-source proche de Closure Compiler
  • Parmi les transformations effectuées par Shader Minifier figurent
    • Renommage des variables et fonctions
    • Inlining de variables
    • Évaluation des expressions arithmétiques constantes
    • Inlining de fonctions
    • Suppression du code mort
    • Fusion de déclarations
  • Réduire la taille du code lui-même ne suffisait pas : il fallait une sortie favorable à la compression
    • Certaines transformations réduisent la taille du code mais augmentent la taille après compression
    • Pendant le développement itératif de la démo, la taille après compression devait être vérifiée en continu
  • Les nouvelles améliorations de Shader Minifier ont permis d’économiser environ 600 octets dans le binaire compressé
  • Au final, les 42 ko de code shader tiennent dans environ 5 ko après minification et compression
    • La place restante accueille la musique et le code C++

Un résultat bâti sur le travail des autres

  • Cette démo est construite sur des travaux réalisés par d’autres, notamment les techniques de raymarching, les logiciels de génération musicale et les algorithmes de compression
  • Les nouvelles fonctionnalités ajoutées à Shader Minifier devraient, espérons-le, aider à créer de meilleures démos à l’avenir
  • La catégorie 8 ko offre davantage de possibilités que le 4 ko, et l’espoir demeure qu’elle gagne en popularité
  • À titre de comparaison, le texte original compte environ 21 000 caractères ; tel quel, il nécessiterait 21 ko

1 commentaires

 
GN⁺ 2024-01-25
Avis sur Hacker News
  • Je suis le créateur du film original dont celui-ci s’est inspiré : https://www.youtube.com/watch?v=khWXdkryBE4
    L’original pèse déjà plusieurs Mo rien que pour Blender, le SVG et les fichiers audio ; c’est vraiment impressionnant que le remake tienne dans 8 ko
    Content de voir qu’une licence Creative Commons a réellement servi ; j’aurais juste aimé qu’ils réutilisent le même personnage de hérisson et la même mélodie audio

  • Sloot est ressuscité !!!
    https://en.m.wikipedia.org/wiki/Sloot_Digital_Coding_System

    • Peut-être pas Sloot, mais l’algorithme de compression ci-dessous a été assez efficace pour faire tenir tout un film dans quelques ko
      On rassemble plein de demosceners, on leur montre le film, puis on les enferme quelque temps dans une pièce avec un ordinateur
      Ça marche encore mieux avec un grand écran, un gros système audio, de la bière et de la junk food ; il ne reste plus qu’à récupérer l’exécutable final
    • Ce n’est rien. Voici un programme d’un octet qui affiche l’intégrale de Shakespeare :
      a
      Un interpréteur écrit en Rust pour les performances évalue la commande a et écrit sur STDOUT une charge utile spécifique incluse dans le runtime
    • « En 1999, quelques jours avant la signature d’un contrat de vente de son invention, Sloot est mort subitement d’une crise cardiaque. Le code source n’a pas été retrouvé, et la technologie comme les affirmations n’ont jamais été reproduites ni vérifiées »
      Une sacrée malchance pour tout le monde
    • Mon interprétation rapide après avoir parcouru l’article en diagonale, c’est qu’il avait mal expliqué un algorithme de hachage à des non-techniciens
      Ils n’ont probablement pas compris a) que c’était une fonction à sens unique et b) que, même si elle ne l’était pas, plusieurs entrées peuvent être mappées vers le même hash
  • Les vidéos de la demoscene sont assez amusantes, mais ridiculement méconnues
    https://www.youtube.com/playlist?list=PLRQwR4-_0PR9HBI_GZs1nTn7q5LTkGeCN
    Ce serait bien d’avoir une chaîne qui en décortique les coulisses avec de bons visuels, un peu comme retro game mechanics

    • Regarder les démos en vidéo sur YouTube, c’est un peu de la triche, mais je suis plus prudent qu’il y a 20 ans quand il s’agit de télécharger et d’exécuter des exécutables au hasard, donc je suis content que les vidéos existent
      À l’époque où il n’y en avait pas, il fallait attendre un bon moment la décompression et le traitement avant de pouvoir être émerveillé ou déçu
      Avant, je regardais beaucoup de démos pendant les pauses, et c’était un fond sonore/visuel parfait pour mon état d’esprit de l’époque
    • Ça me semble être l’occasion de partager ma démo préférée de tous les temps. Intrinsic Gravity de Still : https://youtu.be/-ZxPhDC-r3w
    • D’accord. Ce serait génial de pouvoir voir les rouages internes de choses comme .kkrieger
      https://en.wikipedia.org/wiki/.kkrieger
    • Quand on aura résolu l’AGI, j’aimerais voir une demoscene de l’intelligence. Jusqu’où pourra-t-on la compresser ?
      Et, au-delà, à quel point pourra-t-on faire petit un monde composé d’agents intelligents interagissant entre eux ?
      Ajout : ceux qui downvotent, vous ne rêvez jamais ? Il y a vraiment beaucoup de choses incroyablement fantastiques qui nous attendent
    • Ça me rappelle shader wars
  • Je sais que la technique derrière est assez différente, mais ça me rappelle le vieux .kkrieger. Un FPS 3D complet compressé en 96 ko, c’était vraiment génial
    https://en.m.wikipedia.org/wiki/.kkrieger

  • Quand on regarde beaucoup de productions demoscene et qu’on lit comment elles sont faites, on finit par en comprendre les limites ; il devient évident que des éléments comme le mouton, en particulier les sections coniques des pattes, peuvent être exprimés par des équations très compactes et animés de façon similaire
    Mais à ma connaissance, la plupart des démos s’appuient sur le GPU et de puissantes capacités d’accélération 3D, alors que les démos en 2D façon animation japonaise semblent rares, voire quasi inexistantes, dans de petites tailles
    La 3D animation est-elle réellement plus facile ?
    Au passage, « mouton » veut dire “sheep” en français, d’où “mutton”

    • La méthode décrite montre justement, il me semble, pourquoi la 3D est courante dans les démos. Les formes géométriques 3D peuvent être exprimées sous forme d’équations fermées qui nécessitent un minimum d’espace de stockage
      À l’inverse, la manière « mathématique » de stocker une animation en style 2D consiste plus ou moins à enregistrer les contours sous forme de courbes SVG et à utiliser des algorithmes de remplissage de zones pour la mise en couleur
      Même avec un calcul grossier, un seul arc de courbe ou une seule ligne peut prendre autant d’espace que la description d’un volume géométrique entier ; et si l’on veut aussi stocker l’animation de ces courbes, il faut bien plus d’espace que pour les petites matrices de transformation d’un volume 3D
      La complexité de l’algorithme de rendu augmenterait sans doute aussi, à la fois en complexité algorithmique réelle et en complexité spatiale
      Nuance : on pourrait soutenir que stocker plusieurs bitmaps et les rejouer comme un flipbook est aussi « mathématique » si l’on utilise un algorithme de décompression procédurale qui génère les images complètes à partir des différences
      Mais je ne pense pas qu’une telle approche existe vraiment, et ses besoins en espace seraient énormes par rapport à la description de volumes 3D
    • La plupart des animations 2D décrivent de toute façon des événements dans un espace 3D ; en général, il sera donc beaucoup plus simple de décrire directement les événements 3D
  • C’est un peu plus gros que 8 ko, mais si vous aimez les animations étonnamment sombres sur des moutons, je recommande l’open movie Cosmos Laundromat de la Blender Foundation
    Les films de cette fondation sont toujours étrangement sombres pour des démos techniques, mais celui-ci m’a semblé particulièrement réussi
    [0] https://www.youtube.com/watch?v=Y-rmzh0PI3c

  • Petit avertissement : le film se termine par « to be continued », et la page Wikipedia indique ceci :
    « Ce film était à l’origine conçu comme le point de départ d’un long métrage. Une suite en court métrage avait un scénario et un design, mais n’est jamais entrée en production. En 2020, [le créateur] a annoncé que cet unique film constituerait l’intégralité du projet »
    Si vous ne voulez pas subir la déception de ne jamais voir la conclusion d’un concept intéressant, il vaut peut-être mieux passer votre chemin

    • Au passage, pour ajouter quelque chose en lien, grâce à Netflix, l’intégralité du film est disponible au téléchargement depuis S3 sous forme de séquence de 18 192 images EXR non compressées
      Cela m’a été utile pour faire des essais HDR dans DaVinci Resolve
      [1] https://opencontent.netflix.com/#h.uyzoa2bivz2j
    • L’expression « étrangement sombre » décrit assez bien le sens de l’humour néerlandais, et c’est sans doute la principale raison
  • 8KB est une catégorie de la demoscene. Pouet propose un index bien organisé de ce type de productions
    0. https://www.pouet.net/prodlist.php?type%5B%5D=8k&page=1

  • Razor 1911. De la pure nostalgie

    • Un groupe surtout connu pour le warez et le cracking, ainsi que pour l’ASCII art dans les fichiers FILE_ID.DIZ et .nfo
      Cela me rappelle beaucoup de bons souvenirs d’il y a des décennies :-)
    • Cela m’a fait plaisir de voir qu’ils créent encore quelque part de belles œuvres artistiques
  • C’est vraiment stupéfiant, et j’aime le fait que, même avec ces contraintes, l’histoire soit aussi drôle et créative. Vraiment impressionnant
    J’aimerais en savoir plus sur les détails techniques des astuces et économies utilisées pour réduire le nombre d’octets

  • Drôle et absolument bluffant. J’ai enfin vu un film dont je n’ai pas réussi à prévoir la fin