<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title><![CDATA[Cinevva Blog]]></title>
        <link>https://app.cinevva.com</link>
        <description><![CDATA[Nouveautés produit, carnets de création et expérimentations.]]></description>
        <lastBuildDate>Sun, 16 Aug 2026 04:06:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>scripts/build-rss.mjs</generator>
        <copyright>Copyright 2024-present Cinevva</copyright>
        <item>
            <title><![CDATA[Simuler un environnement d’exécution qui dit non : comment nous transformons le navigateur en bac à sable pour qu’il se comporte comme WeChat]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-07-19-sandboxing-wechat-strict-mode</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-07-19-sandboxing-wechat-strict-mode</guid>
            <pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Notre créateur de jeux par IA génère désormais des jeux portables vers WeChat, mais une règle formulée dans un prompt reste une suggestion. Nous avons donc intégré un bac à sable en mode strict à l’aperçu : un script qui force le navigateur à lever une exception là où WeChat le ferait, puis renvoie directement les violations dans la propre boucle de débogage de l’IA. Voici sa conception, la liste des interdictions et les quatre exceptions qui nous ont le plus appris.]]></description>
            <content:encoded><![CDATA[<h1>Simuler un environnement d’exécution qui dit non : comment nous transformons le navigateur en bac à sable pour qu’il se comporte comme WeChat</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<img src="https://cdn.cinevva.com/blog/wechat-strict-sandbox-hero.jpg" alt="Une petite scène de jeu 3D sous un dôme de verre transparent, avec des icônes d’outils barrées flottant à l’extérieur" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Nous avons récemment lancé un <a href="/fr/news/2026-07-19-wechat-mini-game-mode">mode mini-jeu WeChat</a> dans notre Créateur de jeux : un profil de compilation qui maintient les jeux générés par IA dans le sous-ensemble de la plateforme web capable de survivre à un portage vers l’environnement d’exécution de WeChat. Ce profil est un ensemble de règles de génération. Pas d’interface DOM, pas de Pointer Lock, audio en mp3 uniquement, pas de code dynamique. Le modèle les respecte comme les modèles respectent les règles, c’est-à-dire généralement.</p>
<p>« Généralement » ne suffit pas lorsque chaque violation est invisible dans le navigateur et fatale après l’export. Un jeu doté d’une barre de vie en HTML fonctionne parfaitement dans notre aperçu, puis affiche un écran vide sur WeChat, car les mini-jeux WeChat n’ont aucun DOM. Nous avons donc construit ce qui transforme ces règles en lois physiques plutôt qu’en suggestions : un bac à sable en mode strict qui force le navigateur lui-même à refuser ce que WeChat ne peut pas faire. Cet article explique son fonctionnement.</p>
<h2>La décision fondamentale : simuler l’absence, pas la présence</h2>
<p>L’approche évidente consisterait à émuler WeChat : implémenter <code>wx.createCanvas</code>, <code>wx.onTouchStart</code>, <code>wx.setStorageSync</code>, puis exécuter les jeux sur cette surface <code>wx.*</code> émulée. Nous avons choisi l’approche inverse, en raison de la structure de notre pipeline d’export.</p>
<p>En mode WeChat, les jeux sont toujours écrits à partir des API du navigateur. Lors de la mise en paquet, un adaptateur — selon l’approche standard de la communauté weapp-adapter — mappe ces API de navigateur vers <code>wx.*</code>. Nos jeux n’appellent donc jamais directement <code>wx.*</code> ; un émulateur <code>wx</code> testerait des chemins de code qui n’existent pas. Ce qui casse réellement un portage n’est pas l’absence de <code>wx.*</code> dans le navigateur. C’est la <em>présence</em> d’API de navigateur sans équivalent dans WeChat, utilisées discrètement pendant la génération. <code>document.createElement('div')</code>. <code>requestPointerLock()</code>. IndexedDB. Un fichier <code>.ogg</code> parfaitement décodé par Chrome qui ne le sera jamais sur WeChat pour iOS.</p>
<p>Le bac à sable simule donc l’absence. Tout ce que WeChat ne possède pas est supprimé, rendu inutilisable ou signalé dans l’aperçu, et le jeu se développe dès sa première image dans l’intersection des deux plateformes.</p>
<h2>Son emplacement : un service worker qui était déjà là</h2>
<p>Notre aperçu dans l’éditeur suit un chemin de diffusion inhabituel qui a rendu cette fonctionnalité presque gratuite. Dans l’éditeur, les jeux ne sont pas servis depuis le réseau. Un service worker intercepte les requêtes <code>/game/*</code> et sert les fichiers depuis IndexedDB, ce qui permet à l’iframe d’aperçu de se recharger instantanément sans aller-retour réseau. Ce worker injectait déjà deux fichiers virtuels dans chaque page de jeu : un script de capture des erreurs qui transmet à l’éditeur les sorties de console et les plantages, ainsi qu’un inspecteur de scène en direct.</p>
<p>Le bac à sable est le troisième fichier virtuel, <code>_wechat-strict.js</code>, injecté dans le <code>&lt;head&gt;</code> de la page immédiatement après le script de capture et avant l’exécution de tout code du jeu. Cet ordre est crucial à deux égards. Il doit s’exécuter après le script de capture afin que ses appels à <code>console.error</code> soient déjà interceptés et transmis à l’éditeur, mais avant les modules du jeu afin que les interdictions soient en place dès l’exécution de la première ligne de code.</p>
<p>L’activation tient en une seule ligne. L’état du bouton WeChat du créateur est conservé dans <code>localStorage</code>, et l’iframe d’aperçu partage la même origine que l’éditeur ; le harnais n’a donc qu’à lire directement l’indicateur :</p>
<pre><code class="language-js">try {
  if (localStorage.getItem('cinevva-gc-profile') !== 'wechat-minigame') return;
} catch (e) { return; }
</code></pre>
<p>Pour chaque jeu qui ne cible pas WeChat, le script se résume à une comparaison de chaînes et à un retour anticipé. Aucun indicateur de compilation, aucun aller-retour avec le serveur, aucun état à synchroniser avec le service worker.</p>
<h2>La liste des interdictions et la classification à deux niveaux</h2>
<p>Toutes les violations ne méritent pas la même réponse ; le harnais distingue donc deux niveaux. Les API qui <em>n’existent pas</em> sur WeChat lèvent une exception, car c’est ce qui se produit après l’export et qu’un plantage pendant la génération constitue un aperçu fidèle d’un plantage lors de la validation. Les éléments qui <em>existent mais cassent plus tard</em> déclenchent des erreurs de console bien visibles sans modifier le comportement, car les bloquer masquerait l’état réel du jeu à la personne qui travaille dessus.</p>
<p>Le niveau qui lève une exception : la création de tout élément d’interface HTML, via <code>createElement</code> comme via <code>createElementNS</code> — Three.js crée son canvas avec la variante NS, les deux chemins doivent donc utiliser le même filtre —, <code>eval</code>, <code>new Function</code>, <code>requestPointerLock</code> et l’enregistrement d’un service worker. Chaque exception indique la correction à apporter dans son message :</p>
<pre><code class="language-js">throw violate(&quot;document.createElement('&quot; + tag + &quot;') — Les mini-jeux WeChat n’ont pas de DOM. &quot; +
  &quot;Dessinez l’interface sur un canvas (canvas 2D hors écran -&gt; THREE.CanvasTexture sur un quadrilatère en espace écran) &quot; +
  &quot;et gérez vous-même la détection des touchers.&quot;);
</code></pre>
<p>Le niveau de signalement : l’accès à <code>indexedDB</code> — qui renvoie <code>undefined</code>, exactement comme WeChat, en plus d’une erreur orientant vers <code>localStorage</code> —, l’audio dans tout format que WeChat pour iOS ne peut pas décoder — vérifié à trois endroits : le constructeur <code>Audio</code>, le setter <code>src</code> du prototype de l’élément multimédia et les URL récupérées —, les requêtes réseau vers toute origine autre que celle du jeu ou notre CDN de ressources — sur WeChat, elles nécessitent un domaine inscrit sur liste blanche et enregistré auprès de l’ICP —, ainsi que toute apparition de Tone.js.</p>
<p>Un audit après chargement s’exécute également peu après la stabilisation de la page : il parcourt le <code>&lt;body&gt;</code> et signale tous les éléments HTML provenant directement du balisage du jeu plutôt que d’une création dynamique. Les HUD statiques échappent à un filtre sur <code>createElement</code> ; ce filtre ne suffit donc pas à lui seul.</p>
<p>Chaque signalement est dédupliqué par message et plafonné à cinquante par session. Un bug de boucle inversée qui crée une div à chaque image produit une seule erreur, pas un déluge qui noie l’information utile.</p>
<h2>Les quatre exceptions qui nous ont le plus appris</h2>
<p>Construire un bac à sable consiste surtout à décider de ce qu’il ne faut <em>pas</em> rendre inutilisable, et chaque exception que nous avons ajoutée est née d’une panne de nos propres outils pendant le développement.</p>
<p><strong>Notre débogueur repose sur eval.</strong> L’outil <code>execute_js</code> du créateur, que l’IA utilise pour sonder le jeu en direct, évalue du code via le gestionnaire de messages du script de capture, qui appelle <code>eval</code>. Rendre <code>eval</code> naïvement inutilisable aurait privé l’IA de ses propres yeux. La solution : avant de le neutraliser, le harnais conserve la véritable fonction dans une propriété non énumérable, puis le gestionnaire du script de capture se rabat sur celle-ci :</p>
<pre><code class="language-js">Object.defineProperty(window, '__cinevvaRealEval', { value: window.eval, enumerable: false });
window.eval = function () { throw violate('eval() est interdit dans les mini-jeux WeChat.'); };
// script de capture, lors de la réception du message :
returnValue = (window.__cinevvaRealEval || eval)(e.data.code);
</code></pre>
<p>Le code du jeu qui tente d’utiliser <code>eval</code> échoue toujours. Pas le débogueur.</p>
<p><strong>L’enregistreur crée lui aussi des éléments.</strong> Notre enregistreur de reels est injecté dans l’iframe du jeu sous forme de <code>&lt;script&gt;</code> créé avec le propre <code>document.createElement</code> de l’iframe, puis télécharge les vidéos terminées au moyen d’un clic synthétique sur un élément <code>&lt;a&gt;</code>. Bloquer ces balises aurait cassé l’enregistrement, uniquement et précisément en mode WeChat. La balise <code>script</code> reste donc autorisée et <code>a</code> déclenche un avertissement sans lever d’exception. Un jeu qui inclut un véritable lien est tout de même signalé, tandis que nos outils continuent de fonctionner.</p>
<p><strong>Les événements clavier restent disponibles.</strong> L’IA vérifie les commandes grâce à un outil qui synthétise des frappes au clavier et observe ensuite comment l’état du jeu a évolué. Supprimer les entrées clavier pour simuler un téléphone aurait détruit la boucle de vérification des commandes qui détecte notamment les inversions de caméra. La priorité au tactile est imposée par les règles de génération et le schéma de commandes du profil, pas par le bac à sable.</p>
<p><strong>WebGL2 reste disponible, et la raison mérite sa propre section ci-dessous.</strong> La première version de la liste des interdictions bloquait <code>getContext('webgl2')</code> afin d’imposer le rendu « compatible WebGL1 » exigé par le profil. Sauf que Three.js <a href="https://github.com/mrdoob/three.js/releases/tag/r163">a entièrement supprimé la prise en charge de WebGL1 dans la version r163</a>, et que nous utilisons la r181 : bloquer WebGL2 aurait rendu vierge chaque jeu 3D créé dans ce mode. La règle de rendu du profil était incohérente, issue d’une vision obsolète de la plateforme. L’audit du bac à sable a donc audité la spécification du bac à sable lui-même, et en tirant sur ce fil, nous avons découvert quelque chose d’important.</p>
<h2>La question de WebGL2, correctement résolue</h2>
<p>Ne pas toucher à la création du contexte soulevait immédiatement une autre question : si notre moteur exige WebGL2, les jeux cassent-ils sur les appareils WeChat qui ne disposent que de WebGL1 ? Nos recherches nous ont fourni l’image la plus claire dont nous disposons sur le niveau minimal de rendu de la plateforme ; la voici, sources à l’appui.</p>
<p>Sur Android, l’environnement d’exécution des mini-jeux WeChat fournit WebGL2 avec toute version moderne de la bibliothèque de base, et le matériel GLES3 sous-jacent est pratiquement universel. iOS est le véritable enjeu. La documentation technique de WeChat sur la <a href="https://wechat-miniprogram.github.io/minigame-unity-webgl-transform/Design/WebGL2.html">prise en charge de WebGL2</a> et le <a href="https://developers.weixin.qq.com/minigame/dev/guide/performance/perf-high-performance-plus.html">mode hautes performances+</a> précise que, sur iPhone, WebGL2 n’est correctement disponible que dans l’environnement hautes performances+, lequel exige une version récente du client WeChat — 8.0.45 ou ultérieure, davantage sous iOS 14 — et, dans les faits, iOS 15.5 ou plus récent. D’après la propre description de Tencent, le mode hautes performances ordinaire exécute WebGL2 « avec davantage de problèmes », tandis que l’environnement iOS normal ne le prend pas du tout en charge.</p>
<p>Le véritable danger réside dans la forme que prend l’échec. Dans les environnements non pris en charge, <code>getContext('webgl2')</code> peut renvoyer un contexte évalué comme vrai mais défectueux au lieu de null, un comportement que des développeurs ont <a href="https://developers.weixin.qq.com/community/develop/doc/000aee72868a381d6c261686651c00">directement demandé à Tencent de corriger</a>. Un jeu qui fait confiance à cette valeur de retour n’échoue pas proprement. Il affiche des artefacts ou un écran noir silencieux.</p>
<p>Nous traitons donc le problème sur trois niveaux. Le bac à sable ne touche pas à la création du contexte WebGL2, car la rendre inutilisable reviendrait à lutter contre notre propre moteur. Le profil de génération exige désormais une barrière au démarrage dans chaque jeu : la création du moteur de rendu doit être enveloppée dans un bloc try/catch, puis suivie d’un test fonctionnel (<code>typeof gl.createVertexArray === 'function'</code>, une capacité propre à WebGL2 qu’un faux contexte ne possédera pas), avec en cas d’échec un message convivial dessiné sur le canvas demandant de mettre WeChat à jour, plutôt qu’un écran vide. C’est le même modèle que celui utilisé par les mini-jeux convertis depuis Unity, ce qui explique pourquoi on rencontre dans la pratique des invitations à mettre à niveau plutôt que des écrans noirs. Enfin, lorsque l’exporteur sera disponible, il imposera le mode hautes performances+ dans <code>game.json</code> afin que le chemin compatible soit celui utilisé par défaut.</p>
<p>Quel est le coût d’un minimum WebGL2 en matière d’audience ? Les utilisateurs sur une version antérieure à iOS 15.5 environ ou avec un client WeChat plus ancien que la version 8.0.45 : une faible proportion à un chiffre en 2026, mais concentrée sur les appareils anciens, ce qui importe davantage pour certains genres que pour d’autres. Si les données réelles de distribution montrent un jour que cette longue traîne mérite d’être ciblée, nous gardons en réserve une solution de repli peu coûteuse : épingler le profil WeChat sur Three r162, la dernière version compatible avec WebGL1. Les jeux du profil n’utilisent que des API fondamentales stables ; le retour à une version antérieure se résume donc à modifier une seule ligne dans l’import map, une option que nous conservons délibérément jusqu’à ce que les données l’exigent.</p>
<h2>Boucler la boucle avec le modèle</h2>
<p>Voici ce qui rend cette fonctionnalité particulièrement utile pour un produit centré sur l’IA. Après chaque compilation, l’agent du créateur appelle un outil qui lit la console du jeu et attend la fin du démarrage. Le script de capture transmet <code>console.error</code> à ce flux. Une violation <code>[wechat-strict]</code> n’est donc pas un avertissement qu’un humain pourrait ignorer en faisant défiler la page. Elle arrive dans le canal précis que le modèle consulte déjà avant de déclarer la compilation terminée, au cours du même tour, avec la correction indiquée dans le message.</p>
<p>Le profil de génération comble le dernier manque avec une seule instruction : traiter chaque message <code>[wechat-strict]</code> comme un bug bloquant la compilation, en corriger la cause et ne jamais tenter de détecter ou de contourner le harnais. Un jeu qui ne fonctionne que lorsque le bac à sable est désactivé est un jeu qui échouera après l’export, et le modèle en est explicitement informé.</p>
<p>En pratique, le bac à sable transforme un problème de conformité flou — « le modèle a-t-il respecté les quatorze règles ? » — en la boucle de débogage que le système maîtrise déjà — « la console affiche une erreur, corrigez-la ».</p>
<h2>Ce qu’un navigateur ne peut pas simuler</h2>
<p>Par souci d’honnêteté : ce bac à sable détecte les violations liées à la surface des API, qui représentent selon nous la grande majorité des causes d’échec d’un portage. Il ne peut pas détecter les performances sur un véritable téléphone, l’exécution de JavaScript sans JIT sous iOS, les particularités de l’implémentation WebGL de WeChat ni les différences de comportement des codecs au-delà des extensions de fichiers. Pour cela, il faut utiliser l’environnement réel.</p>
<p>Le bac à sable constitue donc le premier de trois niveaux. Le deuxième, lorsque notre exporteur produira des projets pour les outils de développement WeChat, sera le simulateur officiel piloté sans interface graphique via la CLI des DevTools et miniprogram-automator : démarrer le paquet exporté, vérifier que la première image s’affiche, simuler un toucher. Le troisième est le parcours officiel sur appareil, avec des codes QR d’aperçu et le débogage à distance sur des téléphones physiques, là où résident les vérités que rien d’autre ne peut révéler. Chaque niveau est plus lent et plus fidèle que le précédent, et chacun doit rendre rares les passages au suivant.</p>
<p>Pour essayer ce mode, activez l’option « Mode mini-jeu WeChat » dans le <a href="/fr/create">Créateur de jeux</a>. Pour en savoir plus sur le marché et le contexte réglementaire, consultez <a href="/fr/blog/2026-07-19-wechat-mini-games-guide-for-developers-outside-china">notre guide pratique pour atteindre le demi-milliard de joueurs de WeChat</a>.</p>
<h2>Références</h2>
<ul>
<li><a href="https://developers.weixin.qq.com/minigame/dev/guide/runtime/js-support.html">Documentation officielle de WeChat : prise en charge de JavaScript dans les mini-jeux</a></li>
<li><a href="https://developers.weixin.qq.com/minigame/en/dev/guide/best-practice/adapter.html">Documentation officielle de WeChat : la couche d’adaptation</a></li>
<li><a href="https://developers.weixin.qq.com/minigame/dev/guide/performance/perf-high-performance-plus.html">Documentation officielle de WeChat : mode hautes performances+</a></li>
<li><a href="https://wechat-miniprogram.github.io/minigame-unity-webgl-transform/Design/WebGL2.html">Documentation technique de WeChat : prise en charge du rendu WebGL2 dans les mini-jeux</a></li>
<li><a href="https://github.com/wechat-miniprogram/minigame-unity-webgl-transform/blob/main/Design/iOSOptimization.md">Documentation technique de WeChat : modes hautes performances et hautes performances+ sur iOS</a></li>
<li><a href="https://developers.weixin.qq.com/community/develop/doc/000aee72868a381d6c261686651c00">Communauté de développement WeChat : les contextes WebGL2 défaillants doivent renvoyer null</a></li>
<li><a href="https://developers.weixin.qq.com/miniprogram/en/dev/devtools/auto/">Documentation officielle de WeChat : miniprogram-automator</a></li>
<li><a href="https://github.com/mrdoob/three.js/releases/tag/r163">Notes de version de Three.js r163 (suppression de la prise en charge de WebGL1)</a></li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mini-jeux WeChat : le plus grand marché de jeux instantanés sur lequel vous ne pouvez pas simplement déployer votre jeu]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-07-19-wechat-mini-games-guide-for-developers-outside-china</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-07-19-wechat-mini-games-guide-for-developers-outside-china</guid>
            <pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[500 millions de joueurs mensuels, un marché de 5,6 milliards de dollars qui a doublé en un an et une plateforme que votre jeu web ne peut pas atteindre avec une simple URL. Ce que sont réellement les mini-jeux WeChat, les réglementations auxquelles vous devrez faire face en tant que développeur hors de Chine, la seule voie de monétisation qui ne nécessite pas de licence et la place qu'occupe le nouveau mode mini-jeu de Cinevva.]]></description>
            <content:encoded><![CDATA[<h1>Mini-jeux WeChat : le plus grand marché de jeux instantanés sur lequel vous ne pouvez pas simplement déployer votre jeu</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<img src="https://cdn.cinevva.com/blog/wechat-mini-games-market-hero.jpg" alt="Un smartphone affichant un mini-jeu devant une porte munie d'un sceau d'approbation, avec à côté un document tamponné et des pièces de monnaie" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Si vous créez des jeux web, il existe un marché d'un demi-milliard de joueurs mensuels que vos jeux ne peuvent pas atteindre, quelle que soit la qualité de leur prise en charge des mobiles. Ce marché se trouve au sein de WeChat, ses revenus ont presque doublé l'année dernière et y accéder nécessite à la fois un portage technique et une procédure réglementaire dont la plupart des développeurs occidentaux n'ont jamais entendu parler.</p>
<p>Nous venons de publier la première partie de notre réponse : un <a href="/fr/news/2026-07-19-wechat-mini-game-mode">mode mini-jeu WeChat</a> expérimental dans le Game Creator de Cinevva. Cet article présente l'histoire dans son ensemble : ce qu'est réellement la plateforme, quelles sont ses règles et à quoi ressemble une voie réaliste pour un développeur situé hors de Chine.</p>
<h2>Le marché, en chiffres faciles à sous-estimer</h2>
<p>WeChat est l'application de communication par défaut en Chine, avec plus de 1,4 milliard d'utilisateurs actifs mensuels au total. Les mini-jeux y prennent la forme d'expériences accessibles instantanément : aucune installation, aucune boutique d'applications, un lancement depuis un message, une recherche ou un partage. En 2025, Tencent a annoncé que la plateforme de mini-jeux avait dépassé les 500 millions d'utilisateurs actifs mensuels, avec plus d'un milliard de joueurs inscrits et environ 500 000 développeurs.</p>
<p>Les revenus sont eux aussi bien réels. Le marché chinois des mini-jeux a généré 39,8 milliards de yuans en 2024, soit environ 5,6 milliards de dollars, en hausse de 99 % sur un an. Environ 69 % provenaient des achats intégrés et 31 % de la publicité. Gardez cette répartition à l'esprit, car la part de la publicité s'avère particulièrement importante pour les développeurs étrangers.</p>
<p>Pour donner un ordre de grandeur : les revenus des mini-jeux sur cette seule année sont comparables à ceux de l'ensemble du marché mondial des jeux web sur lequel reposent des plateformes comme Poki, CrazyGames et <a href="http://itch.io">itch.io</a>. Ce n'est pas une niche. C'est un univers parallèle avec ses propres règles.</p>
<h2>Pourquoi votre jeu web ne se porte pas tel quel</h2>
<p>Un mini-jeu WeChat ressemble à un jeu web et s'écrit en JavaScript. Il est donc naturel de supposer que son portage ne prendra qu'un week-end. Ce n'est pas le cas, pour des raisons structurelles.</p>
<p>Les mini-jeux ne s'exécutent pas dans un navigateur. Ils fonctionnent dans l'environnement d'exécution propre à WeChat, qui fournit un canvas, WebGL et un ensemble d'API <code>wx.*</code>. Il n'y a ni DOM ni CSS. Chaque bouton, barre de vie, menu et boîte de dialogue de votre jeu doit être dessiné sur le canvas, et votre propre code doit en gérer la détection des interactions. Si l'interface de votre jeu repose sur du HTML, elle n'existe tout simplement pas sur WeChat.</p>
<p>Le modèle d'entrée est différent. Il n'y a ni Pointer Lock ni souris : une caméra de FPS pensée pour ordinateur doit donc être adaptée à une rotation par glissement. L'appareil principal n'a pas de clavier. Si votre jeu n'est pas entièrement jouable au tactile, il n'est pas jouable.</p>
<p>La taille des paquets est limitée à 4 Mo pour le paquet principal et à 20 Mo au total avec les sous-paquets, et tout ce que votre jeu exécute doit se trouver dans ce paquet soumis à validation. Ce qui nous amène à la règle qui façonne toute la plateforme : WeChat interdit l'exécution dynamique de code. Pas d'<code>eval</code>, pas de <code>new Function</code>, pas de téléchargement de JavaScript à l'exécution, et Tencent rejette activement les applications qui intègrent des interpréteurs JS pour contourner cette restriction. Chaque jeu est un artefact fermé et soumis à validation.</p>
<p>Cette dernière règle explique pourquoi il n'existera jamais d'« application portail » diffusant des jeux web dans WeChat, qu'elle soit proposée par nous ou par quelqu'un d'autre. Chaque jeu passe séparément par le processus de validation en tant que mini-jeu autonome. Tout outil qui souhaite vous aider doit donc vous permettre de produire des paquets propres, autonomes et exclusivement basés sur le canvas. C'est autour de cette contrainte de conception que nous avons bâti notre nouveau mode.</p>
<h2>La réalité réglementaire</h2>
<p>Passons maintenant à la partie qui n'a rien à voir avec le code. La Chine réglemente les jeux comme des médias publiés, et les exigences se superposent en plusieurs couches. Aucune n'est secrète, mais presque personne hors de Chine ne les explique clairement. Les voici donc.</p>
<p><strong>Dépôt ICP.</strong> Depuis fin 2023, chaque mini-programme et chaque mini-jeu doit effectuer un dépôt ICP, c'est-à-dire un enregistrement auprès des autorités chinoises réalisé par l'intermédiaire de la plateforme WeChat. La procédure prend généralement une à trois semaines.</p>
<p><strong>Preuve de droits d'auteur pour chaque titre.</strong> Chaque jeu doit disposer soit d'un enregistrement des droits d'auteur sur le logiciel, soit de la certification électronique des droits d'auteur, plus rapide. La procédure électronique prend environ dix à quinze jours par jeu. Il s'agit d'un coût par titre, ce qui compte si vous prévoyez de publier de nombreux petits jeux.</p>
<p><strong>Le banhao, si vous faites payer les joueurs.</strong> Tout jeu proposant des achats intégrés doit obtenir une licence de publication de jeu auprès de la National Press and Publication Administration, communément appelée banhao. Deux éléments sont déterminants pour vous en tant que développeur étranger. Premièrement, seule une entreprise chinoise peut déposer une demande. Deuxièmement, les titres importés suivent une procédure d'approbation distincte et plus lente, qui prend historiquement six à huit mois lorsqu'elle avance effectivement. En pratique, un développeur hors de Chine ne peut accéder à la publication monétisée qu'en passant par un éditeur chinois agréé, qui soumet le jeu, détient la licence et en assure l'exploitation.</p>
<p><strong>L'exception des jeux gratuits financés par la publicité.</strong> Un jeu gratuit ne proposant aucun paiement n'a pas besoin de banhao. Il peut tout de même générer des revenus grâce aux composants publicitaires propres à WeChat. Et rappelez-vous : la publicité représentait 31 % des revenus des mini-jeux en 2024, soit plus de 1,7 milliard de dollars. C'est la seule voie qu'un petit développeur peut emprunter sans partenaire commercial chinois, et c'est ainsi qu'une grande partie de la longue traîne du marché fonctionne réellement.</p>
<p>En toute franchise : publier un mini-jeu gratuit financé par la publicité implique des formalités qui se mesurent en semaines. Publier un jeu monétisé est un projet de développement commercial qui se mesure en mois, avec un partenaire qu'il faudra choisir avec soin.</p>
<h2>Comment le mode mini-jeu de Cinevva répond à ces contraintes</h2>
<p>Nous ne pouvons pas effectuer les démarches administratives à votre place. En revanche, nous pouvons nous assurer que le jeu lui-même ne constitue jamais un obstacle, dès le premier prompt.</p>
<p>Lorsque le mode mini-jeu WeChat est activé, le Game Creator crée le jeu dans le sous-ensemble portable : interface dessinée sur le canvas plutôt qu'en HTML, commandes pensées d'abord pour le tactile avec rotation par glissement au lieu du Pointer Lock, budget de rendu adapté à un téléphone de milieu de gamme avec un écran de repli clair lorsque WebGL2 n'est pas réellement disponible, budget de ressources strict avec audio exclusivement au format mp3, aucune importation dynamique, aucun chargement de code à l'exécution et sauvegardes compatibles avec l'API de stockage de WeChat. Le jeu continue de fonctionner dans le navigateur comme tous les jeux Cinevva, ce qui vous permet de le créer, de le tester et de le partager normalement. Il reste simplement dans le sous-ensemble qui résistera au portage.</p>
<p>L'exportateur qui générera, à partir de l'un de ces jeux, un projet prêt à être ouvert dans WeChat DevTools est en cours de développement. Nous avons choisi cet ordre volontairement. Un exportateur peut regrouper votre code, mais il ne peut pas supprimer rétroactivement un HUD en HTML ni repenser des commandes utilisables uniquement à la souris. La portabilité dépend de la manière dont le jeu est conçu : le mode devait donc venir en premier.</p>
<p>Si vous avez lu notre <a href="/fr/guides/wechat-mini-game-engines">guide des moteurs pour mini-jeux</a> ou le <a href="/fr/guides/publish-wechat-douyin-mini-games">guide de publication de mini-jeux sur WeChat et Douyin</a>, vous retrouverez ici le même raisonnement appliqué à la génération : le portage le moins coûteux est celui que vous n'avez jamais à réaliser.</p>
<h2>Une stratégie réaliste pour un développeur hors de Chine</h2>
<p>Voici ce que nous ferions concrètement pour un premier titre. Créez un petit jeu conçu pour de courtes sessions, car c'est ce que la plateforme récompense : les succès sont des jeux qui donnent envie de relancer immédiatement une partie, pas des épopées. Concevez d'abord pour le mode portrait et exclusivement pour le tactile. Publiez-le gratuitement avec de la publicité afin de rester hors du champ du banhao pendant que vous découvrez si les joueurs chinois s'intéressent réellement à votre jeu. Gérez vous-même le dépôt ICP et la certification électronique des droits d'auteur : ces démarches sont fastidieuses, mais tout à fait réalisables. Ce n'est qu'une fois que le jeu aura démontré sa capacité de rétention et que son potentiel réel d'achats intégrés sera évident qu'il faudra entamer les discussions avec un éditeur, chiffres à l'appui. Une négociation avec un éditeur se déroule très différemment lorsque votre jeu possède déjà une audience.</p>
<p>C'est la stratégie que le nouveau mode est conçu pour accompagner : rendre la production de jeux candidats presque gratuite, afin que vous puissiez découvrir ce qui fonctionne avant de commencer à engager des dépenses réglementaires.</p>
<p>Si vous prévoyez de vous lancer sur ce marché, nous aimerions sincèrement échanger avec vous pendant que le mode prend encore forme. <a href="/fr/contact">Contactez-nous</a> ou commencez à créer dans le <a href="/fr/create">Game Creator</a>.</p>
<h2>Références</h2>
<ul>
<li><a href="https://www.pocketgamer.biz/wechat-mini-games-crosses-500m-monthly-active-users-as-tencent-shifts-focus-to-retention/">PocketGamer.biz : les mini-jeux WeChat dépassent les 500 millions d'utilisateurs actifs mensuels</a></li>
<li><a href="https://www.ichongqing.info/2025/07/02/wechat-mini-games-hits-1b-users-becomes-global-magnet-for-game-developers/">iChongqing : les mini-jeux WeChat atteignent un milliard d'utilisateurs</a></li>
<li><a href="https://www.oreateai.com/blog/2025-wechat-mini-game-ecosystem-development-report-indepth-analysis-of-market-trends-and-typical-cases/7f0b62beb192f52409ae047d0bbae184">Oreate : rapport 2025 sur le développement de l'écosystème des mini-jeux WeChat, avec les données du marché de 2024</a></li>
<li><a href="https://developers.weixin.qq.com/minigame/dev/guide/runtime/js-support.html">Documentation officielle de WeChat : prise en charge de JavaScript et interdiction du code dynamique</a></li>
<li><a href="https://developers.weixin.qq.com/minigame/en/dev/guide/best-practice/adapter.html">Documentation officielle de WeChat : couche d'adaptation pour le code conçu comme dans un navigateur</a></li>
<li><a href="https://nikopartners.com/game-regulations-in-china-everything-you-need-to-know/">Niko Partners : réglementation des jeux en Chine</a></li>
<li><a href="https://clearlaunch.dev/regulations/china-game-approval">ClearLaunch : exigences et application de la procédure chinoise d'approbation des jeux, ou banhao</a></li>
<li><a href="https://appinchina.co/blog/the-complete-guide-to-wechat-mini-games/">AppInChina : guide complet des mini-jeux WeChat</a></li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Bénéfices records, licenciements records : comment les développeurs de jeux retrouvent vraiment un emploi en 2026]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-07-02-game-industry-layoffs-getting-rehired</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-07-02-game-industry-layoffs-getting-rehired</guid>
            <pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[L’industrie du jeu vidéo a généré 195,6 milliards de dollars l’an dernier tout en licenciant un tiers de ses effectifs. Voici les chiffres réels sur les licenciements, les raisons pour lesquelles le marché de l’emploi est devenu si impitoyable et ce qui permet réellement aux gens de retrouver du travail.]]></description>
            <content:encoded><![CDATA[<h1>Bénéfices records, licenciements records : comment les développeurs de jeux retrouvent vraiment un emploi en 2026</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, CEO de Cinevva</em></p>
<p>L’industrie du jeu vidéo a généré environ <strong>195,6 milliards de dollars</strong> en 2025. Sa meilleure année de tous les temps. Sur la même période, près de <strong>un développeur sur trois a perdu son emploi</strong>.</p>
<p>Relisez ces deux phrases. Elles ne devraient pas pouvoir coexister, et pourtant nous en sommes là. Si vous avez été licencié au cours des deux dernières années, vous savez déjà que ces chiffres ne donnent pas l’impression d’un simple ralentissement. C’est comme si l’on vous assurait que le bateau va bien alors que c’est vous qui passez par-dessus bord.</p>
<p>J’ai été développeuse indépendante. J’ai vu des amis afficher la bannière « Open to Work » trois fois en dix-huit mois. Ce n’est donc pas un avis provocateur lancé depuis la touche. Examinons ce qui s’est réellement passé, puis la partie qui compte le plus si vous cherchez actuellement un emploi : ce qui permet vraiment aux gens d’être réembauchés.</p>
<h2>Les chiffres sont pires que le ressenti</h2>
<p>La vague de licenciements a commencé en 2022 et ne s’est jamais vraiment arrêtée. Ce n’est pas seulement un mauvais trimestre. C’est une restructuration profonde qui dure depuis quatre ans.</p>
<table>
<thead>
<tr>
<th>Année</th>
<th>Emplois supprimés dans le jeu vidéo</th>
</tr>
</thead>
<tbody>
<tr>
<td>2022</td>
<td>~8 500</td>
</tr>
<tr>
<td>2023</td>
<td>~10 500</td>
</tr>
<tr>
<td>2024</td>
<td>~14 600 (le pic)</td>
</tr>
<tr>
<td>2025</td>
<td>~9 200</td>
</tr>
<tr>
<td>2026</td>
<td>en passe de dépasser 2025, projection à ~11 580</td>
</tr>
</tbody>
</table>
<p>Cela représente environ <strong>44 000 à 55 000 emplois disparus</strong> sur la période, selon le suivi auquel vous vous fiez (<a href="https://en.wikipedia.org/wiki/2022%E2%80%932026_video_game_industry_layoffs">Wikipedia</a>, <a href="https://gamesbeat.com/global-gamings-workforce-grew-0-6-in-4-years-but-north-americas-shrank-11-5-amir-satvat/">GamesBeat</a>). Le rapport 2026 de la GDC sur l’état de l’industrie du jeu vidéo, fondé sur une enquête menée auprès de plus de 2 300 professionnels, révèle que <strong>28 % des développeurs dans le monde ont perdu leur emploi au cours des deux dernières années, et 33 % aux États-Unis</strong> (<a href="https://www.gamesindustry.biz/gdc-survey-reveals-layoffs-up-6-36-of-industry-using-ai-and-overwhelming-support-for-unionisation-in-the-us">GamesIndustry.biz</a>). La moitié a déclaré que son employeur actuel ou le plus récent avait procédé à des licenciements au cours des douze derniers mois.</p>
<p>L’Amérique du Nord a été la plus durement touchée. Le suivi des effectifs réalisé par Amir Satvat, ce qui se rapproche le plus d’un recensement permanent dans cette industrie, montre que les effectifs régionaux ont diminué alors que les effectifs mondiaux ont à peine bougé. Environ <strong>19 % des professionnels nord-américains du jeu vidéo ont été touchés</strong> (<a href="https://gamesbeat.com/global-gamings-workforce-grew-0-6-in-4-years-but-north-americas-shrank-11-5-amir-satvat/">GamesBeat</a>).</p>
<p>Voici le chiffre qui explique pourquoi la recherche d’emploi semble impossible, même après la « reprise » des recrutements. Satvat estime qu’environ <strong>288 000 personnes cherchaient un emploi dans le jeu vidéo</strong> pendant cette période. Jeunes diplômés, vétérans licenciés et personnes en reconversion postulaient tous au même nombre restreint de postes. Lorsque vous envoyez aujourd’hui un CV en réponse à une offre, vous n’êtes pas en concurrence avec une douzaine de personnes. Vous êtes en concurrence avec un stade entier.</p>
<h2>Pourquoi une année record a tout de même entraîné des licenciements</h2>
<p>Les revenus n’ont pas disparu. Ils se sont concentrés. Un nombre de plus en plus réduit de géants du jeu-service accapare une part démesurée du marché, ce qui signifie moins de nouveaux paris, moins de nouvelles équipes et moins de projets de taille moyenne qui absorbaient autrefois les talents. Les investissements privés dans le jeu vidéo ont chuté de plus de moitié en 2025 (<a href="https://respawn.outlookindia.com/gaming/gaming-news/record-profits-record-layoffs-inside-gamings-2026-paradox">Outlook Respawn</a>). Les studios ont réagi comme ils le font toujours : annuler des projets, réduire les budgets et confier davantage de travail à des prestataires.</p>
<p>Les bénéfices sont donc réels, la souffrance aussi, et tous deux ont la même cause. C’est une maigre consolation lorsque c’est vous qui actualisez sans cesse votre boîte de réception, mais cela vous apprend quelque chose d’utile. Ce marché n’attend pas de revenir brusquement à la situation de 2021. Les personnes qui retrouvent un emploi ne l’attendent pas non plus. Elles s’adaptent à la façon dont le recrutement fonctionne réellement aujourd’hui.</p>
<h2>Le marché de l’emploi a un problème d’authenticité</h2>
<p>Parlez à quiconque examine des candidatures aujourd’hui et vous entendrez toujours la même plainte. Ces personnes croulent sous les dossiers. Les offres d’emploi attirent des centaines de candidatures en quelques heures, et la plupart semblent avoir été écrites par la même personne parce que, d’une certaine manière, elles l’ont été par le même outil.</p>
<p>Des recruteurs ont expliqué à TechRound que le plus difficile n’est pas de repérer l’utilisation de l’IA, mais que le déluge de « contenus bien optimisés et dépourvus de contexte » rend presque impossible l’identification de la personne qui comprend réellement le poste (<a href="https://techround.co.uk/news/linkedin-ai-slop-content-recruitment-process/">TechRound</a>). LinkedIn affirme désormais détecter les contenus génériques rédigés par l’IA dans environ 94 % des cas et limiter discrètement leur diffusion (<a href="https://techround.co.uk/news/linkedin-ai-slop-content-recruitment-process/">TechRound</a>).</p>
<p>Retournez la situation et cela devient la meilleure nouvelle de tout cet article. Quand tout se ressemble, être précis devient un super-pouvoir. Dans un flux au vernis identique, la personne qui montre un vrai travail et une vraie réflexion ne se contente pas de sortir du lot. Elle est le seul signal au milieu du bruit.</p>
<h2>Les preuves l’emportent toujours sur la présentation</h2>
<p>Pour les métiers du jeu vidéo en particulier, ce qui fait réellement la différence n’est pas un PDF plus élégant. Ce sont des preuves dont quelqu’un peut faire l’expérience. Un recruteur fort de douze années d’expérience l’a dit sans détour : un build jouable ou une bande démo vous aide davantage que presque tout autre élément de votre candidature.</p>
<p>Voilà le changement. Un CV dit à un recruteur que vous comprenez le game design. Un extrait jouable lui permet de le ressentir en 30 à 90 secondes. On ne peut pas falsifier une mécanique fonctionnelle aussi facilement qu’une puce sur un CV. On ne peut pas feindre un niveau explorable, un shader en temps réel, un rig qui bouge réellement ou un signal audio interactif associé à une vraie scène. L’interactivité constitue une preuve qui résiste à l’examen, car derrière elle se trouve une personne capable de défendre chaque décision.</p>
<p>Et c’est précisément ce dernier point que les recruteurs cherchent réellement à évaluer. Leur méthode pour repérer les portfolios artificiellement enrichis par l’IA est simple : poser des questions sur le processus, pas sur le résultat. Quelqu’un qui a généré une étude de cas peut décrire ce qu’elle contient, mais pas les décisions qui la sous-tendent. Les meilleurs portfolios de 2026 comportent donc tous les quatre mêmes éléments :</p>
<table>
<thead>
<tr>
<th>Couche</th>
<th>La question à laquelle elle répond</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contexte</td>
<td>Le brief, la plateforme et les contraintes réelles (budget, délai, limites techniques)</td>
</tr>
<tr>
<td>Contribution</td>
<td>Ce dont vous étiez personnellement responsable par rapport au travail de l’équipe</td>
</tr>
<tr>
<td>Décisions</td>
<td>Les compromis que vous avez faits, ainsi que ce que vous avez conservé, modifié et rejeté</td>
</tr>
<tr>
<td>Résultat</td>
<td>Des résultats mesurables et vérifiables qu’un inconnu peut contrôler</td>
</tr>
</tbody>
</table>
<p>Cette partie sur ce que vous avez « conservé, modifié et rejeté » est celle que la plupart des gens omettent, alors que c’est précisément celle qui démontre votre discernement. Le discernement est la chose la plus difficile à simuler, et c’est exactement ce qu’un studio achète : quelqu’un capable de choisir la bonne idée parmi dix options plausibles. Oubliez la conclusion du type « j’ai tellement appris ». Montrez la décision que vous avez prise et expliquez pourquoi elle servait mieux le joueur.</p>
<h2>Un mot sur l’IA, car cette industrie a gagné le droit d’être en colère</h2>
<p>Je dirige une plateforme qui utilise des outils d’IA, et je ne vais pas prétendre que cette tension n’existe pas. La même enquête de la GDC révèle que <strong>52 % des développeurs estiment désormais que l’IA générative a un impact négatif sur l’industrie, contre 18 % il y a deux ans</strong>, et que l’opposition la plus forte vient des artistes, des auteurs et des programmeurs (<a href="https://www.gamesindustry.biz/gdc-survey-reveals-layoffs-up-6-36-of-industry-using-ai-and-overwhelming-support-for-unionisation-in-the-us">GamesIndustry.biz</a>). Lorsque vous venez d’être licencié et qu’on vous dit qu’un modèle pourrait accomplir une partie de votre ancien travail, vous conseiller de « simplement utiliser l’IA pour votre portfolio » relève de la gifle, pas du conseil.</p>
<p>Je vais donc le dire clairement. Personne n’est embauché en remettant à un recruteur quelque chose qu’une machine a généré et qu’il est incapable d’expliquer. Le travail doit être le vôtre. Vos décisions, votre savoir-faire, votre responsabilité. Si vous avez utilisé un outil quelque part dans un projet, dites-le et montrez le discernement que vous avez exercé par-dessus : ce que vous avez conservé, ce que vous avez écarté et pourquoi votre décision était meilleure pour le joueur. Les responsables du recrutement font confiance à la personne qui traite un outil comme un stagiaire ayant besoin d’être guidé, pas à celle qui se cache derrière lui. Être transparent sur votre processus témoigne de votre professionnalisme, lequel se résume souvent à ceci : « J’ai réfléchi aux conséquences avant de publier. »</p>
<h2>Où présenter les preuves</h2>
<p>Un conseil pratique, puisque tant de ces échanges commencent sur LinkedIn. Le fil d’actualité pénalise fortement les liens externes, contrairement à la section Sélection de votre profil. C’est le seul endroit qui transforme un lien en une carte d’aperçu propre sans aucune pénalité, et elle se trouve tout en haut de votre profil, là où les recruteurs regardent en premier. Épinglez trois à cinq éléments, en commençant par votre meilleur travail, et rappelez-vous que la plupart des gens consultent votre profil sur leur téléphone. Mettez en avant l’élément que quelqu’un peut ouvrir et découvrir en quelques secondes.</p>
<p>Faites simple, rapide et honnête. Une démo qui se charge en moins de deux secondes et présente clairement une seule idée vaut mieux qu’un projet ambitieux qui saccade ou renvoie une erreur 404. Les recruteurs qui examinent trente portfolios en un après-midi n’attendent pas, et un lien cassé évoque de mauvaises habitudes.</p>
<h2>Pourquoi cela nous tient à cœur</h2>
<p>Chez Cinevva, nous créons des jeux jouables instantanément dans le navigateur, ce qui signifie qu’une grande partie de notre travail correspond exactement aux besoins d’un portfolio moderne : de vrais projets qu’un recruteur peut ouvrir et essayer depuis un simple lien, sans téléchargement ni installation. Nous suivons ce problème de près, car les personnes qu’il touche font partie des nôtres. Si vous avez été licencié, vos compétences ne se sont pas évaporées. Le marché est devenu plus bruyant, et la manière de faire vos preuves a changé.</p>
<p>L’ancienne question était : « Où avez-vous travaillé ? » La nouvelle est : « Montrez-moi quelque chose auquel je peux jouer et expliquez-moi pourquoi vous l’avez conçu ainsi. » Cette deuxième question est plus difficile à contourner et plus favorable aux personnes qui savent réellement faire le travail. Si c’est votre cas, ce marché est brutal, mais il n’est pas fermé. Il attend des preuves auxquelles il peut faire confiance.</p>
<hr>
<h2>Sources</h2>
<ul>
<li><a href="https://www.gamesindustry.biz/gdc-survey-reveals-layoffs-up-6-36-of-industry-using-ai-and-overwhelming-support-for-unionisation-in-the-us">État de l’industrie du jeu vidéo selon la GDC 2026 : licenciements, IA et syndicalisation</a></li>
<li><a href="https://gamesbeat.com/global-gamings-workforce-grew-0-6-in-4-years-but-north-americas-shrank-11-5-amir-satvat/">GamesBeat : les effectifs mondiaux du jeu vidéo ont augmenté de 0,6 % en quatre ans, tandis que ceux d’Amérique du Nord ont diminué</a></li>
<li><a href="https://en.wikipedia.org/wiki/2022%E2%80%932026_video_game_industry_layoffs">Wikipedia : licenciements dans l’industrie du jeu vidéo de 2022 à 2026</a></li>
<li><a href="https://respawn.outlookindia.com/gaming/gaming-news/record-profits-record-layoffs-inside-gamings-2026-paradox">Outlook Respawn : bénéfices records, licenciements records</a></li>
<li><a href="https://www.gamedeveloper.com/business/industry-layoffs-are-seemingly-slowing-but-the-damage-has-already-been-done">Game Developer : les licenciements ralentissent, mais le mal est fait</a></li>
<li><a href="https://techround.co.uk/news/linkedin-ai-slop-content-recruitment-process/">TechRound : LinkedIn confronté à une explosion des contenus médiocres générés par l’IA</a></li>
</ul>
<hr>
<p><strong>À lire aussi :</strong></p>
<ul>
<li><a href="/fr/blog/2026-01-18-skills-over-degrees">Le marché de l’emploi se transforme : des diplômes aux compétences</a></li>
<li><a href="/fr/blog/2026-01-18-ai-controversy-and-post-ai-economy">Controverse autour de l’IA, confiance et économie post-IA du jeu vidéo</a></li>
<li><a href="/fr/blog/2026-02-10-the-intuitive-mind">L’esprit intuitif à l’ère de l’IA</a></li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Les meilleurs jeux gratuits sur navigateur à découvrir maintenant (sans téléchargement)]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-06-30-best-free-browser-games-no-download</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-06-30-best-free-browser-games-no-download</guid>
            <pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Une sélection des meilleurs jeux sur navigateur auxquels vous pouvez jouer instantanément, sans téléchargement ni installation. Des jeux indépendants très bien notés mêlant réflexion, roguelike, narration et rythme.]]></description>
            <content:encoded><![CDATA[<h1>Les meilleurs jeux gratuits sur navigateur à découvrir maintenant (sans téléchargement)</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, CEO de Cinevva</em></p>
<p>Bon, nous n'avions absolument pas prévu de passer une semaine entière sur <a href="http://itch.io">itch.io</a>. Ce qui devait être une recherche rapide s'est transformé en... disons qu'au bout d'une douzaine d'heures, nous avons arrêté de compter. L'objectif était de trouver des jeux auxquels on peut jouer immédiatement — sans téléchargement ni installateur qui mouline en arrière-plan. Un clic, et vous y êtes. C'est tout.</p>
<p>Voici ceux que nous avons retenus.</p>
<h2>Les expériences interactives les mieux notées</h2>
<div class="showcase-card">
<img src="/img/showcases/anxiety.png" alt="Capture d'écran d'Adventures With Anxiety" loading="lazy">
<p><strong>Adventures With Anxiety!</strong> — Histoire interactive<br>
Plus de 6 500 évaluations pour une moyenne de 4,9. C'est fou qu'un jeu qui vous oblige à affronter l'anxiété ait autant de fans, non ? Il met mal à l'aise de la meilleure des façons — celle qui vous reste en tête. <a href="https://ncase.itch.io/anxiety">Jouez-y ici</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/wbwwb.png" alt="Capture d'écran de We Become What We Behold" loading="lazy">
<p><strong>We Become What We Behold</strong> — Critique sociale<br>
7 600 évaluations et une note de 4,8/5. Dix minutes pour le terminer. Des jours pour cesser d'y penser. <a href="https://ncase.itch.io/wbwwb">Jouez-y ici</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/six-cats-under.png" alt="Capture d'écran de Six Cats Under" loading="lazy">
<p><strong>Six Cats Under</strong> — Aventure et réflexion<br>
Douillet et ingénieux. Les graphismes font ici une grande partie du travail, et c'est tout à son avantage. Une note de 4,8/5 attribuée par plus de 6 400 personnes. <a href="https://teambeanloop.itch.io/six-cats-under">Jouez-y ici</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/friday-night-funkin.gif" alt="Capture d'écran de Friday Night Funkin" loading="lazy">
<p><strong>Friday Night Funkin'</strong> — Jeu de rythme<br>
À ce stade, celui-ci est partout. Plus de 11 700 évaluations et une note de 4,7/5. <a href="https://ninja-muffin24.itch.io/funkin">Jouez-y ici</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/sort-the-court.png" alt="Capture d'écran de Sort the Court" loading="lazy">
<p><strong>Sort the Court!</strong> — Simulation de royaume<br>
Votre seule mission : dire oui ou non. Ça n'a l'air de rien. Puis deux heures disparaissent pendant que vous continuez à gérer un petit royaume. <a href="https://graebor.itch.io/sort-the-court">Jouez-y ici</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/dragonsweeper.png" alt="Capture d'écran de Dragonsweeper" loading="lazy">
<p><strong>Dragonsweeper</strong> — Roguelike de réflexion<br>
Prenez le Démineur et combinez-le avec des mécaniques de RPG. Une note de 4,9/5 — c'est peut-être bien le meilleur jeu de toute la liste. <a href="https://danielben.itch.io/dragonsweeper">Jouez-y ici</a></p>
</div>
<h2>Roguelikes et jeux de stratégie jouables sur navigateur</h2>
<div class="showcase-card">
<img src="/img/showcases/backpack-hero.png" alt="Capture d'écran de Backpack Hero" loading="lazy">
<p><strong>Backpack Hero</strong> — Roguelike de gestion d'inventaire<br>
La version navigateur est entièrement gratuite. La version payante ajoute du contenu, mais la version web gratuite se suffit à elle-même. <a href="https://thejaspel.itch.io/backpack-hero">Jouez-y ici</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/vampire-survivors.png" alt="Capture d'écran de Vampire Survivors" loading="lazy">
<p><strong>Vampire Survivors</strong> — Bullet heaven<br>
Il ne s'agit que d'une démo — un seul niveau et sept personnages. C'est tout de même suffisant pour comprendre pourquoi ce jeu a englouti le temps libre de tout le monde. <a href="https://poncle.itch.io/vampire-survivors">Jouez-y ici</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/dome-romantik.gif" alt="Capture d'écran de Dome Romantik" loading="lazy">
<p><strong>Dome Romantik</strong> — Minage et défense<br>
À l'origine, c'était un projet de game jam. Il est devenu Dome Keeper. Puis un énorme succès. Le prototype original tient toujours la route. <a href="https://bippinbits.itch.io/dome-romantik">Jouez-y ici</a></p>
</div>
<h2>Pourquoi ces jeux en particulier ?</h2>
<p>Nous avons testé chacun de ces liens. Pas un simple survol — nous nous sommes vraiment installés pour jouer à ces jeux. Ils se chargent sans accroc. Aucun comportement étrange dans le navigateur. Des milliers d'évaluations viennent de personnes qui n'ont jamais eu à toucher un bouton de téléchargement : elles ont cliqué et se sont lancées directement. Ce parcours sans friction entre « tiens, ça a l'air intéressant » et le moment où l'on joue vraiment ? La plupart des gens sous-estiment à quel point cela change la donne.</p>
<h2>Découvrez davantage de jeux web</h2>
<p>Vous en voulez encore ?</p>
<ul>
<li><a href="https://itch.io/games/top-rated/html5">Les jeux HTML5 les mieux notés sur itch.io</a></li>
<li><a href="https://itch.io/games/top-rated/genre-platformer/html5">Les meilleurs jeux de plateforme HTML5</a></li>
<li><a href="https://itch.io/games/top-rated/html5/year-2024">Les jeux de 2024-2025</a></li>
</ul>
<h2>Jeux indépendants remarquables nécessitant un téléchargement</h2>
<p>Certains excellents jeux ne fonctionnent pas dans votre navigateur. Ceux-ci valent l'étape supplémentaire :</p>
<div class="showcase-card">
<img src="/img/showcases/slice-dice.png" alt="Capture d'écran de Slice & Dice" loading="lazy">
<p><strong>Slice &amp; Dice</strong> — Roguelike à dés<br>
Fonctionne sur Windows, Mac et Android. <a href="https://tann.itch.io/slice-dice">Téléchargez-le sur itch.io</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/stacklands.png" alt="Capture d'écran de Stacklands" loading="lazy">
<p><strong>Stacklands</strong> — Construction de village avec des cartes<br>
Disponible sur Windows et Mac. <a href="https://sokpop.itch.io/stacklands">Obtenez-le sur itch.io</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/luck-be-a-landlord.png" alt="Capture d'écran de Luck be a Landlord" loading="lazy">
<p><strong>Luck be a Landlord</strong> — Roguelike de machine à sous<br>
Ça ressemble à un simple gadget. Ce n'en est pas un. Une véritable dimension stratégique se cache sous cette apparence de machine à sous. <a href="https://trampolinetales.itch.io/luck-be-a-landlord">itch.io</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/patricks-parabox.png" alt="Capture d'écran de Patrick's Parabox" loading="lazy">
<p><strong>Patrick's Parabox</strong> — Casse-têtes récursifs<br>
Attendez-vous à vous creuser la tête. Et à aimer ça malgré tout. <a href="https://patricktraynor.itch.io/patricks-parabox">itch.io</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/dome-keeper.jpg" alt="Capture d'écran de Dome Keeper" loading="lazy">
<p><strong>Dome Keeper</strong> — Minage et défense<br>
La version complète du prototype de game jam mentionné plus haut. <a href="https://store.steampowered.com/app/1637320/Dome_Keeper/">Trouvez-le sur Steam</a></p>
</div>
<div class="showcase-card">
<img src="/img/showcases/brotato.jpg" alt="Capture d'écran de Brotato" loading="lazy">
<p><strong>Brotato</strong> — Survie en arène<br>
<a href="https://store.steampowered.com/app/1942280/Brotato/">Disponible sur Steam</a></p>
</div>
<hr>
<p>Si vous créez des jeux, voici une idée qui mérite réflexion : <strong>les versions navigateur ne mettent aucun obstacle entre les joueurs et votre création</strong>. Aucun téléchargement qui attend dans une file d'attente avant d'être oublié. Aucun écran d'installation à parcourir. Un clic, et les joueurs commencent déjà à se faire une opinion sur ce que vous avez créé. On ne parle pas assez de cette immédiateté — pourtant, elle change tout.</p>
<p>Vous voulez offrir la même chose avec votre propre jeu ? Vous pouvez en créer un directement dans votre navigateur avec le <a href="/fr/create">créateur de jeux Cinevva</a>, et ceux que nous mettons en avant rejoignent notre <a href="/fr/arcade">Arcade communautaire</a>.</p>
<ul>
<li><a href="/fr/creators">Pour les créateurs de jeux</a></li>
<li><a href="/fr/tutorials/ship-web-game-fast">Créer un jeu web qui se charge rapidement</a></li>
</ul>
<style>
.showcase-card {
  margin: 1.5rem 0;
  padding: 1rem;
  border-radius: 8px;
  background: var(--vp-c-bg-soft);
}

.showcase-card img {
  max-width: 100%;
  height: auto;
  border-radius: 6px;
  margin-bottom: 0.75rem;
  display: block;
}

@media (min-width: 640px) {
  .showcase-card {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 1rem;
    align-items: start;
  }
  
  .showcase-card img {
    margin-bottom: 0;
    width: 200px;
  }
}
</style>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Moteurs de jeux web en 2026 : PlayCanvas face à Three.js, Babylon.js et Unity WebGL]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-06-09-web-game-engines-2026-comparison</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-06-09-web-game-engines-2026-comparison</guid>
            <pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Une comparaison actuelle et honnête des principales solutions pour créer un jeu 3D dans le navigateur en 2026 : PlayCanvas (v2.20), Three.js, Babylon.js et Unity WebGL. Ce qu'elles sont, à qui elles s'adressent, leur prise en charge de WebGL2 et WebGPU, leurs licences et comment choisir.]]></description>
            <content:encoded><![CDATA[<h1>Moteurs de jeux web en 2026 : PlayCanvas face à Three.js, Babylon.js et Unity WebGL</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, directeur technique et cofondateur de Cinevva</em></p>
<img src="https://cdn.cinevva.com/blog/web-engines-2026-hero.jpg" alt="Une fenêtre de navigateur affichant une scène 3D passant d'un maillage filaire blanc à gauche à un paysage low-poly coloré à droite" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Si vous souhaitez publier en 2026 un jeu 3D fonctionnant dans un navigateur, vous avez le choix entre quatre solutions grand public et quelques options plus récentes. On les regroupe sous l'appellation « moteurs de jeux web », mais ce sont en réalité des outils très différents, conçus pour résoudre des problèmes différents. Un mauvais choix peut vous coûter des mois. Voici une comparaison actuelle et vérifiée, rédigée par une équipe qui développe professionnellement un moteur 3D web et qui a évalué toutes ces solutions avant de créer le sien.</p>
<p>Nous allons examiner chaque option, ce qu'elle est réellement, à qui elle s'adresse et quelles sont ses limites, puis vous proposer un petit guide de décision à la fin. Les numéros de version mentionnés sont exacts en juillet 2026.</p>
<blockquote>
<p>Vous cherchez un panorama complet ? Cet article se concentre sur les quatre principales solutions 3D. Pour découvrir les 11 moteurs web classés et comparés, y compris des options 2D comme Phaser, Defold et Construct, consultez notre guide des <a href="/fr/guides/web-game-engines-comparison">meilleurs moteurs de jeux web pour 2026</a>.</p>
</blockquote>
<h2>Comparaison rapide</h2>
<table>
<thead>
<tr>
<th></th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
<th>Cinevva</th>
</tr>
</thead>
<tbody>
<tr>
<td>Type de solution</td>
<td>Moteur complet + éditeur hébergé</td>
<td>Bibliothèque de rendu</td>
<td>Moteur complet + éditeur</td>
<td>Moteur de bureau, export web</td>
<td>Plateforme de mondes native pour l'IA</td>
</tr>
<tr>
<td>Architecture</td>
<td>Entité-composant (ECS)</td>
<td>Graphe de scène</td>
<td>Graphe de scène + composants</td>
<td>GameObject/composant</td>
<td>Graphe de scène + créateur IA</td>
</tr>
<tr>
<td>Scripts</td>
<td>TypeScript / JavaScript</td>
<td>JavaScript (à vous de créer le reste)</td>
<td>TypeScript / JavaScript</td>
<td>C# (compilé en Wasm)</td>
<td>Langage naturel + code</td>
</tr>
<tr>
<td>Moteur de rendu</td>
<td>WebGL2, WebGPU en cours de maturation</td>
<td>WebGL2, WebGPU en progression</td>
<td>WebGL2, WebGPU avancé</td>
<td>WebGL2 (export)</td>
<td>WebGPU uniquement</td>
</tr>
<tr>
<td>Éditeur</td>
<td>Hébergé, commercial</td>
<td>Aucun</td>
<td>Web, gratuit</td>
<td>Bureau, commercial</td>
<td>Immersif, au sein du monde</td>
</tr>
<tr>
<td>Physique</td>
<td>Ammo / intégrations</td>
<td>À fournir</td>
<td>Havok intégré</td>
<td>Intégrée (PhysX)</td>
<td>Solveur de personnage personnalisé</td>
</tr>
<tr>
<td>Licence</td>
<td>Moteur MIT, éditeur propriétaire</td>
<td>MIT</td>
<td>Apache 2.0</td>
<td>Propriétaire</td>
<td>Propriétaire</td>
</tr>
<tr>
<td>Idéal pour</td>
<td>Jeux de navigateur produits comme en studio</td>
<td>3D personnalisée, contrôle total</td>
<td>Jeux avec tout le nécessaire intégré</td>
<td>Portage de jeux Unity existants</td>
<td>Création de jeux dans le monde par IA</td>
</tr>
</tbody>
</table>
<p>La suite de cet article explique ce tableau.</p>
<h2>PlayCanvas</h2>
<p>PlayCanvas est ce qui se rapproche le plus, sur le web, d'un flux de travail à la Unity. Le moteur est open source sous licence MIT et se trouve à la version 2.20.6 en juillet 2026. Il repose sur un système entité-composant, la logique du jeu s'écrit en TypeScript ou en JavaScript, et les ressources passent par une chaîne de traitement côté serveur qui produit des fichiers GLB. De véritables jeux commerciaux l'utilisent, tout comme de grandes entreprises en production, Snap étant un exemple public.</p>
<p>Deux idées reçues sur PlayCanvas méritent d'être corrigées. Premièrement, bien que le moteur soit sous licence MIT, l'éditeur visuel hébergé qu'utilisent réellement la plupart des équipes est un produit commercial, et non open source. Vous pouvez utiliser librement le moteur sans cet éditeur, mais le flux de travail associant éditeur et cloud est payant. Deuxièmement, PlayCanvas privilégie WebGL2. Il propose une voie WebGPU, mais celle-ci est encore en cours de maturation et ne constitue pas le moteur de rendu par défaut. Ne choisissez donc pas PlayCanvas aujourd'hui en espérant utiliser des shaders de calcul WebGPU en production.</p>
<p>Le domaine dans lequel PlayCanvas est véritablement en tête est le Gaussian splatting 3D. Son éditeur et sa visionneuse SuperSplat, que l'équipe a publiés en open source, comptent parmi les meilleurs outils disponibles pour capturer et diffuser des scènes de splats sur le web, avec une diffusion en continu basée sur WebGPU pour les captures volumineuses. Si vous souhaitez créer des environnements photoréalistes numérisés, c'est une vraie raison de commencer ici.</p>
<p><strong>Choisissez PlayCanvas si</strong> vous voulez un éditeur semblable à Unity et une chaîne de traitement gérée des ressources pour le navigateur, et si vous produisez le type de jeu que créerait un studio. <strong>Cherchez ailleurs si</strong> vous avez besoin des fonctions de calcul WebGPU dès aujourd'hui ou si vous souhaitez éviter un éditeur commercial hébergé.</p>
<h2>Three.js</h2>
<p>Three.js n'est pas un moteur de jeu. C'est une bibliothèque de rendu, et de très loin la plus utilisée dans ce domaine. Elle fournit un graphe de scène, des caméras, des lumières, des matériaux, de la géométrie, des chargeurs et un moteur de rendu, puis s'arrête là. Il n'y a ni éditeur, ni physique, ni système d'entités, ni opinion imposée sur la manière de structurer un jeu. À vous de les ajouter ou de les trouver dans l'écosystème.</p>
<p>Ce compromis résume toute la proposition. Vous bénéficiez d'un contrôle maximal et de la plus grande communauté de la 3D web, au prix de devoir créer ou assembler tout ce qui se trouve au-dessus du moteur de rendu. Son moteur de rendu WebGPU progresse régulièrement et peut déjà être utilisé, même si, comme pour les autres solutions, la voie WebGL2 reste l'option mature par défaut. Three.js est sous licence MIT.</p>
<p><strong>Choisissez Three.js si</strong> vous souhaitez un contrôle total et une base minimale, et si vous disposez des compétences techniques nécessaires pour construire vos systèmes de jeu par-dessus. <strong>Cherchez ailleurs si</strong> vous voulez disposer immédiatement d'un éditeur et de systèmes de jeu.</p>
<h2>Babylon.js</h2>
<p>Babylon.js est un moteur complet sous licence Apache 2.0, soutenu par une équipe de Microsoft. Contrairement à Three.js, il intègre les éléments que vous devriez autrement assembler vous-même : un modèle de composants, le moteur physique Havok, un éditeur web gratuit et une chaîne de traitement des ressources. Ses travaux sur WebGPU ont progressé rapidement et figurent parmi les plus avancés des moteurs généralistes, même s'il conserve WebGL2 comme voie offrant la compatibilité la plus large. Babylon.js 9.0, sorti en mars 2026, a encore renforcé cette avance avec un éclairage groupé sur les deux backends, un éclairage volumétrique reposant sur des shaders de calcul WebGPU et la prise en charge des ombres pour les splats gaussiens 3D.</p>
<p>Si votre raisonnement est « je veux un moteur complet, avec la physique incluse, et ses conventions me conviennent », Babylon constitue un excellent choix par défaut et probablement l'option gratuite la plus complète du secteur.</p>
<p><strong>Choisissez Babylon.js si</strong> vous souhaitez une solution prête à l'emploi, avec la physique intégrée et un éditeur gratuit. <strong>Cherchez ailleurs si</strong> vous voulez une dépendance très légère ou recherchez spécifiquement un flux de travail hébergé comparable à celui de Unity.</p>
<h2>Unity WebGL</h2>
<p>Unity WebGL n'est pas un moteur web, mais une cible d'exportation. Vous développez dans l'éditeur de bureau Unity, en C#, puis compilez votre projet en un bundle WebGL qui s'exécute dans le navigateur grâce à WebAssembly. C'est donc la réponse évidente dans un cas précis : vous avez déjà un jeu Unity et souhaitez en créer une version pour navigateur.</p>
<p>Pour un projet pensé d'abord pour le web, cette solution entraîne de véritables coûts. Le poids du runtime et du téléchargement est important, le démarrage est plus lent qu'avec un moteur web natif, et les performances dans les navigateurs mobiles constituent un problème connu. Unity est propriétaire et sa sortie WebGL cible WebGL2. Unity 6 propose un backend WebGPU expérimental, mais celui-ci est désactivé par défaut et n'est pas prêt pour la production.</p>
<p><strong>Choisissez Unity WebGL si</strong> vous avez un projet Unity existant à porter vers le navigateur ou si votre équipe travaille déjà entièrement dans Unity. <strong>Cherchez ailleurs si</strong> le chargement instantané sur des téléphones de milieu de gamme est une exigence absolue, ou si vous partez de zéro avec une approche web-first.</p>
<h2>La place de la création native pour l'IA et intégrée au monde</h2>
<p>Toutes les solutions ci-dessus partagent une même hypothèse : un développeur construit le jeu à son bureau, dans un éditeur, puis publie un runtime. Cette hypothèse est correcte pour la plupart des projets et, si elle correspond au vôtre, choisissez l'une des quatre options précédentes.</p>
<p>Il faut néanmoins savoir que ce n'est plus la seule possibilité, car une catégorie différente est en train d'émerger. Nous développons Cinevva, une plateforme de mondes exclusivement basée sur WebGPU, dans laquelle les jeux sont créés depuis l'intérieur même du monde. Au lieu d'ouvrir un éditeur, vous incarnez un avatar présent dans l'espace, vous décrivez ce que vous souhaitez, puis un créateur IA transforme votre demande en terrain, en objets et en comportements pendant que vous êtes sur place. La création et le jeu ont lieu au cours de la même session. Sous le capot, cela nous a conduits à adopter exclusivement WebGPU avec un terrain reposant sur des shaders de calcul, à écrire un solveur de personnage personnalisé plutôt qu'un moteur physique généraliste, et à développer un système d'animation et de reciblage. Nous détaillons ces choix dans notre article expliquant <a href="/fr/blog/2026-06-08-why-we-built-our-own-webgpu-engine">pourquoi nous avons créé notre propre moteur WebGPU</a>.</p>
<p>Cinevva ne remplace ni PlayCanvas ni Babylon. Si vous êtes développeur et créez un jeu précis, ces outils sont adaptés. Cinevva répond à un objectif différent : permettre aux personnes qui ne développent pas de moteurs de créer et de partager des espaces jouables simplement en les décrivant. Nous le mentionnons ici parce que la question « quel moteur de jeu web devrais-je utiliser ? » admet de plus en plus une cinquième réponse qui n'est pas du tout un moteur.</p>
<h2>Matrice complète des fonctionnalités</h2>
<p>Le tableau rapide ci-dessus en donne les grandes lignes. Voici la version détaillée, regroupée par sous-système. Quelques précisions utiles pour bien la lire : « À fournir » signifie que le moteur n'intègre pas la fonctionnalité, mais que l'écosystème ou votre propre code peut l'ajouter. Cela concerne particulièrement Three.js, qui est délibérément une bibliothèque de rendu : dans son cas, « À fournir » relève de sa philosophie plutôt que d'une lacune. Pour Unity, « Export uniquement » signifie que la fonctionnalité existe dans l'éditeur de bureau et est incluse dans la version WebGL, plutôt que d'être native au web. Les cellules des solutions concurrentes reflètent leurs capacités prêtes à l'emploi et leur comportement bien documenté à la mi-2026. Les cellules de Cinevva correspondent à ce qui fonctionne dans notre version publiée, tandis que la mention « prévu » désigne ce qui est conçu mais pas encore développé.</p>
<h3>Rendu</h3>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th>Cinevva</th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Moteur de rendu principal</td>
<td>WebGPU uniquement</td>
<td>WebGL2 (WebGPU en bêta)</td>
<td>WebGL2 (WebGPU en progression)</td>
<td>WebGL2 (WebGPU avancé)</td>
<td>WebGL2 (export)</td>
</tr>
<tr>
<td>Shaders de calcul en production</td>
<td>Oui (dépendance centrale)</td>
<td>Bêta</td>
<td>Via WebGPU</td>
<td>Oui (WebGPU)</td>
<td>Export uniquement, limité</td>
</tr>
<tr>
<td>Création de shaders</td>
<td>Nœuds TSL + calcul</td>
<td>Fragments de shaders / GLSL</td>
<td>GLSL + nœuds (TSL)</td>
<td>Matériau nodal / GLSL / WGSL</td>
<td>ShaderLab / HLSL</td>
</tr>
<tr>
<td>Éclairage groupé / forward+</td>
<td>Oui (froxel)</td>
<td>Oui</td>
<td>À fournir</td>
<td>Oui</td>
<td>Oui</td>
</tr>
<tr>
<td>Nuages volumétriques et météo</td>
<td>Oui</td>
<td>À fournir</td>
<td>À fournir</td>
<td>Partiel</td>
<td>À fournir</td>
</tr>
<tr>
<td>Outils de Gaussian splatting 3D</td>
<td>Prévus</td>
<td>Oui (SuperSplat, référence du secteur)</td>
<td>Communauté</td>
<td>Oui</td>
<td>Extensions</td>
</tr>
</tbody>
</table>
<h3>Monde et terrain</h3>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th>Cinevva</th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Streaming intégré des grands mondes</td>
<td>Oui (chunks de 64 m)</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
<td>Export uniquement</td>
</tr>
<tr>
<td>Système de terrain</td>
<td>Carte de hauteur hybride + marching cubes/SDF</td>
<td>À fournir</td>
<td>À fournir</td>
<td>Extension</td>
<td>Intégré (bureau)</td>
</tr>
<tr>
<td>Sculpture du terrain à l'exécution</td>
<td>Oui (GPU)</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
<td>Non (pendant l'édition)</td>
</tr>
<tr>
<td>Grottes et surplombs (véritable topologie 3D)</td>
<td>Oui (marching cubes)</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
</tr>
<tr>
<td>Feuillage et herbe instanciés par GPU</td>
<td>Oui</td>
<td>Oui</td>
<td>À fournir</td>
<td>Oui</td>
<td>Oui</td>
</tr>
</tbody>
</table>
<h3>Physique et personnage</h3>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th>Cinevva</th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Moteur physique</td>
<td>Solveur cinématique personnalisé</td>
<td>Intégration d'Ammo</td>
<td>À fournir (Rapier/Cannon/Ammo)</td>
<td>Havok intégré</td>
<td>PhysX intégré</td>
</tr>
<tr>
<td>Dynamique des corps rigides</td>
<td>Non (par conception)</td>
<td>Oui</td>
<td>À fournir</td>
<td>Oui</td>
<td>Oui</td>
</tr>
<tr>
<td>Contrôleur de personnage</td>
<td>Oui (FSM multimode)</td>
<td>Modèles / Ammo</td>
<td>À fournir</td>
<td>Oui</td>
<td>Intégré</td>
</tr>
<tr>
<td>Collision intégrée au terrain (SDF)</td>
<td>Oui</td>
<td>Non</td>
<td>À fournir</td>
<td>Non</td>
<td>Non</td>
</tr>
</tbody>
</table>
<h3>Animation</h3>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th>Cinevva</th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Animation squelettique</td>
<td>Oui</td>
<td>Oui</td>
<td>Oui</td>
<td>Oui</td>
<td>Oui</td>
</tr>
<tr>
<td>Fondu / machine à états</td>
<td>Oui (FSM de résolution)</td>
<td>Oui (graphe d'états d'animation)</td>
<td>Mixeur (fondu à fournir)</td>
<td>Oui</td>
<td>Oui (Mecanim)</td>
</tr>
<tr>
<td>Reciblage de squelette</td>
<td>Oui (pipeline)</td>
<td>Limité</td>
<td>Communauté</td>
<td>Partiel</td>
<td>Oui (humanoïde)</td>
</tr>
<tr>
<td>Cinématique inverse</td>
<td>Prévue</td>
<td>Limitée</td>
<td>Communauté</td>
<td>Oui</td>
<td>Oui</td>
</tr>
</tbody>
</table>
<h3>Multijoueur et backend</h3>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th>Cinevva</th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Multijoueur intégré</td>
<td>Oui (autorité à la périphérie)</td>
<td>À fournir (Photon/Colyseus)</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir (Netcode, non natif pour le web)</td>
</tr>
<tr>
<td>Monde partagé persistant</td>
<td>Oui (Durable Objects par chunk)</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
</tr>
<tr>
<td>Chat vocal spatial</td>
<td>Oui (WebRTC + HRTF)</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
<td>À fournir</td>
</tr>
</tbody>
</table>
<h3>Création et conception</h3>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th>Cinevva</th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Éditeur</td>
<td>Immersif, au sein du monde</td>
<td>Hébergé, de type logiciel de bureau (commercial)</td>
<td>Aucun (minimal)</td>
<td>Web (gratuit)</td>
<td>Bureau (commercial)</td>
</tr>
<tr>
<td>Création incarnée dans le monde</td>
<td>Oui</td>
<td>Non</td>
<td>Non</td>
<td>Non</td>
<td>Non</td>
</tr>
<tr>
<td>Création en langage naturel / par IA</td>
<td>Oui (créateur IA)</td>
<td>Non</td>
<td>Non</td>
<td>Non</td>
<td>Non</td>
</tr>
<tr>
<td>Scripts</td>
<td>Langage naturel + JS</td>
<td>TypeScript / JS</td>
<td>JavaScript</td>
<td>TypeScript / JS</td>
<td>C#</td>
</tr>
</tbody>
</table>
<h3>Ressources et distribution</h3>
<table>
<thead>
<tr>
<th>Fonctionnalité</th>
<th>Cinevva</th>
<th>PlayCanvas</th>
<th>Three.js</th>
<th>Babylon.js</th>
<th>Unity WebGL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Chaîne de traitement des ressources côté serveur</td>
<td>Oui (GLB + LOD + KTX2 + Draco)</td>
<td>Oui (GLB)</td>
<td>Chargeurs uniquement</td>
<td>Outils d'importation</td>
<td>Oui</td>
</tr>
<tr>
<td>Génération de ressources par IA intégrée</td>
<td>Oui (3D, image, audio, musique)</td>
<td>Non</td>
<td>Non</td>
<td>Non</td>
<td>Non</td>
</tr>
<tr>
<td>Recherche fédérée de ressources</td>
<td>Oui (onze fournisseurs)</td>
<td>Boutique de ressources</td>
<td>Non</td>
<td>Non</td>
<td>Boutique de ressources</td>
</tr>
<tr>
<td>Fonctionne dans le navigateur, sans installation</td>
<td>Oui</td>
<td>Oui</td>
<td>Oui</td>
<td>Oui</td>
<td>Oui (lourd)</td>
</tr>
<tr>
<td>Licence du moteur</td>
<td>Plateforme propriétaire</td>
<td>Moteur MIT, éditeur propriétaire</td>
<td>MIT</td>
<td>Apache 2.0</td>
<td>Propriétaire</td>
</tr>
</tbody>
</table>
<p>La tendance qui se dégage de cette matrice est le véritable enseignement. Les moteurs généralistes répartissent leurs atouts horizontalement : chacun couvre correctement la plupart des besoins, tout en vous laissant gérer le monde, le backend et le processus de création. Cinevva concentre ses capacités verticalement : la plateforme couvre moins de domaines, mais maîtrise l'ensemble du parcours, du moteur de rendu jusqu'au monde partagé que vous créez de l'intérieur. Aucune de ces approches n'est intrinsèquement meilleure. Elles répondent à des questions différentes.</p>
<h2>Comment choisir</h2>
<p>Choisissez l'outil en fonction de votre situation plutôt qu'à partir d'une simple liste de fonctionnalités.
Si vous avez déjà un jeu Unity, exportez-le avec Unity WebGL et acceptez son poids. Si vous êtes un studio qui souhaite un workflow basé sur un éditeur pour créer des jeux sur navigateur, utilisez PlayCanvas. Si vous voulez un moteur complet et gratuit avec la physique incluse, utilisez Babylon.js. Si vous voulez un contrôle total et disposez de l’équipe nécessaire pour construire votre jeu à partir d’un moteur de rendu minimal, utilisez Three.js. Et si votre objectif n’est pas de créer un seul jeu, mais de permettre aux utilisateurs de créer et de jouer dans un monde partagé en décrivant ce qu’ils souhaitent, c’est la catégorie dans laquelle nous travaillons, et vous pouvez <a href="/fr/">essayer Cinevva</a>.</p>
<p>Quel que soit votre choix, réalisez des prototypes concrets pour l’évaluer avant de vous engager. Chacune de ces solutions peut prendre en charge un véritable jeu, et changer de technologie à mi-parcours vous coûtera des mois que vous n’aurez pas consacrés à publier votre jeu.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Pourquoi nous avons créé notre propre moteur WebGPU au lieu de forker PlayCanvas]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-06-08-why-we-built-our-own-webgpu-engine</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-06-08-why-we-built-our-own-webgpu-engine</guid>
            <pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Three.js, Babylon.js, PlayCanvas, Unity WebGL : nous les avons tous évalués avant d'écrire notre propre moteur de rendu. Voici les points forts de chacun, les quatre décisions qui nous ont poussés à abandonner les solutions existantes et les cas dans lesquels vous ne devriez surtout pas faire comme nous.]]></description>
            <content:encoded><![CDATA[<h1>Pourquoi nous avons créé notre propre moteur WebGPU au lieu de forker PlayCanvas</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<img src="https://cdn.cinevva.com/blog/why-webgpu-engine-hero.jpg" alt="Un monde stylisé en low poly rendu par le moteur WebGPU de Cinevva : des collines ondulantes, une grotte creusée dans une falaise, une rivière sinueuse et un avatar avançant sur un chemin à l'heure dorée" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Toute personne ayant un profil technique qui découvre Cinevva World pose la même question dans les cinq premières minutes. Vous êtes une petite équipe. Il existe des moteurs 3D web éprouvés. Pourquoi avoir écrit votre propre moteur de rendu, votre propre système de physique des personnages et votre propre système d'animation, au lieu de vous appuyer sur PlayCanvas ou Babylon pour livrer plus vite ?</p>
<p>C'est une question légitime, et répondre « parce que ce n'est pas nous qui l'avons inventé » serait une mauvaise réponse. Nous avons fait nos devoirs. Nous avons testé les moteurs existants, lu leur code source et réalisé des prototypes avec deux d'entre eux avant de prendre notre décision. Cet article présente la version honnête de cette décision : les véritables points forts des solutions prêtes à l'emploi, les quatre aspects pour lesquels nos besoins divergeaient suffisamment pour justifier une création maison, et les situations où vous devriez choisir l'une de ces solutions plutôt que de nous imiter. Nous avons documenté le développement lui-même dans une longue <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">série technique consacrée à la création d'un monde ouvert dans le navigateur</a>. Lorsqu'une affirmation ci-dessous s'appuie sur un journal de développement concret, je fournirai le lien correspondant.</p>
<h2>Les solutions que nous avons réellement évaluées</h2>
<p>Quatre types d'outils sont qualifiés de « moteurs de jeu web », alors qu'ils ne sont pas de même nature.</p>
<p><strong>Three.js</strong> est une bibliothèque de rendu, pas un moteur de jeu. Elle vous fournit un graphe de scène, des matériaux, des chargeurs et un moteur de rendu, puis vous laisse faire. Pas d'éditeur, pas de physique, pas de système d'entités, aucune opinion sur la manière dont votre jeu doit être structuré. C'est à la fois son attrait et son coût. Vous construisez vous-même tout ce qui se trouve au-dessus du moteur de rendu, mais rien ne vous impose de contraintes. Elle est sous licence MIT, possède le plus vaste écosystème du secteur et, lorsque vous tombez sur un obscur bug de shader à deux heures du matin, une réponse vous attend sur un forum.</p>
<p><strong>Babylon.js</strong> est un moteur complet doté d'un graphe de scène, d'une intégration physique (Havok), d'un pipeline de ressources et d'un éditeur web. Il est sous licence MIT, soutenu par une équipe de Microsoft, et ses travaux sur WebGPU progressent rapidement. Si vous voulez une solution tout-en-un et acceptez de travailler dans la structure imposée par le moteur, c'est un excellent choix par défaut.</p>
<p><strong>PlayCanvas</strong> est ce qui se rapproche le plus d'un « Unity pour le web ». À l'heure où nous écrivons ces lignes, le moteur est en version 2.19.6, publiée le 5 juin 2026, et son code source est disponible sous licence MIT. Cependant, ce que la plupart des gens utilisent réellement est l'éditeur visuel hébergé, qui est un produit commercial et non open source. PlayCanvas repose sur un système entité-composant, permet d'écrire des scripts en TypeScript ou JavaScript, fait transiter les ressources par un pipeline GLB côté serveur, et de véritables jeux commerciaux ont été publiés avec lui (Snap utilise notamment PlayCanvas en production). Son moteur de rendu fonctionne avec WebGL2 et propose une voie WebGPU encore en cours de maturation, plutôt que de l'utiliser par défaut. Ce dernier détail compte plus qu'il n'y paraît, et j'y reviendrai.</p>
<p><strong>Unity WebGL</strong> n'est pas du tout un moteur web. C'est une cible d'exportation. Vous développez dans l'éditeur de bureau Unity, puis compilez le projet sous forme de bundle WebGL. C'est le bon outil si vous possédez déjà un jeu Unity et souhaitez le proposer dans un navigateur, mais le mauvais si « se charge instantanément dans un onglet sur un téléphone de milieu de gamme » constitue une exigence absolue, car le poids de l'environnement d'exécution et du téléchargement est lui aussi embarqué.</p>
<p>N'importe laquelle de ces solutions constitue une base raisonnable pour un jeu web classique. Mais notre jeu web n'avait rien de classique.</p>
<h2>Première décision : WebGPU est notre socle, pas notre ligne d'arrivée</h2>
<p>Voici la divergence qui nous éloigne de toutes les solutions existantes. Pour nous, WebGPU est une exigence, pas une fonctionnalité que nous adopterons progressivement.</p>
<p>Notre terrain n'est pas une carte de hauteurs statique. Il s'agit d'un système hybride associant un champ de cartes de hauteurs diffusé en continu et des chunks générés par marching cubes à partir d'un champ de distance signé. Le monde peut ainsi contenir de véritables grottes et surplombs, que les créateurs peuvent sculpter en direct. Les brosses de sculpture, la dispersion de la végétation et le maillage du terrain s'exécutent tous sous forme de compute shaders. Sans calcul GPU, le monde ne fonctionne pas en mode dégradé : il ne fonctionne tout simplement pas.</p>
<p>C'est l'exact opposé de la position actuelle des moteurs généralistes. Leur prise en charge de WebGPU est conçue comme une amélioration progressive venant compléter un moteur de rendu principalement basé sur WebGL2, avec une solution de repli pour les navigateurs qui ne le prennent pas en charge. PlayCanvas, en particulier, privilégie WebGL2, tandis que WebGPU y est encore en bêta. C'est le bon choix pour eux, car leur mission consiste à exécuter la plus grande variété possible de jeux sur la plus grande variété possible d'appareils. Notre objectif est plus étroit, mais plus poussé. Nous avons donc fait le choix inverse : nous sommes passés exclusivement à WebGPU lors du prototype 13 et n'avons jamais fait marche arrière. Les navigateurs sans WebGPU ne bénéficient pas d'un mode dégradé : ils ne sont pas pris en charge. Nous considérons cela comme une donnée de couverture plutôt que de prétendre qu'un repli vers WebGL2 ne nécessite que l'activation d'une option de configuration. Ce n'est pas le cas. Il faudrait réécrire partiellement nos étapes de terrain et de végétation.</p>
<p>Construire sur un moteur privilégiant WebGL2 nous aurait obligés soit à lutter contre ses hypothèses de repli pour chaque fonctionnalité de calcul GPU, soit à maintenir éternellement deux voies de rendu. Posséder notre propre moteur de rendu nous a permis de considérer le calcul GPU comme la base.</p>
<h2>Deuxième décision : un solveur de personnage, pas un moteur physique</h2>
<p>La solution classique consiste à intégrer un moteur physique. Nous l'avons essayée. Un premier prototype a validé le fonctionnement de Rapier dans un worker, avec un résultat tout à fait correct.</p>
<p>Nous avons tout de même écrit notre propre système, et la version livrée ne contient aucune trace de Rapier, Cannon ou Ammo. La raison tient au périmètre. Nous n'avons pas besoin de corps rigides, d'articulations, de ragdolls ni d'un solveur de contraintes. Nous avons besoin qu'une seule capsule se déplace correctement sur le terrain, et que la marche, la glissade, le vol plané, l'escalade et la nage partagent une réponse unique à la question : « Suis-je au sol, sur quelle surface et avec quelle inclinaison ? » Un moteur physique généraliste complique cette tâche au lieu de la simplifier, car ces modes finissent par entrer en conflit avec ses ressorts et amortisseurs internes.</p>
<p>Notre contrôleur de personnage est donc une <a href="/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character">machine à états multicanal modulaire</a>. Chaque mode est une petite unité qui indique s'il souhaite prendre le contrôle pendant cette frame et qui, s'il l'obtient, définit la vélocité et l'orientation. Les modes sont départagés par priorité sur trois canaux — ressource, puis posture, puis locomotion — et consultent tous la même requête de terrain. La collision repose sur le sondage d'une capsule par rapport à la carte de hauteurs ou au champ de distance signé, selon le chunk.</p>
<p>Le détail dont je suis le plus fier n'a rien de spectaculaire. La détection du sol analyse toutes les surfaces de la colonne verticale située sous les pieds du personnage et sélectionne la plus haute se trouvant au niveau de la capsule ou en dessous, au lieu de se fier au gradient du champ de distance signé. Au bord d'un surplomb, la surface latérale la plus proche est la paroi de la falaise. Une normale basée sur le gradient oscille alors entre « sol » et « mur », ce qui provoque des glissades fantômes. Grâce à la requête par colonne, se tenir sur une corniche devient parfaitement banal, ce qui est exactement le résultat recherché. Un collisionneur de champ de hauteur prêt à l'emploi ne nous aurait pas fourni cela gratuitement, car il ne peut même pas accéder à notre terrain. Celui-ci réside sur le GPU dans notre propre format. Aucun moteur externe ne peut gérer ses collisions sans que nous recopiions l'intégralité du champ dans le format de son collisionneur à chaque modification.</p>
<h2>Troisième décision : des animations transférables entre les rigs</h2>
<p>Les avatars utilisent le rig Synty POLYGON, tandis que nos clips de mouvement proviennent d'une vaste bibliothèque d'animations ouverte. Comme le rig et les clips partagent les mêmes noms d'os, le cas courant ne nécessite aucun retargeting à l'exécution : les pistes sont simplement associées par leur nom. Nous avons présenté l'ensemble du pipeline de personnages dans l'article sur les <a href="/fr/blog/2026-05-10-open-world-browser-part-25-universal-characters">personnages universels</a>.</p>
<p>Le travail intéressant concerne les cas où ils ne correspondent pas. Nous avons créé une passe de retargeting qui transpose un squelette sur un autre, et son bon fonctionnement a nécessité de résoudre trois problèmes précis. Il faut d'abord aligner les poses de référence, car l'association d'une pose en A et d'une pose en T ajoute silencieusement environ trente degrés par articulation. Il faut ensuite mettre à l'échelle le mouvement de la racine et la longueur des pas avec deux ratios distincts : la distance entre les hanches et le sol pour la verticale, et les proportions globales pour l'horizontale. Enfin, il faut tester les clips retargetés par rapport à leur source à une fréquence d'échantillonnage fixe, afin que les régressions soient détectées avant qu'un joueur ne les remarque. Ce pipeline nous permet d'ajouter de nouvelles sources d'animation et, à terme, des personnages fournis par les créateurs, sans corriger manuellement chaque clip.</p>
<p>Par-dessus vient se greffer une machine à états d'animation qui choisit à chaque frame la famille de clips appropriée en fonction de l'état du mouvement : variante de saut selon la vitesse, intensité de l'atterrissage selon l'impact, direction de la pente calculée à partir du produit scalaire de la vélocité et du gradient du terrain, arrêts respectant la phase des pieds pour éviter que le personnage ne termine sa course en moonwalk, et temporisation de cent millisecondes afin qu'un simple choc contre un mur ne fasse pas alterner frénétiquement les états de marche et de chute. Rien de tout cela n'est exotique, mais c'est le genre de résultat qu'on n'obtient qu'en maîtrisant entièrement cette couche.</p>
<h2>Quatrième décision : la partie qu'aucun moteur ne fournit</h2>
<p>Les trois autres décisions portent sur le fonctionnement du monde. Celle-ci concerne la nature même du produit, et explique pourquoi la comparaison avec PlayCanvas relève finalement d'une erreur de catégorie.</p>
<p>PlayCanvas, Babylon, Unity et une application Three.js développée sur mesure reposent tous sur le même modèle : vous créez votre jeu assis à un bureau, dans un éditeur 2D, en observant le monde de l'extérieur, puis vous appuyez sur Lecture et publiez un environnement d'exécution distinct. Même l'édition intégrée au moteur suppose que vous soyez devant une station de travail, en train de manipuler une scène que vous observez.</p>
<p>Cinevva World inverse ce modèle. Vous créez depuis l'intérieur du monde, sous la forme d'un avatar se tenant dans le même espace que celui qu'occuperont vos joueurs, en décrivant ce que vous voulez et en le regardant apparaître. La création et le jeu forment une seule session continue, et non une étape de production reliée à un environnement d'exécution. Les références les plus proches sont Roblox, Rec Room et Horizon Worlds, et non les moteurs 3D web. Même ces plateformes conservent toutefois l'essentiel de la création sur ordinateur. Ce qui rend notre approche viable sans éditeur piloté à la souris, c'est le constructeur IA, qui transforme « place ici un quai éclairé par des lanternes » en géométrie et en positionnement.</p>
<p>Vous ne pouvez pas simplement greffer cela sur un moteur généraliste, car il ne s'agit pas d'une fonctionnalité de rendu. C'est le principe fondateur de toute la pile technologique, depuis la possibilité de modifier le terrain à l'exécution jusqu'à la manière dont le réseau considère chaque objet comme quelque chose qu'une personne se tenant à proximité vient de créer.</p>
<h2>Quand vous ne devriez pas faire comme nous</h2>
<p>Voici la partie que les récits du genre « nous avons créé notre propre moteur » omettent généralement. Si vous voulez publier un jeu dans le navigateur ce trimestre, ne nous imitez pas. Utilisez PlayCanvas si vous voulez un éditeur dans le style de Unity et un pipeline géré. Utilisez Babylon si vous voulez un moteur complet incluant la physique et acceptez de travailler dans sa structure. Utilisez Three.js si vous recherchez un contrôle maximal, un minimum de contraintes conceptuelles et disposez d'une équipe capable de construire les couches supérieures. Réalisez un portage depuis Unity si vous possédez déjà un jeu Unity.</p>
<p>Écrire son propre moteur de rendu, son propre système physique et son propre système d'animation n'est le bon choix que lorsque le principe même de votre produit est incompatible avec les solutions existantes, lorsque ce que vous construisez n'est pas un jeu fonctionnant sur un moteur, mais un lieu qui se trouve être constitué d'un moteur. C'était notre cas. Ce n'est probablement pas le vôtre, et ce n'est pas un problème. L'intérêt d'une évaluation honnête est de savoir dans quelle situation vous vous trouvez avant d'écrire la première ligne de code.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Un monde vide est un monde triste : ce que toutes les plateformes de création, disparues ou toujours en vie, peuvent nous apprendre]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-06-06-how-creator-worlds-live-or-die</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-06-06-how-creator-worlds-live-or-die</guid>
            <pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Nous avons conçu une IA capable de construire des bâtiments entiers dans un monde 3D partagé à partir d’une simple phrase. Avant de l’ouvrir à tous, nous avons étudié le lancement de toutes les plateformes comparables que nous avons pu trouver. Le cimetière est immense, les survivantes sont rares, et la frontière entre les deux est bien plus nette que le battage médiatique ne veut l’admettre. Voici leurs histoires, les chiffres et ce que nous faisons différemment.]]></description>
            <content:encoded><![CDATA[<h1>Un monde vide est un monde triste : ce que toutes les plateformes de création, disparues ou toujours en vie, peuvent nous apprendre</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<figure style="margin:1.5rem 0">
<img src="/img/blog/ai-builder-hero.webp" alt="L’AI Builder de Cinevva construisant une maison à deux étages à partir d’une instruction saisie dans le chat" style="width:100%;border-radius:10px">
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Notre AI Builder. Quelqu’un a écrit « construis un deuxième étage » et l’IA a posé le plancher, monté les murs, puis proposé d’ajouter des escaliers et un toit, le tout dans un monde où d’autres personnes peuvent entrer et se promener.</figcaption>
</figure>
<p>Nous pouvons désormais générer des mondes, et je ne parle pas d’une vidéo de démonstration. L’image ci-dessus montre notre éditeur. Quelqu’un a écrit « construis un deuxième étage » dans une fenêtre de chat et a regardé l’IA poser le plancher, monter les murs de façade, latéraux et arrière, puis proposer de poursuivre avec des escaliers et un toit. Pas de simples accessoires ou textures, mais des structures entières, placées dans un monde où d’autres personnes peuvent entrer et se promener. Notre pipeline produit aussi les éléments nécessaires : modèles 3D, musique, sons et matériaux.</p>
<p>Nous sommes donc sur le point d’ouvrir Cinevva World au public. Et avant de le faire, je suis allé lire les avis de décès.</p>
<p>Car voici la vérité dérangeante sur ce que nous construisons. Depuis quinze ans, des équipes très talentueuses et très bien financées essaient de faire fonctionner des mondes créatifs partagés. Quelques-unes ont donné naissance à certaines des plus grandes créations jamais réalisées par l’humanité. La plupart sont mortes. Ce qui sépare ces deux destins, ce n’est ni le talent ni l’argent, car certains des projets disparus disposaient de bien plus des deux que nous n’en aurons jamais. La différence tient à une poignée de décisions que presque personne ne prend correctement du premier coup. Voici ce que j’ai découvert.</p>
<h2>Le cimetière</h2>
<p>Commençons par les échecs, car ils sont plus sincères que les réussites. Les gagnants réécrivent leur histoire pour en faire un récit bien ordonné. Les perdants laissent fuiter des notes internes.</p>
<h3>Meta Horizon Worlds : la pièce vide la plus chère jamais construite</h3>
<p>Meta a dépensé des dizaines de milliards dans son pari sur le métavers. Reality Labs a enregistré une <a href="https://www.cnbc.com/2026/03/18/meta-horizon-worlds-metaverse-vr.html">perte d’exploitation de 6,02 milliards de dollars en un seul trimestre</a>, annoncée début 2026, après avoir perdu plusieurs milliards par trimestre pendant des années. Son produit phare était Horizon Worlds. Voici ce que cet argent a permis d’obtenir.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/mNbz7-ejnZw" title="Horizon Worlds — bande-annonce officielle" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Horizon Worlds de Meta, bande-annonce officielle. La plateforme dans laquelle Meta a investi des milliards abandonne la prise en charge de la VR en juin 2026 pour tenter de survivre sous la forme d’une application mobile.</figcaption>
</figure>
<p>Une note interne ayant fuité, relayée par <a href="https://fortune.com/2022/10/07/metas-horizon-world-quality-poor-not-even-employees-using-says-metaverse-vp-leaked-memo/amp/">Fortune</a> et <a href="https://ryanschultz.com/2022/10/07/leaked-internal-memos-from-meta-detail-problems-with-horizon-worlds-and-horizon-workrooms/">Ryan Schultz</a>, révélait que seuls 9 % environ des mondes construits par les créateurs avaient reçu la visite d’au moins 50 personnes. L’immense majorité n’avait enregistré aucune visite en dehors de celle de son propre créateur. Une note résumait le problème d’engagement en une phrase que tous les fondateurs de ce secteur devraient se faire tatouer : <a href="https://kotaku.com/meta-facebook-horizon-worlds-vr-mark-zuckerberg-1849669048">« un monde vide est un monde triste »</a>. Des chercheurs souhaitant interroger des utilisateurs actifs n’ont pu en trouver que 514, tant le nombre de joueurs était faible.</p>
<p>Les chiffres de rétention étaient pires encore. Des données ayant fuité montraient que <a href="https://mixed-news.com/en/horizon-worlds-leak-only-1-in-10-users-return-web-launch-coming/">seul un utilisateur sur dix environ revenait</a> après son premier mois. Meta a discrètement ramené son propre objectif pour 2022 de 500 000 utilisateurs mensuels à 200 000, et même alors, la plateforme comptait moins d’utilisateurs simultanés que VRChat et moins que Second Life, un jeu datant de 2003. Les tableaux de bord internes de dogfooding montraient que les propres employés de Meta ne l’utilisaient pas, ce qui a poussé la direction à tenir les responsables comptables des connexions hebdomadaires de leurs équipes. Un vice-président a placé l’équipe en « confinement qualité » pour le reste de l’année et reconnu par écrit qu’elle travaillait sur un produit qui n’avait pas trouvé son adéquation produit-marché.</p>
<p>En mars 2026, Meta a <a href="https://www.cnbc.com/2026/03/18/meta-horizon-worlds-metaverse-vr.html">annoncé l’abandon complet de la VR</a> et la transformation d’Horizon Worlds en une application presque exclusivement mobile, destinée aux personnes ne possédant pas de casque et fonctionnant, selon sa propre présentation, comme Roblox. Le métavers le plus coûteux jamais construit a fini sa vie en essayant de devenir ce qu’il aurait dû être dès le premier jour.</p>
<p>La leçon n’est pas que « la VR est arrivée trop tôt », même si c’était le cas. La leçon, c’est qu’aucune quantité de capitaux ne peut remplir un monde vide. La densité de contenu ne s’achète pas avec un budget marketing. Si les mondes sont vides, les gens partent ; et lorsque les gens partent, les mondes restent vides. Cette boucle a dévoré Meta tout cru.</p>
<h3>Decentraland et The Sandbox : les villes fantômes à un milliard de dollars</h3>
<p>En octobre 2022, une seule donnée a mis le feu à l’univers des cryptomonnaies. <a href="https://www.coindesk.com/web3/2022/10/07/its-lonely-in-the-metaverse-decentralands-38-daily-active-users-in-a-13b-ecosystem">DappRadar indiquait</a> que Decentraland comptait 38 utilisateurs actifs quotidiens. The Sandbox en comptait 522. Chacun de ces projets affichait alors une capitalisation boursière d’environ 1,3 milliard de dollars.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/Zg5vcdEeLOA" title="The Sandbox — teaser officiel" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">The Sandbox, teaser officiel. The Sandbox et Decentraland étaient chacune valorisées autour de 1,3 milliard de dollars alors que leur nombre d’utilisateurs quotidiens se situait entre quelques centaines et quelques milliers.</figcaption>
</figure>
<p>Les responsables de ces projets ont contesté ces chiffres, et les nuances comptent en réalité davantage que la moquerie. Le chiffre de 38 ne comptabilisait que les portefeuilles uniques effectuant une transaction sur la blockchain au cours d’une journée, ce que DappRadar appelle les « payeurs », et non les personnes qui se connectaient et se promenaient sans interagir avec la blockchain. Decentraland a répondu qu’il comptait <a href="https://blockworks.com/news/metaverse-platforms-set-the-record-straight-about-daily-active-users">56 697 utilisateurs mensuels connectés</a> et environ 8 000 utilisateurs quotidiens selon d’autres estimations. The Sandbox revendiquait 39 000 utilisateurs quotidiens pendant une saison alpha, avec une rétention de 40 % au quatorzième jour.</p>
<p>Mais attardez-vous un instant sur cet écart, car c’est le chiffre le plus important de tout cet article. Même en retenant les estimations les plus généreuses, on obtient des valorisations à un milliard de dollars reposant, au mieux, sur quelques milliers d’utilisateurs quotidiens. À titre de comparaison, nos recherches ont montré qu’à la même période, Roblox, qui ne reposait pas sur la blockchain, comptait <a href="https://blockworks.com/news/metaverse-platforms-set-the-record-straight-about-daily-active-users">52 millions d’utilisateurs actifs quotidiens, mais seulement 11,3 millions d’utilisateurs payants mensuels</a>. Dans les mondes virtuels gratuits, la plupart des gens effectuent rarement, voire jamais, de transaction. Cette réalité vaut dans les deux sens. Elle signifie que la raillerie autour des 38 utilisateurs actifs quotidiens était injuste, mais aussi que la valorisation d’un milliard de dollars relevait de la pure fiction. Les deux mesuraient le mauvais indicateur. La valorisation mesurait la spéculation autour des jetons. La moquerie mesurait les transactions des portefeuilles. Aucune ne permettait de savoir si quelqu’un s’amusait.</p>
<p>Voilà le piège des indicateurs de vanité dans sa forme la plus pure. Comptes inscrits, capitalisation des jetons, marché total adressable, retombées dans la presse : aucun de ces chiffres ne vous dit si votre monde est vivant. Les seuls qui comptent sont le nombre de personnes qui reviennent et la volonté de celles qui créent de continuer à créer.</p>
<h3>Dreams : le plus bel outil dans lequel personne ne pouvait rester</h3>
<p>Media Molecule, le studio à l’origine de LittleBigPlanet, a consacré près d’une décennie à Dreams, un outil de création si puissant que des utilisateurs y ont conçu des jeux jouables, des courts métrages d’animation et des albums de musique. La critique l’adorait. Pourtant, en <a href="https://www.pushsquare.com/news/2023/07/dreams-live-service-ending-as-media-molecule-couldnt-find-a-sustainable-path">septembre 2023, le studio a mis fin au service en ligne</a>, déclarant simplement qu’il n’avait pas réussi à trouver de modèle économique viable.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/2ltgkcoQzow" title="Dreams | Bande-annonce avec date de sortie | PS4" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Dreams, par Media Molecule. Un outil de création adoré de ses utilisateurs, dont le service en ligne a pris fin en 2023 sans jamais avoir permis aux créateurs de gagner le moindre centime.</figcaption>
</figure>
<p>L’analyse post-mortem ressemble à une liste de blessures auto-infligées, et je dis cela avec un immense respect, car nous pourrions commettre chacune de ces erreurs. Dreams <a href="https://www.pushsquare.com/news/2023/07/dreams-live-service-ending-as-media-molecule-couldnt-find-a-sustainable-path">n’a jamais permis aux créateurs de monétiser leur travail</a>. La boutique intégrée promise n’a jamais vu le jour. Il n’y avait ni objets cosmétiques, ni passe de combat, ni rien du tout. Les créateurs sérieux, ceux qui transforment un outil en plateforme, n’avaient aucune raison d’y investir des années sans aucune perspective de retour. Le jeu est sorti uniquement sur PS4, sans version PC ni version PS5 native au lancement, ce qui a fortement limité son audience potentielle. Et le cofondateur <a href="https://icon-era.com/threads/media-molecule-co-founders-biggest-regret-with-dreams-was-not-focusing-on-multiplayer-understands-new-ip-is-more-of-a-game-than-a-creative-tool.11724/">Mark Healey a déclaré plus tard que son plus grand regret était de ne pas avoir privilégié le multijoueur</a>, ajoutant que l’équipe avait fini par comprendre que sa création fonctionnait mieux comme un jeu que comme un outil de création.</p>
<p>Relisez cette dernière partie. Le studio de contenu généré par les utilisateurs le plus talentueux au monde a conclu, après coup, que son produit aurait dû être d’abord un jeu, puis un outil. C’est la phrase la plus importante de toute cette étude, et nous y reviendrons.</p>
<h3>Le reste du cimetière</h3>
<p>Le schéma se répète avec une sinistre régularité.</p>
<p><a href="https://www.gamedeveloper.com/business/crayta-to-shut-down-after-launch-platform-stadia-bites-the-dust">Crayta</a>, une plateforme collaborative de création de contenu généré par les utilisateurs, a fermé en mars 2023 et ses utilisateurs ont perdu tout ce qu’ils avaient créé. Son développeur avait été racheté par Meta en 2021, ce qui n’a pas empêché la fermeture de la plateforme. La cause immédiate : elle avait été lancée sur Google Stadia et, lorsque Stadia est mort, Crayta est mort avec lui. Dépendre d’une plateforme revient à braquer une arme chargée sur sa propre tempe.</p>
<figure style="margin:1.5rem 0">
<img src="https://cdn.cinevva.com/blog/lived-or-died-crayta.webp" alt="Crayta, la plateforme collaborative de création de jeux" loading="lazy" style="width:100%;border-radius:10px">
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Crayta a été lancé sur Google Stadia. Lorsque Stadia a fermé en 2023, Crayta a disparu avec lui, et les créateurs ont perdu tout ce qu’ils avaient conçu. Image tirée de la bande-annonce.</figcaption>
</figure>
<p>Project Spark, l’outil de création de jeux en contenu généré par les utilisateurs de Microsoft, avait fermé des années plus tôt en suivant la même trajectoire : un outil puissant, mais trop peu de personnes qui restaient. Sansar, le successeur en VR de Second Life conçu par Linden Lab, a été revendu après avoir échoué à trouver son public. High Fidelity, la deuxième entreprise de métavers fondée par Philip Rosedale, lui-même créateur de Second Life, a totalement abandonné les mondes virtuels pour changer d’activité.</p>
<figure style="margin:1.5rem 0">
<img src="https://cdn.cinevva.com/blog/lived-or-died-secondlife.webp" alt="Second Life, le monde virtuel créé par ses utilisateurs de Linden Lab" loading="lazy" style="width:100%;border-radius:10px">
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Second Life, le monde lancé par Linden Lab en 2003, fonctionne toujours après plus de vingt ans. Ses propres successeurs en VR, Sansar et High Fidelity de Philip Rosedale, ont tous deux échoué. Image tirée de la bande-annonce.</figcaption>
</figure>
<p>Et dans un signal qu’il est impossible d’ignorer puisqu’il se manifeste en ce moment même, Rec Room <a href="https://www.uploadvr.com/vrchat-statement-after-rec-room-and-horizon-worlds-fold/">a annoncé sa fermeture à compter du 1er juin 2026</a>, déclarant n’avoir jamais atteint une rentabilité durable.</p>
<p>Les fondateurs d’entreprises du métavers, occupés à construire des métavers, ne parvenaient pas eux-mêmes à rester dans leurs propres métavers. Cela devrait terrifier quiconque pense que le plus difficile est le rendu graphique.</p>
<figure style="margin:1.5rem 0">
<img src="https://cdn.cinevva.com/blog/lived-or-died-recroom.webp" alt="Rec Room, le monde social multiplateforme fondé sur le contenu généré par les utilisateurs" loading="lazy" style="width:100%;border-radius:10px">
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Rec Room, l’un des plus grands mondes sociaux de création, a accueilli plus de 150 millions de joueurs au total et a malgré tout fermé le 1er juin 2026, sans jamais parvenir à transformer cette audience en activité viable. Image tirée de la bande-annonce.</figcaption>
</figure>
<h2>Les survivants, et ce qui les a réellement sauvés</h2>
<p>Maintenant, les vivants. Ce qui est instructif, c’est qu’aucun n’a gagné comme on aurait pu s’y attendre, et que plusieurs ont réussi malgré le non-respect de règles suivies par ceux qui ont échoué.</p>
<h3>Roblox : le succès fulgurant qui a pris douze ans</h3>
<p>Tout le monde cite Roblox comme modèle. Presque personne ne reproduit ce qui s’est réellement passé, parce que ce qui s’est réellement passé, ce sont douze années à ressembler à un échec.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/LTnMKjXEnMY" title="ROBLOX - Bande-annonce du jeu" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Roblox. Il a attiré environ 100 personnes en 2006 et mis douze ans à connaître une croissance exponentielle, entièrement grâce à la possibilité donnée à la communauté de créer les jeux.</figcaption>
</figure>
<p>La bêta de Roblox en 2006 a attiré <a href="https://www.howtheygrow.co/p/how-roblox-grows">environ 100 passionnés de technologie, avec un pic de 30 à 40 personnes connectées simultanément</a>. La plateforme a ensuite progressé laborieusement pendant plus de dix ans. Elle est passée de 9 millions d’utilisateurs début 2016 à 90 millions en 2019, puis à 214 millions en 2023. Son fondateur, David Baszucki, a refusé d’abandonner sa vision fondée sur le contenu généré par les utilisateurs, même lorsque Minecraft l’a largement dépassé en 2009. Il considérait cette évolution comme une lente maturation délibérée, et la décision véritablement déterminante a été de laisser la communauté créer tous les jeux plutôt que de les produire en interne. Cela a supprimé le coût du contenu et transformé chaque créateur en moteur de croissance.</p>
<p>Mais voici ce que manquent ceux qui reproduisent aveuglément le modèle, et pourquoi je garde une deuxième source ouverte. L’<a href="https://www.matthewball.co/all/roblox2024">analyse de 2024</a> de Matthew Ball montre que Roblox fonctionne à perte de manière brutale, même à très grande échelle, avec une marge opérationnelle d’environ -38 %, seulement 6 % des utilisateurs achetant sa monnaie au cours d’un mois donné et un revenu par utilisateur très inférieur à celui des plateformes sur console. Roblox est le plus grand jeu au monde et peine encore à gagner de l’argent. La boucle de croissance est réelle, mais l’équation économique est difficile. Quiconque vous affirme qu’une plateforme de création est une machine à imprimer de l’argent essaie de vous vendre quelque chose.</p>
<p>La leçon plus profonde se cache pourtant à la vue de tous dans ces chiffres d’utilisateurs. L’écrasante majorité des centaines de millions d’utilisateurs de Roblox ne créent jamais rien. Ils viennent pour <em>jouer</em> à un catalogue de dizaines de millions de jeux conçus par d’autres. Il a fallu dix ans et une économie rémunérant les développeurs pour constituer ce catalogue. Roblox n’est pas un outil de création auquel les gens jouent accessoirement. C’est un endroit où jouer, dans lequel une petite fraction des utilisateurs crée accessoirement. Cet ordre change tout.</p>
<h3>Minecraft : le monde qui ne pouvait pas être vide</h3>
<p>Minecraft n’a jamais connu le problème du monde vide, et comprendre pourquoi est la clé de tout.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/MmB9b5njVbA" title="Bande-annonce officielle de Minecraft" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Minecraft, bande-annonce officielle. Grâce à la survie procédurale, le monde n’était jamais vide et n’avait jamais besoin de contenu créé par les utilisateurs pour valoir la peine d’être joué.</figcaption>
</figure>
<p>Minecraft est sorti comme un jeu de survie doté d’une génération procédurale. Le monde se construit tout seul, à l’infini, et dès la première minute, vous ne restez pas devant une toile vierge à vous demander quoi faire. La nuit tombe et quelque chose va vous tuer, alors vous creusez. Le jeu est amusant pour une personne seule, sans aucun autre utilisateur ni contenu généré par les utilisateurs, indéfiniment, car le générateur procédural est une machine à contenu inépuisable. Les mods, les serveurs, les cartes personnalisées, tout l’écosystème créatif : tout cela s’est développé <em>par-dessus</em> un jeu qui se vendait déjà tout seul. Le mode créatif est arrivé après que des millions de joueurs étaient déjà devenus accros à la survie. Le bouche-à-oreille et YouTube ont fait le reste.</p>
<p>Minecraft a résolu le problème du démarrage à froid en ne le rencontrant jamais. C’est le code de triche, et nous allons le lui voler.</p>
<h3>Jeux par navigateur : la distribution comme unique avantage défendable</h3>
<p>Il y a ensuite le genre qui se rapproche le plus de notre véritable mode de diffusion : le jeu par navigateur sans téléchargement.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/oznO3GGzu2s" title="Bande-annonce de Krunker.io" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Krunker, un FPS par navigateur dans la tradition des jeux .io. Tout l’avantage défendable du genre repose sur la distribution : un lien qui charge un jeu complet sans téléchargement.</figcaption>
</figure>
<p><a href="https://news.viverse.com/post/what-are-io-games">Agar.io a atteint environ 5 millions de joueurs quotidiens en quelques semaines</a> après son lancement en 2015. <a href="http://Slither.io">Slither.io</a> a cumulé 68 millions de téléchargements sur mobile et 67 millions de parties dans le navigateur. Les recherches expliquent sans détour pourquoi : <a href="https://news.viverse.com/post/what-are-io-games">« l’avantage concurrentiel défendable du genre .io réside dans la distribution elle-même, pas dans la mécanique de jeu. »</a> Vous cliquez sur un lien et vous jouez. Aucune installation, aucun compte, aucune boutique d’applications. <a href="https://medium.com/the-megacool-blog/9-growth-hacks-from-viral-hit-game-agar-io-7d8f71f70a0c">La prise en main d’Agar.io se faisait en un clic</a> : saisissez un nom, appuyez sur Jouer, et vous y êtes. Le jeu s’est propagé dans les applications de messagerie comme les vidéos se propagent sur TikTok.</p>
<p>Mais ce cadeau cache une lame, qui peut également se retourner contre nous. L’absence de téléchargement implique l’absence d’engagement. La même fluidité qui fait entrer quelqu’un lui permet aussi de repartir, car fermer un onglet ne coûte rien. La viralité sur navigateur est réelle, mais elle est aussi superficielle par défaut. Les jeux .io y ont survécu en étant immédiatement et manifestement amusants dès les dix premières secondes. Les mondes par navigateur qui sont morts étaient faciles d’accès, mais ne vous donnaient aucune raison de rester une fois arrivé.</p>
<h3>Among Us, Figma, Townscaper, VRChat : quatre autres façons de gagner</h3>
<p>Quatre exemples rapides, car chacun isole un levier différent.</p>
<p><a href="https://www.esports.net/news/how-among-us-blew-up-online-two-years-after-release/">Among Us</a> est sorti en 2018 et n’a pratiquement rien rapporté pendant deux ans. Les développeurs ont failli l’abandonner. Puis, en septembre 2020, une <a href="https://howtomarketagame.com/2020/09/14/among-us-the-4-lessons-of-their-viral-success/">succession de streamers à l’audience toujours plus importante</a> l’a découvert, jusqu’à un stream de plusieurs heures par Sodapoppin, et le jeu est devenu un phénomène mondial. Son premier coup de pouce est venu d’une mise en avant sur la page d’accueil d’<a href="http://itch.io">itch.io</a> et d’un public coréen qui a fini par représenter la moitié de ses ventes. Leçon : le décollage peut survenir des années après le lancement, par un canal que vous n’aviez pas prévu, à condition de survivre assez longtemps pour être découvert.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/NSJ4cESNQfE" title="Bande-annonce de la sortie d’Among Us sur Steam" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Among Us. Il est resté presque inaperçu pendant deux ans avant qu’une succession de streamers n’en fasse un phénomène mondial en 2020.</figcaption>
</figure>
<p><a href="https://review.firstround.com/the-5-phases-of-figmas-community-led-growth-from-stealth-to-enterprise/">Figma</a> est resté en développement confidentiel pendant environ trois ans, a été lancé gratuitement, puis a attendu deux années supplémentaires avant de devenir payant. Son moment d’activation était le multijoueur : l’instant où un designer voyait quelqu’un d’autre modifier le même fichier. Et voici ce qui compte. À l’origine, l’offre gratuite de Figma vous limitait à 2 collaborateurs, ce qui étouffait précisément le moment magique qui vendait le produit. L’entreprise a corrigé cela en rendant le nombre de collaborateurs illimité afin que les utilisateurs puissent réellement faire cette expérience. Elle a amorcé sa communauté en ciblant directement des designers influents sur Twitter. Leçon : trouvez votre déclic, puis supprimez tous les obstacles qui en séparent un nouvel utilisateur.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/n5gJgkO2Dg0" title="Présentation de lancement du produit Figma" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Figma. Son moment d’activation était le multijoueur, alors l’entreprise a supprimé la limite de collaborateurs de l’offre gratuite, qui bloquait précisément la magie permettant de vendre le produit.</figcaption>
</figure>
<p><a href="https://www.gamedeveloper.com/game-platforms/how-townscaper-works-a-story-four-games-in-the-making">Townscaper</a> était un petit jouet créatif devenu un succès grâce aux captures d’écran. Son développeur a vu son nombre d’abonnés sur Twitter passer d’environ 20 000 à 90 000 simplement en publiant des extraits pendant le développement, et il y a vu le premier signe que le jeu fonctionnerait bien. Leçon : si votre création est belle et que les gens la partagent spontanément avant même son lancement, c’est la validation la plus authentique qui soit.</p>
<figure style="margin:1.5rem 0">
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:10px">
<iframe src="https://www.youtube-nocookie.com/embed/hqq25n6cQqo" title="Bande-annonce de lancement de Townscaper" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>
<figcaption style="color:#9aa7b4;font-size:0.85rem;margin-top:0.5rem">Townscaper, bande-annonce de lancement. Un jouet créatif sans objectifs qui s’est propagé grâce aux captures d’écran partagées spontanément par les utilisateurs.</figcaption>
</figure>
<p>Et <a href="https://www.uploadvr.com/vrchat-statement-after-rec-room-and-horizon-worlds-fold/">VRChat</a>, au cours des mêmes mois où les milliards de Meta et Rec Room ont tous deux capitulé, a atteint un record de près de 160 000 utilisateurs simultanés. La plateforme est animée par sa communauté et ses créateurs, et dispose de moyens très inférieurs à ceux des projets morts autour d’elle. VRChat attribue sa survie à la fidélité et à la créativité de sa communauté plutôt qu’au capital. Leçon, formulée par le survivant lui-même : la passion de créateurs impliqués survit à l’argent.</p>
<h2>Ce qui sépare réellement les vivants des morts</h2>
<p>Mettez ces histoires côte à côte et le schéma saute aux yeux.</p>
<table>
<thead>
<tr>
<th>Plateforme</th>
<th>Son pari</th>
<th>Son point d’arrivée</th>
<th>La leçon</th>
</tr>
</thead>
<tbody>
<tr>
<td>Minecraft</td>
<td>Une survie procédurale, amusante pour un joueur seul</td>
<td>Jeu le plus vendu de tous les temps, plus de 350 millions d’exemplaires</td>
<td>Ne jamais avoir un monde vide</td>
</tr>
<tr>
<td>Roblox</td>
<td>Des jeux créés par la communauté et une rémunération des créateurs</td>
<td>Plus de 80 millions d’utilisateurs quotidiens après douze ans</td>
<td>Être d’abord un lieu où jouer, puis un outil</td>
</tr>
<tr>
<td>Jeux .io</td>
<td>Jeu par navigateur en un clic, sans téléchargement</td>
<td>Des millions de joueurs en quelques semaines</td>
<td>La distribution est l’avantage défendable, l’engagement reste superficiel</td>
</tr>
<tr>
<td>Figma</td>
<td>Offre gratuite, multijoueur en temps réel</td>
<td>Une offre de rachat d’environ 20 milliards de dollars</td>
<td>Lever chaque obstacle au déclic</td>
</tr>
<tr>
<td>Among Us</td>
<td>Un jeu social peu coûteux et de la patience</td>
<td>3,8 millions d’utilisateurs simultanés, avec deux ans de retard</td>
<td>Survivre assez longtemps pour être découvert</td>
</tr>
<tr>
<td>Townscaper</td>
<td>Un beau jouet digne d’être capturé en image</td>
<td>Environ 380 000 ventes, diffusion organique</td>
<td>Le partage spontané est le signal le plus authentique</td>
</tr>
<tr>
<td>VRChat</td>
<td>L’énergie de la communauté et des créateurs</td>
<td>Environ 160 000 utilisateurs simultanés en 2026</td>
<td>La passion survit au capital</td>
</tr>
<tr>
<td>Horizon Worlds</td>
<td>Des milliards de capitaux, la VR avant tout</td>
<td>VR abandonnée en 2026, environ 1 utilisateur sur 10 revenait</td>
<td>L’argent ne peut pas remplir un monde vide</td>
</tr>
<tr>
<td>Decentraland / Sandbox</td>
<td>Une économie de jetons avant un jeu</td>
<td>Valorisation d’environ 1,3 milliard de dollars, quelques centaines d’utilisateurs quotidiens</td>
<td>Une valorisation n’est pas un public</td>
</tr>
<tr>
<td>Dreams</td>
<td>Un outil complet, sans économie ni multijoueur</td>
<td>Service en ligne arrêté en 2023</td>
<td>Rémunérer les créateurs, proposer le multijoueur, être un jeu</td>
</tr>
<tr>
<td>Crayta</td>
<td>Une plateforme de contenu généré par les utilisateurs dépendante de Stadia</td>
<td>Fermée avec Stadia en 2023</td>
<td>Ne jamais bâtir sur une plateforme qui peut vous expulser</td>
</tr>
<tr>
<td>Project Spark / Sansar</td>
<td>Des outils puissants, aucune raison de revenir</td>
<td>Fermés ou réorientés</td>
<td>Un outil sans jeu ne crée aucune habitude quotidienne</td>
</tr>
</tbody>
</table>
<p>Ce qui tue les mondes créatifs, c’est la pièce vide. Horizon Worlds, Decentraland, Dreams, Crayta, Project Spark et Sansar sont tous morts de la même manière : pas assez de monde, pas assez de contenu, pas assez de raisons de revenir, dans une boucle qui s’autoalimente. Le problème du démarrage à froid est le combat contre le boss. Tout le reste n’est qu’un niveau de didacticiel.</p>
<p>Ce qui les sauve, c’est d’avoir une raison d’exister avant l’arrivée de la foule. Minecraft avait la survie procédurale. Roblox avait une décennie de jeux patiemment amorcés. Les jeux .io procuraient du plaisir en dix secondes. Chaque survivant méritait votre temps même avec un catalogue vide. Chaque victime était un magnifique contenant attendant que d’autres personnes lui donnent une raison d’être visité. C’est la confession de Dreams érigée en principe général : créez d’abord un jeu, puis une plateforme. La plateforme est le deuxième acte. Il faut la mériter en proposant d’abord quelque chose que les gens utilisent pour sa valeur propre.
Les indicateurs qui prédisent réellement la survie ne sont pas ceux du communiqué de presse. Les recherches convergent sur ce point. Le véritable signe d’une adéquation produit-marché, c’est que votre <a href="https://newsletter.pmcurve.com/p/retention-masterclass">courbe de rétention s’aplatit</a> : une cohorte de personnes continue de revenir indéfiniment. Une courbe qui décline jusqu’à zéro signifie qu’il n’y a pas d’adéquation, quelle que soit la taille du haut de l’entonnoir. Les ordres de grandeur du secteur sont d’environ <a href="https://solsten.io/blog/d1-d7-d30-retention-in-gaming">40 % de rétention au jour 1, 20 % au jour 7 et 10 % au jour 30</a>, le jour 30 étant celui qui prédit la santé à long terme. Et pour tout produit comportant un volet créateur, c’est du côté de l’offre que vous mourez : <a href="https://forkoff.xyz/blog/founder-growth/two-sided-marketplace-cold-start-2026">environ 67 % des marketplaces qui échouent s’effondrent à cause de l’offre</a>, et non de la demande. La rétention des créateurs est le signe vital. Les comptes inscrits et les valorisations ne sont que des indicateurs de vanité.</p>
<p>Quelques autres leçons ressortent de ces histoires. Permettez à vos créateurs de gagner de l’argent, sinon vous les perdrez, car Dreams a prouvé que les personnes talentueuses n’investiront pas des années sans aucune contrepartie. Le multijoueur est généralement le cœur du projet, pas une fonctionnalité, comme Media Molecule l’a elle-même regretté. Ne bâtissez jamais votre maison sur une plateforme qui peut vous expulser, comme l’a montré Crayta. Et l’absence de téléchargement est une arme à double tranchant : c’est le meilleur canal d’acquisition qui existe et le plus faible moteur d’engagement, ce qui signifie que tout repose sur la qualité des soixante premières secondes.</p>
<h2>Le piège qui se cache sous tout cela</h2>
<p>Il reste une couche supplémentaire, propre à notre époque, car nous disposons d’un outil que les disparus n’ont jamais eu. Nous pouvons générer des mondes grâce à l’IA. Ce qui soulève un paradoxe sur lequel je change d’avis depuis des semaines.</p>
<p>Si c’est de l’IA, chaque monde ne devrait-il pas être unique, généré à neuf pour chaque personne et adapté à ses goûts ? Mais si le monde de chacun est unique, comment qui que ce soit peut-il croiser quelqu’un d’autre, puisqu’un moment partagé exige un lieu partagé ? Et si le monde doit malgré tout être partagé, pourquoi utiliser l’IA plutôt que de créer artisanalement un seul lieu magnifique, comme le ferait un véritable studio ?</p>
<p>Chacune de ces pistes, poussée jusqu’au bout, mène à quelque chose qui ne fonctionne pas. Un monde généré uniquement pour vous est un monde où il n’y a personne. Genie de Google peut produire un environnement jouable à partir d’une phrase, puis celui-ci disparaît une minute plus tard : solo, sans mémoire, sans voisins. C’est l’impasse de la solitude. Mais créer artisanalement un monde fixe est aussi une impasse : il faut une centaine de personnes et plusieurs années pour remplir deux kilomètres carrés à la main, et quand vous avez terminé, le monde est figé, identique à votre centième visite comme à la première, incapable de grandir à mesure que les gens arrivent.</p>
<p>Pour sortir de cette impasse, il faut cesser de considérer « le monde » comme une seule chose. Il comporte trois couches, et le caractère unique comme le caractère partagé peuvent exister dans des couches différentes sans entrer en conflit. Le sol sur lequel tout le monde se tient est un monde partagé unique, sélectionné selon des exigences élevées : le lieu de rencontre. Ce que vous y apportez — vos éléments générés par l’IA, le bâtiment que vous avez créé — est profondément unique, et vous le déposez <em>dans</em> le monde partagé afin que votre singularité devienne une découverte pour tous les autres. Et le chemin que vous y suivez — les quêtes que vous recevez, ce vers quoi il vous oriente — est personnalisé sans coût supplémentaire, car un chemin n’est pas un lieu. Le faux dilemme opposait « un monde personnalisé », qui tue le multijoueur, à « un parcours personnalisé dans un monde partagé », qui l’enrichit. C’est le second que nous voulons.</p>
<p>Le rôle de l’IA n’a donc jamais été de remettre à chaque personne un monde privé. Son rôle est de donner à un monde partagé unique une impression d’infini, de vie, et de personnaliser la manière de le parcourir. Son caractère unique vient de tout ce qui s’y accumule au fil du temps, de chaque contribution singulière apportée par un créateur. Chaque serveur Minecraft est unique. Tous les joueurs d’un serveur donné partagent le même monde. Ce n’est pas une contradiction. C’est tout le secret, et la capture d’écran en haut de cet article — l’IA construisant une maison qui reste en place dans un monde où d’autres personnes peuvent entrer — montre ce secret devenu réalité.</p>
<h2>Comment nous lançons Cinevva World</h2>
<p>Voici donc ce que tout cela signifie concrètement pour nous. Pas un plan marketing. Cinq paris, délibérément opposés aux cinq facteurs qui ont tué tous les projets de la première moitié de cet article. Et j’évaluerai chacun d’eux selon la solidité des preuves qui l’étayent réellement, car un plan qu’on ne peut pas évaluer n’est qu’un souhait bien éclairé.</p>
<p><strong>Premier pari, celui sur lequel je miserais l’entreprise : nous lançons un jeu, pas une plateforme.</strong> Ce n’est pas une préférence, mais le constat le plus constant de toute cette étude. Minecraft, Roblox et Fortnite ont tous commencé par proposer quelque chose d’amusant à <em>faire</em>, puis ont développé par-dessus la couche destinée aux créateurs. Media Molecule, le studio de création le plus doué qui soit, a conclu après coup que Dreams aurait d’abord dû être un jeu. La première chose qu’une nouvelle personne fera dans Cinevva World ne sera donc pas de contempler une parcelle vide. Elle entrera dans une boucle de jeu avec un objectif, une raison d’être là un mardi, quand presque personne d’autre n’est en ligne. La plupart des personnes qui viendront joueront sans jamais construire, et c’est voulu, ce n’est pas un échec. C’est la structure réelle de Roblox : des centaines de millions de joueurs reposant sur une mince couche de créateurs. La boucle de croissance vient dans un second temps, et nous devons la mériter. Je suis certain de ce point parce que le cimetière a déjà mené l’expérience.</p>
<p><strong>Deuxième pari, celui qui est véritablement un pari : l’IA nous permet de produire l’offre que tous les autres sont morts en attendant.</strong> Je veux expliquer précisément pourquoi il s’agit d’un pari et non d’une garantie, car aucune plateforme de cette étude n’a jamais été amorcée grâce à l’IA générative. J’ai un mécanisme, pas une preuve. Le mécanisme est le suivant : environ deux tiers des plateformes bifaces meurent du côté de l’offre, et non de la demande. La solution classique consiste à simuler l’offre jusqu’à ce qu’elle devienne réelle, comme DoorDash livrait elle-même les repas, comme l’équipe de Reddit publiait sous de faux pseudonymes et comme les fondateurs de Roblox ont construit eux-mêmes les premiers jeux pendant des années. La génération nous permet de faire la même chose, à un coût et à une vitesse qu’aucun d’eux ne pouvait atteindre. Mais voici ce qui m’oblige à rester lucide, et c’est la pierre tombale de Decentraland : Decentraland regorgeait de contenu généré et attirait environ trente personnes par jour, car être joli ne signifie pas mériter le temps des gens. Je ne parie donc pas sur le fait que l’IA rendra le monde amusant. Elle en est incapable. Je parie sur le fait que l’IA éliminera le goulot d’étranglement lié à la création d’éléments, afin que chaque heure de notre rare travail humain soit consacrée à la seule chose qui fidélise réellement les gens : la boucle de jeu. L’IA construit la colline à bas coût. Nous construisons à la main la raison de la gravir. Et nous limitons l’amorçage synthétique à <a href="https://forkoff.xyz/blog/founder-growth/two-sided-marketplace-cold-start-2026">environ 30 %, en le remplaçant par du véritable contenu de créateurs sous environ 60 jours</a>, car une fausse offre qui ne devient jamais réelle détruit la confiance dès que quelqu’un s’en aperçoit.</p>
<p><strong>Troisième pari : nous publions nos propres critères d’arrêt.</strong> C’est ce qui distingue un plan fondé sur la réalité d’un plan fondé sur l’espoir. Dans absolument tous les cas, l’indicateur qui prédisait la survie était l’<a href="https://newsletter.pmcurve.com/p/retention-masterclass">aplatissement de la courbe de rétention</a>, c’est-à-dire le fait qu’une cohorte continue de revenir au lieu de décroître jusqu’à zéro. C’est donc le test auquel nous nous soumettrons, publiquement. Si la rétention au jour 7 de l’alpha ne se stabilise pas, si les joueurs qui ne construisent jamais ne reviennent pas, si les gens passent à côté du contenu d’amorçage généré par l’IA sans s’y intéresser, alors la thèse est fausse, et nous la modifierons ou nous arrêterons. Nous suivrons les joueurs et les créateurs dans deux entonnoirs distincts, avec deux moments de déclic distincts, car les données montrent clairement qu’il ne s’agit pas de la même personne. Et nous ne prétendrons pas que l’argent accomplit le travail initial. Avec cinquante utilisateurs, ou même mille, les pourboires et les revenus de la marketplace s’arrondissent à zéro : à ce stade, ils ne fidélisent donc personne. La rétention initiale vient de la boucle de jeu ou elle ne vient pas du tout. Dreams est la preuve de ce qui arrive lorsqu’on inverse les priorités.</p>
<p><strong>Quatrième pari : nous commençons délibérément à petite échelle.</strong> Les recherches sur les produits en réseau sont unanimes. On ne lance pas un produit auprès d’un marché, mais auprès d’un <a href="https://www.lennysnewsletter.com/p/atomic-network">« réseau atomique »</a>, le plus petit groupe suffisamment dense pour vivre de lui-même. Facebook a commencé dans une seule université. Le nôtre existe déjà : les créateurs de Cinevva qui ont généré des éléments 3D, de la musique et du son, et qui disposent déjà d’un inventaire à placer. Cinquante d’entre eux dans un même monde, à la même heure, avec un objectif et un territoire à explorer, formeront quelque chose de plus dense et de plus vivant que ne le seront jamais cinquante mille inscriptions dispersées. Les preuves étayent fortement ce pari.</p>
<p><strong>Cinquième pari, le plus fragile, et je le dis ouvertement : nous disons la vérité sur le vide au lieu de le cacher.</strong> Un monde soigné mais silencieux paraît mort, et tous les projets qui ont échoué ont essayé de le dissimuler à l’aide d’effets d’ambiance. Nous tenterons l’approche inverse. C’est la décision la moins éprouvée des cinq, plus proche de l’intuition que d’un constat. Les premières personnes à entrer ne visitent pas un produit inachevé. Elles sont les citoyens fondateurs d’un lieu qui est vide <em>parce qu’elles sont arrivées tôt</em>, et ce qu’elles construiront sera toujours debout lorsque les mille suivantes arriveront.</p>
<p>Voilà le plan, évalué avec honnêteté. Quatre paris fortement étayés par le cimetière, et un véritable pari sur un outil dont les disparus ne disposaient pas, encadré par la discipline précise qui leur a fait défaut. Nous pouvons générer la colline. Nous ne pouvons pas générer le sentiment d’être le premier à la gravir, ni l’inconnu qui vous salue depuis le sommet. Notre travail consiste à rendre ce salut possible, à le prouver sur la courbe de rétention et à cesser de nous raconter des histoires dès que la courbe nous dit non.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 30 : une caméra qui respecte les murs]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-06-04-open-world-browser-part-30-collision-aware-camera</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-06-04-open-world-browser-part-30-collision-aware-camera</guid>
            <pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Vingt-neuf parties ont permis de construire un monde dans lequel on peut marcher, nager et bâtir, le tout observé par une caméra qui traversait sans vergogne les collines et les murs des maisons. La partie 30 corrige ce problème avec une perche sensible aux collisions : un bras à ressort qui ne gère que sa propre longueur, un contrat de collision interchangeable permettant au terrain, aux objets et aux bâtiments d'arrêter la caméra via la même interface, ainsi qu'une sonde de distance signée qui tient compte de ce qu'une carte de hauteurs ne pourrait jamais représenter, les surplombs et les grottes. Le système s'intègre comme un post-traitement au-dessus d'OrbitControls sans réécrire une seule ligne du contrôle orbital auquel nous faisions déjà confiance.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 30 : une caméra qui respecte les murs</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un prototype exploratoire et fournit les liens vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character">partie 29</a> nous a donné un contrôleur unique capable de piloter n'importe quel corps. Le corps se déplace désormais correctement. Il marche, glisse, nage, plane, grimpe dans les grottes et se baisse sous les surplombs. Le problème vient de ce qui l'observe. Pendant vingt-neuf parties, la caméra était un système orbital standard qui suivait le joueur et ne faisait qu'une seule chose astucieuse pour éviter les situations embarrassantes : elle refusait de s'incliner sous l'horizon afin de ne pas pouvoir passer sous un sol plat. Cette limite est révélatrice. Elle existe parce que la caméra ignorait totalement où se trouvait réellement la géométrie du monde ; la seule défense contre le clipping consistait donc à interdire les angles où il risquait le plus de se produire. Approchez-vous d'une colline et la caméra se retrouvait à l'intérieur. Entrez dans l'une des grottes en marching cubes de la <a href="/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes">partie 7</a> et vous contempliez l'intérieur d'un rocher. Construisez une maison avec les outils de création de la <a href="/fr/blog/2026-04-28-open-world-browser-part-16-structure-and-authoring">partie 16</a>, placez-vous dans une pièce, et la caméra flottait de l'autre côté du mur en cadrant le bardage. Cette partie apprend à la caméra à respecter la géométrie autant que le corps le fait déjà.</p>
<h2>La forme du problème et celle de la solution</h2>
<p>Une caméra à la troisième personne a une tâche difficile et une douzaine de tâches faciles. Les plus simples sont le suivi, le lissage et le contrôle orbital, que nous avions déjà. La tâche difficile correspond à ce que la littérature universitaire appelle une contrainte de visibilité. L'étude que tout le monde cite, <em>Camera Control in Computer Graphics</em> de Christie et Olivier, structure tout le domaine autour de cette idée : garder le sujet dans le cadre et visible tout en respectant le monde. À l'exécution, cela se résume à une question d'une simplicité trompeuse, posée à chaque image. Le joueur est le pivot. L'utilisateur a fait pivoter la caméra et réglé le zoom jusqu'à une position souhaitée, à une certaine distance derrière le joueur. Jusqu'où la caméra peut-elle réellement reculer le long de cette ligne avant de pénétrer dans un élément solide ? Répondez honnêtement à cette question et la caméra se replie devant la colline, se rapproche le long de la perche lorsque vous reculez dans un coin et s'arrête au plafond de la grotte au lieu de le traverser.</p>
<p>Le modèle qui résout ce problème est ancien et éprouvé. Unreal l'appelle un bras à ressort, Godot fournit un nœud <code>SpringArm3D</code> et Cinemachine de Unity répartit cette fonction entre un système de suivi à la troisième personne et une extension de désoccultation. L'idée reste toujours la même. La caméra est suspendue à l'extrémité d'une perche ancrée au pivot. Elle reste à la longueur souhaitée lorsque la trajectoire est dégagée, se rétracte vers le pivot lorsqu'un obstacle gêne le passage et revient progressivement à sa position lorsque la voie se libère. Dans <em>Real-Time Cameras</em>, Mark Haigh-Hutchinson, responsable des caméras de Metroid Prime, consacre des chapitres entiers aux modes de défaillance qui transforment une implémentation naïve en machine à donner la nausée aux joueurs. Nous avons repris ce modèle pour créer notre propre système, suffisamment petit pour être lu d'une traite, dans <code>public/world/src/camera-rig.mjs</code>.</p>
<figure style="margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12);border-radius:8px;overflow:hidden;background:#0d1117">
<svg viewBox="0 0 680 290" width="100%" role="img" aria-label="Une perche à bras télescopique se rétractant le long de son propre axe afin que la sphère-sonde repose contre un mur au lieu de le traverser" style="display:block">
  <rect x="382" y="40" width="46" height="222" fill="#39424f" stroke="#5b6675"/>
  <text x="405" y="280" fill="#9aa7b4" font-size="13" text-anchor="middle" font-family="sans-serif">mur</text>
  <line x1="110" y1="212" x2="600" y2="70" stroke="#9aa7b4" stroke-width="2" stroke-dasharray="6 6"/>
  <line x1="110" y1="212" x2="360" y2="140" stroke="#6ea8fe" stroke-width="3"/>
  <circle cx="110" cy="212" r="6" fill="#e6edf3"/>
  <text x="110" y="234" fill="#e6edf3" font-size="13" text-anchor="middle" font-family="sans-serif">pivot (tête du joueur)</text>
  <circle cx="600" cy="70" r="9" fill="none" stroke="#9aa7b4" stroke-width="2"/>
  <text x="600" y="52" fill="#9aa7b4" font-size="13" text-anchor="middle" font-family="sans-serif">position souhaitée (zoom utilisateur)</text>
  <circle cx="360" cy="140" r="22" fill="rgba(110,168,254,0.18)" stroke="#6ea8fe" stroke-width="2"/>
  <circle cx="360" cy="140" r="5" fill="#6ea8fe"/>
  <text x="300" y="104" fill="#6ea8fe" font-size="13" text-anchor="middle" font-family="sans-serif">caméra + rayon de sonde r</text>
  <text x="222" y="192" fill="#6ea8fe" font-size="13" font-family="sans-serif" transform="rotate(-16 222 192)">ℓ (limitée)</text>
</svg>
<figcaption style="padding:0.6rem 1rem;color:#9aa7b4;font-size:0.85rem;border-top:1px solid rgba(255,255,255,0.12);font-family:sans-serif">La perche maintient la caméra au niveau de zoom souhaité par l'utilisateur jusqu'à ce qu'un obstacle s'interpose. Elle se rétracte alors le long du même axe afin que la sphère-sonde repose contre la surface au lieu de laisser le plan proche la traverser.</figcaption>
</figure>
<h2>Une perche qui ne gère que sa longueur</h2>
<p>La règle de conception qui nous a permis de garder ce système compact vient du travail sur le contrôleur de la partie 29 : gérer entièrement une seule chose et refuser de s'occuper du reste. Le système gère la longueur de la perche, rien d'autre. Le lacet, le tangage, l'amortissement de l'orbite, les gestes tactiles et la molette restent tous sous la responsabilité d'OrbitControls, qui s'en charge déjà très bien et que nous n'avons aucune envie de réécrire. Notre système n'est donc pas un contrôleur de caméra. C'est un post-traitement exécuté après les calculs orbitaux qui ne corrige qu'une seule valeur : la distance entre le pivot et la caméra.</p>
<p>Cette décision paraît élégante, mais elle a failli tout casser immédiatement à cause de la logique interne d'OrbitControls. Au début de chaque mise à jour, OrbitControls lit la position actuelle de la caméra et en déduit son rayon orbital. Ce comportement est normalement invisible. Mais dès que notre système rapproche la caméra pour éviter un mur, OrbitControls lit cette position raccourcie à l'image suivante, en conclut que l'utilisateur a dû zoomer et intègre ce raccourcissement au zoom souhaité. Après quelques images, la caméra s'est effondrée sur la tête du joueur et refuse de reprendre sa place. La solution tient en deux appels qui encadrent la mise à jour orbitale, et c'est toute l'intégration. Avant l'exécution d'OrbitControls, <code>beforeControls()</code> replace la caméra à la distance totale non raccourcie de l'image précédente, afin que les calculs orbitaux lisent toujours le véritable zoom de l'utilisateur. Après l'exécution d'OrbitControls, <code>afterControls(dt)</code> lit la nouvelle position souhaitée calculée par l'orbite, résout la collision, amortit la longueur et place la caméra là où elle doit réellement effectuer le rendu. L'intention de l'utilisateur et la correction des collisions n'interfèrent jamais, et le travelling conserve exactement la même réactivité qu'avant l'ajout du système. Le test sans interface graphique que nous avons écrit pour ce système vérifie précisément ce comportement : poussez la perche contre un mur pendant soixante images, retirez le mur, et la longueur revient progressivement au zoom total de dix mètres choisi par l'utilisateur au lieu de rester bloquée à la distance de collision.</p>
<h2>Un seul contrat pour tous les colliders</h2>
<p>Le système ne cherche jamais à savoir de quoi le monde est constitué. Un collider est simplement un objet doté d'une méthode <code>probe</code>. Le système lui transmet un rayon, une distance maximale et le rayon de la sonde de la caméra, puis reçoit une valeur : la distance maximale que la caméra peut parcourir avant d'être bloquée par ce collider. Le système interroge tous les colliders enregistrés et retient l'impact le plus proche. C'est tout le contrat, et il reprend exactement l'approche adoptée par le contrôleur de personnage lorsqu'il a transformé la locomotion en comportements interchangeables. Le terrain s'y branche, les objets s'y branchent, les enveloppes des bâtiments s'y branchent, chacun derrière la même méthode <code>probe</code>, tandis que le système reste inconscient de leur nature. Ajouter un nouveau type d'obstacle revient à ajouter un collider à une liste, pas à modifier la caméra.</p>
<p>Un détail du contrat justifie pleinement sa présence : le rayon de la sonde. Nous ne lançons pas un rayon fin du pivot vers la caméra ; nous projetons une sphère suffisamment grande pour contenir le plan proche de la caméra. Un rayon simple arrête le centre de la caméra au niveau du mur, mais le plan proche possède une largeur. Ses coins seraient donc déjà enfouis dans le mur avant même que le rayon central ne signale un impact. Balayer une petite sphère plutôt que lancer un rayon est la méthode utilisée par toutes les implémentations commercialisées. Unreal expose ce réglage sous le nom de taille de sonde, et c'est ce qui distingue une caméra reposant proprement contre une surface d'une caméra qui permet de voir au travers sur les bords de l'écran.</p>
<p>Nous ne choisissons pas ce rayon au hasard, nous le calculons. Le point du plan proche le plus éloigné de la caméra est l'un de ses coins, et sa distance se déduit directement de la projection. Avec un plan proche $n$ et un champ de vision vertical $\theta$, la demi-hauteur vaut $h = n\tan(\theta/2)$, la demi-largeur vaut $w = h\cdot\text{aspect}$, et le coin se trouve à</p>
<p>$$
r_{\text{near}} = \sqrt{n^2 + w^2 + h^2}
$$</p>
<p>Le rayon de la sonde correspond à cette distance jusqu'au coin, multipliée par une petite marge de sécurité, avec une valeur minimale fixe pour qu'il ne descende jamais sous un seuil raisonnable dans un frustum très étroit. Chaque fois que le champ de vision, le format d'image ou le plan proche change, le rayon est recalculé. Ainsi, le redimensionnement d'une fenêtre ou un zoom modifiant la projection ne peut pas rendre silencieusement la sonde trop petite pour couvrir les coins qu'elle est censée protéger.</p>
<h2>Le collider qui connaît les grottes</h2>
<p>Le collider du terrain est l'élément le plus intéressant, car le terrain de notre moteur n'est pas une carte de hauteurs. Depuis la partie 7, il s'agit d'un champ de distance signé, une fonction qui indique à quelle distance chaque point de l'espace se trouve de la surface solide la plus proche et s'il est à l'intérieur ou à l'extérieur de la roche. Une valeur positive désigne l'air, une valeur négative la roche. Ce simple fait permet à notre caméra de faire quelque chose qui est structurellement impossible avec une carte de hauteurs. Une carte de hauteurs connaît l'altitude du sol pour des coordonnées x et z données. Elle n'a aucune notion de plafond, car il n'existe jamais qu'une seule surface au-dessus de chaque point. Une caméra fondée sur une carte de hauteurs peut donc vous empêcher de pénétrer dans une colline, mais elle ignore totalement qu'un rebord en surplomb ou le plafond d'une grotte se trouve au-dessus du pivot, et elle traverse allègrement les deux. Un champ de distance connaît toutes les surfaces en trois dimensions. La même sonde qui arrête la caméra contre le flanc d'une colline l'arrête donc contre le plafond d'une grotte, sans le moindre cas particulier.</p>
<figure style="margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12);border-radius:8px;overflow:hidden;background:#0d1117">
<svg viewBox="0 0 680 320" width="100%" role="img" aria-label="Sous un surplomb, une carte de hauteurs laisse la perche traverser la roche du plafond, tandis qu'un champ de distance signé l'arrête sous celui-ci" style="display:block">
  <path d="M680,320 L680,40 L250,40 L250,150 L430,150 L430,250 L250,250 L250,320 Z" fill="#39424f" stroke="#5b6675" stroke-width="1.5"/>
  <text x="340" y="205" fill="#9aa7b4" font-size="13" text-anchor="middle" font-family="sans-serif">grotte</text>
  <text x="540" y="100" fill="#5b6675" font-size="13" text-anchor="middle" font-family="sans-serif">roche solide</text>
  <line x1="330" y1="212" x2="230" y2="60" stroke="#9aa7b4" stroke-width="2" stroke-dasharray="6 6"/>
  <line x1="290" y1="150" x2="250" y2="90" stroke="#f85149" stroke-width="4"/>
  <circle cx="330" cy="212" r="6" fill="#e6edf3"/>
  <text x="330" y="234" fill="#e6edf3" font-size="13" text-anchor="middle" font-family="sans-serif">joueur</text>
  <circle cx="230" cy="60" r="10" fill="none" stroke="#f85149" stroke-width="2"/>
  <text x="230" y="42" fill="#f85149" font-size="13" text-anchor="middle" font-family="sans-serif">carte de hauteurs : traverse ✗</text>
  <circle cx="305" cy="170" r="9" fill="rgba(86,211,100,0.2)" stroke="#56d364" stroke-width="2"/>
  <text x="420" y="172" fill="#56d364" font-size="13" text-anchor="middle" font-family="sans-serif">SDF : s'arrête au plafond ✓</text>
</svg>
<figcaption style="padding:0.6rem 1rem;color:#9aa7b4;font-size:0.85rem;border-top:1px solid rgba(255,255,255,0.12);font-family:sans-serif">Une carte de hauteurs ne stocke qu'une seule surface par colonne. Elle ne voit donc jamais la dalle de roche située au-dessus du joueur et laisse la perche traverser le plafond. Le champ de distance est négatif à l'intérieur de cette dalle ; la sonde entre donc en contact avec elle et maintient la caméra juste sous la voûte de la grotte.</figcaption>
</figure>
<p>Faire progresser la sonde dans le champ est une technique qui possède un nom et un article de référence. Le <em>Sphere Tracing</em> de John Hart, publié en 1996, est la méthode standard pour faire avancer un rayon dans un champ de distance. L'astuce est que le champ ne se contente pas d'indiquer si vous avez touché quelque chose : il donne aussi la distance que vous pouvez parcourir en toute sécurité sans rencontrer d'obstacle. Au lieu d'avancer à minuscules pas fixes, vous échantillonnez donc le champ, progressez de la marge qu'il indique et recommencez, en faisant de grandes enjambées dans l'air libre puis des pas courts et prudents à l'approche d'une surface.
Il y a toutefois un piège imposé par notre terrain. Un véritable champ de distance indique la distance euclidienne réelle jusqu’à la surface la plus proche, et avancer de toute cette marge est toujours sans risque. Mais là où le terrain est encore une carte de hauteur plutôt qu’un volume de voxels sculptés, le champ que nous pouvons échantillonner à peu de frais n’est pas une véritable distance : c’est un dégagement vertical, l’écart mesuré tout droit vers le sol. Sur une pente, cette valeur surestime la distance que la caméra peut réellement parcourir, car le rocher le plus proche se trouve sur le côté, et non directement en dessous. La distance réelle est plus petite d’un facteur qui augmente avec le gradient, $\sqrt{1 + \lVert\nabla h\rVert^2}$ ; un pas dimensionné d’après la marge indiquée dépasse donc la limite et peut sauter complètement par-dessus une crête. La solution consiste à appliquer une sous-relaxation, en n’avançant que d’une fraction de la marge indiquée plutôt que de sa totalité. Cela reste sûr jusqu’à une pente d’environ soixante degrés et ne coûte, dans les régions de voxels à distance réelle, que quelques échantillons supplémentaires. Nous y ajoutons une taille de pas minimale, afin qu’une paroi de grotte épaisse d’une ou deux cellules ne soit jamais entièrement franchie, ainsi qu’un plafond plus strict pour garder la marche peu coûteuse. Lorsque la sphère finit par toucher la surface, une courte bissection affine le point de contact, puis la caméra recule de son propre rayon et s’y immobilise.</p>
<p>Formellement, la perche est un rayon $\mathbf{r}(t) = \mathbf{p} + t,\mathbf{d}$ partant du pivot $\mathbf{p}$ dans la direction unitaire $\mathbf{d}$, et la marche avance d’une fraction sous-relaxée de la marge avant contact indiquée par le champ, bornée aux deux extrémités :</p>
<p>$$
t_{n+1} = t_n + \operatorname{clamp}!\big(\lambda,(\Phi(\mathbf{r}(t_n)) - r),; s_{\min},; s_{\max}\big)
$$</p>
<p>Ici, $\Phi$ est la distance signée, positive dans l’air et négative dans la roche, $r$ est le rayon de la sonde, $\lambda \in (0, 1]$ est le facteur de sous-relaxation qui empêche le champ de la carte de hauteur, lequel surestime les distances, de franchir une pente d’un bond, et $s_{\min}, s_{\max}$ sont les bornes du pas qui empêchent d’ignorer une paroi mince et évitent que la marche ne s’éternise. Le contact correspond au premier $t$ pour lequel $\Phi(\mathbf{r}(t)) \le r$, ce qui signifie que la surface de la sphère a atteint la roche ; la caméra se maintient alors à cette longueur d’arc diminuée de son rayon.</p>
<p>Le collisionneur fournit une méthode supplémentaire en guise de filet de sécurité : la dépénétration. La collision devrait d’abord empêcher la caméra d’entrer dans un solide, mais quelques situations peuvent lui échapper : un pivot à cheval sur une paroi assez mince pour que la perche commence à l’intérieur, une grotte tout juste creusée avec les outils de terrain alors que la caméra se trouvait dans la roche qui vient de disparaître, ou encore un élément de construction placé autour de la caméra. Dans ces cas, le collisionneur vérifie si la caméra termine l’image à l’intérieur d’un solide, là où $\Phi(\mathbf{c}) &lt; r$. Si c’est le cas, il lit le gradient du champ, qui pointe directement vers l’air libre puisque $\Phi$ augmente lorsque l’on quitte la roche, puis déplace la caméra vers l’extérieur dans cette direction :</p>
<p>$$
\mathbf{c} \leftarrow \mathbf{c} + \big(r - \Phi(\mathbf{c})\big),\frac{\nabla \Phi(\mathbf{c})}{\lVert \nabla \Phi(\mathbf{c}) \rVert}
$$</p>
<p>Une poignée de ces itérations converge vers l’isosurface de valeur $r`. C’est ce qui permet à un outil de sculpture de retirer le sol sous la caméra et à la vue de se rétablir dès l’image suivante au lieu de devenir noire.</p>
<h2>Se rapprocher brusquement, s’éloigner en douceur et ne pas tressaillir devant les poteaux de clôture</h2>
<p>Une perche qui saute simplement à la distance de collision à chaque image est pire que l’absence de perche, car le monde regorge d’objets minces derrière lesquels la caméra passe pendant une seule image : un poteau de clôture, un lampadaire, un tronc d’arbre. Une caméra qui se précipite pour éviter chacun d’eux puis repart tout aussi brusquement donne la nausée. Le livre de Haigh-Hutchinson et la conférence très appréciée d’Itay Keren sur les mouvements de caméra aboutissent tous deux à la même intuition : la caméra doit réagir aux menaces et au danger plus vite qu’elle ne s’en détend. L’amortissement est donc délibérément asymétrique. Lorsqu’un obstacle occultant apparaît et que la perche doit se raccourcir, elle se replie presque instantanément, car une seule image d’interpénétration est disgracieuse et les joueurs tolèrent un rapprochement rapide. Lorsque l’obstacle disparaît et que la perche veut s’allonger, elle se déploie lentement, et seulement après l’écoulement d’un bref délai pendant lequel le passage est resté continuellement dégagé. Ce délai constitue l’hystérésis qui élimine les tressaillements. Faites pivoter rapidement la caméra devant un poteau mince : le poteau ne reste jamais hors du chemin assez longtemps pour déclencher l’extension lente, si bien que la caméra le dépasse en douceur comme s’il n’était pas là, exactement comme l’œil le souhaite. Cinemachine expose la même idée sous la forme de valeurs distinctes d’amortissement à l’entrée et à la sortie d’une collision. C’est cette asymétrie qui donne l’impression d’un opérateur de caméra plutôt que d’un ressort.</p>
<p>Dans le code, cela tient en une ligne de lissage exponentiel, avec un taux choisi selon le signe de la variation. Si $\ell$ est la longueur actuelle de la perche, $a$ la longueur autorisée par la collision pour cette image et $\Delta t$ la durée de l’image, alors</p>
<p>$$
\ell \leftarrow \ell + (a - \ell)\big(1 - e^{-k,\Delta t}\big), \qquad
k = \begin{cases}
k_{\text{in}}  &amp; a \le \ell \[2pt]
k_{\text{out}} &amp; a &gt; \ell
\end{cases}, \quad k_{\text{in}} \gg k_{\text{out}}
$$</p>
<p>et la branche d’éloignement progressif ne s’exécute qu’une fois le passage resté dégagé pendant le délai $\tau$. La forme $1 - e^{-k,\Delta t}$ n’est pas seulement élégante. Elle fixe la constante de temps de la réponse à $1/k$ indépendamment de la fréquence d’affichage, de sorte que la caméra se comporte de façon identique à 30 et à 144 images par seconde. Avec le mélange constant naïf $\ell \leftarrow \ell + \alpha(a - \ell)$, elle réagirait trop vite sur une machine rapide et de manière pâteuse sur une machine lente.</p>
<p>Le dernier comportement concerne les intérieurs exigus qui ont motivé toute cette partie. Lorsque la perche se replie suffisamment pour placer la caméra juste au-dessus du joueur, nous masquons son propre avatar et laissons la vue se rapprocher de la première personne. C’est ce que fait Breath of the Wild dans un sanctuaire exigu et la solution de repli de la plupart des jeux à la troisième personne dans un coin, car l’autre possibilité — une caméra coincée contre un mur et fixant l’arrière d’une tête — est inutilisable. Le rig expose un unique indicateur à cet effet, que la boucle du monde lit pour modifier la visibilité de l’avatar local. Avec une perche de moins d’un mètre, vous êtes de fait en vue à la première personne, les murs sont respectés et, dès que vous reculez dans une pièce suffisamment spacieuse, l’avatar réapparaît progressivement tandis que la perche se déploie.</p>
<h2>Ce qui viendra se brancher ensuite</h2>
<p>Le collisionneur de terrain est disponible dès aujourd’hui et représente la moitié la plus difficile, car le terrain est omniprésent et un champ de distance est délicat à sonder. Les objets et les enveloppes de bâtiments issus des prototypes de création constituent la moitié facile, et le contrat est déjà prêt à les accueillir. Un second collisionneur, fondé sur le lancer de rayons, lance un rayon du pivot vers la caméra contre une liste de maillages et signale l’impact le plus proche de la même façon que le collisionneur de terrain. La version peu coûteuse ne lance qu’un seul rayon, ce qui suffit jusqu’à ce que le nombre d’objets augmente. L’amélioration consiste à remplacer le rayon par une sphère balayée à l’aide de three-mesh-bvh, la bibliothèque de Garrett Johnson qui enveloppe un maillage dans une hiérarchie de volumes englobants afin que les requêtes spatiales s’exécutent en temps logarithmique plutôt que par force brute. Dans les deux cas, le rig ne change pas. Il interroge une liste plus longue de collisionneurs et retient l’impact le plus proche, ce qui est précisément la raison pour laquelle nous avons conçu d’abord le contrat, puis les collisionneurs.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Une perche qui ne gère qu’une seule valeur.</strong> Le rig de caméra est un post-traitement appliqué à OrbitControls, et non son remplaçant. Il gère la longueur de la perche et laisse le lacet, le tangage, le zoom et les gestes au contrôleur orbital auquel nous faisions déjà confiance. L’intégration repose sur deux appels d’encadrement : <code>beforeControls()</code> rétablit la distance complète de l’image précédente afin que les calculs orbitaux lisent le véritable zoom choisi par l’utilisateur au lieu de confondre un raccourcissement dû à une collision avec un rapprochement de caméra, puis <code>afterControls(dt)</code> résout la collision et écrit la position affichée. Sans cette paire, la caméra se rabat sur le joueur en quelques images.</p>
<p><strong>Un contrat de collisionneur interchangeable.</strong> Un collisionneur est n’importe quel objet doté d’une méthode <code>probe</code> qui répond à la question : « quelle distance la caméra peut-elle parcourir depuis le pivot le long de ce rayon avant que vous ne la bloquiez ? » Le rig interroge chaque collisionneur et retient l’impact le plus proche, sans savoir si l’obstacle est le terrain, un objet ou un mur. C’est la même discipline consistant à ne gérer qu’une seule responsabilité que celle employée par le <a href="/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character">contrôleur de personnage interchangeable</a> pour la locomotion. La sonde est une sphère balayée dimensionnée pour contenir le plan proche, et non un rayon fin, afin que les coins du tronc de vision n’interpénètrent jamais une surface que le rayon central aurait manquée. Son rayon est dérivé de la projection : la distance jusqu’à un coin du plan proche, $\sqrt{n^2 + w^2 + h^2}$, multipliée par une marge de sécurité. Il est recalculé chaque fois que le champ de vision, le rapport d’aspect ou le plan proche change.</p>
<p><strong>Une sonde de terrain à distance signée qui respecte les surplombs et les grottes.</strong> Puisque le terrain est un <a href="/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes">champ de distance signée</a> plutôt qu’une carte de hauteur, la même sonde qui arrête la caméra contre un versant l’arrête contre le plafond d’une grotte, cas qu’un lancer de rayon sur une carte de hauteur est structurellement incapable de détecter. La marche emploie le traçage de sphères de Hart : elle avance d’une fraction sous-relaxée de la marge indiquée par le champ, avec une borne à chaque extrémité, car les régions fondées sur une carte de hauteur indiquent un dégagement vertical plutôt qu’une véritable distance et surestiment le déplacement possible de la caméra sur une pente ; un pas complet franchirait donc une crête. Une passe de dépénétration guidée par le gradient sert de filet de sécurité et rétablit la vue lorsque le sol est sculpté sous la caméra.</p>
<p><strong>Un amortissement asymétrique avec délai d’attente.</strong> La perche se replie rapidement lorsqu’un obstacle occultant apparaît, puis se redéploie lentement lorsqu’il disparaît, et seulement après une courte période de dégagement continu. Ainsi, un mouvement rapide devant un poteau de clôture ne fait jamais bondir la caméra. Sous un seuil de repli, le rig active un indicateur de vue quasi subjective et la boucle du monde masque l’avatar local, solution de repli habituelle dans les intérieurs exigus plutôt que de laisser la caméra enfouie dans un mur.</p>
<h2>Références</h2>
<p>La formulation du contrôle de caméra comme une contrainte de visibilité vient de Marc Christie et Patrick Olivier, <a href="https://people.irisa.fr/Marc.Christie/Publications/2008/CON08.html"><em>Camera Control in Computer Graphics</em></a> (Computer Graphics Forum, 2008). La marche dans le champ de distance est celle de John C. Hart, <a href="https://graphics.stanford.edu/courses/cs348b-20-spring-content/uploads/hart.pdf"><em>Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces</em></a> (The Visual Computer, 1996). Le modèle de perche à ressort et sa collision par sphère-sonde sont documentés dans le <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/using-spring-arm-components-in-unreal-engine">Spring Arm Component</a> d’Epic ainsi que dans le <a href="https://docs.unity3d.com/Packages/com.unity.cinemachine@3.1/manual/CinemachineDeoccluder.html">Cinemachine Deoccluder</a> et le <a href="https://docs.unity3d.com/Packages/com.unity.cinemachine@3.1/manual/CinemachineThirdPersonFollow.html">Third Person Follow</a> d’Unity. L’intuition concernant le mouvement et l’amortissement vient de Mark Haigh-Hutchinson, <em>Real-Time Cameras</em> (Morgan Kaufmann, 2009), et d’Itay Keren, <a href="https://www.gamedeveloper.com/design/scroll-back-the-theory-and-practice-of-cameras-in-side-scrollers"><em>Scroll Back: The Theory and Practice of Cameras in Side-Scrollers</em></a> (GDC 2015). La voie d’amélioration du collisionneur de maillages repose sur <a href="https://github.com/gkjohnson/three-mesh-bvh">three-mesh-bvh</a> de Garrett Johnson.</p>
<hr>
<p>Partie 30 sur 30.
Précédent : <a href="/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character">Partie 29 — Un contrôleur pour n’importe quel corps</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ne rendez pas l'herbe derrière la colline : élimination des objets occultés tenant compte du terrain]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-19-terrain-occlusion-culling</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-19-terrain-occlusion-culling</guid>
            <pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Huit techniques d'élimination des objets occultés évaluées pour un monde ouvert dans le navigateur, celle qui l'emporte parce que notre terrain est une carte de hauteurs, et un prototype interactif qui vous permet de les comparer dans votre propre scène.]]></description>
            <content:encoded><![CDATA[<h1>Ne rendez pas l'herbe derrière la colline : élimination des objets occultés tenant compte du terrain</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>La <a href="/fr/blog/2026-05-14-open-world-browser-part-28-grass-and-occlusion">partie 28</a> présentait le prototype 57 et comparait ses quatre méthodes d'élimination en quelques paragraphes. Voici la version longue : pourquoi ces quatre-là, quelles étaient les quatre autres et le raisonnement qui sous-tend celle que nous allons mettre en production.</p>
<p>Il y a une colline entre la caméra et une prairie. Inutile de rendre la prairie. Tous les moteurs AAA modernes le savent. La plupart des moteurs 3D pour navigateur ne le savent pas, y compris le nôtre jusqu'à la semaine dernière.</p>
<p>Cet article est le fruit d'une recherche menée sérieusement, ancrée dans notre véritable base de code, de la création d'un prototype fonctionnel et d'une évaluation des techniques selon leur utilité <em>pour nous en particulier</em>, sachant que nous déployons aujourd'hui sur WebGL et demain sur WebGPU.</p>
<p>Vous pouvez essayer le prototype en direct ci-dessous avant de poursuivre votre lecture. <kbd>T</kbd>/<kbd>Y</kbd>/<kbd>U</kbd>/<kbd>I</kbd> permettent de basculer entre les méthodes d'élimination, <kbd>C</kbd> fait défiler les préréglages de caméra et <kbd>B</kbd> affiche en fil de fer rouge les chunks masqués afin que vous puissiez voir ce que le test élimine. Le HUD indique combien d'instances sont rejetées à chaque étape.</p>
<div style="position:relative;padding-bottom:62%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.08)">
<iframe src="/fr/spikes/57-terrain-occlusion/" title="Prototype 57 : élimination des objets occultés par le terrain" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#1a1f2a" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/57-terrain-occlusion/" target="_blank">Ouvrir le prototype 57 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=57-terrain-occlusion">Voir le code source</a></p>
<h2>Ce que fait actuellement notre moteur — et ce qu'il ne fait pas</h2>
<p>Notre gestionnaire de streaming de chunks charge autour du joueur un anneau de chunks de 64 m, répartis sur trois niveaux de détail. Chaque chunk contient des arbres instanciés dispersés sur la carte de hauteurs. La logique d'élimination, qui se trouve dans <code>Chunk.updateObjectVisibility</code>, repose sur un simple test de distance : les arbres situés à moins de 60 m sont rendus avec leur maillage complet, ceux situés entre 60 et 140 m sous forme de panneaux d'affichage, et ceux au-delà ne sont pas rendus.</p>
<p>Cette méthode passe à côté de deux énormes catégories de travail inutile :</p>
<ol>
<li>Tout ce qui se trouve dans le disque de 60 m, <em>mais derrière la caméra</em>. Nous retrions le tampon d'instances chaque fois que le joueur se déplace de plus de 4 m, mais nous ne testons pas le tronc de vue. En moyenne, la moitié du disque est donc envoyée au GPU pour être simplement découpée après la transformation des sommets.</li>
<li>Tout ce qui se trouve dans le disque de 60 m, <em>mais derrière une colline</em>. Notre terrain présente 80 m de dénivelé et de nombreuses vallées. Lorsque la caméra est dans une vallée, la plus grande partie de la végétation située dans le rayon d'élimination est géométriquement masquée par la crête la plus proche. Nous la rendons quand même.</li>
</ol>
<p>Cet article se concentre sur la deuxième catégorie. C'est aussi celle que les moteurs AAA corrigent grâce à tout un arsenal d'astuces qui, à première vue, ne se transpose pas facilement au navigateur.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0">
<iframe src="https://www.youtube.com/embed/wavnKZNSYqU" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen></iframe>
</div>
<p><em>Gilbert Sanders, de Guerrilla, explique comment Horizon Zero Dawn rend la végétation de son monde ouvert. Les vingt dernières minutes sont consacrées à la chaîne d'élimination, et c'est une véritable leçon magistrale sur les raisons pour lesquelles « rendre moins » est préférable à « rendre plus vite ».</em></p>
<h2>Huit techniques, évaluées pour notre cas</h2>
<p>J'ai étudié la chaîne moderne d'élimination des objets occultés et évalué chaque élément selon son intérêt pour un monde ouvert procédural, fondé sur une carte de hauteurs et exécuté dans un navigateur. Deux axes : la valeur — la quantité de travail inutile éliminée dans notre scène — et le coût — l'effort d'ingénierie, ainsi que la part de notre pile technique qu'il faut modifier. Le raisonnement complet se trouve dans le dossier de recherche à l'origine de cet article ; en voici la version courte.</p>
<p><strong>1. Élimination par tronc de vue pour chaque instance. Valeur élevée, faible coût.</strong> À l'heure actuelle, nous ne testons pas du tout le tronc de vue pour notre végétation instanciée. L'ajout de ce test réduit approximativement de moitié le travail dans n'importe quelle vue, demande une trentaine de lignes de code et peut être mis en production dès aujourd'hui avec WebGL. C'est de loin le gain facile le plus important, et nous aurions dû le faire il y a plusieurs mois.</p>
<p><strong>2. Lancer de rayon sur l'horizon de la carte de hauteurs. Valeur élevée, coût faible à moyen.</strong> On fait progresser un rayon de la caméra vers chaque instance candidate en échantillonnant la hauteur du terrain le long du trajet. Si le terrain dépasse le rayon à un moment quelconque, l'instance est occultée. Cette approche fonctionne précisément parce que notre monde est une carte de hauteurs, ce qui réduit la visibilité à un problème unidimensionnel le long de la direction horizontale. La version dense a une complexité O(nombre d'étapes) par instance et par image. La version accélérée — l'élément suivant — la ramène à O(log(nombre d'étapes)).</p>
<p><strong>3. Accélération par pyramide de hauteurs maximales. Valeur élevée, coût moyen.</strong> Une mipmap 2D de la carte de hauteurs dans laquelle chaque texel stocke la hauteur maximale du terrain à l'intérieur de son empreinte. Elle permet au lancer de rayon sur l'horizon d'avancer par grands pas lorsque les environs sont plats et de n'affiner le test qu'au niveau des collines. C'est cette structure qui rend la technique nº 2 suffisamment peu coûteuse pour la production.</p>
<p><strong>4. Élimination hiérarchique par tampon de profondeur (Hi-Z/HZB). Valeur élevée, coût élevé.</strong> On construit une mipmap du tampon de profondeur, on projette les limites de chaque instance dans l'espace écran, puis on les teste avec le niveau de mipmap approprié. C'est la méthode GPU moderne de référence, utilisée par Nanite dans Unreal, la géométrie virtuelle de Bevy et le portage WebGPU de VTK. Elle fonctionne pour tout, pas seulement pour le terrain, mais nécessite WebGPU, des appels de rendu indirects et une passe de calcul. Elle devient rentable lorsque nous rendons des millions de brins d'herbe, et non quelques milliers d'arbres.</p>
<p><strong>5. Hi-Z en deux passes, à la manière de Nanite. Gain marginal par rapport à la nº 4, coût élevé.</strong> On effectue un nouveau rendu de la profondeur de l'image actuelle après la première passe afin d'éviter l'artefact de désoccultation sur une image. Cela ne vaut le coup qu'une fois que notre pipeline piloté par le GPU est suffisamment avancé pour que ce coût ne soit qu'incrémental.</p>
<p><strong>6. Rastériseur logiciel d'occultation (Frostbite/Intel MOC). Valeur moyenne, coût élevé.</strong> On rastérise sur le CPU un tampon de profondeur basse résolution contenant les grands éléments occultants. Aucune latence de relecture. Les implémentations de référence sont en C++ avec AVX/SSE ; leur portage vers WASM constitue un véritable projet, alors que notre lancer de rayon sur la carte de hauteurs apporte l'essentiel des mêmes gains pour une fraction du travail.</p>
<p><strong>7. PVS précalculé. Faible valeur, coût élevé.</strong> Une solution élégante pour les cartes statiques de l'époque de Quake. Notre terrain est procédural et infini : tout prétraitement doit donc s'effectuer au moment du streaming des chunks, ce qui coûte à peu près autant que de calculer directement la visibilité à l'exécution. À écarter.</p>
<p><strong>8. Élimination par horizon à la manière de Cesium. Aucun intérêt pour nous, coût moyen.</strong> Cette technique est conçue pour les ellipsoïdes planétaires. Notre monde est relativement plat et délimité ; les calculs ne s'appliquent pas et ne produiraient soit aucun effet, soit des éliminations erronées. À écarter.</p>
<p>Conclusion : implémenter maintenant la nº 1 et les nº 2+3 sur le CPU, avec WebGL. Prévoir la nº 4 pour la migration vers WebGPU. Écarter le reste.</p>
<h2>Pourquoi le lancer de rayon sur une carte de hauteurs l'emporte pour les terrains dans le navigateur</h2>
<p>Le conseil habituel de toute présentation moderne sur le rendu est : « Construisez un tampon Hi-Z. » La <a href="https://www.youtube.com/watch?v=eviSykqSUUw">présentation approfondie de Brian Karis sur Nanite à la SIGGRAPH 2021</a> est la référence en la matière, et vous devriez la regarder au moins une fois.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0">
<iframe src="https://www.youtube.com/embed/eviSykqSUUw" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen></iframe>
</div>
<p>C'est la bonne réponse pour un moteur qui fait déjà tout passer par des appels de rendu indirects pilotés par le GPU. La plupart des moteurs pour navigateur, y compris le nôtre, ne fonctionnent pas ainsi. Nous avons des tampons d'instances gérés côté CPU, des appels de rendu WebGL et aucune étape de calcul. Greffer Hi-Z sur cette pile implique de migrer simultanément vers WebGPU, de réécrire le pipeline de végétation pour utiliser des appels indirects et d'ajouter une passe de construction de la pyramide de profondeur. Cela représente trois mois de travail avant même d'obtenir la première image démontrant la validité de l'idée.</p>
<p>Le lancer de rayon sur la carte de hauteurs fonctionne sur le CPU, avec WebGL et avec les données dont nous disposons déjà. Il exploite une caractéristique de notre monde dont les moteurs AAA ne peuvent pas tirer parti : <strong>nos éléments occultants sont décrits par une fonction de hauteur unidimensionnelle</strong>. Échantillonner cette fonction le long d'un rayon ne demande que deux lectures de tableau et une multiplication. Un tampon Hi-Z devrait redécouvrir le même fait pixel par pixel.</p>
<p>La technique a été publiée pour la première fois sous le titre « Horizon Occlusion Culling for Hierarchical Terrains » lors d'IEEE Visualization 2002 (<a href="https://saksagan.ceng.metu.edu.tr/courses/ceng591/RAPORLAR/HacerYalim.pdf">PDF</a>). Elle est restée dans la boîte à outils pendant deux décennies parce qu'elle présente le bon profil : peu coûteuse lorsque le terrain est plat, coûteuse uniquement là où se trouvent réellement des collines et trivialement parallélisable.</p>
<h2>Représentation de la pyramide de hauteurs maximales</h2>
<p>Le lancer de rayon naïf échantillonne <code>terrainHeight</code> en environ 24 points le long de chaque rayon et s'interrompt dès que le terrain le traverse. Cela convient pour des milliers d'arbres. La méthode s'effondre avec des centaines de milliers de brins d'herbe.</p>
<p>La solution consiste à créer une mipmap de la carte de hauteurs dans laquelle chaque texel stocke la hauteur <strong>maximale</strong> à l'intérieur de son empreinte :</p>
<pre><code>level 0 (256×256, 2.25m per texel):  max h of 4×4 jittered samples
level 1 (128×128, 4.5m  per texel):  max(0,0), max(1,0), max(0,1), max(1,1)
level 2 ( 64×64,  9.0m per texel):  same reduction one level up
...
level 8 (   1×1,  576m per texel):  global max
</code></pre>
<p>Lorsque le segment du rayon est long et plat, on échantillonne un niveau grossier : une seule lecture nous indique qu'« aucun point du terrain dans ce carré de 9 m ne dépasse jamais 12 m d'altitude, et le rayon se trouve ici à 30 m, donc on peut continuer ». Ce n'est que lorsqu'un texel grossier indique que « le terrain <em>pourrait</em> dépasser le rayon » que l'on descend d'un niveau pour affiner le test. L'ensemble de la structure n'occupe que quelques centaines de Ko et se construit en quelques dizaines de millisecondes.</p>
<p>Sous forme de schéma :</p>
<pre><code>                                            ray from eye
        eye 1.7m                          o─────────────────────►
              o─────────────────────────·─·─·─·─·─·──────────────
              │                          \                       │
              │   level 3 (huge step)     \   level 0 (refine)   │
              │   &quot;no terrain above 8m&quot;    \   &quot;9m hill here!&quot;   │
              │                              \                   │
        ──────┴────────────/▔▔▔\─────────────/▔▔▔▔▔\─────────────
                                 hill A (8m)   hill B (12m)
                                                ↑
                                                blocks here
</code></pre>
<p>Pour le segment du rayon qui passe à proximité de la colline A, la lecture au niveau 3 — « la hauteur maximale dans cette zone de 18 m de large est de 8 m » — nous indique déjà que le rayon, à une altitude de 1,7 m plus quelques mètres d'élévation, ne rencontre aucun obstacle. Une seule requête nous permet de sauter 36 m de progression. Au-dessus de la colline B, le niveau 3 indique « le maximum est ici de 12 m » ; nous descendons alors d'un niveau, et le niveau 0 confirme « oui, 12 m à ce texel précis ». Nous rejetons donc l'instance.</p>
<p>La construction de la pyramide se trouve dans <code>height-pyramid.mjs</code>, et les méthodes d'élimination qui l'utilisent dans <code>cull.mjs</code>. Les deux fichiers sont consultables dans le <a href="/fr/blog/spike-source?spike=57-terrain-occlusion">navigateur du code source du prototype</a>.</p>
<h2>Ce que montre réellement le prototype</h2>
<p>Ouvrez le prototype 57 ci-dessus et essayez les quatre méthodes :</p>
<ul>
<li><strong>T0</strong> correspond à ce que fait actuellement la version de production. Uniquement un test de distance. Depuis C1 — le fond de la vallée —, le HUD indique qu'environ 12 000 brins d'herbe sont visibles.</li>
<li><strong>T1</strong> ajoute l'élimination par tronc de vue pour chaque instance. Le nombre d'éléments visibles est approximativement divisé par deux, car tout ce qui se trouve derrière la caméra ou sur les côtés est éliminé avant d'atteindre le GPU.</li>
<li><strong>T2</strong> ajoute le lancer de rayon exhaustif sur la carte de hauteurs. Dans C1, le nombre diminue encore de 60 à 80 %, car la plus grande partie du champ se trouve de l'autre côté de la crête la plus proche. La colonne Cull-ms augmente, car nous échantillonnons <code>terrainHeight</code> environ 24 fois par instance.</li>
<li><strong>T3</strong> remplace la méthode exhaustive par la pyramide de hauteurs maximales. Cull-ms revient à une valeur proche de T1 tout en conservant le gain de visibilité. C'est la méthode à mettre réellement en production.</li>
</ul>
<p>Ce comportement correspond à ce que constatent les moteurs de production. La série en deux parties d'Acerola sur le rendu de l'herbe — <a href="https://www.youtube.com/watch?v=Y0Ko0kvwfgA">Comment les jeux parviennent-ils à rendre autant d'herbe ?</a> et <a href="https://www.youtube.com/watch?v=PNvlqsXdQic">Ce que j'ai fait pour optimiser l'herbe de mon jeu</a> — est l'explication la plus accessible sur YouTube des raisons pour lesquelles il faut consacrer ses efforts d'ingénierie à l'étape d'élimination plutôt qu'à celle de l'ombrage.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0">
<iframe src="https://www.youtube.com/embed/Y0Ko0kvwfgA" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen></iframe>
</div>
<h2>La caméra C3 et le cas d'échec « sommet de colline »</h2>
<p>Le troisième préréglage de caméra du prototype place la caméra au sommet d'une colline, tournée vers l'ensemble de l'aire de jeu. Dans ce cas, l'élimination par horizon ne fait presque rien, car aucun terrain ne se trouve entre la caméra et la majeure partie du monde. Le HUD montre que T2/T3 ne réduisent le nombre d'éléments visibles que de 5 à 10 % par rapport à T1.</p>
<p>C'est une fonctionnalité, pas un bug. La technique cesse de fonctionner exactement là où elle le devrait : lorsqu'il n'y a rien pour masquer la vue. L'élimination par tronc de vue continue d'effectuer un vrai travail, l'élimination par distance continue de limiter le budget et le test d'horizon revient proprement à une opération sans effet. Si vous implémentez cette technique, vous devez vérifier que ce cas sans effet reste lui aussi peu coûteux. C'est pourquoi la pyramide de hauteurs maximales est importante même lorsqu'aucun rayon n'est rejeté : le niveau le plus grossier suffit souvent à confirmer que « rien n'est masqué ».</p>
<h2>Ce que nous allons mettre en production</h2>
<p>Il reste trois choses à faire, dans cet ordre.</p>
<p>Premièrement, intégrer T1 et T3 à la version de production de <code>Chunk.updateObjectVisibility</code>. La pyramide doit être placée un niveau plus haut, dans le gestionnaire de chunks, car elle couvre plusieurs chunks. L'élimination reste dans <code>Chunk</code> afin que le regroupement existant par chunk continue de fonctionner. Effort estimé : une journée, tests compris.
Deuxièmement, faire de même pour l’herbe dès que nous en aurons. Le prototype actuel répartit 12 000 brins sur une zone de jeu de 576 m ; la densité en production devrait être environ dix fois supérieure. Les chemins de culling sur CPU traitent 12 000 brins en moins d’une milliseconde, et c’est l’accélération par pyramide qui permet de maintenir cette performance à 120 000.</p>
<p>Troisièmement, lors de la migration vers <code>THREE.WebGPURenderer</code>, porter cette même boucle dans un shader de calcul. Les métadonnées deviennent un tampon de stockage. Le culling écrit les arguments de <code>drawIndirect</code>. La pyramide est téléversée sous forme de texture 2D, avec la réduction par maximum déjà intégrée. La structure du code reste presque identique, et c’est bien là tout l’intérêt : nous ne faisons pas reposer la migration sur un nouvel algorithme, nous transférons un algorithme déjà éprouvé vers une voie plus rapide.</p>
<p>Guerrilla a présenté la version GPU de cette approche pour le système de placement procédural d’Horizon Zero Dawn ; le pipeline de rendu importe moins que les structures de données, et les leurs ont la même forme :</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0">
<iframe src="https://www.youtube.com/embed/ToCozpl1sYY" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen></iframe>
</div>
<p>Le Hi-Z trouvera toujours sa place lorsque nous intégrerons des bâtiments et des éléments de décor denses qui produisent des occultations dans des directions que la carte de hauteur ne peut pas décrire. Mais la pyramide de carte de hauteur restera dans le pipeline, car elle est nettement moins coûteuse que le Hi-Z pour les instances proches du sol, et l’herbe qui chevauche la silhouette d’une crête correspond précisément au cas que le Hi-Z gère le moins bien.</p>
<h2>Références</h2>
<p>La justification complète, classée par priorité, ainsi que les décisions architecturales figurent ci-dessus ; voici les sources de référence pour chaque technique, approximativement dans l’ordre où elles apparaissent dans la pile.</p>
<ul>
<li>Yalim &amp; Akman, <a href="https://saksagan.ceng.metu.edu.tr/courses/ceng591/RAPORLAR/HacerYalim.pdf">Culling d’occultation par horizon pour les terrains hiérarchiques</a> (IEEE Visualization 2002, l’article fondateur sur le culling par horizon du terrain).</li>
<li>Turitzin, <a href="https://miketuritzin.com/post/hierarchical-depth-buffers/">Tampons de profondeur hiérarchiques</a> (l’explication la plus claire de la construction et de l’interrogation des chaînes de mipmaps Hi-Z).</li>
<li>VKGuide, <a href="https://vkguide.dev/docs/gpudriven/compute_culling/">Culling basé sur le calcul</a> (référence pour le pipeline de culling piloté par GPU ; le portage vers WebGPU est mécanique).</li>
<li>Kruskonja, <a href="https://medium.com/@mil_kru/two-pass-occlusion-culling-4100edcad501">Culling d’occultation en deux passes</a> (l’architecture de type Nanite expliquée en dehors des présentations d’Epic).</li>
<li>Karis et al., <a href="https://www.youtube.com/watch?v=eviSykqSUUw">Nanite — Analyse approfondie (SIGGRAPH 2021)</a> (la référence moderne sur le culling piloté par GPU à l’échelle AAA).</li>
<li>Karis, <a href="https://www.youtube.com/watch?v=NRnj_lnpORU">Conférence HPG 2022 : le parcours vers Nanite</a> (contexte plus général expliquant l’importance de l’étape de culling).</li>
<li>Sanders, <a href="https://www.youtube.com/watch?v=wavnKZNSYqU">Entre technologie et art : la végétation d’Horizon Zero Dawn</a> (la pile de culling de végétation AAA de bout en bout).</li>
<li>Guerrilla, <a href="https://www.youtube.com/watch?v=ToCozpl1sYY">Placement procédural à l’exécution basé sur le GPU dans Horizon Zero Dawn</a> (placement instancié et visibilité par instance).</li>
<li>Scthe, <a href="https://github.com/Scthe/nanite-webgpu">Nanite WebGPU</a> (une implémentation WebGPU de référence, avec HZB).</li>
<li>Kitware, <a href="https://www.kitware.com/webgpu-occlusion-culling-in-vtk/">Culling d’occultation WebGPU dans VTK</a> (le premier HZB en production côté navigateur dont j’ai connaissance).</li>
<li>Acerola, <a href="https://www.youtube.com/watch?v=Y0Ko0kvwfgA">Comment les jeux parviennent-ils à afficher autant d’herbe ?</a> et <a href="https://www.youtube.com/watch?v=PNvlqsXdQic">Comment j’ai optimisé l’herbe de mon jeu</a> (la présentation YouTube accessible).</li>
<li>RasterGrid, <a href="https://www.rastergrid.com/blog/2010/10/hierarchical-z-map-based-occlusion-culling/">Culling d’occultation basé sur une carte Hi-Z</a> (l’introduction au Hi-Z qui fait toujours référence).</li>
<li>Intel, <a href="https://www.intel.com/content/www/us/en/developer/articles/technical/masked-software-occlusion-culling.html">Culling d’occultation logiciel masqué</a> (la famille des rastériseurs CPU, si vous décidez un jour que l’option nº 6 en vaut la peine).</li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 29 : un seul contrôleur, n’importe quel corps]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character</guid>
            <pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 58 construit un contrôleur de personnage dans lequel le moteur physique ne sait rien de la marche, de la nage ou du vol plané. Chaque comportement est un contrôleur enfichable, le même moteur pilote le joueur, un cheval montable et un PNJ autonome, et l’ensemble fonctionne sans interface graphique dans Node. Le spike 59 lui associe un avatar recalé sans toucher à un seul contrôleur, tandis que le spike 60 lui associe un pack Synty qui ne nécessite aucun recalage, derrière un sélecteur de clips testable unitairement.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 29 : un seul contrôleur, n’importe quel corps</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et contient des liens vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-14-open-world-browser-part-28-grass-and-occlusion">partie 28</a> portait sur l’herbe et l’occlusion. Vingt-huit parties ont permis de construire un monde dans lequel évoluer : un terrain lisible, de l’eau dans laquelle nager, une végétation qui tient jusqu’à l’horizon, un serveur qui mémorise vos modifications. Cette partie traite de ce qui se déplace à travers tout cela, et c’est l’aboutissement de l’ensemble du moteur, car l’objectif n’est pas de créer un contrôleur de joueur. Il s’agit de créer un contrôleur qui ne se soucie ni du corps qu’il pilote, ni de l’origine de ses animations, ni de savoir si un humain ou une IA est aux commandes. Le spike 58 construit le déplacement comme une pile de comportements enfichables au-dessus d’un moteur physique qui ne sait rien de la locomotion, puis valide l’architecture en faisant évoluer trois corps différents avec une même implémentation. Le spike 59 associe à ce contrôleur un véritable avatar recalé sans en modifier une seule ligne. Le spike 60 lui associe un autre pack d’animations qui ne nécessite aucun recalage et extrait la logique de sélection des clips pour la rendre véritablement testable.</p>
<h2>Un moteur physique qui ne sait rien de la marche</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/58-pluggable-character/" title="Spike 58 : personnage enfichable" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/58-pluggable-character/" target="_blank">Ouvrir le spike 58 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=58-pluggable-character">Voir le code source</a></p>
<p>La règle de conception est radicale, et c’est précisément tout l’intérêt : le moteur de capsule ne gère aucune locomotion. Ni marche, ni course, ni saut, ni friction, ni limite de vitesse maximale, pas même la gravité. Il intègre une capsule cinématique au terrain, rien de plus. Chaque comportement de locomotion — marche, glissade, vol plané, escalade, nage, accroupissement, endurance — vit dans un contrôleur autonome enregistré auprès du moteur. À chaque frame, le moteur actualise tous les contrôleurs, demande à chacun s’il souhaite prendre le contrôle, puis laisse le candidat ayant la priorité la plus élevée écrire la vélocité. La marche ne bénéficie d’aucun statut particulier. C’est simplement le contrôleur de priorité la plus basse qui répond toujours oui ; elle devient donc le comportement par défaut quand rien d’autre ne se déclenche. Un contrôleur se compose de six petites fonctions : un <code>tick</code> qui met à jour son propre état interne à chaque frame, même lorsqu’il n’est pas actif ; un prédicat pur <code>wantsControl</code> qui revendique la frame ; un <code>applyForces</code>, exécuté uniquement par le contrôleur gagnant, qui écrit la vélocité et applique sa propre gravité s’il en a besoin ; ainsi que les fonctions facultatives <code>onEnter</code>, <code>onExit</code> et <code>stateName</code>. La nage renvoie <code>ownsCollision: true</code> depuis <code>applyForces</code> afin de prendre en charge la gestion du terrain, car sinon son ressort de flottabilité entrerait en conflit avec l’alignement des pieds au sol du moteur.</p>
<p>Même les éléments qui ne relèvent pas de la locomotion finissent par transparaître à travers ce contrat, ce qui a imposé l’idée suivante : les canaux. Une première conception à canal unique faisait passer tous les comportements par un seul arbitrage. Un gestionnaire d’endurance ou une posture accroupie devait donc se faire passer pour de la locomotion, puis refuser le contrôle au moyen d’une astuce où <code>wantsControl</code> renvoyait faux, uniquement pour pouvoir effectuer sa comptabilité interne. La solution consiste à répartir les contrôleurs dans des canaux nommés qui effectuent leur arbitrage indépendamment et s’appliquent dans un ordre fixe : ressource, puis posture, puis locomotion. Le canal de ressource s’exécute en premier, car ses écritures, comme la diminution de l’endurance, sont lues par les autres. La posture s’exécute ensuite, car la réduction de la hauteur de la capsule lors de l’accroupissement doit être appliquée avant que la marche ne la lise pour limiter la vitesse maximale. La locomotion s’exécute en dernier et possède l’écriture de la vélocité pour la frame. Un observateur d’endurance, un modificateur d’accroupissement et un contrôleur de nage actif peuvent ainsi coexister proprement, chacun dans son propre canal, sans qu’aucun contrôleur ait à mentir sur sa nature. La démo représente tout cela au moyen d’une capsule nue dont la couleur varie selon le contrôleur actif, ce qui permet d’observer l’arbitrage : le vert de la marche passe à l’orange sur une pente raide lorsque la glissade prend le relais, au cyan dans le lac lorsque la nage l’emporte, puis au crème dans les airs quand vous déclenchez le vol plané.</p>
<h2>Un seul moteur, trois corps</h2>
<p>Le véritable test d’un moteur qui « ne gère aucune locomotion » n’est pas le joueur. Il faut vérifier si le même moteur, sans aucune modification, peut piloter quelque chose qui n’est pas du tout un joueur. <code>createCapsuleEngine</code> est une fabrique pure, sans état au niveau du module, sans singleton et sans effets secondaires propres à chaque instance. Le spike l’instancie donc trois fois. Le joueur est une première instance dotée de l’ensemble complet des contrôleurs. Un cheval montable constitue une deuxième instance qui n’enregistre qu’un contrôleur de marche, rendant ainsi toute l’idée littérale : le vocabulaire de déplacement d’un corps correspond simplement aux contrôleurs qui ont été enregistrés. Le cheval est donc plus rapide sur terrain plat et ne peut physiquement ni escalader, ni nager, ni planer, car ces contrôleurs n’ont jamais été ajoutés. Un PNJ autonome constitue une troisième instance, actualisée à chaque frame en même temps que le joueur et pilotée par un contrôleur d’errance qui synthétise ses propres entrées afin que le corps se dirige seul, sans aucune intervention au clavier. Le moteur n’apprend jamais que l’un de ses corps est un cheval ou qu’un autre est piloté par une IA. Tous utilisent le même intégrateur de capsule, avec des listes de contrôleurs différentes.</p>
<p>La monture est le seul élément qui vit délibérément en dehors du moteur. Transférer le contrôle entre le joueur et le cheval nécessite de coordonner deux moteurs. Or, un contrôleur s’exécute au sein d’un seul moteur et ne peut pas voir au-delà de cette frontière. La logique de monture se situe donc au niveau de l’hôte : il s’agit d’une petite machine à états qui décide quel moteur est exécuté pendant cette frame, immobilise l’autre, amène progressivement le cavalier sur la selle au moyen d’un <code>smoothstep</code> sur trois quarts de seconde et indique à la caméra quel corps suivre. La fabrique du moteur n’entend jamais parler de tout cela. C’est la frontière que l’architecture trace et respecte. Les comportements propres à un seul corps sont des contrôleurs ; la coordination entre plusieurs corps relève de l’hôte. C’est cette séparation qui permettrait d’ajouter un quatrième ou un quarantième corps sans aucun coût architectural supplémentaire.</p>
<h2>Testé sans navigateur</h2>
<p>Comme le moteur n’accède ni à <code>window</code>, ni à <code>document</code>, ni à Three.js, l’ensemble fonctionne sans interface graphique. Le spike fournit un banc de test Node qui simule l’interface du terrain, pilote le moteur frame par frame avec des entrées scriptées et vérifie l’état obtenu. Les régressions particulièrement pénibles à détecter par des sessions de jeu sont ainsi repérées par un script : transition entre saut et atterrissage, seuils d’entrée et de sortie de l’eau, désactivation automatique de l’escalade lorsqu’une surface s’aplatit, rythme de diminution de l’endurance. Exécuter deux fois la même séquence d’entrées et de pas de temps, puis vérifier que l’état de sortie est identique octet par octet, garantit le déterminisme du moteur, propriété sur laquelle une future version en réseau finira par s’appuyer. Une régression détectée par le banc de test mérite d’être conservée : passer d’un sol praticable à une pente trop raide pour être parcourue déclenchait auparavant le contrôleur de glissade et faisait rebondir le joueur vers le haut de la pente. La correction a consisté à ajouter une condition de descente. La glissade ne démarre que si la capsule tombe effectivement sur la pente. Marcher horizontalement contre une paroi abrupte bloque désormais proprement le joueur au lieu de le faire glisser, et le test qui vérifie que « marcher contre une pente non praticable bloque le joueur » garantit que cela reste corrigé.</p>
<h2>Brancher un véritable avatar sans toucher aux contrôleurs</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/59-pluggable-with-avatar/" title="Spike 59 : système enfichable avec avatar" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/59-pluggable-with-avatar/" target="_blank">Ouvrir le spike 59 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=59-pluggable-with-avatar">Voir le code source</a></p>
<p>Le spike 59 vérifie si la couche des contrôleurs est réellement découplée du corps. Il remplace la capsule colorée par un véritable personnage skinné — le rig FBX 3MIKE, accompagné de clips de la Quaternius Universal Animation Library recalés sur celui-ci — sans modifier les contrôleurs. La jonction se résume à une simple chaîne de caractères. Chaque contrôleur communique déjà un nom d’état par l’intermédiaire de <code>stateName</code> — idle, walk, run, jump, fall, land, slide, glide, swim, swimIdle — et la couche de l’avatar associe ce nom à un clip recalé au moyen d’une table d’alias, puis effectue un fondu enchaîné lors du changement. Le contrôleur de marche sélectionne dynamiquement son propre sous-état à partir du contact au sol, de la vélocité verticale et de la vitesse horizontale. Un seul contrôleur de marche pilote ainsi l’attente, la marche, la course, le saut, la chute et l’atterrissage, tandis que l’avatar se contente de suivre le nom communiqué. Comme ce spike est conçu pour un seul joueur, l’avatar abandonne l’indirection par entiers utilisée sur le réseau et l’abstraction multi-personnages des précédents spikes en réseau, pour associer directement le nom de l’état à un clip. Le fait que toute cette amélioration visuelle — le passage d’une capsule à un humain doté d’un rig — n’ait modifié aucune ligne du code de locomotion démontre précisément l’intérêt d’un contrôleur enfichable.</p>
<h2>Un pack sans recalage et un sélecteur testable</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/60-polygon-animations/" title="Spike 60 : animations POLYGON" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/60-polygon-animations/" target="_blank">Ouvrir le spike 60 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=60-polygon-animations">Voir le code source</a></p>
<p>Le spike 60 branche un troisième corps, le pack POLYGON Base Locomotion de Synty, et son chargeur est presque inexistant. Chaque clip est fourni sous la forme d’un fichier FBX autonome contenant une copie intégrée du même squelette Synty ainsi qu’une animation précalculée. Comme le rig du personnage et chaque clip utilisent des noms d’os identiques, il suffit de récupérer le clip dans <code>fbx.animations[0]</code> et de le lire directement dans le mixer du personnage, sans aucune bibliothèque de recalage. Three.js résout les cibles des pistes d’animation à partir du nom des os plutôt que de l’identité des objets. Un pack de type Synty ou Mixamo créé pour le rig correspondant fonctionne donc directement. C’est un contraste délibéré avec le parcours UAL du spike précédent, qui nécessite un recalage conséquent parce que les clips sources et le rig cible ont été créés pour des squelettes différents. Même contrôleur, même jonction par nom d’état, mais deux pipelines d’animation totalement différents derrière celle-ci.</p>
<p>L’autre moitié du spike 60 consiste à rendre testable la logique de sélection des clips. Choisir quel clip lire repose sur de nombreux seuils d’appréciation, et cette logique était auparavant enfouie dans la couche de l’avatar, à côté de FBXLoader et du DOM, où elle ne pouvait pas être testée. Le spike extrait le sélecteur dans des fonctions pures qui n’accèdent ni à Three.js ni à la fenêtre : elles reçoivent un simple enregistrement du joueur — vélocité, vitesse horizontale, orientation, contact au sol, normale du sol, vélocité d’impact — ainsi qu’un substitut d’action de clip, puis renvoient une chaîne d’alias de clip. Les seuils peuvent ainsi devenir des constantes nommées et couvertes par des assertions. Le saut se décline en variantes de marche, de course et de sprint selon des tranches de vitesse alignées sur le véritable seuil de sprint du contrôleur de marche ; les atterrissages se divisent en variantes douces, moyennes et brutales selon la vélocité d’impact ; les variantes de clips en montée et en descente se déclenchent à partir d’un produit scalaire de projection sur la pente, avec une zone morte qui utilise le clip de terrain plat en dessous d’environ sept degrés ; une transition entre l’attente et la locomotion choisit toujours la direction avant, afin qu’un départ à l’arrêt ne lise jamais un clip à l’envers ; et une transition d’arrêt est soumise à une durée minimale passée dans la boucle, car les clips d’arrêt alignés sur la phase des pieds de Synty contiennent environ une seconde de décélération prédéfinie, ce qui paraît ridicule lorsqu’elle est ajoutée à un mouvement où le joueur a à peine fait un pas. Un petit anti-rebond d’état retarde l’état de chute de quelques frames, afin qu’une perte de contact avec le sol pendant une seule frame au passage d’une jonction ne fasse pas clignoter l’animation. Extraire toutes ces règles de l’enveloppe de rendu permet de les tester unitairement sans GPU, en appliquant la même discipline d’exécution sans interface graphique que celle adoptée pour le moteur dans le spike 58. C’est ce qui distingue des sensations de locomotion réglées au hasard de sensations que l’on peut définir et stabiliser précisément.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Un moteur physique dépourvu de locomotion.</strong> Le moteur de capsule intègre un corps cinématique au terrain et ne gère ni la marche, ni la course, ni le saut, ni la friction, ni la limite de vitesse, ni la gravité. Chaque comportement est un contrôleur enregistré qui expose <code>tick</code>, <code>wantsControl</code>, <code>applyForces</code>, ainsi que les fonctions facultatives <code>onEnter</code>/<code>onExit</code>/<code>stateName</code>. La marche n’est que le comportement par défaut de priorité minimale qui répond toujours oui, et un contrôleur peut renvoyer <code>ownsCollision: true</code> pour prendre en charge la gestion du terrain (c’est le cas de la nage, afin que son ressort de flottabilité n’entre pas en conflit avec l’alignement des pieds au sol).</p>
<p><strong>Des canaux d’arbitrage indépendants.</strong> Les contrôleurs sont enregistrés dans des canaux nommés (ressource, posture, locomotion) qui effectuent leur arbitrage séparément et s’appliquent dans un ordre fixe. Les observateurs comme l’endurance et les modificateurs comme l’accroupissement coexistent ainsi avec la locomotion active au lieu de simuler une prise de contrôle pour ensuite la refuser. Les écritures du canal de ressource (endurance) sont lues par la posture et la locomotion ; les écritures de la posture (hauteur de la capsule accroupie) sont lues par la limite de vitesse de la locomotion ; la locomotion s’exécute en dernier et possède l’écriture de la vélocité.
<strong>Une seule fabrique, de nombreux corps.</strong> <code>createCapsuleEngine</code> est une fabrique pure sans singleton : le même moteur anime donc le joueur, un cheval montable limité à la marche et un PNJ autonome alimenté par des entrées synthétiques. Le vocabulaire de déplacement d’un corps correspond exactement à l’ensemble de ses contrôleurs enregistrés. Le cheval ne peut donc ni grimper ni nager, puisque ces contrôleurs n’ont jamais été ajoutés. La gestion de la monture se situe au niveau de l’hôte, et non dans un contrôleur, car elle coordonne deux moteurs qui ne peuvent pas communiquer directement. Voir le <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD piloté par le GPU</a>.</p>
<p><strong>Tests déterministes sans interface graphique.</strong> Le moteur n’accède ni à <code>window</code>, ni à <code>document</code>, ni à Three.js. Un banc de test Node peut donc le piloter image par image et vérifier les séquences de saut et d’atterrissage, les seuils de nage, la sortie automatique de l’escalade et la consommation d’endurance. Rejouer deux fois des entrées identiques et vérifier que les sorties le sont aussi démontre le déterminisme. Un test de régression verrouille également le seuil de descente qui empêche le joueur de glisser en arrière lorsqu’il marche horizontalement contre une paroi abrupte.</p>
<p><strong>Liaison d’avatar indépendante du corps, avec un sélecteur testable.</strong> Les contrôleurs renvoient le nom d’un état sous forme de chaîne, puis la couche visuelle l’associe à un clip. Remplacer une capsule par un avatar 3MIKE + UAL reciblé ne nécessite donc aucune modification du code de locomotion. Les clips Synty POLYGON partagent les noms d’os du rig et se lisent sans reciblage — Three.js associe les pistes selon le nom des os — contrairement au lourd processus de reciblage de la voie UAL. Le sélecteur de clips est extrait sous forme de fonctions pures, avec des constantes nommées pour les paliers de vitesse de saut, la sévérité de l’atterrissage, les variantes de projection sur les pentes, une transition depuis l’immobilité toujours orientée vers l’avant, une durée minimale de protection pour la transition d’arrêt et un filtre anti-rebond pour l’état de chute. Les sensations de locomotion peuvent ainsi être testées unitairement plutôt que réglées au hasard.</p>
<hr>
<p>Partie 29 sur 30.
Précédent : <a href="/fr/blog/2026-05-14-open-world-browser-part-28-grass-and-occlusion">Partie 28 — De l’herbe jusqu’à l’horizon et un sol qui se dissimule</a>
Suivant : <a href="/fr/blog/2026-06-04-open-world-browser-part-30-collision-aware-camera">Partie 30 — Une caméra qui respecte les murs</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 28 : de l'herbe jusqu'à l'horizon et un sol qui se masque lui-même]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-14-open-world-browser-part-28-grass-and-occlusion</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-14-open-world-browser-part-28-grass-and-occlusion</guid>
            <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le Spike 56 fait en sorte qu'un demi-million de touffes d'herbe ressemblent à un champ plutôt qu'à des confettis verts, grâce à un seul noyau de calcul et une astuce de quads croisés. Le Spike 57 compare quatre méthodes pour éliminer ce qu'une colline masque et constate que la méthode accélérée est aussi la plus juste géométriquement.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 28 : de l'herbe jusqu'à l'horizon et un sol qui se masque lui-même</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-13-open-world-browser-part-27-island-and-terrain">partie 27</a> a construit l'île et lui a donné un sol qui ne semble pas carrelé. Cette partie aborde les deux éléments qui donnent au terrain l'impression d'être habité plutôt que vide. Le Spike 56 porte sur l'herbe, ce détail de surface qui transforme une pente texturée en un lieu que l'on aurait envie de traverser, avec pour difficulté de faire en sorte qu'un champ ressemble bien à un champ plutôt qu'à des traits épars. Le Spike 57 fait l'inverse d'afficher davantage : il s'agit de ne pas afficher ce qu'une colline masque déjà, et le résultat intéressant est que la méthode rapide pour le déterminer s'est aussi révélée être la bonne.</p>
<h2>Une herbe qui ressemble à un champ, pas à des confettis</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/56-grass/" title="Spike 56 sur l'herbe" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/56-grass/" target="_blank">Ouvrir le Spike 56 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=56-grass">Voir le code source</a></p>
<p>La décision fondamentale est géométrique. Une seule lame fine et effilée occupe moins d'un pixel sous la plupart des angles de caméra ; un demi-million de lames plates ressemblent donc à de rares confettis verts plutôt qu'à un champ. La solution est une touffe en quads croisés : trois quads effilés, espacés de soixante degrés autour de l'axe vertical local, de sorte que, quelle que soit la direction d'observation, au moins un quad soit presque perpendiculaire à la caméra et que chaque instance couvre à l'écran une surface réelle équivalant à environ trois largeurs de brin. C'est ce qui fait la différence entre des traits verts et de l'herbe.</p>
<p>Un noyau de calcul WebGPU positionne chaque touffe une seule fois à l'initialisation. Il hache l'indice de l'instance pour produire cinq flux aléatoires décorrélés, choisit une position XZ dans la parcelle, échantillonne la hauteur du sol à partir du même FBM que celui utilisé par le maillage du sol côté CPU — un portage TSL doté d'un modulo qui préserve le signe afin que les valeurs correspondent exactement et que les brins reposent sur la surface au lieu de flotter au-dessus —, calcule une normale par différences centrales, puis détermine aléatoirement la largeur, la hauteur et la teinte de chaque touffe. Côté rendu, un graphe de sommets TSL contourne entièrement la matrice d'instance et écrit directement dans l'espace de projection : il redimensionne le quad croisé unitaire, aligne l'axe vertical local sur la normale du sol grâce à la formule de Rodrigues, puis le translate vers la position de la touffe. Le LOD en fonction de la distance ne nécessite aucune passe d'élimination, car le graphe de sommets multiplie la hauteur de la touffe par $1 - \text{smoothstep}(\text{fadeNear}, \text{fadeFar}, \text{dist})$ ; les touffes éloignées s'aplatissent donc jusqu'à une hauteur nulle et ne consomment plus de fill rate. Le vent combine deux octaves de sinus et de cosinus appliquées aux coordonnées XZ de la touffe dans le monde et au temps. Le mouvement est horizontal et modulé par la fraction de hauteur, afin que la base reste ancrée tandis que les pointes bougent. Une légère inclinaison tenant compte de la caméra penche aussi chaque touffe vers le joueur, afin qu'elle s'ouvre avec la perspective au lieu de ressembler à une carte plate.</p>
<p>La recette des couleurs est empruntée au shader URP inspiré de Breath of the Wild de NedMakesGames : avec un ombrage plat, la couleur du brin est une interpolation entre une teinte à la racine et une teinte à la pointe selon la fraction de hauteur, ces deux teintes étant elles-mêmes interpolées entre deux palettes à partir de la valeur de teinte propre à chaque touffe. On obtient ainsi l'aspect bicolore moucheté d'un véritable champ d'herbe de BotW. L'éclairage diffus utilise la normale du sol orientée par rapport au soleil, avec un minimum ambiant, car les normales de chaque quad croisé produisent trop de bruit pour être ombrées individuellement dans un rendu stylisé. Tout le champ est rendu en un seul appel de dessin, entièrement positionné et animé sur le GPU.</p>
<h2>Quatre façons d'éliminer ce qu'une colline masque</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/57-terrain-occlusion/" title="Spike 57 sur l'occlusion du terrain" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/57-terrain-occlusion/" target="_blank">Ouvrir le Spike 57 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=57-terrain-occlusion">Voir le code source</a></p>
<p>Le Spike 57 compare quatre méthodes d'élimination sur le même monde procédural diffusé en streaming par le client de production, avec une échelle verticale multipliée par 1,8 afin que les collines masquent réellement la végétation. T0 se limite à la distance, comme le fait déjà le système de visibilité des chunks en production. T1 ajoute l'élimination par frustum, le gain peu coûteux le plus important, en supprimant tout ce qui se trouve en dehors du cône de vision. T2 ajoute un test d'horizon tenant compte du terrain : un rayon est parcouru depuis l'œil jusqu'à chaque instance, qui est rejetée si la heightmap s'élève au-dessus du rayon à un point quelconque du trajet. Un arbre derrière une crête est ainsi éliminé même s'il se trouve dans le frustum. T3 conserve le même test d'horizon, mais l'accélère avec une pyramide de hauteurs maximales. T4 porte l'ensemble du test distance-frustum-horizon dans un noyau de calcul TSL, qui écrit pour chaque instance un facteur de visibilité lu par le matériau de la végétation afin de réduire à zéro les sommets masqués. Un compteur de visibilité persistante sur deux images atténue le scintillement d'une seule image lorsqu'un point d'échantillonnage passe juste à l'intérieur ou à l'extérieur d'un texel au moindre mouvement de la caméra. Cette durée reste volontairement courte, car une valeur plus longue masquerait les erreurs algorithmiques au lieu de les corriger.</p>
<p>C'est avec la pyramide que le spike prend tout son sens, et la leçon consiste à faire correspondre le test à ce qui apparaît réellement à l'écran. Le terrain rendu est un maillage triangulaire dont les sommets sont placés sur une grille fixe de deux mètres. Entre les sommets, le rastériseur effectue une interpolation linéaire ; la hauteur réellement affichée à l'intérieur d'un rectangle est donc le maximum des sommets qu'il contient, jamais le pic du bruit continu situé entre eux. Si la pyramide d'occlusion échantillonne le bruit sur une grille plus fine, elle détecte des pics fantômes que le maillage n'affiche jamais et commence à bloquer des rayons dont la caméra voit pourtant clairement le trajet. Les texels de base de la pyramide sont donc placés exactement sur la grille des sommets, chacun stockant le maximum de ses quatre sommets d'angle, tandis que les niveaux supérieurs emploient des réductions maximales 2x2 classiques. La pyramide est ainsi exacte par rapport au maillage rendu. Pour le test du rayon à chaque étape, le code échantillonne délibérément le maillage par interpolation bilinéaire au lieu d'interroger la pyramide, car une requête AABB plus petite qu'un texel renvoie le maximum de l'ensemble du texel et augmente artificiellement la hauteur de plusieurs mètres sur les crêtes abruptes, provoquant précisément la sur-occlusion qui masquerait des éléments pourtant visibles.</p>
<p>Le résultat surprenant est que T2, la référence par force brute, est la méthode erronée. Comme T2 échantillonne directement le bruit continu en un point, elle détecte entre les sommets du maillage des pics qui ne sont jamais rendus. Elle produit donc une légère sur-occlusion et masque de la végétation que le joueur peut réellement voir. La méthode pyramidale est à la fois plus rapide, grâce à la réduction AABB en $O(\log N)$ pour les tests au niveau des chunks, et plus juste géométriquement, puisqu'elle ne peut renvoyer que des hauteurs effectivement affichées par le maillage. C'est tout l'intérêt de ce spike : la structure accélérée n'est pas un compromis de qualité accepté au nom de la vitesse, mais la version qui correspond à la réalité. L'élimination des chunks effectue un test en cinq points pour chacun d'eux — les quatre angles supérieurs et le centre, à la hauteur maximale du chunk — et exclut l'empreinte du chunk testé de l'ensemble des occludeurs, afin qu'un chunk ne puisse jamais s'occulter lui-même. La méthode recommandée pour la production est T4 : placer les métadonnées des instances et le champ de hauteurs dans des buffers de stockage, puis exécuter la même boucle dans un shader de calcul avec un appel de dessin indirect, puisque le moteur de rendu passe de toute façon à WebGPU.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Herbe GPU en quads croisés.</strong> Trois quads effilés, espacés de soixante degrés par touffe, garantissent qu'au moins l'un d'eux sera presque perpendiculaire à la caméra sous n'importe quel angle. Un demi-million d'instances ressemblent ainsi à un champ plutôt qu'à des confettis de moins d'un pixel. Un noyau de calcul positionne chaque touffe — hauteur du sol FBM échantillonnée avec le même modulo préservant le signe que le maillage CPU, normale par différences centrales, taille et teinte propres à chaque touffe —, tandis qu'un graphe de sommets TSL contourne la matrice d'instance pour redimensionner la touffe, l'aligner sur la normale par rotation de Rodrigues, la translater et lui appliquer un mouvement de vent modulé par la hauteur. Le LOD en fonction de la distance est gratuit : les touffes s'aplatissent jusqu'à une hauteur nulle selon $1 - \text{smoothstep}(\text{fadeNear}, \text{fadeFar}, \text{dist})$. La couleur suit la recette BotW de NedMakesGames, avec un dégradé de la racine à la pointe appliqué à deux palettes mélangées selon la teinte. Voir le <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD piloté par le GPU</a>.</p>
<p><strong>Élimination par occlusion de l'horizon du terrain.</strong> Quatre méthodes sont comparées sur le monde de production : distance, puis frustum, puis un test de rayon sur la heightmap qui rejette les instances situées derrière une crête, puis une pyramide de hauteurs maximales qui accélère ce test, et enfin un portage en calcul TSL. La pyramide échantillonne exactement sur la grille de sommets de deux mètres utilisée pour la tessellation du maillage — texel de base égal au maximum des quatre sommets d'angle, puis réductions maximales 2x2 vers le haut —, de sorte qu'elle ne renvoie que les hauteurs réellement affichées par le rastériseur.</p>
<p><strong>Accélérée et juste, sans compromis.</strong> L'échantillonnage ponctuel du bruit continu — la méthode T2 par force brute — détecte entre les sommets du maillage des pics qui ne sont jamais rendus, ce qui produit une sur-occlusion de la végétation visible. La pyramide alignée sur la grille des sommets est à la fois plus rapide — réduction AABB en $O(\log N)$ — et géométriquement exacte. Les tests de rayon à chaque étape échantillonnent donc le maillage par interpolation bilinéaire au lieu d'interroger le maximum par texel de la pyramide, ce qui augmenterait artificiellement les hauteurs de plusieurs mètres sur les crêtes abruptes. L'élimination des chunks emploie un test en cinq points et exclut l'empreinte propre à chaque chunk afin qu'il ne puisse jamais s'occulter lui-même. La méthode de production place les métadonnées et le champ de hauteurs dans des buffers de stockage pour effectuer l'élimination dans un shader de calcul avec un appel de dessin indirect.</p>
<hr>
<p>Partie 28 sur 29.
Précédent : <a href="/fr/blog/2026-05-13-open-world-browser-part-27-island-and-terrain">Partie 27 — Une île issue du bruit, avec un sol qui ressemble à un sol</a>
Suivant : <a href="/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character">Partie 29 — Un seul contrôleur, n'importe quel corps</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 27 : une île née du bruit, un sol qui ressemble vraiment à un sol]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-13-open-world-browser-part-27-island-and-terrain</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-13-open-world-browser-part-27-island-and-terrain</guid>
            <pubDate>Wed, 13 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 54 fait naître une île crédible à partir du seul bruit : un FBM déformé dans le domaine et façonné par une atténuation radiale, puis une érosion hydraulique qui creuse un véritable réseau de drainage, avant l’attribution de biomes selon l’altitude et l’humidité. Le spike 55 corrige le plus vieux problème des textures, la tuile qui se répète, grâce à quatre modes d’échantillonnage et à celui qui l’emporte vraiment.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 27 : une île née du bruit, un sol qui ressemble vraiment à un sol</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-12-open-world-browser-part-26-water">partie 26</a> a ajouté de l’eau au monde. Cette partie construit la terre qui se trouve dessous, en deux étapes qui reproduisent la formation d’un lieu réel. Le spike 54 crée le terrain lui-même : non pas sculpté à la main, mais généré à partir de bruit, puis érodé jusqu’à présenter le réseau de drainage et le littoral d’un endroit réellement traversé par l’eau. Le spike 55 crée l’enveloppe de ce terrain en résolvant le problème qui met en échec presque tous les sols procéduraux : une texture répétée paraît répétée. Ensemble, ils répondent à une question : un créateur peut-il obtenir une île plausible et un sol qui ressemble vraiment à un sol, sans qu’un artiste intervienne sur l’un ou l’autre ? La réponse est oui, à condition de reproduire les bonnes recettes.</p>
<h2>Un littoral que le bruit seul ne peut pas produire</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/54-procgen-island/" title="Spike 54 Île procédurale" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/54-procgen-island/" target="_blank">Ouvrir le spike 54 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=54-procgen-island">Voir le code source</a></p>
<p>La carte de hauteur suit les recettes de Red Blob Games, qui empilent de petites transformations corrigeant chacune un défaut précis du bruit brut. Le mouvement brownien fractionnaire additionne plusieurs octaves de bruit de Perlin afin que le terrain présente à la fois de larges collines et des détails fins. Mais un FBM simple paraît doux et aligné sur une grille ; le point d’échantillonnage subit donc d’abord une déformation du domaine : chaque coordonnée est décalée par un autre champ de bruit, $p' = p + \text{noise}(p)\cdot s$, ce qui courbe les crêtes pour leur donner une forme organique plutôt que de les verrouiller sur les axes. Vient ensuite la redistribution : élever l’altitude à une puissance $e^{,k}$ abaisse les tons moyens afin que le monde présente des vallées plates et des sommets acérés, au lieu d’une même pente douce partout. L’élément qui en fait une île plutôt qu’une succession infinie de collines est une atténuation radiale : on calcule la distance euclidienne depuis le centre, $d = \min(1, \sqrt{n_x^2 + n_y^2})$, puis on interpole l’altitude vers $1 - d$ afin que le terrain plonge dans l’océan sur les bords. Un second champ de bruit indépendant devient une carte d’humidité, qui ne modifie pas la forme, mais alimentera ensuite l’étape de création des biomes.</p>
<p>On obtient ainsi une forme, mais une forme issue du bruit. Elle ne comporte ni rivières, ni vallées creusées par l’eau, ni cônes de sédiments, car rien ne s’est encore écoulé à sa surface. La solution est l’érosion hydraulique, adaptée de l’implémentation sous licence MIT de Sebastian Lague. Des milliers de gouttes d’eau apparaissent aléatoirement et dévalent les pentes. Chacune conserve une inertie pour éviter les virages brusques à angle droit, emporte des sédiments lorsqu’elle accélère et les dépose lorsqu’elle ralentit ou forme une flaque. Une brosse circulaire précalculée répartit chaque événement d’érosion sur un petit rayon afin de produire un creusement lisse plutôt qu’une rayure d’un pixel, tandis que l’évaporation fait disparaître la goutte au terme de sa durée de vie. Avec suffisamment de gouttes, le terrain acquiert ce que le bruit ne peut pas simuler : des réseaux de drainage qui convergent, des vallées qui s’élargissent en aval et des zones planes où les sédiments se sont déposés.</p>
<h2>L’érosion sur le GPU et des rivières qui savent où elles se trouvent</h2>
<p>L’érosion sur CPU est correcte, mais lente. Le spike l’adapte donc aussi à un shader de calcul WebGPU, et l’aspect intéressant tient aux contraintes imposées par les opérations atomiques du GPU. Les hauteurs sont stockées sous forme de valeurs <code>i32</code> à virgule fixe, avec une échelle d’un million, car WGSL ne propose aucune addition atomique sur les nombres flottants. La seule manière de permettre à des milliers de gouttes de déposer et de retirer en parallèle des sédiments dans une même cellule sans condition de concurrence consiste à utiliser <code>atomicAdd</code> et <code>atomicSub</code> sur des entiers. La conversion aller-retour en virgule fixe peut parfois rendre une cellule légèrement négative sous l’effet de nombreux entraînements simultanés, ce qui est sans conséquence puisque la valeur est bornée lors de la relecture. La version CPU précalcule une table de brosses par cellule, mais sur une grille de $1001^2$, cette table occupe environ 100 Mo. L’adaptation GPU recalcule donc à la volée la brosse de chaque goutte, échangeant un peu de calcul contre une importante économie de mémoire. Les positions de départ des gouttes proviennent toujours du CPU, au moyen d’un générateur Mulberry32, afin que les exécutions restent déterministes.</p>
<p>Deux passes supplémentaires transforment le terrain érodé en carte lisible. Le drainage emploie la méthode d’accumulation des écoulements D8 de Red Blob : on dépose une unité de pluie sur chaque cellule, on trie toutes les cellules par altitude décroissante, puis on transmet l’eau accumulée par chacune à son unique voisin le plus bas. L’eau s’accumule ainsi dans les vallées naturelles, et toute cellule dont l’accumulation dépasse un seuil est marquée comme rivière. Le problème est qu’une carte de hauteur érodée comporte des cuvettes, c’est-à-dire des dépressions locales sans issue descendante. L’eau qui pénètre dans une cuvette s’y arrête, ce qui interrompt l’accumulation. Avant le calcul du drainage, le remplissage des cuvettes de Planchon-Darboux relève donc chacune d’elles juste au-dessus de son voisin le plus bas, $\text{minNeighbour} + \epsilon$, en effectuant moins de vingt itérations jusqu’à ce que chaque cellule dispose d’une voie d’écoulement. Enfin, les biomes sont attribués selon la méthode de Red Blob : une table de correspondance à deux axes croise altitude et humidité, de sorte qu’une cellule haute et sèche devient rocheuse, tandis qu’une cellule basse et humide devient marécageuse. Des règles prioritaires tenant compte de la pente s’y ajoutent : les faces abruptes deviennent rocheuses quelle que soit leur humidité, la bande correspondant au niveau de l’eau devient une plage et les altitudes les plus élevées se couvrent de neige. Le résultat est une île immédiatement lisible, générée de bout en bout à partir de deux graines de bruit.</p>
<h2>La tuile qui n’arrête pas de se répéter</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/55-terrain-shading/" title="Spike 55 Ombrage du terrain" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/55-terrain-shading/" target="_blank">Ouvrir le spike 55 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=55-terrain-shading">Voir le code source</a></p>
<p>Un terrain a besoin d’un matériau couvrant des kilomètres à partir d’une texture de quelques mètres de côté. Dès qu’une tuile est mise à l’échelle pour recouvrir le sol, l’œil repère immédiatement la répétition. Le spike 55 compare quatre modes d’échantillonnage sur une géométrie et un éclairage identiques, de sorte que la seule variable soit la manière dont la texture est lue. Le point de référence est <code>plain</code> : un seul échantillon aux coordonnées UV mises à l’échelle, avec une répétition évidente tous les quelques mètres. Il n’existe que pour servir de solution à battre. Les couches PBR gratuites proviennent de Poly Haven via leur CDN : texture diffuse, carte ARM regroupant occlusion ambiante, rugosité et métallicité, ainsi qu’une normale GL. Le tout est sous licence CC0 et chargé avec une anisotropie de 8 et des mipmaps.</p>
<p>Les trois autres modes proposent différentes approches contre la répétition. Le mode <code>hex-linear</code> correspond à la grille triangulaire de Heitz-Neyret : une grille triangulaire inclinée est placée sur la surface, et chaque fragment se trouve dans un triangle dont les trois sommets utilisent chacun un échantillon décalé aléatoirement. Ces échantillons sont mélangés selon des poids barycentriques, de sorte que les régions voisines lisent des parties différentes de la texture et que la répétition à grande échelle disparaisse. Mais un simple mélange linéaire de trois échantillons moyenne leurs couleurs au bord des triangles, ce qui laisse apparaître un motif triangulaire visible, exactement l’artefact rencontré dans le spike 48. Le mode <code>hex-vp</code> applique la correction publiée dans le même article de 2018 : après la somme barycentrique, il soustrait la couleur moyenne de la texture, remet la valeur à l’échelle avec l’inverse de la racine carrée de la somme des poids au carré afin de maintenir une variance constante, puis réajoute la moyenne. Le contraste reste ainsi stable dans toute la zone de mélange et les triangles disparaissent. C’est pourquoi le matériau effectue d’abord une relecture de 1 × 1 de chaque texture chargée pour estimer sa couleur moyenne. Le quatrième mode, <code>iq-untiled</code>, correspond à la technique 3 de Texture Repetition d’Inigo Quilez : deux échantillons décalés sont mélangés selon un motif de variation basse fréquence. Il ne nécessite que deux échantillons au lieu de trois et ne présente aucune structure triangulaire au départ, ce qui en fait l’option économique qui reste convaincante.</p>
<p>Une projection triplanaire facultative peut être ajoutée à n’importe lequel de ces modes. C’est elle qui permet au même matériau d’envelopper une falaise sans s’étirer. Au lieu d’utiliser un seul jeu de coordonnées UV, le shader échantillonne trois projections suivant les axes du monde : les surfaces orientées vers X lisent le plan YZ, celles orientées vers Y lisent ZX et celles orientées vers Z lisent XY. Il les mélange selon <code>abs(normalWorld)</code>, élevé à une puissance d’accentuation comprise entre quatre et huit, afin qu’une pente à 45 degrés ne transforme pas les trois projections en une bouillie d’images fantômes. Le banc d’essai rend le compromis évident : le mélange hexagonal préservant la variance l’emporte en qualité, tandis que la solution à deux échantillons de Quilez privilégie les performances. Tous deux surpassent assez largement le mode simple pour qu’aucun créateur ne soit tenté de publier un terrain utilisant la tuile ordinaire.</p>
<h2>Technologies mentionnées dans ce chapitre</h2>
<p><strong>Génération d’une carte de hauteur par empilement de recettes.</strong> Les recettes de Red Blob Games combinent le bruit brut pour former un terrain : FBM déformé dans le domaine ($p' = p + \text{noise}(p)\cdot s$) pour produire des crêtes organiques, redistribution $e^{,k}$ pour obtenir des vallées plates et des sommets acérés, et atténuation radiale euclidienne interpolée vers $1 - d$ pour plonger les bords dans l’océan et créer une île. Un second champ de bruit fournit la carte d’humidité utilisée pour attribuer les biomes. Voir <a href="/fr/guides/landscape-generation-browser">génération de paysages</a>.</p>
<p><strong>Érosion hydraulique sur CPU et GPU.</strong> Le modèle de gouttes de Sebastian Lague — inertie, capacité de transport, brosse circulaire précalculée pour les dépôts et évaporation — creuse un véritable réseau de drainage que le bruit ne peut pas simuler. L’adaptation au calcul WebGPU stocke les hauteurs dans des <code>i32</code> à virgule fixe, avec une échelle d’un million, afin que les gouttes puissent appliquer <code>atomicAdd</code>/<code>atomicSub</code> en parallèle sur la même cellule. Elle recalcule à la volée la brosse de chaque goutte pour éviter une table d’environ 100 Mo sur une grille de $1001^2$, et génère les positions de départ avec un générateur Mulberry32 déterministe.</p>
<p><strong>Drainage D8 avec remplissage des cuvettes.</strong> L’accumulation des écoulements D8 de Red Blob dépose une unité de pluie par cellule, trie les cellules par altitude décroissante et transmet l’eau au voisin le plus bas de chaque cellule, en marquant comme rivières celles qui dépassent un seuil. Le remplissage des cuvettes de Planchon-Darboux commence par relever chaque dépression locale jusqu’à $\text{minNeighbour} + \epsilon$ en moins de vingt itérations, afin que l’eau ne reste jamais piégée et que l’accumulation demeure connectée. Une table de correspondance à deux axes croisant altitude et humidité attribue les biomes, avec des règles tenant compte de la pente qui imposent la roche sur les falaises, la plage au niveau de l’eau et la neige sur les sommets.</p>
<p><strong>Quatre méthodes pour casser la répétition des textures.</strong> Sur une géométrie et un éclairage identiques : <code>plain</code> (un échantillon, répétition évidente), <code>hex-linear</code> (grille triangulaire de Heitz-Neyret, dont le mélange linéaire laisse apparaître un motif triangulaire), <code>hex-vp</code> (la correction préservant la variance de la section §3.3 d’EGSR 2018, qui soustrait la moyenne, remet à l’échelle par l’inverse de la racine carrée des poids au carré et réajoute la moyenne, ce qui nécessite une relecture moyenne de 1 × 1 par texture) et <code>iq-untiled</code> (technique 3 à deux échantillons d’Inigo Quilez). La projection triplanaire échantillonne trois plans suivant les axes du monde et les mélange selon <code>abs(normalWorld)</code> élevé à une puissance de 4 à 8 afin d’éviter l’étirement sur les falaises. La préservation de la variance l’emporte en qualité, tandis que la solution à deux échantillons l’emporte en coût. Voir <a href="/fr/guides/landscape-generation-browser#terrain-materials">matériaux de terrain</a>.</p>
<hr>
<p>Partie 27 sur 29.
Précédent : <a href="/fr/blog/2026-05-12-open-world-browser-part-26-water">Partie 26 — Trois façons de créer de l’eau</a>
Suivant : <a href="/fr/blog/2026-05-14-open-world-browser-part-28-grass-and-occlusion">Partie 28 — De l’herbe jusqu’à l’horizon et un sol qui se dissimule</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 26 : trois façons de créer de l’eau]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-12-open-world-browser-part-26-water</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-12-open-world-browser-part-26-water</guid>
            <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 51 a créé une eau avec des réflexions en espace écran et s’est heurté à sa limite fondamentale : elle ne peut pas refléter ce que la caméra n’a jamais vu. Le spike 52 est passé à un miroir planaire, la technique utilisée par tous les jeux commercialisés, et a éliminé un bug qui faisait disparaître les reflets sombres. Le spike 53 a intégré une bibliothèque d’eau prête pour la production afin de voir à quoi ressemble une solution aboutie.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 26 : trois façons de créer de l’eau</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-10-open-world-browser-part-25-universal-characters">partie 25</a> habillait les avatars. Cette partie traite de l’eau et comporte trois spikes, car l’eau est la surface sur laquelle un raccourci bon marché et la bonne solution paraissent identiques sur une capture d’écran, mais complètement différents en mouvement. Le spike 51 construit les réflexions en espace écran, la solution tentante, et se heurte directement à sa limite intrinsèque. Le spike 52 passe à la méthode réellement utilisée par tous les jeux commercialisés. Le spike 53 intègre une bibliothèque d’eau aboutie pour mesurer la distance qui nous sépare d’une solution « terminée ». Les trois partagent la même couche de réfraction : entre les deux premiers, la seule variable qui change est donc la manière dont la réflexion est calculée.</p>
<h2>Une réflexion tirée de l’image déjà rendue</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/51-water-ssr-caustics/" title="Spike 51 : eau avec SSR" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/51-water-ssr-caustics/" target="_blank">Ouvrir le spike 51 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=51-water-ssr-caustics">Voir le code source</a></p>
<p>La réflexion en espace écran réutilise l’image déjà rendue. Pour chaque pixel de l’eau, on réfléchit le rayon de vue sur la surface, on fait progresser ce rayon réfléchi dans le tampon de profondeur et, lorsque le rayon passe derrière une surface enregistrée, on a trouvé ce que l’eau reflète, directement échantillonné depuis le tampon de couleur. Le portage suit ligne par ligne le <code>SSRNode</code> de three.js, lui-même inspiré du guide d’introduction au SSR de lettier. La progression est un parcours DDA en espace écran : on projette le début et la fin du rayon dans les coordonnées des pixels, puis on avance le long de l’axe le plus long, à raison d’un échantillon par pixel. À chaque étape, la profondeur du rayon réfléchi nécessite une interpolation corrigée pour la perspective, $\frac{1}{1/z_0 + s,(1/z_1 - 1/z_0)}$, car une interpolation linéaire de la coordonnée Z en espace vue est tout simplement incorrecte et produit des intersections au mauvais endroit.</p>
<p>Deux éléments rendent cette technique utilisable plutôt que terriblement lente. La progression grossière est limitée à 64 étapes, car un long rayon projeté sur un millier de pixels effectuerait sinon des centaines d’itérations par fragment ; avec un plan d’eau d’un million de fragments, multiplié par des centaines d’itérations et plusieurs échantillonnages de texture, la scène tomberait à 30 i/s. Le réglage de qualité contrôle le pas effectif à l’intérieur de cette limite plutôt que le nombre d’itérations. Et comme une progression grossière plafonnée laisse apparaître des bandes en escalier, un raffinement binaire de six itérations divise successivement l’intervalle entre le dernier échec et l’intersection. Il offre ainsi une précision 64 fois supérieure au pas initial, suffisante pour empêcher les fragments d’eau voisins de se verrouiller sur la même position d’intersection grossière. Un dernier contrôle de distance entre le point et la ligne confirme que le candidat se trouve réellement sur le rayon réfléchi, et pas seulement à la même profondeur. Il utilise une tolérance d’épaisseur automatiquement ajustée à la largeur en espace vue d’un pixel à cette profondeur : plus stricte de près et plus permissive au loin.</p>
<p>La partie honnête de ce spike figure dans ses propres commentaires : le SSR ne peut pas refléter ce que la caméra principale n’a jamais échantillonné. Le dessous d’un arbre, tout ce qui se trouve hors champ ou derrière un objet : rien de cela n’existe dans les tampons et ne peut donc apparaître dans la réflexion. C’est cette « perte d’informations du mauvais côté » qu’aucun réglage de qualité de la progression ne peut corriger, et c’est précisément la raison d’être du spike suivant.</p>
<h2>Le miroir qui ne peut pas mentir</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/52-water-planar/" title="Spike 52 : eau avec réflexion planaire" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/52-water-planar/" target="_blank">Ouvrir le spike 52 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=52-water-planar">Voir le code source</a></p>
<p>Une réflexion planaire rend la scène une seconde fois depuis une caméra reflétée par rapport au plan de l’eau, dans une cible hors écran que le shader de l’eau échantillonne ensuite. C’est la technique de référence, celle qu’utilisent UE5 Water, le <code>WaterMesh</code> de three.js, ABZÛ et Sea of Thieves, car elle dispose de chaque pixel de la scène, y compris de la géométrie que le SSR ne pourra jamais voir. En TSL, c’est presque déconcertant de simplicité : <code>reflector()</code> alloue la caméra auxiliaire reflétée et sa cible de rendu, on ajoute sa cible au maillage pour qu’elle soit mise à jour à chaque image, puis on échantillonne sa couleur. Lorsque les vagues seront ajoutées, la réflexion ondulera grâce à un décalage de distorsion appliqué au nœud UV du réflecteur, exactement comme dans la ligne utilisée par <code>WaterMesh</code>.</p>
<p>Le bug qui mérite d’être consigné ne se trouvait pas dans le miroir, mais dans le filet de sécurité. Une version antérieure mélangeait la sortie du réflecteur avec un ciel procédural utilisé comme solution de repli, pondérée par l’intensité de la couleur réfléchie, selon l’hypothèse qu’une réflexion proche de zéro signifiait que la cible ne contenait rien à cet endroit. Mais une ombre sombre sous la canopée possède elle aussi une faible intensité : la pondération n’atteignait donc jamais sa valeur maximale et ces pixels réellement sombres se retrouvaient mélangés au ciel lumineux. Le symptôme signalé par l’utilisateur était précis : la cible brute du miroir affichait des arbres sombres parfaits en mode débogage, alors que le rendu composé présentait des réflexions délavées. Le diagnostic était que le shader « fait disparaître les couleurs sombres ». La correction a consisté à supprimer ce mécanisme. La cible du réflecteur est fiable après la première image ; aucune solution de repli n’est donc nécessaire. Les deux spikes partagent la même réfraction sous-jacente : ils échantillonnent la scène derrière la surface, reconstruisent la profondeur de chaque pixel sous la ligne d’eau et appliquent une extinction de Beer-Lambert par canal, de sorte que le rouge disparaît en quelques mètres tandis que le bleu persiste. Un masque de ciel empêche l’arrière-plan situé au plan lointain d’être noyé dans la brume. L’effet de Fresnel de Schlick mélange la réfraction lorsque l’on regarde directement vers le fond et la réflexion lorsque l’on regarde à l’horizontale.</p>
<h2>À quoi ressemble une solution aboutie</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/53-water-pro/" title="Spike 53 : eau professionnelle" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/53-water-pro/" target="_blank">Ouvrir le spike 53 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=53-water-pro">Voir le code source</a></p>
<p>Le spike 53 sert à comparer le développement en interne à l’adoption d’une solution existante. Il intègre <code>threejs-water-pro</code> tel qu’il est distribué, avec son préréglage tropical, son clipmap océanique par défaut, son suivi de caméra et son ciel de Rayleigh, puis charge le même glTF d’île que celui utilisé dans la démo de la bibliothèque. L’objectif est de mesurer l’écart entre un shader d’eau plane artisanal et un système océanique complet, et cet écart est considérable : celui-ci possède un système de flottabilité qui échantillonne la hauteur des vagues en plusieurs points sous la coque d’un navire, afin qu’il tangue et roule au lieu de simplement monter et descendre, ainsi qu’un générateur de sillage, de l’écume sur le rivage et à la surface, et une passe de masque qui empêche l’eau d’être rendue à l’intérieur de la coque pour éviter que les ondulations ne traversent le pont.</p>
<p>Son intégration a révélé le genre de détail que l’on ne découvre qu’en utilisant une bibliothèque plutôt qu’en lisant son README. Les textures d’écume sont référencées par leur nom de fichier dans le préréglage, mais leur chargement incombe à l’utilisateur de la bibliothèque. Sans elles, le rivage apparaît comme une ligne de flottaison nette plutôt que comme des vagues qui se brisent. L’île est positionnée de manière à ce que sa géométrie sous-marine descende sous le fond de l’océan, permettant au maillage du fond fourni par la bibliothèque de masquer l’anneau extérieur du modèle sans laisser apparaître le bord du plan. C’est le mode d’utilisation prévu par la bibliothèque : fournir un modèle 3D plutôt que générer le terrain de manière synthétique. Enfin, l’anticrénelage est volontairement désactivé dans le moteur de rendu au profit d’une passe FXAA en post-traitement, car le MSAA mélange les fragments de bord avec l’arrière-plan avant l’application de la brume atmosphérique sensible à la profondeur. Cela laisse une fine frange sombre partout où la géométrie rencontre la brume. Résoudre le crénelage après la brume plutôt qu’avant supprime cette frange. Ce spike réduit surtout le risque lié à la décision elle-même : un océan de qualité production constitue un vaste système spécialisé et, lorsque nous en avons besoin, adopter une bibliothèque maintenue est préférable à reconstruire de zéro les sillages, la flottabilité et l’écume. Le shader à miroir planaire du spike 52 reste quant à lui la bonne solution pour les petites étendues d’eau intérieures qu’un créateur place dans son propre monde.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Eau avec réflexion en espace écran.</strong> Un rayon de vue réfléchi progresse dans le tampon de profondeur en espace écran au moyen d’un DDA, avec une interpolation corrigée pour la perspective en 1/z, une limite stricte du nombre d’étapes afin de plafonner le coût par fragment et une passe de raffinement binaire pour éliminer les bandes laissées par une progression plafonnée. Une vérification de la distance entre le point et la ligne, avec une épaisseur adaptée à la profondeur, rejette les fausses intersections. La limite absolue de cette méthode est qu’elle ne peut refléter que la géométrie déjà échantillonnée par la caméra principale : les surfaces hors champ ou vues du mauvais côté n’apparaissent donc jamais. Voir <a href="/fr/guides/landscape-generation-browser#terrain-materials">les matériaux de terrain</a>.</p>
<p><strong>Réflexion par miroir planaire.</strong> Une caméra auxiliaire reflétée par rapport au plan de l’eau rend la scène dans une cible hors écran que le shader de l’eau échantillonne, produisant des réflexions au pixel près, y compris pour la géométrie invisible au SSR. C’est la technique utilisée par UE5 Water et le <code>WaterMesh</code> de three.js, la distorsion des vagues étant appliquée sous forme de décalage au nœud UV du réflecteur. Une solution de repli vers le ciel, pondérée par l’intensité, effaçait à tort les pixels de réflexion sombres. La cible du réflecteur étant fiable après la première image, la suppression de cette solution de repli a corrigé le problème.</p>
<p><strong>Réfraction teintée par la profondeur selon Beer-Lambert.</strong> Les deux shaders échantillonnent la scène derrière la surface, reconstruisent la profondeur de chaque pixel d’arrière-plan sous la ligne d’eau et appliquent une extinction exponentielle par canal — le rouge disparaît en quelques mètres, tandis que le bleu persiste — composée vers une couleur de brume aquatique. Un masque de ciel empêche le plan lointain d’être noyé dans la brume. L’effet de Fresnel de Schlick mélange la réfraction à incidence normale et la réflexion aux angles rasants.</p>
<p><strong>Adoption d’une bibliothèque d’eau prête pour la production.</strong> <code>threejs-water-pro</code> fournit un clipmap océanique, un ciel de Rayleigh, une flottabilité multipoint pour le tangage et le roulis des navires, des sillages, de l’écume et une passe de masque de coque. Les détails côté utilisateur comptent : les textures d’écume doivent être chargées explicitement, la géométrie sous-marine de l’île doit descendre sous le fond de l’océan afin que celui-ci masque ses bords, et l’anticrénelage doit être appliqué sous forme de passe FXAA en post-traitement plutôt qu’avec le MSAA, afin d’éviter une frange sombre de brume sur les contours de la géométrie.</p>
<hr>
<p>Partie 26 sur 29.
Précédent : <a href="/fr/blog/2026-05-10-open-world-browser-part-25-universal-characters">Partie 25 — Un seul squelette, toutes les tenues</a>
Suivant : <a href="/fr/blog/2026-05-13-open-world-browser-part-27-island-and-terrain">Partie 27 — Une île créée à partir de bruit, un sol qui ressemble à un vrai sol</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment les moteurs modernes réduisent le suraffichage du feuillage]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-11-foliage-overdraw</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-11-foliage-overdraw</guid>
            <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Prépasses Z, imposteurs octaédriques, élimination de clusters pilotée par le GPU, alpha haché, tampons de visibilité et ombres par champs de distance : l’ensemble de techniques qui fait passer le suraffichage du feuillage de 8-15x à 1-2x dans les scènes de forêt dense.]]></description>
            <content:encoded><![CDATA[<h1>Comment les moteurs modernes réduisent le suraffichage du feuillage</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<img src="/img/blog/foliage_overdraw_hero.webp" alt="Canopée de forêt dense composée de cartes de feuilles superposées les unes derrière les autres devant un soleil couchant, suggérant un important suraffichage" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Une forêt est l’une des pires choses que l’on puisse demander à un GPU d’afficher. Chaque feuille est un quadrilatère texturé, découpé par un masque alpha. Des dizaines de ces quadrilatères se superposent le long de chaque rayon de vue. Le rastériseur n’a aucun moyen de savoir à l’avance quels fragments réussiront le test alpha, si bien que l’optimisation standard d’early-Z qui accélère les scènes opaques est en grande partie désactivée. Résultat : un seul pixel à l’écran peut exécuter le shader de feuille complet 10 ou 15 fois avant que l’image ne soit finalisée. C’est le suraffichage du feuillage, et il s’agit de la partie la plus coûteuse d’une image de monde ouvert dans tous les jeux sortis au cours de la dernière décennie.</p>
<p>La bonne nouvelle, c’est que le problème est résolu. Non pas au sens où quelqu’un aurait trouvé une astuce unique, mais parce qu’il existe un ensemble de sept ou huit techniques qui, combinées, font passer le suraffichage effectif de 8-15x dans une forêt dense à 1-2x. Tous les moteurs modernes intègrent une version de cet ensemble. Voici les techniques qui le composent, la raison d’être de chacune et leurs références incontournables.</p>
<h2>1. Pourquoi le suraffichage du feuillage est si coûteux</h2>
<p>Dans une scène opaque classique, le GPU effectue un rejet anticipé selon la profondeur : avant même l’exécution du shader de pixels, le matériel consulte le tampon de profondeur existant et ignore les fragments qui se trouvent déjà derrière quelque chose. Cette opération est pratiquement gratuite, et c’est elle qui maintient le coût des géométries denses à un niveau raisonnable.</p>
<p>Le feuillage à test alpha perturbe ce mécanisme. Le shader de fragments doit réellement s’exécuter pour évaluer la texture alpha et appeler <code>discard</code> (ou <code>clip</code>) sur les pixels exclus par le masque. Le matériel ne peut pas savoir si un fragment sera éliminé avant l’exécution du shader. Sur la plupart des GPU, l’utilisation de <code>discard</code> n’importe où dans un shader désactive donc entièrement l’early-Z pour cet appel de rendu. Sur les GPU à tuiles (mobiles, puces de série M et certaines consoles), elle peut désactiver le Hi-Z et la compression de profondeur pour toute l’image. Le shader de pixels s’exécute une fois pour chaque triangle couvrant un pixel, et la plupart de ces évaluations se terminent par un rejet.</p>
<p>Superposez 10 quadrilatères de feuilles devant un seul pixel à l’écran, et le shader de feuille s’exécutera 10 fois. Multipliez cela par 4 millions de pixels, et le coût devient énorme. L’article de Marco Salvi, <a href="https://therealmjp.github.io/posts/to-earlyz-or-not-to-earlyz/">Early-Z ou pas Early-Z</a>, constitue l’introduction la plus accessible aux raisons matérielles de ce phénomène. C’est le meilleur point de départ si vous ne vous êtes jamais penché sur le sujet.</p>
<img src="/img/blog/foliage_overdraw_layers.webp" alt="Vue latérale du rayon d’un seul pixel à l’écran traversant douze cartes de feuilles superposées, chacune étant mise en évidence pour montrer l’empilement des fragments soumis au test alpha" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<h2>2. La prépasse de profondeur pour la géométrie masquée</h2>
<p>Le gain le plus important à lui seul — et la technique intégrée à tous les moteurs modernes — consiste à diviser le rendu du feuillage en deux passes. La première passe écrit uniquement la profondeur, à l’aide d’un shader minimal qui effectue le test alpha, élimine les pixels masqués et n’écrit rien d’autre. La seconde passe affiche le matériau complet avec le test de profondeur réglé sur « égal » et l’écriture de profondeur désactivée. Chaque pixel visible ne calcule désormais la BRDF complète qu’une seule fois, quel que soit le nombre de quadrilatères de feuilles empilés derrière lui.</p>
<p>Cela peut sembler représenter deux fois plus de travail, puisque les mêmes triangles sont traités deux fois. Mais le shader de prépasse est si peu coûteux — un échantillonnage de texture, un rejet et une écriture de profondeur — que les économies réalisées lors de la passe principale dépassent largement ce surcoût. Dans une forêt dense, la passe principale n’exécute plus le shader de feuille complet 10 à 15 fois par pixel, mais exactement une seule fois.</p>
<p>Unreal, Frostbite, Decima ainsi que les branches modernes de Rage et Dunia utilisent tous cette technique. C’est également la principale raison pour laquelle les matériaux masqués restent moins coûteux que les matériaux translucides dans chacun de ces moteurs.</p>
<p>Pour approfondir :</p>
<ul>
<li>Pettineo, <a href="https://therealmjp.github.io/posts/to-earlyz-or-not-to-earlyz/">Early-Z ou pas Early-Z</a> (l’article de référence sur l’interaction de <code>discard</code> avec le Hi-Z et l’early-Z).</li>
<li>Wihlidal, <a href="http://www.frostbite.com/2016/03/optimizing-the-graphics-pipeline-with-compute/">Optimiser le pipeline graphique avec le calcul GPU</a> (GDC 2016, la prépasse de profondeur de Frostbite et son architecture d’élimination pilotée par la prépasse).</li>
<li>Sanders, <a href="https://www.gdcvault.com/play/1025066/Between-Tech-and-Art-The">Entre technologie et art : la végétation de Horizon Zero Dawn</a> (GDC 2018, avec le rendu du feuillage en deux passes de Decima).</li>
<li>Persson, <a href="http://www.humus.name/index.php?page=News&amp;ID=255">Quelques remarques sur le Z</a> (toujours l’une des explications les plus claires du test de profondeur « égal » et de la rentabilité d’une prépasse).</li>
</ul>
<h2>3. LOD agressifs et imposteurs octaédriques</h2>
<p>Le deuxième gain le plus important consiste à ne jamais afficher les feuilles lorsqu’il est possible de l’éviter. Les ressources de feuillage comportent plusieurs niveaux de LOD. Le plus proche utilise le maillage complet, avec des cartes pour chaque feuille. À moyenne distance, les feuilles sont regroupées dans des cartes composites plus denses : un bouquet de 30 feuilles devient une seule carte texturée présentant la même silhouette. Au-delà d’un certain seuil de distance, l’arbre entier devient un <em>imposteur</em> : un petit élément géométrique texturé avec des vues de l’arbre précalculées sous plusieurs angles.</p>
<p>Le format d’imposteur moderne est l’<strong>imposteur octaédrique</strong> : un élément géométrique à 8 faces, texturé avec un atlas de vues capturées depuis des points répartis sur une sphère au moyen d’une projection octaédrique. À l’exécution, le shader sélectionne les deux ou trois vues précalculées les plus proches selon la direction de la caméra, puis effectue une interpolation entre elles. On obtient ainsi un substitut composé de quelques triangles qui paraît tridimensionnel sous n’importe quel angle et peut bénéficier d’un éclairage correct, de normal maps et même d’une animation par le vent. L’implémentation de Ryan Brucks, initialement publiée sous forme de plugin communautaire et désormais intégrée à Unreal, fait référence. Les forêts comptant des milliards d’arbres de Microsoft Flight Simulator sont essentiellement constituées d’imposteurs octaédriques partout, sauf à proximité de la caméra.</p>
<p>Le gain structurel le plus important est qu’à distance, les imposteurs sont <em>opaques ou presque opaques</em>. La carte composite de 30 feuilles est un seul quadrilatère masqué au lieu de 30. L’imposteur représentant l’arbre entier ne comporte que quelques faces, au lieu de milliers. Le suraffichage à longue distance devient presque nul.</p>
<p>Pour approfondir :</p>
<ul>
<li>Brucks, <a href="https://shaderbits.com/blog/octahedral-impostors">Imposteurs octaédriques</a> (la référence incontournable, avec les calculs et l’implémentation dans UE).</li>
<li>Halen, <a href="https://dev.epicgames.com/community/learning/tutorials/JmL3/octahedral-impostors-unreal-engine">Imposteurs octaédriques dans Unreal Engine</a> (le processus de travail intégré à UE).</li>
<li>Häggström, <a href="https://www.diva-portal.org/smash/get/diva2:1135856/FULLTEXT01.pdf">Rendu de végétation en temps réel</a> (une thèse claire couvrant les chaînes de LOD, les imposteurs et les calculs sur lesquels ils reposent).</li>
<li>Crytek, <a href="http://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-4-next-generation-speedtree-rendering">Intégration de SpeedTree dans CryEngine 3</a> (GPU Gems 3, toujours la meilleure introduction aux chaînes de LOD des arbres).</li>
</ul>
<h2>4. Élimination par clusters pilotée par le GPU</h2>
<p>Même avec une prépasse de profondeur, celle-ci a un coût : elle doit toujours <em>traiter</em> chaque triangle de chaque arbre visible ou potentiellement visible. Les moteurs modernes réduisent ce coût grâce à l’élimination de clusters pilotée par le GPU, qui écarte des groupes entiers de triangles avant même qu’ils n’atteignent le rastériseur.</p>
<p>Le pipeline fonctionne ainsi : chaque maillage est préalablement divisé en clusters de 64 ou 128 triangles, chacun doté d’une boîte englobante serrée et d’un cône de normales. Au moment du rendu, un compute shader parcourt la liste des instances, teste chaque instance par rapport au frustum, puis chaque cluster de chaque instance visible, avant de soumettre chaque cluster restant à un <em>test d’occlusion Hi-Z</em> fondé sur la pyramide de profondeur de l’image précédente. Des branches entières cachées derrière une colline ou masquées par un autre arbre sont éliminées avant même l’exécution du moindre shader de sommets. Le résultat est une liste compacte d’arguments « afficher ces clusters », transmise directement à un unique appel <code>DrawIndirect</code>.</p>
<p>C’est ce qui permet d’afficher une forêt de 10 000 arbres en quelques millisecondes plutôt qu’en quelques secondes. La présentation d’Ubisoft sur Assassin’s Creed Unity a introduit ce pipeline sous une forme exploitable en production : 20 à 40 % des triangles éliminés, 30 à 80 % des triangles d’ombre écartés et 10 fois plus d’instances à l’écran que sur la génération précédente. La présentation de Wihlidal sur Frostbite est allée encore plus loin. Nanite dans UE5 est l’aboutissement visible de cette évolution : une élimination par clusters jusqu’au niveau du pixel.</p>
<p>Pour approfondir :</p>
<ul>
<li>Haar et Aaltonen, <a href="https://advances.realtimerendering.com/s2015/aaltonenhaar_siggraph2015_combined_final_footer_220dpi.pdf">Pipelines de rendu pilotés par le GPU</a> (SIGGRAPH 2015, la présentation fondatrice sur Assassin’s Creed Unity).</li>
<li>Wihlidal, <a href="http://www.frostbite.com/2016/03/optimizing-the-graphics-pipeline-with-compute/">Optimiser le pipeline graphique avec le calcul GPU</a> (GDC 2016, la prépasse de Frostbite pilotée par le GPU).</li>
<li>Karis, Stubbe, Wihlidal, <a href="https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf">Étude approfondie de la géométrie virtualisée Nanite</a> (SIGGRAPH 2021, élimination par clusters à la granularité des meshlets).</li>
<li>Liktor, <a href="https://research.activision.com/publications/2021/09/geometry-rendering-pipeline-architecture-at-activision">Architecture du pipeline de rendu géométrique chez Activision</a> (la version de l’élimination par clusters utilisée par Call of Duty, 2021).</li>
</ul>
<h2>5. Tri des instances et des clusters de l’avant vers l’arrière</h2>
<p>Une fois que la prépasse remplit son rôle, l’<em>ordre</em> commence à compter. La prépasse écrit la profondeur, mais uniquement pour les fragments qui réussissent le test alpha. Si vous affichez d’abord l’arrière de la forêt et terminez par l’avant, chaque fragment de premier plan écrase un fragment d’arrière-plan, tandis que le shader de prépasse s’exécute quand même pour ce dernier. Si vous procédez de l’avant vers l’arrière, chaque appel de rendu successif remplit davantage le tampon de profondeur avec des valeurs plus petites, et le Hi-Z rejette de plus en plus de fragments d’arrière-plan avant l’exécution du shader.</p>
<p>C’est pourquoi presque tous les moteurs modernes trient les instances de feuillage selon leur distance à la caméra avant de lancer la prépasse. Le tri est peu coûteux — quelques centaines de milliers d’instances sur le GPU au moyen d’un tri radix — et transforme la prépasse elle-même en opération d’auto-élimination. Pour la même raison, l’élimination au niveau des clusters effectue le tri à la granularité des meshlets. La prépasse de profondeur et l’ordonnancement de l’avant vers l’arrière forment le genre de duo où chaque technique est efficace séparément, mais où leur combinaison l’est encore davantage.</p>
<p>Pour approfondir :</p>
<ul>
<li>Persson, <a href="http://www.humus.name/Articles/Persson_DepthInDepth.pdf">La profondeur en profondeur</a> (notes architecturales sur l’ordonnancement du tampon de profondeur, la rentabilité d’une prépasse et le comportement du Hi-Z).</li>
<li>Giesen, <a href="https://fgiesen.wordpress.com/2011/07/04/a-trip-through-the-graphics-pipeline-2011-part-7/">Voyage au cœur du pipeline graphique</a> (explication technique détaillée de l’influence de l’ordre de rendu sur le taux de rejet du Hi-Z).</li>
<li>Wihlidal, <a href="http://www.frostbite.com/2016/03/optimizing-the-graphics-pipeline-with-compute/">Optimiser le pipeline graphique avec le calcul GPU</a> (GDC 2016, avec notamment le tri des instances sur GPU de Frostbite).</li>
</ul>
<h2>6. Transitions de LOD tramées et alpha haché</h2>
<p>L’autre grand piège est le fondu. La méthode naïve pour effectuer une transition entre deux LOD — ou faire apparaître ou disparaître progressivement une instance à mesure que la caméra s’en approche — consiste à utiliser le mélange alpha. Mais une géométrie mélangée ne peut pas écrire dans le tampon de profondeur, ce qui fait basculer chaque arbre en transition vers le lent pipeline translucide et rend la prépasse inopérante. La solution consiste à maintenir la géométrie dans le pipeline masqué et à effectuer le fondu <em>au sein même du test alpha</em>.</p>
<p>Deux techniques principales :</p>
<ul>
<li><strong>Les transitions de LOD tramées</strong> échantillonnent une matrice de Bayer 4x4 ou 8x8 — ou une texture de bruit bleu en espace écran — et l’utilisent comme modificateur de seuil par pixel. Un arbre à 50 % de transition conserve un damier de pixels ; les pixels manquants sont remplis par le damier complémentaire du LOD suivant. Le TAA lisse le damier en une transition fluide sur deux ou trois images. La technique est peu coûteuse, stable et s’intègre à tout le reste du moteur.</li>
<li><strong>Le test alpha haché</strong> (Wyman et McGuire, I3D 2017) remplace le seuil alpha fixe de 0,5 par un seuil haché par pixel appartenant à [0,1). Les géométries alpha éloignées qui disparaîtraient normalement entièrement — parce que leur alpha après mipmapping passe sous 0,5 — conservent une dispersion stable de pixels visibles. Là encore, le TAA se charge du nettoyage.</li>
</ul>
<p>Ces deux techniques maintiennent le feuillage dans le pipeline opaque/masqué où la prépasse de profondeur fonctionne. Vous évitez ainsi de payer le coût complet du rendu translucide simplement pour faire apparaître progressivement un élément. L’<strong>alpha-to-coverage</strong> est l’équivalent de cette idée à l’époque du MSAA : convertir l’alpha en masque de couverture sous-pixel afin d’obtenir une transparence partielle sans quitter le pipeline masqué. Son inconvénient est que l’A2C n’est réellement efficace qu’avec le MSAA, que la plupart des moteurs modernes à rendu différé n’utilisent plus.</p>
<p>Pour approfondir :</p>
<ul>
<li>Wyman et McGuire, <a href="https://research.nvidia.com/labs/rtr/publication/wyman2017hashed/">Test alpha haché</a> (I3D 2017, l’article de référence sur l’alpha haché).</li>
<li>Castaño, <a href="http://the-witness.net/news/2010/09/computing-alpha-mipmaps/">Calcul des mipmaps alpha</a> (blog de The Witness, la bonne méthode pour générer les mipmaps des textures à test alpha afin d’éviter la disparition des arbres éloignés).</li>
<li>Yuksel, <a href="https://cemyuksel.com/research/alphadistribution/alpha_distribution.pdf">Distribution alpha pour le test alpha</a> (une amélioration plus récente des mipmaps alpha).</li>
<li>NVIDIA, <a href="https://developer.nvidia.com/content/transparency-or-translucency-rendering">Test alpha anticrénelé</a> (une vue d’ensemble de l’A2C, de l’alpha haché et des solutions tramées).</li>
</ul>
<h2>7. Réduction du coût d’ombrage des pixels masqués</h2>
<p>Même avec une prépasse parfaite et une élimination parfaite, chaque pixel visible du feuillage doit toujours être ombré une fois. Les moteurs réduisent également ce coût :</p>
<ul>
<li><strong>BRDF moins coûteuse</strong>. Le feuillage est mat et n’a pas vraiment besoin d’un chemin spéculaire Cook-Torrance complet. Une diffuse de Lambert enveloppée et une approximation spéculaire en une ligne suffisent amplement.</li>
<li><strong>Normal maps à plus basse fréquence</strong>. Les feuilles sont déjà visuellement chargées. Une normal map de 256x256 produit le même rendu qu’une version de 1024x1024 aux distances d’observation habituelles, tout en économisant de la bande passante.</li>
<li><strong>Ni parallaxe, ni anisotropie, ni vernis</strong>. Les fonctionnalités PBR correspondantes sont désactivées pour les feuilles.</li>
<li><strong>Transmission mince double face</strong> au lieu d’une diffusion sous-surfacique complète. Les feuilles transmettent la lumière venant de derrière, mais on peut la simuler avec un simple produit scalaire d’éclairage arrière.</li>
<li><strong>Ombrage en demi-résolution</strong> dans certains moteurs. Le feuillage est ombré à 1/4 ou 1/2 du taux de pixels, puis mis à l’échelle. Le bruit stochastique du TAA masque le rééchantillonnage.</li>
<li><strong>Pas de textures de détail ni de décalcomanies</strong> par défaut sur les matériaux masqués.</li>
</ul>
<p>Chacune de ces optimisations apporte individuellement un petit gain. Ensemble, elles permettent aux shaders de feuillage masqué de s’exécuter 2 à 3 fois plus vite que le matériau opaque équivalent.</p>
<p>Pour approfondir :</p>
<ul>
<li>Lagarde et de Rousiers, <a href="https://seblagarde.wordpress.com/2015/07/14/siggraph-2014-moving-frostbite-to-physically-based-rendering/">Passage de Frostbite au rendu physiquement réaliste 3.0, sections sur le feuillage et la translucidité</a> (SIGGRAPH 2014, les ajustements PBR de référence pour les matériaux minces double face).</li>
<li>Jimenez, <a href="https://www.iryoku.com/stare-into-the-future">Rendu de personnages de nouvelle génération</a> (les calculs de Lambert enveloppé et de translucidité arrière, conçus à l’origine pour la peau, mais largement adaptés aux feuilles).</li>
<li>Sanders, <a href="https://www.gdcvault.com/play/1025066/Between-Tech-and-Art-The">Entre technique et art : la végétation de Horizon Zero Dawn</a> (GDC 2018, avec les simplifications apportées au shader de feuillage de Decima).</li>
</ul>
<h2>8. Buffers de visibilité et Nanite pour les matériaux masqués</h2>
<p>La solution la plus propre au suraffichage consiste à <em>découpler entièrement l’ombrage de la rastérisation</em>. Un buffer de visibilité rastérise la géométrie dans un buffer compact (uniquement l’identifiant du triangle et celui de l’instance pour chaque pixel), puis exécute le matériau complet dans une passe différée qui lit ce buffer et n’ombre chaque pixel qu’une seule fois. Par construction, il n’y a aucun suraffichage à l’étape d’ombrage. L’article de Burns et Hunt paru en 2013 a introduit cette technique ; Nanite d’UE5 en est l’implémentation prête pour la production, y compris pour le feuillage masqué depuis UE 5.5.</p>
<p>La particularité de Nanite est d’y parvenir grâce à une géométrie virtualisée au niveau des clusters : le rastériseur utilise donc lui-même un chemin logiciel pour les triangles plus petits qu’un pixel et maintient le suraffichage sous contrôle. Dans Nanite, les matériaux masqués ont besoin de la fonctionnalité « programmable raster » : le test alpha s’exécute pendant la passe du buffer de visibilité, mais l’ombrage du matériau n’a toujours lieu qu’une seule fois par pixel visible lors de la résolution différée. Ainsi, le feuillage Nanite très dense, autrefois réputé lent avec les matériaux masqués, rivalise désormais avec les matériaux opaques, voire coûte moins cher, car le suraffichage est nul au moment de l’ombrage. Il y a toutefois un compromis : il est souvent préférable de faire traiter par Nanite une géométrie d’arbre <em>opaque</em> et très détaillée plutôt que de conserver des arbres peu détaillés composés de cartes masquées, car le chemin masqué ajoute le coût de la rastérisation programmable.</p>
<p>Pour approfondir :</p>
<ul>
<li>Burns et Hunt, <a href="http://jcgt.org/published/0002/02/04/">Le buffer de visibilité : une approche de l’ombrage différé optimisée pour le cache</a> (JCGT 2013, l’article fondateur).</li>
<li>Karis, Stubbe, Wihlidal, <a href="https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf">Analyse approfondie de la géométrie virtualisée de Nanite</a> (SIGGRAPH 2021, l’architecture de production).</li>
<li>Epic, <a href="https://media.gdcvault.com/gdc2024/Slides/GDC+slide+presentations/Nanite+GPU+Driven+Materials.pdf">Matériaux Nanite pilotés par le GPU</a> (GDC 2024, avec le pipeline des matériaux masqués et de la rastérisation programmable).</li>
<li><a href="https://www.sctheblog.com/blog/nanite-materials-notes/">Notes sur « Matériaux Nanite pilotés par le GPU »</a> (une présentation claire par un tiers de cette même conférence).</li>
</ul>
<h2>9. Représentations d’ombres distinctes pour le feuillage</h2>
<p>Les shadow maps du feuillage avec test alpha constituent le deuxième problème d’ombres le plus coûteux dans une frame de monde ouvert, juste après les grandes cascades. Chaque cascade nécessite sa propre prépasse de profondeur, chaque prépasse exécute le test alpha, et le coût s’accumule rapidement avec 4 cascades et des dizaines de frustums lumineux. La plupart des moteurs ne rendent donc pas les ombres du feuillage de la même manière que sa couleur.</p>
<p>Substitutions courantes :</p>
<ul>
<li><strong>Ombres par champs de distance de maillage</strong> (UE Lumen, moteurs personnalisés). Chaque maillage possède un champ de distance signé précalculé. Un court lancer de cône à travers le SDF produit une ombre douce sans jamais toucher au maillage avec test alpha. Cette technique convient particulièrement bien aux arbres, car le SDF représente la silhouette de la canopée comme une seule masse solide et ignore les détails de chaque feuille.</li>
<li><strong>Cascades de plus basse résolution pour le feuillage</strong>. Les ombres du feuillage sont rendues dans une tranche en demi-résolution, puis agrandies avec un filtrage adaptatif aux contours. L’œil ne remarque pas la baisse de résolution, car les ombres sont déjà douces.</li>
<li><strong>Shadow maps avec conversion alpha-vers-couverture et MSAA</strong>. Dans les moteurs qui conservent le MSAA dans leur chemin d’ombres, l’A2C produit des ombres de feuillage aux contours lisses sans supporter le coût complet du test alpha.</li>
<li><strong>Ombres par capsules pour le tronc et les grosses branches</strong>, champ de distance pour la canopée, test alpha complet uniquement dans la cascade la plus proche. Différentes représentations sont utilisées selon la distance, puis fusionnées dans la passe d’éclairage.</li>
<li><strong>Distance de désactivation du WPO</strong>. Le World Position Offset piloté par le vent est désactivé au-delà d’un certain seuil afin que les données d’ombre mises en cache restent valides d’une frame à l’autre. Les Virtual Shadow Maps d’UE reposent largement sur cette technique.</li>
</ul>
<p>Pour approfondir :</p>
<ul>
<li>Epic, <a href="https://dev.epicgames.com/documentation/unreal-engine/distance-field-soft-shadows-in-unreal-engine">Ombres douces par champs de distance dans Unreal Engine</a> (la référence d’UE pour les ombres par champs de distance de maillage).</li>
<li>Wright, <a href="https://advances.realtimerendering.com/s2022/SIGGRAPH2022-Advances-Lumen-Wright%20et%20al.pdf">Lumen : illumination globale en temps réel dans Unreal Engine 5</a> (SIGGRAPH 2022, avec l’intégration des SDF de maillage de Lumen pour le feuillage).</li>
<li>Epic, <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/virtual-shadow-maps-in-unreal-engine">Virtual Shadow Maps</a> (l’architecture moderne des ombres d’UE5, avec les règles propres au feuillage pour la désactivation du WPO et la mise en cache).</li>
<li>Persson, <a href="https://www.humus.name/index.php?page=3D&amp;ID=81">Shadow maps en cascade dans la pratique</a> (la référence toujours incontournable sur les CSM, avec des remarques sur les objets projetant des ombres avec test alpha).</li>
</ul>
<h2>10. Vent, animation et mise en cache des ombres</h2>
<p>Un problème connexe plus subtil : la plupart des feuillages bougent. L’animation des sommets pilotée par le vent (World Position Offset dans UE, ou son équivalent dans Frostbite et Decima) rend la géométrie du feuillage instable d’une frame à l’autre, ce qui compromet la mise en cache et la reprojection des ombres. Les moteurs modernes luttent contre ce phénomène de deux façons :</p>
<ul>
<li><strong>Limiter le WPO à distance</strong>. Au-delà d’un certain seuil, l’amplitude de l’animation due au vent diminue progressivement jusqu’à zéro. De toute façon, l’œil ne distingue pas ce mouvement à une telle distance, et les caches d’ombres restent valides.</li>
<li><strong>Intégrer le vent aux volumes englobants des clusters</strong>. Les boîtes englobantes des clusters sont agrandies en fonction du décalage WPO maximal, de façon à conserver un culling prudent sans devoir les renvoyer à chaque frame.</li>
<li><strong>Décalages de phase par instance</strong>. Les arbres identiques utilisent une graine aléatoire propre à chaque instance pour décaler la phase du vent. Ainsi, toute la forêt ne se balance pas à l’unisson, sans qu’il soit nécessaire de calculer une animation unique pour chaque arbre.</li>
</ul>
<p>C’est le genre de détail qui n’apparaît pas dans les listes de techniques, mais qui, en pratique, fait toute la différence entre une forêt à 3 ms et une forêt à 9 ms.</p>
<p>Pour approfondir :</p>
<ul>
<li>Sanders, <a href="https://www.gdcvault.com/play/1025066/Between-Tech-and-Art-The">Entre technique et art : la végétation de Horizon Zero Dawn</a> (GDC 2018, avec le pipeline d’animation par le vent et la mise en cache des ombres de Decima).</li>
<li>McAuley, <a href="https://www.gdcvault.com/play/1022235/Rendering-the-World-of-Far">Le rendu du monde de Far Cry 4</a> (GDC 2015, avec notamment l’échantillonnage de la grille de vent pour la végétation).</li>
<li>Epic, <a href="https://dev.epicgames.com/community/learning/tutorials/qLk6/unreal-engine-virtual-shadow-maps">Feuillage et Virtual Shadow Maps</a> (les recommandations de la communauté UE5 sur la distance de désactivation du WPO et la mise en cache des VSM).</li>
</ul>
<h2>11. Le calcul combiné</h2>
<p>Aucune de ces techniques n’est une solution miracle. Ce qui devient intéressant, c’est ce qui se produit lorsqu’on les cumule :</p>
<ul>
<li>Une passe naïve de feuillage masqué dans une scène de forêt dense affiche un suraffichage effectif de 8 à 15x. Chaque pixel visible exécute le shader de feuille entre 8 et 15 fois.</li>
<li>L’ajout d’une prépasse de profondeur ramène le suraffichage de la passe principale à environ 1x, mais la prépasse elle-même parcourt encore tout.</li>
<li>Avec un tri de l’avant vers l’arrière, la prépasse commence à s’élaguer elle-même.</li>
<li>Avec un culling GPU au niveau des clusters, la prépasse ne traite plus que ce qui pourrait réellement être visible.</li>
<li>Avec des chaînes de LOD et des imposteurs, le <em>nombre</em> de quadrilatères visibles chute d’un ordre de grandeur au-delà de 30 m.</li>
<li>Avec le chemin buffer de visibilité / Nanite, l’étape d’ombrage ne s’exécute réellement qu’une fois par pixel, même en cas de chevauchement dense.</li>
<li>Avec les ombres par champs de distance, le coût des ombres cesse d’évoluer avec le test alpha.</li>
</ul>
<p>Le résultat principal, répété dans les conférences citées ci-dessus : le suraffichage effectif s’effondre de 8–15x à 1–2x, et le coût total du feuillage par frame est divisé par 4 à 6 dans une scène de forêt dense. C’est précisément ce qui permet aux jeux modernes en monde ouvert d’afficher des forêts à plus de 60 FPS sur du matériel grand public.</p>
<img src="/img/blog/foliage_overdraw_pipeline.webp" alt="Schéma montrant le pipeline de rendu du feuillage sous la forme de cinq étapes empilées : culling GPU, prépasse triée, passe de couleur masquée avec profondeur égale, ombrage par buffer de visibilité et chemin d’ombres distinct par champs de distance" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<h2>12. Ce que cela signifie pour le navigateur</h2>
<p>L’essentiel de cette architecture se transpose sans difficulté à WebGPU. Nous avons déjà déployé le culling piloté par le GPU, le dispatch indirect, l’occlusion Hi-Z et les prépasses triées de l’avant vers l’arrière dans le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">moteur de monde ouvert pour navigateur</a>. La prépasse de profondeur pour la géométrie masquée est simple à mettre en œuvre : WebGPU prend en charge le test de profondeur « égal » et <code>discard</code> dans les fragment shaders, avec les mêmes réserves concernant l’early-Z. Les imposteurs octaédriques se portent mécaniquement : les calculs se résument à déplier une sphère sur un octaèdre et à indexer un atlas.</p>
<p>Les éléments les plus difficiles sont les plus modernes. Un buffer de visibilité dans WebGPU implique d’écrire un identifiant de triangle 32 bits dans une cible de rendu, puis de résoudre les matériaux dans une passe de calcul plein écran ; les briques nécessaires existent, mais leur orchestration est complexe. Les ombres par champs de distance de maillage nécessitent une texture 3D par ressource et un court lancer de cône, deux opérations à la portée de WebGPU. L’alpha haché et les LOD tramés ne demandent chacun qu’une fonction de shader.</p>
<p>La voie à suivre pour le feuillage dans les navigateurs est la même que pour tout le reste de cette architecture : déployer d’abord les composants peu coûteux et robustes (prépasse, instances triées, LOD, imposteurs, alpha haché), puis ajouter les mécanismes plus lourds (buffer de visibilité, ombres par SDF de maillage). Le niveau matériel minimal des navigateurs est enfin assez élevé pour qu’aucune raison architecturale n’empêche une forêt WebGPU d’offrir le même rendu et les mêmes performances que sur console. Il ne reste que des problèmes d’ingénierie, et ce sont précisément ceux que nous aimons résoudre.</p>
<h2>Pour approfondir l’ensemble de l’architecture</h2>
<p>Si vous cherchez une source unique qui rassemble tous ces sujets, les archives SIGGRAPH « Advances in Real-Time Rendering in Games » (<a href="https://advances.realtimerendering.com/">advances.realtimerendering.com</a>) regroupent les conférences de référence sur le feuillage et le rendu piloté par le GPU depuis 2014. Les <a href="http://www.adriancourreges.com/blog/">articles d’Adrian Courrèges sur le profilage GPU</a> proposent des analyses frame par frame de GTA V et Horizon Zero Dawn qui montrent chaque passe abordée ici dans son ordre de production. Pour les calculs propres au test alpha, la <a href="https://cwyman.org/">page de recherche de Chris Wyman</a> présente les articles consacrés à l’alpha haché et à la transparence stochastique, avec des shaders de référence. Enfin, les chapitres de <a href="https://www.realtimerendering.com/">Real-Time Rendering, 4e édition</a> consacrés à la transparence, à l’échantillonnage et à la gestion de la profondeur restent le meilleur point de départ théorique.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 25 : un seul squelette pour toutes les tenues]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-10-open-world-browser-part-25-universal-characters</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-10-open-world-browser-part-25-universal-characters</guid>
            <pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le Spike 50 a évalué si un pack de personnages CC0 pouvait servir de base à un produit de personnalisation d’avatars. Il s’est avéré que quatre packs de Quaternius partageaient délibérément le même squelette à 65 articulations, transformant ainsi le spike : au lieu d’un problème de pipeline d’animation, il suffisait de réassocier les squelettes par nom.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 25 : un seul squelette pour toutes les tenues</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et fournit les liens vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-09-open-world-browser-part-24-persistence-and-wind">partie 24</a> a rendu le monde persistant et donné du mouvement à sa végétation grâce au vent. Cette partie revient aux personnages, mais sous l’angle de leur garde-robe : un créateur peut-il choisir un corps de base, changer de vêtements, sélectionner une coiffure et lancer n’importe quelle animation, le tout à partir d’un pack de ressources gratuit, sans aucun travail de rigging ni création d’animations ? Construire un rig personnalisé n’est pas le bon risque à éliminer. La vraie question est de savoir si un pack CC0 peut répondre à l’ensemble des besoins.</p>
<h2>Un pack conçu pour fonctionner comme un tout</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/50-universal-characters/" title="Spike 50 sur les personnages universels" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/50-universal-characters/" target="_blank">Ouvrir le Spike 50 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=50-universal-characters">Voir le code source</a></p>
<p>Quaternius propose quatre ressources dont la compatibilité semble presque trop parfaite : Universal Base Characters, avec des corps masculins et féminins ainsi que huit coiffures et deux jeux de sourcils ; Modular Character Outfits, réparti en éléments pour le corps, les bras, les jambes et les pieds, avec des accessoires tels que des spallières et une capuche ; et les packs un et deux d’Universal Animation Library, soit 262 clips au total couvrant les déplacements, le combat, l’escalade, l’agriculture, la pêche, les poses d’attente et les interactions sociales. L’examen de leurs fichiers glTF a révélé la raison de cette compatibilité : tous les maillages des quatre packs sont riggés sur le même squelette de style UE5 à 65 articulations, avec les mêmes noms d’articulations et les mêmes poses de liaison, de la racine au bassin puis de la colonne vertébrale à la tête, des bras symétriques comportant quinze articulations de doigts de chaque côté et des jambes symétriques. Aucun retargeting n’est nécessaire. C’est l’exact opposé du problème de retargeting entre trois squelettes rencontré dans le Spike 35, où un rig facial à 213 os devait communiquer avec un rig Mixamo à 65 os. Ici, les quatre packs ont été volontairement créés sur le squelette de la bibliothèque d’animations, et leur correspondance est exacte.</p>
<p>Le spike est donc devenu un exercice de liaison plutôt qu’un problème de pipeline d’animation. La couche de base charge le glTF d’un corps, clone la scène avec <code>SkeletonUtils.clone</code> afin que chaque personnage dispose de sa propre instance de squelette au lieu de répercuter les modifications des os dans la scène mise en cache par le chargeur, puis met en cache ce squelette à 65 os comme squelette canonique, avec l’objet d’armature servant de racine à l’animation.</p>
<h2>Réassocier un manteau à un corps</h2>
<p>L’astuce qui permet à l’ensemble de fonctionner tient au fait qu’il est impossible de simplement reparenter le maillage d’une tenue dans la scène : il référence encore les os de son propre glTF, placés dans une autre sous-arborescence. Chaque élément modulaire, qu’il s’agisse du corps d’un paysan ou des jambes d’un rôdeur, arrive sous la forme de son propre SkinnedMesh, avec son propre clone de la même armature. La liaison parcourt les os du squelette source dans leur ordre d’origine, recherche chacun d’eux par son nom dans le squelette canonique, puis construit un nouveau <code>Skeleton</code> à partir de ces os réassociés en réutilisant les matrices inverses de liaison de la source. Elle appelle ensuite <code>SkinnedMesh.bind</code> avec la matrice de liaison identité de la source. Après la liaison, le maillage est détaché de sa scène source et directement rattaché à la racine du personnage. Comme la matrice de liaison capture la transformation globale au moment de la liaison et que la racine reste à l’identité, la transformation globale propre au maillage n’affecte jamais le résultat : le skinning s’effectue entièrement à travers les os désormais partagés.</p>
<p>Les cheveux et les sourcils occupent eux aussi des emplacements, et ils ont permis de corriger une hypothèse erronée. La structure des dossiers indiquait « rigged to head bone », ce qui laissait penser que les cheveux étaient un maillage statique rattaché à l’os de la tête avec un décalage précalculé. Ce n’est pas le cas. Chaque fichier de coiffure contient un SkinnedMesh pondéré sur les 65 articulations, les poids des cheveux longs s’étendant jusqu’au haut de la colonne vertébrale et au cou afin qu’ils retombent lorsque le personnage baisse la tête. Les cheveux suivent donc le même processus de réassociation par nom que n’importe quel élément de tenue. Le seul cas particulier est l’emplacement du corps de la tenue : lorsqu’il est utilisé, le maillage du corps de base est masqué afin d’éviter que la tenue ne le traverse, tandis que les bras, les jambes, les pieds et les accessoires se superposent simplement, car le pack a été conçu pour qu’ils coexistent avec le corps de base, dont la peau reste visible partout où les vêtements ne la couvrent pas.</p>
<h2>262 animations gratuites</h2>
<p>Une fois le squelette partagé, l’animation devient presque anecdotique. Les deux fichiers glb des bibliothèques sont chargés, leurs clips sont regroupés dans une seule liste nommée, et les noms de pistes de ces clips font référence aux noms exacts des articulations via <code>bone.position</code>, <code>bone.quaternion</code> et <code>bone.scale</code>. Un <code>AnimationMixer</code> ayant pour racine l’armature du personnage trouve directement ces os par leur nom et les anime, avec un fondu enchaîné lors de chaque changement. Le panneau droit de la visionneuse contient une liste filtrable des 262 clips, chacun accompagné d’une pastille indiquant sa source : une recherche sur « fish » affiche ainsi toutes les animations de pêche des deux bibliothèques. Un éclairage trois points classique avec des ombres douces, une grille polaire sous les pieds et un tone mapping ACES préservent la lisibilité des textures de base stylisées sans écraser les ombres de la combinaison sombre.</p>
<p>La réduction des risques pour le produit est considérable. La fonctionnalité de personnalisation d’avatars peut être livrée à partir d’un pack CC0 sans aucun travail de rigging ni création d’animations : il suffit d’intégrer les quatre packs et de connecter une interface de garde-robe pour disposer immédiatement d’environ six tenues, multipliées par huit coiffures, deux sexes et 262 animations. Le squelette à 65 articulations est suffisamment petit pour que le coût du skinning GPU soit négligeable, et le travail sur le skinning par lots réalisé dans le Spike 45 prend déjà en charge plus de 200 avatars avec un rig de cette taille. La licence CC0 n’impose aucune restriction d’utilisation, ce qui autorise l’usage commercial sur une plateforme payante destinée aux créateurs. La question encore ouverte concerne les tenues importées par les utilisateurs. Il s’agit d’un problème de phase 3, qui sera résolu en effectuant, au moment de l’importation, un retargeting vers ce squelette canonique, soit l’inverse du retargeting à l’exécution du Spike 35. Les ressources occupent environ 147 Mo sur disque sous la forme de PNG 2K bruts et de fichiers glb d’animation non compressés. En production, ce volume serait ramené à environ 30 Mo grâce à la compression de textures KTX2 et à un passage dans gltf-transform.</p>
<h2>Technologies présentées dans ce chapitre</h2>
<p><strong>Un squelette canonique unique pour tout un pack.</strong> Les quatre packs de Quaternius — corps de base, tenues modulaires et deux bibliothèques d’animations — sont tous riggés sur un squelette identique de style UE5 à 65 articulations, avec les mêmes noms d’articulations et les mêmes poses de liaison. Leur combinaison ne nécessite donc aucun retargeting. Le clonage de la base avec <code>SkeletonUtils.clone</code> attribue à chaque personnage sa propre instance de squelette au lieu de modifier la scène mise en cache par le chargeur.</p>
<p><strong>Réassociation des squelettes par nom.</strong> Chaque élément modulaire fournit son propre SkinnedMesh référençant sa propre armature. La réassociation parcourt les os sources dans l’ordre, associe chacun d’eux par son nom au squelette canonique, construit un nouveau <code>Skeleton</code> en réutilisant les matrices inverses de liaison de la source, puis appelle <code>SkinnedMesh.bind</code> avec la matrice de liaison identité. Le maillage est ensuite rattaché à la racine partagée du personnage afin que le skinning s’effectue entièrement à travers les os partagés. L’emplacement du corps de la tenue masque le corps de base pour éviter les intersections ; les autres emplacements se superposent.</p>
<p><strong>Les cheveux comme emplacement skinné.</strong> Les cheveux ne sont pas un élément statique attaché à l’os de la tête, mais un SkinnedMesh pondéré sur les 65 articulations. Les cheveux longs sont également pondérés sur le haut de la colonne vertébrale et le cou, de façon à retomber lorsque le personnage baisse la tête. Ils suivent le même processus de réassociation que les éléments de tenue.</p>
<p><strong>Retargeting gratuit des animations indexées par nom.</strong> Les clips de l’Animation Library référencent les articulations par leur nom exact. Un <code>AnimationMixer</code> ayant pour racine l’armature partagée peut donc exécuter directement les 262 clips avec des fondus enchaînés, sans retargeting propre à chaque clip. La licence CC0 couvre l’usage commercial, et le faible nombre d’articulations maintient le coût du skinning GPU à un niveau suffisamment bas pour réutiliser le pipeline de skinning par lots du Spike 45. Voir le <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD piloté par le GPU</a>.</p>
<hr>
<p>Partie 25 sur 29.
Précédent : <a href="/fr/blog/2026-05-09-open-world-browser-part-24-persistence-and-wind">Partie 24 - Sauvegarder un monde et rendre le vent visible</a>
Suivant : <a href="/fr/blog/2026-05-12-open-world-browser-part-26-water">Partie 26 - Une eau convaincante à toutes les échelles</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 24 : sauvegarder un monde et rendre le vent visible]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-09-open-world-browser-part-24-persistence-and-wind</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-09-open-world-browser-part-24-persistence-and-wind</guid>
            <pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le Spike 47 a rendu les modifications du monde persistantes : un Durable Object arbitre la propriété, conserve des blobs binaires par chunk et fait converger le terrain de chaque pair grâce à un protocole de modification commutatif. Le Spike 49 a fidèlement porté en TSL les shaders de vent Godot de Quaternius, avant de traquer une recompilation à chaque frame qui faisait chuter la scène à 1 fps.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 24 : sauvegarder un monde et rendre le vent visible</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et contient les liens vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-08-open-world-browser-part-23-avatars-and-voice">partie 23</a> a peuplé le monde et donné une voix à ses habitants. Cette partie explique comment faire en sorte que le monde se souvienne de ce qu'ils y ont fait et paraisse vivant même lorsque personne n'y touche. Le Spike 47 porte sur la persistance : un créateur sculpte le terrain et place des accessoires, puis ces modifications survivent à un rechargement, se synchronisent avec tous les autres pairs et sont arbitrées proprement lorsque deux personnes modifient le monde simultanément. Le Spike 49 porte sur le vent : nous avons porté les shaders de végétation d'un pack de nature stylisée afin que les arbres, les buissons et l'herbe bougent comme l'artiste l'avait prévu, ce qui s'est davantage transformé en combat contre le compilateur de shaders que contre les mathématiques.</p>
<h2>Un monde qui se souvient</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/47-world-persistence/" title="Spike 47 : persistance du monde" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/47-world-persistence/" target="_blank">Ouvrir le Spike 47 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=47-world-persistence">Voir le code source</a></p>
<p>Le scénario est une session de création partagée. Les joueurs parcourent un monde, placent des accessoires d'un clic, les suppriment d'un clic droit et sculptent le sol en faisant glisser un pinceau, soit pour élever ou abaisser la heightmap, soit pour creuser des grottes volumétriques dans le terrain SDF, ce qui fait passer un chunk aux marching cubes. Tout ce qu'ils font est conservé dans un <code>WorldChunkDO</code> Cloudflare, un Durable Object faisant autorité côté serveur et reposant sur son propre stockage SQLite. Les clients n'écrivent pas directement dans l'état. Ils envoient une intention, le DO arbitre puis diffuse le résultat. Comme le DO constitue l'unique source de vérité, un nouvel arrivant reçoit un instantané et rejoint exactement le même monde que celui vu par tous les autres.</p>
<p>Deux détails de la persistance se sont révélés particulièrement utiles. Le terrain n'est pas stocké sous forme de journal d'événements à rejouer lors de la connexion ; il est conservé sous forme de blobs binaires par chunk qui constituent la source de vérité et sont téléversés à la fin de chaque coup de pinceau. Un nouvel arrivant charge donc directement les octets validés au lieu de réexécuter des milliers d'échantillons du pinceau. Ces blobs utilisent également une clé de stockage par chunk plutôt qu'une seule grosse ligne, car un chunk SDF pèse 168 Ko et le DO impose une limite de 2 Mo par ligne. Un index des chunks répertorie les clés existantes afin que le DO puisse réhydrater toute la carte à son réveil. Côté client, l'identité s'appuie sur deux types de stockage : l'identifiant du joueur réside dans <code>sessionStorage</code>, de sorte que deux onglets correspondent à deux pairs distincts plutôt qu'à un même pair qui s'écrase lui-même dans la table des joueurs du DO, tandis que le nom affiché réside dans <code>localStorage</code>, afin qu'un changement de nom effectué dans un onglet soit répercuté dans tous les autres.</p>
<h2>Faire converger les modifications simultanées</h2>
<p>La vraie difficulté de la création multijoueur apparaît lorsque deux personnes modifient une même zone du sol exactement au même instant. Le spike répartit les modifications selon leurs propriétés algébriques. Les opérations additives — élever et abaisser la heightmap, ainsi qu'ajouter et soustraire dans le SDF — sont commutatives : leur application dans n'importe quel ordre produit le même résultat. Elles empruntent donc un chemin optimiste fondé sur des empreintes, dans lequel chaque client applique localement l'opération et envoie l'empreinte, que le DO diffuse à tout le monde sans aucune coordination. L'ordre n'a réellement aucune importance, il n'y a donc rien à coordonner.</p>
<p>Les opérations dépendantes de l'ordre, le lissage et l'aplanissement, constituaient le cas intéressant. La première conception leur attribuait un verrou régional : un client demandait un verrou lors de l'appui sur le pointeur, le DO l'accordait ou le refusait, le client mettait les échantillons en mémoire tampon pendant l'appui et, lorsque le pointeur était relâché, le DO appliquait tout le tracé de manière atomique. Cela fonctionnait, mais nécessitait un protocole distinct, avec sa propre durée de vie du verrou et son propre aller-retour d'acceptation ou de refus. La solution plus élégante qui l'a remplacé consiste à précalculer un delta : l'émetteur exécute localement le pinceau de lissage ou d'aplanissement, puis envoie la liste obtenue des deltas par cellule. Chaque pair se contente alors d'ajouter ces deltas à ses propres cellules sans rien recalculer. En figeant son résultat à la source, cette approche transforme une opération dépendante de l'ordre en opération commutative. L'ensemble du système de modification repose ainsi sur un protocole commutatif unique et uniforme, offrant une convergence identique sans aucun verrou. Les verrous d'accessoires subsistent pour une autre raison : le verrouillage par enregistrement a remplacé la suppression réservée au propriétaire. N'importe quel pair peut donc supprimer n'importe quel accessoire, sauf si quelqu'un l'a verrouillé, auquel cas seul le détenteur du verrou peut le retirer. L'annulation et le rétablissement fonctionnent en permettant au client de choisir l'identifiant d'un accessoire avant de le placer. Il connaît ainsi cet identifiant avant même le retour du serveur et peut inverser ses propres actions de manière déterministe. Les WebSockets en hibernation permettent à une salle inactive de ne rien coûter, comme pour le relais d'avatars bon marché de la partie précédente.</p>
<h2>Un portage fidèle du vent, suivi d'un combat</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/49-gpu-wind-props/" title="Spike 49 : vent GPU sur les accessoires" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/49-gpu-wind-props/" target="_blank">Ouvrir le Spike 49 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=49-gpu-wind-props">Voir le code source</a></p>
<p>Le Spike 49 reprend le Stylized Nature MegaKit de Quaternius et porte son système de vent vers notre pile technologique. Le pack fournit ses shaders Godot sources — quatre au total — et la bonne décision consistait à les traduire fidèlement plutôt qu'à les réinventer. La fonction de vertex shader de l'écorce est vide, les troncs restent donc rigides. Une première tentative reposant sur un masque procédural faisait onduler les troncs, et la correction a simplement consisté à ne plus leur appliquer de vent du tout. Les feuilles reçoivent un balancement chaotique par sommet à partir d'un hash en impulsion triangulaire, masqué selon la hauteur afin que les cimes bougent tandis que la base reste enracinée. Le feuillage de base reçoit un balancement sinusoïdal modulé par du bruit dans l'espace monde. L'herbe reprend ce feuillage de base en y ajoutant une ondulation en lignes de vent : une texture de bruit défilante est échantillonnée à travers une courbe de puissance, de sorte que seules les bandes claires de la texture contribuent au mouvement, ce qui produit les ondulations visibles qui traversent une prairie. La répartition suit la convention de nommage des matériaux du pack : un matériau nommé <code>Leaves_Birch</code> est donc dirigé vers le chemin des feuilles et <code>Grass_Common</code> vers celui de l'herbe, sans aucune déduction approximative.</p>
<p>L'une des surprises du portage est que la couleur des feuilles ne se trouve pas dans la texture. Quaternius construit entièrement leur apparence à partir d'un dégradé vertical et d'un liseré de Fresnel : l'albédo mélange une couleur supplémentaire située au bas de la cime avec la couleur des feuilles au sommet, en fonction de la hauteur. Une teinte de diffusion sous-surfacique est ajoutée sous forme d'émission, mise à l'échelle par un terme de Fresnel $(1 - \mathbf{N}\cdot\mathbf{V})^3$. C'est pourquoi une simple passe texturée paraissait terne. Les couleurs proviennent du dégradé et du liseré, pas de la texture peinte. Le portage lit un attribut de sommet <code>heightFactor</code> précalculé plutôt que la coordonnée Y locale brute. Cet attribut est normalisé pour chaque groupe de feuilles au chargement à partir de la coordonnée Y dans l'espace monde, afin que le dégradé et le masque du vent se comportent correctement, quelle que soit la manière dont l'importation FBX a orienté les axes locaux de chaque maillage. Les constantes de couleur Godot sont définies en sRGB et converties en espace linéaire avant d'atteindre le shader. Le portage effectue donc la même conversion au lieu de fournir directement les valeurs sRGB lumineuses comme des valeurs linéaires, ce qui délaverait le feuillage.</p>
<h2>La recompilation qui a englouti le débit d'images</h2>
<p>Si cette version a d'abord été livrée avec une base FBX, tandis que l'ensemble complet des matériaux de vent était mis de côté dans un fichier <code>.bak</code>, c'est à cause d'un bug de recompilation à chaque frame qui faisait chuter la scène à environ 1 fps. L'ajout de TSL personnalisé aux matériaux chargés depuis le FBX poussait Three.js à reconstruire les programmes de shaders à chaque frame, comme si <code>needsUpdate</code> restait constamment activé. La méthode de diagnostic consistait à réduire chaque matériau de feuillage à une simple passe texturée sans nœuds personnalisés, puis à vérifier si la boucle de recompilation persistait. Si elle s'arrêtait, le graphe personnalisé était responsable ; si elle continuait, la cause se trouvait en amont, dans la configuration des matériaux FBX ou dans Three.js lui-même. La correction qui a permis de rétablir les véritables shaders consistait à lier sous forme d'uniformes toutes les différences propres aux matériaux — couleur des feuilles, couleur SSS, intensité et mélange. Tous les éléments feuillus partagent ainsi un même programme compilé, au lieu que le compilateur produise un nouveau shader pour chaque combinaison de couleurs unique et sature la file de compilation.</p>
<p>Deux autres éléments méritent d'être conservés. L'herbe est rendue sous la forme d'un <code>InstancedMesh</code> par fichier source, et son mouvement sous l'effet du vent est calculé dans l'espace monde, car les phases sinusoïdales dépendent de la position dans cet espace. Mais le déplacement doit être appliqué dans l'espace local avant l'application de la matrice d'instance, et WGSL ne fournit aucune fonction <code>inverse()</code>. Pour une matrice d'instance composée d'une translation, d'une rotation autour de Y et d'une échelle uniforme, l'inverse de la partie 3×3 supérieure correspond simplement à sa transposée divisée par le carré de l'échelle. Le shader multiplie donc le déplacement dans l'espace monde par la matrice de modèle transposée, puis le divise par la longueur au carré de la première colonne de la matrice, ce qui permet de retrouver l'échelle sans racine carrée. Lorsque la transformation des sommets réapplique ensuite la matrice, le mouvement se retrouve dans l'espace monde exactement comme prévu, indépendamment de la rotation ou de l'échelle de chaque touffe. Chaque touffe effectue également un culling GPU par sommet contre le frustum : le centre de l'instance est projeté dans l'espace de clipping et, s'il se trouve hors du frustum en tenant compte d'une marge, tous ses sommets sont rabattus sur l'origine locale afin que les trois sommets de chaque triangle coïncident. Le rastériseur élimine alors le triangle dégénéré et aucun travail de fragment, de test alpha ou d'ombre n'est effectué pour l'herbe hors écran. Cette technique s'ajoute au culling grossier par sphère englobante de chunk déjà effectué par Three.js. Elle est construite avec la fonction <code>step</code> sur des flottants plutôt qu'avec des booléens, afin que son résultat puisse être directement multiplié dans le mélange des positions.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Persistance du monde faisant autorité côté serveur.</strong> Un Durable Object <code>WorldChunkDO</code> arbitre le placement des accessoires et les modifications du terrain, conserve des blobs binaires par chunk comme source de vérité — afin que les nouveaux arrivants chargent les octets validés au lieu de rejouer un journal d'événements — et utilise une clé de stockage par chunk pour rester sous la limite de 2 Mo par ligne du DO. L'identifiant du joueur réside dans <code>sessionStorage</code>, afin que chaque onglet soit un pair distinct ; le nom affiché réside dans <code>localStorage</code>, afin que les changements de nom se propagent entre les onglets.</p>
<p><strong>Convergence commutative des modifications.</strong> Les opérations additives sur le terrain — élévation et abaissement, ajout et soustraction SDF — sont commutatives et empruntent un chemin optimiste fondé sur des empreintes, sans coordination. Les opérations dépendantes de l'ordre — lissage et aplanissement — deviennent commutatives grâce à l'envoi de deltas précalculés par cellule plutôt qu'à l'acquisition d'un verrou régional. L'ensemble du système converge ainsi au moyen d'un protocole uniforme sans verrou. Les verrous par accessoire remplacent la suppression réservée au propriétaire, tandis que les identifiants d'objet choisis par le client permettent des annulations et rétablissements déterministes avant le retour du serveur. Voir le <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD piloté par le GPU</a>.</p>
<p><strong>Portage fidèle des shaders de Godot vers TSL.</strong> Les quatre shaders de vent sources de Quaternius sont traduits ligne par ligne : écorce rigide, balancement des feuilles masqué selon la hauteur, balancement du feuillage dans l'espace monde et herbe animée par une ondulation en lignes de vent défilantes. Ils sont sélectionnés selon la convention de nommage des matériaux du pack. La couleur des feuilles provient d'un dégradé de hauteur associé à un liseré SSS piloté par Fresnel plutôt que de la texture, et les constantes créées en sRGB sont converties en espace linéaire afin que le rendu corresponde aux images de référence.</p>
<p><strong>Éviter les recompilations de shaders à chaque frame.</strong> Ajouter du TSL personnalisé aux matériaux FBX peut maintenir <code>needsUpdate</code> actif et reconstruire les programmes à chaque frame, faisant chuter les performances à environ 1 fps. Lier sous forme d'uniformes toutes les différences propres aux matériaux permet à toutes les variantes de partager un même programme compilé, au lieu de produire un shader distinct pour chaque jeu de paramètres unique. Une passe de diagnostic simplement texturée permet de déterminer si la cause vient du graphe personnalisé ou de la configuration en amont.</p>
<p><strong>Vent dans l'espace monde sur du feuillage instancié.</strong> Le vent de l'herbe est calculé dans l'espace monde, puis reconverti dans l'espace local au moyen d'un inverse calculé manuellement — transposée divisée par le carré de l'échelle — car WGSL ne fournit pas de fonction <code>inverse()</code>. Un culling GPU par sommet contre le frustum réduit les touffes hors écran à des triangles dégénérés, évitant tout travail de fragment ou d'ombre. Il s'ajoute au culling par sphère englobante de chunk effectué par Three.js.</p>
<hr>
<p>Partie 24 sur 29.
Précédente : <a href="/fr/blog/2026-05-08-open-world-browser-part-23-avatars-and-voice">Partie 23 - Cinquante avatars et une voix dans la salle</a>
Suivante : <a href="/fr/blog/2026-05-10-open-world-browser-part-25-universal-characters">Partie 25 - Un seul squelette pour toutes les tenues</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 23 : cinquante avatars et une voix dans la pièce]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-08-open-world-browser-part-23-avatars-and-voice</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-08-open-world-browser-part-23-avatars-and-voice</guid>
            <pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 45 a fait passer 50 personnages de 13 ms de JavaScript par image à un seul appel de rendu grâce au skinning GPU par lots, puis nous avons passé deux heures à traquer une erreur WGSL qui s'est révélée être causée par une cible de morphing. Le spike 46 a mis en place la voix de proximité avec un son spatialisé HRTF, avant de supprimer la moitié de la chaîne audio pour correspondre à ce que proposent réellement Meet et Teams.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 23 : cinquante avatars et une voix dans la pièce</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-07-open-world-browser-part-22-clouds-and-meshlets">partie 22</a> a ajouté un ciel au-dessus du monde. Cette partie y ajoute des habitants. Un monde ouvert à la troisième personne doit pouvoir afficher plus de 50 personnages à tout moment, tout en permettant d'entendre ceux qui se trouvent à proximité. Le spike 45 concerne le rendu : envoyer autant d'avatars animés au GPU sans faire fondre le thread principal. Le spike 46 concerne l'audio : une communication vocale pair à pair dont la panoramique et l'atténuation dépendent de la position, réglée pour sonner comme un appel vidéo normal plutôt que comme une démo technique.</p>
<h2>Un seul appel de rendu pour cinquante danseurs</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/45-avatar-network-sync/" title="Spike 45 : synchronisation réseau des avatars" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/45-avatar-network-sync/" target="_blank">Ouvrir le spike 45 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=45-avatar-network-sync">Voir le code source</a></p>
<p>Le fonctionnement par défaut de Three.js attribue à chaque personnage son propre <code>SkinnedMesh</code>, son propre <code>AnimationMixer</code>, son propre transfert de matrices d'os et son propre appel de rendu. Avec 50 avatars sur le Mac local, cela représentait environ 13 ms de surcharge JavaScript pure par image, avant même que le GPU ne fasse quoi que ce soit. La question à lever pour toute la partie multijoueur était de savoir si une architecture unique de skinning par lots pouvait maîtriser ce coût et évoluer linéairement avec le nombre de personnages.</p>
<p>La solution consiste à séparer l'endroit où l'animation est calculée de celui où elle est rendue. Trois classes de personnages partagent un même modèle FBX. Le joueur local est un <code>Avatar</code> normal, un clone complet du squelette avec son propre mixer, suivant le parcours standard de Three.js, puisqu'il n'y en a jamais qu'un. Chaque pair distant est un <code>VirtualSkeleton</code> : lui aussi est un clone complet doté de son propre mixer exécutant les mêmes clips, mais chaque nœud <code>SkinnedMesh</code> est supprimé immédiatement après le clonage afin de ne conserver que les os. Il n'est jamais ajouté à la scène. À chaque image, une fois le mixer mis à jour et les matrices stabilisées, il compacte <code>(bone.matrixWorld × boneInverse)</code> pour les 100 os dans un emplacement d'un <code>Float32Array</code> partagé. Le <code>BatchSkinnedRenderer</code> possède ensuite un <code>InstancedMesh</code> par élément de géométrie, tous lisant un unique <code>StorageBufferAttribute</code> de matrices d'os dimensionné à <code>maxInstances × numBones × mat4</code>, soit 60 × 100 × 64 = 384 Ko. Un <code>MeshStandardNodeMaterial</code> doté de <code>positionNode</code> et <code>normalNode</code> personnalisés lit directement dans ce tampon de stockage les quatre influences osseuses de chaque sommet. Le résultat est un transfert vers le tampon de stockage et un appel de rendu par élément de géométrie pour toute la foule, quel que soit le nombre de personnes qui la composent. Le skinning s'effectue dans le vertex shader, et le coût JavaScript par avatar se réduit à l'exécution d'un mixer et à la copie de 100 matrices.</p>
<p>Le HUD chargé de mesurer cela a également dû être reconstruit. L'ancienne version signalait un « dépassement du budget » lorsque le temps GPU total d'une image dépassait 3 ms, mais une image comprend toujours la shadow map, le sol et le mesh skinné complet du joueur local, qui représentent ensemble 3 à 5 ms sur du matériel réel, quel que soit le nombre de pairs synthétiques présents. La solution est un budget qui s'étalonne automatiquement : tant qu'aucun avatar traité par lots n'est présent, il capture le temps GPU réel comme référence au moyen d'une EMA rapide, puis fige cette référence et augmente linéairement le budget de 0,06 ms par avatar ajouté dès que les avatars synthétiques apparaissent. Il affiche PASS au repos sur toutes les machines, puis resserre proportionnellement le budget à mesure que la foule grandit.</p>
<h2>Le bug était un visage invisible</h2>
<p>Les premiers essais dans Chrome montraient des ombres argentées au sol, mais aucun avatar, avec une erreur d'analyse WGSL : <code>cannot index type 'f32'</code> sur une ligne tentant d'indexer <code>object.nodeUniform2[i]</code>, alors que l'uniforme était déclaré comme un scalaire. Pour être honnête, la première correction était mauvaise, mais elle a tout de même fonctionné. Nous avons supposé que le parcours des matrices d'instances d'<code>InstancedMesh</code> générait le code erroné, et son remplacement par un <code>StorageInstancedBufferAttribute</code> a fait disparaître l'erreur dans Chrome. Mais elle a disparu parce que le nouveau parcours produisait un shader différent, et non parce qu'il corrigeait la cause : c'est le type de correctif le plus dangereux.</p>
<p>Le véritable responsable était le morphing. Le fichier 3MIKE.fbx contient des expressions faciales sous forme de blend shapes, la géométrie clonée hérite des <code>morphAttributes</code>, et <code>MorphNode.setup()</code> de Three.js déclare <code>morphTargetInfluences</code> comme un <code>float</code> scalaire avant de tenter de lui appliquer <code>.element(i)</code> dans une boucle générée, ce qui correspond exactement à l'indexation de scalaire rejetée par le compilateur. La correction tient en une ligne : définir <code>geometry.morphAttributes = {}</code> sur les géométries qui n'utilisent pas le morphing, afin que Three.js n'injecte jamais le <code>MorphNode</code>. Le correctif accidentel pour Chrome est resté quelque temps avant de se retourner contre nous : dans Safari, le parcours d'instanciation par stockage produisait 256 fois l'erreur <code>Vertex buffer is not big enough</code>, car le backend WebGPU de Safari ne le traduit pas correctement. Revenir en arrière était la bonne décision, et le simple tampon de stockage des matrices d'os, qui repose sur le cœur de WebGPU plutôt que sur un parcours d'instanciation généré, fonctionne correctement partout. La leçon à retenir est la suivante : lorsqu'un correctif fonctionne dans un navigateur sans que vous puissiez en expliquer le mécanisme, vous avez corrigé un symptôme ; il faut donc lire le WGSL réellement généré. Un shim <code>getCompilationInfo()</code> ajouté plus tard dans le spike a transformé le vague message « module is not valid » de Three.js en véritable erreur Tint, et s'est révélé rentable à maintes reprises.</p>
<p>Une technique connexe pour contourner le framework se trouve juste à côté. Three.js détecte les noms d'attributs standard <code>skinIndex</code> et <code>skinWeight</code>, puis tente d'injecter son propre <code>SkinningNode</code>, même dans un <code>InstancedMesh</code> dont le <code>positionNode</code> personnalisé effectue déjà le skinning. Renommer ces attributs en <code>boneIndex</code> et <code>boneWeight</code> les masque au framework, et le TSL personnalisé les lit sous leurs nouveaux noms.</p>
<h2>Un relais qui vous oublie entre deux mots</h2>
<p>La première version synchronisait les pairs via <code>BroadcastChannel</code>, un substitut limité au même navigateur utilisant le véritable format de transmission et la véritable cadence, et le commentaire du protocole promettait que le passage à un transport réel ne demanderait qu'une ligne. Tenir cette promesse a nécessité un <code>AvatarRoomDO</code>, un Durable Object Cloudflare de 74 lignes qui ne décode même pas la trame binaire de 36 octets. Il transmet chaque message tel quel à tous les autres pairs présents dans la salle, car l'identifiant de l'expéditeur est intégré à la trame et chaque destinataire filtre son propre écho côté client. Le relais n'a aucune connaissance des identités. Les WebSockets en hibernation rendent une salle inactive gratuite : le DO quitte la mémoire entre les messages et l'environnement d'exécution restaure les sockets étiquetées à l'arrivée du paquet suivant. À raison de 10 événements par seconde et par pair, cela représente 36 000 requêtes au DO par heure-pair, soit environ un demi-centime, avec un trafic sortant gratuit chez Cloudflare et un coût environ 6 à 10 fois inférieur à celui d'une architecture WebSocket équivalente sur AWS.</p>
<p>Ce remplacement a révélé un bug de machine à états qu'il vaut la peine de retenir. Un joueur distant continuait à marcher après s'être arrêté. La requête d'animation vérifiait <code>this._state</code>, le clip en cours de lecture, au lieu du dernier nom mis en file d'attente. Ainsi, lorsque deux messages réseau arrivaient pendant le même tick, d'abord <code>walk</code>, puis <code>idle</code>, <code>idle</code> était comparé à un état qui n'avait pas encore évolué et était silencieusement ignoré. Le pair restait bloqué à marcher indéfiniment, car les paquets <code>idle</code> suivants étaient dédupliqués en amont comme étant inchangés. La correction consiste à toujours remplacer le nom en attente et à laisser l'assistant de transition court-circuiter les véritables requêtes visant le même état, ce qu'il faisait déjà. Cette catégorie de bug est générale : une vérification de déduplication effectuée par rapport à la mauvaise valeur de référence élimine silencieusement l'entrée qui compte.</p>
<p>Safari a nécessité deux protections supplémentaires. Il ouvre le WebSocket plus vite que Chrome, si bien que le premier message entrant d'un pair pouvait arriver avant la fin de la construction du moteur de rendu par lots et déréférencer une valeur nulle ; ignorer les messages tant que le moteur de rendu est absent ne présente aucun risque, puisque les pairs rediffusent leur état toutes les 100 ms. De plus, <code>'gpu' in navigator</code> renvoyait true alors que <code>requestAdapter()</code> renvoyait null. Three.js basculait donc silencieusement vers WebGL2, où la chaîne de skinning par tampon de stockage ne possède aucune traduction valide et générait une avalanche d'erreurs. Vérifier la présence d'un véritable adaptateur et confirmer que le backend est bien WebGPU transforme un rendu dégradé en un message clair sur l'écran de chargement. Il existait même un écart de dialecte WGSL : Three.js génère la forme moderne à deux arguments <code>@interpolate(flat, either)</code>, qui n'est pas encore prise en charge par le compilateur de WebKit. Nous l'avons contourné en réécrivant le code source du shader lors de son passage dans <code>createShaderModule</code> afin de supprimer le second argument, sans aucun coût puisque l'interpolation flat transporte la même valeur sur chaque sommet dans tous les cas.</p>
<h2>Une voix qui se déplace dans la pièce</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/46-proximity-voice/" title="Spike 46 : voix de proximité" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/46-proximity-voice/" target="_blank">Ouvrir le spike 46 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=46-proximity-voice">Voir le code source</a></p>
<p>Le spike 46 est consacré à la voix de proximité : une communication WebRTC pair à pair avec un son spatialisé HRTF, dont l'objectif explicite est d'atteindre une qualité comparable à Google Meet et Microsoft Teams dans une pièce calme à modérément bruyante. Un <code>VoiceRoomDO</code> gère la signalisation sous la forme d'un relais JSON : il envoie à chaque nouveau pair la liste des participants, annonce les arrivées et les départs, achemine le SDP et les informations ICE vers un pair précis grâce à l'étiquette du socket, et diffuse les mises à jour de position qui pilotent les panoramiques spatiaux. Il ajoute l'identifiant de l'expéditeur à chaque message afin d'empêcher les pairs de se faire passer les uns pour les autres, et l'audio lui-même ne transite jamais par le DO. Il existe une <code>RTCPeerConnection</code> par pair distant, et le pair dont l'identifiant est le plus petit dans l'ordre lexicographique émet toujours l'offre. Les deux côtés s'accordent ainsi sur l'initiateur sans nécessiter une implémentation complète du modèle perfect negotiation.</p>
<p>Côté réception, l'audio de chaque pair traverse un <code>PannerNode</code> configuré en HRTF avec une atténuation inversement proportionnelle à la distance, tandis que l'<code>AudioListener</code> est mis à jour à chaque image à partir de la position et de l'orientation du joueur local au moyen de <code>forwardX = sin(facing)</code>, <code>forwardZ = cos(facing)</code>, ce qui correspond à la convention d'orientation <code>atan2(wx, wz)</code> de la scène. Une particularité de Chrome nous a coûté une heure : un <code>MediaStream</code> consommé uniquement par Web Audio peut parfois ne pas récupérer les paquets. Chaque flux est donc également associé à un élément <code>&lt;audio&gt;</code> masqué et mis en sourdine afin de forcer la planification du décodeur. En matière de qualité, les navigateurs utilisent par défaut Opus en mono à environ 32 kbit/s. Le spike modifie donc la ligne <code>fmtp</code> de chaque offre et réponse pour passer à 128 kbit/s, activer la correction d'erreurs FEC intrabande et désactiver le DTX, puis appelle <code>setParameters</code> avec un débit maximal élevé afin de garantir que l'encodeur utilise réellement ce qu'annonce le SDP. Après l'augmentation du débit, la FEC apporte la deuxième amélioration audible la plus importante, en compensant les pertes de paquets sans renégociation.</p>
<h2>Supprimer pour obtenir un son propre</h2>
<p>La chaîne audio livrée est bien plus petite que celle avec laquelle j'avais commencé, et cette simplification a été la véritable leçon. La première version comportait un filtre passe-haut, un limiteur de clics réglé pour détecter le bruit du clavier, un compresseur, un noise gate et un fondu enchaîné entre le signal traité et le signal direct, le tout piloté par un panneau flottant doté de plus de douze curseurs. Lorsque l'utilisateur a signalé que les clics du clavier restaient audibles, le premier réflexe a été de renforcer le limiteur de clics et de réduire la part du signal direct : un empilement de rustines. La réponse structurelle était qu'une fois un débruiteur par ML intégré à la chaîne, le limiteur de clics, le noise gate et l'essentiel du filtre passe-haut deviennent tous redondants, car RNNoise est précisément entraîné sur les bruits de clavier, de souris et de frappe, tandis que l'écrêtage d'amplitude n'est qu'une version nettement inférieure de la même fonction. Les clients en production utilisent un débruitage par ML, une annulation d'écho, un gain automatique et un compresseur doux pour niveler le volume, rien de plus. Quatre étages ont donc été supprimés, tout comme le panneau de curseurs et les options permettant de choisir le mode de réduction du bruit, ne laissant qu'une seule chaîne fixe.</p>
<p>Chaque étage conservé justifie sa présence. L'annulation d'écho du navigateur reste activée, car RNNoise ne traite pas l'écho et, sans elle, la boucle entre les haut-parleurs et le microphone n'a aucune limite. La suppression du bruit du navigateur est désactivée, car la superposer à RNNoise produit des artefacts sur les consonnes fricatives : il faut donc choisir un seul débruiteur. Le gain automatique du navigateur reste activé, car sa désactivation rendait le signal trop faible pour que le compresseur puisse travailler, et le <code>DynamicsCompressorNode</code> de Web Audio ne possède aucun paramètre de gain de compensation ; le nivellement général du navigateur et le compresseur rapide du spike opèrent sur des échelles de temps différentes et peuvent coexister. RNNoise est mélangé à 92 % de signal traité et 8 % de signal direct, car il peut trop atténuer les consonnes non voisées comme s, ch et f, dont la probabilité vocale diminue. La petite part de signal direct les préserve au prix d'une légère réintroduction des bruits de frappe.
Deux fonctionnalités viennent compléter l’ensemble. Le push-to-talk ne modifie pas <code>track.enabled</code>, car cela supprime tout ce qui se trouve encore dans les tampons du pipeline et coupe la dernière syllabe lorsque la touche est relâchée. À la place, un <code>GainNode</code> placé vers la fin de la chaîne effectue une transition avec <code>setTargetAtTime</code> : une attaque rapide pour préserver la première syllabe et un relâchement lent pour laisser passer la dernière consonne, tandis que la piste reste activée en permanence. Et un délai de diffusion de cinq secondes, demandé comme fonctionnalité de type radio, utilise en parallèle une branche de contournement et une branche avec <code>DelayNode</code>, entre lesquelles s’effectue un fondu enchaîné. Un bouton d’interruption coupe instantanément la sortie retardée et affiche un compte à rebours sur le HUD avant la reprise du son. L’intégration du débruiteur a été une petite saga en soi : le worklet RNNoise publié utilise des imports à spécificateur nu qu’aucun CDN ne sait résoudre. La solution a donc consisté à créer localement avec esbuild un bundle autonome de 1,9 Mo, avec le WASM encodé en base64 directement intégré, puis à le commiter dans le dépôt et à le référencer par une URL relative au module afin qu’il soit résolu aussi bien par le serveur de développement que par le build VitePress et le domaine personnalisé. Si le chargement du worklet échoue un jour, la chaîne continue à produire du son au moyen d’un simple filtre passe-haut et d’un compresseur, et le HUD affiche l’échec en rouge.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Skinning GPU groupé pour les foules.</strong> Les avatars distants exécutent un <code>VirtualSkeleton</code> sans rendu (un clone complet dont les maillages skinnés ont été retirés, mais qui conserve les os et dispose de son propre mixer) qui regroupe <code>bone.matrixWorld × boneInverse</code> pour chaque os dans un <code>StorageBufferAttribute</code> partagé. Un <code>InstancedMesh</code> par élément de géométrie lit ces matrices dans des <code>positionNode</code>/<code>normalNode</code> TSL personnalisés. Ainsi, toute la foule ne nécessite qu’un seul transfert vers le stockage et un seul appel de dessin par élément, le travail CPU par avatar se limitant à une mise à jour du mixer et à une copie de matrice. Voir <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD piloté par le GPU</a>.</p>
<p><strong>Lire le WGSL généré, pas le symptôme.</strong> Une erreur de compilation <code>cannot index type 'f32'</code> provenait du <code>MorphNode</code> de Three.js, qui déclarait <code>morphTargetInfluences</code> comme un scalaire avant de tenter de l’indexer. Le problème a été corrigé en supprimant les <code>morphAttributes</code> des géométries qui n’utilisent pas de morphing. Une première correction, qui ne faisait que modifier le chemin de shader généré, masquait la cause et a ensuite cassé Safari. Renommer <code>skinIndex</code>/<code>skinWeight</code> en <code>boneIndex</code>/<code>boneWeight</code> masque les attributs à l’injection automatique de <code>SkinningNode</code> par Three.js, afin qu’un matériau de skinning personnalisé prenne en charge les calculs.</p>
<p><strong>Relais Durable Object en hibernation.</strong> Un <code>AvatarRoomDO</code> entièrement binaire transmet des trames de 36 octets à tous les autres pairs sans les décoder, l’identité de l’émetteur étant intégrée à la trame et l’écho vers soi-même étant filtré côté client. Les WebSockets en hibernation rendent une salle inactive gratuite, et cette architecture coûte environ un demi-centime par heure et par pair à 10 Hz, bien moins que l’équivalent avec des WebSockets managés. Un garde-fou de déduplication qui comparait l’animation avec l’état en cours de lecture plutôt qu’avec le dernier état mis en file d’attente supprimait silencieusement les messages d’arrêt et bloquait les joueurs distants dans une boucle de marche.</p>
<p><strong>Voix de proximité WebRTC avec HRTF.</strong> Une <code>RTCPeerConnection</code> par pair, avec les rôles d’offre et de réponse déterminés par l’ordre des identifiants de pairs, un son acheminé vers un <code>PannerNode</code> HRTF doté d’un <code>AudioListener</code> mis à jour à chaque image selon l’orientation du joueur, et des paramètres Opus modifiés pour atteindre 128 kbit/s avec FEC intrabande afin d’améliorer la résilience. Un élément <code>&lt;audio&gt;</code> masqué et coupé force Chrome à récupérer les paquets d’un flux utilisé uniquement par Web Audio.</p>
<p><strong>Ingénierie audio soustractive.</strong> Atteindre une qualité comparable à Meet ou Teams impliquait de retirer des étapes, pas d’en ajouter : débruitage par ML, annulation d’écho, gain automatique et compresseur doux, sans noise gate ni limiteur de clics, car un débruiteur ML entraîné sur le bruit de clavier rend inutile l’écrêtage fondé sur l’amplitude. Le push-to-talk fait varier un <code>GainNode</code> en fin de chaîne avec une enveloppe asymétrique au lieu de modifier l’état de la piste, afin de ne pas couper les syllabes, et le worklet du débruiteur est distribué sous la forme d’un unique bundle esbuild autonome pour contourner la résolution des imports à spécificateur nu.</p>
<hr>
<p>Partie 23 sur 29.
Précédent : <a href="/fr/blog/2026-05-07-open-world-browser-part-22-clouds-and-meshlets">Partie 22 - Des nuages que l’on peut éclairer et un culling qu’il faut alimenter</a>
Suivant : <a href="/fr/blog/2026-05-09-open-world-browser-part-24-persistence-and-wind">Partie 24 - Sauvegarder un monde et rendre le vent visible</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer un monde ouvert dans le navigateur, partie 22 : des nuages que l’on peut éclairer et un culling qu’il faut alimenter]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-07-open-world-browser-part-22-clouds-and-meshlets</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-07-open-world-browser-part-22-clouds-and-meshlets</guid>
            <pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 43 a affiché un ciel physiquement réaliste et des nuages volumétriques en moins de 2 ms, avec des couchers de soleil ne nécessitant aucun réglage selon l’heure de la journée. Le spike 44 a mis en place un culling GPU par meshlets et montré que l’occlusion Hi-Z ne fonctionne que si les occludeurs sont assez grands pour survivre à une réduction par maximum.]]></description>
            <content:encoded><![CDATA[<h1>Créer un monde ouvert dans le navigateur, partie 22 : des nuages que l’on peut éclairer et un culling qu’il faut alimenter</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-06-open-world-browser-part-21-visibility-buffer">partie 21</a> portait sur une technique de rendu qui n’a pas tenu ses promesses. Cette partie en présente une qui les a tenues, et une autre qui a nécessité une correction minutieuse pour fonctionner tout court. Le spike 43 concerne le ciel : une atmosphère physiquement réaliste et des nuages volumétriques, la base qui donne à une scène l’allure d’un véritable lieu plutôt que d’une démo technique. Le spike 44 concerne le culling GPU de type meshlet, avec une leçon essentielle : la qualité du test d’occlusion dépend directement des occludeurs qu’on lui fournit.</p>
<h2>Un ciel issu de la physique, pas d’un dégradé</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/43-clouds-atmosphere/" title="Spike 43 : nuages et atmosphère" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/43-clouds-atmosphere/" target="_blank">Ouvrir le spike 43 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=43-clouds-atmosphere">Voir le code source</a></p>
<p>Presque tous les effets météorologiques cinématographiques dépendent de deux éléments d’infrastructure : une atmosphère physiquement réaliste, afin que la couleur du ciel et celle du soleil évoluent avec l’heure de la journée selon les lois de la physique plutôt qu’à partir d’un dégradé réglé à la main, et un volume de nuages volumétriques, afin que le ciel possède une structure 3D plutôt qu’une cubemap précalculée. Le spike 43 construit exactement cette paire sur la pile WebGPU et TSL existante, sans rien ajouter, car une fois ces deux éléments en place, le reste de la pile météorologique — brouillard, rayons crépusculaires, surfaces mouillées, neige — devient une succession d’extensions plus petites et déjà bien maîtrisées.</p>
<p>L’atmosphère repose sur le modèle de Hillaire 2020, un ensemble de tables de correspondance calculées dans des compute shaders WGSL. Une table de transmittance intègre la lumière du soleil à travers les profils de densité de Rayleigh, de Mie et de l’ozone, et n’est recalculée que lorsque le soleil se déplace. Une table de vue du ciel est recalculée à chaque frame, car elle est suffisamment peu coûteuse pour qu’un mécanisme conditionnel ne justifie pas le code supplémentaire, avec une paramétrisation non linéaire autour de l’horizon afin d’éviter les bandes. Pour l’instant, la diffusion multiple utilise une approximation analytique à la place de la table complète, et les couchers de soleil restent convaincants : ce raccourci se fait donc bien oublier. Les nuages reposent sur un ray marching de style Schneider Nubis à travers une couche horizontale, dont la forme provient d’une texture Perlin-Worley 128³ érodée par une texture Worley 32³. Toutes deux sont générées au démarrage par calcul GPU, sans aucun téléchargement réseau. L’éclairage combine l’extinction selon la loi de Beer, une fonction de phase à deux lobes et une approximation de l’effet de poudre. Le couplage essentiel vient du fait que la couleur solaire du nuage échantillonne la même table de transmittance à chaque étape, de sorte que l’éclairage des nuages suit le coucher du soleil sans nécessiter une seconde passe de réglage.</p>
<p>Sur un M1, l’ensemble prend entre 1,1 et 2,0 ms avec des nuages en demi-résolution, bien en dessous du budget de 6 ms, utilise environ 14 Mo de mémoire GPU et tourne à plus de 100 FPS. Les deux hypothèses principales se sont vérifiées en pratique. Le coucher de soleil est le plan phare, le moment qui donne au moteur de rendu une dimension cinématographique, et il découle directement de la physique sans réglage propre à chaque heure de la journée. Autre heureux hasard : avec une couche nuageuse paramétrée entre 800 m et 4 000 m, les nuages lointains à l’horizon ressemblent à des crêtes montagneuses sombres depuis une caméra basse, ce qui dote le monde d’un relief d’arrière-plan sans que personne ait à le modéliser.</p>
<p>Une remarque d’architecture mérite d’être conservée. L’approche naturelle consiste à peindre d’abord le ciel dans la swap chain, puis à laisser three.js dessiner la géométrie par-dessus avec <code>autoClear = false</code>. Cela ne fonctionne pas avec le moteur de rendu WebGPU de la r184, car ce drapeau ne contrôle pas l’opération de chargement de la couleur comme il le fait dans WebGL : three.js écrase donc le ciel à chaque frame. La solution consiste à effectuer le rendu three.js dans une cible hors écran, puis à réaliser la composition finale (<code>mix(skyCloud, scene, scene.alpha)</code>, suivie d’ACES puis de sRGB) dans notre propre passe, qui possède la swap chain.</p>
<h2>Un culling qui ne vaut que par ses occludeurs</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/44-meshlet-clusters/" title="Spike 44 : clusters de meshlets" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/44-meshlet-clusters/" target="_blank">Ouvrir le spike 44 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=44-meshlet-clusters">Voir le code source</a></p>
<p>Le spike 44 compare les performances de quatre modes de rendu : rendu forward classique, culling des clusters sur CPU, culling par compute GPU et buffer de visibilité avec culling d’occlusion Hi-Z. Le chemin Hi-Z est le plus intéressant, et il cachait un bug discret : son HUD indiquait que l’occlusion était activée, mais le compteur « Hi-Z killed » restait éternellement bloqué à exactement 0,0 %. Le culling du frustum fonctionnait, donc tout ce qui alimentait le shader de culling était correct. La moitié consacrée à l’occlusion ne faisait rien, tout en coûtant autant que si elle fonctionnait.</p>
<p>Un test d’occlusion Hi-Z projette à l’écran la boîte englobante d’un cluster, choisit un niveau mip de la pyramide de profondeur afin que le rectangle à l’écran couvre environ 2×2 texels, échantillonne la profondeur de l’occludeur le plus éloigné dans ce rectangle, puis rejette le cluster si son point le plus proche se trouve toujours derrière cet occludeur. La pyramide de profondeur est construite à chaque frame en initialisant le mip 0 à partir d’une prépasse de profondeur opaque, puis en appliquant des réductions successives par maximum. La prépasse opaque ne contient délibérément que des occludeurs solides — le sol et des proxies de troncs pour chaque arbre — car le feuillage avec test alpha créerait des trous susceptibles de tromper une réduction par maximum.</p>
<p>Le bug était géométrique, et non logique. Le proxy du tronc était une boîte de 0,5 m sur 4 m sur 0,5 m. À 30 m, il occupe environ 17 pixels à l’écran. Mais un cluster d’herbe typique de 50 m sélectionne le mip 5, où chaque texel couvre 32 pixels de la source. Un tronc de 17 pixels ne recouvre entièrement aucun texel du mip 5 ; chaque texel touchant le tronc touche donc aussi le sol environnant. Dès la toute première réduction 2×2 par maximum, la valeur de profondeur la plus élevée est retenue, c’est-à-dire celle du sol plus éloigné derrière le tronc, et la profondeur du tronc disparaît dès la première réduction. Au mip 5, la pyramide contient alors presque partout la profondeur du sol. Le cluster n’est jamais plus éloigné que le sol, et rien n’est donc jamais occulté.</p>
<p>La solution consiste à agrandir suffisamment le proxy pour qu’il domine les texels qu’il occupe, en le dimensionnant selon la silhouette de l’arbre plutôt que selon son bois. Un proxy d’environ 2 m sur 6 m sur 2 m reste plus petit que la canopée réelle, de sorte que les feuilles visibles à travers les trouées ne sont jamais éliminées à tort, mais il est assez grand pour survivre à la réduction par maximum jusqu’aux distances pertinentes. Le compteur d’occlusion est immédiatement passé au-dessus de zéro. On peut généraliser cette conclusion en une règle pour le moteur de production : tout objet utilisé comme occludeur Hi-Z fiable doit être dimensionné selon sa silhouette à l’écran, car l’efficacité du Hi-Z dans les scènes ouvertes riches en végétation dépend davantage de la couverture des occludeurs au niveau mip concerné que de l’élégance des calculs du test de profondeur. L’occlusion de l’herbe par l’herbe ne peut de toute façon pas se déclencher, puisqu’un brin se trouve à la même profondeur que le sol situé sous lui. Les vrais gains viennent donc des arbres qui occultent la végétation lointaine et des arbres qui en occultent d’autres.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>LUT d’atmosphère de Hillaire 2020.</strong> Une table de transmittance — la lumière solaire traversant les profils de Rayleigh, de Mie et de l’ozone — recalculée uniquement lorsque le soleil se déplace, associée à une table de vue du ciel recalculée à chaque frame avec une paramétrisation non linéaire de l’horizon, produit des couleurs de ciel et de soleil physiquement réalistes qui suivent l’heure de la journée sans dégradé réglé à la main. Une approximation analytique de la diffusion multiple remplace la table complète jusqu’à ce qu’un artefact impose son calcul réel. Le coucher de soleil découle directement de la physique, sans réglage propre à chaque heure de la journée.</p>
<p><strong>Nuages volumétriques Schneider Nubis.</strong> Un ray marching à travers une couche horizontale, façonnée par une texture Perlin-Worley 128³ générée au démarrage et érodée par une texture Worley 32³, avec un éclairage fondé sur l’extinction selon la loi de Beer, une phase à deux lobes et un terme de poudre. Échantillonner à chaque étape la couleur solaire des nuages depuis la même table de transmittance permet à leur éclairage de suivre gratuitement le lever et le coucher du soleil. Le ray marching en demi-résolution coûte environ quatre fois moins cher que celui en pleine résolution, sans perte de qualité visible aux distances habituelles : c’est le compromis standard en production.</p>
<p><strong>Composition de WebGPU brut avec three.js sur la r184.</strong> Peindre le ciel dans la swap chain puis dessiner la géométrie three.js par-dessus avec <code>autoClear = false</code> échoue, car ce drapeau ne contrôle pas l’opération de chargement de la couleur dans le backend WebGPU. Il faut effectuer le rendu three.js dans une cible RGBA16F hors écran, puis appliquer le <code>mix</code> final, le tone mapping et la conversion sRGB dans une passe qui possède la swap chain.</p>
<p><strong>Culling d’occlusion Hi-Z et dimensionnement des occludeurs.</strong> Une pyramide de profondeur construite par réduction successive par maximum permet à une passe de culling GPU de rejeter les clusters dont le point le plus proche se trouve derrière l’occludeur le plus éloigné dans leur rectangle à l’écran. Le test peut silencieusement ne rien faire si les occludeurs sont trop petits pour dominer un texel au niveau mip sélectionné, car la première réduction par maximum remplace la profondeur de l’occludeur par celle de l’arrière-plan plus éloigné situé derrière lui. Les occludeurs doivent être dimensionnés selon leur silhouette à l’écran, et non selon leur noyau physique. Voir <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD piloté par le GPU</a>.</p>
<hr>
<p>Partie 22 sur 29.
Précédent : <a href="/fr/blog/2026-05-06-open-world-browser-part-21-visibility-buffer">Partie 21 — Un moteur de rendu plus rapide qui ne l’était pas</a>
Suivant : <a href="/fr/blog/2026-05-08-open-world-browser-part-23-avatars-and-voice">Partie 23 — Cinquante avatars et une voix dans la pièce</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 21 : un moteur de rendu plus rapide qui ne l'était pas]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-06-open-world-browser-part-21-visibility-buffer</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-06-open-world-browser-part-21-visibility-buffer</guid>
            <pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 40 a mis en œuvre un buffer de visibilité pour de l'herbe dense, la technique que tout le monde présente comme la meilleure contre l'overdraw, et a mesuré des performances inférieures de 44 % à celles d'un rendu forward classique à haute densité sur Apple Silicon. Voici pourquoi, et dans quels cas il faut réellement l'utiliser.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 21 : un moteur de rendu plus rapide qui ne l'était pas</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-05-open-world-browser-part-20-parallax-occlusion">partie 20</a> simulait la profondeur d'une surface sur un quad plat. Cette partie porte sur une décision d'architecture de rendu, et c'est le spike où la réponse des manuels s'est révélée inadaptée à notre matériel. La question était la suivante : pour de l'herbe à découpe alpha d'une densité cinématographique, sous une caméra à la troisième personne, avons-nous besoin d'un buffer de visibilité avant de passer à une densité adaptée à 200 joueurs ? Le conseil généralement donné est un oui catégorique. Nous l'avons construit, mesuré, et la réponse était non.</p>
<h2>La technique que tout le monde recommande</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/40-visibility-buffer/" title="Spike 40 sur le buffer de visibilité" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/40-visibility-buffer/" target="_blank">Ouvrir le spike 40 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=40-visibility-buffer">Voir le code source</a></p>
<p>Un buffer de visibilité divise le rendu en deux passes. La passe 1 rastérise la géométrie et écrit uniquement les identifiants des triangles et des instances dans une cible entière compacte, avec la profondeur, sans effectuer le moindre shading. La passe 2 est une passe plein écran qui lit les identifiants de chaque pixel couvert, récupère à nouveau les sommets du triangle concerné, reconstruit les attributs interpolés et calcule le shading de chaque pixel visible exactement une fois. L'argument de vente est un rejet parfait de l'overdraw : le test de profondeur s'applique à des fragments qui n'ont subi aucun calcul de shading, de sorte que le matériau coûteux n'est exécuté que sur ce qui est réellement visible.</p>
<p>Le spike exécute deux chemins de rendu sur un même canevas et un même appareil, afin que la seule variable soit l'endroit où le shading est effectué. Le chemin forward utilise un <code>MeshStandardNodeMaterial</code> normal via three.js. Le chemin du buffer de visibilité est un pipeline WebGPU brut à deux passes, exécuté en dehors de three.js. Il lit la texture d'herbe de three.js directement depuis le backend, écrit <code>(instanceId, triId)</code> dans une cible <code>RG32Uint</code> pendant la passe 1, puis résout l'éclairage pendant la passe 2. Les deux chemins partagent la même configuration d'éclairage de référence.</p>
<p>Deux remarques d'implémentation méritent d'être retenues. WebGPU ne possède toujours pas de builtin <code>primitive_index</code> portable dans les fragment shaders. L'astuce consiste donc à intégrer un identifiant de triangle par sommet dans une géométrie non indexée, puis à le lire avec une interpolation <code>flat</code>. Cela multiplie par trois le nombre de sommets, mais le coût est négligeable pour une carte d'herbe de 12 sommets. Par ailleurs, partager le canevas avec le moteur de rendu de three.js ne pose pratiquement aucun problème, tant que vous ne reconfigurez jamais le contexte et ne modifiez pas les dimensions du canevas, qui appartiennent tous deux à three.js. Le plus délicat a été de mesurer le chemin forward, car three.js ne fournit aucun hook permettant d'insérer des requêtes de timestamp GPU dans sa passe de rendu. La solution de contournement consiste à encadrer son travail par deux passes de timestamp sans opération, soumises avant et après, que le GPU exécute dans l'ordre de soumission.</p>
<h2>Les chiffres évoluent dans le mauvais sens</h2>
<p>Sur un Mac de série M à environ 1080p, avec des brins d'herbe composés de cartes croisées répartis sur un terrain de 80 m :</p>
<p>Avec 50 000 instances, le chemin du buffer de visibilité l'emportait de 25 %, avec 4,13 ms contre 5,51 ms pour le rendu forward. À 100 000 instances, les deux étaient à égalité. À 200 000 instances, le rendu forward l'emportait de 44 %, avec 5,44 ms contre 7,80 ms pour le buffer de visibilité. Le chemin du buffer de visibilité devient <em>relativement moins performant</em> à mesure que la densité augmente, soit l'exact opposé de l'idée répandue selon laquelle il gagne précisément lorsque l'overdraw est important.</p>
<h2>Pourquoi le rendu forward tient bon</h2>
<p>Les GPU Apple Silicon sont des moteurs de rendu différé par tuiles, ce qui change complètement le calcul. Le rendu forward sur un TBDR dispose d'une étape cachée d'élimination des surfaces masquées qui s'exécute avant le fragment shader : le rastériseur collecte tous les fragments associés à une tuile, les trie par profondeur, et seuls ceux qui subsistent après le test alpha atteignent le fragment shader. Le chemin forward bénéficie donc déjà gratuitement, au niveau matériel, de l'essentiel de la promesse du buffer de visibilité consistant à « calculer le shading une seule fois par pixel ». À mesure que les brins envahissent l'écran, davantage de fragments sont rejetés par l'élimination des surfaces masquées avant le moindre calcul de shading, et le coût effectif par pixel du rendu forward reste à peu près constant au lieu d'augmenter avec l'overdraw.</p>
<p>La passe 1 du chemin du buffer de visibilité bénéficie du même avantage du TBDR. Le problème se situe entièrement dans la passe 2. Celle-ci lit, pour chaque pixel, la matrice de son instance depuis un buffer qui atteint 12,8 Mo avec 200 000 instances, soit bien plus que la capacité de n'importe quel cache GPU. Les pixels voisins à l'écran appartiennent généralement à des instances d'herbe différentes — la répartition repose sur une grille avec jitter, si bien que les brins voisins possèdent des identifiants d'instance arbitraires — et chaque wave qui accède à ce buffer subit donc des défauts de cache divergents. À lui seul, cet accès aléatoire incohérent coûte environ 4 ms par image. Le rendu forward l'évite entièrement, car la matrice d'instance arrive avec le sommet via les attributs par instance. Lorsque le fragment shader s'exécute, les données transformées des sommets se trouvent donc déjà dans les registres locaux de la tuile, sans nécessiter de lecture aléatoire à l'échelle de plusieurs mégaoctets.</p>
<p>C'est exactement le coût que la passe de classification des matériaux de Nanite cherche à amortir : elle regroupe les pixels par instance et lance des waves de calcul triées afin que les lectures de chaque wave soient cohérentes. Nous ne disposons pas de cela. Une estimation rapide indique qu'un tri des pixels par instance ramènerait ces 4 ms à environ 1,5 à 2 ms et repousserait le point de croisement à 400 000 ou 500 000 instances. Mais cela reviendrait à empiler des optimisations sur une architecture qui n'est déjà pas gagnante ici.</p>
<h2>La conclusion honnête, et l'audit qui l'a rendue possible</h2>
<p>Pour du feuillage à cartes croisées avec test alpha sur Apple Silicon via WebGPU, le chemin forward utilisant le pipeline TSL de three.js présente déjà un coût inférieur ou égal à celui du buffer de visibilité. Toute la plomberie du buffer de visibilité n'apporte aucun gain perceptible avant de dépasser largement 200 000 instances, et seulement si l'on ajoute également une passe de tri ou de regroupement. Pour le moteur de production, la décision pratique est donc de conserver l'ensemble rendu forward, LOD et imposteurs issu des spikes précédents, et de ne pas investir dans une infrastructure de buffer de visibilité tant que nous ne ciblons pas principalement des GPU NVIDIA ou AMD dédiés — sur lesquels le coût de l'overdraw est plus linéaire — ou tant que nous ne passons pas à une architecture à meshlets, où le buffer de visibilité constitue de toute façon la sortie naturelle.</p>
<p>Comme ce résultat est contre-intuitif, la conclusion n'a de valeur que si la comparaison est équitable. Le spike a donc fait l'objet d'un audit complet. Plusieurs vrais bugs ont été découverts et corrigés : un curseur d'échelle des brins qui désynchronisait silencieusement les deux chemins, la moitié des brins du rendu forward qui apparaissaient presque noirs à cause de normales antiparallèles — corrigées grâce à la technique classique des normales de feuillage orientées vers le haut — et le buffer de visibilité qui produisait une image environ deux fois trop lumineuse à cause d'un facteur de Lambert choisi manuellement au lieu du facteur <code>1/π</code> respectant la conservation de l'énergie, d'un terme ambiant codé en dur et de l'absence de tone mapping. La correction a consisté à reproduire exactement dans WGSL la courbe filmique ACES de three.js, puis à lire à chaque image les couleurs et les intensités des lumières directement depuis les éclairages réels de la scène. La seule différence connue restante, l'absence de spéculaire directe dans la passe 2, favorise <em>le buffer de visibilité</em>. Le rendu forward effectue donc strictement plus de travail par pixel et continue malgré tout de gagner à haute densité. La conclusion principale est ainsi prudente, et non optimiste. Une réserve subsiste : tous ces résultats sont propres aux puces de série M, et le point de croisement pourrait très bien s'inverser sur une carte dédiée. Il faudra donc relancer les mesures avant d'adopter cette pile pour des cibles non-Apple.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Rendu avec buffer de visibilité.</strong> La passe 1 rastérise la géométrie et écrit uniquement les identifiants des triangles et des instances, ainsi que la profondeur, sans effectuer de shading. La passe 2 est une résolution plein écran qui lit les identifiants de chaque pixel couvert, récupère à nouveau le triangle source, reconstruit les attributs barycentriques corrigés en perspective et calcule une seule fois le shading de chaque pixel visible. Comme WebGPU ne possède pas de <code>primitive_index</code> portable pour les fragments, l'identifiant du triangle est intégré sous forme d'attribut par sommet interpolé avec <code>flat</code> dans une géométrie non indexée.</p>
<p><strong>Élimination des surfaces masquées d'un TBDR contre résolution différée.</strong> Sur un GPU à rendu différé par tuiles comme Apple Silicon, le rendu forward rejette déjà les fragments occultés avant l'exécution du fragment shader. Il bénéficie donc gratuitement de l'essentiel de l'avantage du buffer de visibilité consistant à n'effectuer le shading qu'une fois, et son coût par pixel reste à peu près constant lorsque l'overdraw augmente. À l'inverse, une passe de résolution d'un buffer de visibilité doit assumer le coût d'accès aléatoires incohérents à un vaste buffer par instance — 12,8 Mo pour 200 000 instances — qui devient dominant à haute densité, sauf si les pixels sont d'abord triés ou regroupés par instance, comme le fait la classification des matériaux de Nanite.</p>
<p><strong>Partage d'un canevas avec le WebGPURenderer de three.js.</strong> Les command buffers WebGPU bruts s'intercalent correctement avec les soumissions de three.js dans la file partagée, tant que vous ne rappelez jamais <code>context.configure()</code> et ne modifiez pas <code>canvas.width/height</code>, deux éléments contrôlés par le moteur de rendu. Le temps GPU du chemin forward, pour lequel three.js ne fournit aucun hook, peut être mesuré en encadrant son appel de rendu par deux passes de rendu de timestamp sans opération. Le GPU exécute en effet les command buffers dans l'ordre de soumission.</p>
<p><strong>Validation d'un benchmark contre-intuitif.</strong> Un résultat de performances surprenant n'est fiable que si la comparaison est équitable. L'audit des deux chemins pour garantir un contenu de scène et un shading identiques — même tone mapping ACES, modèle de Lambert respectant la conservation de l'énergie, lumières lues depuis les mêmes objets et échelle des brins identique — a permis de transformer « le buffer de visibilité est plus lent », d'un probable artefact de mesure, en une conclusion défendable. La seule asymétrie restante favorise l'interprétation la plus prudente.</p>
<hr>
<p>Partie 21 sur 29.
Précédent : <a href="/fr/blog/2026-05-05-open-world-browser-part-20-parallax-occlusion">Partie 20 - Simuler la profondeur sur un plan plat</a>
Suivant : <a href="/fr/blog/2026-05-07-open-world-browser-part-22-clouds-and-meshlets">Partie 22 - Des nuages que l'on peut traverser, et un culling qui porte ses fruits</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 20 : simuler la profondeur sur un plan plat]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-05-open-world-browser-part-20-parallax-occlusion</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-05-open-world-browser-part-20-parallax-occlusion</guid>
            <pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 39 a porté le parallax occlusion mapping vers TSL, s'est heurté à deux limites du contrôle de flux des shaders WebGPU, a révélé que le bug venait d'un manque de rigueur dans le portage littéral, puis a construit un plan de référence en géométrie réelle pour disposer d'un étalon fiable.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 20 : simuler la profondeur sur un plan plat</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, directeur technique et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-04-open-world-browser-part-19-imposters">partie 19</a> utilisait un quadrilatère plat pour simuler un arbre entier à distance. Cette partie utilise un quadrilatère plat pour simuler la profondeur de près : le parallax occlusion mapping, l'astuce qui donne à une route pavée l'apparence de joints creusés sur 5 cm sans ajouter le moindre sommet. L'objectif était de l'intégrer à la pile de production (Three.js r184, WebGPU, TSL), afin que les matériaux détaillés du terrain puissent profiter de cette illusion de profondeur là où elle compte et ne payer ailleurs que le coût d'une texture plate.</p>
<h2>Trois façons de simuler la profondeur, côte à côte</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/39-parallax-pom/" title="Spike 39 sur le parallax occlusion mapping" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/39-parallax-pom/" target="_blank">Ouvrir le spike 39 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=39-parallax-pom">Voir le code source</a></p>
<p>Le spike place côte à côte trois plans plats de 5 × 5 m, tous fondés sur la même structure de matériau et ne différant que par les UV transmis aux échantillonneurs. La version plate échantillonne directement la texture et sert de référence. La parallaxe à échantillon unique décale une fois les UV dans la direction de vue selon la hauteur à cet endroit ; elle est peu coûteuse et acceptable avec une faible amplitude, mais flotte aux angles rasants. Le POM effectue un ray marching dans l'espace tangent : il avance le long du rayon de vue, trouve la première couche où le rayon passe sous le champ de hauteur, puis affine l'intersection. L'espace tangent reste simple, car chaque plan de test est aligné sur les axes : quelques inversions de signe suffisent donc à exprimer la direction de vue dans l'espace tangent, au lieu d'une matrice TBN complète par sommet. Le jeu de textures est récupéré en direct depuis l'API de fichiers de Polyhaven, par le même chemin que celui utilisé par la recherche de modèles de la partie 17.</p>
<h2>Deux obstacles WebGPU et un ray marching sans branchement</h2>
<p>La boucle POM classique interrompt la recherche à la première intersection. Avec r184, cela ne fonctionne pas, pour deux raisons distinctes. <code>If(...).and(...)</code> se compilait sans erreur, mais produisait du WGSL dans lequel le corps de la boucle ne s'exécutait jamais ; l'affinement après la boucle travaillait donc sur des données invalides et le plan apparaissait presque blanc. De plus, <code>Break()</code> sous forme de nœud autonome n'était pas encore disponible dans la version r184. Même avec un <code>If</code> fonctionnel, il était donc impossible d'exprimer « s'arrêter à la première intersection ». Ces deux problèmes renvoient à des défauts connus de three.js concernant la sur-optimisation du contrôle de flux TSL à travers les limites de <code>If</code> et de <code>Loop</code> dans cette série de versions.</p>
<p>La réécriture est dépourvue de branchements. Chaque itération échantillonne systématiquement la texture, ce qui maintient l'accès à celle-ci dans un flux de contrôle uniforme, comme l'exige la spécification WGSL, puis intègre le nouvel état à l'aide d'un indicateur <code>done</code> stocké sous forme de nombre flottant. Une fois <code>done</code> passé à 1, les appels à <code>mix</code> de chaque itération reviennent à « conserver l'état inchangé », l'équivalent sans branchement d'un <code>break</code>. L'indicateur <code>done</code> est construit avec un utilitaire <code>step</code> implémenté sous la forme <code>0.5 + 0.5 × sign(x + ε)</code>, car la conversion des booléens en nombres flottants s'est montrée irrégulière dans la série r18x, tandis que <code>sign()</code> est fiable partout. En contrepartie, chaque fragment exécute les 64 itérations, quel que soit l'endroit où l'intersection se produit réellement. Mais c'est le bon compromis à l'échelle du fragment : le moteur limite de toute façon l'exécution au nombre maximal d'étapes, et un vrai GPU spéculerait également au-delà d'un « véritable » arrêt. Un repli propre est essentiel ici : un dernier <code>mix(baseUV, refined, done)</code> garantit qu'à amplitude nulle, à l'extrémité lointaine du fondu selon la distance, aucun fragment ne rencontre la surface, <code>done</code> reste à 0 et le matériau POM est strictement identique bit à bit à la version plate. C'est précisément l'intérêt de cette technique de LOD selon la distance : revenir au coût d'une texture plate là où l'effet est de toute façon inférieur à un pixel.</p>
<h2>Le bug venait d'un manque de rigueur, pas des mathématiques</h2>
<p>La version sans branchement fonctionnait, mais son apparence restait déformée : des artefacts horizontaux étirés avec une amplitude modérée, et un résultat subtilement incorrect et peu net à faible amplitude. La correction est venue d'une consigne d'une ligne : aller lire la référence canonique. Le tutoriel de LlamAcademy qui a inspiré ce travail n'est qu'un nœud Unity ShaderGraph ; la véritable implémentation se trouve donc dans le fichier <code>PerPixelDisplacement.hlsl</code> d'Unity. Sa lecture ligne par ligne a révélé trois différences sémantiques que j'avais introduites sans m'en rendre compte : un décalage d'une unité dans la hauteur de référence du rayon (Unity effectue une première avancée avant la boucle ; mon référentiel était donc déphasé d'une étape entière, ce qui plaçait l'intersection dans la mauvaise couche environ une fois sur deux), une convention de signe pour le décalage maximal dont dépend l'étape d'affinement, et un choix de suivi entre décalage cumulé et UV cumulés qui compliquait mes calculs d'affinement et embrouillait le signe.</p>
<p>La cause profonde n'était pas une erreur isolée, mais le mélange de deux références. Je m'étais appuyé sur le tutoriel POM de LearnOpenGL, qui utilise des conventions de signe semblables mais différentes ainsi qu'une autre formule d'affinement. J'avais ainsi abouti à un hybride dont les deux tiers des calculs correspondaient à une source et le tiers restant à l'autre. La réécriture est un portage presque littéral du HLSL d'Unity vers TSL : mêmes noms de variables, même avancée initiale, même affinement, avec en plus l'indicateur <code>done</code> sans branchement. La leçon mérite d'être retenue : lorsque vous portez depuis une autre pile un shader dont le bon fonctionnement est établi, commencez par le transposer ligne par ligne avec les mêmes noms, puis adaptez-le au style local. Ne le redérivez pas à partir d'une deuxième référence en plein portage.</p>
<h2>Un plan de référence qui ne peut pas mentir</h2>
<p>Il manquait au comparatif côte à côte l'élément le plus évident : un plan en géométrie réelle. Sans lui, l'affirmation « le POM rend plutôt bien » est irréfutable. Plutôt bien par rapport à quoi ? Le spike a donc ajouté un quatrième plan, dont les positions réelles des sommets sont déplacées à partir de la même heightmap. WebGPU ne propose aucune tessellation matérielle — elle ne figure tout simplement pas dans la spécification, notamment pour préserver la compatibilité avec Metal. Le substitut est donc un plan très densément subdivisé (256 × 256 segments, soit 131 072 triangles), avec un déplacement des sommets dans l'étape de vertex shader. La même uniforme d'amplitude pilote à la fois le POM et le plan géométrique, de sorte que leurs effets s'estompent ensemble et que la comparaison reste équitable à toutes les distances.</p>
<p>Avec la vérité terrain affichée à l'écran, les observations qualitatives sont devenues mesurables. Sur une orbite inclinée à 16° vers le sol, le POM et le plan tessellé produisent le même ombrage interne. Aux angles rasants, ils divergent exactement là où ils le doivent : le POM reste contraint par le bord rectangulaire parfaitement droit de la géométrie, tandis que le vrai maillage révèle à l'horizon le profil irrégulier de véritables pics et creux captant la lumière. Le « flottement » du POM sur les bords est donc désormais manifestement inhérent à l'algorithme, et non causé par la texture ou l'éclairage. Les deux profils de coût apparaissent également clairement : le POM est limité par les fragments — son coût dépend du nombre de pixels couverts — tandis que le plan tessellé est limité par les sommets — son coût dépend de la densité du maillage, quelle que soit sa couverture à l'écran. Pour un morceau de terrain, qui paie déjà le coût en sommets d'un plan piloté par une heightmap, le POM est la bonne solution pour les détails plus fins que le maillage.</p>
<p>Le plan de référence a également révélé un bug d'expérience utilisateur plus subtil. L'utilisateur avait l'impression que la surface s'enfonçait à mesure que l'amplitude augmentait. Cela venait de la convention d'Unity, qui considère le plan géométrique comme le sommet du champ de hauteur : les pics restent donc alignés avec lui tandis que tout le reste est déplacé vers le bas par parallaxe, ce qui abaisse la surface moyenne sous le plan de référence d'une valeur de <code>(1 − mean_h) × amplitude</code>. La correction recentre la convention afin que <code>h = 0.5</code> corresponde au plan : les pics se soulèvent vers la caméra et les creux s'enfoncent. L'algorithme s'exécute exactement comme le prescrit Unity ; le spike se contente de post-traiter le résultat avec un demi-décalage afin que l'« amplitude » corresponde à ce qu'une personne s'attend à voir en manipulant un curseur.</p>
<p>Le plan de référence a permis de trancher un dernier point. Un curseur « Étapes » semblait ne rien faire, donnant l'impression d'un bug de raccordement alors que ce n'en était pas un. L'affinement par sécante en trois itérations, effectué après la recherche linéaire, est si efficace — l'article de Tatarchuk de 2006 sur le POM indique qu'une recherche en 4 étapes suivie de 3 étapes de sécante est visuellement indiscernable d'une recherche en 64 étapes — que, sur une heightmap lisse, tous les nombres d'étapes de 4 à 64 convergent vers les mêmes UV sous-texel. La solution a été d'ajouter une option d'activation plutôt que de refaire le raccordement : lorsque la sécante est désactivée, le curseur d'étapes devient le seul réglage de la précision de l'intersection. Descendre à 4 fait donc apparaître des paliers visibles sur les pavés, tandis que remonter à 64 les lisse de nouveau. Cette option repose sur une uniforme 0/1 qui utilise <code>mix</code> pour transformer chaque mise à jour de l'état de la sécante en opération neutre lorsqu'elle est désactivée. Son basculement ne reconstruit donc jamais le matériau et ne provoque aucune saccade.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Parallax occlusion mapping en TSL.</strong> Le POM fait progresser par ray marching la direction de vue à travers un champ de hauteur dans l'espace tangent, trouve la première couche où le rayon passe sous la surface, puis affine l'intersection. Il produit ainsi la profondeur de joints en retrait sur un quadrilatère plat, sans géométrie supplémentaire. Un dernier <code>mix(baseUV, refined, done)</code> rend le matériau strictement identique bit à bit à la version plate lorsqu'aucun fragment ne rencontre la surface. C'est ce qui permet à l'atténuation de l'amplitude par LOD selon la distance de ramener le coût à celui d'une texture plate au loin. Voir les <a href="/fr/guides/landscape-generation-browser#terrain-materials">matériaux de terrain</a>.</p>
<p><strong>Boucles sans branchement pour le contrôle de flux WebGPU.</strong> Avec Three.js r184, un <code>If(...).and(...)</code> TSL peut produire du WGSL dont le corps de boucle ne s'exécute jamais, et la forme autonome de <code>Break()</code> n'est pas disponible. La solution portable consiste à échantillonner la texture sans condition à chaque itération — ce qui maintient l'accès à la texture dans un flux de contrôle uniforme conformément à la spécification WGSL — et à utiliser un indicateur <code>done</code> stocké sous forme de nombre flottant, qui transforme avec <code>mix</code> chaque mise à jour d'état en opération neutre une fois activé. Un utilitaire <code>step</code> construit à partir de <code>sign(x + ε)</code> évite les conversions peu fiables des booléens en nombres flottants. Le coût reste égal au nombre maximal d'itérations, quel que soit le point de sortie anticipée : c'est le bon compromis à l'échelle du fragment.</p>
<p><strong>Portage littéral des shaders.</strong> Le portage depuis un autre moteur d'un shader dont le bon fonctionnement est établi doit d'abord reprendre l'original ligne par ligne et conserver ses noms de variables, avant d'être adapté au style local. Le mélange de deux références — le fichier <code>PerPixelDisplacement.hlsl</code> d'Unity et le tutoriel LearnOpenGL — a produit un hybride avec un décalage d'une unité dans la hauteur de référence du rayon, un signe de décalage inversé et une formule d'affinement dont la limitation masquait les poids hors plage sous forme de discontinuités spatiales. Il faut une seule vérité terrain canonique, pas une redérivation.</p>
<p><strong>Référence en géométrie réelle déplacée par sommets.</strong> En l'absence de tessellation matérielle dans WebGPU, un plan densément subdivisé — 256² segments — et déplacé dans l'étape de vertex shader tient lieu de géométrie réelle pour valider une simulation exécutée à l'étape de fragment shader. Piloter les deux avec la même uniforme d'amplitude garantit une comparaison honnête à toutes les distances. Le POM est limité par les fragments — son coût dépend du nombre de pixels couverts — tandis que le plan géométrique est limité par les sommets — son coût dépend de la densité du maillage. Ils divergent donc précisément sur les silhouettes, ce qui prouve que le flottement des bords du POM est inhérent à l'algorithme et non un artefact.</p>
<hr>
<p>Partie 20 sur 29.
Précédent : <a href="/fr/blog/2026-05-04-open-world-browser-part-19-imposters">Partie 19 - L'imposteur qui doit survivre à une forêt</a>
Suivant : <a href="/fr/blog/2026-05-06-open-world-browser-part-21-visibility-buffer">Partie 21 - Un moteur de rendu plus rapide qui ne l'était pas</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer un monde ouvert dans le navigateur, partie 19 : l’imposteur qui doit survivre à une forêt]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-04-open-world-browser-part-19-imposters</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-04-open-world-browser-part-19-imposters</guid>
            <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 38 a réduit un arbre à deux textures sur un billboard, puis une réécriture KISS et le choix d’un N impair ont permis d’obtenir une correspondance parfaite. Les spikes 41 et 42 ont supprimé les sauts grâce au mélange hémi-octaédrique et transféré un million d’arbres sur le GPU.]]></description>
            <content:encoded><![CDATA[<h1>Créer un monde ouvert dans le navigateur, partie 19 : l’imposteur qui doit survivre à une forêt</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-02-open-world-browser-part-18-ai-scattering">partie 18</a> a donné aux créateurs un pinceau capable de remplir un versant d’arbres. Le problème, c’est le coût de ces arbres lorsqu’ils sont des dizaines de milliers à l’écran. Un arbre lointain n’a pas besoin de 2 000 triangles pour occuper quatre pixels. Cette partie traite du niveau de LOD le plus profond : l’imposteur, un quad plat portant une image de l’arbre, et du chemin parcouru entre « un arbre qui paraît juste » et un million d’arbres sur le GPU.</p>
<h2>Un arbre, ce sont deux textures sur un billboard</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/38-imposters/" title="Spike 38 sur les imposteurs octaédriques" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/38-imposters/" target="_blank">Ouvrir le spike 38 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=38-imposters">Voir le code source</a></p>
<p>Un imposteur pré-rend un objet depuis une grille d’angles de vue dans deux atlas de textures, l’un pour la couleur et l’autre pour les normales en espace monde, puis affiche à l’exécution un unique quad orienté vers la caméra qui échantillonne la tuile correspondant à la vue actuelle. La cuisson se fait en deux passes par tuile : la couleur diffuse avec un matériau non éclairé, afin qu’aucun éclairage ne soit intégré à la texture, et les normales encodées sous la forme <code>normalWorld × 0.5 + 0.5</code>, l’alpha étant repris depuis la source afin que la silhouette corresponde pixel pour pixel. Le matériau d’exécution est un <code>MeshStandardNodeMaterial</code> complet : l’imposteur reçoit donc toujours le soleil et l’IBL de la scène comme n’importe quelle autre surface. Le gain vient de la géométrie : un seul quad au lieu de milliers de triangles, les détails étant stockés dans une texture de 1 Mo.</p>
<p>Le fait d’isoler ce travail dans son propre spike a constitué une leçon en soi. L’imposteur avait commencé comme le LOD le plus profond du système de dispersion du spike 37, et chaque itération sur la cuisson devait être testée à travers toute la chaîne de dispersion, la validité de la cuisson étant entremêlée avec la migration des matrices d’instances et les changements de LOD. Le fait de le séparer pour n’avoir qu’un objet et un quad, côte à côte avec l’original, a réduit le temps d’itération de plusieurs minutes à quelques secondes.</p>
<h2>Quand la réponse académique est la mauvaise</h2>
<p>La première implémentation utilisait un encodage octaédrique, la méthode classique pour projeter les directions d’une sphère dans un carré. Elle réussissait les tests numériques d’aller-retour, et pourtant l’utilisateur continuait à envoyer des captures où l’imposteur sautait sur une tuile montrant l’arbre légèrement vu du dessus plutôt que de face. Six séries de corrections ont suivi — une uniforme de direction de vue par quad, une cuisson au format carré, des matériaux de débogage et un billboarding statique — et chacune était réellement nécessaire, mais aucune ne corrigeait le véritable bug. La solution n’est apparue qu’avec cette consigne : « tout repenser depuis zéro, KISS, sans rustines ».</p>
<p>La réécriture a abandonné le repliement octaédrique au profit d’un simple azimut par élévation : <code>az = atan2(dir.x, dir.z)</code>, <code>el = asin(dir.y)</code>, <code>uv = (az/2π, el/π + ½)</code>. C’est tout l’encodage : aucune normalisation L1, aucun cas limite lié au signe de zéro. Cette méthode n’est pas meilleure ici parce qu’elle serait plus précise — elle échantillonne la sphère de façon moins uniforme — mais parce que le sélecteur de cellules sur le CPU et le shader sur le GPU utilisent les mêmes primitives. Ils ne peuvent donc pas diverger sur une direction située à la limite, contrairement à ce qui se produisait discrètement avec la paire octaédrique. Dans la surimpression, l’atlas se lit comme une planche-contact : la colonne indique l’angle autour de l’objet et la ligne son élévation, ce qui est immédiatement compréhensible.</p>
<p>Même après cela, le reproche de l’aspect « légèrement vu du dessus » persistait, et sa cause était un choix de quantification, pas l’encodage. Avec une grille 4×4, les centres des lignes se trouvent à ±22,5° et ±67,5° : il n’existe donc aucune ligne à exactement 0° d’élévation. Un observateur regardant à l’horizontale — le cas de loin le plus fréquent — tombe toujours sur une ligne cuite avec une inclinaison. La solution consiste à utiliser un N impair : dans une grille 5×5, les centres des lignes se trouvent à 0°, ±36° et ±72°, de sorte que l’observateur horizontal obtient une tuile cuite exactement à l’horizontale. La même famille d’erreurs de « décalage d’une demi-cellule » réapparaît dans le travail sur la parallaxe de la partie suivante, avec le même remède : mon point d’échantillonnage discret se trouve-t-il réellement là où je pense pour l’entrée canonique ?</p>
<p>Deux autres éléments ont compté. Le billboard doit être statique par morceaux, et non continuellement orienté vers la caméra. L’imposteur est une image plate prise depuis une direction de cuisson précise : le plan de l’image du quad à l’exécution doit donc correspondre au plan de cette caméra de cuisson. Cela signifie qu’il conserve son orientation sur tout l’arc où la même cellule reste sélectionnée, puis saute à la limite. Quant au budget, il doit privilégier les directions dans lesquelles les joueurs regardent réellement : une passe ultérieure a entièrement supprimé les lignes inclinées vues du dessus au profit de 24 emplacements disposés sur un anneau horizontal, espacés de 15°, plus une tuile vue directement du dessus, car les arbres sont presque toujours observés à hauteur des yeux.</p>
<h2>Le saut, et comment le mélange l’a effacé</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/42-imposter-flicker/" title="Spike 42 comparant le scintillement des imposteurs" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/42-imposter-flicker/" target="_blank">Ouvrir le spike 42 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=42-imposter-flicker">Voir le code source</a></p>
<p>Le billboard statique par morceaux est imperceptible à distance, mais produit un saut de près, ce qui ne pose aucun problème jusqu’à ce qu’on tourne autour. Le spike 42 a placé quatre variantes côte à côte — l’objet original, la version de référence azimut/élévation 5×5 et deux grilles hémi-octaédriques — afin d’isoler le scintillement et de le supprimer. Deux artefacts sont à l’origine du saut. Le changement de cellule se produit parce que le fragment shader quantifie la direction de vue dans l’une des 25 cellules : franchir une limite remplace donc la tuile échantillonnée et réoriente le quad au cours de la même image. La dégénérescence au pôle correspond à la vue du dessus, où tous les azimuts se réduisent à un même point et où la jonction entre l’anneau et la tuile supérieure constitue la pire transition de l’atlas.</p>
<p>La projection hémi-octaédrique corrige ces deux problèmes. Elle projette continûment l’hémisphère supérieur sur le carré unité : des directions 3D adjacentes aboutissent ainsi sur des UV adjacents, sans singularité au pôle ni besoin d’une tuile spéciale vue du dessus. Le scintillement est supprimé grâce au mélange bilinéaire des cellules : au lieu de sauter vers la tuile la plus proche, on trouve le groupe de 2×2 tuiles encadrant la direction encodée et on mélange les quatre, pour un total de 8 lectures de texture — 4 diffuses et 4 normales. Les vues adjacentes se fondent désormais l’une dans l’autre au lieu de produire un saut. Le mélange de deux vecteurs unitaires n’étant pas lui-même de longueur unitaire, la normale obtenue est renormalisée, ce qui se comporte comme une slerp pour les petits angles séparant les tuiles voisines. Le coût est réel — un atlas 12×12 pèse environ 9 Mo contre 1,6 Mo pour la version azimut/élévation, et la cuisson prend à peu près cinq fois plus de temps avec 288 passes vers des cibles de rendu — mais la cuisson n’a lieu qu’une seule fois au chargement, et le mélange produit un résultat sans saut. C’est ce qui rend les imposteurs utilisables lorsque la caméra se déplace réellement.</p>
<h2>Un million d’arbres, une seule copie de la position de la caméra par image</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/41-imposter-forest/" title="Spike 41 sur la forêt d’imposteurs" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/41-imposter-forest/" target="_blank">Ouvrir le spike 41 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=41-imposter-forest">Voir le code source</a></p>
<p>Le code d’exécution du spike 38 effectue un <code>lookAt</code> sur le CPU pour chaque quad à chaque image, ce qui convient pour un arbre mais devient fatal pour une forêt. Avec un million d’arbres, les mises à jour des matrices à chaque image et le transfert du tampon d’instances domineraient tout le reste. Le spike 41 transfère l’intégralité de la chaîne de traitement par image sur le GPU. Le centre, le lacet et l’échelle de chaque instance sont envoyés une seule fois à la construction sous forme d’attributs instanciés, puis ne changent plus. Le vertex shader construit la base du billboard à partir de la direction de vue en espace monde <code>camPos − center</code> et déploie un quad unité partagé dans l’espace monde. Le fragment shader effectue l’encodage hémi-octaédrique et le mélange bilinéaire pour chaque pixel. Pour toute la forêt, l’unique travail du CPU à chaque image est un seul <code>Vector3.copy</code> mettant à jour l’uniforme de position de la caméra, un coût qui ne dépend absolument pas du nombre d’arbres.</p>
<p>Un aspect élégant des mathématiques est que le lacet propre à chaque instance s’annule lors du décodage des normales. La cuisson stocke les normales dans le repère de la caméra de cuisson et, puisqu’une rotation de lacet autour de l’axe vertical du monde conserve +Y et que le produit vectoriel est équivariant par rotation, la base d’exécution construite avec une référence verticale en espace monde correspond déjà à la base de cuisson après rotation. Le shader décode donc directement les normales à travers les varyings de la base d’exécution, sans jamais utiliser le lacet propre à l’instance. Le placement utilise une grille avec jitter plutôt qu’une dispersion purement aléatoire : la zone est divisée en cellules, puis un arbre est placé dans chacune d’elles au centre avec un décalage limité. Cela garantit un espacement minimal — aucun arbre ne se superpose à un autre — tout en conservant l’apparence d’une forêt naturelle. Un détail facile à oublier concerne la sphère englobante. Le modèle géométrique n’est qu’un quad unité : three.js éliminerait donc toute la forêt par culling de frustum dès que la caméra ne regarderait plus vers l’origine. Définir explicitement une sphère englobante couvrant toute la zone, plus la marge d’un quad, empêche les arbres situés aux angles d’être tronqués lorsque la caméra les observe sous un angle rasant.</p>
<h2>Technologies présentées dans ce chapitre</h2>
<p><strong>Atlas d’imposteurs octaédriques et azimut-élévation.</strong> Un imposteur cuit un objet depuis une grille de directions de vue dans un atlas diffus et un atlas de normales en espace monde, puis affiche un unique billboard échantillonnant la tuile correspondante, remplaçant ainsi des milliers de triangles par deux textures. La projection octaédrique classique offre une couverture uniforme de la sphère, mais elle est sujette aux divergences CPU/GPU aux limites du repliement. Une simple grille azimut par élévation échantillonne la sphère de manière moins uniforme, mais garantit par construction que le sélecteur de cellules et le shader donnent le même résultat. Utilisez un N impair afin qu’une ligne tombe exactement à 0° d’élévation, et consacrez le budget de tuiles à l’anneau horizontal puisque les objets sont principalement observés à hauteur des yeux.</p>
<p><strong>Orientation du billboard statique par morceaux.</strong> Un imposteur est une image prise depuis une direction de cuisson précise : le plan de l’image du quad à l’exécution doit donc correspondre au plan de la caméra de cuisson, plutôt que de faire continuellement face à la caméra d’exécution. Le quad conserve son orientation sur tout l’arc où une même cellule reste sélectionnée, puis saute à la limite. Ce saut est invisible à la distance d’utilisation d’un imposteur et ne devient perceptible que de près, là où les imposteurs ne sont pas utilisés.</p>
<p><strong>Atlas hémi-octaédrique avec mélange bilinéaire des cellules.</strong> La projection continue de l’hémisphère supérieur sur le carré unité supprime la singularité au pôle et la tuile spéciale vue du dessus. Le saut disparaît en échantillonnant le groupe de 2×2 tuiles encadrant la direction de vue encodée et en mélangeant bilinéairement les quatre tuiles — soit 8 lectures — afin que les vues adjacentes se fondent l’une dans l’autre. Les normales mélangées sont renormalisées, ce qui approxime une slerp sur le faible angle séparant les tuiles. Le prix à payer est un atlas plus grand — environ 9 Mo en 12×12 — et une cuisson unique plus longue, en échange d’un éclairage sans saut lorsque la caméra se déplace.</p>
<p><strong>Imposteurs instanciés pilotés par le GPU.</strong> Le centre, le lacet et l’échelle propres à chaque instance sont envoyés une seule fois sous forme d’attributs instanciés. Le vertex shader construit la base du billboard et déploie un quad unité partagé, tandis que le fragment shader effectue l’encodage et le mélange pour chaque pixel. Pour toute la forêt, le coût CPU par image se limite à une seule copie de l’uniforme de position de la caméra, indépendamment du nombre d’instances. Le lacet propre à chaque instance s’annule lors du décodage des normales, car le lacet autour de l’axe vertical du monde est préservé par la construction de la base au moyen du produit vectoriel. Une sphère englobante explicite couvrant toute la forêt empêche three.js d’éliminer par culling de frustum l’ensemble du maillage instancié lorsque la caméra ne regarde plus vers l’origine du quad unité servant de modèle. Voir <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD piloté par le GPU</a>.</p>
<hr>
<p>Partie 19 sur 29.
Précédente : <a href="/fr/blog/2026-05-02-open-world-browser-part-18-ai-scattering">Partie 18 — Un pinceau de dispersion qui semble guidé par l’IA</a>
Suivante : <a href="/fr/blog/2026-05-05-open-world-browser-part-20-parallax-occlusion">Partie 20 — Simuler la profondeur sur un plan plat</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nuages volumétriques et effets météorologiques dans les jeux modernes]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-04-volumetric-clouds-and-weather</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-04-volumetric-clouds-and-weather</guid>
            <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Comment les jeux modernes rendent les nuages volumétriques, la diffusion atmosphérique, le brouillard, la pluie, la neige et la météo dynamique, avec les articles fondateurs et les présentations de moteurs à l’origine de chaque technique.]]></description>
            <content:encoded><![CDATA[<h1>Nuages volumétriques et effets météorologiques dans les jeux modernes</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<img src="/img/blog/clouds_weather_hero.webp" alt="Scène AAA stylisée avec d’imposants nuages d’orage volumétriques, des éclairs, des rideaux de pluie et une route pavée mouillée reflétant le ciel" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Il y a quelques semaines, j’ai écrit sur <a href="/fr/blog/2026-05-03-aaa-rendering-techniques">les techniques de rendu réellement utilisées dans les jeux AAA modernes</a>. Un domaine que je n’avais fait qu’effleurer était le ciel et la météo, car il mérite sa propre liste. Les nuages, le brouillard, la pluie et la neige sont les systèmes qui transforment une démo de terrain en un véritable lieu. Ils partagent aussi bien plus de code qu’il n’y paraît. Les nuages volumétriques, le brouillard au sol et les rayons crépusculaires reposent tous sur le même ray marching. Les routes mouillées, l’accumulation de neige et les traces de pas utilisent toutes la même combinaison de déplacement et d’astuces PBR. Le vent est un unique vecteur de direction que tous les éléments de la scène consultent.</p>
<p>Voici un tour d’horizon bref et assumé de la façon dont les grands studios conçoivent ces effets en 2026, accompagné des articles et présentations de moteurs à l’origine de chaque élément.</p>
<h2>1. Ciel et atmosphère physiquement réalistes</h2>
<p>La diffusion atmosphérique est la base. La couleur du ciel, la brume à l’horizon, la teinte bleutée des montagnes lointaines et l’orange du coucher de soleil proviennent tous de la diffusion de la lumière dans l’air. Les moteurs modernes calculent ces phénomènes à partir de la physique : diffusion de Rayleigh pour le bleu, diffusion de Mie pour le halo autour du soleil et absorption par l’ozone pour le violet profond au zénith.</p>
<p>La méthode originale de Bruneton, publiée en 2008, précalculait tout dans des tables de correspondance 4D, ce qui limitait la variation dynamique de l’heure et produisait des artefacts de LUT lorsque le soleil était bas. La mise à jour de Sébastien Hillaire datant de 2020, utilisée par le composant Sky Atmosphere d’UE5, remplace la LUT multidimensionnelle par quelques textures 2D et une approximation de la diffusion multiple actualisée à chaque image. Elle fonctionne aussi bien sur téléphone que sur PC haut de gamme.</p>
<img src="/img/blog/clouds_weather_atmosphere.webp" alt="Ciel physiquement réaliste à l’heure dorée, avec un dégradé fluide de l’orange au bleu au-dessus de la silhouette de montagnes lointaines" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Hillaire, <a href="https://sebh.github.io/publications/egsr2020.pdf">Une technique évolutive et prête pour la production pour le rendu du ciel et de l’atmosphère</a> (EGSR 2020, la référence moderne utilisée dans UE5).</li>
<li>Bruneton et Neyret, <a href="https://hal.inria.fr/inria-00288758/document">Diffusion atmosphérique précalculée</a> (EGSR 2008, l’approche originale par LUT).</li>
<li>Bruneton, <a href="https://ebruneton.github.io/precomputed_atmospheric_scattering/">Diffusion atmosphérique précalculée : une nouvelle implémentation</a> (implémentation de référence open source, avec prise en charge de l’ozone et de plusieurs planètes).</li>
<li>Epic Games, <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/sky-atmosphere-component-in-unreal-engine">Composant Sky Atmosphere</a> (documentation d’UE5).</li>
</ul>
<h2>2. Nuages volumétriques avec du bruit Perlin-Worley</h2>
<p>La technique fondamentale des nuages dans les jeux modernes est apparue dans la présentation d’Andrew Schneider consacrée à Horizon Zero Dawn en 2015. Les nuages ne sont pas des maillages. Ce sont des fonctions de densité 3D définies par plusieurs couches de bruit : un mélange Perlin-Worley à basse fréquence donne la forme générale du nuage, tandis qu’un bruit de Worley à plus haute fréquence érode sa silhouette pour créer des bords vaporeux. Une carte météorologique — une texture 2D échantillonnée selon les coordonnées XZ du monde — contrôle la couverture, le type de nuage et les précipitations dans chaque région. Un gradient dépendant de la hauteur effectue une transition entre les profils de cumulus, de stratus et de cirrus selon l’altitude.</p>
<p>Le moteur de rendu fait progresser un rayon depuis la caméra à travers le volume nuageux, en accumulant la densité et la diffusion. L’itération « Nubis » de 2017 a ajouté la création et l’animation à l’échelle régionale, et l’implémentation originale sur PS4 rendait l’ensemble du ciel en environ 2 ms. La plupart des studios qui proposent aujourd’hui des nuages volumétriques s’inscrivent encore dans la lignée de cet article.</p>
<img src="/img/blog/clouds_weather_noise.webp" alt="Forme de nuage 3D décomposée en motifs superposés de bruits de Perlin et de Worley, montrant comment les détails érodent la silhouette" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Schneider, <a href="https://www.guerrilla-games.com/read/the-real-time-volumetric-cloudscapes-of-horizon-zero-dawn">Les paysages nuageux volumétriques en temps réel de Horizon Zero Dawn</a> (SIGGRAPH 2015, la référence incontournable).</li>
<li>Schneider, <a href="https://advances.realtimerendering.com/s2017/Nubis%20-%20Authoring%20Realtime%20Volumetric%20Cloudscapes%20with%20the%20Decima%20Engine%20-%20Final%20.pdf">Nubis : création de paysages nuageux volumétriques en temps réel avec le moteur Decima</a> (SIGGRAPH 2017, la suite consacrée à l’échelle régionale).</li>
<li>Hillaire, <a href="https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf">Rendu physiquement réaliste du ciel, de l’atmosphère et des nuages dans Frostbite</a> (SIGGRAPH 2016, la version de Frostbite).</li>
<li>Häggström, <a href="https://www.diva-portal.org/smash/get/diva2:1223894/FULLTEXT01.pdf">Rendu en temps réel de nuages volumétriques</a> (une thèse claire et accessible comprenant l’intégralité du code des shaders).</li>
</ul>
<h2>3. Nuages voxelisés et Nubis³</h2>
<p>L’évolution de Nubis présentée en 2023 a entièrement abandonné la représentation 2,5D des formes au profit de véritables voxels 3D. Chaque voxel stocke directement la densité du nuage, ce qui permet aux artistes de sculpter et d’animer les formes nuageuses comme ils modèlent un terrain. Le coût d’une représentation plus dense est compensé par l’accélération du ray marching à l’aide de champs de distance signés compressés et d’une ingénieuse remise à l’échelle des données voxelisées clairsemées.</p>
<p>Le résultat produit des paysages nuageux que l’on peut traverser <em>en vol</em> sans que les artifices sous-jacents ne deviennent visibles. C’est excessif pour la plupart des studios, mais c’est la direction prise par les productions haut de gamme.</p>
<img src="/img/blog/clouds_weather_voxel_clouds.webp" alt="Cumulus décomposé en grille de voxels 3D, avec des bords vaporeux lissés qui se fondent dans son intérieur anguleux" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Schneider, <a href="https://www.guerrilla-games.com/read/nubis-cubed">Nubis³ : méthodes (et folie) pour modéliser et rendre des nuages immersifs en temps réel à base de voxels</a> (SIGGRAPH 2023, la présentation sur les nuages voxelisés).</li>
<li>Schneider, <a href="https://www.guerrilla-games.com/read/nubis-evolved">Nubis, nouvelle évolution</a> (SIGGRAPH 2022, le pont entre la 2,5D et la 3D complète).</li>
<li>Schneider, <a href="https://www.schneidervfx.com/">Effets volumétriques et VFX en temps réel</a> (site personnel d’Andrew, avec notes de cours et analyses détaillées).</li>
</ul>
<h2>4. Rendu des nuages en couches et arrière-plans 2D</h2>
<p>Tous les studios ne peuvent pas se permettre des nuages entièrement volumétriques, et tous les angles de caméra n’en ont pas besoin. De nombreux jeux combinent plusieurs techniques : des cirrus de haute altitude rendus sous forme de couche 2D défilante, des cumulus d’altitude moyenne rendus par ray marching volumétrique et des stratus de basse altitude représentés par une fine couche de milieu participant. À l’horizon, on trouve souvent une cubemap de ciel précalculée, dans laquelle la passe volumétrique se fond au-delà d’une certaine distance.</p>
<p>Cette superposition permet de maîtriser le budget des nuages. Un seul type de nuage en qualité maximale peut consommer 4 à 6 ms ; l’emploi de niveaux de qualité différents selon l’altitude des nuages permet de conserver le même rendu pour moitié moins cher.</p>
<img src="/img/blog/clouds_weather_cloud_layers.webp" alt="Ciel au coucher du soleil divisé en trois couches nuageuses : fins cirrus en altitude, cumulus à mi-hauteur et brume de stratus près de l’horizon" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Vos, <a href="https://www.gdcvault.com/play/1023327/The-Real-Time-Volumetric-Cloudscapes">Les paysages nuageux volumétriques en temps réel de Horizon Zero Dawn</a> (vidéo GDC 2016, l’analyse des différentes couches se trouve dans la seconde moitié).</li>
<li>Bauer, <a href="https://www.youtube.com/watch?v=IYxtqlD1-Ac">Création du monde atmosphérique de Red Dead Redemption 2</a> (SIGGRAPH 2019, pipeline combiné de RDR2 pour le ciel, les nuages et les effets volumétriques).</li>
<li>Hillaire, <a href="https://www.gdcvault.com/play/1029105/Volumetric-Clouds-and">Nuages volumétriques et mégaparticules dans REDengine 4</a> (GDC 2025, pipeline de nuages de Cyberpunk 2077).</li>
</ul>
<h2>5. Brouillard volumétrique avec des grilles de froxels</h2>
<p>Le brouillard est un champ 3D, pas un effet 2D en espace écran. L’approche moderne standard repose sur une grille de froxels : une texture 3D alignée sur le tronc de vision de la caméra, dont chaque cellule (« froxel » = frustum + voxel) stocke la densité et la couleur éclairée. Un compute shader injecte dans la grille la diffusion provenant de chaque source lumineuse, accumule l’extinction le long du rayon de vue, puis applique le résultat lors d’une passe plein écran.</p>
<p>C’est ce qui permet d’obtenir des faisceaux lumineux à travers les fenêtres, du brouillard coloré autour des sources ponctuelles et des volumes visibles autour des explosions. C’est aussi le mécanisme sous-jacent de la « perspective atmosphérique », qui fond les objets lointains dans l’air ambiant. Cette technique a été introduite par Bart Wronski pour Assassin’s Creed 4, puis standardisée par Sébastien Hillaire dans Frostbite.</p>
<img src="/img/blog/clouds_weather_froxel_fog.webp" alt="Tronc de vision de la caméra représenté sous forme de grille de froxels 3D, avec de petites cellules près de la caméra, de plus grandes au loin et des particules de brouillard à l’intérieur" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Wronski, <a href="https://bartwronski.com/wp-content/uploads/2014/08/bwronski_volumetric_fog_siggraph2014.pdf">Brouillard volumétrique : une solution unifiée de diffusion atmosphérique basée sur les compute shaders</a> (SIGGRAPH 2014, l’article original sur les grilles de froxels).</li>
<li>Hillaire, <a href="http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/">Rendu volumétrique unifié et physiquement réaliste dans Frostbite</a> (SIGGRAPH 2015, l’implémentation prête pour la production).</li>
<li>Kovalovs, <a href="https://history.siggraph.org/wp-content/uploads/2022/08/2020-Talks-Kovalovs_Volumetric-Effects-of-The-Last-of-Us-Part-Two.pdf">Effets volumétriques de The Last of Us Part Two</a> (SIGGRAPH 2020, avec des notes sur le jitter temporel et la composition tenant compte de la profondeur).</li>
<li>Wright et al., <a href="https://advances.realtimerendering.com/s2022/SIGGRAPH2022-Advances-Lumen-Wright%20et%20al.pdf">Lumen : illumination globale en temps réel dans Unreal Engine 5</a> (SIGGRAPH 2022, traite notamment de l’interaction entre Lumen et le brouillard volumétrique).</li>
</ul>
<h2>6. Rayons divins et faisceaux crépusculaires</h2>
<p>Les rayons de soleil visibles dans un air brumeux ne constituent pas un effet distinct. Ils découlent du même système de brouillard, à condition que la densité du brouillard et la shadow map soient toutes deux accessibles au même compute shader. Lorsque le shader injecte de la lumière dans un froxel, il échantillonne la shadow map à la position de ce froxel dans le monde. Les cellules dans l’ombre restent sombres, tandis que celles exposées à la lumière prennent la couleur du soleil. En parcourant le résultat avec le rayon de la caméra, les cellules lumineuses forment des faisceaux continus.</p>
<p>Il existe des variantes moins coûteuses en espace écran — un flou radial partant de la position du soleil et appliqué au tampon de profondeur — qui restent le bon choix sur mobile ou sur du matériel peu puissant. Elles ne fonctionnent pas lorsque le soleil est hors champ, mais ne coûtent presque rien.</p>
<img src="/img/blog/clouds_weather_god_rays.webp" alt="Forêt à l’aube, avec des rayons de soleil traversant les troncs et le brouillard au sol pour former des faisceaux lumineux bien définis" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Mitchell, <a href="https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-13-volumetric-light-scattering-post-process">Diffusion volumétrique de la lumière comme post-traitement</a> (GPU Gems 3, l’approche en espace écran par flou radial).</li>
<li>Engelhardt et Dachsbacher, <a href="https://cg.ivd.kit.edu/publications/2010/epipolar/EpipolarSampling.pdf">Échantillonnage épipolaire pour les ombres et les rayons crépusculaires dans les milieux participants</a> (I3D 2010, la technique GPU plus précise).</li>
<li>Vos, <a href="https://www.guerrilla-games.com/read/volumetric-light-effects-in-killzone-shadow-fall">Effets de lumière volumétrique dans Killzone: Shadow Fall</a> (SIGGRAPH 2014, la version de production avec intégration des ombres).</li>
</ul>
<h2>7. Éclairs et événements météorologiques stochastiques</h2>
<p>Un éclair est un effet d’une seule image composé de deux parties : le maillage de l’éclair et la réaction globale de l’éclairage et du mappage tonal de la scène. L’éclair lui-même est généralement un maillage de billboards procédural construit à l’aide d’un algorithme récursif de subdivision de segments, perturbé aléatoirement pour créer un aspect chaotique et aminci vers le sol. Certains moteurs le rendent comme un flash additif en espace écran, tandis que d’autres utilisent une géométrie émissive entièrement éclairée, qui illumine le monde grâce à l’injection d’une lumière ponctuelle pendant une image.</p>
<p>Le plus intéressant est tout ce qui se passe <em>autour</em> : la base des nuages s’éclaire par-dessous, le sol s’illumine pendant deux images, la mesure de l’exposition automatique met quelques images à se rétablir et un coup de tonnerre retardé est joué selon la distance. Bien exécutée, cette approche transforme un flash de 16 ms en une séquence de cinq secondes qui fait ressentir la météo comme un phénomène se produisant dans le monde, et pas simplement au-dessus de lui.</p>
<img src="/img/blog/clouds_weather_lightning.webp" alt="Éclair ramifié jaillissant d’un nuage d’orage au crépuscule et illuminant la base des nuages ainsi que la silhouette d’un petit village" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Reed et Wyvill, <a href="https://dl.acm.org/doi/10.1145/192161.192256">Simulation visuelle de la foudre</a> (SIGGRAPH 1994, l’algorithme récursif de génération d’éclairs encore utilisé par tous).</li>
<li>Kim et Lin, <a href="https://gamma.cs.unc.edu/POWER/">Animation rapide de la foudre à l’aide d’un maillage adaptatif</a> (IEEE TVCG 2007, une approche davantage fondée sur la physique).</li>
<li>Bauer, <a href="https://www.youtube.com/watch?v=IYxtqlD1-Ac">Création du monde atmosphérique de Red Dead Redemption 2</a> (SIGGRAPH 2019, pipeline de tempête de RDR2, notamment la synchronisation des éclairs).</li>
</ul>
<h2>8. Particules de pluie, maillages de pluie et gouttes en espace écran</h2>
<p>Dans les jeux modernes, la pluie qui tombe ne repose que rarement sur de simples particules. La solution à la fois économique et convaincante consiste à utiliser un petit ensemble de textures défilantes étirées sur des quadrilatères verticaux ou alignés sur l’écran, éclairés par les mêmes sondes de ciel et de soleil que le reste de la scène. Plus près de la caméra, des particules individuelles en forme de traînées ajoutent du détail. Sur l’objectif même de la caméra, des textures de gouttelettes, des coulures et des ondulations d’impact renforcent l’impression d’être « au cœur de la tempête ».</p>
<p>Le vent influe sur la direction de la pluie. Le même vecteur de vent déplace la carte météorologique des nuages, courbe l’herbe et incline les quadrilatères de pluie. Un seul vecteur à l’échelle de la scène, des dizaines de systèmes qui l’exploitent.
<img src="/img/blog/clouds_weather_rain_storm.webp" alt="Violent orage nocturne, avec des rideaux de pluie éclairés par des phares lointains et des ondulations sur une route mouillée" style="width:100%;border-radius:8px;margin:1.5rem 0" /></p>
<p>Pour aller plus loin :</p>
<ul>
<li>Tatarchuk, <a href="https://gpuopen.com/wp-content/uploads/2018/04/Tatarchuk-Rain-Rendering-EGSR2006.pdf">Rendu en temps réel de pluie contrôlable par les artistes dans les environnements urbains</a> (EGSR 2006, la référence incontournable sur la pluie en couches).</li>
<li>Garg et Nayar, <a href="https://www1.cs.columbia.edu/CAVE/projects/rain_ren/rain_ren.php">Rendu photoréaliste des traînées de pluie</a> (SIGGRAPH 2006, la physique de la lumière traversant les gouttes de pluie).</li>
<li>Wojciechowski, <a href="https://www.gdcvault.com/play/1027382/Rain-In-Cyberpunk-2077">La pluie dans Cyberpunk 2077</a> (GDC 2021, une présentation détaillée d'une production moderne).</li>
</ul>
<h2>9. Surfaces mouillées, flaques et ondulations</h2>
<p>Une pluie qui ne modifie pas le sol paraît immédiatement artificielle. Les surfaces mouillées réagissent en assombrissant leur albédo (l'eau absorbe la lumière incidente), en aplatissant leurs normales (le film d'eau lisse la microsurface) et en réduisant leur rugosité (l'eau se comporte presque comme un miroir aux angles rasants). La modification du shader est minime, mais le changement visuel est considérable.</p>
<p>Les flaques reposent sur des masques : un masque basé sur la hauteur ou peint sur les sommets définit les creux qui se remplissent d'eau à mesure qu'augmente un paramètre d'« humidité ». Les ondulations utilisent des textures de normales en flipbook, déclenchées par les impacts des gouttes de pluie. Les meilleures implémentations font évoluer progressivement l'état d'humidité, afin qu'un long orage détrempe lentement le monde tandis qu'une brève averse n'assombrit que les points hauts.</p>
<img src="/img/blog/clouds_weather_wet_surfaces.webp" alt="Rue pavée mouillée de nuit, avec des flaques peu profondes reflétant des enseignes au néon et des cercles d'ondulation provoqués par les gouttes de pluie" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Lagarde et de Rousiers, <a href="https://seblagarde.wordpress.com/2015/07/14/siggraph-2014-moving-frostbite-to-physically-based-rendering/">Faire évoluer Frostbite vers le rendu physiquement réaliste 3.0, section 5.5</a> (SIGGRAPH 2014, les ajustements PBR de référence pour les surfaces mouillées).</li>
<li>Lagarde, <a href="https://seblagarde.wordpress.com/2011/08/17/feeding-a-physical-based-lighting-mode/">Adopter un modèle d'ombrage physiquement réaliste</a> (la série d'articles originale avec les calculs du PBR mouillé).</li>
<li>Cyanilux, <a href="https://www.cyanilux.com/tutorials/rain-effects-breakdown/">Analyse des effets de pluie</a> (une présentation accessible dans Shader Graph des flaques, ondulations et gouttelettes).</li>
</ul>
<h2>10. Accumulation et déformation de la neige</h2>
<p>La neige pose le problème symétrique de la pluie : le monde doit en garder la trace, pas simplement la recevoir. L'approche standard utilise une texture d'« accumulation de neige » vue du dessus, qui se remplit au fil du temps partout où le ciel est visible, visibilité calculée à partir d'une carte de profondeur ou d'ombres vue du dessus. Le shader du terrain échantillonne ce masque et fusionne le matériau de neige dans les zones ombragées, tout en appliquant un déplacement correspondant à l'épaisseur de neige dans les zones exposées.</p>
<p>Les empreintes de pas et les traces de pneus sont rendues dans une carte de déformation glissante centrée sur le joueur. À mesure que la caméra se déplace, les anciennes empreintes sortent de la zone et la texture se reboucle. Le shader du terrain ou de la neige échantillonne cette carte de déformation et abaisse les sommets aux endroits où elle a été modifiée. La neige de Battlefield 5 utilise pour cela la tessellation matérielle ; les approches moins coûteuses se contentent d'un maillage de terrain à haute densité avec déplacement des sommets.</p>
<img src="/img/blog/clouds_weather_snow_deformation.webp" alt="Paysage enneigé stylisé avec des empreintes récentes, des congères accumulées contre les rochers, des flocons en chute et des montagnes lointaines voilées de brume" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Barré-Brisebois, <a href="https://www.digitalfoundry.net/articles/digitalfoundry-2018-hands-on-with-battlefield-5s-closed-alpha">Prise en main de Battlefield 5 : l'importance des petits détails</a> (l'article de production consacré à la neige de Frostbite).</li>
<li>St-Amour, <a href="https://is.muni.cz/th/m2v6i/real-time_snow_deformation.txt?lang=cs">Déformation de la neige en temps réel</a> (une thèse présentant tous les détails de l'implémentation GPU).</li>
<li>Andersson, <a href="https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/chapter5-andersson-terrain-rendering-in-frostbite.pdf">Rendu de terrain dans Frostbite par projection procédurale de matériaux dans les shaders</a> (la base du système de projection sur laquelle repose l'accumulation de neige).</li>
</ul>
<h2>11. Le vent comme système global</h2>
<p>Le vent n'est pas un effet de particules. Dans les moteurs de production, il s'agit d'un unique vecteur global, parfois d'un champ 3D à basse résolution, que <em>chaque</em> système dynamique lit dans son shader de sommets. Les brins d'herbe se courbent, les branches oscillent, les tissus claquent, les feuilles dérivent, la pluie s'incline, la fumée est transportée et les cartes météorologiques des nuages défilent. Une variable uniforme mise à jour à chaque image, des dizaines de systèmes consommateurs.</p>
<p>La version plus élaborée utilise une « grille de vent » qui stocke la direction et l'intensité échantillonnées selon la position dans le monde, ce qui permet de créer des tempêtes avec des rafales localisées, des vallées abritées et des sillages derrière les bâtiments. La végétation reçoit aussi généralement un décalage par sommet précalculé lors de la création des assets, afin que des arbres identiques ne se balancent pas à l'unisson. Le résultat est un monde qui respire au même rythme.</p>
<img src="/img/blog/clouds_weather_wind_foliage.webp" alt="Vent violent couchant l'herbe et les arbres, avec des feuilles tourbillonnant au premier plan sous des nuages d'orage" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>McAuley, <a href="https://www.gdcvault.com/play/1022235/Rendering-the-World-of-Far">Le rendu du monde de Far Cry 4</a> (GDC 2015, avec notamment la grille de vent de Far Cry 4 pour la végétation).</li>
<li>Habel et Wimmer, <a href="https://www.cg.tuwien.ac.at/research/publications/2007/HABEL-2007-IIM/">Rendu réaliste en temps réel de paysages à l'aide de nuages de panneaux</a> (plus ancien, mais les calculs du vent appliqué à la végétation restent inchangés).</li>
<li>Frostbite, <a href="https://www.guerrilla-games.com/read/the-vegetation-of-horizon-zero-dawn">La végétation de Horizon Zero Dawn</a> (SIGGRAPH 2017, avec l'architecture du système de vent).</li>
</ul>
<h2>12. Tempêtes de sable, blizzards et météo dense</h2>
<p>Les phénomènes météorologiques violents constituent une catégorie de rendu à part entière. Une tempête de sable est un brouillard épais, opaque et aligné sur le sol, avec un fort biais directionnel et un brouillard de distance agressif. Un blizzard ajoute un déferlement de particules près de la caméra et réduit la visibilité. Les cendres volcaniques et la fumée utilisent la même architecture, avec des couleurs différentes.</p>
<p>Ce qui rend ces effets convaincants, ce ne sont pas les particules, mais le <em>couplage</em> : le soleil s'assombrit, le ciel se teinte, l'étalonnage colorimétrique du post-traitement change, l'ambiance sonore bascule, le son des pas se modifie et la voix du joueur, s'il en a une, devient étouffée. Le moteur de rendu n'est que le messager ; l'immersion vient du fait que tous les systèmes du jeu réagissent simultanément.</p>
<img src="/img/blog/clouds_weather_sandstorm.webp" alt="Mur de poussière orange et de sable progressant sur une plaine désertique, sous un ciel menaçant avec un bleu limpide à l'arrière" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Burley, <a href="https://www.gdcvault.com/play/1024954/The-Real-time-Sky-and">Le ciel et l'atmosphère en temps réel d'Uncharted: The Lost Legacy</a> (GDC 2018, avec des remarques sur la composition des phénomènes météorologiques denses).</li>
<li>Khalifa, <a href="https://www.gdcvault.com/play/1027587/Atmospheric-Weather-Effects-in-Forza">Effets météorologiques atmosphériques dans Forza Horizon 5</a> (GDC 2022, la météo moderne d'un monde ouvert).</li>
<li>Karis, <a href="https://advances.realtimerendering.com/s2021/">La technologie derrière la démo « Lumen in the Land of Nanite » d'Unreal Engine 5</a> (SIGGRAPH 2021, la pile volumétrique de la scène poussiéreuse dans la grotte).</li>
</ul>
<h2>13. Heure de la journée et ciels dynamiques</h2>
<p>Le cycle de la journée en temps réel est le multiplicateur qui justifie l'intégration de tous les autres systèmes de cette liste. La direction et la couleur du soleil évoluent sur un cycle de 24 minutes ou de 24 heures. La LUT atmosphérique est mise à jour selon l'angle du soleil. L'éclairage des nuages est recalculé à chaque image. Les cascades d'ombres se réorientent. Les sondes de réflexion s'actualisent. La couleur ambiante évolue. L'exposition du post-traitement s'adapte.</p>
<p>Obtenir ce résultat sans artefacts visibles repose principalement sur la mise en cache des textures et la stabilité temporelle. Les techniques rapides précalculent le ciel pour des angles fixes du soleil, puis interpolent entre eux ; les techniques plus lentes recalculent tout à chaque image. Le modèle atmosphérique de Hillaire de 2020 est suffisamment rapide pour être recalculé, ce qui explique son intégration dans UE5. La carte météorologique des nuages défile avec le vent, de sorte que la couverture évolue naturellement sans que personne ait à créer d'images clés.</p>
<img src="/img/blog/clouds_weather_time_of_day.webp" alt="Trois bandes verticales sur un même paysage : aube rose, midi bleu et coucher de soleil rouge, partageant toutes la même silhouette de colline" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Hillaire, <a href="https://sebh.github.io/publications/egsr2020.pdf">Une technique évolutive et prête pour la production de rendu du ciel et de l'atmosphère</a> (EGSR 2020, avec une analyse du cycle dynamique de la journée).</li>
<li>Pesce, <a href="https://www.realtimerendering.com/raytracinggems/rtg2/index.html">Rendu du ciel en temps réel : techniques et compromis</a> (chapitre de Ray Tracing Gems II, panorama moderne).</li>
<li>Bauer, <a href="https://www.youtube.com/watch?v=IYxtqlD1-Ac">Créer le monde atmosphérique de Red Dead Redemption 2</a> (SIGGRAPH 2019, avec le pipeline du cycle de 24 heures).</li>
</ul>
<h2>14. La machine à états météorologiques</h2>
<p>Sous l'ensemble se trouve une petite machine à états. La plupart des jeux proposent entre 4 et 12 états météorologiques — ciel dégagé, partiellement nuageux, couvert, pluie légère, forte pluie, orage, brouillard, neige, blizzard, tempête de sable — chacun étant défini par un ensemble de paramètres : couverture et type de nuages, vitesse et direction du vent, type et intensité des précipitations, teintes de la lumière ambiante, profil audio et étalonnage du post-traitement.</p>
<p>Les transitions sont des interpolations linéaires entre des ensembles de paramètres sur une durée de 30 à 120 secondes. La transition n'est pas un cas particulier : il s'agit simplement d'une interpolation entre deux états, chaque sous-système de rendu lisant les valeurs courantes des paramètres pour l'image en cours. La météo peut être scénarisée — une cinématique exige un orage —, déterminée par une graine — de manière reproductible pour chaque région et chaque journée en jeu, afin que deux joueurs dans le même monde voient la même météo —, ou entièrement définie sur une grille de régions. Les pipelines les plus propres considèrent ces trois méthodes comme différents ordonnanceurs écrivant dans le même tampon de paramètres.</p>
<img src="/img/blog/clouds_weather_state_machine.webp" alt="Comparaison côte à côte de la même scène sous un soleil dégagé à gauche et sous une pluie orageuse à droite, avec des surfaces mouillées et brillantes" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Bauer, <a href="https://www.youtube.com/watch?v=IYxtqlD1-Ac">Créer le monde atmosphérique de Red Dead Redemption 2</a> (SIGGRAPH 2019, avec le pipeline des états météorologiques).</li>
<li>Khalifa, <a href="https://www.gdcvault.com/play/1027587/Atmospheric-Weather-Effects-in-Forza">Effets météorologiques atmosphériques dans Forza Horizon 5</a> (GDC 2022, avec l'approche par fusion de paramètres).</li>
<li>Schneider, <a href="https://www.guerrilla-games.com/read/the-real-time-volumetric-cloudscapes-of-horizon-zero-dawn">Les paysages nuageux volumétriques en temps réel de Horizon Zero Dawn</a> (SIGGRAPH 2015, avec l'évolution des nuages pilotée par une carte météorologique).</li>
</ul>
<h2>15. Les moments cinématographiques</h2>
<p>Toute cette pile existe pour produire quelques moments emblématiques. Se tenir sur une crête tandis qu'un front orageux approche. Regarder un rayon de soleil percer dans une clairière sous la canopée. Sortir d'une grotte pour découvrir la neige. Traverser un cumulus en vol et voir sa lumière intérieure.</p>
<p>Ce sont les moments que les joueurs capturent en image. Ce sont aussi ceux où tous les systèmes ci-dessus doivent fonctionner simultanément : nuages volumétriques, diffusion atmosphérique, brouillard intégrant les ombres, PBR mouillé, vent dans la végétation, étalonnage colorimétrique selon l'heure de la journée et transition entre états météorologiques, le tout composant une seule image. Il suffit que l'un de ces éléments soit incorrect pour que la magie se brise.</p>
<img src="/img/blog/clouds_weather_cloud_sea.webp" alt="Vue plongeante sur une mer de nuages d'où émerge un unique sommet montagneux, avec les cimes nuageuses éclairées par le soleil et les vallées plongées dans l'ombre en contrebas" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<h2>Ce que cela implique pour le navigateur</h2>
<p>La plupart de ces techniques se transposent naturellement à WebGPU. Nous avons déjà livré un brouillard atmosphérique de base, la fusion de skyboxes équirectangulaires et une brume de distance en espace écran dans le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">moteur de monde ouvert pour navigateur</a>. Les éléments plus complexes — nuages entièrement volumétriques, brouillard sur grille de froxels, PBR mouillé avec flaques dynamiques, cartes de déformation de neige — constituent l'étape suivante évidente, maintenant que le pipeline de terrain est stable. L'échantillonnage par fragment de la couleur du brouillard depuis la skybox dans le spike 24 est une pièce de ce puzzle. La prochaine sera un ray marching de nuages dans un shader de calcul, alimentant la même LUT atmosphérique.</p>
<p>La bonne nouvelle, c'est que le niveau minimal du matériel exécutant les navigateurs est désormais suffisamment élevé. Les shaders de calcul WebGPU, les textures 3D, l'exécution indirecte et les requêtes d'horodatage sont tous disponibles. Le modèle atmosphérique de Hillaire de 2020 a été porté plusieurs fois vers WebGL. Nubis de Schneider dispose d'implémentations de référence open source en GLSL, qui se traduisent en WGSL au moyen de modifications mécaniques. Plus aucune contrainte de rendu n'empêche un jeu par navigateur d'avoir le même ciel qu'un jeu sur console. Il ne reste que des contraintes d'ingénierie, et ce sont précisément celles que nous aimons résoudre.</p>
<h2>Pour approfondir l'ensemble de la pile</h2>
<p>Si vous cherchez une source unique réunissant tous ces sujets, les archives SIGGRAPH « Advances in Real-Time Rendering in Games » (<a href="https://advances.realtimerendering.com/">advances.realtimerendering.com</a>) regroupent les présentations de référence sur la météo et l'atmosphère depuis 2014. Pour des analyses de production expliquant comment certains jeux rendent leur ciel et leur météo, les <a href="http://www.adriancourreges.com/blog/">articles de profilage GPU d'Adrian Courrèges</a> proposent des décompositions détaillées, image par image, de GTA V, Horizon Zero Dawn et Doom Eternal. Pour les calculs propres au ciel et à l'atmosphère, le <a href="https://www.scratchapixel.com/lessons/3d-basic-rendering/volume-rendering-for-developers/intro-volume-rendering.html">chapitre de scratchapixel.com consacré au rendu volumétrique</a> constitue l'introduction la plus accessible, tandis que le <a href="https://github.com/sebh/UnrealEngineSkyAtmosphere">dépôt de l'implémentation</a> open source de Hillaire sert de référence de qualité production.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Courte liste des techniques de rendu utilisées dans les jeux AAA modernes]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-03-aaa-rendering-techniques</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-03-aaa-rendering-techniques</guid>
            <pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Les principales techniques de rendu à l’origine des graphismes AAA actuels, de la géométrie virtualisée Nanite au path tracing ReSTIR et à la mise à l’échelle fondée sur le machine learning, avec des références techniques approfondies pour chacune.]]></description>
            <content:encoded><![CDATA[<h1>Courte liste des techniques de rendu utilisées dans les jeux AAA modernes</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<img src="/img/blog/aaa_rendering_hero.webp" alt="Scène composite présentant des matériaux PBR, une géométrie virtualisée et un éclairage par lancer de rayons dans un jeu AAA moderne" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Si vous examinez le pipeline de rendu d’un jeu AAA de 2026, vous constaterez que l’essentiel repose sur un petit ensemble de techniques employées par de nombreux studios. Les noms changent selon les moteurs, mais les principes restent les mêmes. Voici une courte liste des techniques qui font réellement le travail à l’écran, accompagnées d’une illustration et de quelques références techniques approfondies pour chacune.</p>
<h2>1. Rendu physiquement réaliste (PBR)</h2>
<p>Les matériaux sont décrits par des textures d’albédo, de rugosité, de métallicité, de normales et d’occlusion ambiante, puis éclairés par des shaders qui respectent la conservation de l’énergie (réflexion spéculaire de Cook-Torrance, réflexion diffuse de Lambert ou de Disney). C’est la base sur laquelle repose tout moteur AAA moderne. Si une surface conserve un aspect cohérent sous la lumière du soleil, d’une lampe ou d’une lampe torche, c’est grâce au PBR.</p>
<img src="/img/blog/aaa_pbr_materials.webp" alt="Casque en fer rendu avec des matériaux PBR, accompagné d’échantillons de textures flottants présentant les cartes d’albédo, de rugosité, de métallicité et de normales" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Burley, <a href="https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf">Ombrage physiquement réaliste chez Disney</a> (notes du cours à l’origine de la « BRDF Disney », SIGGRAPH 2012).</li>
<li>Karis, <a href="https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf">Ombrage réaliste dans Unreal Engine 4</a> (SIGGRAPH 2013, la présentation de référence sur le PBR d’UE4).</li>
<li>Lagarde et de Rousiers, <a href="https://seblagarde.wordpress.com/2015/07/14/siggraph-2014-moving-frostbite-to-physically-based-rendering/">Migration de Frostbite vers le rendu physiquement réaliste 3.0</a> (SIGGRAPH 2014, pipeline complet de Frostbite).</li>
<li><a href="https://www.realtimerendering.com/">Real-Time Rendering, 4e édition, chapitre 9</a> (l’ouvrage de référence).</li>
</ul>
<h2>2. Ombrage différé et par tampon de visibilité</h2>
<p>La géométrie écrit d’abord ses attributs (normales, identifiants de matériaux, profondeur) dans un G-buffer ou un tampon de visibilité. L’éclairage est ensuite calculé lors d’une passe plein écran qui lit ces tampons et applique l’ombrage une seule fois à chaque pixel. Les tampons de visibilité, utilisés par Nanite et des systèmes similaires, vont plus loin en ne stockant que les identifiants des triangles, puis en déterminant ultérieurement les paramètres des matériaux pour chaque pixel. Le coût du surdessin reste ainsi faible, même avec une géométrie dense.</p>
<img src="/img/blog/aaa_deferred_gbuffer.webp" alt="G-buffer d’ombrage différé divisé en panneaux présentant la couleur finale, les normales dans l’espace monde, la profondeur et les identifiants de matériaux" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Engel, <a href="https://download.nvidia.com/developer/presentations/2004/6800_Leagues/6800_Leagues_Deferred_Shading.pdf">Ombrage différé</a> (NVIDIA, la présentation fondatrice).</li>
<li>Burns et Hunt, <a href="http://jcgt.org/published/0002/02/04/">Le tampon de visibilité : une approche de l’ombrage différé optimisée pour le cache</a> (JCGT 2013, l’article à l’origine du tampon de visibilité).</li>
<li>Wihlidal, <a href="http://www.frostbite.com/2016/03/optimizing-the-graphics-pipeline-with-compute/">« Optimisation du pipeline graphique avec le calcul générique »</a> (GDC 2016, pipeline différé de Frostbite fondé sur le calcul générique).</li>
<li>Karis, Stubbe, Wihlidal, <a href="https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf">Analyse approfondie de la géométrie virtualisée Nanite</a> (SIGGRAPH 2021, avec une présentation détaillée du tampon de visibilité de Nanite).</li>
</ul>
<h2>3. Géométrie virtualisée (de type Nanite)</h2>
<p>Les maillages sont précalculés sous forme d’une hiérarchie de groupes. À l’exécution, le GPU charge et sélectionne les groupes à la résolution correspondant à chaque pixel, ce qui permet d’obtenir des détails d’une précision inférieure au pixel sans créer manuellement de niveaux de détail. Nanite d’Unreal en est l’exemple le plus connu. D’autres moteurs proposent désormais leurs propres variantes. En pratique, cela permet d’utiliser en temps réel des ressources de qualité cinématographique sans avoir à créer de LOD.</p>
<img src="/img/blog/aaa_virtualized_geometry.webp" alt="Rendu lisse à gauche et groupes de meshlets plus petits qu’un pixel, affichés en couleurs vives, à droite" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Karis, Stubbe, Wihlidal, <a href="https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf">Analyse approfondie de la géométrie virtualisée Nanite</a> (SIGGRAPH 2021).</li>
<li>Brian Karis, <a href="https://www.youtube.com/watch?v=eviSykqSUUw">Présentation de Nanite à la GDC 2021</a> (une présentation vidéo accessible).</li>
<li>Liktor, <a href="https://research.activision.com/publications/2021/09/geometry-rendering-pipeline-architecture-at-activision">Architecture du pipeline de rendu géométrique chez Activision</a> (rendu fondé sur des groupes, 2021).</li>
<li>Schied et al., <a href="https://research.nvidia.com/publication/2017-07_spatiotemporal-variance-guided-filtering-real-time-reconstruction-path-traced">Filtrage guidé par la variance spatio-temporelle</a> (techniques apparentées d’élimination des groupes non visibles).</li>
</ul>
<h2>4. Lancer de rayons en temps réel pour les ombres, les reflets et l’occlusion ambiante</h2>
<p>Le lancer de rayons matériel (DXR, Vulkan RT) projette des rayons d’ombre, des rayons de réflexion miroir ou brillante et des rayons d’occlusion ambiante dans un BVH construit à chaque image. Même quelques rayons par pixel surpassent les techniques en espace écran, notamment pour les reflets hors écran et les ombres de contact. La plupart des jeux l’emploient de manière ciblée plutôt que pour tout le rendu.</p>
<img src="/img/blog/aaa_hardware_raytracing.webp" alt="Voiture de sport rouge brillante dans un showroom de luxe, avec des reflets précis calculés par lancer de rayons et des ombres de contact" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Microsoft, <a href="https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html">Spécification fonctionnelle de DirectX Raytracing (DXR)</a> (la référence de l’API).</li>
<li>Wyman, <a href="http://intro-to-dxr.cwyman.org/">Introduction à DirectX Raytracing</a> (notes du cours SIGGRAPH, très accessibles).</li>
<li>Boksansky et Marrs, <a href="https://www.realtimerendering.com/raytracinggems/rtg2/index.html">Ray Tracing Gems II, chapitres 17 à 19</a> (PDF gratuit consacré aux techniques DXR modernes).</li>
<li>Stachowiak, <a href="https://www.ea.com/frostbite/news/stochastic-screen-space-reflections">Réflexions stochastiques en espace écran</a> (Frostbite, le lien entre SSR et lancer de rayons).</li>
</ul>
<h2>5. Lancer de rayons logiciel (de type Lumen)</h2>
<p>Tous les joueurs ne possèdent pas de carte RTX. Les moteurs proposent donc aussi des solutions de secours fondées sur les champs de distance ou les caches de surfaces. Lumen d’Unreal, par exemple, lance des rayons dans des champs de distance signés et des caches de surfaces pour calculer à faible coût l’illumination globale diffuse, et ne passe aux rayons matériels que lorsque cela s’avère nécessaire. C’est ainsi que les jeux AAA obtiennent sur consoles un rendu qui évoque le lancer de rayons.</p>
<img src="/img/blog/aaa_software_rt_lumen.webp" alt="Intérieur d’une cathédrale avec diffusion des couleurs des vitraux et superposition filaire d’un SDF révélant la géométrie approximative" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Wright et al., <a href="https://advances.realtimerendering.com/s2022/SIGGRAPH2022-Advances-Lumen-Wright%20et%20al.pdf">Lumen : illumination globale en temps réel dans Unreal Engine 5</a> (SIGGRAPH 2022, l’article sur Lumen).</li>
<li>Epic Games, <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/lumen-technical-details-in-unreal-engine">Détails techniques de Lumen</a> (documentation officielle du moteur).</li>
<li>Wright, <a href="https://advances.realtimerendering.com/s2021/Wright%20et%20al%20-%20Radiance%20Caching%20for%20Real-Time%20Global%20Illumination%20-%20SIGGRAPH%202021.pdf">Mise en cache de la radiance pour l’illumination globale en temps réel</a> (SIGGRAPH 2021).</li>
<li>Wright, <a href="https://www.youtube.com/watch?v=2GYXuM10riw">Présentation de Lumen à la GDC 2022</a> (version vidéo).</li>
</ul>
<h2>6. ReSTIR et échantillonnage par réservoirs</h2>
<p>Pour calculer l’éclairage direct et indirect provenant de milliers de sources lumineuses, ReSTIR (Reservoir Spatio-Temporal Importance Resampling, ou rééchantillonnage d’importance spatio-temporel par réservoirs) réutilise les échantillons lumineux entre les pixels et les images. C’est ainsi que des jeux comme Cyberpunk 2077 avec Path Tracing maintiennent un faible niveau de bruit avec seulement un ou deux rayons par pixel. Cette technique devrait apparaître dans davantage de moteurs à mesure que le path tracing s’impose comme objectif pour le haut de gamme.</p>
<img src="/img/blog/aaa_ray_tracing_restir.webp" alt="Rue cyberpunk avec des reflets calculés par lancer de rayons sur la chaussée mouillée et des trajets visibles de lumière indirecte" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Bitterli et al., <a href="https://research.nvidia.com/sites/default/files/pubs/2020-07_Spatiotemporal-reservoir-resampling/ReSTIR.pdf">Rééchantillonnage spatio-temporel par réservoirs pour le lancer de rayons en temps réel avec éclairage direct dynamique</a> (SIGGRAPH 2020, l’article à l’origine de ReSTIR).</li>
<li>Ouyang et al., <a href="https://research.nvidia.com/publication/2021-06_restir-gi-path-resampling-real-time-path-tracing">ReSTIR GI : rééchantillonnage des trajets pour le path tracing en temps réel</a> (HPG 2021, ReSTIR pour l’illumination indirecte).</li>
<li>Lin et al., <a href="https://research.nvidia.com/publication/2022-07_generalized-resampled-importance-sampling-foundations-restir">Échantillonnage d’importance rééchantillonné généralisé</a> (SIGGRAPH 2022, les fondements mathématiques).</li>
<li>NVIDIA, <a href="https://www.nvidia.com/en-us/geforce/news/cyberpunk-2077-ray-tracing-overdrive-technology-preview-on-rtx-4090/">Analyse technique approfondie du path tracing de Cyberpunk 2077</a> (blog d’ingénierie).</li>
</ul>
<h2>7. Nuages, brouillard et atmosphère volumétriques</h2>
<p>Le rendu du ciel est obtenu par parcours de rayons à travers des volumes de bruit et de densité 3D. L’atmosphère utilise des tables de diffusion précalculées, à la manière de Bruneton, pour gérer les transitions du soleil et de la lune. Le brouillard repose sur une grille de froxels, comparable à une texture 3D alignée sur le tronc de vision, qui capture l’éclairage local. Ensemble, ces techniques permettent de traiter la météo comme un véritable système plutôt que comme une simple skybox.</p>
<img src="/img/blog/aaa_volumetrics.webp" alt="Vallée montagneuse au coucher du soleil, avec des nuages volumétriques et des rayons crépusculaires traversant l’air brumeux" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Schneider, <a href="https://www.guerrilla-games.com/read/the-real-time-volumetric-cloudscapes-of-horizon-zero-dawn">Les paysages nuageux volumétriques en temps réel de Horizon Zero Dawn</a> (SIGGRAPH 2015, la référence incontournable sur les nuages).</li>
<li>Hillaire, <a href="https://sebh.github.io/publications/egsr2020.pdf">Une technique évolutive et prête pour la production de rendu du ciel et de l’atmosphère</a> (EGSR 2020, le successeur moderne de Bruneton utilisé dans UE5).</li>
<li>Wronski, <a href="https://bartwronski.com/2014/08/22/volumetric-fog-siggraph-2014/">Brouillard volumétrique : une solution unifiée fondée sur des compute shaders pour la diffusion atmosphérique</a> (SIGGRAPH 2014, brouillard en froxels d’Assassin’s Creed 4).</li>
<li>Hillaire, <a href="https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite">Rendu volumétrique physiquement réaliste et unifié dans Frostbite</a> (SIGGRAPH 2015).</li>
</ul>
<h2>8. Cartes d’ombres en cascades et cartes d’ombres virtuelles</h2>
<p>Pour les ombres du soleil, les cartes d’ombres en cascades divisent le tronc de vision en plusieurs plages et effectuent le rendu de chacune à une résolution adaptée. Les cartes d’ombres virtuelles vont plus loin : une unique carte d’ombres gigantesque est divisée en pages, et seules les pages visibles par la caméra sont rendues. C’est ainsi que les jeux AAA conservent des ombres nettes près du joueur sans consommer une quantité démesurée de mémoire.</p>
<img src="/img/blog/aaa_cascaded_shadows.webp" alt="Scène extérieure sur laquelle sont superposés trois troncs de cascades colorés montrant le niveau de détail des ombres près de la caméra" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Dimitrov, <a href="https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf">Cartes d’ombres en cascades</a> (livre blanc de NVIDIA, la référence standard).</li>
<li>Microsoft, <a href="https://learn.microsoft.com/en-us/windows/win32/dxtecharts/cascaded-shadow-maps">Techniques courantes pour améliorer les cartes de profondeur des ombres</a> (documentation DirectX).</li>
<li>Wright, <a href="https://advances.realtimerendering.com/s2023/index.html#VirtualShadowMaps">Cartes d’ombres virtuelles dans le chapitre 4 de Fortnite Battle Royale</a> (SIGGRAPH 2023, la présentation des VSM d’UE5).</li>
<li>Epic Games, <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/virtual-shadow-maps-in-unreal-engine">Documentation sur les cartes d’ombres virtuelles</a>.</li>
</ul>
<h2>9. Effets en espace écran (SSAO, SSR, SSGI, SSSSS)</h2>
<p>La lecture à faible coût des tampons de profondeur et de normales permet d’obtenir l’occlusion ambiante (SSAO), les reflets (SSR), l’illumination globale à un rebond (SSGI) et la diffusion sous la surface de la peau (SSSSS). Ces techniques ne peuvent pas reproduire les détails hors écran, raison pour laquelle le lancer de rayons les remplace progressivement, mais elles restent omniprésentes en tant que solution de base rapide.</p>
<img src="/img/blog/aaa_screen_space_effects.webp" alt="Comparaison côte à côte d’une cuisine avec un éclairage plat à gauche et des effets SSAO, SSR et SSGI à droite" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Pour aller plus loin :</p>
<ul>
<li>Mittring, <a href="https://www.crytek.com/wp-content/uploads/Finding%20Next%20Gen%20-%20CryEngine%202.pdf">À la recherche de la nouvelle génération : CryENGINE 2</a> (SIGGRAPH 2007, l’article à l’origine du SSAO).</li>
<li>McGuire et al., <a href="https://research.nvidia.com/publication/scalable-ambient-obscurance">Obscurance ambiante évolutive</a> (HPG 2012, SSAO moderne).</li>
<li>Stachowiak et Uludag, <a href="https://www.ea.com/frostbite/news/stochastic-screen-space-reflections">Réflexions stochastiques en espace écran</a> (Frostbite, la référence sur le SSR).</li>
<li>Jimenez, <a href="https://www.iryoku.com/separable-sss/">Diffusion sous la surface séparable</a> (la technique utilisée pour la peau dans la plupart des moteurs AAA).</li>
<li>Mara et al., <a href="https://research.activision.com/publications/2016/09/deep-g-buffers-for-stable-global-illumination-approximation">Espace écran profond</a> (Activision, une technique de la famille SSGI).</li>
</ul>
<h2>10. Anticrénelage temporel et mise à l’échelle par machine learning (DLSS, FSR, XeSS)</h2>
<p>L’image est rendue à une résolution interne inférieure, puis reconstruite à l’aide des vecteurs de mouvement, de la profondeur et des images précédentes. Les systèmes de mise à l’échelle fondés sur le machine learning (DLSS 3/4, FSR 3, XeSS) y ajoutent la génération d’images, qui interpole des images intermédiaires à partir du flux optique. La plupart des jeux AAA sont désormais conçus en partant du principe qu’un système de mise à l’échelle sera activé, ce qui modifie la manière de répartir le budget de rendu du reste de l’image.</p>
<img src="/img/blog/aaa_ml_upscaling.webp" alt="Comparaison côte à côte d’une entrée en basse résolution et d’une sortie nette du même personnage, reconstruite par machine learning" style="width:100%;border-radius:8px;margin:1.5rem 0" />
Analyses approfondies :
- Karis, [Suréchantillonnage temporel de haute qualité](http://advances.realtimerendering.com/s2014/index.html#_HIGH-QUALITY_TEMPORAL_SUPERSAMPLING) (SIGGRAPH 2014, la présentation de référence sur le TAA).
- Salvi, [Une exploration du suréchantillonnage temporel](https://research.nvidia.com/sites/default/files/pubs/2016-03_An-Excursion-in/dlss_DTAA-2-2.pdf) (NVIDIA, sur la voie menant au DLSS).
- Edelsten, [Véritablement nouvelle génération : intégrer l'apprentissage profond aux jeux et aux graphismes](https://www.gdcvault.com/play/1026184/) (GDC 2019, architecture du DLSS).
- AMD, [Détails techniques de FidelityFX Super Resolution 3](https://gpuopen.com/fidelityfx-super-resolution-3/) et Intel, [Article technique sur XeSS](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-xess-technical-paper.html).
<h2>11. Rendu piloté par le GPU et mesh shaders</h2>
<p>L'élimination des objets invisibles, la sélection du niveau de détail et la soumission des appels de rendu s'exécutent entièrement sur le GPU. Les mesh shaders remplacent le pipeline de sommets, de géométrie et de tessellation par une étape plus flexible, comparable à un shader de calcul, qui produit des meshlets. Associée aux appels de rendu indirects multiples, cette approche écarte totalement le CPU de la boucle critique par objet.</p>
<img src="/img/blog/aaa_gpu_driven_meshlets.webp" alt="Scène de spatioport extraterrestre avec une surimpression colorée des groupes de meshlets illustrant le rendu piloté par le GPU" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Analyses approfondies :</p>
<ul>
<li>Haar et Aaltonen, <a href="https://advances.realtimerendering.com/s2015/aaltonenhaar_siggraph2015_combined_final_footer_220dpi.pdf">Pipelines de rendu pilotés par le GPU</a> (SIGGRAPH 2015, la présentation fondatrice sur Assassin's Creed Unity).</li>
<li>Wihlidal, <a href="https://www.frostbite.com/2016/03/optimizing-the-graphics-pipeline-with-compute/">Optimiser le pipeline graphique avec le calcul GPU</a> (GDC 2016, élimination pilotée par le GPU dans Frostbite).</li>
<li>Kubisch, <a href="https://developer.nvidia.com/blog/introduction-turing-mesh-shaders/">Introduction aux mesh shaders de Turing</a> (NVIDIA, l'introduction de référence aux mesh shaders).</li>
<li>Pesce, <a href="https://www.bartwronski.com/">Tour d'horizon des mesh shaders</a> (ainsi que des blogs d'ingénierie similaires rassemblés dans les <a href="http://www.adriancourreges.com/blog/">analyses RenderDoc d'Adrian Courrèges</a>).</li>
</ul>
<h2>12. Rendu des cheveux, des tissus et de la peau</h2>
<p>Les cheveux utilisent un ombrage anisotrope de type Marschner avec une géométrie à base de mèches (NVIDIA HairWorks, AMD TressFX ou des systèmes natifs du moteur). Les tissus sont simulés sur le GPU à l'aide d'une dynamique basée sur les positions, puis rendus avec une composante spéculaire anisotrope. La peau utilise une diffusion sous-surface en espace écran, complétée par un éclairage enveloppant préintégré. C'est généralement sur ces trois éléments que l'écart de budget entre une production AAA et un jeu indépendant saute aux yeux.</p>
<img src="/img/blog/aaa_hair_cloth_skin.webp" alt="Gros plan sur un guerrier avec des cheveux modélisés mèche par mèche, une cape en tissu et une peau rendue par diffusion sous-surface" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Analyses approfondies :</p>
<ul>
<li>Marschner et al., <a href="https://www.cs.cornell.edu/~srm/publications/SG03-hair.pdf">Diffusion de la lumière par les fibres capillaires humaines</a> (SIGGRAPH 2003, le modèle fondateur pour les cheveux).</li>
<li>Chiang et al., <a href="https://benedikt-bitterli.me/pchfm/pchfm.pdf">Un modèle pratique et contrôlable de cheveux et de fourrure pour le path tracing en production</a> (Disney 2016, largement utilisé dans les approximations en temps réel).</li>
<li>Müller et al., <a href="https://matthias-research.github.io/pages/publications/posBasedDyn.pdf">Dynamique basée sur les positions</a> (la référence standard pour la simulation de tissus).</li>
<li>Jimenez et al., <a href="https://www.iryoku.com/separable-sss/">Diffusion sous-surface séparable</a> et <a href="https://www.iryoku.com/translucency/">Translucidité réaliste de la peau en temps réel</a>.</li>
</ul>
<h2>13. Décalcomanies, texturation virtuelle et superposition de matériaux</h2>
<p>La variation des surfaces provient de décalcomanies superposées — impacts de balles, saleté, sang et crasse — projetées sur le tampon de profondeur, ainsi que de textures virtuelles qui chargent les détails haute résolution juste à temps. La superposition de matériaux mélange plusieurs ensembles PBR par pixel à l'aide de masques et d'une projection triplanaire, ce qui permet à un seul rocher d'en évoquer cinq différents.</p>
<img src="/img/blog/aaa_decals_virtual_textures.webp" alt="Mur de bunker en béton altéré avec des décalcomanies d'impacts de balles, des graffitis et un encart montrant un atlas de pages de texture virtuelle" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Analyses approfondies :</p>
<ul>
<li>Pranckevičius, <a href="https://aras-p.info/blog/2009/02/27/deferred-decals/">Décalcomanies différées</a> et articles suivants (la série de billets classique d'Aras).</li>
<li>Mittring, <a href="http://advances.realtimerendering.com/s2012/Epic/UnrealEngine4_Mittring_SIGGRAPH2012_Final_Cleaned.pdf">La technologie derrière la « démo Elemental d'Unreal Engine 4 »</a> (SIGGRAPH 2012, avec des détails sur la texturation virtuelle).</li>
<li>van Waveren, <a href="https://www.realtimerendering.com/blog/id-tech-5-challenges-from-texture-virtualization-to-massive-parallelization/">Les défis d'id Tech 5 : de la virtualisation des textures au parallélisme massif</a> (SIGGRAPH 2009, la présentation sur MegaTexture).</li>
<li>Williams, <a href="https://www.gdcvault.com/play/1022144/Crafting-the-World-of-The">Superposition de matériaux dans The Order: 1886</a> (GDC 2014, matériaux PBR superposés).</li>
</ul>
<h2>14. Transparence indépendante de l'ordre</h2>
<p>Les cheveux, le feuillage, les particules et le verre ne peuvent pas être triés proprement. Les moteurs AAA utilisent des techniques comme l'OIT pondérée et fusionnée, l'épluchage de profondeur ou les listes chaînées par pixel pour les rendre correctement sans étape de tri sur le CPU. Dans une scène riche en végétation, c'est discrètement l'une des parties les plus coûteuses d'une image.</p>
<img src="/img/blog/aaa_oit_transparency.webp" alt="Scène forestière avec du feuillage translucide superposé, de la fumée, des gouttelettes sur du verre et des cheveux translucides" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Analyses approfondies :</p>
<ul>
<li>McGuire et Bavoil, <a href="http://jcgt.org/published/0002/02/09/">Transparence indépendante de l'ordre par fusion pondérée</a> (JCGT 2013, l'article sur la WBOIT).</li>
<li>Bavoil et Myers, <a href="https://developer.download.nvidia.com/SDK/10/opengl/src/dual_depth_peeling/doc/DualDepthPeeling.pdf">Transparence indépendante de l'ordre avec épluchage de profondeur double</a> (livre blanc de NVIDIA).</li>
<li>Yang et al., <a href="https://www.cse.chalmers.se/edu/year/2017/course/TDA362/Per-Pixel%20Linked%20List.pdf">Construction simultanée de listes chaînées en temps réel sur le GPU</a> (la référence sur les listes chaînées par pixel).</li>
<li>Wyman, <a href="https://research.nvidia.com/publication/2016-06_exploring-and-expanding-continuum-oit-algorithms">Exploration et extension du continuum des algorithmes d'OIT</a> (HPG 2016, étude comparative).</li>
</ul>
<h2>15. Cache neuronal de radiance et débruiteurs par apprentissage automatique</h2>
<p>La couche la plus récente. Le cache neuronal de radiance de NVIDIA apprend l'éclairage indirect de chaque scène et l'interroge au lieu de lancer davantage de rayons. Les débruiteurs par apprentissage automatique — OptiX, Intel Open Image Denoise ou solutions internes personnalisées — nettoient en quelques millisecondes les signaux épars issus du lancer de rayons. Cette catégorie devrait progresser rapidement au cours des deux prochaines années.</p>
<img src="/img/blog/aaa_neural_radiance_denoise.webp" alt="Cathédrale rendue par lancer de rayons à 1 échantillon par pixel, bruitée à gauche, et résultat propre débruité par apprentissage automatique à droite" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Analyses approfondies :</p>
<ul>
<li>Müller et al., <a href="https://research.nvidia.com/publication/2021-06_real-time-neural-radiance-caching-path-tracing">Cache neuronal de radiance en temps réel pour le path tracing</a> (SIGGRAPH 2021, l'article sur le NRC).</li>
<li>Schied et al., <a href="https://research.nvidia.com/publication/2017-07_spatiotemporal-variance-guided-filtering-real-time-reconstruction-path-traced">Filtrage spatio-temporel guidé par la variance : reconstruction en temps réel de l'illumination globale calculée par path tracing</a> (HPG 2017, SVGF).</li>
<li>Chaitanya et al., <a href="https://research.nvidia.com/publication/interactive-reconstruction-monte-carlo-image-sequences-using-recurrent-denoising">Reconstruction interactive de séquences d'images de Monte-Carlo à l'aide d'un autoencodeur récurrent de débruitage</a> (SIGGRAPH 2017, le premier débruiteur récurrent par apprentissage automatique).</li>
<li>Intel, <a href="https://www.openimagedenoise.org/documentation.html">Documentation d'Open Image Denoise</a> (débruiteur de production open source).</li>
</ul>
<h2>Ce que cela implique pour le navigateur</h2>
<p>Nous avons déployé plusieurs de ces techniques en WebGPU dans notre <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">moteur de monde ouvert pour navigateur</a>. Les cascades de cartes d'ombres, l'instanciation pilotée par le GPU, le PBR triplanaire, le brouillard en espace écran et le terrain virtualisé basé sur des clipmaps fonctionnent tous à 120 images par seconde dans un onglet. Le reste — lancer de rayons matériel, mesh shaders et mise à l'échelle par apprentissage automatique — arrivera sur le Web à mesure que la spécification WebGPU évoluera. Pour aller plus loin, consultez nos guides sur les <a href="/fr/guides/browser-3d-open-world-tech">technologies de mondes ouverts dans le navigateur</a> et la <a href="/fr/guides/landscape-generation-browser">génération de paysages</a>.</p>
<h2>Pour approfondir l'ensemble de la chaîne</h2>
<p>Si vous ne devez lire qu'un seul livre, <a href="https://www.realtimerendering.com/">Real-Time Rendering, 4e édition</a> est la référence incontournable couvrant la plupart des sujets ci-dessus. Pour suivre les recherches en cours, les archives du cours SIGGRAPH « Advances in Real-Time Rendering in Games » (<a href="https://advances.realtimerendering.com/">advances.realtimerendering.com</a>) proposent gratuitement les PDF d'analyses approfondies de moteurs AAA remontant à 2006. Pour des analyses de production expliquant comment des jeux précis rendent chaque image, les <a href="http://www.adriancourreges.com/blog/">articles de profilage GPU d'Adrian Courrèges</a> sont incontournables.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 18 : un pinceau de dispersion qui semble guidé par l’IA]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-02-open-world-browser-part-18-ai-scattering</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-02-open-world-browser-part-18-ai-scattering</guid>
            <pubDate>Sat, 02 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 37 a permis de créer un pinceau de dispersion heuristique qui évite de placer les arbres sur les falaises et maintient les galets au bord de l’eau, sans LLM, avant de l’optimiser avec l’instanciation, les LOD selon la distance et la correction d’une série de bugs pour revenir à un coup de pinceau de 4 ms.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 18 : un pinceau de dispersion qui semble guidé par l’IA</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et contient des liens vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-05-01-open-world-browser-part-17-animations-and-search">partie 17</a> a doté le joueur d’un ensemble d’animations digne d’un jeu de combat et d’un moyen d’importer n’importe quel modèle CC0 dans le monde. Cette partie revient aux outils de création. La palette du spike 34 place un accessoire par clic, ce qui convient pour mettre en scène un objet principal, mais ne sert à rien pour créer une forêt. Le spike 37 introduit le pinceau : faites-le glisser sur le terrain et les arbres apparaissent là où ils devraient pousser.</p>
<h2>Un placement « par IA » sans IA</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/37-ai-scattering/" title="Spike 37 : dispersion d’accessoires assistée par IA" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/37-ai-scattering/" target="_blank">Ouvrir le spike 37 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=37-ai-scattering">Voir le code source</a></p>
<p>Ce spike cherche à déterminer si un pinceau purement heuristique peut sembler suffisamment intelligent pour se passer d’un LLM. Le critère d’un placement « par IA » est concret : les arbres évitent les falaises, les rochers s’inclinent dans la pente et les galets de plage s’arrêtent au bord de l’eau, le tout dès le premier coup de pinceau. Nous y sommes parvenus avec des prédicats de pente et d’altitude, des tirages pondérés et un espacement propre à chaque famille, sans le moindre appel à un modèle.</p>
<p>Le pinceau fonctionne sur une carte de hauteurs CPU de 257×257 comportant des reliefs ajustés à la main afin que chaque préréglage dispose d’une zone appropriée : des montagnes au nord pour les sélections à pentes mixtes, une bande de falaises à l’est pour les éboulis, une plaine côtière au sud pour la plage et la prairie, et une cuvette lacustre au sud-ouest. Le terrain précalcule les couleurs des sommets à partir d’un classificateur de biome <code>(altitude, slope)</code>. Avant même de peindre le premier arbre, on peut donc voir où un préréglage s’appliquera. Cinq préréglages sont fournis sous forme de données plates, chacun avec une liste de sélections telles que <code>{ category, weight, slopeMin, slopeMax, altMin, altMax, minSpacing, alignToSlope }</code>. Cliff and Scree définit <code>slopeMin: 0.3</code>, de sorte que les rochers ne soient placés que sur de véritables pentes, ainsi que <code>alignToSlope: true</code>, afin que le vecteur vertical de chaque bloc rocheux suive la normale de la surface.</p>
<p>Pour chaque trait, le moteur de dispersion échantillonne <code>densityPerM2 × area</code> points candidats à l’intérieur du disque du pinceau, lit la hauteur et la pente de chaque candidat, filtre les sélections du préréglage selon les prédicats satisfaits, en choisit une par tirage pondéré, puis vérifie l’espacement à l’aide d’une table de hachage spatiale limitée au rayon concerné. L’ensemble est déterministe : un générateur aléatoire Mulberry32 initialisable contrôle chaque tirage, si bien que <code>(seed, brush events)</code> reproduit exactement n’importe quelle session. Sur le terrain initial, un trait Mixed Forest appliqué à une prairie plate a placé 139 candidats sur 158 en 5 ms. Sur une falaise, le même préréglage n’en a placé que 106 sur 226, et le HUD a indiqué que 81 avaient été rejetés à cause de la pente. Cette ventilation des rejets constitue toute l’expérience utilisateur : on comprend <em>pourquoi</em> la falaise n’a reçu que quelques arbres au lieu de devoir le deviner.</p>
<p>L’intérêt de conserver les préréglages sous forme de données plates est que la future version fondée sur un LLM ne nécessitera qu’un remplacement de JSON, pas une réécriture. <code>paint({ preset })</code> ne se soucie pas de savoir si <code>preset.picks</code> provient d’une recette ajustée à la main ou d’un worker ayant transformé « forêt de feuillus avec des blocs rocheux couverts de mousse » en pondérations. Le moteur ne code jamais en dur l’identifiant d’un accessoire non plus : l’utilisation d’un autre catalogue ne nécessite donc aucune modification du moteur.</p>
<h2>De 300 appels de rendu à 49</h2>
<p>La première version affichait chaque placement sous la forme d’un <code>clone(true)</code> d’un groupe composé de plusieurs maillages. Cela convient pour quelques centaines d’accessoires, mais devient un obstacle à la limite de 2 500, où le nombre d’appels de rendu grimpe à plusieurs milliers. Avant d’en arriver là, nous sommes passés à <code>InstancedMesh</code>, avec un lot par <code>(propId, partIndex)</code>. La capacité de chaque lot double à mesure qu’il grandit : un <code>InstancedMesh</code> plus grand est alloué, les matrices actives y sont copiées, le parent de la scène est remplacé et l’ancien attribut est libéré. L’effacement utilise une suppression par échange, de sorte que retirer une instance reste une opération O(1), quelle que soit la taille du lot. Le déterminisme, l’espacement et le HUD des rejets restent inchangés, car cette substitution se déroule entièrement sous le niveau de l’enregistrement du placement.</p>
<p>Un diagnostic effectué sur le pack MegaKit a permis de trancher une véritable question d’architecture. Un maillage glTF à plusieurs primitives — tronc et feuilles — peut parvenir dans three.js soit sous la forme d’un seul maillage doté d’un tableau de matériaux et de <code>geometry.groups</code>, soit sous celle de maillages frères distincts ayant chacun un seul matériau. Pour ce pack, le chargeur choisit la seconde voie : chaque partie est un maillage à matériau unique dont les groupes sont vides. C’est la meilleure structure pour la dispersion, car des lots distincts par primitive permettent au lot des troncs de grandir indépendamment de celui des feuilles si leurs nombres divergent. Le nombre d’appels de rendu reste identique dans les deux cas, mais la structure de mémoire est meilleure avec cette séparation. Le gain mesuré s’est confirmé : un trait de forêt qui nécessitait environ 300 appels de rendu n’en demandait plus que 49, et une session complète à plusieurs traits a atteint 3 221 instances à 75 FPS avec 51 appels de rendu, un plafond que la méthode par clonage ne pouvait jamais atteindre avant l’effondrement du budget d’image.</p>
<h2>LOD selon la distance et quatre bugs qui s’y cachaient</h2>
<p>L’instanciation a réduit le nombre d’appels de rendu, mais chaque instance affichait toujours l’intégralité de ses triangles, même pour les arbres situés à 90 m dont le détail des feuilles ne représentait que deux pixels. Nous avons donc précalculé trois niveaux de LOD par partie d’accessoire avec meshoptimizer — complet, 50 %, 15 % —, étendu la clé de lot à <code>(propId, partIndex, lod)</code> et ajouté une méthode <code>move()</code> qui transfère un placement entre des lots frères sans allocation. Les plages de distance vont de 0 à 30 m, de 30 à 90 m, puis au-delà, avec une hystérésis de ±4 m autour de chaque limite afin qu’une caméra stationnant près d’un seuil ne déplace pas sans cesse un placement dans un sens puis dans l’autre tout en retéléversant sa matrice à chaque image. La réévaluation est plafonnée à 4 Hz et n’a lieu que si la caméra s’est réellement déplacée. Une caméra immobile ne coûte donc qu’une comparaison de distance au carré par image.</p>
<p>C’est dans ce chemin de LOD que se cachaient les bugs les plus instructifs. Le premier se manifestait par des placements qui disparaissaient ou se dupliquaient lorsque la caméra tournait autour de la scène, et le problème empirait à mesure que celle-ci se remplissait. La cause était une matrice temporaire partagée : <code>move()</code> lisait la transformation d’un placement dans <code>_tmpMat</code>, définie au niveau du module, mais la suppression par échange du lot source utilisait cette même <code>_tmpMat</code> pour son réagencement interne. Elle écrasait donc la matrice transportée avant que la destination ne puisse l’écrire. Le bug épargnait uniquement le cas où l’emplacement déplacé était déjà le dernier de son lot, soit une probabilité d’environ <code>1/count</code>. Cela correspond exactement au « scintillement rare qui empire à mesure que la scène grandit » observé pendant le test de jeu. La correction a consisté à réserver une matrice <code>_carryMat</code> exclusivement à <code>move()</code>. Après un test de résistance totalisant 1 274 déplacements, le groupe est resté identique au pixel près.</p>
<p>Le deuxième bug était plus subtil : chaque transition de LOD semblait fluide, <em>sauf</em> la première. Les arbres passant au LOD1 présentaient un changement visible d’ombrage alors que leur silhouette variait à peine, tandis que les réductions de triangles plus importantes aux niveaux suivants restaient imperceptibles. Le simplificateur utilisant <code>LockBorder</code> ne déplace ni ne crée jamais de sommets, de sorte que les sommets conservés gardent exactement leurs normales. Pourtant, nous appelions tout de même <code>computeVertexNormals()</code> après chaque simplification. Le LOD0 conserve intactes les normales originales créées par l’artiste, tandis que le LOD1 et les suivants recevaient le recalcul générique de three.js fondé sur la moyenne des faces. Le passage de 0 à 1 était le seul endroit de la chaîne où le régime des normales changeait, et c’est donc là que se produisait le saut visuel. La suppression de cette unique ligne défensive a corrigé l’ombrage et, en prime, réduit de moitié environ le temps de précalcul par accessoire, puisque nous avons cessé de recalculer les normales de quatre LOD par partie.</p>
<p>L’examen de la sortie du simplificateur a révélé un troisième gain. Chaque LOD était un <code>original.clone()</code> doté d’un nouvel index, et <code>BufferGeometry.clone()</code> copie en profondeur chaque attribut. Cinq LOD conservaient donc cinq copies indépendantes des tampons de positions, de normales, d’UV et de couleurs, alors que leurs valeurs étaient strictement identiques bit pour bit. Nous avons remanié le système pour partager les références d’attributs et ne conserver qu’un tampon d’index privé par LOD. Pour une partie d’arbre typique, le nombre d’identités d’attributs distinctes est ainsi passé de 20 à 9, et chaque tampon de sommets n’est plus téléversé qu’une seule fois vers le GPU. Ce stockage partagé impose deux règles : ne jamais modifier les données d’attribut depuis un LOD particulier et ne jamais appeler <code>dispose()</code> sur la géométrie d’un seul LOD, car ces deux opérations affecteraient tous les LOD frères partageant le tampon.</p>
<p>Le quatrième bug n’avait rien à voir avec la peinture. Le simple fait d’agiter le curseur au-dessus du terrain faisait chuter la fréquence d’images, sans qu’aucun bouton soit enfoncé. Le gestionnaire <code>pointermove</code> effectuait un lancer de rayon sur le maillage du terrain, un plan de 131 072 triangles dépourvu de structure spatiale. three.js parcourait donc l’intégralité du tampon d’index à chaque événement, jusqu’à 1 000 événements par seconde. Nous n’avions absolument pas besoin du maillage pour cette recherche, puisque le terrain est une carte de hauteurs paramétrique. Une marche de rayon adaptative sur <code>sampleHeight</code> — grands pas très au-dessus de la surface, minimum de 0,4 m à proximité, puis 12 dichotomies lors du changement de signe — nécessite environ 8 à 30 échantillons par rayon au lieu de 131 072 tests de triangles. Elle est environ trois ordres de grandeur moins coûteuse, et le survol respecte de nouveau la fréquence d’images maximale.</p>
<h2>Le coût ne fait que se déplacer ; assurez-vous qu’il quitte le clic</h2>
<p>Après la migration du spike vers <code>WebGPURenderer</code> avec three r184 — la cible de production —, un profil DevTools a montré que le tout premier coup de pinceau bloquait l’exécution pendant 265 ms, dont 79 % dans le WASM de meshoptimizer. Le précalcul représentait un véritable travail, environ 180 appels de simplification pour un préréglage à froid, mais il s’exécutait dans le gestionnaire de clic parce que <code>preloadProps</code> se contentait de récupérer et d’analyser les scènes sans jamais déclencher le précalcul des LOD. La correction a consisté à faire exécuter l’intégralité de ce précalcul en arrière-plan lors de la sélection d’un préréglage : <code>preloadProps</code> appelle désormais le chemin de résolution des parties, met en cache la promesse en cours afin qu’un clic rapide la rejoigne au lieu de créer une tâche en double, et mémorise le prétraitement par géométrie que le simplificateur répétait quatre fois par partie. Dans le HUD, le premier coup de pinceau est passé de 209 ms à 4 ms. Le temps WASM n’a pas disparu : il a simplement quitté le chemin critique de l’utilisateur et s’exécute pendant que celui-ci observe le terrain pour décider où peindre.</p>
<p>C’est la leçon récurrente de ce spike. Presque aucune de ces corrections n’a changé ce que le pinceau <em>fait</em>. Elles ont changé le <em>moment</em> où le coût est payé : plus pendant le clic, plus pendant le survol, plus au niveau du seuil près duquel la caméra stationne. Un outil de dispersion qui semble instantané n’effectue pas moins de travail : il l’effectue lorsque l’utilisateur ne l’attend pas.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Dispersion heuristique selon l’adéquation.</strong> Un pinceau échantillonne des points candidats dans un disque, lit <code>(height, slope)</code> pour chaque point dans une carte de hauteurs CPU, filtre les sélections d’un préréglage selon des prédicats de pente et d’altitude, en choisit une par tirage pondéré, puis la rejette si elle enfreint l’espacement minimal propre à sa famille, suivi dans une table de hachage spatiale. Les sélections alignées sur la pente font pivoter leur vecteur vertical selon la normale de la surface. Le placement paraît ainsi intentionnel — les arbres évitent les falaises, les rochers s’inclinent dans les pentes et les galets s’arrêtent au bord de l’eau — sans aucun poids appris. Le préréglage reste constitué de données plates, de sorte qu’une liste de sélections générée par un LLM puisse le remplacer directement.</p>
<p><strong>Placement déterministe avec des chargements asynchrones.</strong> Un générateur aléatoire Mulberry32 initialisable contrôle chaque tirage, de sorte que <code>(seed, brush events)</code> reproduise exactement une session. Les tirages aléatoires ont lieu avant tout <code>await</code>, et les réservations d’espacement sont insérées dans l’index spatial avant la résolution du clone glTF. Les candidats concurrents se respectent donc mutuellement, et le chargement asynchrone des ressources ne peut pas perturber la séquence.</p>
<p><strong><code>InstancedMesh</code> répartis en lots avec modifications O(1).</strong> Un <code>InstancedMesh</code> par <code>(propId, partIndex, lod)</code>, avec une capacité doublée à la demande en copiant les matrices actives dans un tampon plus grand. L’effacement et l’éviction FIFO utilisent une suppression par échange et un tableau de références inverses qui corrige l’index de l’instance déplacée. Une suppression reste donc O(1), quelle que soit la taille du lot. Un diagnostic a confirmé que les parties glTF arrivent sous forme de maillages à matériau unique. La création d’un lot par primitive constitue donc le chemin actif et fournit à chaque primitive un lot dont la capacité peut grandir indépendamment.</p>
<p><strong>LOD selon la distance, avec hystérésis et tampons d’attributs partagés.</strong> Trois niveaux simplifiés par meshopt pour chaque partie, sélectionnés selon des plages de distance avec une hystérésis de ±4 m afin qu’une caméra proche d’un seuil ne provoque pas des basculements incessants, réévalués à une fréquence plafonnée et uniquement lors d’un véritable mouvement de caméra. Puisque la simplification <code>LockBorder</code> ne déplace jamais les sommets, tous les LOD partagent les mêmes tampons de positions, de normales, d’UV et de couleurs, et ne diffèrent que par leur tampon d’index privé. Le nombre de tampons de sommets GPU distincts est ainsi réduit d’environ moitié. La suppression d’un appel défensif à <code>computeVertexNormals</code> conserve les normales de l’artiste à l’identique dans tous les LOD et élimine l’unique discontinuité d’ombrage de la chaîne. Consultez <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD et meshoptimizer</a>.</p>
<p><strong>Lancer de rayon analytique sur une carte de hauteurs pour les recherches à haute fréquence.</strong> Une recherche liée à <code>pointermove</code> sur un maillage plan de 131 000 triangles parcourt l’intégralité du tampon d’index à chaque événement. Son remplacement par une marche de rayon adaptative sur la fonction de hauteur analytique — grands pas loin de la surface, petit pas minimal à proximité, dichotomie lors du changement de signe de $(\text{ray}_y - \text{terrain}_y)$ — ne coûte que quelques dizaines d’échantillons au lieu de dizaines de milliers de tests de triangles. C’est environ trois ordres de grandeur moins coûteux, et un vecteur de sortie préalloué évite toute allocation dans le chemin critique.</p>
<p><strong>Sortir le travail du chemin critique de l’interaction.</strong> Les opérations ponctuelles coûteuses — précalcul des LOD meshopt, compilation des pipelines WGSL — doivent s’exécuter pendant les périodes d’inactivité, pas dans le gestionnaire de clic. Le préchargement du précalcul complet du préréglage actif lors de sa sélection, la mise en cache de la promesse en cours afin qu’un clic rapide la rejoigne plutôt que de la dupliquer, et la mémorisation du prétraitement par géométrie ont réduit la latence du premier coup de pinceau de 209 ms à 4 ms, sans diminuer la quantité totale de travail.</p>
<hr>
<p>Partie 18 sur 29.
Précédent : <a href="/fr/blog/2026-05-01-open-world-browser-part-17-animations-and-search">Partie 17 - Des animations qui n'avaient pas besoin de reciblage, et une recherche de ressources en direct</a>
Suivant : <a href="/fr/blog/2026-05-04-open-world-browser-part-19-imposters">Partie 19 - L'imposteur qui doit survivre à une forêt</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 17 : des animations qui n'avaient pas besoin de reciblage et une recherche de ressources en direct]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-05-01-open-world-browser-part-17-animations-and-search</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-05-01-open-world-browser-part-17-animations-and-search</guid>
            <pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 35 a reciblé 262 clips de combat et de locomotion sur le squelette du joueur, puis créé un test de parité hors ligne pour vérifier leur fidélité. Le spike 36 a intégré une recherche en direct de modèles Polyhaven sans étape de build.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 17 : des animations qui n'avaient pas besoin de reciblage et une recherche de ressources en direct</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>La <a href="/fr/blog/2026-04-28-open-world-browser-part-16-structure-and-authoring">partie 16</a> nous a donné un monde dans lequel placer des objets. Cette partie offre au joueur de meilleures choses à y faire : un ensemble d'animations digne d'un jeu de combat et un moyen d'importer dans le monde n'importe lequel des quelque mille modèles CC0 en saisissant une recherche.</p>
<h2>262 clips, un squelette, trois corrections de reciblage</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/35-combat-animations/" title="Spike 35 : animations de combat" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/35-combat-animations/" target="_blank">Ouvrir le spike 35 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=35-combat-animations">Voir le code source</a></p>
<p>Le squelette du joueur est un squelette <code>3MIKE</code> de style CC4 comportant 213 os, dont un rig facial de plus de 80 os et toutes les articulations des doigts. Les sources d'animation ne lui correspondent pas. La première version reciblait des clips de combat Mixamo sélectionnés à la main, mais le choix disponible était limité et la locomotion était maladroitement répartie entre Mixamo et quelques animations d'attente Kimodo au format BVH. La véritable avancée est arrivée lorsque nous avons remplacé la bibliothèque source par les deux packs Universal Animation Library de Quaternius : 262 clips au total sur un même mannequin cohérent de style UE5 à 65 os, avec des enchaînements à l'épée, du tir à l'arc, de l'escalade, des courses murales, des esquives, des réactions aux coups, des emotes et un ensemble de locomotion bien plus propre.</p>
<p>Il faut donc passer d'une source à 65 os à une cible à 213 os, sans noms d'os, orientations de pose en T ni proportions de membres en commun. Chaque clip est remappé en trois étapes : une table de correspondance des noms d'os, un alignement de la pose de référence afin que les deux rigs partagent une orientation de référence, puis une mise à l'échelle des pistes de position pour éviter que le rig source, plus petit, n'enfonce le personnage jusqu'aux genoux dans le sol. Pour y parvenir, nous avons dû traquer une série de bugs, chacun nous apprenant quelque chose de précis.</p>
<p>Le personnage ressortait excessivement tordu, chaque épaule et chaque coude pivotant d'environ 30° de trop. La cause était une différence de pose : la source UAL est fournie dans une véritable pose en T, tandis que la pose de référence de CC4 est une pose en A. Le système de reciblage supposait que les deux rigs partageaient la même pose de référence, de sorte que la différence entre les poses A et T s'ajoutait à chaque delta calculé image par image. La correction force la chaîne des bras de CC4 dans une véritable pose en T pour capturer la pose de référence avant d'effectuer le reciblage, ce qui permet de conserver de petits deltas.</p>
<p>Ensuite, le corps a cessé de se déplacer. Lors des projections en arrière, le haut du corps reculait, mais les pieds restaient plantés au sol. UAL place le déplacement sur l'os <code>root</code>, pas sur le bassin ; lire la position du bassin produisait donc un mouvement presque nul. La correction additionne <code>root.position</code> et <code>pelvis.position</code>, met à l'échelle la composante horizontale et écrit une seule piste de position des hanches. Ce faisant, nous avons découvert que les décalages verticaux comportaient une erreur d'environ 12 %, car nous avions utilisé le rapport global des proportions des membres pour l'axe Y, alors que la hauteur du bassin nécessite le rapport entre les hanches et le sol. Deux rapports pour deux axes : $\text{verticalRatio} = |{\text{targetHipY}}/{\text{sourceHipY}}|$ pour Y, et le rapport de proportions pour l'horizontale. Plus aucun dépassement. Par ailleurs, une série d'avertissements NaN concernant <code>RL_BoneRoot.position</code> provenait du mappage du <code>root</code> d'UAL, situé à $Y=0$, sur un os cible dont la piste de position est normalisée en la divisant par sa valeur Y dans la pose de référence. Division par zéro, NaN, puis piste silencieusement abandonnée. La correction a consisté à supprimer entièrement le mappage de la racine, puisque sa translation est déjà intégrée à la piste des hanches.</p>
<p>La plus petite correction était aussi la plus satisfaisante à regarder. Le personnage tenait l'épée avec des doigts mous, figés dans la pose de référence, car la table de correspondance ne contenait aucune entrée pour les doigts et que le système de reciblage n'écrit des pistes que pour les os mappés. L'ajout de 30 os de doigts — cinq doigts, trois segments, deux mains, en laissant de côté le quatrième os auxiliaire de l'extrémité fourni par UAL, qui ne déforme pas le maillage — a permis à la main de se refermer sur la poignée et de s'ouvrir lorsqu'elle la lâche.</p>
<h2>Valider 262 clips sans en regarder 262</h2>
<p>Il est impossible de vérifier visuellement une bibliothèque de 262 clips. Nous avons donc créé un test hors ligne de parité des trajectoires : un script Node sans interface graphique charge chaque pack, échantillonne le squelette source à 60 Hz, exécute le pipeline de reciblage, puis compare les positions et rotations de chaque os dans l'espace global à celles de la source après mise à l'échelle. La dérive maximale du bassin sur l'axe Y n'était que de 0,003 m. Les mains présentaient un décalage constant de 2,5°, d'abord interprété comme un problème de suivi des doigts. Mais un décalage constant correspond au delta de la pose de référence entre la main plate d'UAL et la pose de référence légèrement incurvée de CC4, et il reste inchangé pendant tout le clip. Les véritables erreurs d'animation apparaissent sous forme de dérives qui varient d'une image à l'autre. Une fois ce point compris, le test est devenu un contrôle de régression en une seule passe : si la dérive d'un clip cesse de respecter cette valeur de référence constante, une modification récente a cassé le reciblage.</p>
<p>Un mannequin de référence affiché côte à côte a rendu la partie visuelle du débogage décisive. Appuyer sur la barre oblique inverse affiche à côté du joueur le rig source auquel appartient le clip actuel. La question « cette épaule est-elle tordue ? » devient alors « la torsion existe-t-elle dans la source ou le reciblage l'a-t-il ajoutée ? ». Les tests numériques détectent les régressions, la référence visuelle révèle les erreurs de pose de référence que les chiffres ne font pas apparaître et, ensemble, ils ont mis fin aux suppositions.</p>
<p>Une fois le pipeline fiabilisé, le passage de la base WASD/saut/nage de Mixamo à UAL s'est résumé à une petite table d'alias : la machine à états finis continue d'utiliser des noms d'états génériques tels que <code>idle</code> et <code>walk</code>, qui sont associés aux noms des clips UAL lors de la lecture. La nage nécessitait des décalages de rig propres à chaque clip, car les poses de crawl et de nage sur place ancrent le bassin à des hauteurs anatomiques différentes. Nous immergeons donc le rig d'un demi-mètre pour la nage active et plus profondément pour la nage sur place, avec une transition fluide entre les deux à 5 Hz.</p>
<h2>Saisissez un mot, obtenez un modèle</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/36-polyhaven-models/" title="Spike 36 : modèles Polyhaven" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/36-polyhaven-models/" target="_blank">Ouvrir le spike 36 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=36-polyhaven-models">Voir le code source</a></p>
<p>Le spike 34 nous a pris une journée pour constituer manuellement un pack CC0. La solution à long terme est un champ de recherche. Polyhaven publie environ 1 100 modèles CC0 derrière une API JSON permissive et un CDN déterministe. Ce spike relie l'ensemble du parcours — requête, miniatures, chargement et rendu — depuis une page statique sans étape de build, en environ 300 lignes de JavaScript natif auxquelles s'ajoute three.js.</p>
<p>Au démarrage, la page récupère une fois l'intégralité du catalogue, soit environ 600 Ko. La recherche repose entièrement sur un score calculé côté client — le nom prime sur l'identifiant, qui prime sur la catégorie, qui prime sur l'étiquette — avec un délai anti-rebond de 120 ms, puis affiche les 60 meilleures cartes. Les miniatures sont chargées à la demande au moyen d'un <code>IntersectionObserver</code>, afin que la saisie ne déclenche pas 60 requêtes simultanées. La partie intéressante est le chargement. Le point d'accès aux fichiers de Polyhaven expose du glTF multifichier, et non du GLB, avec des textures partagées entre les résolutions et réparties dans des fichiers séparés. Il renvoie également une table <code>include</code> associant chaque chemin relatif à une URL absolue du CDN. Plutôt que de télécharger et modifier nous-mêmes le JSON, nous transmettons cette table à <code>LoadingManager.setURLModifier</code>, appelé pour chaque dépendance requise par le chargeur — le fichier <code>.bin</code> et chaque texture — afin de la résoudre via le CDN. Un clic, un seul fichier en apparence. L'API comme le CDN définissent des règles CORS permissives, vérifiées avec <code>curl</code> avant d'écrire le moindre code client : aucun proxy n'est donc nécessaire. Les matériaux PBR s'affichent correctement avec <code>RoomEnvironment</code> et les valeurs par défaut du tone mapping ACES, sans correction propre à chaque ressource. Les textures 1k maintiennent la taille d'un modèle courant entre 2 et 5 Mo, au lieu de 20 à 40 Mo en 4k.</p>
<p>Une passe WASM de meshoptimizer complète l'ensemble avec un réducteur non destructif : chaque maillage conserve un clone de sa géométrie d'origine, et toute modification du ratio reconstruit un tampon d'indices à partir de ce clone au lieu d'appliquer des simplifications cumulatives. Les géométries à plusieurs matériaux sont simplifiées groupe par groupe, puis <code>geometry.groups</code> est reconstruit afin que les emplacements de matériaux ne soient pas fusionnés. Un fauteuil passe ainsi de 5 626 triangles en qualité maximale à 2 812 à mi-résolution.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Reciblage de squelette avec alignement de la pose de référence.</strong> Transférer une animation d'un squelette à un autre lorsque les noms des os, les proportions et les poses de référence diffèrent nécessite trois corrections : une table de correspondance des noms d'os, un alignement de la pose de référence afin que les deux rigs partagent la même orientation de référence — en forçant la chaîne des bras de la pose en A de la cible à adopter la pose en T de la source — et une mise à l'échelle des pistes de position. Une différence de pose ajoute la rotation entre A et T à chaque delta calculé image par image, ce qui double la rotation des articulations. Les os auxiliaires non mappés doivent être ignorés, car un os racine situé à $Y=0$ provoque silencieusement une division par zéro lors de la normalisation des pistes de position.</p>
<p><strong>Deux rapports d'échelle pour un seul rig.</strong> Le déplacement horizontal utilise le rapport global des proportions des membres — c'est-à-dire la taille générale du squelette — tandis que le décalage vertical du bassin utilise le rapport entre les hanches et le sol $|{\text{targetHipY}}/{\text{sourceHipY}}|$, car la proportion entre les jambes et le torse diffère d'un rig à l'autre. Utiliser un seul rapport pour les deux axes fait dépasser la cible pendant les escalades et l'enfonce sous le sol pendant les phases de récupération. Dans UAL, le déplacement se trouve également sur l'os <code>root</code>, et non sur le bassin. Les deux doivent donc être additionnés dans une seule piste de position des hanches afin de préserver le mouvement dans les clips de projection, d'escalade et de locomotion.</p>
<p><strong>Test hors ligne de parité des trajectoires.</strong> Un script sans interface graphique échantillonne le squelette source à 60 Hz, exécute le pipeline de reciblage, puis compare les transformations globales de chaque os à celles de la source. Un décalage constant d'une image à l'autre est le delta inoffensif de la pose de référence, tandis qu'une dérive variable indique une véritable erreur. Le test devient ainsi un contrôle de régression qui se déclenche lorsqu'une modification supprime ou déforme une piste. L'isolation des GLB pack par pack — un nouveau chargement libéré avant de passer au suivant — évite les conflits de cache pendant l'ensemble du test.</p>
<p><strong>LoadingManager.setURLModifier pour les graphes glTF d'un CDN.</strong> Lorsqu'un CDN fournit un glTF sous la forme d'un graphe d'URI relatives accompagné d'une table d'inclusion — chemin relatif vers URL absolue — <code>setURLModifier</code> résout via le CDN chaque dépendance demandée par le chargeur sans réécrire le JSON. Une distribution multifichier et multirésolution devient ainsi un chargement en un seul clic. Libérer la géométrie, les matériaux et les textures de chaque modèle précédent avant de charger le suivant évite l'accumulation de centaines de Mo de mémoire GPU au cours d'une session de navigation.</p>
<p><strong>Simplification non destructive des maillages.</strong> Conserver un clone de la géométrie d'origine de chaque maillage et reconstruire uniquement le tampon d'indices pour chaque ratio de simplification permet des modifications rapides tout en évitant les dégâts cumulatifs provoqués par des simplifications répétées. Traiter séparément chaque portion de <code>geometry.groups</code>, puis reconstruire les groupes, préserve l'affectation des différents matériaux. Consultez <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">LOD et meshoptimizer</a> pour découvrir comment cette méthode alimente le LOD fondé sur la distance.</p>
<hr>
<p>Partie 17 sur 29.
Précédent : <a href="/fr/blog/2026-04-28-open-world-browser-part-16-structure-and-authoring">Partie 16 — Une structure pour un monde qui ne cesse de grandir</a>
Suivant : <a href="/fr/blog/2026-05-02-open-world-browser-part-18-ai-scattering">Partie 18 — Un pinceau de dispersion au placement digne d'une IA</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Anatomie d'un moteur de jeu IA : ce que contient réellement le prompt]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-04-30-anatomy-of-an-ai-game-engine</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-04-30-anatomy-of-an-ai-game-engine</guid>
            <pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Cinevva ressemble à une simple zone de texte. Derrière celle-ci se trouvent un orchestrateur, 26 outils typés, onze fournisseurs de ressources, une iframe de jeu en direct dans laquelle l'agent peut jouer, ainsi qu'une vitrine connectée à des reels de gameplay. Voici la machine entière, pièce par pièce.]]></description>
            <content:encoded><![CDATA[<h1>Anatomie d'un moteur de jeu IA : ce que contient réellement le prompt</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, CEO de Cinevva</em></p>
<p>La remarque que les inconnus font le plus souvent à propos de Cinevva, c'est qu'il ne s'agit que d'une surcouche d'IA. Ils arrivent sur la page d'accueil, voient une unique zone de saisie affichant « Décrivez votre jeu », puis décident qu'ils ont compris le produit. Ils ont compris la porte d'entrée. Le produit, c'est tout ce que vous ne voyez pas lorsque vous saisissez cette phrase, plus, depuis cette semaine, un monde ouvert dans lequel vos joueurs entrent pour découvrir votre jeu.</p>
<p>Derrière cette zone de saisie se trouvent un moteur de jeu complet, un orchestrateur doté de 26 outils typés, une flotte de modèles génératifs, une recherche de ressources agrégée auprès de onze fournisseurs, une iframe de jeu en direct que l'agent peut lire et modifier, ainsi qu'une vitrine où ce même agent publie votre jeu terminé sous la forme d'un reel de gameplay à faire défiler. Rien de tout cela n'est exotique. Ce n'est que de la plomberie. Le travail intéressant a consisté à décider quoi connecter à quoi.</p>
<p>Cet article passe en revue toute la machine, de haut en bas. C'est l'explication que nous donnons aux investisseurs techniques et celle que nous donnons aux développeurs qui envisagent de contribuer. Ils cherchent la même réponse.</p>
<h2>1. Le prompt est l'interface, pas le système</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-prompt.png" alt="La zone de saisie du prompt Cinevva sur la page d'accueil. Un unique champ de texte affiche « Créez un jeu de tir spatial néon avec des astéroïdes, des bonus et une bande-son synthé ». En dessous se trouvent trois suggestions et le logo Cinevva." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>La première décision de conception a été de tout masquer jusqu'à ce que l'utilisateur soit réellement investi. La page d'accueil n'a qu'une seule mission : transformer une phrase en projet. Nous avons séparé l'expérience en un espace public — le prompt, les sélections et le fil de la vitrine — et un espace créateur — l'IDE, les outils de ressources et le processus de publication — afin que les personnes qui veulent simplement jouer n'aient pas à voir le cockpit.</p>
<p>C'est pourquoi le texte indicatif alterne entre des exemples comme « Créez un jeu de serpent avec des graphismes néon » et « Jeu de tir sur des zombies en vue du dessus ». Ce ne sont pas des tutoriels. Ce sont des autorisations à se lancer. L'utilisateur comprend qu'une « idée de jeu d'arcade bizarre » est une entrée parfaitement valide.</p>
<p>Dès que vous envoyez votre demande, trois choses se produisent. Le système crée un nouveau projet de jeu doté d'un identifiant stable. Il initialise une arborescence de fichiers vide (<code>index.html</code>, <code>game.js</code>, <code>style.css</code>, <code>assets/</code>, <code>GDD.md</code>). Puis il transmet votre phrase à l'orchestrateur avec une fenêtre de contexte de projet pointant vers ces fichiers vides. À partir de là, vous parlez à un agent qui peut agir.</p>
<h2>2. Derrière la zone de saisie, un IDE complet dans le navigateur</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-ide.png" alt="L'IDE de création Cinevva en mode sombre. Trois panneaux : à gauche, une arborescence contenant GDD.md, index.html, game.js, style.css et un dossier de ressources ; au centre, un éditeur de code avec coloration syntaxique affichant la configuration d'un moteur de rendu Three.js ; à droite, un aperçu en direct montrant un jeu de tir spatial néon en vue du dessus, avec un ATH SCORE et VIES." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>L'espace de création est un IDE à trois panneaux qui fonctionne entièrement dans le navigateur. L'arborescence des fichiers se trouve à gauche, l'éditeur de code au centre, l'iframe du jeu en direct à droite, et une zone de chat longe le bas de l'écran. Nous avons choisi cette disposition délibérément. Les personnes qui écrivent déjà du code la reconnaissent immédiatement. Celles qui n'en écrivent pas peuvent ignorer le panneau central et communiquer uniquement par le chat.</p>
<p>L'arborescence des fichiers n'est pas une métaphore. Chaque projet est réellement un petit site statique composé de fichiers HTML, JS et CSS, d'images générées, de modèles GLB et de fichiers audio, servis directement depuis R2 par l'intermédiaire d'un Cloudflare Worker. Il n'y a aucune étape de compilation. Et la raison est importante. Cela signifie que l'agent peut lire et modifier les fichiers que vous publieriez réellement, et que le même artefact fonctionne dans l'iframe, sur un téléphone encapsulé avec Capacitor, sur Steam avec Tauri et dans Discord en tant qu'Embedded Activity. Un seul système de fichiers, six cibles de distribution.</p>
<p>Chaque jeu que nous générons doit exposer deux fonctions sur <code>window</code>. <code>getGameState()</code> renvoie un objet simple décrivant le jeu en cours — position du joueur, score, vies, ennemis et minuteurs. <code>setGameState(patch)</code> applique une mise à jour partielle au jeu en direct. Cette minuscule API constitue le contrat qui permet à l'agent d'inspecter et de modifier un jeu en cours sans le recharger. Nous reviendrons sur son importance dans la section 6.</p>
<h2>3. L'orchestrateur, là où réside le véritable moteur</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-orchestrator.png" alt="Schéma de l'orchestrateur Cinevva. Un nœud central intitulé « Orchestrateur (LLM) » est relié à huit nœuds d'outils disposés autour de lui : list_game_files, read_files, edit_files, write_files, generate_image (Flux), generate_skybox (Blockade), generate_music (ElevenLabs) et rig_model (Tripo). Une ligne pointillée relie l'orchestrateur à un nœud « Iframe du jeu » intitulé « captures d'écran + console »." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Ce que les gens ne voient pas lorsqu'ils qualifient Cinevva de « surcouche d'IA », c'est que l'IA n'écrit pas tant du code qu'elle ne dirige un petit orchestre. L'orchestrateur est une boucle d'appel d'outils exécutée avec un modèle de pointe, dotée d'un prompt système d'environ mille lignes et d'une interface typée comprenant 26 fonctions. Les définitions des outils constituent le moteur. Le modèle en est le chef d'orchestre.</p>
<p>Les 26 outils se répartissent en cinq familles. Les outils de système de fichiers (<code>list_game_files</code>, <code>read_files</code>, <code>search_game_file</code>, <code>edit_files</code>, <code>write_files</code>, <code>delete_files</code>, <code>rename_files</code>) permettent à l'agent de traiter votre jeu comme n'importe quelle autre base de code. Les outils de projet (<code>create_new_game</code>, <code>list_games</code>, <code>open_game</code>, <code>ask_user</code>, <code>game_ready</code>) gèrent le cycle de vie d'un projet et sa restitution à l'utilisateur. Les outils génératifs (<code>generate_image</code>, <code>generate_skybox</code>, <code>generate_music</code>, <code>generate_sfx</code>, <code>rig_model</code>, <code>apply_animation</code>, <code>list_animations</code>, <code>list_skybox_styles</code>) font appel à des modèles spécialisés que nous présenterons ensuite. Les outils de connaissances (<code>search_fonts</code>, <code>list_library_docs</code>, <code>search_library_docs</code>, <code>list_generations</code>) fournissent à l'agent des sources de vérité fiables et indexées plutôt que des API inventées. Enfin, les outils d'exécution (<code>read_console_logs</code>, <code>execute_js</code>) permettent à l'agent de voir et de manipuler le jeu en direct.</p>
<p>Un premier échange typique ressemble à ceci. L'utilisateur saisit « Créez un jeu de tir spatial néon ». Le modèle émet un appel à <code>write_files</code> avec un fichier <code>GDD.md</code> décrivant la conception, puis appelle de nouveau <code>write_files</code> avec <code>index.html</code>, <code>game.js</code> et <code>style.css</code>. Il appelle <code>generate_music</code> avec <code>prompt=&quot;synthwave space shooter, 120 BPM, driving bassline&quot;</code>, ce qui lance une tâche auprès d'ElevenLabs Music et renvoie une URL MP3 dans R2. Il appelle <code>generate_skybox</code> avec un style du modèle 3 de Blockade Labs pour le champ d'étoiles. Il appelle <code>game_ready(title, message)</code> pour faire basculer l'iframe vers la nouvelle version, puis <code>read_console_logs</code> afin de confirmer que le jeu a démarré sans erreur. S'il détecte une trace de pile, il revient à <code>edit_files</code> et corrige le problème avant même que l'utilisateur ne le signale.</p>
<p>La propriété intéressante de cette architecture est que le prompt système et les schémas des outils sont les seuls éléments propres au produit. Remplacez le modèle et tout continue de fonctionner. Au cours de l'année écoulée, nous avons migré entre trois modèles de pointe sans modifier l'interface, car cette interface nous appartient.</p>
<h2>4. Le volet ressources : une distribution entre plusieurs modèles</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-assets.png" alt="Un tableau de bord de génération de ressources en trois panneaux. Le panneau de gauche est un générateur de musique avec le prompt « jeu de tir spatial synthwave, 120 BPM, ligne de basse entraînante », un curseur de durée réglé sur 60 secondes et un bouton Générer, ainsi qu'une carte de forme d'onde en cours de lecture intitulée « Thème de la dérive des astéroïdes ». Le panneau central est un générateur de skybox avec le prompt « espace profond, nébuleuse lointaine, deux lunes » et une grille de quatre miniatures d'environnements à 360 degrés. Le panneau de droite présente un aperçu rotatif d'un vaisseau spatial violet low poly en 3D, avec un bouton « Rig + Marche » et un indicateur d'état affichant « Rigging... 1 min 42 s restantes »." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>L'agent ne génère lui-même ni images, ni musique, ni sons, ni objets 3D. Il fait appel à des spécialistes. Le volet ressources de Cinevva distribue les tâches entre les meilleurs modèles spécialisés, reliés de façon à ce que la sortie de l'un constitue une entrée valide pour le suivant.</p>
<p>Pour les images et les sprites, <code>generate_image</code> appelle Flux Pro 1.1 avec une largeur et une hauteur toujours multiples de 32, ainsi qu'un format de sortie qui passe au PNG lorsque le prompt demande de la transparence. Pour les morceaux complets, l'agent appelle ElevenLabs Music avec un prompt stylistique et une durée en secondes. Pour les effets ponctuels, ElevenLabs SFX produit en cinq à quinze secondes un extrait à partir d'un texte comme « tir de pistolet laser, blaster de science-fiction ». Pour les environnements à 360 degrés, <code>generate_skybox</code> appelle Blockade Labs avec l'un des quelque quatre-vingt-dix styles disponibles, en utilisant par défaut Digital Painting du modèle 3. Pour les personnages 3D, <code>rig_model</code> envoie un GLB à Tripo afin de lui ajouter une armature et un rig de créature bipède, quadrupède, hexapode, octopode, serpentine ou aquatique, puis <code>apply_animation</code> exécute l'une des quinze animations prédéfinies — attente, marche, course, saut, escalade, plongeon, coup tranchant, tir, blessure, chute, rotation, ainsi que les marches propres aux créatures non bipèdes.</p>
<p>Chacune de ces opérations est une tâche, et non un appel synchrone. Elles prennent de quelques secondes à plusieurs minutes. L'orchestrateur les lance, renvoie immédiatement un <code>jobId</code>, puis un canal séparé transmet le résultat dans la conversation lorsque la ressource est prête. Cela change complètement l'expérience. L'agent peut continuer à développer la logique du jeu pendant qu'un modèle 3D finit d'être riggé en arrière-plan. L'utilisateur n'a jamais à attendre qu'une unique opération critique se termine.</p>
<p>Tous les artefacts arrivent au même endroit : R2, derrière <code>cdn.cinevva.com</code>, et sont accessibles au moyen de simples URL. Ainsi, l'appel <code>write_files</code> suivant de l'agent peut simplement intégrer <code>&lt;audio src=&quot;https://cdn.cinevva.com/audio/abc.mp3&quot;&gt;</code>, et le jeu fonctionne immédiatement. Aucun SDK, aucun bundler de ressources, aucun manifeste.</p>
<h2>5. Recherche de ressources auprès de onze fournisseurs</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-asset-search.png" alt="La page de recherche de ressources Cinevva en mode sombre. La barre de recherche en haut contient la requête « roche d'astéroïde » et une rangée de filtres par fournisseur affiche Tous, PolyHaven, Sketchfab, Kenney, Quaternius, AmbientCG, OpenGameArt, Freesound, Smithsonian, Synty et Jamendo, avec Sketchfab sélectionné. En dessous se trouve une grille de miniatures de modèles d'astéroïdes sur quatre colonnes, chaque carte affichant le badge de son fournisseur, une étiquette de licence CC0 ou CC-BY et un bouton « Utiliser dans le jeu ». Une petite légende dans le coin supérieur droit montre une bulle de discussion indiquant « ChatGPT : l'un des meilleurs outils gratuits de ressources pour les créateurs de jeux »." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>La génération est idéale lorsque vous avez besoin de quelque chose de précis. La recherche est plus rapide lorsque la ressource existe déjà. La <a href="/fr/assets">Bibliothèque de ressources</a> est un moteur de recherche fédéré couvrant onze fournisseurs de ressources gratuites ou sous licence, dont les résultats sont réunis dans un seul fil classé.</p>
<p>Le registre des fournisseurs se trouve dans le worker et exécute chaque requête en parallèle auprès de PolyHaven, Sketchfab, Freesound, Kenney, AmbientCG, Quaternius, OpenGameArt, de la collection 3D du Smithsonian, de TurboSquid, de Synty et de Jamendo. Chaque fournisseur implémente une interface commune (<code>search</code>, <code>getMetadata</code>, <code>isBrowsable</code>, <code>getSupportedTypes</code>), de sorte que l'ajout d'un nouveau fournisseur ne nécessite qu'un seul fichier TypeScript. La couche d'agrégation gère la pagination par curseur entre des fournisseurs utilisant des systèmes de pagination différents, déduplique les licences, normalise les types de ressources dans une taxonomie commune — modèle, texture, audio, HDRI, sprite — et associe chaque ressource à sa licence afin que l'agent ne choisisse jamais un élément qu'il ne peut pas redistribuer.</p>
<p>Lorsque ChatGPT a recommandé cet outil le trimestre dernier comme l'un des meilleurs moteurs gratuits de recherche de ressources pour les créateurs de jeux, nous sommes allés lire la recommandation. Elle visait juste sur l'essentiel. L'intérêt n'est pas de proposer une barre de recherche couvrant une seule bibliothèque. L'intérêt est qu'un développeur indépendant recherchant une « texture de mur en pierre » obtient côte à côte des résultats de PolyHaven et d'AmbientCG, une version stylisée de Kenney et une option de photogrammétrie du Smithsonian, le tout avec une seule requête et des métadonnées de licence cohérentes. L'agent peut interroger le même endpoint et choisir des ressources de la même manière qu'un humain.</p>
<p>C'est la partie du moteur dont l'importance est souvent sous-estimée. La plupart des démos de création de jeux par IA génèrent tout de zéro et produisent un rendu uniforme, brillant et légèrement étrange. Les jeux intéressants sur Cinevva associent des ressources générées à de véritables photogrammétries CC0 du Smithsonian et à un arbre low poly fabriqué à la main par Quaternius. L'orchestrateur peut réaliser ce mélange en un seul échange.</p>
<h2>6. L'agent joue au jeu</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-debug.png" alt="Une vue de débogage en direct. À gauche, un jeu de tir spatial néon en cours d'exécution montre le vaisseau du joueur esquivant des astéroïdes, avec un SCORE de 4 820 et 2 vies restantes. À droite, deux panneaux superposés : un panneau execute_js affichant l'appel JSON.stringify(getGameState()) et un objet JSON renvoyé contenant la position, la vélocité et la santé du joueur, le score, le nombre de vies et le nombre d'astéroïdes ; et un panneau read_console_logs affichant les dernières lignes du journal, notamment l'apparition d'un astéroïde, la collecte d'un bonus, un avertissement de chargement de texture et une mesure de 58 FPS." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>C'est la partie qui surprend le plus les ingénieurs lorsque nous la leur présentons. L'agent ne se contente pas d'écrire du code dans des fichiers. Il exécute le jeu dans une iframe isolée, lit sa console, prend des captures d'écran séparant l'interface utilisateur de la scène 3D et exécute du JavaScript dans l'environnement en direct afin d'inspecter ou de modifier son état.
<code>read_console_logs</code> renvoie les cinquante dernières lignes de <code>console.log</code>, <code>console.warn</code>, <code>console.error</code> ainsi que les exceptions non interceptées provenant de l’iframe. Après chaque appel à <code>game_ready</code>, le prompt système oblige l’agent à appeler <code>read_console_logs</code> et à corriger toutes les erreurs avant de rendre le projet à l’utilisateur. Cette règle à elle seule élimine la plupart des échecs du type « l’IA a dit que c’était terminé, mais l’écran est noir » que rencontrent les autres outils.</p>
<p><code>execute_js</code> est l’outil le plus puissant. Il exécute du JavaScript arbitraire dans la portée globale du jeu. Comme chaque jeu doit implémenter <code>getGameState</code> et <code>setGameState</code>, l’agent peut exécuter <code>JSON.stringify(getGameState())</code> pour lire l’intégralité de l’état du jeu en cours d’exécution, puis <code>setGameState({player: {x: 500, y: 100}})</code> pour téléporter le joueur, <code>setGameState({lives: 99})</code> pour lui accorder l’invincibilité ou <code>setGameState({level: 3})</code> pour passer directement à la suite. Il peut également inspecter des couches plus basses, par exemple avec <code>scene.children.map(c =&gt; c.type)</code> pour examiner le graphe de scène Three.js ou <code>document.querySelectorAll('canvas').length</code> pour confirmer que le moteur de rendu a bien été monté.</p>
<p>Vous comprenez pourquoi c’est important. Lorsqu’un utilisateur dit « le joueur reste coincé dans le mur au niveau deux », l’agent n’a pas besoin de deviner. Il ouvre le niveau deux, exécute <code>getGameState()</code>, examine les données de collision, applique un correctif avec <code>execute_js</code> pour le tester, puis seulement ensuite écrit la correction sur le disque. Il débogue comme vous déboguez. Nous considérons l’iframe comme un pair, pas comme une cible.</p>
<h2>7. La documentation des bibliothèques, cette ennuyeuse source de vérité</h2>
<p>Nous indexons la documentation officielle des bibliothèques réellement utilisées par les jeux (Three.js, Tone.js, Cannon, et une liste qui ne cesse de s’allonger) dans un référentiel JSON structuré et interrogeable. L’agent se tourne vers <code>search_library_docs(&quot;threejs&quot;, &quot;Mesh&quot;)</code> avant de se tourner vers le web ouvert. C’est plus rapide, versionné, et cela n’hallucine jamais une méthode renommée deux versions mineures plus tôt.</p>
<p>Ce n’est pas très spectaculaire. Mais c’est ce qui distingue une IA qui produit du code fonctionnel d’une IA qui produit du code convaincant en apparence, mais qui plante à la quarantième ligne. Une grande partie de la qualité visible des jeux créés avec Cinevva découle de cette seule décision.</p>
<h2>8. De jouable à découvrable : les reels et la vitrine</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-reels.png" alt="Une vitrine de courtes vidéos de gameplay. Au centre, une carte en forme de téléphone diffuse une vidéo au format portrait d’un jeu de tir spatial néon en plein combat, avec des icônes sociales empilées à droite (un cœur avec 14 k, une bulle de discussion avec 3,2 k, une flèche de partage avec 1,8 k et un bouton « Jouer maintenant »). La légende du reel en bas indique « Asteroid Drift • par @indiedev • 3,1 M de vues ». À gauche, un petit extrait de discussion intitulé « Depuis la discussion du créateur » affiche le message de l’assistant « Le jeu est en ligne. Le reel a été publié automatiquement dans votre vitrine. » À droite, un panneau Vitrine répertorie trois jeux tendance : Asteroid Drift, Roadhawk Runner et Birb Clicker, chacun avec un nombre de vues et un badge « Tendance »." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Un jeu auquel personne ne joue reste un loisir privé. Le problème non résolu le plus difficile du développement indépendant n’est plus la création des jeux. C’est leur distribution. Le moteur ne s’arrête donc pas à « jouable ». Il s’arrête à « devant les joueurs ».</p>
<p>Chaque projet sur Cinevva possède une URL de partage stable, comme <code>play.cinevva.com/{game-id}</code>, qui ouvre la dernière version du jeu dans une iframe en pleine page, avec une petite surcouche pour les mentions J’aime et les partages. Lorsque l’agent appelle <code>game_ready</code>, il ne se contente pas d’activer l’aperçu dans l’environnement de développement. Il propose également de publier une vidéo de gameplay de 15 secondes dans la <a href="/fr/play">vitrine Cinevva</a>. Le reel est un véritable enregistrement d’une partie, capturé directement depuis l’iframe (nous fournissons un petit script d’enregistrement que l’agent injecte dans l’environnement d’exécution du jeu). Ce que le spectateur voit défiler est donc du vrai gameplay, et non un montage promotionnel.</p>
<p>Cela transforme la boucle de travail du développeur. L’ancienne boucle consistait à coder, compiler, empaqueter, téléverser, rédiger une page Steam, lutter contre un algorithme, puis surveiller un compteur de listes de souhaits. La nouvelle boucle consiste à saisir une phrase, regarder l’agent construire le jeu, appuyer sur Publier, puis voir son jeu apparaître dans le même flux vertical que tous les autres. La découverte se fait dans le même produit que la création, à la même URL et avec le même compte.</p>
<p>Pour les membres de la communauté des développeurs, c’est la partie à examiner de près. La distribution a toujours été le fossé défensif que les moteurs de jeu n’intégraient pas. Unity fournit un éditeur, puis vous publiez le jeu sur Steam. Unreal fournit un éditeur, puis vous publiez sur l’Epic Store. Cinevva fournit les deux moitiés. Le moteur et la vitrine communiquent par l’intermédiaire du même orchestrateur qui a construit le jeu.</p>
<h2>9. Les jeux les plus performants : le cercle vertueux de la découverte</h2>
<img src="/img/blog/engine-anatomy/cinevva-engine-charts.png" alt="Le classement des jeux les plus performants sur Cinevva. Trois onglets en haut indiquent Tendances, Récents et Meilleurs, l’onglet Meilleurs étant sélectionné. En dessous, un podium présente trois jeux à la une : en première position The Breaker Belt avec 42,1 k parties, en deuxième Roadhawk Runner avec 27,7 k parties et en troisième Asteroid Drift avec 37,1 k parties. Sous le podium se trouve une grille plus dense de six petites cartes de jeux, notamment Pixel Platformer, Zombie Shooter, Fruit Ninja Clone, Tower Defense, Card Battler et Zombie Builder, chacune avec une miniature, le nom d’utilisateur de son créateur et son nombre de parties." style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Le flux de reels constitue une moitié du cercle vertueux. Le <a href="/fr/charts">classement des jeux les plus performants</a> constitue l’autre. Il classe les jeux selon les tendances, les nouveautés et les meilleurs résultats de tous les temps, en pondérant le taux de complétion (le joueur a-t-il terminé une session ?) plutôt que le nombre brut de clics. Le taux de complétion est plus difficile à manipuler que le nombre d’installations. Un jeu qui retient l’attention pendant soixante secondes surpasse un jeu que l’on ouvre puis ferme aussitôt.</p>
<p>Chaque signal alimente le tour suivant de l’orchestrateur. Lorsque l’agent dit « ajoutons un bonus », il ne puise pas dans une bibliothèque de modèles figée. Il s’appuie sur un modèle qui a observé quelles mécaniques retiennent réellement les joueurs sur Cinevva ce mois-ci. Le cercle vertueux n’est pas simplement un classement au bas de la page. C’est le signal d’entraînement de l’ensemble du moteur.</p>
<p>Pour les investisseurs, c’est là que réside notre avantage défensif fondé sur les données. Nous n’avons pas seulement des jeux générés. Nous avons des jeux générés accompagnés de véritables métriques d’engagement au niveau des sessions, reliées aux prompts qui les ont produits, aux choix d’assets effectués par l’agent et aux joueurs qui les ont aimés. Chaque boucle renforce la suivante.</p>
<h2>10. Le moteur devient un jeu</h2>
<p>Voici la partie que je laisse entrevoir sur les réseaux sociaux depuis des mois. Tout ce qui est décrit dans les sections 1 à 9 concerne un système qui construit des jeux. Ce qui est plus difficile à décrire, tant qu’on ne l’a pas vu, c’est ce qui se produit lorsque le système lui-même devient un lieu.</p>
<p>Créer un jeu reste un travail difficile, même avec tout ce que nous avons développé. Si vous n’avez encore jamais créé de jeu, vous vous retrouvez soudain responsable de l’intrigue, des personnages, du monde, des mécaniques, de qui fait quoi et quand, du comportement de la lumière, de la superposition des effets spéciaux, de la musique, des effets sonores et des mouvements de caméra. C’est beaucoup. Et en plus de tout cela, vous voulez que le résultat vous ressemble, pas qu’il ait l’air d’un modèle recraché par l’outil de quelqu’un d’autre. Le prompt et l’orchestrateur prennent en charge l’essentiel du travail mécanique. Ce qu’ils ne peuvent pas faire seuls, c’est offrir à votre création terminée une scène où se présenter sous le regard des autres.</p>
<p>Nous avons donc construit cette scène. Pendant la majeure partie de cette année, <a href="/fr/about">Oleg</a> et l’équipe du moteur ont publié leurs travaux dans une <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">série en 14 parties</a> consacrée à l’intégration d’un monde ouvert dans le navigateur. Un terrain sculpté que l’on peut creuser en temps réel. Des biomes qui se couvrent automatiquement de roche ou d’herbe selon la pente et l’altitude. 80 000 brins d’herbe qui se courbent dans le vent sur un terrain généré par calcul. Un personnage qui marche, sprinte, saute et plane à la fois sur un terrain à carte de hauteurs et sur un terrain volumétrique, au cours d’une même étape. Rien de tout cela n’était une démonstration technique conçue pour elle-même. C’était le socle de ce qui suit.</p>
<p>Aujourd’hui, le monde ouvert est accessible dans une première version. Vous choisissez un avatar et vous y entrez. Vos jeux publiés apparaissent autour de vous sous forme d’espaces à visiter, comme ils apparaissent aujourd’hui dans les <a href="/fr/play">reels de la vitrine</a> et dans le <a href="/fr/charts">classement des jeux les plus performants</a>, à ceci près que vous vous y rendez à pied au lieu de les faire défiler d’un geste. Vous pouvez entrer dans le jeu d’un inconnu depuis le monde sans quitter ce monde. Vous pouvez vous tenir à côté de la personne qui a créé ce à quoi vous venez de jouer et lui dire ce que vous ajouteriez ensuite. Vous pouvez sculpter un morceau de terrain avec quelqu’un que vous venez de rencontrer et en faire le point de départ d’un nouveau projet. La découverte cesse d’être un flux. Elle devient une promenade.</p>
<p>Notre moteur a toujours été un moteur de jeu au sens technique. Aujourd’hui, il le devient aussi au sens littéral. Ce qui construit votre jeu est désormais un jeu en soi. Le même orchestrateur que dans la section 3, les mêmes garde-fous pour les assets que dans la section 4, le même contrat <code>getGameState</code> que dans la section 6, les mêmes reels que dans la section 8 et le même classement que dans la section 9 s’assemblent tous pour former ce lieu unique dans lequel vos joueurs peuvent évoluer. La frontière entre créer et jouer a toujours été artificielle. Elle a maintenant disparu.</p>
<h2>Ce que nous pensons avoir bien fait</h2>
<p>La première chose que nous avons bien faite a été de considérer le prompt comme un moyen d’interaction avec l’interface plutôt que comme un produit. Le produit, c’est l’orchestrateur, la palette d’outils, les garde-fous pour les assets et la vitrine. Le prompt est le moyen le moins coûteux possible de faire entrer un utilisateur dans le système.</p>
<p>La deuxième a été d’imposer un système de fichiers plat, natif du navigateur et sans étape de compilation. C’est ce qui permet à un jeu Cinevva d’être publié sur mobile, ordinateur, Steam et Discord à partir d’une seule source de vérité. C’est aussi ce qui permet à l’agent de lire ce qu’il a écrit.</p>
<p>La troisième a été le contrat selon lequel chaque jeu généré implémente <code>getGameState</code> et <code>setGameState</code>. Cette seule ligne dans le prompt système est ce qui donne à l’agent l’allure d’un collaborateur plutôt que d’un générateur de code. L’agent peut jouer, et un système capable de jouer peut déboguer.</p>
<p>La quatrième a été d’intégrer la vitrine à la même boucle que le moteur. Nous ne gagnerons pas uniquement grâce à la qualité brute du modèle. Avoir deux mois d’avance sur un générateur à poids ouverts ne constitue pas un avantage défendable. Nous gagnons lorsque le même agent qui construit votre jeu le publie également, mesure ses performances et apprend de la façon dont des inconnus y ont joué.</p>
<p>Si vous voulez voir toute la machine en mouvement, le prompt de la page d’accueil reste la porte d’entrée. Saisissez une phrase. Regardez ce qui se passe derrière. Puis revenez ici et dites-nous quelle partie du moteur nous devrions présenter ensuite.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 16 : une structure pour un monde qui ne cesse de grandir]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-04-28-open-world-browser-part-16-structure-and-authoring</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-04-28-open-world-browser-part-16-structure-and-authoring</guid>
            <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 33 a découpé un monolithe de 6 285 lignes en modules et révélé trois bugs cachés liés à l’ordre d’initialisation. Le spike 34 s’est appuyé sur cette structure pour ajouter plus de 100 accessoires plaçables sans rien alourdir.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 16 : une structure pour un monde qui ne cesse de grandir</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et renvoie vers toutes les parties.</p>
<p>Depuis la <a href="/fr/blog/2026-04-13-open-world-browser-part-13-terrain-sculpting">partie 13</a>, chaque spike suivait la même recette : copier le monolithe précédent, puis ajouter une fonctionnalité. À la fin du <a href="/fr/blog/2026-04-13-open-world-browser-part-15-multiplayer-and-water">spike 32</a>, ce monolithe comptait 6 285 lignes d’<code>index.html</code> dans un unique <code>&lt;script type=&quot;module&quot;&gt;</code>. Les recherches dans le code produisaient trop de bruit, trouver où ajouter une fonctionnalité prenait plus de temps que l’écrire, et toute modification architecturale touchait un fichier trop volumineux pour en appréhender mentalement le diff. Avant d’ajouter une autre fonctionnalité, nous avons donc remis de l’ordre dans la structure.</p>
<h2>Découper le monolithe sans modifier le moindre comportement</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/33-code-structure/" title="Spike 33 : refactorisation de la structure du code" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/33-code-structure/" target="_blank">Ouvrir le spike 33 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=33-code-structure">Voir le code source</a></p>
<p>La contrainte était stricte : chaque découpage devait être une pure refactorisation, pas une reconception. Le monolithe est devenu 19 fichiers <code>.mjs</code> accompagnés d’une enveloppe hôte de 151 lignes. Des modules de premier niveau gèrent la scène, l’eau, l’herbe, le personnage, la physique, le multijoueur et l’interface, tandis qu’un fichier <code>wgsl.mjs</code> contient toutes les chaînes de code source WGSL et constitue l’unique source de vérité côté GPU. Un sous-dossier <code>terrain/</code> regroupe la heightmap, le SDF, les chunks, les enveloppes de buffers GPU, la brosse, le LOD et la persistance. Le total atteint 6 555 lignes, soit essentiellement le monolithe augmenté du code standard des imports. Aucun changement net de volume, mais une nette amélioration de la navigabilité.</p>
<p>Puis la page s’est chargée sur un écran noir. Deux messages d’erreur, pour deux causes profondes sans rapport. Le premier était une plainte de WebGPU concernant la liaison d’un buffer de zéro octet. Dans le monolithe, le buffer de la brosse SDF était alloué à la demande lors de l’apparition du premier chunk de marching cubes, et la fabrique de groupes de liaison s’exécutait fortuitement plus tard, une fois le buffer créé. La séparation de <code>terrain/gpu.mjs</code> et <code>terrain/brush.mjs</code> a réordonné l’évaluation des modules : la fabrique s’exécutait désormais en premier et tentait de lier un espace réservé <code>null</code>. La solution consistait à différer la création du groupe de liaison jusqu’au premier dispatch grâce à un helper <code>getOrCreateBindGroup(chunk)</code>. Le modèle « tout créer dès le départ » n’était qu’un artefact de l’unique chemin d’initialisation du monolithe.</p>
<p>Le deuxième message était un avertissement inquiétant à propos d’un squelette FBX, mais il s’est révélé être une fausse piste. Il s’affichait depuis le spike 25 et était sans conséquence. Le personnage ne manquait qu’en raison de la propagation du premier bug : chaque dispatch de calcul levait une exception, la heightmap n’était jamais écrite, les échantillons de hauteur renvoyaient 0, et le personnage apparaissait à l’origine avant de tomber à travers le monde. Une fois le buffer corrigé, le personnage s’animait normalement, avertissement compris.</p>
<p>Voilà la véritable leçon de cette refactorisation. Le monolithe dissimulait toutes les relations du type « X doit exister avant de construire Y » dans l’ordre d’exécution du script, de haut en bas. La modularisation a bouleversé cet ordre et révélé trois autres bugs d’ordonnancement latents : la dispersion de l’herbe avant le téléversement de la heightmap, l’ajout du plan d’eau avant la fin du décodage de la carte d’environnement et le chargement de la persistance après la première frame. Tous trois se corrigeaient en une ligne, et aucun n’aurait été découvert sans ce découpage.</p>
<h2>Une centaine d’accessoires sans rien faire grossir</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/34-world-authoring/" title="Spike 34 : création du monde" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/34-world-authoring/" target="_blank">Ouvrir le spike 34 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=34-world-authoring">Voir le code source</a></p>
<p>Le spike 34 devait vérifier si cette structure portait ses fruits. L’objectif était de créer une palette en vue subjective permettant de placer des arbres, des rochers, des buissons, des champignons et des chemins issus d’un pack de modèles CC0, avec alignement sur le terrain, persistance, synchronisation multijoueur et colliders physiques, le tout sans quitter les commandes. Chaque ligne de nouveau code a été ajoutée dans cinq nouveaux fichiers sous <code>src/props/</code>, et aucun module existant n’a gagné plus de dix lignes de raccordement.</p>
<p>Le parcours des assets a connu un détour qu’il vaut la peine de consigner, car c’est exactement le genre de problème qui fait perdre une journée. Nous avons commencé avec le pack Ultimate Nature de Quaternius, une bibliothèque FBX dépourvue de textures intégrées. Les matériaux FBX étaient fournis en <code>MeshPhong</code> sans map ; nous avons donc câblé manuellement une table de correspondance entre noms de matériaux et fichiers PNG, converti les matériaux Phong en Standard et défini les espaces colorimétriques à la main. Environ 30 % des matériaux n’avaient aucun PNG correspondant, et plusieurs noms étaient ambigus entre des arbres similaires. Un deuxième pack FBX présentait la même lacune. La solution n’était pas d’ajouter encore des tables de correspondance, mais de choisir un pack mieux conçu : le Stylized Nature MegaKit de Quaternius fournit 116 fichiers glTF complets avec matériaux PBR intégrés et normales précalculées. Remplacer <code>FBXLoader</code> par <code>GLTFLoader</code> a supprimé la conversion d’échelle des centimètres vers les mètres, la table des textures et la conversion Phong, tout en réduisant <code>library.mjs</code> d’environ 80 lignes. Conclusion : le glTF avec PBR est le pipeline adapté aux packs CC0 qui le proposent, tandis que le FBX avec association manuelle des textures demandait deux fois plus de code pour deux fois moins de qualité.</p>
<p>Le parcours glTF comportait aussi quelques pièges. La palette affiche 116 miniatures, et avec WebGPU, <code>canvas.toDataURL()</code> renvoie une image vide pour une surface <code>GPUCanvasContext</code>. Les miniatures sont donc rendues dans un <code>RenderTarget</code>, relues avec <code>readRenderTargetPixelsAsync</code>, puis copiées dans un canvas 2D en tenant compte de l’alignement des lignes sur 256 octets imposé par WebGPU. Les aperçus fantômes clonent chaque matériau pour le teinter en vert, ce qui cassait sur les maillages dont la propriété <code>material</code> est un tableau ; une branche <code>Array.isArray</code> a résolu le problème. Enfin, les normal maps 16 bits qui occupaient environ 200 Mo ont été converties une fois pour toutes avec <code>mogrify -depth 8</code>, ramenant leur taille à environ 32 Mo sans différence visuelle, puisque les navigateurs réduisent de toute façon leur précision lors du téléversement.</p>
<h2>Quand la géométrie rendue n’existe que sur le GPU</h2>
<p>Le bug le plus instructif concernait l’aperçu fantôme, qui se déplaçait par à-coups de 1 à 2 mètres avec le curseur. Les maillages du terrain stockent les positions des sommets dans un <code>StorageBufferAttribute</code>, car le pipeline de calcul les écrit directement sur le GPU. Le <code>Raycaster</code> CPU de three.js ne peut donc pas les voir et ne renvoie rien. La solution de repli était un ray marching grossier, par pas de 1,5 m, sur la heightmap analytique ; ce pas fixe produisait la grille visible par l’utilisateur. Nous l’avons remplacé par un parcours adaptatif : pas de 2,5 m loin au-dessus de la surface, réduction à 0,4 m à moins de 5 m de celle-ci, puis 14 itérations de bissection lorsque le signe de $(\text{ray}_y - \text{terrain}_y)$ s’inverse. On obtient ainsi une précision inférieure au millimètre avec environ 30 grands pas et 14 bissections par lancer. Lorsque la géométrie rendue ne réside que sur le GPU, inutile de lutter contre le raycaster : parcourez plutôt la source analytique.</p>
<h2>Des colliders qui restent fidèles après les modifications</h2>
<p>Nous avons choisi des approximations primitives plutôt que des enveloppes convexes ou des colliders de maillage. Les accessoires Quaternius sont arrondis, low-poly et dépourvus de concavités significatives. Les enveloppes auraient donc demandé environ 50 fois plus de code et coûté 10 fois plus à l’exécution pour un gameplay identique. Chaque accessoire est réduit à une forme unique dérivée de sa boîte englobante : une capsule verticale pour les arbres et les cactus, une sphère pour les rochers, une capsule horizontale orientée selon l’axe longitudinal pour les troncs, et rien pour les buissons et fleurs décoratifs. Il est possible de marcher sur les rochers et les troncs — la poussée est uniquement verticale afin de pouvoir tenir dessus — tandis que les arbres et les cactus sont bloquants — la poussée s’exerce dans les trois dimensions pour empêcher d’escalader un tronc. Une table de hachage spatiale de 8 m limite les tests à chaque frame au voisinage 3×3 du joueur, soit généralement entre zéro et six accessoires.</p>
<p>Deux décisions de conception ont préservé la cohérence du système. L’alignement sur le terrain est un indicateur du manifeste, et non une énumération de catégories codée en dur. L’aperçu fantôme et le placement définitif lisent donc la même valeur <code>placement.alignToTerrain</code> et ne peuvent pas diverger. De plus, les accessoires placés réagissent aux modifications du terrain par l’intermédiaire d’un helper unique : après un coup de brosse, qu’il soit local ou rejoué depuis un pair, <code>refreshPlacementsInRadius</code> rééchantillonne le sol sous chaque accessoire dans le disque concerné, réapplique l’alignement et recalcule les extrémités du collider. Sculptez une colline sous un arbre, et l’arbre montera avec elle. La persistance et le multijoueur réutilisent exactement le modèle du spike 31 : une liste plate de <code>{uid, propId, x, y, z, rotY, scale}</code> est stockée, tandis que les événements de placement, de suppression et d’ajustement sont répliqués via BroadcastChannel.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Décomposition en modules ES sous WebGPU.</strong> Découper un <code>&lt;script type=&quot;module&quot;&gt;</code> monolithique en imports <code>.mjs</code> avec chemins simples ne nécessite aucun bundler lorsque les modules sont servis comme assets statiques, et TSL de three.js fonctionne parfaitement au-delà des frontières entre modules. Le coût caché réside dans l’ordre d’initialisation : un monolithe encode « construire X avant Y » dans l’ordre du script, de haut en bas, tandis que les modules sont évalués selon l’ordre des imports. Une fabrique de groupes de liaison GPU peut donc s’exécuter avant que son buffer n’existe. Le modèle de correction consiste à employer une initialisation à la demande (<code>getOrCreate...</code> lors de la première utilisation) et à attendre la bonne promesse plutôt qu’à dépendre de l’ordre des déclarations.</p>
<p><strong>glTF avec PBR intégré contre FBX avec association manuelle.</strong> Le glTF utilise les mètres, référence ses propres textures et fournit directement des <code>MeshStandardMaterial</code>. Un pack CC0 conçu en glTF s’intègre donc directement dans un pipeline PBR. Les packs FBX dépourvus de métadonnées de liaison des textures nécessitent une table manuelle de correspondance entre noms de matériaux et fichiers PNG, qui devient obsolète à chaque mise à jour du pack, ainsi qu’une conversion de Phong vers Standard et un étiquetage manuel des espaces colorimétriques. Un filet de sécurité pour la végétation transforme les matériaux <code>transparent</code> sans <code>alphaTest</code> en cartes découpées avec <code>alphaTest: 0.5</code>, afin qu’elles soient correctement triées derrière la géométrie opaque.</p>
<p><strong>Miniatures hors écran avec WebGPU.</strong> <code>canvas.toDataURL()</code> renvoie une image vide pour un canvas adossé à un <code>GPUCanvasContext</code>, car aucune voie ne permet de revenir d’une surface de présentation vers un contexte 2D. Le rendu dans un <code>RenderTarget</code>, la lecture des pixels avec <code>readRenderTargetPixelsAsync</code> et leur copie dans un canvas 2D fonctionnent, à condition de parcourir les données selon le pas de lecture aligné sur 256 octets de WebGPU. Les résultats sont mis en cache dans <code>localStorage</code> sous une clé dont la version est incrémentée, afin qu’une modification du pack invalide les rendus obsolètes.</p>
<p><strong>Ray marching adaptatif sur une heightmap analytique.</strong> Lorsque les sommets du terrain résident dans un <code>StorageBufferAttribute</code> GPU, le raycaster CPU ne peut pas les voir. Parcourir la fonction analytique de hauteur avec un grand pas loin de la surface, un petit pas à proximité, puis affiner par recherche binaire lors de l’inversion du signe de $(\text{ray}_y - \text{terrain}_y)$ offre une précision inférieure au millimètre pour un nombre borné d’échantillons. La même primitive alimente le curseur de la brosse et l’aperçu fantôme des accessoires.</p>
<p><strong>Colliders capsules primitifs avec table de hachage spatiale.</strong> Chaque accessoire est réduit, selon sa catégorie, à une capsule ou une sphère dérivée de sa boîte englobante, enregistrée sous la forme <code>{kind, walkable, radius, p1, p2}</code> dans chaque cellule de hachage de 8 m qu’elle recouvre. À chaque frame, le joueur teste uniquement les accessoires présents dans son voisinage de cellules 3×3, avec une résolution capsule contre capsule pour chacun. Les approximations sur lesquelles on peut marcher — rochers et troncs — appliquent une poussée uniquement verticale ; les approximations bloquantes — arbres — appliquent une poussée complète en 3D. Consultez <a href="/fr/guides/landscape-generation-browser#physics-on-sdf-terrain">les collisions avec un terrain SDF</a> pour découvrir les calculs de capsules sur lesquels repose cette méthode.</p>
<hr>
<p>Partie 16 sur 29.
Précédente : <a href="/fr/blog/2026-04-13-open-world-browser-part-15-multiplayer-and-water">Partie 15 — Remplacer la base de référence, puis la synchroniser</a>
Suivante : <a href="/fr/blog/2026-05-01-open-world-browser-part-17-animations-and-search">Partie 17 — Des animations sans retargeting et une recherche d’assets en direct</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Quatre paris que j’ai faits sur le jeu vidéo en 2022. Ce qu’ils sont devenus.]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-04-24-2022-predictions-aging-well</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-04-24-2022-predictions-aging-well</guid>
            <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Lors d’une interview en 2022, j’ai misé sur les moteurs de jeu WebGL, GPT-3 pour les dialogues, la collaboration créative en temps réel et l’essor des indépendants. Quatre ans plus tard, voici où en est chacun de ces paris.]]></description>
            <content:encoded><![CDATA[<h1>Quatre paris que j’ai faits sur le jeu vidéo en 2022. Ce qu’ils sont devenus.</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, CEO de Cinevva</em></p>
<img src="/img/blog/tech-done-different.jpg" alt="Interview pour le podcast Tech Done Different, mai 2022" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>En mai 2022, j’ai participé à une interview pour le podcast <em>Tech Done Different</em> de Ted Harrington. L’article d’origine est <a href="https://medium.com/@vio-202020/interview-with-tech-done-different-3b9bf74d99c2">toujours disponible sur Medium</a>. Je dirigeais Cinevva depuis environ deux ans. Nous étions cinq, avions levé 200 000 dollars, affichions au mur un prix Mozilla Builders et avions beaucoup d’idées sur l’avenir du développement de jeux.</p>
<p>La plupart de ces idées n’étaient pas à la mode en 2022. Le Web3 faisait beaucoup de bruit, les studios AAA dominaient les débats, GPT-3 était un jouet de recherche et WebGL servait aux amateurs pour créer des jeux lors de game jams. Affirmer à voix haute que « tout créatif deviendra développeur de jeux » suscitait des hochements de tête polis, mais aucun chèque.</p>
<p>La semaine dernière, j’ai relu cette interview. Certaines de mes déclarations ressemblent à des vœux pieux. D’autres donnent l’impression que je disposais d’une machine à remonter le temps. Voici les quatre paris qui ont le mieux vieilli, accompagnés de ce à quoi ressemble réellement chacun d’eux en 2026.</p>
<h2>Pari n° 1 : les moteurs de jeu ont leur place dans le navigateur</h2>
<p>Ce que j’ai dit en 2022 :</p>
<blockquote>
<p>La technologie WebGL peut être utilisée sur plusieurs plateformes, y compris les appareils mobiles, contrairement à d’autres API qui peuvent être limitées aux PC. Que vous soyez confortablement installé chez vous ou en déplacement, vous pouvez donc toujours créer et publier. Les jeux WebGL n’utilisent qu’une fraction de la puissance CPU et GPU requise par un jeu PC traditionnel.</p>
</blockquote>
<p>En 2022, c’était un pari à contre-courant. Tous les grands moteurs reposaient sur des téléchargements, des installations et des cycles de mise à jour. Les jeux sur navigateur évoquaient la nostalgie de Flash et Friv.</p>
<p>En 2026, notre <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">moteur de monde ouvert tourne à 120 FPS dans un onglet de navigateur</a>. Terrain, arbres, physique, synchronisation multijoueur : tout tient dans une seule URL. Aucun téléchargement, aucune boutique d’applications, aucune installation.</p>
<img src="/img/blog/open-world-browser-120fps.png" alt="Le monde ouvert de Cinevva tournant à 120 FPS dans un onglet de navigateur, avec terrain, arbres, physique et capsule de joueur rendus dans un seul onglet" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Le navigateur a rattrapé son retard grâce à l’arrivée simultanée de trois avancées. WebGPU a été déployé dans tous les grands navigateurs. SharedArrayBuffer et l’isolation entre origines sont devenus fiables. WebAssembly est devenu suffisamment rapide pour exécuter de véritables moteurs physiques compilés depuis Rust. Notre équipe a <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">publié un journal de terrain en 12 parties</a> détaillant le coût réel de chacune de ces étapes. En résumé, le navigateur est désormais un véritable environnement d’exécution pour les jeux, et non plus l’endroit où l’on place la démo du vrai jeu.</p>
<p>Ce que je n’avais pas anticipé dans ce pari : j’ai sous-estimé la part du travail relevant de la culture d’ingénierie. Les moteurs de jeu sur navigateur ne sont plus freinés par les navigateurs. Ils le sont par l’idée qu’un vrai jeu doit disposer d’un lanceur.</p>
<h2>Pari n° 2 : l’IA fait partie du pipeline, ce n’est pas une fonctionnalité annexe</h2>
<p>Ce que j’ai dit en 2022 :</p>
<blockquote>
<p>Pour que notre plateforme fonctionne parfaitement, nous utilisons des outils de création de jeux open source, mais nous développons aussi notre propre technologie à l’aide du traitement automatique du langage naturel, de la vision par ordinateur et de solutions OpenAI, comme GPT3 pour les dialogues et DALL:E pour la génération d’images à partir de texte.</p>
</blockquote>
<p>En mai 2022, GPT-3 était une API payante que la plupart des gens n’avaient jamais utilisée. DALL·E avait à peine un an et n’était accessible que sur liste d’attente. Affirmer que nous allions les utiliser comme outils de production pour le développement de jeux relevait, au mieux, du jeu de rôle ambitieux.</p>
<p>En 2026, tout pipeline créatif sérieux intègre des modèles de langage et de la génération d’images, y compris le nôtre. Aujourd’hui, Cinevva intègre ElevenLabs Music pour les bandes-son, ElevenLabs SFX pour les effets sonores, Hunyuan3D et SAM3D pour les assets, Flux pour les visuels, Mubert pour l’audio adaptatif et AceStep pour l’instrumentation. On saisit des prompts, on obtient des assets exploitables, et tout se passe dans l’éditeur.</p>
<img src="/img/blog/prompting-3d-games.png" alt="Génération d’assets 3D à partir de prompts dans l’éditeur Cinevva" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Les preuves se trouvent dans le rapport <em>State of the Game Industry</em> de la GDC 2026. <a href="/fr/signals/2026-03-28-the-52-52-split">52 % des entreprises du jeu vidéo déclarent utiliser l’IA générative en production</a>. Et 52 % des développeurs affirment que l’utilisation de l’IA a un impact négatif sur l’industrie. Ces deux chiffres sont vrais. Ce qu’il faut retenir, c’est que la technologie sous-jacente est devenue incontournable plus rapidement que quiconque ne voulait l’admettre.</p>
<p>Ce sur quoi je me suis trompée : je pensais que la résistance viendrait des ingénieurs préoccupés par la qualité. Elle est surtout venue des artistes préoccupés par leur emploi. Cette conversation est <a href="/fr/signals/2026-03-16-when-ai-overrides-the-artist">bien plus complexe que je ne l’avais prévu</a>, et ceux qui la présentent comme une victoire sans ambiguïté ne prêtent pas attention à la réalité.</p>
<h2>Pari n° 3 : le développement de jeux a besoin d’une collaboration à la Google Docs</h2>
<p>Ce que j’ai dit en 2022 :</p>
<blockquote>
<p>La gestion du code source et des versions d’une application est souvent liée à la collaboration à distance. Aujourd’hui, pour y parvenir, il faut savoir où aller, quelle version de l’application télécharger, rester synchronisé avec tout le monde et, à Dieu ne plaise, si un membre de l’équipe utilise la mauvaise version de l’application, cela devient un problème. Notre objectif est d’offrir une expérience en quasi-temps réel semblable à Google Docs : simple, fluide, mais amusante.</p>
</blockquote>
<p>La collaboration en temps réel dans les outils de programmation était une idée marginale en 2022. Replit la proposait. VS Code Live Share existait. La plupart des moteurs professionnels nécessitaient Perforce, un VPN et un ingénieur senior chargé de surveiller votre branche.</p>
<p>En 2026, la collaboration en temps réel est devenue la norme pour tout outil créatif qui veut être pris au sérieux. Figma l’a normalisée pour le design. Cursor et Bolt l’ont normalisée pour le code. Lovable l’a normalisée pour le prototypage d’applications. Les moteurs de jeu étaient à la traîne, pas à l’avant-garde.</p>
<img src="/img/blog/open-world-chunk-streaming.png" alt="Streaming en temps réel des zones d’un jeu en monde ouvert entre plusieurs joueurs" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Dans notre propre moteur, la synchronisation multijoueur fonctionne désormais comme une <a href="/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy">expérimentation de premier plan</a>, avec streaming des zones, présence et état partagé dans le navigateur. L’expérience se rapproche davantage de « ouvre le lien et rejoins-nous » que de « configure Perforce, demande la bonne version sur Slack et installe le lanceur ».</p>
<p>L’élément que j’ai sous-estimé : le plus difficile dans la collaboration en temps réel pour les jeux n’est pas le réseau. C’est la résolution des conflits d’intention créative. Lorsque deux personnes modifient le même niveau en même temps, c’est un problème d’expérience utilisateur avant d’être un problème de synchronisation, et la plupart des outils ne l’ont pas encore résolu.</p>
<h2>Pari n° 4 : l’avenir est indépendant</h2>
<p>Ce que j’ai dit en 2022 :</p>
<blockquote>
<p>Les créatifs qui n’étaient pas en mesure de produire du contenu vidéoludique pourront désormais le faire. Nous découvrirons une multitude de nouvelles façons de percevoir et d’exprimer la créativité. Cette fois, le processus ne sera plus contrôlé uniquement par les entreprises ou les grands studios AAA. L’avenir est indépendant.</p>
</blockquote>
<p>C’était la phrase la plus romantique de l’interview, et la plus facile à balayer d’un revers de main en 2022. Les budgets AAA n’avaient jamais été aussi élevés. Les projets indépendants n’avaient jamais eu autant de mal à gagner en visibilité. Les chiffres semblaient aller dans la direction opposée.</p>
<p>En 2026, les chiffres se sont inversés. Nous avons créé <a href="/fr/blog/2026-02-18-a-breaker-belt">A Breaker Belt</a> en trois jours, à deux personnes, pour le web, le mobile et le PC à partir d’une seule base de code. Il aurait autrefois fallu une petite équipe et trois mois. <a href="/fr/signals/2026-03-23-solo-devs-shipping-not-vibing">Les développeurs solo publient réellement leurs jeux, ils ne se contentent pas de « vibe coder »</a>, et l’édition 2026 du Steam Next Fest a enregistré un nombre record de participations d’équipes composées d’une seule personne.</p>
<img src="/img/social/industry-solo-dev-shipping.png" alt="Des développeurs solo publiant des jeux de qualité professionnelle à l’aide d’outils d’IA et de moteurs sur navigateur" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Le coût nécessaire pour déterminer si une idée étrange fonctionne s’est effondré. Voilà le véritable changement. Tout le reste — l’IA dans le pipeline, le navigateur comme environnement d’exécution, la collaboration en temps réel — est à son service. Avec les bons outils, un développeur solo dispose aujourd’hui de la capacité de production d’un studio de 20 personnes en 2018.</p>
<p>Ce que je n’avais pas vu assez clairement venir en 2022 : le goulot d’étranglement se déplace de la production vers la découverte. Lorsque tout le monde peut publier, se faire connaître devient le problème le plus difficile. C’est sur cela que portent nos prochains paris, et nous sommes en train de les faire.</p>
<h2>Ce sur quoi je me suis délibérément trompée</h2>
<p>Quelques-uns des paris de 2022 sont encore en suspens ou doivent être révisés.</p>
<p>Le Web3 occupait plus de place qu’il n’aurait dû dans cette interview. Le chiffrement et les preuves à divulgation nulle de connaissance restent pertinents comme fondements des droits sur les contenus. En revanche, le cadrage autour des jetons et des places de marché a mal vieilli, et une grande partie de l’énergie que j’y ai consacrée en 2022 aurait été mieux investie dans les outils d’IA, là où se trouvait le véritable potentiel.</p>
<p>J’ai également sous-estimé à quel point l’idée que « tout le monde peut créer un jeu » relève d’un problème de découverte et de distribution, et non d’un problème d’outillage. Créer est désormais facile. Se faire connaître et être payé restent difficiles, et c’est là que se concentre aujourd’hui l’essentiel de notre travail.</p>
<p>C’est le pari que nous faisons actuellement. Nous venons de lancer un fil de reels à la TikTok sur <a href="/fr/play">cinevva.com/play</a>. Des extraits verticaux de véritables séquences de gameplay, à faire défiler, quinze secondes chacun, sans bandes-annonces ni vidéos marketing. Si quelque chose attire votre attention, il suffit d’un clic pour y jouer dans votre navigateur. Aucun téléchargement, aucune installation, aucun tutoriel entre vous et le jeu.</p>
<p>Pour les développeurs indépendants, l’importance de cette approche tient à une réalité mathématique brutale. L’édition 2026 du Steam Next Fest a enregistré un nombre record de participations, ce qui semble formidable jusqu’à ce que l’on calcule le budget d’attention disponible par jeu. La plupart des démos ont reçu moins de mille impressions. Le tunnel des pages de boutique était déjà défaillant avant que l’IA n’aggrave le problème. Une interface de défilement dans laquelle le gameplay fait lui-même office de publicité réduit toute la boucle de découverte à un seul geste. Elle présente les développeurs solo aux joueurs de la même manière que TikTok présente des musiciens inconnus à son public. C’est la pièce manquante entre « tout le monde peut publier » et « tout le monde peut réussir ».</p>
<p>Si vous souhaitez lire l’interview originale dans son intégralité, <a href="https://medium.com/@vio-202020/interview-with-tech-done-different-3b9bf74d99c2">elle est disponible ici</a>. La relire s’est révélée utile. La plupart de ce que nous avons affirmé à voix haute en 2022 s’est avéré constituer une feuille de route viable, simplement exécutée plus rapidement que nous ne l’avions imaginé.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 13 : sculpture du terrain et mort de la fonction mathématique]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-04-13-open-world-browser-part-13-terrain-sculpting</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-04-13-open-world-browser-part-13-terrain-sculpting</guid>
            <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Trois faux départs, un maillage qui explose et un bug de raccord qui nous a obligés à repenser le fonctionnement des données de terrain.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 13 : sculpture du terrain et mort de la fonction mathématique</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et renvoie vers toutes les parties.</p>
<p>Pendant douze parties, nous avons construit un moteur de terrain que l’on pouvait regarder. Survoler. Admirer sans voir les raccords se fissurer. Cette fois, nous voulions pouvoir le toucher.</p>
<p>L’objectif semblait simple : permettre à un joueur de sculpter le terrain avec une brosse, en temps réel, sans casser aucun des systèmes que nous avions mis 24 spikes à construire. Il nous a fallu trois tentatives, deux bugs qui ressemblaient à des erreurs de rendu mais qui provenaient en réalité du modèle de données, et une remise en question fondamentale du fonctionnement des données de terrain.</p>
<h2>Trois faux départs</h2>
<p>Le spike 25 était censé être facile. La base de code de production disposait déjà d’un raycaster capable de détecter les intersections avec les maillages du terrain. L’outil de placement l’utilise pour déposer des objets. Un outil de sculpture à la brosse suit le même principe, sauf qu’il modifie les valeurs de la heightmap au lieu d’instancier un prefab. Simple.</p>
<p>Première tentative : je l’ai intégré directement au code TypeScript de production dans <code>world/client/</code>. Nouveau fichier <code>terrain-brush.ts</code>, modifications de <code>chunk.ts</code>, changements de protocole, mises à jour de composants Vue. En moins d’une heure, j’avais une brosse qui fonctionnait plus ou moins, mais les frontières entre chunks présentaient des discontinuités visibles dans les normales. Impossible de savoir si le bug venait de mon code de brosse, du raccordement existant entre les chunks ou d’une interaction avec la boucle de rendu complète. C’est exactement le type de situation que la méthodologie des spikes vise à éviter. J’avais ignoré la règle et je l’ai immédiatement payé. J’ai tout annulé.</p>
<p>Deuxième tentative : un spike autonome, mais j’ai choisi Three.js 0.170.0 et WebGL. Le code de production utilise WebGL, donc ce choix semblait naturel. Mais les spikes 13 à 24 étaient tous passés à WebGPU. Construire un spike de brosse avec WebGL aurait prouvé que cela fonctionnait avec l’ancien moteur de rendu, et non avec celui vers lequel nous migrons. Mauvaise direction. J’ai recommencé.</p>
<p>Troisième tentative : WebGPU, <code>WebGPURenderer</code>, shader de calcul pour générer les sommets, avec la même stack que le spike 22. Cette fois, l’architecture était la bonne. Cinq opérations de brosse fonctionnaient : élever, abaisser, lisser, aplanir, ajouter du bruit. P95 du cycle de brosse inférieur à 4 ms sur M1.</p>
<p>Et le bug de raccord était toujours là.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/25-heightmap-brush/" title="Spike 25 : brosse de heightmap" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/25-heightmap-brush/" target="_blank">Ouvrir le spike 25 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=25-heightmap-brush">Voir le code source</a></p>
<h2>Le bug de raccord qui refusait de mourir</h2>
<p>La solution standard pour calculer les normales entre plusieurs chunks consiste à faire chevaucher leurs bordures : chaque chunk stocke un anneau de données supplémentaire provenant de ses voisins, afin que le calcul des normales à la frontière puisse échantillonner les deux côtés. Je l’ai mise en œuvre. J’ai copié les données des bords voisins dans un buffer étendu. Les raccords se fissuraient toujours.</p>
<p>J’ai examiné les calculs. À la frontière entre le chunk A (cx=-1) et le chunk B (cx=0), les deux chunks doivent calculer la même normale au sommet partagé. Le shader du chunk A échantillonnait <code>mix(own_col31, own_col32, 0.85)</code>. Celui du chunk B échantillonnait <code>mix(neighbor_edge, own_col0, 0.85)</code>. Il s’agit de deux chemins d’interpolation bilinéaire différents à travers des données différentes. Même avec des données de bordure correctes, les deux chunks calculent des normales différentes pour le même point.</p>
<p>C’est à ce moment-là que j’ai compris que la copie des bordures n’était pas le véritable bug. Le véritable bug était le modèle de données.</p>
<p>Tous les spikes 1 à 24 utilisaient une fonction mathématique procédurale appelée <code>height_at()</code>. On lui fournit des coordonnées dans le monde, elle renvoie une hauteur. Propre, globale, sans état. La brosse ne pouvait pas modifier une fonction mathématique, j’avais donc ajouté par-dessus un buffer <code>displacement</code>. Le terrain était désormais <code>height_at(x,z) + displacement[i]</code>. Le shader GPU embarquait 30 lignes de fonctions de bruit pour le terrain de base, auxquelles s’ajoutait le code d’interpolation bilinéaire pour la couche de déplacement. Pour déterminer quelle valeur de déplacement produirait la hauteur cible, la brosse d’aplanissement devait soustraire <code>height_at()</code>. Deux systèmes superposés, calculant des choses différentes avec des stratégies d’échantillonnage différentes.</p>
<p>Ce n’est pas ainsi que fonctionne un vrai jeu. En production, un terrain créé par un artiste repose sur des données échantillonnées stockées dans des buffers. La fonction procédurale était une approximation pratique héritée des premiers spikes. Elle avait rempli son rôle. Désormais, elle provoquait activement des bugs.</p>
<p>Je l’ai supprimée.</p>
<p>Chaque chunk possède désormais une <code>heightmap</code> sous la forme d’un Float32Array contenant les véritables valeurs de hauteur. À sa création, elle est remplie par du bruit procédural. Ensuite, la fonction de bruit n’est plus jamais appelée. La brosse modifie directement les hauteurs stockées. Le shader GPU lit un seul buffer à l’aide d’une seule fonction : <code>hm_at(i,j)</code>. Les normales utilisent des différences centrales alignées sur la grille à partir des mêmes données. Plus d’ambiguïté liée à l’interpolation bilinéaire. Plus d’incohérence entre deux systèmes. Le shader est passé de 90 à 40 lignes.</p>
<p>Les raccords se sont corrigés d’eux-mêmes. Au niveau d’un bord partagé, les deux chunks lisent désormais les mêmes valeurs de hauteur discrètes dans leurs buffers respectifs, avec les bons points intérieurs du voisin dans la bordure de chevauchement. Mêmes données en entrée, mêmes normales en sortie.</p>
<p>Ce n’était pas une leçon sur les brosses. C’était une leçon d’architecture des données révélée par la brosse.</p>
<h2>Le maillage qui explose</h2>
<p>Le spike 26 était l’équivalent volumétrique. Modifier à la brosse un volume SDF de 64 au cube, puis recréer le maillage avec marching cubes. La même question que pour le spike 25, mais en 3D.</p>
<p>La première fois que je l’ai exécuté, le maillage a explosé. De longues pointes partaient dans toutes les directions, comme un oursin qui passe une très mauvaise journée.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/26-sdf-brush/" title="Spike 26 : brosse SDF" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/26-sdf-brush/" target="_blank">Ouvrir le spike 26 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=26-sdf-brush">Voir le code source</a></p>
<p>La table de cas MC que j’avais générée contenait 3840 entrées au lieu de 4096. La table complète fait $256\ \text{cas} \times 16\ \text{emplacements} = 4096$, mais la mienne faisait $256 \times 15 = 3840$, avec seize lignes manquantes à partir du cas 112. Toutes les recherches effectuées après cet index étaient décalées : le numéro de cas ne correspondait donc plus aux données de triangulation. Lorsque marching cubes lit la mauvaise entrée, il crée des arêtes dont les deux extrémités se trouvent du même côté de la surface. Sur une arête qui traverse réellement la surface, le paramètre d’interpolation</p>
<p>$$
t = \frac{-v_a}{v_b - v_a}
$$</p>
<p>se situe dans $[0, 1]$, car $v_a$ et $v_b$ sont de signes opposés. Sur une fausse arête, ils ont le même signe : $v_b - v_a$ est donc proche de zéro ou change de signe, et $t$ sort brutalement de $[0, 1]$, ce qui place le sommet très loin du volume. Multipliez cela par quelques centaines de cellules incorrectes et vous obtenez un hérisson.</p>
<p>La correction était d’une simplicité déconcertante : copier, octet par octet, la table validée du spike 12. Leçon retenue. Ne jamais régénérer une table de correspondance lorsqu’il en existe déjà une version éprouvée.</p>
<p>Le deuxième bug était plus subtil. La brosse de lissage était censée adoucir les formes du terrain. Au lieu de cela, elle créait des plis très marqués. Le problème : je ramenais chaque valeur SDF vers zéro, c’est-à-dire vers l’isosurface. Intuitivement, cela semble devoir lisser les choses, mais cette opération écrase le champ de distance. Les voxels situés au-dessus et au-dessous de la surface convergent tous vers zéro, aplatissant tout ce qui se trouve dans le rayon de la brosse. À la frontière, les voxels lissés rencontrent les voxels non lissés avec une rupture nette. La brosse de « lissage » était en réalité une génératrice de plis.</p>
<p>La solution était un véritable lissage laplacien. Au lieu de ramener chaque valeur vers zéro, il faut la rapprocher de la moyenne de ses 6 voisins directs :</p>
<p>$$
\phi_i \leftarrow \phi_i + \lambda\left(\frac{1}{6}\sum_{j \in N(i)} \phi_j - \phi_i\right)
$$</p>
<p>Le terme entre parenthèses est un laplacien discret, et $\lambda \in (0, 1]$ représente l’intensité du lissage. Cela moyenne la géométrie locale et adoucit la forme de la surface tout en préservant le gradient du champ de distance au lieu de l’écraser.</p>
<h2>Tout à la fois</h2>
<p>Le spike 27 constituait la validation de l’intégration. Il fallait prendre le pipeline complet du spike 24 — patches de heightmap, chunks MC, raccords Transvoxel et LOD avec géomorphing — et le combiner avec le modèle de données échantillonnées du spike 25 et les deux types de brosses.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/27-hm-mc-brush/" title="Spike 27 : intégration HM + MC + brosses" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/27-hm-mc-brush/" target="_blank">Ouvrir le spike 27 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=27-hm-mc-brush">Voir le code source</a></p>
<p>La première chose que j’ai faite a été d’arracher <code>height_at()</code> de tous les shaders. Les trois shaders de calcul — remplissage SDF, patch de heightmap et raccord Transvoxel — se lient désormais au même buffer GPU de heightmap 129x129 et utilisent la même fonction d’interpolation bilinéaire <code>hm_sample()</code> via un préambule WGSL partagé. Une seule source de données, plusieurs consommateurs. Les fonctions de bruit procédural présentes dans chaque shader depuis le spike 1 avaient disparu.</p>
<p>C’est alors que les problèmes intéressants ont commencé.</p>
<p>Lorsqu’une brosse SDF verrouille un chunk en mode MC, le raccord Transvoxel entre ce chunk et son voisin sous forme de heightmap doit échantillonner le volume SDF, et non la heightmap. J’ai étendu le shader de raccord avec des liaisons de buffers de stockage supplémentaires et des indicateurs MC propres à chaque chunk. Quatre combinaisons de frontières à gérer : HM-HM, HM-MC, MC-HM, MC-MC.</p>
<p>Le LOD constituait une autre énigme. Dans les spikes précédents, passer un chunk MC à un LOD inférieur impliquait de remplir à nouveau le SDF avec une résolution plus grossière. J’ai remplacé cette méthode par un échantillonnage fondé sur un pas : les données SDF restent en pleine résolution, soit 65 points de grille. Le shader MC calcule un pas à partir du rapport entre la taille de la grille et le nombre de cellules. Au LOD0, le pas vaut 1. Au LOD1, il vaut 2, ce qui revient à échantillonner un voxel sur deux. Les chunks peuvent changer librement de LOD sans modifier leurs données SDF.</p>
<p>La correction la plus satisfaisante concernait la création dynamique de chunks verticaux. Sculptez vers le haut au-delà du sommet d’un chunk et un nouveau chunk exclusivement MC apparaît au-dessus, avec un SDF initialisé à partir de la face limite du chunk inférieur. Sculptez vers le bas et la même chose se produit. Le monde s’agrandit pour accueillir les modifications.</p>
<p>Le dernier piège était la brosse de heightmap, qui ne faisait silencieusement rien sur les chunks verrouillés en mode MC. La brosse HM modifie <code>heightmapCPU</code>, puis la téléverse à nouveau. Les chunks MC ne lisent plus la heightmap, car leur SDF a été rempli à partir de celle-ci avant de diverger. J’ai ajouté <code>syncHeightmapToSdf()</code> : après toute modification de la heightmap, les colonnes SDF des chunks MC situés dans le rayon de la brosse sont recalculées et les nouvelles valeurs sont téléversées. Les deux types de brosses fonctionnent désormais sur les deux types de chunks.</p>
<h2>Ce que nous avons réellement appris</h2>
<p>Les spikes consacrés aux brosses étaient censés répondre à une question de performances : la sculpture peut-elle respecter le budget par frame ? Oui. C’était la partie facile.</p>
<p>La partie difficile a été de découvrir qu’après 24 spikes utilisant <code>height_at()</code> comme source de vérité du terrain, nous avions créé une dépendance invisible qui s’est brisée dès que nous avons tenté de modifier quoi que ce soit. La fonction procédurale était propre, globale et sans état — jusqu’au moment où elle a cessé d’être le terrain.</p>
<p>Voici les règles que nous avons notées et que nous n’oublierons pas :</p>
<ol>
<li>La hauteur du terrain provient de données échantillonnées. Les chunks possèdent leurs propres buffers.</li>
<li>La génération procédurale remplit les données initiales. Elle n’est pas la source de vérité à l’exécution.</li>
<li>La brosse modifie directement les données des chunks. Aucune couche de déplacement.</li>
<li>Les normales sont calculées à partir des mêmes données au moyen de différences centrales alignées sur la grille.</li>
<li>Le chevauchement des bordures — 1 cellule provenant de l’intérieur du voisin — gère les normales entre chunks.</li>
<li>Ne jamais régénérer une table de correspondance lorsqu’il en existe déjà une version éprouvée.</li>
</ol>
<p>Dans la <a href="/fr/blog/2026-04-13-open-world-browser-part-14-world-comes-alive">partie 14</a>, nous cessons de sculpter de la géométrie de débogage pour commencer à lui donner l’apparence et l’atmosphère d’un véritable lieu.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Architecture de heightmap échantillonnée.</strong> Le terrain est stocké sous forme de données appartenant à chaque chunk, au lieu d’être évalué à partir d’une fonction procédurale à l’exécution. Chaque chunk contient un <code>Float32Array</code> de valeurs de hauteur réelles. Le bruit procédural remplit les données initiales au moment de la création, puis la fonction n’est plus jamais appelée. Cette approche élimine l’incohérence entre le terrain fondé sur une fonction mathématique et les couches de modification, simplifie les opérations de brosse — qui modifient directement les valeurs stockées — et rend le shader GPU extrêmement simple : lecture du buffer et calcul des normales au moyen de différences centrales alignées sur la grille. Pour un monde ouvert avec streaming, l’approche standard repose sur des données propres à chaque chunk avec une bordure de chevauchement d’une cellule provenant des voisins. Consultez notre <a href="/fr/guides/landscape-generation-browser#terrain-data-representation">guide de génération de paysages</a>.</p>
<p><strong>Opérations de brosse SDF.</strong> Modification d’un champ de distance signé pour sculpter le terrain. L’ajout — gonflement — utilise une atténuation smooth-step autour d’une sphère. La soustraction — creusement — utilise la même forme avec le signe opposé. Le lissage utilise une moyenne laplacienne : lire les 6 voisins directs, calculer leur moyenne et rapprocher la valeur de cette moyenne. L’approche naïve consistant à ramener les valeurs vers zéro écrase le champ de distance et crée des arêtes vives. Le lissage laplacien préserve le gradient du champ tout en adoucissant les formes. Consultez la section <a href="/fr/guides/landscape-generation-browser#signed-distance-fields-sdfs">Représentation du terrain par SDF</a>.</p>
<p><strong>Transvoxel avec des sources de données mixtes.</strong> Les cellules de transition situées à la frontière entre un chunk MC et un chunk de heightmap doivent échantillonner des données différentes de chaque côté. Le shader de raccord utilise des indicateurs propres à chaque chunk et des liaisons de buffers pour gérer les quatre combinaisons : HM-HM, HM-MC, MC-HM et MC-MC. Lorsqu’un côté est verrouillé en mode MC, le shader interpole trilinéairement le buffer SDF au lieu d’échantillonner la heightmap.
<strong>LOD basé sur le pas d’échantillonnage pour les marching cubes.</strong> Les données SDF sont stockées à pleine résolution, quel que soit le niveau de LOD actuel du chunk. Le shader MC calcule un pas d’échantillonnage à partir du rapport entre les points de la grille SDF et les cellules MC. À pleine résolution, le pas est de 1 ; à mi-résolution, il est de 2. Cela dissocie les données SDF des changements de LOD, ce qui permet aux chunks de changer librement de LOD sans reconstruire le SDF.</p>
<hr>
<p>Partie 13 sur 14.
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-12-lessons">Partie 12 - Anneaux, brume céleste et ce que nous referions</a>
Suivant : <a href="/fr/blog/2026-04-13-open-world-browser-part-14-world-comes-alive">Partie 14 - Le monde prend vie</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 14 : le monde prend vie]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-04-13-open-world-browser-part-14-world-comes-alive</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-04-13-open-world-browser-part-14-world-comes-alive</guid>
            <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Des matériaux qui réagissent à vos sculptures, de l'herbe qui pousse au plafond des grottes et un planeur qui vous fait oublier que vous êtes dans un onglet de navigateur.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 14 : le monde prend vie</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>Après la <a href="/fr/blog/2026-04-13-open-world-browser-part-13-terrain-sculpting">partie 13</a>, nous pouvions sculpter le terrain. Le surélever, creuser des grottes, lisser des falaises, le tout en temps réel et sans rompre les raccords. Mais le monde ressemblait encore à une démo technique. Ombrage plat aux couleurs de débogage, superpositions en fil de fer, géométrie grise avec des couleurs de LOD pour distinguer les chunks. On pouvait le modifier. On ne pouvait pas encore le ressentir.</p>
<p>Trois spikes ont changé cela. Pas l'architecture. Exactement le même pipeline, les mêmes buffers et les mêmes raccords de jointures que dans la partie 13. Nous avons simplement ajouté les couches qui donnent au terrain l'impression d'être un véritable lieu : des surfaces qui réagissent à leur forme, de la vie qui pousse sur ces surfaces et un corps qui les parcourt.</p>
<p>La différence entre « techniquement fonctionnel » et « j'ai envie de rester ici » s'est révélée étonnamment ténue.</p>
<h2>Sculptez une falaise et regardez-la se changer en roche</h2>
<p>Le spike 28 posait une question précise : un matériau à quatre couches avec projection triplanaire peut-il fonctionner sur un terrain généré par calcul sans faire exploser le budget d'une frame ? La réponse était oui, mais le plus intéressant est ce qui s'est produit ensuite.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/28-multi-material/" title="Spike 28 : texturage multimatériau" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/28-multi-material/" target="_blank">Ouvrir le spike 28 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=28-multi-material">Voir le code source</a></p>
<p>Quatre textures procédurales (herbe, roche, sable et neige) générées à partir d'un bruit FBM au démarrage. Aucun fichier externe, aucun pipeline d'assets, seulement des mathématiques et une <code>DataTexture</code>. Les poids des matériaux proviennent de la surface elle-même : sa pente et son altitude. Les zones planes situées sous la limite des arbres reçoivent de l'herbe. Les faces abruptes reçoivent de la roche. Les terrains bas reçoivent du sable. Les sommets élevés reçoivent de la neige. Chaque poids brut provient d'un <code>smoothstep</code> appliqué à la pente et à l'altitude, puis les poids sont normalisés pour chaque fragment afin que $\sum_i w_i = 1$ et que la couleur finale soit simplement le mélange pondéré $\sum_i w_i, c_i$. Cette normalisation empêche la surface de devenir trop sombre ou surexposée là où les biomes se chevauchent. La projection triplanaire se charge de la projection UV pour les chunks MC, dont les triangles ne possèdent pas de coordonnées UV pertinentes.</p>
<p>Le moment décisif : surélevez le terrain avec la brosse pour créer une falaise abrupte, et une texture rocheuse apparaît sur la nouvelle face au cours de la même frame. Aplanissez-la de nouveau, et l'herbe reconquiert la surface. Le matériau ne sait rien de la brosse. Il lit simplement la position dans le monde et la normale de la surface, les mêmes données qui ont servi à construire la géométrie. La boucle de rétroaction entre sculpture et rendu visuel est instantanée et non scriptée.</p>
<p>Il y a un mois et demi, le spike 8 avait testé le coût des matériaux de terrain avec un maillage statique sous WebGL. Le spike 28 prouve que cela fonctionne sur un terrain dynamique généré par calcul, avec tout le pipeline de sculpture en dessous. Nous avions prévu le budget nécessaire, mais le voir tenir les 60 fps avec tout le reste en fonctionnement a tout de même été un soulagement.</p>
<h2>80 000 touffes d'herbe et le plafond d'une grotte</h2>
<p>Le spike 29 est celui où nos instincts de développeurs de jeux ont pris le dessus.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/29-vegetation/" title="Spike 29 : végétation et herbe par shader" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/29-vegetation/" target="_blank">Ouvrir le spike 29 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=29-vegetation">Voir le code source</a></p>
<p>Nous voulions l'herbe de Breath of the Wild. Pas son nombre de polygones, mais ses sensations. Une herbe qui couvre les bons endroits, bouge avec le vent et donne envie de courir dedans.</p>
<p>Chaque touffe d'herbe se compose de trois quadrilatères entrecroisés à des angles de 60 degrés, avec quatre segments verticaux pour permettre la flexion. Cette forme en croix paraît volumétrique sous tous les angles, sans astuces de billboard. Un <code>InstancedMesh</code> par chunk, 80 000 touffes réparties dans le monde, soit environ 2,4 millions de sommets d'herbe au total.</p>
<p>Le placement est la partie essentielle. Le CPU parcourt une grille légèrement irrégulière sur chaque chunk et évalue la même logique de pente et d'altitude que celle utilisée par le shader de matériau sur le GPU. Là où le système de matériaux indique « herbe », l'herbe pousse. Là où la roche ou le sable domine, la densité tombe à zéro. La transition est progressive, car les poids <code>smoothstep</code> sous-jacents produisent des gradients continus aux frontières des biomes. On ne remarque aucune limite, puisqu'il n'y en a pas.</p>
<p>Nous avons ensuite tenté quelque chose dont je n'étais pas certain. De l'herbe sur des surfaces SDF. La fonction de dispersion parcourt chaque colonne du volume SDF et recherche les passages par zéro entre des voxels adjacents. Là où la SDF passe d'une valeur négative à une valeur positive, il existe une surface. La normale de cette surface est simplement le gradient normalisé du champ, $\hat{n} = \frac{\nabla\phi}{\lVert\nabla\phi\rVert}$, calculé à partir des différences entre voxels, et la pente vaut $\arccos(\hat{n} \cdot \hat{y})$, soit l'angle par rapport à la verticale. Si cet angle est suffisamment faible, on place de l'herbe.</p>
<p>Creusez une grotte avec la brosse SDF dans le spike 27. Revenez dans le spike 29 et vous trouverez de l'herbe qui pousse au-dessus du plafond de la grotte. Le code de dispersion ne sait pas ce qu'est une grotte. Il voit simplement une surface présentant la bonne pente à la bonne altitude. C'est ce type de comportement émergent qui rend les systèmes de mondes ouverts si plaisants à construire.</p>
<p>Le vent est une onde sinusoïdale dans le shader de sommets TSL, modulée par la coordonnée V du brin afin que les pointes oscillent tandis que les racines restent ancrées. Appuyez sur V pour alterner entre vent léger, vent fort et absence de vent. Le vent agit simultanément sur les 80 000 touffes sans aucun coût CPU, car tout se passe dans le shader de sommets.</p>
<p>Le spike 7 avait testé 50 000 instances d'herbe et nous craignions d'atteindre la limite. Le spike 29 en fait tourner 80 000 par-dessus le terrain calculé, les raccords de jointures, le texturage multimatériau et les brosses. Regrouper les instances dans un nombre réduit d'appels de rendu <code>InstancedMesh</code> reste plus important que de réduire le nombre de sommets par brin. La leçon du spike 7 s'est confirmée.</p>
<p>Un point que nous avons remis à plus tard : l'herbe ne se met pas à jour lorsque vous sculptez le terrain sous elle. Les matrices d'instance sont définies au moment de la dispersion. Transformez une colline en vallée et l'herbe flottera dans les airs jusqu'à ce que vous appuyiez sur G pour relancer la dispersion. C'est suffisant pour un spike. En production, il faudra relancer la dispersion sur les chunks modifiés.</p>
<h2>Le premier pas</h2>
<p>Le spike 30 est celui auquel je reviens sans cesse.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/30-physics/" title="Spike 30 : physique du terrain" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/30-physics/" target="_blank">Ouvrir le spike 30 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=30-physics">Voir le code source</a></p>
<p>Aucune bibliothèque de physique. Une capsule personnalisée avec un pas de temps fixe à 120 Hz. Le code de production utilise Rapier dans un web worker (le spike 2 a prouvé que la latence convenait), mais ce spike devait valider les requêtes de collision elles-mêmes. Un personnage peut-il parcourir un terrain à heightmap, monter sur un terrain SDF sculpté et ne pas passer au travers ?</p>
<p>Le cœur du système est une fonction appelée <code>terrainQuery(x, y, z)</code>. Elle vérifie si la position se trouve dans un chunk verrouillé en MC. Si c'est le cas, elle interpole de façon trilinéaire la copie CPU de la SDF et renvoie le gradient comme normale de surface. Sinon, elle effectue une lecture de la heightmap avec une normale calculée par différences centrales. Le personnage ne sait pas sur quel système de terrain il se tient. Il demande simplement où se trouve le sol et obtient une réponse.</p>
<p>Les collisions SDF étaient la partie que je m'attendais à trouver difficile. Sept points de sondage autour de la capsule (bas, centre, haut et quatre décalages cardinaux). Un point de sondage à la position $\mathbf{x}$ est en pénétration chaque fois que $\phi(\mathbf{x}) &lt; r$, avec une profondeur de pénétration $p = r - \phi(\mathbf{x})$, et la direction de repoussement est le gradient du champ $\hat{n} = \frac{\nabla\phi}{\lVert\nabla\phi\rVert}$. Pour empêcher la capsule de rentrer de nouveau dans la surface, la vitesse est projetée afin d'annuler sa composante entrante :</p>
<p>$$
\mathbf{v}' = \mathbf{v} - (\mathbf{v} \cdot \hat{n}),\hat{n}
$$</p>
<p>Il ne reste ainsi que la composante qui glisse le long de la surface. Ce n'est pas un moteur physique. Ce sont des requêtes géométriques et une réponse simple. Mais cela gère les grottes, les surplombs et les tunnels sculptés sans aucun code spécifique à chaque forme. Vous entrez dans une grotte creusée dix secondes plus tôt et la capsule suit exactement le contour du plafond.</p>
<p>Le modèle de déplacement a commencé de manière pragmatique, puis il est devenu amusant. Marche, course, sprint, saut, glissade sur les pentes de plus de 45 degrés. J'ai ensuite ajouté un parapente façon BotW et le spike est devenu quelque chose que je n'avais plus envie de fermer.</p>
<p>Appuyez sur Espace lorsque vous êtes en l'air. La gravité passe de -30 à -4. La vitesse de chute est plafonnée à -3. Une aile delta se déploie depuis la capsule avec une interpolation d'échelle. Virez à gauche et l'aile s'incline. L'orientation de la capsule s'aligne automatiquement sur le vecteur de vitesse, afin que vous regardiez toujours dans la direction de votre déplacement. Relâchez Espace pour tomber. Touchez le sol et vous repartez en courant.</p>
<p>La caméra recule pour passer à la troisième personne (touche P). Elle suit le joueur par-derrière avec un lissage du lacet. Un ray marching du joueur vers la caméra teste le terrain en 20 étapes. Entrez en vol dans une grotte et le bras de caméra se raccourcit progressivement au lieu de traverser la roche. Ressortez de l'autre côté et il s'allonge de nouveau.</p>
<p>Voici le moment qui a rendu ce spike si précieux : sculptez une haute falaise avec la brosse de heightmap. Passez à la caméra à la troisième personne. Courez jusqu'au bord. Sautez. Déployez le parapente. Virez à gauche au-dessus du terrain que vous venez de sculpter, tandis que l'herbe cultivée dans le spike 29 ondule en dessous, que la texture rocheuse du spike 28 couvre la falaise et que les raccords Transvoxel tiennent à chaque limite de chunk. Atterrissez de l'autre côté. Tout fonctionne ensemble dans un seul onglet de navigateur.</p>
<h2>Sculpter sous ses propres pieds</h2>
<p>Une chose me laissait dans le doute : que se passe-t-il lorsque vous sculptez le terrain sur lequel se tient le personnage ? Les modifications de la heightmap se propagent instantanément dans <code>terrainQuery()</code>, car la fonction lit directement le buffer CPU. Les modifications SDF se propagent par l'intermédiaire de la copie CPU de la SDF. La capsule résout la pénétration au tick de physique suivant, ce qui, à 120 Hz, se produit en moins de $\frac{1}{120} \approx 8.3$ ms.</p>
<p>Cela fonctionne, tout simplement. Surélevez le sol sous le joueur et il monte avec lui. Creusez le sol et il tombe. Aucun traitement particulier. La physique s'exécute assez rapidement pour que les modifications du terrain sur une seule frame ne produisent jamais de pénétrations importantes. Ce fut un heureux hasard lié au choix d'un pas de temps à 120 Hz. Nous l'avions retenu pour la fluidité des déplacements, et il a également rendu l'édition du terrain sûre sans effort supplémentaire.</p>
<h2>30 spikes plus tard</h2>
<p>Nous avons commencé cette série avec une heightmap plane et 500 cubes. Nous disposons maintenant d'un terrain sculpté avec des grottes volumétriques, des raccords de jointures Transvoxel, un texturage multimatériau qui réagit à la forme de la surface, 80 000 touffes d'herbe ondulant dans le vent et un personnage qui marche, sprinte, saute et plane au-dessus de l'ensemble.</p>
<p>Rien de tout cela n'est encore en production. Le code de <code>world/client/</code> fonctionne toujours sous WebGL avec de simples chunks de heightmap. Tout ce qui provient de ces 30 spikes réside dans des pages HTML autonomes. L'intégration vient ensuite : migration vers <code>WebGPURenderer</code>, connexion de la stratégie hybride HM/MC au gestionnaire de chunks et raccordement des systèmes de brosses et de matériaux au multijoueur.</p>
<p>Mais le système de rendu ne constitue plus le risque principal. Les questions ouvertes concernent désormais les flux de données : persistance des modifications, synchronisation réseau des coups de brosse, sculpture collaborative. Ce qui se passe entre les joueurs, pas entre les triangles.</p>
<p>Si vous suivez cette série depuis la partie 1, merci d'être resté malgré les passages difficiles. Si vous venez de la découvrir, revenez à la <a href="/fr/blog/2026-02-25-open-world-browser-part-01-risk-first">partie 1</a>. C'est dans les fausses pistes que se cachent les leçons.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>TSL (Three Shading Language).</strong> Le système de shaders basé sur des nœuds de Three.js pour le moteur de rendu WebGPU. Les matériaux sont composés à partir de nœuds (<code>positionWorld</code>, <code>normalWorld</code>, <code>smoothstep</code>, <code>triplanarTexture</code>) au moyen de la composition de fonctions en JavaScript. Le graphe de shaders est compilé en WGSL à l'exécution. Pour les cibles WebGPU, TSL remplace le <code>ShaderMaterial</code> GLSL brut et assure l'interopérabilité entre les fonctionnalités standard des matériaux Three.js (éclairages, ombres, brouillard) et une logique personnalisée par fragment.</p>
<p><strong>Projection triplanaire.</strong> Une technique de projection de texture qui échantillonne une texture trois fois (plans XY, XZ et YZ) puis mélange les résultats en fonction de la direction de la normale de surface. Cela élimine l'étirement des UV sur une géométrie de maillage arbitraire, ce qui est essentiel pour les résultats de marching cubes dont les triangles ne possèdent pas de coordonnées UV pertinentes. TSL fournit <code>triplanarTexture()</code> comme nœud intégré.</p>
<p><strong>Végétation instanciée avec des brins en quadrilatères croisés.</strong> Chaque touffe d'herbe est constituée de trois quadrilatères entrecroisés à des angles de 60 degrés, ce qui crée une apparence volumétrique sous tous les angles. Quatre segments verticaux par quadrilatère permettent une flexion fluide pour l'animation du vent. L'ensemble du champ est rendu sous la forme d'un seul <code>InstancedMesh</code> par chunk. La capacité est surallouée de 25 % afin que de nouvelles instances puissent occuper les emplacements réservés lorsque le terrain est modifié, sans réallouer le buffer GPU. Consultez notre <a href="/fr/guides/landscape-generation-browser#vegetation-and-foliage">guide de génération de paysages consacré à la végétation</a>.
<strong>Collision capsule/SDF.</strong> Collision du personnage avec un terrain volumétrique sans moteur physique. La capsule est testée en plusieurs points par rapport au SDF. Lorsque la valeur du champ est inférieure au rayon de la capsule, le gradient fournit la normale orientée vers l’extérieur et la différence donne la profondeur de pénétration. Cette méthode gère les grottes, les surplombs et les tunnels sans code propre à chaque forme. Consultez <a href="/fr/guides/landscape-generation-browser#physics-on-sdf-terrain">les collisions avec un terrain SDF</a>.</p>
<p><strong>Contrôleur de personnage à pas de temps fixe.</strong> La simulation physique s’exécute à 120 Hz, quelle que soit la fréquence d’images, en accumulant le temps réel puis en le consommant par pas de taille fixe. Un nombre maximal de sous-pas évite la spirale de la mort lorsque les images sont lentes. L’adhérence au sol maintient la capsule en contact avec celui-ci lors du déplacement sur les pentes. Le pas de temps fixe garantit un comportement déterministe pour les futures rediffusions multijoueurs.</p>
<hr>
<p>Partie 14 sur 29.
Précédent : <a href="/fr/blog/2026-04-13-open-world-browser-part-13-terrain-sculpting">Partie 13 – La sculpture du terrain et la mort de la fonction mathématique</a>
Suivant : <a href="/fr/blog/2026-04-13-open-world-browser-part-15-multiplayer-and-water">Partie 15 – Remplacer la référence, puis la synchroniser</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer un monde ouvert dans le navigateur, partie 15 : remplacer la base, puis la synchroniser]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-04-13-open-world-browser-part-15-multiplayer-and-water</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-04-13-open-world-browser-part-15-multiplayer-and-water</guid>
            <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Les spikes 31 et 32 nous ont conduits à remplacer l'ancien monde par le meilleur spike, puis ont validé le multijoueur par rejeu des paramètres de pinceau et ajouté un océan avec la nage.]]></description>
            <content:encoded><![CDATA[<h1>Créer un monde ouvert dans le navigateur, partie 15 : remplacer la base, puis la synchroniser</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>Les quatorze premières parties couvraient les spikes 1 à 30. Cette série s'était achevée sur un système de terrain sculptable en temps réel, accompagné d'un personnage capable d'y marcher, d'y planer et d'y tomber. Cette partie reprend au spike 31, et notre première décision n'était pas technique. Il fallait décider quoi faire de tout ce code de spike.</p>
<h2>La décision « remplacer, ne pas rétroporter »</h2>
<p>Nous avions 30 fichiers HTML autonomes, chacun validant un concept isolé, et aucune intégration. Le répertoire de production <code>world/client/</code> utilisait encore l'ancienne pile : WebGL, une heightmap simple, un contrôleur de personnage de 75 lignes et un protocole MessagePack comportant neuf types de messages. Pas d'édition, pas de WebGPU, pas de matériaux, pas de végétation.</p>
<p>Le plan évident consistait à rétroporter un à un les résultats des spikes dans cette base de code de production. Nous l'avons abandonné. Le spike 30 disposait déjà d'un meilleur terrain, d'une meilleure physique, de meilleurs matériaux, d'une meilleure végétation et d'une meilleure caméra que tout ce que <code>world/client/</code> avait jamais proposé. Rétroporter ces éléments dans l'ancien code WebGL aurait signifié lutter sans cesse contre celui-ci. Nous avons donc décidé de remplacer l'implémentation du monde par le spike le plus abouti et de continuer à construire à partir de là. Le spike 30 est devenu notre nouvelle base, et <code>world/client/</code> du code mort.</p>
<p>Cela a redéfini le travail restant. Pour passer d'une « excellente démo technique solo » à un « produit », il nous fallait du multijoueur, de la persistance, du streaming de monde infini et du placement d'objets. La synchronisation multijoueur du terrain est passée en premier, car c'est elle qui impose l'architecture. La question à laquelle elle répond est simple à poser, mais une mauvaise réponse coûte cher : lorsque le joueur A sculpte, qu'est-ce qui transite réellement sur le réseau ?</p>
<h2>Rejouer les paramètres du pinceau plutôt que synchroniser les pixels</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/31-multiplayer-sync/" title="Spike 31 : synchronisation multijoueur du terrain" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/31-multiplayer-sync/" target="_blank">Ouvrir le spike 31 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=31-multiplayer-sync">Voir le code source</a></p>
<p>Avant d'écrire la moindre ligne de code réseau, nous avons suivi précisément le déroulement d'un coup de pinceau. Le pinceau de heightmap parcourt un rayon autour du curseur dans un <code>Float32Array</code> côté CPU, applique une atténuation smoothstep, puis ajoute, soustrait, lisse ou aplatit. Le pinceau SDF fait la même chose en 3D sur une sphère de voxels. Les deux chemins reposent exclusivement sur des calculs de tableaux côté CPU. Aucun calcul GPU dans la boucle, aucun aléatoire, aucune opération à virgule flottante non déterministe. Le même tableau d'entrée et les mêmes paramètres produisent le même résultat sur toutes les machines.</p>
<p>C'est là toute l'astuce. Nous n'envoyons pas le terrain modifié. Nous envoyons les paramètres du pinceau, soit 56 octets par tick de tracé, et chaque client rejoue la même fonction déterministe. Le protocole de synchronisation comprend quatre types de messages : la découverte des pairs, le message de pinceau <code>{op, wx, wy, wz, radius, strength, flattenTarget}</code> et un message de position du joueur à 20 Hz.</p>
<p>Pour ce spike, nous avons complètement contourné le serveur et utilisé <code>BroadcastChannel</code>, l'API du navigateur permettant la communication entre onglets d'une même origine. Ouvrez deux onglets : ils communiquent sans aucune infrastructure. Cela permet d'isoler la question de la synchronisation de la latence, de l'authentification et du raccordement aux Durable Objects. Si le rejeu des paramètres converge entre les onglets, il convergera aussi via un WebSocket.</p>
<p>Les opérations dépendantes de l'ordre constituent le seul cas où le rejeu peut diverger. Élever et abaisser sont des opérations commutatives : <code>val + strength * falloff</code> donne donc le même résultat, quel que soit l'ordre d'application. Le lissage et l'aplatissement lisent les valeurs voisines ; deux clients qui lissent exactement le même endroit au même instant peuvent donc diverger de quelques fractions de millimètre par tick. En pratique, cela ne se produit jamais, et la solution pour la production est déjà évidente : faire transiter les modifications par le DO, lui laisser attribuer un numéro de séquence monotone, appliquer les changements de manière optimiste côté client, puis corriger l'ordre si la séquence faisant autorité diffère. Il s'agit d'une concurrence optimiste classique, et le DO constitue de toute façon un point de sérialisation naturel.</p>
<h2>La capsule du pair qui n'arrêtait pas de disparaître</h2>
<p>Les modifications se sont synchronisées dès le premier essai. Ce ne fut pas le cas de la capsule du joueur distant. Elle apparaissait et disparaissait sans cesse dans l'autre onglet, et il a fallu corriger trois bugs distincts pour qu'elle reste visible.</p>
<p>La capsule apparaissait à l'origine du monde, enfouie sous le terrain, car le message <code>join</code> arrive avant toute donnée de position. Correction : la masquer initialement et ne l'afficher qu'à la première mise à jour de position. La diffusion de la position se trouvait dans la boucle de rendu, et Chrome bride <code>requestAnimationFrame</code> dans les onglets inactifs. La vérification d'obsolescence de l'autre onglet supprimait donc le pair, puis le message suivant le recréait. Correction : déplacer la diffusion vers un <code>setInterval</code>, qui n'est pas bridé pour les onglets visibles. Enfin, le délai d'obsolescence de 5 secondes était trop agressif et pouvait être déclenché par n'importe quelle pause du ramasse-miettes. Correction : le porter à 30 secondes et s'appuyer sur le message <code>leave</code> propre pour les fermetures normales.</p>
<h2>Persistance et arrivée tardive, même format</h2>
<p>Nous avons intégré la persistance au même spike plutôt que d'en créer un nouveau, car le format de sérialisation est identique, que la destination soit IndexedDB ou un autre onglet. Un instantané contient la heightmap complète (un <code>Float32Array</code> de 129×129, soit environ 66 Ko), uniquement les chunks SDF modifiés (chacun de $65^3$, soit environ 1,1 Mo), ainsi que la liste des identifiants de chunks verrouillés en mode marching cubes. Une sauvegarde temporisée écrit dans IndexedDB deux secondes après la dernière modification. Au chargement, le terrain procédural est généré de manière synchrone, puis l'état sauvegardé le remplace avant la première frame significative. L'arrivée tardive réutilise exactement les mêmes octets : lorsqu'un nouvel onglet rejoint la session, un onglet existant contenant des modifications sérialise son état et l'envoie spécifiquement au nouvel arrivant ; la montagne sculptée cinq minutes plus tôt apparaît alors sur son écran.</p>
<p>Le premier test de persistance a révélé un joli bug d'ordonnancement. L'herbe est répartie de manière synchrone à l'initialisation à partir des hauteurs procédurales, mais la restauration IndexedDB est asynchrone et remplace ensuite la heightmap, ce qui laisse chaque brin en lévitation ou enfoui. La correction consiste en une passe <code>refreshAllGrass()</code> qui rééchantillonne la hauteur sous chaque instance et masque tout brin qui se trouve désormais sur une pente ou à une altitude inadaptée. La même fonction sert à la fois au chargement et à l'arrivée tardive.</p>
<h2>La saga des pentes</h2>
<p>Le terrain sculpté est plus accidenté que la base procédurale lisse, et il a révélé trois bugs de physique que l'ancien terrain n'aurait jamais pu exposer. En marchant tout droit vers le haut d'une pente, la capsule glissait latéralement. La cause était une projection de vélocité destinée à maintenir le mouvement tangent au sol, mais écrite en utilisant uniquement les composantes horizontales de la normale. Sur une pente diagonale de normale $(-0.3, 0.9, -0.3)$, marcher vers le nord introduisait de nulle part une vélocité latérale. L'adhérence au sol maintient déjà le joueur sur la surface ; nous avons donc simplement supprimé cette projection.</p>
<p>La dérive persistait en raison d'une seconde source. Les sondes de collision SDF repoussent le corps le long du gradient, à hauteur de la profondeur de pénétration. Sur une pente, le gradient comporte des composantes horizontales. Une pénétration de 0,1 m sur une pente à 15° pousse donc le corps latéralement d'environ 0,026 m par étape ; à 120 Hz, cela représente près de 3 m/s de dérive invisible. Correction : séparer la réponse en fonction de la pente. Sur un sol praticable ($\text{grad}_y$ au-dessus du seuil de marche), la poussée est uniquement verticale. Sur les murs et les falaises, la poussée 3D complète est conservée, car c'est précisément là que la déviation est souhaitable. Nous avons également relevé la limite praticable de $\cos 45°$ à $\cos 60°$ afin que les collines créées au pinceau soient aussi faciles à gravir que dans Breath of the Wild.</p>
<p>Le troisième bug immobilisait la capsule aux frontières des chunks, car les sondes de collision échantillonnaient le SDF d'un seul chunk et obtenaient la sentinelle « profondément dans l'air » lorsqu'une sonde franchissait la limite vers le chunk voisin. La correction a consisté à créer <code>sdfSampleWorld(wx, wy, wz)</code> et <code>sdfGradientWorld(...)</code>, qui trouvent le bon chunk pour n'importe quelle position dans le monde et utilisent une estimation de distance fondée sur la heightmap lorsqu'aucun SDF n'existe. La transition des collisions entre SDF et heightmap est désormais continue.</p>
<h2>L'eau complète le monde</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/32-water-swimming/" title="Spike 32 : eau et nage" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/32-water-swimming/" target="_blank">Ouvrir le spike 32 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=32-water-swimming">Voir le code source</a></p>
<p>Jusqu'ici, tous les spikes reposaient sur le principe « la terre au-dessus de l'eau ». Le spike 32 a ajouté un océan et, avec lui, une nouvelle action de déplacement. Nous avons fixé le niveau de l'eau à 22 dans un terrain dont l'altitude varie approximativement de 8 à 58. Cela inonde les vallées basses, crée des plages sur le littoral et laisse suffisamment de terre ferme pour jouer.</p>
<p>La surface utilise un <code>MeshStandardNodeMaterial</code> construit en TSL, selon la même approche nodale que le terrain. Trois ondes sinusoïdales superposées de fréquences différentes déplacent les sommets, et la normale de surface est calculée à partir des dérivées cosinus analytiques de ces ondes plutôt qu'à partir des normales du maillage. La couleur passe des eaux turquoise peu profondes aux profondeurs bleu-vert sombre à l'aide d'une estimation de profondeur $(\text{level} - y + \text{wave}) \times 0.12$ bornée dans $[0,1]$. L'écume apparaît lorsque cette estimation approche de zéro sur le littoral, et l'alpha suit la profondeur afin que l'eau peu profonde paraisse transparente et l'eau profonde presque opaque.</p>
<p>La nage repose sur un ressort de flottabilité. Le joueur passe en mode nage lorsque ses pieds descendent sous le niveau de l'eau et que le centre de son corps se trouve à moins d'une demi-hauteur de capsule de la surface. Un ressort attire le corps vers une cible située juste sous la surface. Avec une constante de flottabilité de 12 et un amortissement dans l'eau de 4, le joueur flotte de manière stable, la tête hors de l'eau, sans oscillation. La vitesse de nage est inférieure à celle de la marche, avec une accélération flottante et de la traînée. Sauter près de la surface vous propulse hors de l'eau à 60 % de la vélocité normale de saut, et l'entrée dans l'eau limite la vélocité descendante à -5 m/s afin d'éviter de plonger trop profondément. Les collisions avec le terrain restent actives sous l'eau, ce qui permet de marcher sur le fond du lac lorsqu'il remonte au-dessus de la cible de nage. L'indicateur de nage est transmis avec la diffusion de position afin que les autres joueurs vous voient nager, et un dégradé HTML teinte la vue lorsque la caméra passe sous la surface.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Rejeu déterministe des paramètres du pinceau.</strong> Au lieu de diffuser le terrain modifié, chaque client envoie uniquement les paramètres du pinceau et rejoue la même fonction CPU. Cela fonctionne parce que les pinceaux de heightmap et de SDF reposent tous deux sur de purs calculs <code>Float32Array</code>, sans aléatoire ni non-déterminisme GPU. Des entrées identiques produisent donc partout des sorties identiques bit à bit. La charge utile est de 56 octets par tick de tracé. Les opérations commutatives (élever, abaisser) convergent quel que soit leur ordre, tandis que les opérations qui lisent les valeurs voisines (lisser, aplatir) nécessitent un point de sérialisation pour garantir la convergence. En production, le Durable Object remplit ce rôle grâce à des numéros de séquence monotones.</p>
<p><strong>BroadcastChannel comme substitut à WebSocket.</strong> Une API de navigateur permettant la communication entre onglets d'une même origine sans aucun serveur. Elle sert ici à tester le protocole de synchronisation indépendamment de la latence réseau et de l'authentification. Le format de sérialisation (heightmap <code>Float32Array</code> brute, chunks SDF modifiés et identifiants des chunks verrouillés en MC) utilise exactement les mêmes octets pour la persistance IndexedDB et le transfert d'état lors d'une arrivée tardive. Un seul format remplit donc trois fonctions.</p>
<p><strong>Réponse aux collisions SDF séparée selon la pente.</strong> Lorsqu'une sonde de capsule pénètre dans un terrain volumétrique, la solution naïve consiste à repousser le corps le long du gradient SDF selon la profondeur de pénétration. Sur les pentes, ce gradient comporte des composantes horizontales qui introduisent une dérive latérale. En séparant la réponse afin que les surfaces praticables ($\text{grad}_y$ au-dessus du seuil) n'appliquent qu'une poussée verticale, tandis que les surfaces abruptes conservent la poussée 3D complète ainsi que la projection de vélocité, on élimine la dérive sans perdre les collisions avec les murs. Voir <a href="/fr/guides/landscape-generation-browser#physics-on-sdf-terrain">les collisions de terrain SDF</a>.</p>
<p><strong>Eau TSL avec normales de vagues analytiques.</strong> L'océan utilise un matériau nodal dont les sommets sont déplacés par la somme de trois ondes sinusoïdales. Au lieu de recalculer les normales du maillage après le déplacement, la normale de surface est dérivée analytiquement à partir des dérivées cosinus des fonctions d'onde. Cette méthode est moins coûteuse et évite les artefacts des normales calculées par différences finies sur une grille grossière. La couleur, l'écume du littoral et la transparence sont toutes déterminées par une même estimation de profondeur.</p>
<p><strong>Nage par ressort de flottabilité.</strong> La physique de nage modélise le corps comme un ressort amorti attiré vers une cible située juste sous la surface. Avec une constante de flottabilité de 12 et un amortissement de 4, le joueur se stabilise à la surface sans osciller. Des constantes de déplacement distinctes — vitesse réduite, accélération flottante et forte traînée — donnent à la nage des sensations différentes de la marche, tandis que les collisions existantes entre la capsule et le terrain continuent de fonctionner sous l'eau.</p>
<hr>
<p>Partie 15 sur 29.
Précédent : <a href="/fr/blog/2026-04-13-open-world-browser-part-14-world-comes-alive">Partie 14 — Le monde prend vie</a>
Suivant : <a href="/fr/blog/2026-04-28-open-world-browser-part-16-structure-and-authoring">Partie 16 — Une structure pour un monde qui ne cesse de grandir</a>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cubcoats × Cinevva : fiche synthétique des concepts de jeux]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-03-19-cubcoats-game-concepts-one-pager</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-03-19-cubcoats-game-concepts-one-pager</guid>
            <pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Trois concepts de jeux éducatifs (Golden Tree, Kali's Word Island, Island Stories) fondés sur le processus allant du croquis à la vidéo puis à la 3D, ainsi que sur Kali the Kitty. Pour une licence ou une présentation à des partenaires.]]></description>
            <content:encoded><![CDATA[<h1>Cubcoats × Cinevva : fiche synthétique des concepts de jeux</h1>
<p><em>Fondé sur le processus allant du croquis à la vidéo puis à la 3D, ainsi que sur Kali the Kitty (Cubcoats). Pour une licence, un article de blog ou une présentation à des partenaires.</em></p>
<p><strong>Pour partager cette page au format PDF :</strong> Ouvrez-la, puis utilisez la commande <strong>Fichier → Imprimer → Enregistrer au format PDF</strong> de votre navigateur.</p>
<hr>
<h2>Golden Tree — Fiche complète</h2>
<p><strong>Slogan :</strong> <em>Un monde qui grandit avec vous. Venez, arrosez, observez. Aucun niveau. Aucune pression.</em></p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Âges</strong></td>
<td>3–13 ans (public large)</td>
</tr>
<tr>
<td><strong>Format</strong></td>
<td>Monde 3D partagé et épisodique + courts « moments » hebdomadaires</td>
</tr>
<tr>
<td><strong>Plateforme</strong></td>
<td>Web (puis mobile) ; accès immédiat, sans installation</td>
</tr>
<tr>
<td><strong>Accroche virale</strong></td>
<td>« Envoie une graine à un ami. » Croissance partagée et séquences apaisantes faciles à partager.</td>
</tr>
</tbody>
</table>
<h3>Le concept</h3>
<p>Un monde paisible à explorer et à collectionner, qui évolue au <strong>rythme du monde réel</strong>. Une « saison » commune (par exemple, l’Île du printemps) : une île 3D unique que les enfants peuvent parcourir, comme dans la démo jouable de Kali. Il n’y a ni niveaux ni scores traditionnels. La progression prend la forme de <strong>ton arbre a une nouvelle branche</strong>, <strong>tu as vu 12 moments</strong>, <strong>quelqu’un a arrosé ta graine</strong>.</p>
<p>À chaque visite, l’enfant peut : <strong>arroser une plante</strong>, <strong>laisser un souhait</strong> (texte ou emoji), <strong>trouver un personnage Cubcoats caché</strong> ou <strong>regarder un « moment » de 30 secondes</strong> (par exemple, Kali sous l’arbre doré). De nouveaux moments et de courtes séquences narratives sont publiés <strong>chaque semaine</strong>, sous forme épisodique. Ambiance linguistique facultative : « Aujourd’hui, Kali a dit : [une phrase en EN et ZH]. »</p>
<p>L’apprentissage et l’épanouissement sont <strong>subtils</strong> : les thèmes (gentillesse, patience, persévérance) passent par l’écriture et la direction artistique, et non par des leçons. L’expérience ressemble à un jouet ou à un lieu partagé, pas à l’école.</p>
<h3>Boucle de jeu principale</h3>
<ol>
<li><strong>Visiter</strong> l’île (dans un navigateur ou une application).</li>
<li><strong>Effectuer une action apaisante</strong> à chaque visite : arroser, faire un souhait, trouver un personnage ou regarder un moment.</li>
<li><strong>Voir le monde changer</strong> au fil des jours et des semaines (nouvelles fleurs, nouvelles poses de personnages, nouvelles courtes séquences).</li>
<li><strong>Partager</strong> une graine avec un ami ou « visiter l’île d’un ami » pour donner une dimension sociale à la croissance.</li>
</ol>
<h3>Utilisation du processus et des personnages de l’article</h3>
<ul>
<li><strong>Monde 3D jouable :</strong> Même processus que pour la démo de Kali : pose en T → 3D Model Generator → rigging → Cinevva Engine. L’île et les personnages peuvent être explorés à la troisième personne, sans pression.</li>
<li><strong>Arbre doré :</strong> Directement tiré de l’article (le passage de Kali « de la peur à l’espoir » et l’arbre doré magique). L’arbre est le point d’ancrage émotionnel et visuel de la saison.</li>
<li><strong>Courts « moments » :</strong> Produits comme la courte vidéo verticale de l’article : image de début + image de fin + prompt de mouvement → Video Generator (Kling 3.0 Pro), 9:16, environ 15 s. Chaque publication hebdomadaire apporte une petite séquence narrative.</li>
<li><strong>Propriété intellectuelle Cubcoats :</strong> Kali et les sept autres personnages, l’univers insulaire et le style graphique chaleureux aux formes arrondies. Le concept convient à une relance axée sur les licences (2026), avec un produit numérique et épisodique.</li>
</ul>
<h3>Son potentiel viral</h3>
<ul>
<li><strong>Accès immédiat :</strong> Un seul lien, sans parcours d’accueil complexe. « Envoie une graine à un ami » est une invitation simple à partager.</li>
<li><strong>Séquences belles et apaisantes :</strong> Les courts moments peuvent être partagés séparément (sur les réseaux sociaux, avec le message « regarde ces 15 secondes »). Ils se prêtent aux mèmes de manière positive.</li>
<li><strong>Format épisodique :</strong> « Quoi de neuf cette semaine ? » encourage les visites régulières et les conversations.</li>
<li><strong>Approche subtile :</strong> Les parents voient une expérience « calme, créative, sans publicité et sans stress » ; les enfants découvrent la régularité, la patience et de douces surprises, sans enseignement explicite.</li>
</ul>
<h3>Angle possible pour le blog Cinevva</h3>
<p><em>« Nous avons fait passer Kali du croquis à la vidéo, puis à un personnage autour duquel on peut se déplacer. Golden Tree est l’étape suivante : un monde qui grandit en temps réel, dans lequel les enfants viennent, arrosent et regardent de courts moments — sans niveaux ni pression. Même processus. Nouveau format. »</em></p>
<hr>
<h2>Kali's Word Island — En bref</h2>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Slogan</strong></td>
<td>Aide Kali et ses amis à faire grandir l’île. Apprends l’anglais et le chinois en chemin.</td>
</tr>
<tr>
<td><strong>Âges</strong></td>
<td>5–11 ans</td>
</tr>
<tr>
<td><strong>Objectif</strong></td>
<td>Anglais ↔ chinois pour les enfants ; forte ludification et progression structurée.</td>
</tr>
</tbody>
</table>
<p><strong>Concept principal :</strong> L’île des Cubcoats est divisée en zones associées aux personnages (la forêt de Kali, la plage d’un autre personnage). Les enfants aident les personnages en accomplissant de courtes tâches qui nécessitent de comprendre et de s’exprimer en EN/ZH. Progression = « Graines → Jeunes pousses → Arbres → Arbre doré » (la zone suivante se débloque lorsque l’arbre de la zone est complet). Parcours quotidien de la « lanterne », cartes de phrases à collectionner, jauges d’« humeur » des personnages et tableau de bord parental facultatif. De courts moments narratifs (conçus à partir d’une image de début et d’une image de fin) se débloquent lorsqu’une zone est terminée. <strong>Processus :</strong> Même processus 3D + vidéo pour les personnages et les temps forts émotionnels.</p>
<hr>
<h2>Cubcoats: Island Stories — En bref</h2>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Slogan</strong></td>
<td>Crée ton propre épisode de Cubcoats. Remixe l’histoire. Partage ta version.</td>
</tr>
<tr>
<td><strong>Âges</strong></td>
<td>3–13 ans (tranches : 3–6, 7–10, 11–13)</td>
</tr>
<tr>
<td><strong>Objectif</strong></td>
<td>Création et remix d’histoires ; apprentissage intégré, mais pas au cœur de la présentation.</td>
</tr>
</tbody>
</table>
<p><strong>Concept principal :</strong> Des récits épisodiques dans lesquels les enfants choisissent différentes branches (que dit Kali ? où va-t-elle ?). L’activité principale consiste à <strong>remixer</strong> : choisir un personnage, un lieu et 2 à 3 temps forts → produire un court « épisode » (scénario + visuels simples ou, plus tard, une courte vidéo générée par IA). L’apprentissage est subtil (vocabulaire et structures intégrés aux dialogues et aux choix). Dimension sociale : partager son épisode, jouer à la version de quelqu’un d’autre et l’évaluer comme « gentil / drôle / surprenant ». <strong>Processus :</strong> Même apparence des personnages + processus vidéo à deux images pour les « moments » créés par les utilisateurs ; 3D pour préserver la cohérence des personnages et du monde.</p>
<hr>
<p><em>Outils Cinevva mentionnés : Image Generator, Video Generator (Kling 3.0 Pro), 3D Model Generator, auto-rigging, Cinevva Engine. Reprend le processus décrit dans « Du croquis à la vidéo et à la 3D jouable » (2026-03-04).</em></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ce qu'il faut réellement pour créer un monde ouvert dans un navigateur]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-03-14-open-world-browser-medium-article</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-03-14-open-world-browser-medium-article</guid>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Une immersion journalistique dans la série d'ingénierie en 12 parties de Cinevva : 24 expérimentations exploratoires, des premières versions rudimentaires du terrain aux marching cubes sur GPU, en passant par le LOD en streaming et le débogage des raccords Transvoxel.]]></description>
            <content:encoded><![CDATA[<h1>Ce qu'il faut réellement pour créer un monde ouvert dans un navigateur</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, CEO de Cinevva</em></p>
<img src="/img/blog/open-world-browser-120fps.png" alt="Le monde ouvert exécuté dans un navigateur à 120 FPS — terrain, arbres, physique et capsule du joueur, le tout rendu dans un seul onglet" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>L'équipe de Cinevva vient de publier l'un des journaux d'ingénierie les plus transparents de ces dernières années dans le développement de jeux : une <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">série en 12 parties</a> documentant notre tentative de créer un monde ouvert multijoueur qui s'exécute entièrement dans le navigateur. Aucun téléchargement. Aucune boutique d'applications. Juste une URL.</p>
<p>Le projet s'est étendu sur 24 expériences techniques que nous appelons des « spikes » -- de courts prototypes ciblés, conçus chacun pour répondre à une question risquée. Chaque spike a été publié avec du code source fonctionnel que vous pouvez ouvrir et exécuter dans votre navigateur dès maintenant. La série a été écrite par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva, et ressemble moins à du marketing qu'à un journal de terrain tenu sur la ligne de front de ce que les navigateurs peuvent réellement accomplir en 2026.</p>
<p>Ce qui rend la série intéressante à lire -- même si vous n'envisagez jamais de créer des systèmes de terrain --, c'est la méthode sous-jacente. C'est une étude de cas sur la manière de réduire les risques d'un projet ambitieux avant de s'engager dans des dépenses importantes.</p>
<h2>Commencer par la question la plus difficile</h2>
<p>La plupart des projets de monde ouvert échouent selon une séquence prévisible. D'abord, vous avez un superbe concept. Puis une jolie scène. Ensuite, vous découvrez que votre budget par image était déjà épuisé avant même que le gameplay n'existe.</p>
<p>Notre équipe a inversé l'ordre. Le <a href="/fr/blog/2026-02-25-open-world-browser-part-01-risk-first">premier spike</a> était délibérément rudimentaire : un maillage de terrain de 512 mètres, 500 objets instanciés, un relief généré par bruit procédural, un plan d'eau et du brouillard. Pas d'ombres, pas de passe d'embellissement. La seule question était de savoir si un navigateur pouvait maintenir une fréquence d'images stable pendant que la caméra s'y déplaçait.</p>
<p>C'était possible. Et ce « oui » a établi ce qu'Oleg appelle un « contrat de référence » -- un coût mesuré servant de référence pour une scène minimale, auquel chaque fonctionnalité suivante devait se justifier. Si un nouvel effet était superbe mais faisait exploser le budget par image, il n'était pas retenu. Du moins, pas encore.</p>
<p>Ce type de discipline semble évident. En pratique, il est rare dans les environnements de prototypage rapides où tout le monde s'enthousiasme pour la prochaine avancée visuelle.</p>
<h2>Le pari de la physique</h2>
<p>La <a href="/fr/blog/2026-02-25-open-world-browser-part-02-worker-physics">deuxième expérience</a> s'est attaquée à un débat architectural qui divise les développeurs de jeux sur navigateur : la physique doit-elle s'exécuter sur le thread principal, où sa mise en œuvre est plus simple, ou dans un Web Worker, où elle ne peut pas bloquer le rendu ?</p>
<p>Sur le papier, exécuter la physique dans un worker est plus propre. En pratique, la crainte concerne la latence. Chaque événement d'entrée doit franchir deux fois une frontière de communication : une première fois pour atteindre le worker, puis une seconde pour rapporter le résultat. Si cet aller-retour est trop lent, appuyer sur une touche et voir son personnage se déplacer semblera poussif.</p>
<p>L'équipe a intégré le moteur physique Rapier (compilé de Rust vers WebAssembly) dans un worker dédié, mis en place le pipeline de messages, puis effectué des mesures. Le surcoût était négligeable. Les commandes restaient instantanées. Mais nous avons pris soin de préciser que nous avions validé un scénario particulier, et non une règle universelle. Lorsque la charge sur le GPU et la complexité du streaming changeraient par la suite, il faudrait réexaminer ces hypothèses.</p>
<h2>Les spikes ennuyeux qui ont sauvé le projet</h2>
<p>La <a href="/fr/blog/2026-02-25-open-world-browser-part-03-the-unflashy-spikes">troisième partie</a> de la série ne contient aucune capture d'écran. Elle couvre trois expériences peu spectaculaires en apparence, mais lourdes de conséquences pour le produit.</p>
<p>La première visait à déterminer si les Durable Objects de Cloudflare pouvaient gérer la diffusion en temps réel des positions à des fréquences de tick dignes d'un jeu -- autrement dit, le socle du multijoueur. En cas d'échec, toute l'architecture réseau aurait dû être partitionnée très tôt, au lieu de reposer sur la gestion d'une île par une instance unique.</p>
<p>La deuxième validait un profil de qualité mobile : non pas un préréglage pour ordinateur simplement rebaptisé, mais un chemin de rendu explicitement peu coûteux, construit à partir de la même base de terrain. Il s'agissait de savoir si le monde pouvait rester lisible et réactif sous les contraintes des GPU mobiles sans réécrire le moteur de rendu.</p>
<p>La troisième évaluait si les scripts de comportement générés par IA pour les workflows des créateurs seraient suffisamment fiables pour une utilisation en production.</p>
<p>Aucune de ces expériences n'a produit de bande démo. Toutes les trois ont fixé des limites strictes qui ont orienté chaque décision architecturale ultérieure. Oleg écrit que ces « spikes peu spectaculaires ont fait évoluer l'architecture plus rapidement que les spikes visuels ».</p>
<h2>Le streaming : là où les beaux projets s'effondrent</h2>
<img src="/img/blog/open-world-chunk-streaming.png" alt="Spike 6 : streaming des chunks en action — chaque zone colorée est un chunk de terrain qui est chargé et déchargé dynamiquement au fil des déplacements de la caméra" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>On peut dissimuler beaucoup de choses dans une image fixe. On ne peut pas masquer une saccade de 40 millisecondes lorsqu'un joueur franchit la limite d'un chunk en courant.</p>
<p>L'équipe a testé <a href="/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy">le streaming avant de créer un terrain avancé</a>, en séparant délibérément les problématiques. Le Spike 6 a validé le chargement des chunks voisins avec du contenu simple. Ce n'est qu'après avoir obtenu ce signal clair que le Spike 11 a introduit le streaming de heightmaps compressées avec raffinement progressif -- en chargeant d'abord le terrain à une résolution de 17 échantillons, puis de 33, avant de passer à la grille complète de 65 échantillons.</p>
<p>L'ordre des opérations s'est révélé plus important que prévu. Si nous avions commencé directement avec des chunks de hauteur compressés, chaque saccade aurait été ambiguë. S'agissait-il d'un problème de décodage, d'un blocage lors du téléversement d'une texture ou d'un problème de mise à jour de la géométrie ? Tester d'abord un streaming simple a éliminé toute une catégorie d'incertitudes.</p>
<p>Une leçon pratique s'en est dégagée : il faut mesurer directement les blocages liés aux téléversements, et non les déduire du nombre moyen de FPS. Les moyennes masquent les pics de durée d'image, alors que ce sont précisément ces pics que les joueurs ressentent.</p>
<h2>La guerre des budgets visuels</h2>
<p>Trois expériences distinctes se sont attaquées séparément aux coûts de rendu, plutôt que de les regrouper. La densité de la <a href="/fr/blog/2026-02-25-open-world-browser-part-05-budgeting-the-pretty">végétation</a> et l'animation du vent. Les matériaux de terrain multicouches avec projection triplanaire pour les parois rocheuses. Les shadow maps en cascade sous une charge de terrain réaliste.</p>
<p>Le spike consacré à la végétation a révélé que regrouper les instances en un nombre réduit de maillages avait plus d'impact que de diminuer le nombre de polygones par brin. Celui consacré aux matériaux a montré que la projection triplanaire sur les surfaces verticales justifiait son coût GPU, contrairement à l'ajout d'une cinquième couche de textures splat. Celui consacré aux ombres a établi que trois cascades d'une résolution de 1024 produisaient des ombres de contact acceptables sans dépasser 2 millisecondes de temps GPU.</p>
<p>L'équipe a adopté une règle sans détour : une fonctionnalité ne progresse que si son coût peut être justifié par des mesures du temps de rendu par image. Cette contrainte, instaurée tôt, a considérablement simplifié les décisions architecturales ultérieures concernant le terrain volumétrique et les clipmaps.</p>
<h2>Le pivot qui a changé la trajectoire du projet</h2>
<p>Avant le Spike 10, notre modèle mental était le suivant : « un monde plus vaste implique davantage de géométrie ». Après le <a href="/fr/blog/2026-02-25-open-world-browser-part-06-clipmaps">Spike 10</a>, il est devenu : « budget géométrique constant, avec mise à jour d'anneaux centrés sur la caméra ».</p>
<p>Les clipmaps géométriques -- des anneaux concentriques de terrain centrés sur la caméra, chacun étant progressivement moins détaillé -- permettaient de maintenir un nombre de triangles à peu près constant, quelle que soit la distance d'affichage. L'astuce pratique résidait dans le géomorphing aux limites des anneaux : fusionner progressivement les hauteurs des sommets dans le shader afin que le passage d'un niveau de résolution à l'autre reste invisible en mouvement.</p>
<p>Une leçon subtile est venue de la méthodologie de test. Les clipmaps paraissent correctes sur les captures d'écran. Leurs artefacts ne se révèlent que lors de déplacements prolongés de la caméra à travers les limites des anneaux. L'équipe a passé du temps à effectuer des traversées à vitesse constante et à rechercher du bruit temporel. « Les captures d'écran mentaient, écrit Oleg. Le mouvement disait la vérité. »</p>
<h2>Passer sous terre</h2>
<p>Les heightmaps ne peuvent pas représenter des grottes. Elles stockent une seule valeur d'élévation par point d'une grille. Dès que l'on veut des tunnels, des surplombs ou des parois rocheuses sculptées, il faut un terrain volumétrique.</p>
<p>Le <a href="/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes">Spike 12</a> a implémenté les marching cubes sur le GPU au moyen de compute shaders WebGPU, afin d'extraire des maillages triangulaires d'un champ de distance signé en 3D. Quatre chunks de 64³ s'exécutaient simultanément, avec des mises à jour du maillage à chaque image à partir de modifications animées du SDF. Le compute shader gérait l'ensemble du processus -- évaluation du champ, classification des cellules et émission des sommets -- sans aucune relecture par le CPU.</p>
<p>Le véritable défi n'était pas de faire fonctionner le système. Il fallait le faire fonctionner aux côtés de tout le reste. L'intégration au graphe de scène de Three.js, la gestion du cycle de vie des buffers (les buffers WebGPU ne peuvent pas être redimensionnés), la gestion des fences pour éviter de détruire des ressources GPU encore utilisées -- la série consacre <a href="/fr/blog/2026-02-25-open-world-browser-part-08-webgpu-integration">deux</a> parties entières à ce que nous appelons le « durcissement progressif », ce processus peu spectaculaire qui consiste à ajouter une capacité à la fois et à vérifier, après chaque ajout, que la couche précédente fonctionne toujours.</p>
<h2>Le cauchemar des raccords</h2>
<p>La section techniquement la plus éprouvante de la série couvre les <a href="/fr/blog/2026-02-25-open-world-browser-part-09-transvoxel-first-cut">parties 9 à 11</a> et traite de ce qui se produit lorsque des chunks de terrain de résolutions différentes se rejoignent.</p>
<p>Lorsqu'un chunk très détaillé jouxte un chunk moins détaillé, leurs maillages générés indépendamment ne s'alignent pas à la frontière. Il en résulte des fissures visibles, des arêtes qui scintillent et des jonctions en T à travers lesquelles la lumière fuit. L'algorithme Transvoxel résout ce problème grâce à des cellules de transition spéciales qui relient les différences de résolution -- mais son implémentation correcte dans toutes les configurations de chunks, avec un ordre d'enroulement cohérent, une bonne gestion des buffers et des plages de dessin exactes, a nécessité six expériences distinctes.</p>
<p>L'anecdote de débogage la plus mémorable de l'équipe : deux jours passés à traquer un artefact de raccord que nous attribuions à la logique de transition. Le véritable coupable était une donnée obsolète. Le compute shader GPU écrivait N sommets dans un buffer, mais l'appel de dessin était toujours configuré pour afficher N+M sommets provenant de l'image précédente. Ces sommets supplémentaires contenaient des données incohérentes qui produisaient de minces triangles scintillants, semblables à des lames de rasoir. Une seule ligne a suffi à corriger le problème : limiter la plage de dessin au nombre de sommets actifs indiqué par le compteur atomique.</p>
<p>« Les bugs de rendu se font souvent passer pour des bugs de maillage, observe Oleg. La géométrie avait toujours été correcte. »</p>
<h2>Du chaos à la gouvernance</h2>
<p>Après la bataille des raccords, l'équipe a remplacé le comportement ad hoc des chunks par un <a href="/fr/blog/2026-02-25-open-world-browser-part-11-policy-modes">système de règles explicite</a>. Une fonction centrale décidait désormais du niveau de LOD de chaque chunk, de son mode de rendu (heightmap ou marching cubes) et des faces nécessitant des cellules de transition. Des anneaux de distance déterminaient le LOD de base. Une contrainte d'adjacence garantissait que deux chunks voisins ne différaient jamais de plus d'un niveau de résolution. Une bitmap des modifications maintenait les chunks volumétriques en mode marching cubes, quelle que soit leur distance, s'ils contenaient des modifications apportées par des créateurs.</p>
<p>Des surcouches de débogage à code couleur -- vert pour les chunks en heightmap, bleu pour les marching cubes, orange pour les faces de transition -- ont transformé « j'ai vu un bug quelque part près de cette crête » en « le bug apparaît à la position (142, 12, -67), en direction du nord-ouest ».</p>
<p>« Les règles n'ont pas réduit la complexité, écrit Oleg. Elles l'ont organisée. »</p>
<h2>Le résultat final</h2>
<p>Le dernier spike a combiné les anneaux de clipmap, le brouillard de ciel calculé par fragment (en échantillonnant la couleur réelle de la skybox dans la direction de chaque fragment du terrain) et le câblage des modules Three.js dans une démonstration unifiée. Le résultat est un système de terrain qui superpose l'édition volumétrique au premier plan, les chunks de heightmap à moyenne distance et les anneaux de clipmap au loin, sous une couche de règles qui régit le mode, le LOD et les transitions.</p>
<p>La série se termine par des <a href="/fr/blog/2026-02-25-open-world-browser-part-12-lessons">enseignements</a> qu'Oleg dit vouloir appliquer à nouveau dans tout futur projet :</p>
<ul>
<li><strong>Commencez par des spikes consacrés aux risques avant de travailler sur les fonctionnalités.</strong> Répondez aux questions du type « est-ce seulement faisable ? » avant d'investir dans les pipelines de contenu.</li>
<li><strong>Figez les bases dont le bon fonctionnement est confirmé avant les grands sauts d'intégration.</strong> La journée passée à établir un point de contrôle propre permet d'économiser plusieurs jours consacrés ensuite à isoler des régressions.</li>
<li><strong>Imposez des règles et de l'observabilité avant les marathons d'optimisation.</strong> Des conditions nommées avec des règles de déclenchement l'emportent toujours sur les bugs mystérieux.</li>
<li><strong>Testez en mouvement, pas avec des captures d'écran.</strong> Les sauts visuels, le scintillement et les saccades du streaming se cachent tous dans les images fixes.</li>
<li><strong>Mesurez le temps par image de chaque fonctionnalité, pas la moyenne des FPS.</strong> Les moyennes masquent les pics que les utilisateurs ressentent réellement.</li>
<li><strong>Publiez les passages chaotiques.</strong> Les fausses pistes, la chasse aux fantômes, les deux jours passés à accuser le mauvais système. Ce sont les parties dont les autres peuvent réellement tirer des enseignements.</li>
</ul>
<h2>Pourquoi cela compte au-delà de Cinevva</h2>
<p>Cette série est importante pour trois raisons qui dépassent le pipeline de terrain d'une seule entreprise.</p>
<p>Premièrement, elle montre que les compute shaders WebGPU, la physique WebAssembly et les Durable Objects déployés en périphérie ont franchi un cap. En 2026, un monde ouvert multijoueur doté d'un terrain volumétrique, d'une édition en temps réel et d'un LOD en streaming est viable sur le plan architectural dans un onglet de navigateur. Ce n'était pas le cas deux ans auparavant.</p>
<p>Deuxièmement, la méthodologie des spikes -- de petites expériences ciblées répondant chacune à une question risquée au moyen de résultats fonctionnels et mesurables -- fournit un modèle à toute équipe qui tente quelque chose dont la faisabilité reste incertaine. La discipline qui consiste à mesurer avant de s'engager, à établir des références avant d'intégrer et à nommer les cas limites avant d'optimiser s'applique bien au-delà des systèmes de terrain.</p>
<p>Troisièmement, cette transparence radicale est précisément l'objectif. La publication du code source des 24 expériences, y compris les impasses et les détours de débogage ayant duré deux jours, fait de cette série bien plus qu'un blog technique. C'est un carnet d'ingénierie public qui traite le lecteur comme un collègue plutôt que comme un client.
La série complète est disponible dans notre <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>, avec chaque prototype exploratoire exécutable directement dans le navigateur.</p>
<hr>
<p><em>Cet article a été initialement publié sur <a href="https://vio-202020.medium.com/de-risking-an-ambitious-project-before-committing-to-anything-big-like-an-online-open-world-c94a7c05ee06">Medium</a>.</em></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2026-03-04-sketch-to-animated-3d-character-factcheck-note]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-03-04-sketch-to-animated-3d-character-factcheck-note</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-03-04-sketch-to-animated-3d-character-factcheck-note</guid>
            <pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[]]></description>
            <content:encoded><![CDATA[<h1>Note de vérification des faits : « Du croquis à la vidéo et à la 3D jouable »</h1>
<p><strong>Pour :</strong> Oleg Sidorkin<br>
<strong>Objet :</strong> <a href="https://app.cinevva.com/blog/2026-03-04-sketch-to-animated-3d-character">app.cinevva.com/blog/2026-03-04-sketch-to-animated-3d-character</a><br>
<strong>Vérifié dans :</strong> le dépôt cinevva-site (site marketing + worker)</p>
<hr>
<h2>Vérifié ✓</h2>
<ul>
<li>Les <strong>noms des outils</strong> (Générateur d’images, Générateur de vidéos, Générateur de modèles 3D) et le <strong>lien</strong> <code>/fr/tools/hunyuan3d</code> correspondent à la base de code.</li>
<li>L’<strong>auteur</strong> (Oleg Sidorkin, CTO) est correctement indiqué.</li>
<li><strong>Flux de création vidéo :</strong> l’interpolation entre l’image de début et celle de fin, le format 9:16, la durée de 3 à 15 s et le mode storyboard sont tous pris en charge.</li>
<li><strong>Pipeline 3D :</strong> Hunyuan3D et « Générateur de modèles 3D » sont corrects ; le rigging automatique (Tripo) et le flux « importer un fichier GLB → modèle riggé + bibliothèque d’animations » correspondent à l’implémentation.</li>
<li><strong>Cinevva Engine</strong> et la mention « Reflète l’état du produit au début de l’année 2026 » sont exacts.</li>
</ul>
<hr>
<h2>Correction nécessaire</h2>
<p><strong>« Seedance 1.5 Pro »</strong> — Dans ce dépôt, la vidéo est implémentée avec <strong>Kling 3.0 Pro</strong> (<a href="http://fal.ai">fal.ai</a>), et non Seedance 1.5 Pro :</p>
<ul>
<li><code>worker/src/genai/fal-video.ts</code> utilise <code>fal-ai/kling-video/v3/pro/...</code></li>
<li><code>worker/src/index.ts</code> comptabilise le coût avec <code>model: 'kling-3.0-pro'</code></li>
<li><code>tools/video.md</code> indique : « Générez de courtes vidéos par IA à partir de texte ou d’images avec <strong>Kling 3.0 Pro</strong> »</li>
</ul>
<p>Le nom de l’API/du produit est « seedance » (par exemple <code>/genai/seedance/generate</code>), mais le modèle sous-jacent est Kling 3.0 Pro. Seedance 1.5 Pro est un autre modèle (ByteDance). Les sources publiques limitent souvent Seedance 1.5 Pro à environ 10 s ; ici, Kling prend en charge une durée de 15 s.</p>
<p><strong>Recommandation :</strong> Dans l’article, nommez le modèle vidéo <strong>« Kling 3.0 Pro »</strong> (ou, par exemple, « Générateur de vidéos (Kling 3.0 Pro) ») afin que cela corresponde au site marketing et à la documentation. Si le backend de l’application utilise réellement Seedance 1.5 Pro, harmonisez le texte de l’application et <code>tools/video.md</code> afin qu’ils décrivent le même modèle.</p>
<hr>
<h2>Non vérifiable dans le dépôt</h2>
<p>Cubcoats, Kali the Kitty, Mimi Chao et les affirmations commerciales associées sont des éléments externes qui n’ont pas été vérifiés.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Du croquis à la vidéo et à la 3D jouable : personnage, scènes clés et rig]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-03-04-sketch-to-animated-3d-character</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-03-04-sketch-to-animated-3d-character</guid>
            <pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Comment nous avons utilisé le Générateur d'images, le Générateur de vidéos et le Générateur de modèles 3D de Cinevva pour transformer un croquis de personnage en vidéo courte verticale générée par IA et en modèle 3D riggé que l'on peut déplacer dans un navigateur.]]></description>
            <content:encoded><![CDATA[<h1>Du croquis à la vidéo et à la 3D jouable</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, directeur technique de Cinevva</em></p>
<p>Faire passer un personnage d'un croquis à plat à un jeu 3D jouable nécessitait autrefois un concept artist, un modeleur 3D, un rigger, un animateur et quelqu'un pour tout intégrer dans un moteur. Nous voulions voir quelle part de ce pipeline les <a href="/fr/tools/">outils créatifs de Cinevva</a> pouvaient prendre en charge à eux seuls. Voici ce que nous avons découvert.</p>
<p>Le personnage est <strong>Kali the Kitty</strong>, de <a href="https://thecubcoats.com">Cubcoats</a>, une marque pour enfants qui a vendu plus d'un million de sweats à capuche en forme d'animaux en peluche par l'intermédiaire de Nordstrom, Amazon et Disney Store. Cubcoats compte huit personnages originaux aux personnalités bien définies, un univers insulaire fictif et 14 brevets. La marque sera relancée en 2026 sous la forme d'une plateforme axée sur les licences, et nous voulions montrer à quoi ressemble cette propriété intellectuelle lorsqu'elle dépasse le cadre des produits physiques : une courte vidéo verticale et un personnage 3D riggé que l'on peut déplacer dans un navigateur. Tout ce qui suit est le résultat réel de cette exploration.</p>
<p>Faites glisser les vues 3D pour tourner autour des modèles. Faites défiler la pellicule pour voir comment la courte vidéo verticale évolue de la première à la dernière image.</p>
<h2>D'une illustration de livre à plat à un personnage que l'on peut faire pivoter</h2>
<p>Cubcoats disposait déjà de magnifiques illustrations 2D. La direction artistique de Mimi Chao donnait à chaque personnage un style chaleureux, arrondi et dessiné à la main, parfaitement adapté aux sweats à capuche et aux livres illustrés. Mais une illustration à plat ne suffit pas à alimenter un pipeline 3D. Il nous fallait un personnage haute fidélité pouvant servir de référence pour la génération vidéo, la création du maillage et le rigging, toujours à partir du même visage.</p>
<p>Nous avons commencé par fournir les illustrations originales du livre à notre <a href="/fr/tools/flux">Générateur d'images</a>, en lui demandant de produire une version 3D du même personnage.</p>
<figure class="pipeline-media pipeline-media--single">
<img src="/blog/sketch-to-3d-pipeline/kali-flat-book-art.png" alt="Illustrations originales à plat de Kali the Kitty pour Cubcoats" loading="lazy">
<figcaption><strong>Le point de départ.</strong> Les illustrations 2D originales de Kali tirées du livre. Chaleureuses, expressives et entièrement à plat.</figcaption>
</figure>
<p>Les proportions ont été préservées, la personnalité était bien présente et le rendu de dessin animé 3D tout en douceur a fourni à chaque outil en aval une référence cohérente. Nous avons ensuite affiné la pose et le cadrage exacts jusqu'à obtenir une référence principale verrouillée.</p>
<div class="pipeline-media pipeline-media--two">
<figure>
<img src="/blog/sketch-to-3d-pipeline/kali-3d-chatgpt.png" alt="Référence verrouillée du personnage Kali en style 3D" loading="lazy">
<figcaption><strong>Référence principale verrouillée.</strong> Rendu de dessin animé 3D en pied utilisé pour la vidéo et comme référence visuelle du maillage.</figcaption>
</figure>
<figure>
<img src="/blog/sketch-to-3d-pipeline/kali-3d-tpose.png" alt="Référence en T-pose pour la génération 3D et le rigging" loading="lazy">
<figcaption><strong>Planche en T-pose.</strong> Le même personnage, les bras écartés. Cette image a alimenté à la fois le [Générateur de modèles 3D](/fr/tools/hunyuan3d) et l'[outil de rigging automatique](/fr/tools/rigger).</figcaption>
</figure>
</div>
<p>La T-pose est importante, car le rigging exige que les bras soient éloignés du torse. Sans elle, l'outil de rigging automatique fusionne les bras avec le corps et abandonne. Nous avons généré la T-pose à partir de la même apparence verrouillée afin de préserver la silhouette et les proportions à chaque étape.</p>
<h2>Écrivez l'histoire avant de générer quoi que ce soit</h2>
<p>Avant de toucher aux outils vidéo, nous avons rédigé une trame narrative en langage naturel. Dans l'univers de Cubcoats, le trait de personnalité de Kali est « Positive ». C'est elle qui veille à ce que chacun se sente inclus et qui reste optimiste lorsque les choses deviennent difficiles. Nous avons donc construit un arc de dix secondes autour de cette idée :</p>
<blockquote>
<p>Kali entre de nuit dans une forêt sombre et brumeuse, tenant une petite lanterne lumineuse. Elle prend peur, s'assoit seule et manque d'abandonner. Puis elle découvre derrière elle un arbre doré magique qui s'illumine. Émerveillement, joie, pluie de particules dorées. De la peur à l'espoir en un souffle.</p>
</blockquote>
<p>Voilà toute l'intrigue. Elle n'a pas besoin d'être complexe. Elle doit faire tenir un seul basculement émotionnel dans un plan continu, afin que l'image de départ et l'image de fin puissent réellement s'enchaîner.</p>
<h2>Deux images qui encadrent toute l'histoire</h2>
<p>Nous avons généré deux images clés à partir de cette intrigue avec notre <a href="/fr/tools/flux">Générateur d'images</a>. Un format portrait pour correspondre à la courte vidéo verticale. Les prompts décrivent exactement le même personnage à deux moments différents.</p>
<p><strong>Prompt de l'image de départ :</strong></p>
<blockquote>
<p>Rendu d'animation 3D dans le style Pixar de Kali, un petit personnage de chat rose avec de grands yeux ronds et sombres, des oreilles pointues roses, un ventre rose clair et un visage rond et joyeux. Elle se tient à la lisière d'une forêt sombre et brumeuse, la nuit, tenant une petite lanterne lumineuse entre ses deux pattes. Ses oreilles sont légèrement rabattues et son expression est nerveuse, mais déterminée. Éclairage cinématographique avec une lumière de lune bleu froid venant d'en haut et la lueur orange et chaude de la lanterne. Brouillard dense entre les troncs sombres des arbres en arrière-plan. Angle de caméra : plan moyen, en légère contre-plongée. Aucun texte, aucun filigrane.</p>
</blockquote>
<p><strong>Prompt de l'image de fin :</strong></p>
<blockquote>
<p>Rendu d'animation 3D dans le style Pixar de Kali, un petit personnage de chat rose avec de grands yeux ronds et sombres, des oreilles pointues roses et un ventre rose clair. Elle se tient devant un immense arbre magique couvert de fleurs dorées lumineuses, les bras grands ouverts, affichant un immense sourire rayonnant. Des pétales dorés flottent dans l'air autour d'elle. L'arbre diffuse une chaude lumière dorée qui illumine toute la clairière. Un ciel nocturne étoilé est visible au-dessus. Angle de caméra : plan large en légère contre-plongée, composition de révélation épique. Aucun texte, aucun filigrane.</p>
</blockquote>
<div class="pipeline-media pipeline-media--two">
<figure>
<img src="/blog/sketch-to-3d-pipeline/kali-start-frame.png" alt="Image de départ de la vidéo verticale générée par IA" loading="lazy">
<figcaption><strong>Image de départ.</strong> Kali, nerveuse, à la lisière de la forêt avec sa lanterne.</figcaption>
</figure>
<figure>
<img src="/blog/sketch-to-3d-pipeline/kali-end-frame.png" alt="Image de fin de la vidéo verticale générée par IA" loading="lazy">
<figcaption><strong>Image de fin.</strong> Kali, rayonnante de joie devant l'arbre doré.</figcaption>
</figure>
</div>
<p>Seedance effectue une interpolation entre ces deux extrémités. La description du personnage reste identique dans les deux prompts afin que le modèle comprenne qu'il s'agit du même personnage. Seuls la scène, l'émotion et le cadrage changent.</p>
<h2>Le résultat vidéo a dépassé nos attentes</h2>
<p>Nous avons chargé les deux images dans le <a href="/fr/tools/video">Générateur de vidéos</a> de Cinevva, choisi Seedance 1.5 Pro, défini un format 9:16 et une durée de 15 secondes, saisi l'intrigue dans le prompt de mouvement, puis lancé la génération.</p>
<figure class="pipeline-media pipeline-media--single">
<img src="/blog/sketch-to-3d-pipeline/video-generator-setup.png" alt="Générateur de vidéos Cinevva avec les images de départ et de fin chargées" loading="lazy">
<figcaption><strong>La configuration réelle.</strong> Image de départ, image de fin, prompt de mouvement, format vertical 9:16, 15 secondes, son activé.</figcaption>
</figure>
<p>La séquence effectue une interpolation entre les deux images d'extrémité. L'audio natif a été généré au cours de la même passe. Voici des images échantillonnées le long de la timeline.</p>
<div class="pipeline-filmstrip" aria-label="Images extraites de la vidéo verticale générée">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_01.jpg" alt="Image vidéo 1" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_02.jpg" alt="Image vidéo 2" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_03.jpg" alt="Image vidéo 3" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_04.jpg" alt="Image vidéo 4" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_05.jpg" alt="Image vidéo 5" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_06.jpg" alt="Image vidéo 6" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_07.jpg" alt="Image vidéo 7" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_08.jpg" alt="Image vidéo 8" loading="lazy">
<img src="/blog/sketch-to-3d-pipeline/seedance-frames/frame_09.jpg" alt="Image vidéo 9" loading="lazy">
</div>
<p class="pipeline-caption">De gauche à droite : progression de la courte vidéo verticale générée.</p>
<p>Le mode image de départ plus image de fin était le bon choix lorsque nous voulions obtenir une pose finale précise. Pour des arcs émotionnels plus longs, une seule référence accompagnée d'un prompt a bien fonctionné avec le mode storyboard Kling 3.0 Pro du <a href="/fr/tools/video">Générateur de vidéos</a>. Des réglages différents pour des besoins différents.</p>
<h2>Le même personnage, désormais en 3D</h2>
<p>C'est ici que les choses deviennent intéressantes. L'image en T-pose issue de la même famille visuelle que les références vidéo a été envoyée à notre <a href="/fr/tools/hunyuan3d">Générateur de modèles 3D</a> pour une génération 3D à partir d'une image. Le but n'est pas de faire correspondre pixel par pixel le maillage du jeu à la vidéo. Il s'agit de correspondre au souvenir qu'en garde le joueur. Dans le jeu, le personnage doit donner l'impression d'avoir la même personnalité que dans la vidéo. Et comme nous avions verrouillé une identité visuelle unique dès le départ, c'était bien le cas.</p>
<figure class="pipeline-media pipeline-media--single">
<img src="/blog/sketch-to-3d-pipeline/kali-preview.png" alt="Aperçu du rendu du maillage Hunyuan3D" loading="lazy">
<figcaption>Aperçu du rendu du maillage généré. Textures et proportions avant le rigging.</figcaption>
</figure>
<p>Le <a href="/fr/tools/hunyuan3d">Générateur de modèles 3D</a> produit un fichier GLB texturé. Il n'est pas prêt à être intégré dans un jeu, mais Kali est immédiatement reconnaissable. La silhouette correspondait. Les matériaux étaient proches. C'était suffisant pour poursuivre.</p>
<h2>Faites-les pivoter</h2>
<p>Voici le résultat brut du <a href="/fr/tools/hunyuan3d">Générateur de modèles 3D</a>. Aucun rig, seulement un maillage texturé. À côté, le même personnage après rigging automatique, avec une animation de marche intégrée.</p>
<ClientOnly>
  <SketchPipelineModels />
</ClientOnly>
<h2>Rigging et animation</h2>
<p>Le <a href="/fr/tools/hunyuan3d">Générateur de modèles 3D</a> fournit un maillage. Pour que ce maillage puisse bouger, il lui faut un squelette et des animations. Notre plateforme prend automatiquement en charge le rigging : importez le GLB et récupérez un modèle riggé avec une bibliothèque complète d'animations. Cycles de marche, respiration au repos, sauts, tout ce dont un personnage de jeu a besoin.</p>
<p>L'animation de marche par défaut était presque correcte, mais pas tout à fait. Les bras de Kali restaient trop près de son corps et sa tête était inclinée selon un angle légèrement incorrect. Ces deux choix étaient logiques pour un humanoïde générique, mais ne convenaient pas à une petite chatte ronde de dessin animé. Nous avons donc ajouté des corrections d'os à l'exécution dans le code du jeu : un décalage des épaules qui repousse les bras vers l'extérieur et une correction de la rotation de la tête qui redresse son inclinaison. De petits ajustements pour une grande différence. Sans eux, elle semblait raide et robotique. Avec eux, elle ressemblait de nouveau à Kali.</p>
<h2>Jouer avec le personnage dans un navigateur</h2>
<p>Nous avons intégré le modèle riggé au <a href="/fr/engine">moteur Cinevva</a>. Caméra à la troisième personne, déplacements au clavier avec WASD, objets à collecter dispersés dans la scène et caméra orbitale à l'arrêt. Il a fallu plusieurs itérations pour ajuster la fusion des animations : fondu enchaîné entre l'immobilité et la marche, normalisation de la hauteur, correction de l'axe avant et réglage des décalages des os. La caméra à la troisième personne placée derrière le personnage boucle le parcours, du croquis jusqu'à un personnage que l'on peut déplacer.</p>
<p>Essayez vous-même. Utilisez WASD pour vous déplacer.</p>
<div class="pipeline-game-embed">
<iframe src="https://api.cinevva.com/games/game-mmbijtzp-d63d/" allow="fullscreen" loading="lazy"></iframe>
</div>
<h2>Ce que je conseillerais à quelqu'un qui se lancerait demain</h2>
<p>Verrouillez une apparence unique pour le personnage avant d'utiliser le moindre outil de génération. Chaque minute consacrée à perfectionner cette référence vous évitera une heure passée à rechercher la cohérence par la suite.</p>
<p>Commencez par écrire une intrigue très courte. Générez ensuite vos images de départ et de fin avec le <a href="/fr/tools/flux">Générateur d'images</a>. Veillez à ce que les images de référence respectent les limites de taille de l'API. Utilisez une T-pose issue du même système visuel lorsque vous alimentez le <a href="/fr/tools/hunyuan3d">Générateur de modèles 3D</a>.</p>
<p>Fournissez les deux images au <a href="/fr/tools/video">Générateur de vidéos</a> et laissez-le effectuer l'interpolation. Pour le maillage du jeu, riggez le modèle via la plateforme et ajustez les décalages des os à l'exécution si les animations par défaut ne correspondent pas tout à fait aux proportions de votre personnage.</p>
<p>Il y a deux ans, ce pipeline n'existait pas. Il fallait une équipe et un budget. Aujourd'hui, il suffit d'un croquis et des <a href="/fr/tools/">outils créatifs</a> de Cinevva. Je pense que cela change profondément qui peut donner vie à un personnage.</p>
<hr>
<p><em>Outils Cinevva utilisés : <a href="/fr/tools/flux">Générateur d'images</a>, <a href="/fr/tools/video">Générateur de vidéos</a>, <a href="/fr/tools/hunyuan3d">Générateur de modèles 3D</a>, rigging automatique et <a href="/fr/engine">moteur Cinevva</a>. État des outils au début de l'année 2026.</em></p>
<style>
.pipeline-media { margin: 1.25rem 0; }
.pipeline-media figure { margin: 0; }
.pipeline-media img {
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.08);
  display: block;
}
.pipeline-media figcaption {
  margin-top: 0.5rem;
  font-size: 0.9rem;
  color: var(--vp-c-text-2);
  line-height: 1.45;
}
.pipeline-media--two {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}
@media (min-width: 768px) {
  .pipeline-media--two {
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
  }
}
.pipeline-media--single { margin: 1.25rem 0; }
.pipeline-filmstrip {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 12px 4px;
  margin: 1rem 0;
  border-radius: 10px;
  background: var(--vp-c-bg-soft);
  border: 1px solid rgba(255,255,255,0.06);
  -webkit-overflow-scrolling: touch;
}
.pipeline-filmstrip img {
  flex: 0 0 auto;
  height: 180px;
  width: auto;
  border-radius: 6px;
  object-fit: cover;
}
@media (min-width: 900px) {
  .pipeline-filmstrip img { height: 220px; }
}
.pipeline-caption {
  font-size: 0.85rem;
  color: var(--vp-c-text-2);
  margin: 0.35rem 0 1rem;
}
.pipeline-game-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16/9;
  margin: 1.25rem 0;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid rgba(255,255,255,0.08);
}
.pipeline-game-embed iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: none;
}
</style>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer un monde ouvert dans le navigateur, partie 1 : nous avons commencé par essayer de le faire céder]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-01-risk-first</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-01-risk-first</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Avant de créer des grottes ou des shaders sophistiqués, nous devions répondre à une question embarrassante : est-ce seulement possible de faire tourner tout cela dans un navigateur ?]]></description>
            <content:encoded><![CDATA[<h1>Créer un monde ouvert dans le navigateur, partie 1 : nous avons commencé par essayer de le faire céder</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un prototype exploratoire et contient des liens vers toutes les parties.</p>
<p>Nous créons un monde ouvert multijoueur qui fonctionne entièrement dans le navigateur. Aucune installation, aucune boutique d'applications, juste une URL. Dès le départ, le principal risque était évident : un navigateur peut-il seulement afficher un monde 3D persistant à une fréquence d'images jouable, tout en conservant une marge suffisante pour le gameplay, la physique et le réseau ?</p>
<p>La plupart des projets de mondes ouverts échouent selon un ordre prévisible. On commence par avoir un bon concept. Puis une jolie scène. Et l'on finit par constater que tout le budget par image est déjà consommé avant même que le gameplay n'existe.</p>
<p>Nous voulions répondre à la question du budget de rendu avant d'investir dans quoi que ce soit d'autre. Le prototype exploratoire 1 a donc laissé de côté la jolie bande-annonce pour passer directement aux mesures.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/01-terrain/" title="Prototype exploratoire 1 : terrain et instanciation" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/01-terrain/" target="_blank">Ouvrir le prototype exploratoire 1 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=01-terrain">Voir le code source</a></p>
<p>La configuration était volontairement simple. Un maillage de terrain de 512 mètres, un relief procédural obtenu à partir de plusieurs couches de bruit sinusoïdal avec une atténuation vers les bords de l'île, un plan d'eau, un brouillard atmosphérique et 500 objets instanciés. Nous avons utilisé WebGL directement avec Three.js, le tone mapping ACES et aucune ombre.</p>
<p>L'apparence nous importait peu. Ce qui comptait, c'était que la scène reste stable lorsque la caméra s'y déplaçait.</p>
<p>Deux conclusions tirées de ce prototype exploratoire ont façonné l'ensemble du projet.</p>
<p>Premièrement, nous avons confirmé que nous disposions d'une véritable marge de performances sur ordinateur, à condition de rester rigoureux lors de la première passe. Cela nous a donné la confiance nécessaire pour nous attaquer ensuite à une architecture de terrain plus complexe.</p>
<p>Deuxièmement, nous avons établi un contrat de référence. Chaque prototype exploratoire suivant devait justifier son coût par rapport à cette scène. Si une nouvelle fonctionnalité était séduisante mais trop coûteuse, elle n'était pas retenue.</p>
<p>Cette rigueur autour de la référence est devenue cruciale par la suite, lorsque nous avons rencontré des artefacts aux raccords, des transitions entre différents niveaux de détail et une génération de maillage pilotée par des shaders de calcul. Sans référence stable, chaque bug paraît plus important qu'il ne l'est réellement.</p>
<p>Dans la partie 2, nous passons du rendu aux sensations de contrôle. La physique exécutée dans un worker paraît excellente dans les documents d'architecture. Mais cela n'a d'intérêt que si le personnage réagit toujours instantanément lorsque vous appuyez sur une touche.</p>
<h2>Technologies mentionnées dans ce chapitre</h2>
<p><strong>Terrain à carte de hauteurs.</strong> Une grille 2D dans laquelle chaque cellule stocke une seule valeur d'altitude. Le GPU déplace un maillage plat dans le shader de sommets pour créer la surface du terrain. Les cartes de hauteurs sont compactes (une zone de 65x65 occupe environ 8 Ko en 16 bits), adaptées au GPU et rapides à afficher. Leur limite est qu'elles ne peuvent pas représenter les grottes, les surplombs ni aucune surface qui se replie sur elle-même. Pour en savoir plus sur les limites des cartes de hauteurs et les solutions qui leur succèdent, consultez notre <a href="/fr/guides/landscape-generation-browser#why-heightmaps-arent-enough">guide de génération de paysages</a>.</p>
<p><strong>Three.js.</strong> La bibliothèque de rendu que nous avons utilisée tout au long de ce projet. Three.js encapsule WebGL 2 (puis WebGPU) dans un graphe de scène comprenant des caméras, des lumières, des matériaux et des objets géométriques. Elle fournit <code>InstancedMesh</code> pour afficher de nombreux exemplaires d'une même géométrie en un seul appel de rendu, ainsi que l'élimination selon le frustum, des matériaux PBR et des effets de post-traitement. Consultez <a href="https://github.com/mrdoob/three.js">Three.js sur GitHub</a>. Pour comprendre la place de Three.js dans l'architecture d'un monde ouvert sur navigateur, consultez notre <a href="/fr/guides/browser-3d-open-world-tech#three-js">guide des technologies 3D pour navigateur</a>.</p>
<p><strong>InstancedMesh.</strong> Une fonctionnalité de Three.js qui affiche N exemplaires d'une même géométrie en un seul appel de rendu, chacun avec une position, une rotation et une échelle différentes. Les transformations propres à chaque instance sont stockées dans un tampon d'attributs matriciels. C'est ainsi que nous avons affiché 500 objets dans le prototype exploratoire 1 sans effectuer 500 appels de rendu distincts. Pour gérer la végétation à grande échelle, l'élimination des instances pilotée par le GPU pousse ce principe encore plus loin. Consultez notre <a href="/fr/guides/landscape-generation-browser#gpu-driven-vegetation-culling">guide sur l'élimination de la végétation par le GPU</a>.</p>
<p><strong>Budget par image.</strong> À 60 images par seconde, chaque image dispose de $\frac{1000\ \text{ms}}{60} \approx 16.7$ ms pour tout exécuter : la logique JavaScript, la physique, le rendu et la composition. Un prototype exploratoire de « vérification du budget » mesure la part de ce temps consommée par une scène de référence. La marge restante pour tout ce que vous ajouterez ensuite est donc $t_\text{features} = 16.7 - t_\text{baseline}$. Si la scène de référence consomme déjà 12 ms, il ne reste qu'environ 4,7 ms pour le gameplay, la physique et le réseau réunis. Cette approche vient du développement de mondes ouverts AAA, où <a href="/fr/guides/browser-3d-open-world-tech#what-we-can-learn-from-skyrim-and-the-witcher">GTA V, Skyrim et Elden Ring</a> utilisent tous des niveaux de détail et du chargement en continu très agressifs pour respecter des budgets fixes par image.</p>
<hr>
<p>Partie 1 sur 12.<br>
À suivre : <a href="/fr/blog/2026-02-25-open-world-browser-part-02-worker-physics">Partie 2 — La physique dans un worker et la crainte de la latence des commandes</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 2 : la physique dans un Worker et la crainte de la latence des commandes]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-02-worker-physics</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-02-worker-physics</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Nous avons déplacé Rapier dans un Web Worker et mesuré ce qui inquiète tout le monde en premier : les mouvements semblent-ils retardés ?]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 2 : la physique dans un Worker et la crainte de la latence des commandes</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et contient les liens vers toutes les parties.</p>
<p>Quand on développe des jeux multijoueurs dans le navigateur depuis assez longtemps, on finit toujours par entendre ce débat.</p>
<p>« La physique dans un Worker offre une architecture propre. La physique sur le thread principal semble plus sûre. »</p>
<p>Les deux peuvent être vrais. Ce qui compte, ce sont les sensations de contrôle et la latence avec de vraies commandes.</p>
<p>Le spike 2 a été conçu pour répondre à cette question avec des mesures, pas des opinions.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/02-rapier-worker/" title="Spike 2 : Rapier dans un Worker" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/02-rapier-worker/" target="_blank">Ouvrir le spike 2 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=02-rapier-worker">Voir le code source</a></p>
<p>Nous avons réutilisé le terrain du spike 1 et intégré Rapier dans un Worker de module dédié. L’état des commandes était envoyé au Worker à chaque frame, la simulation y était exécutée, puis la position faisant autorité était renvoyée au moteur de rendu.</p>
<p>Les métriques clés étaient la latence entre la commande et le mouvement visible, ainsi que la durée des étapes de simulation physique. Nous avons également surveillé les saccades pendant les déplacements normaux, les accélérations en sprint et les enchaînements de sauts.</p>
<p>Le résultat a dépassé nos attentes. Avec la structure et la cadence de nos messages, la frontière du Worker n’était pas le principal facteur de latence. Les commandes semblaient toujours immédiates, et c’était la seule chose qui comptait pour les joueurs.</p>
<p>L’une des difficultés de cette phase concernait le risque de mauvaise interprétation. Après un résultat concluant, les équipes ont souvent tendance à généraliser à l’excès et à considérer que la question architecturale est définitivement réglée. Ce n’est pas le cas. Nous n’avons validé qu’un scénario concret et qu’un profil matériel. Les spikes suivants devaient encore réévaluer nos hypothèses lorsque la charge du GPU et du streaming a augmenté.</p>
<p>Ce spike nous a également permis d’améliorer notre processus. Nous avons commencé à afficher par défaut la télémétrie des temps d’exécution dans le HUD de nos spikes interactifs. Les discussions de l’équipe sont alors passées de « il y a quelque chose qui cloche » à « ce chemin a ajouté 1,2 ms ».</p>
<p>Dans la partie 3, nous abordons les expériences moins spectaculaires qui nous ont évité de coûteuses surprises par la suite : charge liée à la diffusion, contraintes mobiles et fiabilité de la génération des comportements.</p>
<h2>Technologies mentionnées dans ce chapitre</h2>
<p><strong>Rapier.</strong> Un moteur physique écrit en Rust qui se compile en WebAssembly pour être utilisé dans le navigateur. Il gère les corps rigides, les colliders, les articulations, les contrôleurs de personnage et le lancer de rayons avec des performances 2 à 3 fois inférieures à celles du code natif. Pour les mondes ouverts, Rapier fournit des contrôleurs de personnage joueur — déplacement sur le terrain, franchissement de marches et glissement sur les pentes —, la collision des objets, le lancer de rayons pour les interactions et les volumes déclencheurs. Consultez la <a href="https://rapier.rs/">documentation de Rapier</a> et notre <a href="/fr/guides/browser-3d-open-world-tech#rapier-rust-wasm">guide des technologies 3D pour navigateur consacré à la physique</a>.</p>
<p><strong>Web Workers.</strong> Des threads de navigateur qui exécutent du JavaScript — ou du Wasm — en dehors du thread principal. Exécuter la simulation physique dans un Worker signifie qu’un appel lourd à <code>world.step()</code> ne bloque pas le rendu. À chaque frame, le thread principal envoie l’état des commandes au Worker via <code>postMessage</code>, puis reçoit en retour les positions faisant autorité. Le coût en latence correspond aux deux trajets des messages — environ 0,1 à 0,5 ms chacun sur ordinateur. L’avantage est que le thread de rendu n’est jamais bloqué par la détection des collisions. Les objets transférables — transfert d’<code>ArrayBuffer</code> — éliminent le coût de copie des grands tableaux de positions.</p>
<p><strong>WebAssembly (Wasm).</strong> Un format d’instructions binaire qui s’exécute dans les navigateurs à une vitesse proche du code natif. Rapier, Havok et Recast se compilent tous en Wasm. Avec Rapier-Wasm, une étape de simulation physique prend généralement entre 0,5 et 2 ms pour quelques centaines de corps, contre 5 à 15 ms pour un équivalent en JavaScript. Les modules Wasm sont chargés sous forme de fichiers <code>.wasm</code> récupérés avec le code de liaison JavaScript. Consultez la <a href="https://webassembly.org/">spécification WebAssembly</a>.</p>
<p><strong>Latence entre la commande et l’affichage.</strong> Le délai entre l’appui sur une touche et le changement visuel qui en résulte à l’écran. Pour qu’un mouvement semble « immédiat », ce délai doit rester inférieur à environ 80 ms. Dans une configuration où la physique s’exécute dans un Worker, les délais de la chaîne s’additionnent :</p>
<p>$$
L = t_\text{input} + 2,t_\text{msg} + t_\text{step} + t_\text{render} + t_\text{vsync}
$$</p>
<p>Événement keydown sur le thread principal, <code>postMessage</code> envoyé au Worker puis renvoyé ($2,t_\text{msg}$), étape de simulation physique, application de la nouvelle position par le moteur de rendu, puis synchronisation verticale suivante. Chaque terme est faible pris isolément — environ 0,1 à 0,5 ms par trajet de message sur ordinateur —, mais ils s’additionnent. C’est pourquoi nous avons mesuré $L$ directement au lieu de nous fier au schéma d’architecture.</p>
<hr>
<p>Partie 2 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-01-risk-first">Partie 1 — Nous avons commencé par essayer de tout casser</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-03-the-unflashy-spikes">Partie 3 — Les spikes peu spectaculaires qui nous ont sauvés</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer un monde ouvert dans le navigateur, partie 3 : les prototypes peu spectaculaires qui nous ont sauvés]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-03-the-unflashy-spikes</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-03-the-unflashy-spikes</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[La charge de diffusion des Durable Objects, les contraintes de qualité sur mobile et la fiabilité de la génération de comportements n'avaient rien de spectaculaire, mais elles nous ont évité de coûteuses surprises.]]></description>
            <content:encoded><![CDATA[<h1>Créer un monde ouvert dans le navigateur, partie 3 : les prototypes peu spectaculaires qui nous ont sauvés</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un prototype exploratoire et donne accès à toutes les parties.</p>
<p>Cette partie comporte moins de captures d'écran spectaculaires et davantage de garanties architecturales.</p>
<p>Après les prototypes 1 et 2, nous avons mené trois vérifications de risques qui semblaient modestes, mais dont l'impact sur le produit était majeur.</p>
<p>La première concernait la diffusion à grande échelle avec les Durable Objects. Nous avons testé la distribution des positions entre plusieurs clients à des fréquences de tick comparables à celles d'un jeu, tout en mesurant la distribution de la latence, l'utilisation du processeur par tick et l'intégrité des livraisons. En cas d'échec, nous aurions adopté très tôt une architecture partitionnée plutôt que de confier chaque île à une seule instance.</p>
<p>La deuxième portait sur la validation des contraintes mobiles. Il ne s'agissait pas d'un préréglage pour ordinateur simplement rebaptisé « mobile », mais d'un profil explicitement peu coûteux, dérivé du même terrain de référence.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/01-terrain/?quality=mobile" title="Profil de qualité mobile basé sur le prototype 1" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/01-terrain/?quality=mobile" target="_blank">Ouvrir le profil mobile dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=01-terrain">Voir le code source</a></p>
<p>Nous avons réduit la densité des segments, le nombre d'objets, la pression liée à la résolution de rendu et la portée du brouillard. La question était simple. Ce monde peut-il rester lisible et réactif malgré les contraintes d'un appareil mobile, sans devoir réécrire tout le moteur de rendu ?</p>
<p>La troisième vérification concernait la fiabilité de la génération de comportements dans les workflows des créateurs. Nous avons évalué le taux de JSON valide, la justesse sémantique par rapport aux primitives attendues et la latence des réponses. En cas d'échec, nous aurions adopté une création de comportements strictement fondée sur des formulaires.</p>
<p>L'enseignement essentiel de ce chapitre est que ces prototypes peu spectaculaires ont fait évoluer l'architecture plus rapidement que les prototypes visuels. Ils ont fixé des limites strictes pour la topologie réseau, les promesses sur mobile et l'expérience utilisateur des outils.</p>
<p>Dans la partie 4, nous revenons au travail visible sur le terrain et testons le comportement du streaming pendant les déplacements, plutôt que de nous limiter à des captures d'écran de chargement statique.</p>
<h2>Technologies mentionnées dans ce chapitre</h2>
<p><strong>Cloudflare Durable Objects.</strong> Instances serverless avec état, déployées en périphérie du réseau et dotées d'une persistance intégrée ainsi que de la prise en charge des WebSockets. Chaque Durable Object conserve l'état de référence d'une partition du monde (ou d'un chunk). Les joueurs se connectent par WebSocket et reçoivent les positions diffusées par les autres joueurs présents dans la même partition. Lorsqu'un joueur se déplace vers un chunk adjacent, il se connecte au Durable Object de ce chunk. Les Durable Objects enregistrent automatiquement l'état sur disque et peuvent évoluer jusqu'à des milliers d'instances simultanées. Consultez la <a href="https://developers.cloudflare.com/durable-objects/">documentation de Cloudflare sur les Durable Objects</a> ainsi que notre <a href="/fr/guides/browser-3d-open-world-tech#multiplayer-networking">guide des technologies 3D dans le navigateur consacré au réseau multijoueur</a>.</p>
<p><strong>Partitionnement spatial.</strong> Répartition du monde entre plusieurs instances de serveur selon les régions géographiques. Chaque partition gère une zone rectangulaire de la grille du monde. Lorsque la densité de joueurs évolue, les partitions peuvent se diviser ou fusionner. Les joueurs proches de la limite d'une partition voient le contenu des deux partitions grâce à des requêtes de visibilité interpartitions. C'est ainsi qu'<a href="/fr/guides/browser-3d-open-world-tech#server-architecture">EVE Online gère des milliers de joueurs</a> dans un même univers.</p>
<p><strong>Diffusion WebSocket en éventail.</strong> Distribution en temps réel des mises à jour de position depuis un serveur vers de nombreux clients connectés. La bande passante descendante par client augmente de façon linéaire avec le nombre de joueurs visibles :</p>
<p>$$
B_\text{client} = N_\text{visible} \times b_\text{player}
$$</p>
<p>À des fréquences de tick comparables à celles d'un jeu (20 à 30 Hz), chaque joueur génère environ $b_\text{player} \approx 800$ octets de données de position par seconde. Afficher 200 joueurs coûte donc $200 \times 800 = 160$ Ko/s par client. Le coût serveur d'une approche naïve où chacun communique avec tous les autres est encore pire, puisqu'il augmente comme $N^2$. C'est précisément pourquoi le filtrage par pertinence (gestion spatiale des zones d'intérêt) plafonne $N_\text{visible}$ avant l'envoi vers chaque connexion, tout en respectant le budget temporel du tick. Consultez notre <a href="/fr/guides/browser-3d-open-world-tech#client-server-communication">guide du réseau</a> pour découvrir les compromis entre compression différentielle et fréquence de mise à jour.</p>
<p><strong>Contraintes du rendu sur mobile.</strong> Les GPU mobiles offrent un débit 5 à 10 fois inférieur à celui des GPU pour ordinateur, disposent d'une limite de mémoire d'environ 1 Go (contre 2 à 4 Go sur ordinateur) et réduisent leurs performances thermiques lors d'une charge prolongée. Un profil de qualité mobile diminue la densité des segments, le nombre d'objets, la résolution de rendu, la distance d'affichage et la qualité des ombres. L'objectif n'est pas d'atteindre la parité avec les ordinateurs, mais de préserver la lisibilité et la réactivité. Consultez les <a href="/fr/guides/browser-3d-open-world-tech#browser-3d-performance-real-numbers">chiffres de performance réels de la 3D dans le navigateur</a> pour voir de véritables benchmarks GPU.</p>
<hr>
<p>Partie 3 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-02-worker-physics">Partie 2 — La physique dans un Worker et la crainte de la latence des commandes</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy">Partie 4 — Le streaming avant les terrains sophistiqués</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 4 : le streaming avant les terrains sophistiqués]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Nous avons d'abord testé le chargement et le remplacement des chunks avec du contenu simple, avant de passer au raffinement progressif des heightmaps. Cet ordre s'est révélé payant.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 4 : le streaming avant les terrains sophistiqués</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et contient des liens vers toutes les parties.</p>
<p>C'est généralement au niveau du streaming que les projets qui « ont l'air bien » s'effondrent.</p>
<p>On peut masquer beaucoup de choses dans une image fixe. Impossible, en revanche, de dissimuler un blocage de 40 ms lors du franchissement de la limite d'un chunk.</p>
<p>Nous avons délibérément testé le streaming avant de construire une représentation avancée du terrain. Nous avons ainsi obtenu des données claires sur le comportement du chargement et du déchargement.</p>
<p>Le Spike 6 a validé le renouvellement des chunks voisins avec du contenu simple.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/06-chunk-streaming/" title="Spike 6 : chargement et remplacement des chunks" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/06-chunk-streaming/" target="_blank">Ouvrir le Spike 6 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=06-chunk-streaming">Voir le code source</a></p>
<p>Nous sommes ensuite passés au véritable pipeline de terrain avec le Spike 11 : streaming des chunks d'altitude, décodage côté worker et raffinement progressif, avec des grilles de 17, puis 33 et enfin 65 échantillons.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/11-chunk-streaming/" title="Spike 11 : streaming des chunks de heightmap" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/11-chunk-streaming/" target="_blank">Ouvrir le Spike 11 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=11-chunk-streaming">Voir le code source</a></p>
<p>L'ordre des étapes a eu plus d'importance que nous ne l'avions prévu. Si nous avions commencé directement avec des chunks d'altitude compressés, l'origine de chaque blocage aurait été ambiguë : problème de décodage, de transfert des textures ou de mise à jour de la géométrie. Le Spike 6 a supprimé une couche d'incertitude avant que le Spike 11 n'ajoute de la complexité.</p>
<p>Une leçon pratique tirée de ce chapitre nous a accompagnés dans les spikes suivants. Les blocages causés par les transferts doivent être mesurés directement, et non déduits du nombre moyen d'images par seconde. La moyenne des FPS masque les pics de temps de rendu, alors que ce sont précisément ces pics que les utilisateurs ressentent.</p>
<p>Dans la partie 5, nous abordons le coût visuel, lorsque la végétation, les shaders de terrain et les ombres en cascade se disputent le même budget de rendu par image.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Streaming par chunks.</strong> Le monde est divisé en une grille de chunks indépendants, généralement de 64 × 64 mètres. À mesure que le joueur se déplace, les chunks situés à l'arrière sont déchargés tandis que ceux situés à l'avant sont chargés en streaming. C'est ainsi que fonctionne le <a href="/fr/guides/browser-3d-open-world-tech#skyrim-s-cell-system">système de cellules de Skyrim</a> : une grille de 5 × 5 cellules est chargée autour du joueur et se renouvelle au fil de ses déplacements. Dans le navigateur, la latence réseau vient s'ajouter à l'équation, ce qui rend crucial le préchargement prédictif fondé sur la vitesse du joueur. Consultez notre <a href="/fr/guides/landscape-generation-browser#streaming-architecture-for-terrain">guide de l'architecture de streaming</a>.</p>
<p><strong>Raffinement progressif des heightmaps.</strong> Le terrain est d'abord envoyé en basse résolution, puis progressivement affiné. Les dimensions des grilles ne sont pas arbitraires : chaque niveau utilise une grille de $(2^k + 1) \times (2^k + 1)$, si bien que $17 = 2^4 + 1$, $33 = 2^5 + 1$ et $65 = 2^6 + 1$. Le $+1$ conserve un échantillon commun à chaque limite afin d'aligner les chunks voisins, et chaque étape multiplie approximativement par quatre le nombre d'échantillons ($n^2$ augmente lorsque le côté double). Une grille de 17 × 17 — le minimum pour un chunk de 64 m avec un espacement de 4 m — pèse environ 200 octets une fois compressée et permet d'afficher instantanément une surface visible. On charge ensuite en streaming le raffinement en 33 × 33, puis la résolution complète en 65 × 65. Chaque niveau ajoute des échantillons sans remplacer les données précédentes. Cette approche correspond directement aux anneaux de LOD des clipmaps géométriques, dans lesquels le terrain lointain utilise des données en basse résolution et le terrain proche la résolution complète. Consultez le <a href="/fr/guides/landscape-generation-browser#progressive-chunk-loading">chargement progressif des chunks</a>.</p>
<p><strong>Encodage différentiel et compression.</strong> Les données des heightmaps se compressent bien, car les cellules adjacentes présentent des valeurs similaires. L'encodage différentiel stocke la différence entre chaque cellule et sa valeur prédite — la moyenne de ses voisines —, ce qui regroupe les valeurs autour de zéro. Associé à zlib ou brotli, il fait passer un chunk de 65 × 65 de 8,4 Ko de données brutes à 1–2 Ko compressés. Avec une précision réduite pour les chunks éloignés — 8 bits au lieu de 16 bits —, la taille tombe à 0,5–1 Ko. Consultez la <a href="/fr/guides/landscape-generation-browser#terrain-data-compression-for-streaming">compression des données de terrain</a>.</p>
<p><strong>Préchargement prédictif.</strong> Il consiste à charger les chunks avant l'arrivée du joueur. La distance d'anticipation doit couvrir la distance parcourue par le joueur pendant le chargement d'un chunk, $d_\text{prefetch} = v \cdot t_\text{load}$ ; elle varie donc avec la vitesse : à la vitesse de marche (5 km/h), on précharge 2 chunks en avant (128 m), contre 4 à la vitesse de course (15 km/h). L'anneau de chargement se décale selon la direction de la vitesse. Une file de priorité classe les requêtes en attente selon leur urgence et annule celles concernant les chunks dont le joueur s'est éloigné. Consultez le <a href="/fr/guides/landscape-generation-browser#predictive-pre-fetching">préchargement prédictif</a>.</p>
<hr>
<p>Partie 4 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-03-the-unflashy-spikes">Partie 3 — Les spikes peu spectaculaires qui nous ont sauvés</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-05-budgeting-the-pretty">Partie 5 — Budgétiser les éléments visuels</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 5 : budgétiser les éléments visuels]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-05-budgeting-the-pretty</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-05-budgeting-the-pretty</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[La végétation, les matériaux du terrain et les ombres en cascade étaient superbes. Le véritable défi consistait à prouver qu’ils pouvaient tenir dans le budget d’une frame.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 5 : budgétiser les éléments visuels</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, directeur technique et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et répertorie toutes les parties.</p>
<p>C’est dans ce chapitre que l’ambition visuelle s’est heurtée à l’arithmétique.</p>
<p>Nous avons réparti le coût du rendu entre plusieurs spikes distincts, car les résultats groupés sont difficiles à diagnostiquer. Si vous activez tout en même temps, vous apprenez seulement que la frame est lente. Vous ne savez pas quelle fonctionnalité a englouti le budget.</p>
<p>Le spike 7 ciblait la densité de la végétation et le coût de son animation. L’approche reposait sur une répartition à l’exécution à partir de cartes de densité de 32x32 par chunk de terrain, alimentant de grands ensembles d’<code>InstancedMesh</code>. Chaque brin d’herbe et groupe d’arbustes recevait une animation de vent dans le vertex shader, pilotée par une texture de bruit défilante. La métrique essentielle que nous surveillions n’était pas le nombre de triangles, mais le surcoût des draw calls et le débit de sommets sur les GPU de milieu de gamme. Nous avons constaté que regrouper les instances dans un nombre réduit de meshes comptait davantage que de diminuer le nombre de polygones de chaque brin.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/07-gpu-vegetation/" title="Spike 7 : végétation sur GPU" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/07-gpu-vegetation/" target="_blank">Ouvrir le spike 7 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=07-gpu-vegetation">Voir le code source</a></p>
<p>Le spike 8 poussait plus loin la complexité des matériaux du terrain. Mélange multicouche pondéré selon l’angle de la pente et l’altitude, projection triplanaire facultative pour les parois rocheuses et normal maps propres à chaque couche. Le shader effectuait un splatting basé sur la pente avec quatre couches de textures, chacune nécessitant un échantillon diffus et un échantillon de normale. Cela représente $4 \times 2 = 8$ lectures de texture par fragment avant même d’ajouter l’éclairage, et ce nombre augmente linéairement avec les couches : une cinquième couche implique donc 10 lectures pour chaque pixel. Nous avons effectué nos mesures spécifiquement sur des GPU Intel intégrés afin de déterminer la configuration minimale. Conclusion : la projection triplanaire sur les surfaces verticales valait son coût, mais pas l’ajout d’une cinquième couche de splatting.</p>
<p><a href="/fr/spikes/08-terrain-material/" target="_blank">Ouvrir le spike 8 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=08-terrain-material">Voir le code source</a></p>
<p>Le spike 9 portait sur le coût des cascaded shadow maps avec une charge réaliste de terrain et d’objets. Une CSM à trois cascades constituait notre référence. Nous avons effectué les tests avec un soleil bas sur l’horizon, car c’est dans cette situation que la pression sur les cascades est la plus forte. La cascade lointaine couvre une immense tranche du frustum, et le niveau de détail des ombres dépend de la densité de texels, $\rho \approx \frac{R^2}{A}$ (une shadow map de résolution $R$ étirée sur une surface au sol $A$). Avec une seule map couvrant toute la distance d’affichage, $A$ devient énorme et $\rho$ s’effondre. Les cascades résolvent ce problème en divisant le frustum afin que chaque tranche dispose de sa propre map de $R \times R$ sur une petite surface $A$, ce qui maintient $\rho$ à peu près constant du premier plan jusqu’à l’horizon. Nous avons mesuré la différence de temps GPU entre deux et quatre cascades, puis entre des shadow maps de résolution 1024 et 2048. Résultat : trois cascades en 1024 nous procuraient des ombres de contact acceptables près de la caméra sans dépasser 2 ms de temps GPU sur notre matériel cible.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/09-csm-shadows/" title="Spike 9 : ombres CSM" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/09-csm-shadows/" target="_blank">Ouvrir le spike 9 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=09-csm-shadows">Voir le code source</a></p>
<p>La difficulté de cette phase résidait dans la discipline produit. Certains effets étaient superbes, mais devaient malgré tout être limités, car ils consommaient une part trop importante du budget d’une frame par rapport à leur impact visuel.</p>
<p>Notre règle est devenue simple. Une fonctionnalité ne passe à l’étape suivante que si son coût peut être justifié par des mesures du temps de frame.</p>
<p>Cela paraît évident. Pourtant, cette pratique est peu courante pendant les cycles de prototypage rapide, lorsque tout le monde s’enthousiasme pour la prochaine amélioration visuelle. L’application précoce de cette règle a ensuite beaucoup simplifié les décisions d’architecture concernant les clipmaps et les zones volumétriques, car nous connaissions déjà le coût individuel de chaque fonctionnalité en concurrence pour les mêmes 16 ms.</p>
<p>Dans la partie 6, nous abordons le premier grand changement d’architecture du terrain avec les geometry clipmaps.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>InstancedMesh et végétation sur GPU.</strong> L’<code>InstancedMesh</code> de Three.js affiche N exemplaires d’une même géométrie avec un seul draw call. Pour la végétation, une carte de densité (32x32 par chunk) pilote à l’exécution la répartition de brins d’herbe et de groupes d’arbustes dans des buffers d’instances. L’animation du vent s’exécute dans le vertex shader à l’aide d’une texture de bruit défilante. À grande échelle, le <code>ComputeInstanceCulling</code> de WebGPU élimine les instances hors écran et lointaines avant la rastérisation, tandis qu’<code>IndirectBatchedMesh</code> rassemble plusieurs types de végétation dans un buffer unique affiché avec du multi-draw indirect. Consultez notre <a href="/fr/guides/landscape-generation-browser#gpu-driven-vegetation-culling">guide de création de paysages sur le culling GPU de la végétation</a>.</p>
<p><strong>Mapping triplanaire.</strong> Les textures utilisant un mapping UV classique s’étirent sur les pentes raides, car les coordonnées UV se compriment. Le mapping triplanaire projette les textures le long des trois axes (X, Y, Z) et les mélange en fonction de la normale de la surface. Les parois rocheuses reçoivent la projection X ou Z, sans étirement, tandis que les terrains plats reçoivent la projection Y. Le mélange est fluide et automatique, sans nécessiter de dépliage UV. Pour un terrain PBR, les mêmes pondérations de mélange s’appliquent aux canaux d’albédo, de normale, de rugosité et d’occlusion ambiante. Consultez les <a href="/fr/guides/landscape-generation-browser#triplanar-mapping">détails du mapping triplanaire</a>.</p>
<p><strong>Splatting des matériaux selon la pente et l’altitude.</strong> Au lieu d’utiliser des splat maps peintes à la main, les matériaux sont attribués de manière procédurale dans le fragment shader selon les propriétés du terrain. Les terrains plats à basse altitude reçoivent de l’herbe, les pentes raides de la roche, les hautes altitudes de la neige — uniquement sur les surfaces assez plates pour qu’elle puisse s’accumuler — et les zones proches du niveau de la mer du sable. Les transitions utilisent <code>smoothstep</code> afin d’obtenir un mélange progressif. Dans notre implémentation, chaque chunk de terrain évalue quatre couches de textures, avec un échantillon diffus et un échantillon de normale par couche, soit huit lectures de texture par fragment avant l’éclairage. Consultez l’<a href="/fr/guides/landscape-generation-browser#slope-and-altitude-based-material-assignment">attribution des matériaux selon la pente et l’altitude</a>.</p>
<p><strong>Cascaded Shadow Maps (CSM).</strong> La CSM divise le frustum de la caméra en 3 à 4 plages de distance, appelées cascades. Chaque cascade génère une shadow map depuis le point de vue du soleil, à une résolution adaptée à sa distance. Les cascades proches bénéficient d’ombres en haute résolution — notamment des ombres de contact détaillées sous les arbres et les bâtiments — tandis que les cascades lointaines utilisent une résolution inférieure pour les grandes ombres des montagnes. Le shader du terrain échantillonne toutes les cascades et sélectionne celle qui convient à chaque fragment. Coût en performances : 3 à 4 cascades en 1024x1024 ajoutent environ 0,5 à 1 ms pour le rendu des shadow maps, plus environ 0,2 à 0,3 ms pour leur échantillonnage. Consultez la section sur les <a href="/fr/guides/landscape-generation-browser#shadows-for-terrain">ombres du terrain</a>.</p>
<hr>
<p>Partie 5 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy">Partie 4 — Le streaming avant les terrains sophistiqués</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-06-clipmaps">Partie 6 — Les clipmaps ont changé la donne</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 6 : les clipmaps ont changé la donne]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-06-clipmaps</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-06-clipmaps</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Les clipmaps géométriques nous ont permis de maintenir un coût prévisible pour le terrain tout en parcourant un monde bien plus vaste.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 6 : les clipmaps ont changé la donne</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, directeur technique et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un prototype exploratoire et renvoie vers toutes les parties.</p>
<p>Avant le Spike 10, notre modèle mental était encore « un monde plus vaste implique davantage de géométrie ». Après le Spike 10, il est devenu « un budget géométrique constant, avec des mises à jour d'anneaux centrées sur la caméra ». Ce changement a transformé la trajectoire du projet.</p>
<p>Le principe des clipmaps géométriques est simple. Le terrain est rendu sous la forme d'un ensemble d'anneaux concentriques centrés sur la caméra. L'anneau le plus intérieur possède la plus forte densité de sommets. Pour chaque anneau extérieur $k$, l'espacement des sommets double, $s_k = s_0 \cdot 2^k$ ; il couvre donc une surface au sol $4\times$ supérieure à celle de l'anneau situé juste à l'intérieur.</p>
<p>Ce doublement constitue toute l'astuce. La distance d'affichage augmente géométriquement avec le nombre d'anneaux, $d_\text{view} \approx s_0 \cdot 2^{L}$, tandis que le coût en sommets n'augmente que linéairement :</p>
<p>$$
V_\text{total} \approx N^2 \cdot L
$$</p>
<p>pour $L$ anneaux comportant chacun $N \times N$ sommets. Doubler la distance d'affichage ne coûte qu'un anneau supplémentaire, et non quatre fois plus de géométrie. Le nombre de triangles reste à peu près constant, quelle que soit la taille du monde, car les mêmes $L$ anneaux sont toujours rendus à la même résolution.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/10-clipmap-geomorph/" title="Spike 10 : géomorphing des clipmaps géométriques" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/10-clipmap-geomorph/" target="_blank">Ouvrir le Spike 10 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=10-clipmap-geomorph">Voir le code source</a></p>
<p>L'astuce pratique consistait à appliquer un géomorphing aux limites des anneaux. Lorsqu'un sommet passe d'un anneau de niveau de détail au suivant, sa hauteur doit être interpolée en douceur entre les échantillons haute et basse résolution. Sans cela, des sauts visibles apparaissent chaque fois que la caméra se déplace et que les anneaux se décalent. Nous avons géré ce problème avec un facteur de fusion fondé sur la distance entre le sommet et le bord de l'anneau, en interpolant la hauteur dans le vertex shader :</p>
<p>$$
h = \operatorname{lerp}(h_\text{fine},, h_\text{coarse},, \alpha), \qquad \alpha = \operatorname{smoothstep}(d_\text{near},, d_\text{far},, d)
$$</p>
<p>où $d$ représente la distance entre le sommet et la caméra. À l'intérieur de l'anneau ($d \le d_\text{near}$), le sommet utilise sa hauteur en pleine résolution ; lorsqu'il atteint l'anneau suivant ($d \ge d_\text{far}$), il a déjà migré vers la hauteur grossière que cet anneau utilisera, si bien qu'aucun saut ne peut plus se produire.</p>
<p>Les tests de déplacement de la caméra nous ont révélé une leçon plus subtile. Il est facile d'évaluer les clipmaps sur des captures d'écran statiques et de passer à côté des artefacts de transition. Nous avons consacré du temps à parcourir les limites des anneaux à vitesse constante afin de repérer tout bruit temporel. Les captures d'écran mentaient. Le mouvement disait la vérité.</p>
<p>Ce prototype exploratoire nous a également fourni une frontière architecturale nette. Le terrain proche pouvait devenir progressivement dynamique et coûteux, avec de l'édition volumétrique, des matériaux plus complexes et des interactions physiques. Le terrain lointain pouvait rester stable, prévisible et peu coûteux. Cette séparation est devenue l'épine dorsale de toutes nos décisions architecturales à partir de ce moment.</p>
<p>Si vous envisagez d'utiliser des clipmaps dans votre propre projet, testez des boucles de charge plutôt que de belles prises de vue. Les longs trajets, les changements d'altitude de la caméra et les franchissements répétés des limites sont ce qui révèle les véritables problèmes.</p>
<p>Dans la partie 7, nous ajoutons le maillage volumétrique et passons du « terrain comme surface » au « terrain comme volume modifiable ». C'est à ce moment-là que ce projet a cessé d'être un moteur de rendu pour commencer à devenir un éditeur de mondes.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Clipmaps géométriques.</strong> Introduites par Losasso et Hoppe à la SIGGRAPH 2004 (<a href="https://hhoppe.com/geomclipmap.pdf">article</a>), les clipmaps géométriques rendent le terrain sous la forme d'anneaux carrés concentriques centrés sur la caméra. Chaque anneau couvre deux fois la surface du précédent avec une résolution de sommets divisée par deux. Le nombre total de sommets est constant : environ $N^2 \cdot L$. Avec $N = 256$ et $L = 8$ niveaux, cela représente $256^2 \times 8 \approx 524{,}000$ sommets, quelle que soit la taille du monde. Le CPU met à jour les données de la carte de hauteurs de chaque anneau à mesure que la caméra se déplace. Le vertex shader lit la hauteur dans une texture et déforme la grille plane. Consultez notre <a href="/fr/guides/landscape-generation-browser#geometry-clipmaps">guide de création de paysages consacré aux clipmaps géométriques</a> ainsi que <a href="https://developer.nvidia.com/gpugems/gpugems2/part-i-geometric-complexity/chapter-2-terrain-rendering-using-gpu-based-geometry">GPU Gems 2, chapitre 2</a>.</p>
<p><strong>Géomorphing.</strong> Le principal artefact visuel du niveau de détail des terrains est le saut : les sommets changent brusquement de position lorsqu'une parcelle passe à un autre niveau de détail. Le géomorphing élimine ce problème en interpolant les positions des sommets entre les niveaux de détail dans une zone de transition. Chaque sommet stocke à la fois sa hauteur au niveau de détail actuel et sa hauteur au niveau plus grossier. Un facteur de morphing fondé sur la distance à la caméra effectue une interpolation fluide entre les deux : <code>morphedHeight = mix(fineLodHeight, coarseLodHeight, smoothstep(lodNear, lodFar, distance))</code>. La zone de transition correspond généralement aux 20 % extérieurs de chaque anneau. À des vitesses de caméra normales, la transition est invisible. Consultez les <a href="/fr/guides/landscape-generation-browser#geomorphing-pop-free-lod-transitions">détails du géomorphing</a>.</p>
<p><strong>CDLOD (clipmaps adaptatives par quadtree).</strong> Une amélioration des clipmaps à anneaux fixes proposée par Strugar (2014, <a href="https://www.vertexasylum.com/CDLOD/cdlod_latest.pdf">article</a>). Au lieu d'utiliser des anneaux concentriques à résolution uniforme, CDLOD s'appuie sur un quadtree qui s'adapte à la complexité du terrain. Les zones planes utilisent des nœuds grossiers, tandis que les zones très détaillées — falaises ou crêtes — bénéficient d'une subdivision plus fine. C'est important pour les mondes créés par les utilisateurs, où la complexité peut varier considérablement d'un chunk à l'autre. Consultez la section <a href="/fr/guides/landscape-generation-browser#cdlod-quadtree-adaptive-clipmaps">CDLOD de notre guide de création de paysages</a>.</p>
<hr>
<p>Partie 6 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-05-budgeting-the-pretty">Partie 5 — Établir le budget des jolis effets</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes">Partie 7 — Marching cubes et les premières véritables grottes</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 7 : marching cubes et premières vraies grottes]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 12 a intégré au projet le marching cubes en temps réel avec WebGPU et changé les possibilités offertes par le terrain.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 7 : marching cubes et premières vraies grottes</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et contient les liens vers toutes les parties.</p>
<p>Les cartes de hauteur sont excellentes, jusqu'à ce que l'on ait besoin de surplombs.</p>
<p>Dès que l'on veut des tunnels creusés, des corniches rocheuses en suspension ou des plafonds de grottes, un pipeline reposant exclusivement sur un champ de hauteur devient un obstacle. Une carte de hauteur stocke une seule valeur Y par coordonnée XZ. Il lui est physiquement impossible de représenter une surface qui se replie sur elle-même. Il nous fallait une représentation volumétrique.</p>
<p>Le spike 12 a implémenté le marching cubes sur le GPU à l'aide de compute shaders WebGPU. L'algorithme évalue un champ de distance signé (SDF) sur une grille 3D et extrait un maillage triangulaire à la surface où le champ passe par zéro. Chaque cellule possède 8 sommets, chacun situé à l'intérieur ou à l'extérieur de la surface, ce qui donne $2^8 = 256$ configurations de signes possibles. Une table de correspondance associe chaque configuration à un ensemble de triangles. Les sommets se placent sur les arêtes de la cellule, au point où le champ passe par zéro. Pour une arête reliant les sommets $a$ et $b$, dont les valeurs de champ sont $f_a$ et $f_b$, l'interpolation linéaire place le sommet en</p>
<p>$$
\mathbf{v} = \mathbf{p}_a + t,(\mathbf{p}_b - \mathbf{p}_a), \qquad t = \frac{-f_a}{f_b - f_a}
$$</p>
<p>ce qui correspond à la fraction de l'arête où le champ atteint zéro. Comme $f_a$ et $f_b$ ont des signes opposés sur une arête traversée par la surface, $t$ appartient toujours à $[0, 1]$. Nous avons exécuté ce calcul simultanément sur quatre chunks actifs de 64³ cellules et testé des modifications animées du SDF avec un nouveau maillage généré à chaque frame.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/12-webgpu-marching-cubes/" title="Spike 12 : marching cubes avec WebGPU" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/12-webgpu-marching-cubes/" target="_blank">Ouvrir le spike 12 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=12-webgpu-marching-cubes">Voir le code source</a></p>
<p>Le premier succès a été de valider le pipeline de calcul lui-même. Une seule dispatch pouvait évaluer le SDF, classifier les cellules et émettre les sommets dans un buffer GPU sans aucune relecture par le CPU. Le deuxième a été de découvrir à quelle vitesse « ça fonctionne » se transforme en chasse aux artefacts. Les triangles manquants relevaient rarement d'un problème théorique lié au marching cubes. Ils provenaient plutôt de décalages d'indices dans les tables, de plages de rendu incorrectes qui lisaient au-delà du nombre de sommets actifs ou d'interactions dans des cas limites près des frontières des chunks, là où les échantillons SDF voisins n'étaient pas disponibles.</p>
<p>Ce spike nous a obligés à raisonner en zones. Près de la caméra, il faut une liberté volumétrique afin que les joueurs puissent sculpter, creuser et voir des grottes. Loin de la caméra, il faut l'efficacité des clipmaps, car une carte de hauteur plane y est moins coûteuse et parfaitement suffisante. Cette dualité est devenue la colonne vertébrale de l'architecture que nous avons continué à affiner à partir du spike 13.</p>
<p>L'un de mes moments de débogage préférés a été l'utilisation du mode filaire pendant l'exécution des modifications. Voir la topologie se former et se dissoudre en temps réel rendait immédiatement visibles les compromis de qualité. On pouvait voir où la densité des sommets était suffisante, où elle devenait trop faible et précisément où les transitions de LOD auraient finalement besoin de Transvoxel pour éviter les fissures.</p>
<p>Dans la partie 8, nous abordons le défi de l'intégration. Maintenir des maillages bruts générés par le calcul et la logique du graphe de scène Three.js au sein d'un même pipeline de rendu stable s'est révélé plus difficile que ne le laissait penser la démo isolée.</p>
<h2>Technologies mentionnées dans ce chapitre</h2>
<p><strong>Marching cubes.</strong> Un algorithme permettant d'extraire un maillage triangulaire à partir d'un champ scalaire 3D (Lorensen et Cline, 1987). Chaque cellule d'une grille 3D régulière est classifiée en échantillonnant le champ à ses 8 sommets. La configuration des signes produit un indice de cas (0 à 255), et une table de correspondance associe chaque cas à un ensemble de triangles. Les sommets sont placés sur les arêtes de la grille par interpolation entre les deux extrémités. Comme chaque cellule est traitée indépendamment, l'algorithme est naturellement massivement parallèle, ce qui le rend idéal pour le calcul GPU. Consultez notre <a href="/fr/guides/landscape-generation-browser#signed-distance-fields-sdfs">guide sur les paysages consacré aux SDF et au marching cubes</a>.</p>
<p><strong>Champs de distance signés (SDF).</strong> Une représentation volumétrique qui stocke, en chaque point de l'espace 3D, la distance signée jusqu'à la surface la plus proche. Les valeurs positives se trouvent à l'extérieur, les valeurs négatives à l'intérieur, et le passage par zéro définit la surface. Les SDF peuvent représenter des formes 3D arbitraires : grottes, arches, surplombs et géométries flottantes qu'une carte de hauteur ne peut pas exprimer. Leur édition repose naturellement sur l'algèbre ensembliste appliquée au champ. L'ajout de matière (union de deux formes) s'écrit $d = \min(d_1, d_2)$, le retrait (creusement) s'écrit $d = \max(d_1, -d_2)$ en inversant la forme de découpe, et une fusion douce utilise un minimum lissé tel que</p>
<p>$$
\operatorname{smin}(d_1, d_2, k) = \min(d_1, d_2) - \frac{h^2}{4k}, \qquad h = \max\bigl(k - |d_1 - d_2|,, 0\bigr)
$$</p>
<p>où $k$ contrôle le rayon de fusion. Lorsque $k \to 0$, le résultat redevient un $\min$ strict. Consultez la section <a href="/fr/guides/landscape-generation-browser#signed-distance-fields-sdfs">Représentation du terrain par SDF</a>.</p>
<p><strong>Compute shaders WebGPU.</strong> Des programmes GPU qui exécutent des calculs généraux sans être liés au pipeline de rastérisation. Un compute shader distribue des groupes de threads qui s'exécutent en parallèle. Pour le marching cubes, chaque thread traite une cellule de la grille : il échantillonne le SDF, classifie la cellule, recherche la triangulation, interpole les sommets sur les arêtes et les ajoute à un buffer de maillage à l'aide de compteurs atomiques. Aucune relecture par le CPU n'est nécessaire, car le buffer de sortie est directement utilisé comme données de sommets pour le rendu. Le projet <a href="https://www.willusher.io/webgpu-marching-cubes/">webgpu-marching-cubes</a> de Will Usher illustre le traitement en temps réel d'une grille de 256^3 cellules dans le navigateur. Consultez notre <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">guide sur les paysages consacré au LOD piloté par WebGPU</a>.</p>
<p><strong>Architecture hybride carte de hauteur + SDF.</strong> L'approche pratique pour les terrains dans le navigateur : les cartes de hauteur couvrent l'ensemble du monde (elles sont peu coûteuses et compactes), tandis que les volumes SDF n'existent que dans les chunks qui nécessitent des grottes, des surplombs ou des éléments sculptés par les créateurs (5 à 10 % des chunks). Près de la caméra, la liberté volumétrique permet de sculpter et de créer des grottes. Au loin, les cartes de hauteur fournissent efficacement un terrain plat. Consultez la section <a href="/fr/guides/landscape-generation-browser#hybrid-heightmap-base--volumetric-overlays">Représentation hybride du terrain</a>.</p>
<hr>
<p>Partie 7 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-06-clipmaps">Partie 6 — Les clipmaps ont changé la donne</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-08-webgpu-integration">Partie 8 — Intégrer sans perdre notre base de référence</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 8 : intégrer sans perdre notre base de référence]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-08-webgpu-integration</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-08-webgpu-integration</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Les spikes 13 et 14 portaient moins sur les fonctionnalités que sur la rigueur du processus. Nous avons figé une base de référence propre, puis renforcé chaque couche l’une après l’autre.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 8 : intégrer sans perdre notre base de référence</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et contient les liens vers toutes les parties.</p>
<p>C’est au moment de l’intégration que les projets deviennent chaotiques. Les différents éléments fonctionnent séparément. Vous les connectez et, soudain, chaque bug semble pouvoir venir de n’importe où.</p>
<p>Les spikes 13 et 14 ont été notre réponse à ce piège. Le spike 13 a établi une base de référence Three.js WebGPU propre. Seulement un moteur de rendu, une scène, une caméra et un maillage simple. Pas de terrain, pas de calcul GPU, pas d’effets. Nous avons vérifié que le backend WebGPU de Three.js s’initialisait correctement, que la boucle de rendu était stable et que les matériaux à nœuds TSL (Three.js Shading Language) fonctionnaient comme prévu. Nous n’avons commencé à ajouter des couches qu’une fois cette étape de validation franchie.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/13-threejs-webgpu/" title="Spike 13 : base de référence Three.js WebGPU" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/13-threejs-webgpu/" target="_blank">Ouvrir le spike 13 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=13-threejs-webgpu">Voir le code source</a></p>
<p>Le spike 14 était consacré au renforcement progressif. Nous avons ajouté une fonctionnalité à la fois : d’abord les contrôles de la caméra, puis l’éclairage, ensuite le maillage généré par calcul GPU issu du pipeline marching cubes, et enfin la gestion des buffers permettant d’injecter directement la sortie du GPU dans les attributs géométriques de Three.js. Après chaque ajout, nous vérifiions que la couche précédente continuait de fonctionner correctement.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/14-threejs-webgpu-incremental/" title="Spike 14 : renforcement progressif" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/14-threejs-webgpu-incremental/" target="_blank">Ouvrir le spike 14 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=14-threejs-webgpu-incremental">Voir le code source</a></p>
<p>Cela peut sembler lent. Ça l’a été pendant exactement une journée, puis cette méthode nous en a fait gagner plusieurs peu après, lorsque la logique des raccords et le changement de stratégie se sont complexifiés.</p>
<p>La catégorie précise de bugs qui justifiait cette rigueur était celle des artefacts extrêmement fins. De minces fragments qui ressemblaient à une corruption de la géométrie, mais qui provenaient en réalité de données obsolètes. Le shader de calcul écrivait N sommets dans un buffer, mais l’appel de rendu restait configuré pour afficher N+M sommets provenant de l’image précédente. Ces sommets supplémentaires contenaient les données résiduelles de l’ancienne exécution. Le résultat visuel était un scintillement de triangles extrêmement fins qui apparaissaient et disparaissaient de façon imprévisible.</p>
<p>Ce type de bug ne se résout pas à l’intuition. Il se résout par des modifications contrôlées, qui permettent de savoir exactement ce qui a changé entre le dernier état fonctionnel et l’état défaillant actuel.</p>
<p>L’intégration de WebGPU nous a également beaucoup appris sur le cycle de vie des buffers. Dans WebGPU, les buffers GPU deviennent immuables une fois mappés pour un usage donné. Si vous devez redimensionner un buffer de sommets parce que la sortie de marching cubes a grandi, vous devez créer un nouveau buffer et mettre à jour la liaison. Il n’existe pas de <code>realloc</code>. Gérer correctement ce cycle de vie et détruire les anciens buffers sans entrer en conflit avec des tâches GPU encore en cours a nécessité une gestion explicite des barrières de synchronisation, qui n’existe pas dans WebGL.</p>
<p>Dans la partie 9, nous passons au travail sur les raccords Transvoxel. Ce chapitre commence délibérément par une structure de base. À ce stade, nous avions pleinement assimilé la leçon : précipiter l’intégration crée des mystères, tandis qu’une mise en place contrôlée produit des problèmes que l’on peut déboguer.</p>
<h2>Technologies mentionnées dans ce chapitre</h2>
<p><strong>WebGPU.</strong> Le successeur de WebGL, qui fournit dans le navigateur un accès bas niveau au GPU avec des shaders de calcul et du rendu indirect. Les deux fonctionnalités essentielles de WebGPU pour les mondes ouverts sont les suivantes : les shaders de calcul permettent de générer le terrain, de placer la végétation et d’effectuer le culling côté GPU ; le rendu indirect permet au GPU de décider quoi afficher à partir des résultats des calculs, éliminant ainsi les goulots d’étranglement côté CPU dans les scènes denses. Disponible sur ordinateur dans Chrome, Edge et Firefox. Consultez <a href="/fr/guides/browser-3d-open-world-tech#webgpu-the-performance-unlock">WebGPU comme levier de performances</a>.</p>
<p><strong>Three.js Shading Language (TSL).</strong> Le système de shaders à nœuds de Three.js, qui remplace le code GLSL/WGSL brut par des expressions JavaScript composables. Les nœuds TSL tels que <code>texture()</code>, <code>positionWorld</code>, <code>smoothstep()</code> et <code>fog()</code> construisent à l’exécution un graphe de shader compilé pour le backend approprié (GLSL pour WebGL ou WGSL pour WebGPU). TSL permet d’écrire une seule fois la logique d’un matériau et de cibler les deux moteurs de rendu. Le graphe de nœuds étant évalué à chaque image, les uniformes dynamiques et les branchements conditionnels fonctionnent naturellement.</p>
<p><strong>Cycle de vie des buffers GPU dans WebGPU.</strong> Les buffers WebGPU sont créés avec des indicateurs d’usage spécifiques (<code>VERTEX</code>, <code>STORAGE</code>, <code>COPY_DST</code>, etc.) et ne peuvent pas être redimensionnés après leur création. Si une exécution de marching cubes produit plus de sommets que le buffer ne peut en contenir, vous devez créer un nouveau buffer, mettre à jour la liaison et détruire l’ancien. Détruire un buffer encore référencé par une commande GPU en cours d’exécution provoque des erreurs. Une gestion explicite des barrières de synchronisation (avec <code>device.queue.onSubmittedWorkDone()</code>) garantit que l’ancien buffer n’est pas détruit avant que le GPU ait fini de l’utiliser. Cette rigueur dans la gestion du cycle de vie n’existe pas dans WebGL, où le pilote gère implicitement la mémoire.</p>
<p><strong>Renforcement progressif.</strong> Une méthode rigoureuse d’intégration : établir une base de référence dont le bon fonctionnement est confirmé, ajouter une fonctionnalité à la fois, puis vérifier après chaque ajout que la couche précédente fonctionne toujours. Cette approche fait perdre une journée, mais en fait gagner plusieurs lors des phases ultérieures de débogage, car chaque régression peut être rattachée à une modification précise et contrôlée. Ce modèle consistant à établir une base de référence avant de procéder par incréments est courant dans le <a href="/fr/guides/browser-3d-open-world-tech#what-we-d-build-first">développement de mondes ouverts AAA</a>, où les systèmes sont intégrés dans un ordre précis afin de maîtriser les risques.</p>
<hr>
<p>Partie 8 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes">Partie 7 — Marching cubes et les premières véritables grottes</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-09-transvoxel-first-cut">Partie 9 — Transvoxel a commencé par une structure de base</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 9 : Transvoxel a commencé par une structure de test]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-09-transvoxel-first-cut</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-09-transvoxel-first-cut</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Nous ne nous sommes pas lancés directement dans la gestion complète des raccords. Nous avons d'abord construit un banc de test, puis validé une face avec des données de référence.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 9 : Transvoxel a commencé par une structure de test</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>Les raccords sont l'endroit où la confiance vient mourir.</p>
<p>Tout peut sembler stable jusqu'à ce que deux résolutions se rencontrent. Un chunk en LOD 0 se trouve à côté d'un chunk en LOD 1. Leurs maillages sont générés indépendamment. Le long de leur frontière commune, les positions des sommets ne correspondent pas, car le chunk de résolution inférieure possède une grille deux fois moins dense. Résultat : des fissures visibles, des jonctions en T et des arêtes qui scintillent.</p>
<p>L'algorithme Transvoxel résout ce problème en générant des cellules de transition spéciales le long de la face séparant deux chunks de résolutions différentes. Ces cellules échantillonnent simultanément les grilles haute et basse résolution, puis produisent des triangles qui raccordent les deux surfaces. L'algorithme utilise ses propres tables de correspondance, distinctes de celles du marching cubes classique, avec 512 cas de cellules de transition.</p>
<p>À ce stade, nous avions accumulé suffisamment de cicatrices d'intégration pour savoir qu'il ne fallait pas précipiter l'implémentation.</p>
<p>Le spike 15 avait une seule mission : construire un banc de test des raccords auquel nous pourrions nous fier avant de toucher à l'algorithme complet. Nous avons mis en place un environnement contrôlé dans lequel deux chunks contenant des données SDF connues étaient placés côte à côte à des résolutions différentes, avec des contrôles de visualisation permettant d'afficher ou de masquer indépendamment le maillage principal, le maillage de raccord, le rendu filaire et les normales.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/15-transvoxel-seam/" title="Structure de test du raccord Transvoxel du spike 15" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/15-transvoxel-seam/" target="_blank">Ouvrir le spike 15 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=15-transvoxel-seam">Voir le code source</a></p>
<p>Une fois le banc de test stabilisé, le spike 16 a validé la génération de cellules de transition pilotée par des tables sur une seule face. Nous avons choisi une face alignée sur un axe (la frontière +X), implémenté l'évaluation des cellules de transition uniquement pour cette face, puis comparé le résultat aux données de référence de l'article sur Transvoxel.</p>
<p><a href="/fr/spikes/16-transvoxel-face/" target="_blank">Ouvrir le spike 16 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=16-transvoxel-face">Voir le code source</a></p>
<p>Nous avons délibérément testé une face à la fois, car le raccordement des tables de transition comporte de nombreux modes de défaillance indépendants. Le calcul de l'indice de cas dépend de l'échantillonnage des bons sommets dans les deux grilles. L'indexation des sommets au sein d'une cellule de transition utilise un schéma de numérotation différent de celui des cellules classiques du marching cubes. L'ordre d'enroulement doit être cohérent avec celui du maillage principal, sinon l'élimination des faces arrière fera disparaître les triangles du raccord. Si vous testez les six faces simultanément, tous les symptômes semblent aléatoires. Si vous testez minutieusement une seule face, vous obtenez des erreurs pertinentes et faciles à déboguer.</p>
<p>Autre avancée subtile de cette phase : l'investissement dans les outils. Nous avons très tôt créé des options de visibilité, un rendu limité aux raccords et des indicateurs de LOD codés par couleur. À l'époque, ces contrôles semblaient superflus. Plus tard, lorsque les cas limites se sont compliqués, ils ont largement rentabilisé cet effort, car nous pouvions isoler précisément les cellules de raccord qui se comportaient mal.</p>
<p>À la fin de ce chapitre, nous n'en avions pas « fini avec les raccords ». Nous étions désormais en mesure d'analyser leurs bugs au lieu de les redouter.</p>
<p>Dans la partie 10, les vraies montagnes russes commencent. Coins mêlant plusieurs LOD, inversions de l'ordre d'enroulement, fantômes dus à un surdessin partiel, et ces moments où vous êtes certain que l'algorithme est incorrect avant de découvrir que le bug vient d'une plage de dessin qui lit au-delà du nombre de sommets actifs.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>L'algorithme Transvoxel.</strong> Conçu par Eric Lengyel (<a href="https://transvoxel.org/">transvoxel.org</a>), Transvoxel résout le problème le plus difficile du LOD pour les terrains volumétriques : les raccords entre des chunks de résolutions différentes. Lorsqu'un chunk haute résolution jouxte un chunk basse résolution, les maillages générés par marching cubes ne s'alignent pas à la frontière, ce qui produit des fissures visibles. Transvoxel insère le long des faces de frontière des cellules de transition spéciales qui comblent l'écart de résolution au moyen de triangles supplémentaires correspondant aux deux côtés. L'algorithme utilise ses propres tables de correspondance, distinctes de celles du marching cubes classique, avec 512 cas de cellules de transition ramenés à 73 classes d'équivalence. Il est libre de brevets et a été utilisé dans des jeux commercialisés, notamment Space Engineers et Astroneer. Consultez notre <a href="/fr/guides/landscape-generation-browser#the-transvoxel-algorithm">guide sur les paysages consacré à Transvoxel</a>.</p>
<p><strong>Cellules de transition.</strong> Cellules spéciales générées sur la face séparant deux niveaux de LOD. Contrairement aux cellules classiques du marching cubes, qui échantillonnent 8 coins d'une même grille, les cellules de transition échantillonnent simultanément les grilles haute et basse résolution. La face haute résolution comporte $3 \times 3 = 9$ points d'échantillonnage, tandis que la face basse résolution en comporte $2 \times 2 = 4$. Ce sont ces 9 échantillons qui donnent à l'algorithme ses $2^9 = 512$ cas bruts, ramenés à 73 triangulations distinctes après regroupement des rotations et des réflexions. La classification et la triangulation des cellules utilisent des tables dédiées qui produisent des triangles reliant des sommets aux deux résolutions. Le schéma de numérotation des sommets diffère de celui des cellules MC classiques, ce qui constitue une source fréquente de bugs d'implémentation.</p>
<p><strong>Raccords de transition entre LOD.</strong> Frontière entre deux résolutions de maillage où une incompatibilité topologique provoque des artefacts visuels. Sans raccord, un chunk en LOD 0 doté d'une grille de 1 m placé à côté d'un chunk en LOD 1 doté d'une grille de 2 m produit des jonctions en T : le maillage fin possède le long de la frontière des sommets que le maillage grossier ne partage pas, créant des fissures par lesquelles la lumière transparaît. Transvoxel, le géomorphing et les jupes géométriques sont trois approches permettant de corriger ces raccords. Pour les terrains volumétriques, Transvoxel est la solution de référence, car le géomorphing ne fonctionne qu'avec les cartes de hauteur. Consultez <a href="/fr/guides/landscape-generation-browser#lod-for-volumetric-terrain">le LOD pour les terrains volumétriques</a>.</p>
<hr>
<p>Partie 9 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-08-webgpu-integration">Partie 8 - Intégrer sans perdre notre référence</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-10-seam-chaos">Partie 10 - Le chaos des raccords et le boss final des coins</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 10 : le chaos des raccords et le combat de boss dans les coins]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-10-seam-chaos</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-10-seam-chaos</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Les spikes 17 à 22 ont confronté la théorie aux cas limites. Nous avons traqué des erreurs d'ordre des sommets, des triangles manquants, des fantômes de surdessin et les subtilités de la logique de repli hybride.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 10 : le chaos des raccords et le combat de boss dans les coins</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et contient des liens vers toutes les parties.</p>
<p>Si les parties précédentes semblaient méthodiques, ce chapitre a plutôt ressemblé à un combat.</p>
<p>Les spikes 17 à 22 ont marqué notre période consacrée aux cas limites. Marching cubes à double LOD, raccords entre heightmaps et MC, chunks de coin à résolutions mixtes où trois ou quatre niveaux de LOD se rencontrent, génération des raccords sur le GPU et comportement du mode de repli. Chaque spike traitait un scénario d'échec précis que nous avions rencontré ou anticipé.</p>
<p>Le spike 17 testait le marching cubes double avec deux niveaux de LOD actifs simultanément. La difficulté venait du fait que les voisins d'un même chunk pouvaient avoir des résolutions différentes sur chacune de ses faces. La logique des cellules de transition du spike 16 fonctionnait pour une face à la fois, mais lorsqu'un chunk avait besoin de cellules de transition sur plusieurs faces, la gestion du tampon de sommets devenait complexe. Les cellules de transition de chaque face devaient être générées et ajoutées à la suite sans écraser celles des autres.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/19-transvoxel-corner-grid/" title="Spike 19 : grille de coin Transvoxel" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/19-transvoxel-corner-grid/" target="_blank">Ouvrir le spike 19 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=19-transvoxel-corner-grid">Voir le code source</a></p>
<p>Le premier adversaire récurrent était l'ordre des sommets. À plusieurs reprises, nous pensions avoir des problèmes de topologie avant de découvrir qu'il s'agissait de problèmes d'orientation. L'élimination des faces arrière supprimait des triangles de raccord valides, car leur ordre était inversé par rapport au maillage principal. Même cause profonde, mais symptôme visuel différent selon l'angle de la caméra. La solution a consisté à imposer une convention d'ordre cohérente dans le code d'émission des cellules de transition, puis à la vérifier en activant et désactivant un matériau à double face.</p>
<p>Le deuxième adversaire était la fausse assurance procurée par une correction partielle. Un raccord pouvait sembler parfait sous un angle de caméra, puis se briser lorsque les rôles de LOD s'inversaient entre les chunks de haute et de basse résolution. La cellule de transition est asymétrique. Elle échantillonne différemment le côté haute résolution et le côté basse résolution. Si la logique déterminant « quel côté est en haute résolution » est inversée dans une configuration, le bug n'apparaît que lorsque la caméra atteint une position précise.</p>
<p>Puis est venu l'un de nos retournements de situation préférés. Nous traquions un artefact de coupure de raccord sur des tuiles de heightmap et accusions la logique de transition. Nous y avons passé deux jours. Le véritable coupable était du surdessin résiduel. La géométrie haute résolution d'une frame précédente restait dans la fin du tampon après le passage du chunk à un LOD inférieur. La plage de dessin utilisait toujours l'ancien nombre de sommets, plus élevé. Dès que nous avons limité la plage de dessin au nombre de sommets actifs indiqué par le compteur atomique du compute shader, le « mystérieux problème de raccord » a disparu.</p>
<p>Ce fut un excellent rappel : les bugs de rendu se font souvent passer pour des bugs de génération de maillage. La géométrie avait toujours été correcte. L'appel de dessin lisait simplement au-delà de la fin des données valides.</p>
<p>Au spike 22, nous testions un repli hybride permettant aux chunks de passer du marching cubes au mode heightmap dans certaines conditions, par exemple lorsqu'un chunk ne contient aucune modification volumétrique et se trouve suffisamment loin de la caméra. Cette approche nous offrait une voie plus pragmatique qu'une politique du tout ou rien. Les chunks modifiés proches de la caméra utilisent MC pour offrir une liberté volumétrique. Les chunks éloignés et non modifiés utilisent des heightmaps pour gagner en efficacité.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/22-gpu-mc-heightmap-fallback/" title="Spike 22 : repli de MC vers une heightmap" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/22-gpu-mc-heightmap-fallback/" target="_blank">Ouvrir le spike 22 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=22-gpu-mc-heightmap-fallback">Voir le code source</a></p>
<p>Ce chapitre a été la chute vertigineuse des montagnes russes. Frustrante et productive à la fois. Bon nombre des corrections étaient minimes, parfois une seule ligne modifiant un opérateur de comparaison ou un décalage. Mais la compréhension qu'elles nous ont apportée sur l'interaction entre les transitions de LOD, la gestion des tampons et les plages de dessin était loin d'être insignifiante.</p>
<p>Dans la partie 11, nous abordons la couche de stabilisation qui a suivi le chaos : les modes de chunk basés sur des règles et le passage d'une correction réactive des bugs à des règles système explicites.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Marching cubes à double LOD.</strong> Exécution simultanée du marching cubes à deux niveaux de résolution, avec des cellules de transition reliant leur frontière. La difficulté vient du fait que les voisins d'un même chunk peuvent avoir des résolutions différentes sur chacune de ses faces, ce qui nécessite une génération indépendante des cellules de transition pour chaque face. Les cellules de transition de chaque face sont ajoutées au tampon de sommets sans écraser celles des autres. Des compteurs atomiques suivent le nombre total de sommets actifs sur l'ensemble des faces.</p>
<p><strong>Ordre des sommets.</strong> L'ordre des sommets de chaque triangle détermine quel côté constitue sa face « avant ». Un ordre cohérent — généralement dans le sens antihoraire vu depuis l'extérieur — est nécessaire pour l'élimination des faces arrière. Lorsque les cellules de transition émettent des triangles, leur ordre doit respecter la convention du maillage principal. Un ordre inversé amène l'élimination des faces arrière à supprimer des triangles de raccord valides, ce qui donne l'impression que des surfaces manquent sous certains angles de caméra. Une technique de débogage courante consiste à activer <code>side: THREE.DoubleSide</code> sur le matériau pour déterminer si les artefacts proviennent de l'ordre des sommets ou de véritables trous topologiques.</p>
<p><strong>Repli d'une heightmap vers MC.</strong> Mode de chunk hybride dans lequel les chunks éloignés ou non modifiés utilisent un terrain en heightmap — peu coûteux, avec une surface plane — tandis que les chunks proches ou modifiés utilisent le marching cubes — volumétrique et compatible avec les grottes. La décision de repli dépend de la distance à la caméra et de la présence de modifications SDF dans le chunk. Le raccord entre un chunk de heightmap et un chunk MC nécessite sa propre géométrie de transition, similaire à Transvoxel, mais reliant deux représentations différentes plutôt que deux niveaux de LOD. Consultez <a href="/fr/guides/landscape-generation-browser#hybrid-heightmap-base--volumetric-overlays">la base hybride en heightmap et les couches volumétriques</a>.</p>
<p><strong>Plage de dessin et compteurs atomiques.</strong> Dans une génération de maillage pilotée par le GPU, le compute shader écrit les sommets dans un tampon et incrémente un compteur atomique pour suivre le nombre de sommets émis. L'appel de dessin doit utiliser ce compteur comme nombre de sommets, et non la capacité du tampon. Si la plage de dessin n'est pas limitée au nombre de sommets actifs, des sommets résiduels des frames précédentes — toujours présents à la fin du tampon — produisent une géométrie fantôme : de fines lamelles et des triangles qui scintillent, ressemblant à des erreurs topologiques alors qu'il s'agit en réalité d'artefacts de rendu causés par une lecture au-delà des données valides.</p>
<hr>
<p>Partie 10 sur 12.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-09-transvoxel-first-cut">Partie 9 — Transvoxel a commencé par une structure de base</a><br>
Suivant : <a href="/fr/blog/2026-02-25-open-world-browser-part-11-policy-modes">Partie 11 — Un mode défini par des règles, pas codé en dur</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 11 : un mode piloté par des règles, pas codé en dur]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-11-policy-modes</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-11-policy-modes</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 23 nous a fait passer de correctifs réactifs à des règles explicites. LOD déterminé par la distance, basculement HM/MC selon les modifications et meilleurs outils de débogage des raccords.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 11 : un mode piloté par des règles, pas codé en dur</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu'est un spike et renvoie vers toutes les parties.</p>
<p>Après le chapitre consacré au chaos des raccords, nous devions cesser de réagir au cas par cas et commencer à piloter le système.</p>
<p>Le spike 23 a remplacé les comportements ponctuels par des règles explicites. Au lieu de laisser chaque chunk agir selon ce que suggérait son état local, un système centralisé prenait désormais les décisions. Quel niveau de LOD attribuer à ce chunk ? Doit-il être rendu sous forme de heightmap ou avec marching cubes ? A-t-il besoin de cellules de transition, et sur quelles faces ? Les réponses venaient d'une fonction de règles qui évaluait la distance à la caméra, l'historique des modifications et les états de résolution des voisins.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/23-policy-chunk-modes/" title="Spike 23 : règles de mode des chunks" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/23-policy-chunk-modes/" target="_blank">Ouvrir le spike 23 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=23-policy-chunk-modes">Voir le code source</a></p>
<p>L'attribution du LOD selon la distance utilisait des anneaux concentriques autour de la caméra, sur un principe proche de celui des clipmaps, mais appliqué à la grille de chunks. Les chunks de l'anneau 0 utilisent MC à pleine résolution. Ceux de l'anneau 1 utilisent MC à demi-résolution. Ceux de l'anneau 2 et au-delà utilisent le mode heightmap. La contrainte d'adjacence était essentielle : pour deux chunks voisins quelconques, nous imposons $|L_i - L_j| \le 1$. Le rapport de résolution le long d'une face est $2^{|L_i - L_j|}$, et les tables de transition Transvoxel ne sont définies que pour le cas 2:1 ($|L_i - L_j| = 1$). Un écart de 2 niveaux nécessiterait une transition 4:1 que les tables ne peuvent pas représenter ; les règles forcent donc le chunk le moins détaillé à augmenter sa résolution jusqu'à ce que la contrainte soit respectée.</p>
<p>La logique de basculement HM/MC vérifiait le bitmap de modifications de chaque chunk. Si un chunk comportait la moindre modification volumétrique — grotte, tunnel ou sculpture du terrain —, il restait en mode MC quelle que soit sa distance. Les chunks non modifiés pouvaient passer en mode heightmap lorsqu'ils s'éloignaient suffisamment. Cette approche hybride nous offrait une liberté volumétrique là où elle comptait et de l'efficacité partout ailleurs.</p>
<p>Vu de l'extérieur, ce spike paraissait plus modeste que certains des précédents. En pratique, il a considérablement amélioré le confort de développement.</p>
<p>Lorsque votre système peut expliquer pourquoi un chunk a changé de mode, vous passez moins de temps à deviner. Nous avons ajouté des surcouches avec un code couleur : vert pour les chunks en heightmap, bleu pour les chunks en MC et orange pour les faces dont les transitions étaient actives. Lorsque la visibilité des raccords dispose de contrôles de rendu dédiés, l'ambiguïté du débogage visuel diminue. Et lorsque les plages de dessin sont explicitement liées au nombre de sommets actifs fourni par le système de règles, les fantômes de géométrie obsolète cessent de vous faire perdre tout un après-midi.</p>
<p>Nous avons également remanié le comportement de la caméra dans ce spike. Les spikes précédents utilisaient de simples contrôles orbitaux, adaptés aux captures d'écran mais inutiles pour reproduire les bugs. Le spike 23 a ajouté une caméra volante contrôlée avec WASD, dotée d'une vitesse configurable, d'un verrouillage optionnel de l'altitude et d'un affichage de la position. Cela peut sembler anodin. Pourtant, c'est ce qui fait la différence entre « J'ai vu un bug quelque part près de cette crête » et « Le bug apparaît à la position (142, 12, -67), en regardant vers le nord-ouest ».</p>
<p>L'idée essentielle de ce chapitre est que les règles n'ont pas réduit la complexité. Elles l'ont organisée. Le nombre de cas limites n'avait pas changé. Mais chacun d'eux avait désormais un nom, une condition de déclenchement et un emplacement dans le code où poser un point d'arrêt. C'est une autre forme de victoire, et c'est ce qui détermine si un système peut continuer à évoluer ou s'effondre sous son propre poids.</p>
<p>À la fin du spike 23, nous disposions d'une couche de comportement en champ proche suffisamment prévisible pour être reliée à une stratégie d'anneaux clipmap en champ lointain, sans craindre constamment des bugs d'interaction.</p>
<p>Dans la partie 12, nous abordons le spike 24, où les transitions entre anneaux, le brouillard de la skybox et l'intégration des shaders propre à la version de Three.js concluent ce chapitre du projet.</p>
<h2>Technologies mentionnées dans ce chapitre</h2>
<p><strong>Règles de LOD fondées sur la distance.</strong> Une fonction centrale attribue à chaque chunk un niveau de LOD et un mode de rendu selon sa distance à la caméra, son historique de modifications et l'état de ses voisins. Des anneaux concentriques de distance déterminent le LOD de base : anneau 0 = MC à pleine résolution, anneau 1 = MC à demi-résolution, anneau 2 et au-delà = mode heightmap. La fonction de règles s'exécute à chaque frame pendant les déplacements de la caméra et déclenche les transitions des chunks. Elle remplace les décisions ponctuelles prises par chaque chunk par un système de règles prévisible et facile à déboguer. Consultez la section sur la <a href="/fr/guides/landscape-generation-browser#gpu-driven-lod-with-webgpu">sélection du LOD pilotée par le GPU</a> pour découvrir l'équivalent en compute shader.</p>
<p><strong>Contraintes d'adjacence.</strong> L'algorithme Transvoxel ne gère que les rapports de résolution 2:1. Si deux chunks voisins diffèrent de plus d'un niveau de LOD — par exemple, un LOD 0 à côté d'un LOD 2 —, les tables de transition ne peuvent pas produire une géométrie de raccord valide. Le système de règles impose cette contrainte en augmentant la résolution du chunk le moins détaillé lorsque l'écart de LOD dépasse 1. Cette propagation de contraintes peut se répercuter en cascade : augmenter la résolution d'un chunk peut obliger ses voisins à faire de même. L'implémentation repose sur une simple passe itérative qui converge en 2 à 3 itérations pour les configurations de grille habituelles.</p>
<p><strong>Bitmap de modifications pour la sélection du mode.</strong> Chaque chunk conserve un bitmap indiquant s'il contient des modifications SDF volumétriques — grottes, tunnels ou sculptures. Les chunks comportant la moindre modification restent en mode marching cubes quelle que soit leur distance, afin de préserver les changements du créateur. Les chunks non modifiés passent en mode heightmap lorsqu'ils sont suffisamment éloignés de la caméra, ce qui économise des ressources de calcul et de la mémoire. Le bitmap se résume à un indicateur par chunk, mais peut être étendu pour suivre la densité des modifications et permettre des décisions de mode plus précises.</p>
<p><strong>Surcouches de visualisation pour le débogage.</strong> Un rendu des chunks avec code couleur, où vert = mode heightmap, bleu = mode MC et orange = faces dont les transitions sont actives. Des surcouches propres à chaque chunk affichent le niveau de LOD et le libellé du mode, avec des options de rendu en fil de fer. Ce sont des outils de développement, pas des fonctionnalités livrées aux utilisateurs, mais ils sont rentabilisés à maintes reprises lors du débogage des transitions de LOD et des artefacts de raccord. Associés à une caméra volante contrôlée avec WASD qui indique la position exacte dans le monde, ils transforment « J'ai vu un bug quelque part » en « Le bug apparaît à la position (142, 12, -67) avec cette configuration de LOD ».</p>
<hr>
<p>Partie 11 sur 12.<br>
Précédente : <a href="/fr/blog/2026-02-25-open-world-browser-part-10-seam-chaos">Partie 10 — Le chaos des raccords et le boss final des coins</a><br>
Suivante : <a href="/fr/blog/2026-02-25-open-world-browser-part-12-lessons">Partie 12 — Anneaux, brouillard céleste et ce que nous referions</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un monde ouvert dans le navigateur, partie 12 : anneaux, brume céleste et ce que nous referions]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-12-lessons</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-part-12-lessons</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Le spike 24 a clos ce chapitre avec les transitions entre anneaux de clipmap, l’intégration de la brume au skybox et une longue liste de leçons durement acquises.]]></description>
            <content:encoded><![CDATA[<h1>Construire un monde ouvert dans le navigateur, partie 12 : anneaux, brume céleste et ce que nous referions</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Vous découvrez la série ? Consultez le <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">guide de la série</a>. Il explique ce qu’est un spike et contient les liens vers toutes les parties.</p>
<p>Le spike 24 devait simplement « ajouter des anneaux de clipmap au terrain ». Il s’est transformé en véritable final, touchant à la fois au rendu, aux shaders, à l’infrastructure des modules et à l’intégration visuelle.</p>
<p>La tâche principale liée au terrain consistait à générer des anneaux de clipmap concentriques dans le vertex shader. Chaque anneau est un maillage en grille plane centré sur la caméra, dont les sommets sont déplacés en fonction des échantillons de la heightmap. L’anneau intérieur utilise la pleine résolution. Chaque anneau suivant double l’espacement des sommets et couvre une zone plus vaste. La difficulté réside dans la frontière entre les anneaux : lorsqu’un anneau haute résolution rencontre un anneau basse résolution, les sommets de bord du maillage le plus fin doivent s’aligner sur le milieu de l’arête du maillage plus grossier. Nous avons réalisé un morphing de bord 2:1 en détectant les sommets de frontière — ceux dont la coordonnée de grille est impaire le long du bord de l’anneau — puis en alignant leur hauteur sur le milieu de leurs deux voisins pairs, $h = \tfrac{1}{2}(h_\text{left} + h_\text{right})$. C’est exactement le point où se trouverait l’unique arête de l’anneau grossier ; les bords fin et grossier décrivent donc la même ligne. Cela produit des raccords étanches sans géométrie de transition.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0;border:1px solid rgba(255,255,255,0.12)">
<iframe src="/fr/spikes/24-gpu-clipmap-rings/" title="Spike 24 : anneaux de clipmap et brume céleste" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background:#000" loading="lazy" allowfullscreen></iframe>
</div>
<p><a href="/fr/spikes/24-gpu-clipmap-rings/" target="_blank">Ouvrir le spike 24 dans un nouvel onglet ↗</a> · <a href="/fr/blog/spike-source?spike=24-gpu-clipmap-rings">Voir le code source</a></p>
<p>Puis est venue l’intégration de la brume et du ciel. Nous voulions que le terrain lointain se fonde dans la couleur réelle du ciel, et non dans une constante uniforme. Le shader de brume devait donc connaître la couleur qu’aurait le ciel dans la direction de chaque fragment. Nous avons chargé une texture HDR de skybox équirectangulaire et l’avons échantillonnée dans le fragment shader à partir de la direction de vue allant de la caméra au fragment, convertie en coordonnées UV équirectangulaires grâce au nœud <code>equirectUV</code> de TSL. Le facteur de brume dépendait de la distance, avec <code>positionView.z.negate()</code> pour la profondeur dans l’espace caméra, et était mélangé avec <code>smoothstep</code> entre une distance proche et une distance lointaine.</p>
<p>Le raccordement des modules s’est révélé plus pénible que n’importe quelle partie de la géométrie. Nous sommes passés à Three.js 0.183.1, qui avait restructuré ses fichiers de build. L’import <code>three/tsl</code> devait être résolu vers <code>three.tsl.js</code>, tandis que TSL importait en interne <code>three/webgpu</code> sous forme de spécificateur nu. Les deux correspondances devaient être déclarées explicitement dans l’import map HTML. L’absence de l’une ou de l’autre produisait des erreurs cryptiques comme « does not provide an export » ou « failed to resolve module specifier », sans indiquer quelle correspondance était incorrecte. Une fois les deux ajoutées à l’import map, le graphe de shaders s’est chargé correctement.</p>
<p>Nous avons également rencontré un problème d’orientation du skybox : la texture était affichée à l’envers. La solution consistait à définir <code>flipY = true</code> sur la texture équirectangulaire. Il s’agit de la valeur par défaut de Three.js pour les textures chargées, mais notre code initial la définissait sur <code>false</code>.</p>
<p>La première implémentation de la brume échantillonnait le ciel dans une direction presque constante, ce qui produisait une fine bande aux couleurs de l’horizon au lieu d’un dégradé naturel. La solution a été de calculer, pour chaque pixel, la véritable direction dans l’espace monde allant de la caméra au fragment avec <code>positionWorld.sub(cameraPosition).normalize()</code>, puis de la transmettre à <code>equirectUV</code> pour rechercher la couleur de la brume. Les fragments du terrain se fondent ainsi dans la couleur du ciel qui se trouve réellement derrière eux, ce qui reste correct quel que soit l’angle de la caméra.</p>
<p>Au-delà de toutes ces corrections individuelles, le résultat essentiel est resté intact. Nous disposons désormais d’un système de terrain combinant l’édition volumétrique en champ proche — marching cubes avec raccords Transvoxel —, des chunks de heightmap à moyenne distance et des anneaux de clipmap à longue distance, le tout piloté par une couche de stratégie qui détermine le mode, le LOD et le comportement des transitions.</p>
<p>Si je devais citer les méthodes que je réutiliserais dans le prochain projet, ce seraient celles-ci :</p>
<p>Commencer par des spikes consacrés aux risques avant de développer les fonctionnalités. Le spike 1 a tranché la question « sommes-nous seulement capables d’effectuer le rendu assez vite ? » avant que nous n’investissions dans les pipelines de contenu.</p>
<p>Figer des bases de référence fiables avant les grands sauts d’intégration. Les spikes 13 et 14 nous ont épargné des jours de recherche dichotomique des régressions.</p>
<p>Imposer une couche de stratégie et de l’observabilité avant de se lancer dans des marathons d’optimisation. Le spike 23 a transformé des bugs mystérieux en conditions nommées assorties de règles de déclenchement.</p>
<p>Tester en mouvement, pas avec des captures d’écran. Les sauts de clipmap, le scintillement des raccords et les à-coups de streaming restent tous invisibles dans les images fixes.</p>
<p>Mesurer le coût en temps de frame de chaque fonctionnalité, et non les FPS moyens. Les moyennes masquent les pics que les utilisateurs ressentent réellement.</p>
<p>Et publier les parties chaotiques. Les fausses pistes, les traques de fantômes causés par des buffers obsolètes, les deux jours passés à accuser la logique de transition alors que la plage de dessin était incorrecte. Ce sont les parties dont les gens peuvent vraiment tirer des enseignements.</p>
<h2>Vérification externe : les journaux de développement de Vuntra City</h2>
<p>Après avoir terminé cette série, nous avons examiné les journaux de développement de <code>@VuntraCity</code> afin de confronter nos hypothèses sur les mondes ouverts à une implémentation externe. Il s’agit d’un projet UE5 natif, et non d’une stack pour navigateur, mais les architectures des systèmes sont suffisamment proches pour rendre la comparaison utile.</p>
<p>Le premier enseignement est que la vitesse de déplacement doit être traitée comme un paramètre de contrôle du streaming, et pas seulement comme un élément de gameplay. Dans Vuntra City, les déplacements à grande vitesse sont volontairement dirigés au-dessus de la plupart des intérieurs, et la portée des détails s’adapte à la vitesse de déplacement afin d’éviter la création et la suppression incessantes d’objets ainsi que les blocages (<a href="https://www.youtube.com/watch?v=KKeBElJS6-M">système de transport</a>, <a href="https://www.youtube.com/watch?v=aPuYXyJet38">techniques de performance</a>). Cela correspond à l’orientation de notre couche de stratégie : le mode de déplacement doit influencer directement le rayon des chunks, l’activation des intérieurs et la quantité de travail autorisée par frame.</p>
<p>Le deuxième enseignement concerne l’architecture. Leur système de cartes et d’adresses nécessitait de séparer la topologie du monde des objets rendus afin que les requêtes globales puissent s’exécuter sur les régions non chargées (<a href="https://www.youtube.com/watch?v=6dLn1GQpu2c">cartes et adresses</a>). C’est la même séparation dont nous avons besoin dans le navigateur pour la recherche dans le monde, le calcul des itinéraires de quêtes, les analyses de modération et l’indexation des points d’intérêt, sans imposer des chemins de données liés au rendu.</p>
<p>Le troisième enseignement est la hiérarchisation de la simulation. Leur conception à un million de PNJ maintient un état global et peu coûteux des emplois du temps, puis ne consacre le budget des comportements complexes qu’aux PNJ proches du joueur (<a href="https://www.youtube.com/watch?v=nBV0yAAJUf0">présentation du million de PNJ</a>, <a href="https://www.youtube.com/watch?v=eUi7DB1ar3s">analyse approfondie du système</a>). Cela conforte notre propre modèle de simulation centré sur la zone d’intérêt, dans lequel la fidélité en champ proche et le déterminisme en champ lointain constituent des préoccupations distinctes dotées de budgets séparés.</p>
<p>Le quatrième enseignement porte sur la qualité de la conception, et non sur l’échelle brute. Leurs meilleurs moments d’exploration reposent sur des distributions pondérées, de rares valeurs atypiques et des indices de navigation diégétiques plutôt que sur des superpositions d’interface permanentes (<a href="https://www.youtube.com/watch?v=4MZ5-KQW3pc">notes sur les environnements procéduraux</a>, <a href="https://www.youtube.com/watch?v=ixR1hqZJlv4">boucle sans minicarte</a>). Pour nous, cela rappelle que les systèmes techniques doivent être réglés afin de produire une diversité propice à la découverte, et non simplement un débit maximal.</p>
<h2>Technologies abordées dans ce chapitre</h2>
<p><strong>Géométrie des anneaux de clipmap.</strong> Chaque anneau est un maillage en grille plane centré sur la caméra, dont les sommets sont déplacés en fonction des échantillons de la heightmap. L’anneau intérieur utilise la pleine résolution. Chaque anneau suivant double l’espacement des sommets et couvre une zone plus vaste. La difficulté réside dans la frontière : lorsqu’un anneau haute résolution rencontre un anneau basse résolution, les sommets de bord du maillage le plus fin s’alignent sur le milieu de l’arête du maillage plus grossier. Cette technique provient de l’article de Losasso et Hoppe publié à SIGGRAPH en 2004 (<a href="https://hhoppe.com/geomclipmap.pdf">PDF</a>) et est détaillée dans le <a href="https://developer.nvidia.com/gpugems/gpugems2/part-i-geometric-complexity/chapter-2-terrain-rendering-using-gpu-based-geometry">chapitre 2 de GPU Gems 2</a>. Consultez notre <a href="/fr/guides/landscape-generation-browser#geometry-clipmaps">guide sur les paysages consacré aux geometry clipmaps</a>.</p>
<p><strong>Morphing de bord 2:1.</strong> À la frontière entre deux anneaux de clipmap, l’anneau le plus fin possède des sommets à des positions que l’anneau plus grossier ne partage pas. Les sommets de frontière dont la coordonnée de grille est impaire le long du bord de l’anneau sont détectés, puis leur hauteur est interpolée entre les deux sommets pairs voisins. Cela produit des raccords étanches sans géométrie de transition dédiée. L’interpolation s’exécute dans le vertex shader : <code>morphedHeight = mix(heightLeft, heightRight, 0.5)</code> pour les sommets de frontière, en utilisant le même mécanisme de géomorphing que celui décrit dans <a href="/fr/guides/landscape-generation-browser#geomorphing-pop-free-lod-transitions">notre guide</a>.</p>
<p><strong>Projection de skybox équirectangulaire.</strong> Une image 2D unique représentant l’ensemble des directions de la sphère céleste au moyen d’une projection longitude-latitude. L’axe horizontal couvre 0 à 360 degrés et l’axe vertical 0 à 180 degrés. Une direction de vue normalisée $\hat{d} = (d_x, d_y, d_z)$ est convertie en coordonnées UV à partir de sa longitude et de sa latitude :</p>
<p>$$
u = \frac{1}{2} + \frac{\operatorname{atan2}(d_z,, d_x)}{2\pi}, \qquad v = \frac{1}{2} - \frac{\arcsin(d_y)}{\pi}
$$</p>
<p>Dans Three.js, définir <code>texture.mapping = EquirectangularReflectionMapping</code> avec <code>SRGBColorSpace</code> permet de l’utiliser comme arrière-plan de la scène. Dans TSL, <code>equirectUV(direction)</code> applique la même conversion et transforme une direction de vue 3D en coordonnées UV 2D permettant d’échantillonner la texture.</p>
<p><strong>Couleur de brume par fragment tirée du ciel.</strong> La brume standard fond les fragments vers une couleur constante unique. Dans une scène dotée d’un skybox détaillé, le résultat paraît incorrect, car la couleur du ciel varie selon la direction. La solution consiste à calculer, pour chaque pixel, la direction dans l’espace monde allant de la caméra au fragment (<code>positionWorld.sub(cameraPosition).normalize()</code>), puis à échantillonner le skybox dans cette direction pour obtenir la couleur de la brume. Chaque fragment se fond vers la couleur du ciel qui se trouve réellement derrière lui, ce qui produit un mélange correct quel que soit l’angle de la caméra. Le facteur de brume utilise <code>smoothstep(nearDist, farDist, viewDepth)</code> avec <code>positionView.z.negate()</code> pour la profondeur dans l’espace caméra.</p>
<p><strong>Import maps pour les modules ES.</strong> Un mécanisme natif du navigateur (<code>&lt;script type=&quot;importmap&quot;&gt;</code>) qui associe des spécificateurs de modules nus — comme <code>three/tsl</code> — à des URL réelles. Lorsque Three.js 0.183.1 a restructuré ses fichiers de build, <code>three/tsl</code> devait être résolu vers <code>three.tsl.js</code>, tandis que TSL importait en interne <code>three/webgpu</code> sous forme de spécificateur nu. Les deux correspondances devaient être déclarées explicitement dans l’import map, faute de quoi le navigateur produisait des erreurs comme « does not provide an export » ou « failed to resolve module specifier ».</p>
<h2>Pour aller plus loin</h2>
<p>Pour une présentation plus approfondie des technologies employées dans cette série, consultez nos guides complémentaires :</p>
<ul>
<li><a href="/fr/guides/landscape-generation-browser">Génération de paysages avec LOD dynamique et streaming pour les mondes ouverts dans le navigateur</a> traite des heightmaps, des SDF, des marching cubes, de Transvoxel, des geometry clipmaps, du géomorphing, de l’architecture de streaming, des matériaux de terrain et du rendu de la végétation.</li>
<li><a href="/fr/guides/browser-3d-open-world-tech">Technologies 3D de monde ouvert dans le navigateur pour les mondes multijoueurs créés par les utilisateurs</a> traite des stacks de rendu, de WebGPU, de la physique, du réseau, de l’architecture multijoueur et des enseignements tirés de Skyrim, The Witcher 3, Breath of the Wild et GTA V.</li>
</ul>
<p>Merci d’avoir suivi cette aventure en douze parties.</p>
<p>Partie 1 : <a href="/fr/blog/2026-02-25-open-world-browser-part-01-risk-first">Nous avons commencé par essayer de tout casser</a><br>
Partie 2 : <a href="/fr/blog/2026-02-25-open-world-browser-part-02-worker-physics">La physique dans un worker et la crainte de la latence des commandes</a><br>
Partie 3 : <a href="/fr/blog/2026-02-25-open-world-browser-part-03-the-unflashy-spikes">Les spikes peu spectaculaires qui nous ont sauvés</a><br>
Partie 4 : <a href="/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy">Le streaming avant les terrains sophistiqués</a><br>
Partie 5 : <a href="/fr/blog/2026-02-25-open-world-browser-part-05-budgeting-the-pretty">Budgéter les éléments visuels</a><br>
Partie 6 : <a href="/fr/blog/2026-02-25-open-world-browser-part-06-clipmaps">Les clipmaps ont changé la donne</a><br>
Partie 7 : <a href="/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes">Les marching cubes et les premières véritables grottes</a><br>
Partie 8 : <a href="/fr/blog/2026-02-25-open-world-browser-part-08-webgpu-integration">Intégrer sans perdre notre base de référence</a><br>
Partie 9 : <a href="/fr/blog/2026-02-25-open-world-browser-part-09-transvoxel-first-cut">Transvoxel a commencé par une ossature</a><br>
Partie 10 : <a href="/fr/blog/2026-02-25-open-world-browser-part-10-seam-chaos">Le chaos des raccords et le boss final des coins</a><br>
Partie 11 : <a href="/fr/blog/2026-02-25-open-world-browser-part-11-policy-modes">Un mode piloté par une stratégie, pas codé en dur</a></p>
<hr>
<p>Partie 12 sur 14.<br>
Précédent : <a href="/fr/blog/2026-02-25-open-world-browser-part-11-policy-modes">Partie 11 - Un mode piloté par une stratégie, pas codé en dur</a><br>
Suivant : <a href="/fr/blog/2026-04-13-open-world-browser-part-13-terrain-sculpting">Partie 13 - Sculpture du terrain et mort de la fonction mathématique</a><br>
Guide de la série : <a href="/fr/blog/2026-02-25-open-world-browser-series-guide">/fr/blog/2026-02-25-open-world-browser-series-guide</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Guide de la série sur les mondes ouverts dans le navigateur]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-series-guide</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-25-open-world-browser-series-guide</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Ordre de lecture de la série en 29 parties sur les mondes ouverts dans le navigateur, avec un glossaire clair des spikes et une carte des démos en direct.]]></description>
            <content:encoded><![CDATA[<h1>Guide de la série sur les mondes ouverts dans le navigateur</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO et cofondateur de Cinevva</em></p>
<p>Cette page est le point de navigation central de la série complète.</p>
<p>Si vous êtes arrivé ici depuis une partie prise au hasard, commencez par cette page.</p>
<p>Mise à jour de mars 2026 : nous avons ajouté dans la <a href="/fr/blog/2026-02-25-open-world-browser-part-12-lessons">Partie 12</a> une vérification externe de l’implémentation, tirée des journaux de développement de Vuntra City, et enrichi le <a href="/fr/guides/browser-3d-open-world-tech">guide technique des mondes ouverts dans le navigateur</a> avec les mêmes enseignements.</p>
<p>Mise à jour d’avril 2026 : les Parties 13 et 14 couvrent la sculpture du terrain (Spikes 25 à 27) ainsi que la couche visuelle et ludique : texturation multi-matériaux, végétation instanciée et contrôleur de personnage avec collisions HM+SDF (Spikes 28 à 30).</p>
<p>Mise à jour de mai 2026 : la série a atteint 29 parties. Les Parties 15 à 29 couvrent le long milieu du développement : terrain multijoueur et eau, refactorisation de la structure du code, animations et dispersion assistée par IA, imposteurs et parallaxe, le tampon de visibilité qui n’a pas été rentable, nuages et culling de meshlets, avatars regroupés en lots et chat vocal de proximité, persistance du monde et vent sur GPU, personnages universels, trois approches de l’eau, île procédurale, herbe et occlusion, ainsi qu’un contrôleur de personnage interchangeable capable de piloter n’importe quel corps (Spikes 31 à 60).</p>
<p>Mise à jour de juin 2026 : la Partie 30 ajoute une caméra tenant compte des collisions, avec une perche à ressort, un contrat de collision interchangeable et une sonde de distance signée qui empêche la vue de traverser les surplombs et les plafonds des grottes, et pas seulement les collines.</p>
<h2>Ce que signifie « spike » dans cette série</h2>
<p>Un spike est une expérience courte et ciblée.</p>
<p>Chaque spike met à l’épreuve une question risquée. Nous limitons son périmètre, mesurons ce qui compte et décidons de la prochaine étape. Considérez-le comme un outil d’apprentissage, pas comme une fonctionnalité finalisée.</p>
<p>Dans ce projet, les spikes sont principalement des pages autonomes sous <code>/spikes/*</code> que vous pouvez exécuter en direct.</p>
<h2>Lire la série dans l’ordre</h2>
<ol>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-01-risk-first">Partie 1 : Nous avons commencé par essayer de tout casser</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-02-worker-physics">Partie 2 : La physique dans un worker et la crainte de la latence des commandes</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-03-the-unflashy-spikes">Partie 3 : Les spikes peu spectaculaires qui nous ont sauvés</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-04-streaming-before-fancy">Partie 4 : Le streaming avant les terrains sophistiqués</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-05-budgeting-the-pretty">Partie 5 : Budgétiser les effets visuels</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-06-clipmaps">Partie 6 : Les clipmaps ont changé la donne</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-07-marching-cubes">Partie 7 : Marching cubes et les premières vraies grottes</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-08-webgpu-integration">Partie 8 : Intégrer sans perdre notre référence de base</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-09-transvoxel-first-cut">Partie 9 : Transvoxel a commencé par une ossature</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-10-seam-chaos">Partie 10 : Le chaos des jointures et le combat de boss dans les angles</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-11-policy-modes">Partie 11 : Un mode défini par des règles, pas codé en dur</a></li>
<li><a href="/fr/blog/2026-02-25-open-world-browser-part-12-lessons">Partie 12 : Anneaux, brume céleste et ce que nous referions</a></li>
<li><a href="/fr/blog/2026-04-13-open-world-browser-part-13-terrain-sculpting">Partie 13 : La sculpture du terrain et la mort de la fonction mathématique</a></li>
<li><a href="/fr/blog/2026-04-13-open-world-browser-part-14-world-comes-alive">Partie 14 : Le monde prend vie</a></li>
<li><a href="/fr/blog/2026-04-13-open-world-browser-part-15-multiplayer-and-water">Partie 15 : Remplacer la base, puis la synchroniser</a></li>
<li><a href="/fr/blog/2026-04-28-open-world-browser-part-16-structure-and-authoring">Partie 16 : Une structure adaptée à un monde qui ne cesse de grandir</a></li>
<li><a href="/fr/blog/2026-05-01-open-world-browser-part-17-animations-and-search">Partie 17 : Des animations sans retargeting et une recherche de ressources en direct</a></li>
<li><a href="/fr/blog/2026-05-02-open-world-browser-part-18-ai-scattering">Partie 18 : Un pinceau de dispersion qui donne l’impression d’un placement par IA</a></li>
<li><a href="/fr/blog/2026-05-04-open-world-browser-part-19-imposters">Partie 19 : L’imposteur qui doit survivre à une forêt</a></li>
<li><a href="/fr/blog/2026-05-05-open-world-browser-part-20-parallax-occlusion">Partie 20 : Simuler la profondeur sur un plan plat</a></li>
<li><a href="/fr/blog/2026-05-06-open-world-browser-part-21-visibility-buffer">Partie 21 : Un moteur de rendu plus rapide qui ne l’était pas</a></li>
<li><a href="/fr/blog/2026-05-07-open-world-browser-part-22-clouds-and-meshlets">Partie 22 : Des nuages que l’on peut éclairer et un culling qu’il faut alimenter</a></li>
<li><a href="/fr/blog/2026-05-08-open-world-browser-part-23-avatars-and-voice">Partie 23 : Cinquante avatars et une voix dans la pièce</a></li>
<li><a href="/fr/blog/2026-05-09-open-world-browser-part-24-persistence-and-wind">Partie 24 : Sauvegarder un monde et rendre le vent visible</a></li>
<li><a href="/fr/blog/2026-05-10-open-world-browser-part-25-universal-characters">Partie 25 : Un seul squelette pour toutes les tenues</a></li>
<li><a href="/fr/blog/2026-05-12-open-world-browser-part-26-water">Partie 26 : Trois façons de créer de l’eau</a></li>
<li><a href="/fr/blog/2026-05-13-open-world-browser-part-27-island-and-terrain">Partie 27 : Une île créée à partir de bruit, un sol qui ressemble vraiment à un sol</a></li>
<li><a href="/fr/blog/2026-05-14-open-world-browser-part-28-grass-and-occlusion">Partie 28 : De l’herbe jusqu’à l’horizon et un sol qui se masque lui-même</a></li>
<li><a href="/fr/blog/2026-05-15-open-world-browser-part-29-pluggable-character">Partie 29 : Un seul contrôleur pour n’importe quel corps</a></li>
<li><a href="/fr/blog/2026-06-04-open-world-browser-part-30-collision-aware-camera">Partie 30 : Une caméra qui respecte les murs</a></li>
</ol>
<h2>Carte des spikes en direct</h2>
<p>Commencez par ceux-ci si vous souhaitez parcourir directement la chronologie technique.</p>
<p><a href="/fr/spikes/01-terrain/" target="_blank">Spike 1</a> terrain et instanciation · <a href="/fr/blog/spike-source?spike=01-terrain">source</a><br>
<a href="/fr/spikes/02-rapier-worker/" target="_blank">Spike 2</a> physique dans un worker · <a href="/fr/blog/spike-source?spike=02-rapier-worker">source</a><br>
Le Spike 3, un test de charge de diffusion avec Durable Objects, s’exécutait comme script de service et non comme page autonome<br>
Spike 4 : contraintes de qualité sur mobile, testées via un paramètre de requête du Spike 1<br>
Spike 5 : fiabilité du comportement du LLM, exécuté comme script et non comme page autonome<br>
<a href="/fr/spikes/06-chunk-streaming/" target="_blank">Spike 6</a> chargement et permutation des chunks · <a href="/fr/blog/spike-source?spike=06-chunk-streaming">source</a><br>
<a href="/fr/spikes/07-gpu-vegetation/" target="_blank">Spike 7</a> végétation générée à partir de cartes de densité · <a href="/fr/blog/spike-source?spike=07-gpu-vegetation">source</a><br>
<a href="/fr/spikes/08-terrain-material/" target="_blank">Spike 8</a> coût des matériaux du terrain · <a href="/fr/blog/spike-source?spike=08-terrain-material">source</a><br>
<a href="/fr/spikes/09-csm-shadows/" target="_blank">Spike 9</a> budget des ombres en cascade · <a href="/fr/blog/spike-source?spike=09-csm-shadows">source</a><br>
<a href="/fr/spikes/10-clipmap-geomorph/" target="_blank">Spike 10</a> clipmaps et géomorphing · <a href="/fr/blog/spike-source?spike=10-clipmap-geomorph">source</a><br>
<a href="/fr/spikes/11-chunk-streaming/" target="_blank">Spike 11</a> streaming de chunks de cartes de hauteur · <a href="/fr/blog/spike-source?spike=11-chunk-streaming">source</a><br>
<a href="/fr/spikes/12-webgpu-marching-cubes/" target="_blank">Spike 12</a> marching cubes avec WebGPU · <a href="/fr/blog/spike-source?spike=12-webgpu-marching-cubes">source</a><br>
<a href="/fr/spikes/13-threejs-webgpu/" target="_blank">Spike 13</a> base de référence pour l’intégration de WebGPU · <a href="/fr/blog/spike-source?spike=13-threejs-webgpu">source</a><br>
<a href="/fr/spikes/14-threejs-webgpu-incremental/" target="_blank">Spike 14</a> consolidation progressive · <a href="/fr/blog/spike-source?spike=14-threejs-webgpu-incremental">source</a><br>
<a href="/fr/spikes/15-transvoxel-seam/" target="_blank">Spike 15</a> ossature des jointures · <a href="/fr/blog/spike-source?spike=15-transvoxel-seam">source</a><br>
<a href="/fr/spikes/16-transvoxel-face/" target="_blank">Spike 16</a> première face Transvoxel · <a href="/fr/blog/spike-source?spike=16-transvoxel-face">source</a><br>
<a href="/fr/spikes/17-dual-mc-lod/" target="_blank">Spike 17</a> double LOD MC · <a href="/fr/blog/spike-source?spike=17-dual-mc-lod">source</a><br>
<a href="/fr/spikes/18-transvoxel-heightmap-seam/" target="_blank">Spike 18</a> jointure de carte de hauteur · <a href="/fr/blog/spike-source?spike=18-transvoxel-heightmap-seam">source</a><br>
<a href="/fr/spikes/19-transvoxel-corner-grid/" target="_blank">Spike 19</a> angle à résolutions mixtes · <a href="/fr/blog/spike-source?spike=19-transvoxel-corner-grid">source</a><br>
<a href="/fr/spikes/20-gpu-transvoxel-corner/" target="_blank">Spike 20</a> jointure d’angle sur GPU · <a href="/fr/blog/spike-source?spike=20-gpu-transvoxel-corner">source</a><br>
<a href="/fr/spikes/21-gpu-mc-transvoxel-corner/" target="_blank">Spike 21</a> jointure d’angle MC sur GPU · <a href="/fr/blog/spike-source?spike=21-gpu-mc-transvoxel-corner">source</a><br>
<a href="/fr/spikes/22-gpu-mc-heightmap-fallback/" target="_blank">Spike 22</a> repli de MC vers HM · <a href="/fr/blog/spike-source?spike=22-gpu-mc-heightmap-fallback">source</a><br>
<a href="/fr/spikes/23-policy-chunk-modes/" target="_blank">Spike 23</a> modes de chunks définis par des règles · <a href="/fr/blog/spike-source?spike=23-policy-chunk-modes">source</a><br>
<a href="/fr/spikes/24-gpu-clipmap-rings/" target="_blank">Spike 24</a> anneaux de clipmap et brume céleste · <a href="/fr/blog/spike-source?spike=24-gpu-clipmap-rings">source</a><br>
<a href="/fr/spikes/25-heightmap-brush/" target="_blank">Spike 25</a> pinceau de carte de hauteur sur des données échantillonnées · <a href="/fr/blog/spike-source?spike=25-heightmap-brush">source</a><br>
<a href="/fr/spikes/26-sdf-brush/" target="_blank">Spike 26</a> pinceau SDF via calcul GPU · <a href="/fr/blog/spike-source?spike=26-sdf-brush">source</a><br>
<a href="/fr/spikes/27-hm-mc-brush/" target="_blank">Spike 27</a> intégration HM + MC + pinceau · <a href="/fr/blog/spike-source?spike=27-hm-mc-brush">source</a><br>
<a href="/fr/spikes/28-multi-material/" target="_blank">Spike 28</a> texturation multi-matériaux · <a href="/fr/blog/spike-source?spike=28-multi-material">source</a><br>
<a href="/fr/spikes/29-vegetation/" target="_blank">Spike 29</a> végétation et herbe par shader · <a href="/fr/blog/spike-source?spike=29-vegetation">source</a><br>
<a href="/fr/spikes/30-physics/" target="_blank">Spike 30</a> physique du terrain et contrôleur de personnage · <a href="/fr/blog/spike-source?spike=30-physics">source</a><br>
<a href="/fr/spikes/31-multiplayer-sync/" target="_blank">Spike 31</a> synchronisation du terrain en multijoueur · <a href="/fr/blog/spike-source?spike=31-multiplayer-sync">source</a><br>
<a href="/fr/spikes/32-water-swimming/" target="_blank">Spike 32</a> eau et nage · <a href="/fr/blog/spike-source?spike=32-water-swimming">source</a><br>
<a href="/fr/spikes/33-code-structure/" target="_blank">Spike 33</a> refactorisation de la structure du code · <a href="/fr/blog/spike-source?spike=33-code-structure">source</a><br>
<a href="/fr/spikes/34-world-authoring/" target="_blank">Spike 34</a> création directement dans le monde · <a href="/fr/blog/spike-source?spike=34-world-authoring">source</a><br>
<a href="/fr/spikes/35-combat-animations/" target="_blank">Spike 35</a> animations de combat et retargeting · <a href="/fr/blog/spike-source?spike=35-combat-animations">source</a><br>
<a href="/fr/spikes/36-polyhaven-models/" target="_blank">Spike 36</a> recherche en direct de ressources Poly Haven · <a href="/fr/blog/spike-source?spike=36-polyhaven-models">source</a><br>
<a href="/fr/spikes/37-ai-scattering/" target="_blank">Spike 37</a> dispersion d’objets assistée par IA · <a href="/fr/blog/spike-source?spike=37-ai-scattering">source</a><br>
<a href="/fr/spikes/38-imposters/" target="_blank">Spike 38</a> imposteurs octaédriques · <a href="/fr/blog/spike-source?spike=38-imposters">source</a><br>
<a href="/fr/spikes/39-parallax-pom/" target="_blank">Spike 39</a> parallax occlusion mapping · <a href="/fr/blog/spike-source?spike=39-parallax-pom">source</a><br>
<a href="/fr/spikes/40-visibility-buffer/" target="_blank">Spike 40</a> moteur de rendu avec tampon de visibilité · <a href="/fr/blog/spike-source?spike=40-visibility-buffer">source</a><br>
<a href="/fr/spikes/41-imposter-forest/" target="_blank">Spike 41</a> imposteurs dans une forêt · <a href="/fr/blog/spike-source?spike=41-imposter-forest">source</a><br>
<a href="/fr/spikes/42-imposter-flicker/" target="_blank">Spike 42</a> correction du scintillement des imposteurs · <a href="/fr/blog/spike-source?spike=42-imposter-flicker">source</a><br>
<a href="/fr/spikes/43-clouds-atmosphere/" target="_blank">Spike 43</a> atmosphère et nuages volumétriques · <a href="/fr/blog/spike-source?spike=43-clouds-atmosphere">source</a><br>
<a href="/fr/spikes/44-meshlet-clusters/" target="_blank">Spike 44</a> culling Hi-Z des meshlets · <a href="/fr/blog/spike-source?spike=44-meshlet-clusters">source</a><br>
<a href="/fr/spikes/45-avatar-network-sync/" target="_blank">Spike 45</a> skinning des avatars par lots et synchronisation réseau · <a href="/fr/blog/spike-source?spike=45-avatar-network-sync">source</a><br>
<a href="/fr/spikes/46-proximity-voice/" target="_blank">Spike 46</a> chat vocal de proximité · <a href="/fr/blog/spike-source?spike=46-proximity-voice">source</a><br>
<a href="/fr/spikes/47-world-persistence/" target="_blank">Spike 47</a> persistance du monde avec autorité serveur · <a href="/fr/blog/spike-source?spike=47-world-persistence">source</a><br>
Spike 48 : intégration du rig d’avatar, réutilisée comme code partagé par les spikes suivants (aucune page autonome)<br>
<a href="/fr/spikes/49-gpu-wind-props/" target="_blank">Spike 49</a> vent sur GPU appliqué aux objets et au feuillage · <a href="/fr/blog/spike-source?spike=49-gpu-wind-props">source</a><br>
<a href="/fr/spikes/50-universal-characters/" target="_blank">Spike 50</a> personnages universels et accessoires portables · <a href="/fr/blog/spike-source?spike=50-universal-characters">source</a><br>
<a href="/fr/spikes/51-water-ssr-caustics/" target="_blank">Spike 51</a> eau avec réflexions en espace écran · <a href="/fr/blog/spike-source?spike=51-water-ssr-caustics">source</a><br>
<a href="/fr/spikes/52-water-planar/" target="_blank">Spike 52</a> eau avec miroir planaire · <a href="/fr/blog/spike-source?spike=52-water-planar">source</a><br>
<a href="/fr/spikes/53-water-pro/" target="_blank">Spike 53</a> bibliothèque d’eau prête pour la production · <a href="/fr/blog/spike-source?spike=53-water-pro">source</a>
<a href="/fr/spikes/54-procgen-island/" target="_blank">Spike 54</a> génération procédurale d’îles · <a href="/fr/blog/spike-source?spike=54-procgen-island">source</a><br>
<a href="/fr/spikes/55-terrain-shading/" target="_blank">Spike 55</a> rendu du terrain et atténuation de la répétition des textures · <a href="/fr/blog/spike-source?spike=55-terrain-shading">source</a><br>
<a href="/fr/spikes/56-grass/" target="_blank">Spike 56</a> herbe en quadruples plans croisés rendue par le GPU · <a href="/fr/blog/spike-source?spike=56-grass">source</a><br>
<a href="/fr/spikes/57-terrain-occlusion/" target="_blank">Spike 57</a> élimination des parties occultées du terrain · <a href="/fr/blog/spike-source?spike=57-terrain-occlusion">source</a><br>
<a href="/fr/spikes/58-pluggable-character/" target="_blank">Spike 58</a> contrôleur de personnage interchangeable · <a href="/fr/blog/spike-source?spike=58-pluggable-character">source</a><br>
<a href="/fr/spikes/59-pluggable-with-avatar/" target="_blank">Spike 59</a> contrôleur avec un véritable avatar · <a href="/fr/blog/spike-source?spike=59-pluggable-with-avatar">source</a><br>
<a href="/fr/spikes/60-polygon-animations/" target="_blank">Spike 60</a> animations de personnages polygonaux Synty · <a href="/fr/blog/spike-source?spike=60-polygon-animations">source</a></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Breaker Belt : quand Snake rencontre Arkanoid, développé en vibe coding en trois jours]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-18-a-breaker-belt</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-18-a-breaker-belt</guid>
            <pubDate>Wed, 18 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Un serpent cosmique brise des briques au fil de 50 vagues, avec une musique réactive, une narration par IA et 23 types de briques. Deux personnes. Trois jours. Sur le Web, mobile et PC.]]></description>
            <content:encoded><![CDATA[<h1>A Breaker Belt : quand Snake rencontre Arkanoid, développé en vibe coding en trois jours</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, CEO de Cinevva</em></p>
<img src="https://cdn.cinevva.com/blog/breaker-belt-cover.webp" alt="Gameplay d’A Breaker Belt : un serpent cosmique néon brisant des briques dans l’espace" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>Nous avons créé un jeu où votre serpent fait office de raquette et où les briques ripostent. Il est sorti sur le Web, mobile et PC. Nous étions deux et il nous a fallu environ trois jours. Par intermittence, sans travailler dans l’urgence. Et il est vraiment amusant à jouer.</p>
<p>C’est ce dernier point qui mérite toute votre attention.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:8px;margin:1.5rem 0">
<iframe src="https://www.youtube.com/embed/VaFkfCT3OuU" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen></iframe>
</div>
<h2>Le mélange que personne n’avait demandé</h2>
<p>Snake repose sur la croissance et la perception de l’espace. Arkanoid repose sur les réflexes et l’anticipation des angles. Leurs philosophies de game design sont radicalement différentes, et les combiner ressemble au genre de pitch que l’on décline poliment.</p>
<p>Mais <a href="https://app.cinevva.com/engine">A Breaker Belt</a> réussit ce pari. Vous incarnez un serpent cosmique, un arc vivant de courant néon composé de vertèbres en métal à la dérive et de soie de comète, qui se faufile dans un champ d’astéroïdes rempli de blocs destructibles. Votre tête sert de raquette. Votre queue, qui ne cesse de grandir, est à la fois votre meilleure arme et votre menace la plus constante. Les orbes rebondissent sur votre corps pour fracasser les briques, mais un seul mauvais virage dans votre propre queue et la partie est terminée.</p>
<p>C’est le genre d’expérience hybride étrange qui disparaît généralement avant même que quiconque puisse y jouer, car le coût nécessaire pour découvrir si une idée insolite fonctionne se mesurait traditionnellement en mois. Ici, il s’est mesuré en après-midis.</p>
<h2>Ce qui est réellement sorti</h2>
<p>C’est l’ampleur du projet qui le rend intéressant. Il ne s’agit pas d’un prototype de game jam composé de rectangles temporaires et dépourvu de son.</p>
<p>Le jeu compte 50 vagues. Et ce ne sont pas 50 variations du même mur de briques. Les formations évoluent, passant d’arcs d’initiation accessibles à des anneaux fortifiés qui exigent des tirs inclinés à travers des ouvertures latérales, puis à des labyrinthes diagonaux superposés dotés de passages d’une seule case qui nécessitent une grande précision de pilotage. Les briques explosives font voler leurs voisines en éclats. Les briques fantômes apparaissent et disparaissent. Les briques régénératrices se réparent après avoir été brisées, vous obligeant à hiérarchiser vos cibles au lieu de tout balayer de gauche à droite. Les briques-portails téléportent vos orbes à travers l’arène. Les puits gravitationnels courbent vos tirs en hymnes lents et sinueux. Les émetteurs laser dessinent des lignes rouges dans le vide. Les briques mimétiques paraissent inoffensives jusqu’à ce qu’elles décident de ne plus l’être. À partir de la vague 15, vous évoluez dans un environnement qui ressemble moins à un puzzle qu’à un système vivant qui apprend vos habitudes.</p>
<p>La musique n’est pas une simple boucle. C’est une bande-son réactive qui s’enrichit au rythme de votre partie. Le système sous-jacent joue une ligne de basse, un synthé principal, une nappe, une grosse caisse, une caisse claire et un charleston en mi mineur à 140 BPM. À mesure que l’action s’intensifie, des couches supplémentaires de percussions et de synthétiseurs apparaissent progressivement. Quand le calme revient entre les vagues, les nappes d’ambiance prennent le relais. La musique respire avec vous. Un compositeur dédié passerait des semaines à régler un tel niveau de réactivité. Ici, cela faisait partie intégrante du processus créatif.</p>
<p>Les effets sonores ne sont pas des échantillons tirés d’un pack gratuit. Chaque brique fracassée, bonus récupéré, rebond d’orbe et collision est synthétisé en temps réel. La hauteur diffère selon que le contact se fait avec la tête ou la queue. Une réverbération chaleureuse renforce l’atmosphère spatiale. Un filtre passe-bas préserve le caractère satisfaisant des sons sans les rendre agressifs. Quand vous enchaînez un combo, l’audio vous en informe avant même l’écran.</p>
<p>Et il y a aussi la narration. Chaque vague débute par un nouvel élément narratif raconté par une voix générée par IA. L’écriture possède une véritable personnalité. Première vague : « Ils l’appellent la Ceinture des Briseurs : un ruban de débris artificiels qui encercle la vieille étoile comme un avertissement. » À la vague 38 : « La Ceinture cesse de ressembler à un mur et commence à ressembler à un esprit. Elle ne teste pas vos réflexes, mais vos habitudes. Vous brisez l’habitude. La Ceinture le remarque. » Ce sont 50 chapitres de mythologie cosmique qui vous donnent une raison de vous intéresser à ce serpent brisant des briques dans l’espace.</p>
<p>Les arrière-plans évoluent eux aussi. Les premières vagues présentent de paisibles champs d’étoiles indigo traversés par une douce pluie de météores. À mi-parcours apparaissent des bandes d’aurores et des nuages de nébuleuses. En fin de partie, place à une véritable tempête ionique, avec des traînées cyan acérées sur un espace presque noir. À partir de la vague 15, vous pénétrez dans le territoire de l’horizon des événements : un vide profond ponctué de filaments de nébuleuses se déplaçant lentement et d’un discret effet de lentille gravitationnelle créé par un trou noir. Le jeu communique la progression autant par son atmosphère que par sa difficulté.</p>
<p>Tout cela fonctionne au clavier, à la manette ou sur écran tactile. Le jeu est publié sur le Web, mobile et PC à partir d’une base de code unique. Aucune version distincte. Aucun portage.</p>
<h2>L’équipe dont nous n’avons pas eu besoin</h2>
<p>Voici ce qui devrait interpeller n’importe quel développeur de jeux.</p>
<p>Un jeu proposant un contenu aussi riche nécessiterait généralement un game designer, deux programmeurs, un artiste 2D, un sound designer, un compositeur, un level designer, un scénariste et une équipe d’assurance qualité. Soit huit ou neuf personnes. Même à un rythme indépendant raisonnable, cela représenterait trois à six mois de travail coordonné. Réunions quotidiennes, tickets Jira, pipelines de ressources et débogage propre à chaque plateforme.</p>
<p>Nous avons créé ce jeu à deux en un long week-end avec le <a href="https://app.cinevva.com/engine">Cinevva Engine</a>.</p>
<p>Le talent nécessaire n’a pas changé. C’est le rapport entre l’intention créative et la charge de mise en œuvre qui a évolué. Nous avons consacré notre temps à décider des sensations que le jeu devait procurer, plutôt qu’à lutter contre les outils pour parvenir à nos fins.</p>
<h2>Pourquoi c’est important si vous créez des choses</h2>
<p>La question intéressante n’est pas de savoir si les outils d’IA peuvent aider à créer des jeux plus rapidement. Nous avons déjà la réponse. La vraie question est de savoir ce qu’il advient des idées qui étaient autrefois trop risquées pour être tentées.</p>
<p>« Snake, mais vous êtes la raquette dans une arène à la Arkanoid » n’est pas le genre de projet qu’un producteur donne son feu vert. Quelques développeurs indépendants ont essayé des variantes de ce mélange sur <a href="http://itch.io">itch.io</a> (<a href="https://newdron.itch.io/breaksnake">BreakSnake</a>, <a href="https://neop87.itch.io/snakeout">SnakeOut</a>, <a href="https://merrak.itch.io/snake-break">Snake Break</a>), mais ce sont toutes de petites expériences issues de game jams. Cette fusion de genres n’a jamais bénéficié d’une véritable production avec de la musique réactive, une narration par IA, des dizaines de types de briques et 50 vagues au level design de plus en plus exigeant. Dans le développement traditionnel, un concept risqué nécessitant un tel niveau de finition est abandonné en réunion avant même que quiconque n’écrive une ligne de code.</p>
<p>Quand tenter quelque chose d’étrange coûte un après-midi plutôt que le budget d’un trimestre, les idées insolites prennent vie. Et certaines s’avèrent vraiment bonnes. A Breaker Belt en fait partie.</p>
<h2>Jouez-y. Ou créez le vôtre.</h2>
<p>Vous pouvez jouer à <a href="https://app.cinevva.com/engine">A Breaker Belt</a> dès maintenant sur le Web, mobile et PC. S’il vous donne envie de créer quelque chose, le <a href="https://app.cinevva.com/engine">Cinevva Engine</a> est gratuit. Décrivez ce que vous voulez, améliorez le résultat par itérations, puis publiez-le lorsqu’il est prêt. La <a href="https://app.cinevva.com/tools/music">musique</a>, les <a href="https://app.cinevva.com/tools/sfx">effets sonores</a>, les <a href="https://app.cinevva.com/tools/flux">ressources graphiques</a> et les <a href="https://app.cinevva.com/tools/hunyuan3d">modèles 3D</a> sont tous intégrés.</p>
<p>Votre idée de jeu étrange n’est peut-être qu’à trois jours de devenir réalité.</p>
<hr>
<p><em><a href="https://app.cinevva.com/engine">Jouer à A Breaker Belt</a> | <a href="https://app.cinevva.com/engine">Créer votre propre jeu</a> | <a href="https://cinevva.com/charts">Parcourir les jeux de la communauté</a></em></p>
<p><strong>À lire aussi :</strong></p>
<ul>
<li><a href="/fr/signals/2026-03-13-vibe-coding-new-game-jam">Le vibe coding est la nouvelle game jam</a> — comment les outils d’IA réduisent l’écart entre l’idée et le prototype</li>
<li><a href="/fr/guides/game-jams-hackathons">Game jams et hackathons</a> — le format qui rend possibles les mélanges les plus insolites</li>
<li><a href="/fr/guides/web-game-engines-comparison">Comparatif des moteurs de jeux Web</a> — les moteurs permettant de publier sur le Web, mobile et PC</li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nous ne pensions pas créer une station de radio]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-17-we-didnt-expect-a-radio-station</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-17-we-didnt-expect-a-radio-station</guid>
            <pubDate>Tue, 17 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Cinevva propose un générateur de musique permettant aux créateurs de jeux de composer des bandes-son. Mais certains se sont mis à créer de la musique simplement parce qu’ils le pouvaient. 362 morceaux plus tard, Cinevva Radio est en ligne.]]></description>
            <content:encoded><![CDATA[<h1>Nous ne pensions pas créer une station de radio</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, PDG de Cinevva</em></p>
<img src="https://cdn.cinevva.com/blog/cinevva-radio-lofi-cafe.png" alt="Cinevva Community Radio et ses 8 stations, dont Soundstage, Voltage, Lo-Fi Cafe, Main Stage, Ivory Tower et Electric Dreams, diffusant actuellement Quiet Flute Drone" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<p>La plateforme Cinevva propose un <a href="https://app.cinevva.com/tools/music">générateur de musique</a> qui permet aux créateurs de jeux de produire des bandes-son et des effets sonores sans se soucier des licences. Nous n’avions pas prévu ce qui s’est passé ensuite. Des utilisateurs se sont mis à créer de la musique simplement parce qu’ils en avaient envie. Pas pour des jeux ni pour des projets. Juste pour produire de la musique.</p>
<h2>Bella Bay a créé plus de 100 morceaux en deux semaines</h2>
<p>Je n’exagère pas. Bella Bay, l’une de nos créatrices, a généré plus de 100 morceaux en deux semaines environ. En les écoutant sur <a href="https://cinevva.com/radio">Cinevva Radio</a>, on entend clairement sa progression. Les premiers morceaux sont des expérimentations. Les suivants donnent l’impression d’écouter quelqu’un qui a trouvé son style. Ce genre d’accélération créative ne se produit pas lorsqu’on doit lutter contre ses outils. Elle se produit lorsque les outils savent se faire oublier.</p>
<p>Bella Bay n’est pas une exception dans sa démarche, seulement dans sa productivité. Partout sur la plateforme, les utilisateurs ont commencé à considérer le générateur de musique non plus comme un simple outil destiné à créer des ressources de jeu, mais comme un véritable instrument de création. Des producteurs ont exploré des genres qu’ils n’avaient jamais essayés. Certains ont peaufiné un son jusqu’à trouver le bon. Des artistes travaillant depuis leur chambre, qui n’avaient jamais eu accès à une panoplie complète d’outils de production, en ont soudain obtenu une et s’y sont consacrés à fond.</p>
<h2>Nous avons donc créé une radio</h2>
<p>Il fallait le faire.</p>
<p>Tous ces morceaux restaient dans les comptes de leurs créateurs, sans espace commun. Il n’existait aucun moyen de découvrir ce que les autres produisaient, ni de tomber par hasard sur un morceau capable de transformer votre après-midi.</p>
<p><a href="https://cinevva.com/radio">Cinevva Radio</a> est désormais en ligne. Huit stations. 362 morceaux créés par la communauté, et ce nombre augmente chaque jour. Chaque chanson a été réalisée par une personne utilisant la plateforme. Dès que vous ouvrez la radio, un morceau est en cours de diffusion. Pas besoin de chercher. Pas besoin de choisir. Il suffit d’écouter.</p>
<p><strong><a href="https://cinevva.com/radio#cinematic">Soundstage</a></strong> est notre plus grande station. Elle réunit 111 bandes originales cinématographiques et épiques. Honnêtement, je ne m’y attendais pas. Les gens adorent créer des compositions grandioses dignes du cinéma.</p>
<p><strong><a href="https://cinevva.com/radio#mixed">Discovery</a></strong> accueille tout ce qui ne correspond pas parfaitement à un genre. 78 morceaux. Des rythmes latinos suivis d’afrobeats, puis de country, puis de quelque chose d’impossible à décrire. Cette station est déchaînée.</p>
<p><strong><a href="https://cinevva.com/radio#pop">Main Stage</a></strong> rassemble des morceaux pop et chantés. 56 titres avec des paroles et des voix complètes. Les utilisateurs écrivent des chansons d’amour, de rupture, des chants religieux et des ballades. Certains morceaux sont vraiment accrocheurs.</p>
<p><strong><a href="https://cinevva.com/radio#rock">Voltage</a></strong> est consacrée au rock et au métal. 53 morceaux. Riffs de guitare, énergie punk, grunge et hard rock. C’est celle que j’écoute en travaillant.</p>
<p><strong><a href="https://cinevva.com/radio#lofi">Lo-Fi Cafe</a></strong> correspond exactement à ce que son nom évoque. 25 morceaux d’ambiance relaxants pour étudier, travailler ou simplement profiter de l’atmosphère.</p>
<p><strong><a href="https://cinevva.com/radio#electronic">Electric Dreams</a></strong> propose 22 morceaux électroniques et aux synthétiseurs. Synthwave, techno, EDM, drum and bass.</p>
<p><strong><a href="https://cinevva.com/radio#classical">Ivory Tower</a></strong> est consacrée à la musique classique et orchestrale. 10 morceaux. Pièces pour piano, arrangements pour cordes et compositions symphoniques. Une petite sélection, mais qui continue de s’étoffer.</p>
<p><strong><a href="https://cinevva.com/radio#hiphop">The Cipher</a></strong> est dédiée au hip-hop et au rap. 7 morceaux pour le moment. C’est notre station la plus récente, et elle cherche encore sa voix.</p>
<h2>Ce que vous découvrez en écoutant</h2>
<p>Lorsque vous ouvrez <a href="https://cinevva.com/radio">Cinevva Radio</a>, vous accédez à un flux en direct. Imaginez moins une application de listes de lecture qu’une véritable station de radio. Les morceaux s’enchaînent sans interruption. Vous pouvez réagir en temps réel, discuter avec d’autres auditeurs et voir qui a créé chaque titre.</p>
<p>Les créateurs sont crédités à l’écran pendant la diffusion de leur morceau. Leur nom, leur prompt et le genre sont affichés. Si vous entendez quelque chose qui vous plaît, vous savez qui l’a créé. C’est du contenu produit par la communauté, enrichi d’un espace de découverte. Aucun algorithme ne décide de ce qui mérite d’être écouté. Aucun gardien ne filtre les créations. Vous créez un morceau, il rejoint la station correspondant à son genre, et les gens l’écoutent.</p>
<h2>C’est plus qu’une fonctionnalité</h2>
<p>Pendant des décennies, la production musicale a exigé des logiciels coûteux, des années de formation et l’accès à du matériel hors de portée de la plupart des gens. Ce que nous voyons aujourd’hui, c’est l’émergence d’un autre type de créateur. Des personnes qui pensent en descriptions et en émotions plutôt qu’en notes et en signatures rythmiques.</p>
<p>« Jazz de nuit pluvieuse avec un piano cassé » est une direction artistique. La personne qui a écrit ce prompt a fait un choix créatif. Elle a dirigé l’ambiance, l’instrumentation et le registre émotionnel. Il se trouve simplement qu’elle ne joue pas du piano.</p>
<p>Je ne pense pas que cela rende sa création moins légitime. Je pense que la définition du mot « musicien » est en train de s’élargir. Et lorsqu’on voit une personne comme Bella Bay passer de zéro à cent morceaux en deux semaines, on comprend que l’élan créatif a toujours été là. Ce sont les outils qui manquaient.</p>
<h2>La suite</h2>
<p>Nous travaillons sur des profils communautaires qui permettront aux producteurs de présenter leur catalogue et de développer leur audience. La <a href="https://cinevva.com/charts">page des classements</a> présente déjà les créations de la communauté réalisées avec tous nos outils, et nous voulons insuffler cette même dynamique à la musique.</p>
<p>Mais pour l’instant, la radio est en ligne. <a href="https://cinevva.com/radio">Allez l’écouter</a>. Et si vous souhaitez créer vos propres morceaux, le <a href="https://app.cinevva.com/tools/music">générateur de musique</a> est gratuit. Votre titre sera peut-être bientôt diffusé à l’antenne.</p>
<hr>
<p><em><a href="https://cinevva.com/radio">Écouter Cinevva Radio</a> | <a href="https://app.cinevva.com/tools/music">Créer de la musique</a> | <a href="https://cinevva.com/charts">Parcourir les créations de la communauté</a></em></p>
<p><strong>À lire également :</strong></p>
<ul>
<li><a href="/fr/guides/frontier-gen-ai-models">Modèles d’IA générative de pointe open source</a> — les modèles d’IA musicale et audio qui opèrent en coulisses</li>
<li><a href="/fr/tutorials/web-audio-api-games">API Web Audio pour les jeux</a> — créer du son interactif dans le navigateur</li>
<li><a href="/fr/signals/2026-03-13-vibe-coding-new-game-jam">Le vibe coding est la nouvelle game jam</a> — comment la description d’une intention devient le principal point de départ créatif</li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[L’esprit intuitif à l’ère de l’IA]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-02-10-the-intuitive-mind</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-02-10-the-intuitive-mind</guid>
            <pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Jensen Huang affirme que les compétences mathématiques brutes sont désormais banalisées. Voici pourquoi cela trouve un écho chez quelqu’un qui a échoué au SAT, ignoré les règles et créé quelque chose que les gens voulaient vraiment.]]></description>
            <content:encoded><![CDATA[<h1>L’esprit intuitif à l’ère de l’IA</h1>
<p><em>Par <a href="/fr/about">Mariana Muntean</a>, PDG de Cinevva</em></p>
<img src="https://cdn.cinevva.com/blog/game-jam-houston-2018.png" alt="Mariana Muntean avec ses camarades de classe lors d’une game jam de 48 heures à Houston en 2018" style="width:100%;border-radius:8px;margin:1.5rem 0" />
<small>Avec mes camarades de classe lors d’une game jam de 48 heures à Houston, en 2018</small>
<p>L’IA a essentiellement banalisé la logique brute et la puissance de calcul. Les compétences que nous vénérions autrefois — le calcul mental, la reconnaissance de motifs, la capacité à venir à bout d’algorithmes complexes —, l’IA maîtrise désormais tout cela. Plus vite. Mieux. Sans se fatiguer. Avons-nous encore besoin de les connaître ? Absolument, mais les perceptions évoluent. Aujourd’hui, tout le monde doit s’adapter à cette nouvelle ère : une ère d’interactions intuitives et de résultats.</p>
<p>Selon <a href="https://business.columbia.edu/insights/digital-future/nvidia-ceo-jensen-huang-reveals-keys-ai-and-leadership">Jensen Huang</a>, PDG de NVIDIA, ce qui compte désormais, c’est la capacité à ressentir une « vibe » et à anticiper avant même que les données n’apparaissent. La rencontre entre culture technique et profonde empathie. Cette compréhension intuitive que le silicium ne peut pas atteindre.</p>
<p>Je n’avais jamais entendu une personne jouissant d’une telle crédibilité dire quelque chose qui valide aussi directement l’ensemble de mon parcours.</p>
<h2>Le SAT et le système qui n’était pas fait pour moi</h2>
<p>Il y a quelques années, j’ai passé le SAT et j’ai échoué. Je n’en suis pas fière, mais je n’ai pas essayé de nouveau non plus. Quelque chose me semblait fondamentalement faussé dans tout ce système, et je n’arrivais pas à me défaire de ce sentiment, peu importe combien de personnes me disaient simplement de travailler davantage et de le repasser.</p>
<p>Avec le recul, je pense que j’avais raison.</p>
<p>Le SAT est conçu pour le système éducatif américain. Cela semble évident, mais les implications sont profondes. Les lycées américains préparent les élèves à des schémas précis, à certains types de questions et à des manières particulières de formuler les problèmes. Dès l’école primaire, les élèves baignent dans ce style d’évaluation standardisée. Lorsqu’ils arrivent au SAT, ils en ont assimilé le rythme.</p>
<p>Les élèves internationaux ne bénéficient pas de cet avantage. Nous venons de systèmes fondés sur des philosophies éducatives différentes. Les écoles européennes privilégient souvent la profondeur plutôt que l’étendue, les dissertations plutôt que les questionnaires à choix multiples, les épreuves orales plutôt que les cases à cocher. Les systèmes asiatiques ont leurs propres examens standardisés, mais ils mesurent d’autres choses, d’autres manières. Les traditions éducatives d’Amérique du Sud, d’Afrique et du Moyen-Orient possèdent chacune leur propre logique.</p>
<p>Lorsque vous plongez un élève international dans le SAT, vous ne testez pas seulement ses connaissances. Vous mesurez la vitesse à laquelle il peut s’adapter à une culture d’évaluation étrangère tout en démontrant sa maîtrise du contenu. Vous évaluez autant son aisance culturelle que ses capacités scolaires.</p>
<p>On attendait de moi que je me prépare pendant un été et que je réussisse brillamment. Que j’assimile toute une culture de l’examen, désapprenne mes propres réflexes éducatifs et atteigne un niveau capable d’impressionner les responsables des admissions américaines. Le tout en quelques mois.</p>
<p>J’ai choisi de ne pas le faire.</p>
<p>Quand on est jeune, on est intelligent de différentes manières. De manières intuitives. Je me frayais mon propre chemin, même si je ne savais pas encore expliquer précisément pourquoi. Quelque chose en moi savait que ce système n’était pas le mien.</p>
<h2>Suivre plutôt le fil de la créativité</h2>
<p>J’ai étudié le développement et la conception de jeux parce que j’adorais l’idée d’utiliser la créativité et les effets visuels pour bâtir des mondes virtuels dans lesquels les gens pourraient jouer et interagir. J’aimais la rencontre entre l’art et la technologie, entre la narration et l’interactivité.</p>
<p>Ce que j’ai découvert m’a stupéfiée.</p>
<p>Les jeux exigent une véritable profondeur technique. Simulations physiques, détection des collisions, calcul vectoriel, calculs d’éclairage, optimisation. Je le savais en me lançant. Les mathématiques et l’ingénierie ne sont pas des obstacles au développement de jeux. Elles font partie de ce qui permet aux jeux de fonctionner.</p>
<p>Le problème résidait dans l’écart entre la vision créative et sa mise en œuvre. Les moteurs et les outils dominants du secteur étaient conçus par des ingénieurs pour des ingénieurs. Tout reposait sur des « si » et des « alors », ainsi que sur des blueprints à base de nœuds. Vous vouliez un effet de vortex ? Apprenez à programmer des shaders. Une ambiance lumineuse particulière ? Plongez dans les graphes de matériaux. Des déplacements de personnage qui semblent naturels ? Passez des heures à déboguer le contrôleur du personnage ou les paramètres physiques.</p>
<p>Si vous êtes une personne visuelle, si vous pensez en couleurs vives et en images animées, si vos idées vous viennent sous la forme de scènes complètes, avec leur son, leur texture et leur charge émotionnelle, vous deviez traduire tout cela en langage technique avant de pouvoir en créer la moindre partie. Vous voyez dans votre esprit un monde complet, avec son éclairage, son atmosphère et la façon dont les personnages se déplacent dans l’espace. Puis vous vous asseyez devant votre ordinateur et passez les six heures suivantes à chercher pourquoi votre personnage traverse le sol.</p>
<p>Les fondations techniques sont importantes. Mais les outils obligeaient les créateurs à vivre dans les détails de la mise en œuvre au lieu de faire abstraction de cette complexité. Créer un jeu devrait ressembler au fait de raconter une histoire ou de réaliser un film auquel les gens peuvent participer. C’est là toute la magie de ce médium. Au lieu de cela, on avait l’impression de devoir passer un examen d’ingénierie avant même de pouvoir commencer.</p>
<p>Les réalisateurs de films ne passent pas des années à apprendre le fonctionnement des moteurs physiques avant de pouvoir exprimer leur vision. Ils disposent d’un budget et d’une équipe chargée de l’exécution technique. Mais dans le développement de jeux indépendants, on a rarement un budget. Ce que l’on a, c’est du temps et un accès à des outils. Et si vous êtes une personne technique, vous pouvez créer un jeu exceptionnel. Mais seulement dans ce cas.</p>
<p>C’est ainsi que nous avons obtenu des titres comme Limbo, réalisé par une petite équipe possédant une vision artistique singulière et les compétences techniques nécessaires pour la concrétiser. Ou Undertale, créé en grande partie par une seule personne qui se trouvait avoir la bonne combinaison d’instinct créatif et de talent en programmation. Ou Stardew Valley, pour lequel Eric Barone a passé des années à tout apprendre par lui-même, du pixel art à la composition musicale en passant par la programmation en C#.</p>
<p>Ces jeux ont réussi malgré des obstacles considérables. Mais pour chaque Limbo, des millions de visions créatives sont mortes parce que les outils exigeaient une maîtrise technique que leurs créateurs ne possédaient pas. Moins de 3 % des développeurs de jeux indépendants connaissent un véritable succès. Combien de jeux brillants n’ont jamais vu le jour parce que leurs créateurs se sont heurtés à un mur de « si » et de « alors », puis ont abandonné ?</p>
<p>La barrière à l’entrée n’était pas la créativité. C’était le filtrage technique intégré aux outils eux-mêmes.</p>
<h2>Créer ce qui aurait dû exister</h2>
<p>Il y a donc 5 ans, j’ai commencé à créer quelque chose de différent.</p>
<p>L’idée était simple : le développement de jeux devrait être accessible à toute personne ayant une vision créative. Vous ne devriez pas avoir besoin d’un diplôme en informatique pour vous exprimer à travers des médias interactifs. Les outils devraient s’adapter à la manière dont les personnes créatives pensent réellement, et non l’inverse.</p>
<p>Des investisseurs en capital-risque de Sequoia, Pear, Draper et de dizaines d’autres sociétés m’ont dit que cela ne fonctionnerait pas. C’est du B2C. Le marché n’existe pas. Les créateurs indépendants ne paient jamais rien. On ne peut pas simplifier le développement de jeux sans sacrifier les possibilités. Les joueurs veulent des jeux complexes, et les jeux complexes exigent des outils complexes. Des millions d’excuses déguisées en analyses de marché.</p>
<p>On m’a dit que j’étais folle. C’était peut-être vrai. Mais je revenais toujours à la même question : pourquoi des budgets de plusieurs millions de dollars devraient-ils être indispensables à l’expression créative ? Les réalisateurs de films et les producteurs de jeux célèbres disposent d’équipes et de ressources. Tous les autres n’ont qu’un éditeur de code et une prière. Je voulais créer ce qui comblerait cet écart. Vous décrivez ce que vous voulez, et cela se produit sous vos yeux. Tout en moi savait que c’était la bonne voie. Je le ressentais dans chaque cellule de mon corps.</p>
<p>Aujourd’hui, des milliers de personnes utilisent Cinevva chaque jour pour créer des ressources de jeux 3D, des jeux, de la musique, des niveaux et des expériences. Les projets créés jusqu’à présent cumulent des millions de vues. Et ce chiffre augmente chaque jour. Un pitch de deux minutes sur Sand Hill Road n’est pas exactement le format idéal pour dire : « J’ai échoué au SAT, mais faites confiance à mon intuition. »</p>
<h2>Ce que signifie réellement l’intuition</h2>
<p><a href="https://singjupost.com/transcript-jensen-huangs-interview-cisco-ai-summit-2026/">Huang</a> ne formulait pas seulement une idée philosophique. Il décrivait une véritable évolution de ce qui constitue une intelligence précieuse.</p>
<p>Pendant des décennies, nous avons optimisé les mauvaises choses. Nous avons bâti des systèmes éducatifs qui récompensaient la mémorisation et le calcul. Nous avons conçu des tests standardisés qui mesuraient la capacité à reconnaître des schémas dans des problèmes déjà rencontrés. Nous avons recruté des personnes sur la base de diplômes prouvant qu’elles pouvaient survivre à quatre années de sélection académique. L’IA vient de rendre tout cela moins exceptionnel.</p>
<p>Ce que l’IA ne sait pas faire, du moins pas encore, c’est sentir ce qui manque. Ressentir que quelque chose cloche. Deviner ce dont les gens ont besoin avant qu’ils ne puissent eux-mêmes le formuler. Lire une salle. Comprendre un contexte qui n’est consigné dans aucun jeu de données.</p>
<p>J’ai consacré mon temps, mon expérience d’une vie internationale, mon argent et mes connaissances intuitives à la création de Cinevva. C’est une combinaison difficile à acquérir à l’université. Difficile à évaluer avec le SAT. Difficile à saisir dans un quelconque système de diplômes conçu avant que l’IA ne rende la puissance cognitive brute abondante.</p>
<p>Il y a seize ou vingt ans, des informaticiens décidaient de l’apparence des outils et de leur fonctionnement. Ils les concevaient pour eux-mêmes, pour des gens qui pensaient comme eux. On attendait du reste d’entre nous que nous nous adaptions. Cette époque touche à sa fin. Les personnes qui façonneront la suite seront celles qui comprennent ce que les humains veulent réellement. Celles qui savent sentir quand quelque chose ne va pas et quand quelque chose est juste. Celles qui créent pour les gens plutôt que pour l’élégance technique.</p>
<p>J’ai fait confiance à quelque chose en moi que le système jugeait sans valeur. Et j’avais raison.</p>
<hr>
<p><strong>À lire également :</strong></p>
<ul>
<li><a href="/fr/blog/2026-01-18-skills-over-degrees">Le marché du travail se transforme — des diplômes aux compétences</a></li>
<li><a href="/fr/guides/game-dev-courses">Cours de développement de jeux en ligne</a> — des parcours axés sur les compétences qui contournent les barrières traditionnelles</li>
<li><a href="/fr/signals/2026-03-04-everyone-wants-ai-game-engine">Tout le monde veut désormais devenir le moteur de jeu propulsé par l’IA</a> — l’évolution du secteur vers des outils qui s’adaptent à la manière dont les gens pensent</li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Controverse sur l’IA, confiance et économie post-IA dans le jeu vidéo]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-01-18-ai-controversy-and-post-ai-economy</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-01-18-ai-controversy-and-post-ai-economy</guid>
            <pubDate>Sun, 18 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Un point de vue pragmatique : l’IA n’est pas le vrai sujet, la confiance l’est. Pourquoi la labellisation, les filtres et une distribution équitable comptent davantage que les débats « pro » ou « anti » IA.]]></description>
            <content:encoded><![CDATA[<h1>Controverse sur l’IA, confiance et économie post-IA dans le jeu vidéo</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, directeur technique de Cinevva</em></p>
<p>Vers le milieu de l’année 2024, l’IA dans les jeux est devenue un véritable champ de mines. Des emplois qui disparaissent. Des gens convaincus qu’ils seront les prochains. Des questions de droits d’auteur que même les juristes n’arrivent pas à démêler. Des débats esthétiques sans fin qui tournent en rond. Steam noyé sous des contenus que personne n’a demandés. Et, sous tout cela, cette inquiétude tenace que ressent désormais chaque créateur : « Est-ce que je suis vraiment en train de créer quelque chose, ou est-ce que je me contente de… saisir des prompts ? »</p>
<p>Nous gérons une plateforme. Et dans ce métier, il y a une chose que l’on comprend très vite.</p>
<p><strong>Personne n’aime avoir l’impression d’avoir été trompé.</strong></p>
<h2>Ces chiffres nous ont surpris</h2>
<p>Nous pensions que l’adoption de l’IA progresserait. Mais pas à ce point. Voici où nous en étions fin 2025 :</p>
<table>
<thead>
<tr>
<th>Ce que nous avons suivi</th>
<th>2024</th>
<th>2025</th>
<th>L’évolution</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jeux Steam déclarant l’utilisation de l’IA</td>
<td>~1 000</td>
<td>7 818</td>
<td>Multiplié par 7</td>
</tr>
<tr>
<td>Nouvelles sorties Steam utilisant l’IA</td>
<td>~3 %</td>
<td>~20 %</td>
<td>Une sur cinq</td>
</tr>
<tr>
<td>Développeurs estimant que l’IA nuit à la qualité</td>
<td>34 %</td>
<td>47 %</td>
<td>Treize points de plus</td>
</tr>
<tr>
<td>Développeurs estimant que l’IA améliore la qualité</td>
<td>—</td>
<td>11 %</td>
<td>Peu nombreux</td>
</tr>
<tr>
<td>Revenus des jeux déclarant l’utilisation de l’IA</td>
<td>—</td>
<td>660 M$</td>
<td>Douze jeux ont dépassé les 10 millions</td>
</tr>
</tbody>
</table>
<p>Sources : analyse de Tom's Hardware / Totally Human Media, enquête GDC 2025 auprès des développeurs, rapport Unity 2025 sur le jeu vidéo</p>
<h2>Ce qui a réellement rendu les joueurs aussi sceptiques</h2>
<p>Cette méfiance ne vient pas de nulle part. De vrais problèmes sont survenus. Et les gens s’en souviennent.</p>
<h3>Des comédiens de doublage ont découvert qu’ils avaient été clonés — le jour du lancement</h3>
<p><em>Tomb Raider 4-6 Remastered</em> est sorti avec des versions générées par IA des interprétations vocales originales. Les comédiens l’ont appris en même temps que tout le monde. Le jour du lancement. L’éditeur a fini par retirer ces voix dans une mise à jour, après une montée de la pression juridique. Cette grève de onze mois de la SAG-AFTRA ? C’est précisément ce genre de scénario qui l’alimentait.</p>
<h3>L’expression « bouillie d’IA » est entrée dans le langage courant</h3>
<p><em>Call of Duty: Black Ops 6</em> a discrètement ajouté une déclaration relative à l’IA sur sa page Steam. Après sa sortie. Les joueurs avaient déjà remarqué les anomalies — des défauts visuels disséminés un peu partout. Sur un écran de chargement, un personnage avait six doigts. L’expression « bouillie d’IA » est devenue un raccourci pour désigner tout ce qui semble… bizarre. Creux. Comme si personne ne s’était donné la peine de vérifier.</p>
<h3>Une nomination à un prix a disparu</h3>
<p><em>Clair Obscur: Expedition 33</em> a perdu sa nomination au titre de Jeu de l’année lors des Indie Game Awards. Pourtant, le jeu commercialisé n’utilisait pas d’IA. Le problème venait de contenus temporaires générés par IA pendant le développement — des éléments internes que les joueurs n’ont jamais vus. Les ressources finales avaient toutes été créées par des humains. Cela n’a rien changé. Des semaines de débats ont suivi pour déterminer où tracer précisément la limite.</p>
<h3>Des équipes qui n’avaient jamais utilisé l’IA ont dû le prouver</h3>
<p><em>Chessplus</em> et <em>Peak</em> ont tous deux été accusés d’avoir utilisé l’IA. Aucun des deux ne l’avait fait. Tous deux étaient nommés à des prix. Les deux équipes de développement ont fini par fouiller dans d’anciennes captures d’écran et des fichiers de calques simplement pour démontrer que leur travail avait été réalisé à la main. L’équipe de <em>Peak</em> l’a parfaitement résumé : « Nous sommes peut-être de la bouillie, mais de la bouillie artisanale, produite localement et créée par des humains. »</p>
<h3>Les studios ont pris des directions radicalement opposées</h3>
<p><strong>SNK</strong> — des modérateurs Discord sont partis après l’apparition de visuels évoquant l’IA dans une bande-annonce de <em>Fatal Fury: City of the Wolves</em>. <strong>Games Workshop</strong> — a totalement interdit l’IA dans toutes les propriétés Warhammer. <strong>Larian Studios</strong> — a déclaré n’avoir utilisé l’IA que pour de toutes premières esquisses conceptuelles de Baldur's Gate 3, et absolument pas dans le jeu final. <strong>Tim Sweeney</strong> — a affirmé que les labels relatifs à l’IA sur Steam ne servaient à rien et qu’il fallait les supprimer. <strong>Valve</strong> — a répliqué que les développeurs qui se plaignaient de ces labels craignaient généralement que leur travail paraisse « bâclé ».</p>
<h2>Trouver des jeux est devenu une question de confiance</h2>
<p>Les contenus sont désormais créés plus vite que quiconque ne peut sérieusement les évaluer. Les questions ont changé :</p>
<p>Qu’est-ce que je suis réellement en train de regarder ?
Qui a créé cela ?
Vais-je regretter le temps que j’y consacrerai ?
Est-ce que cela correspond vraiment aux promesses de la bande-annonce ?</p>
<p>Il n’y aura probablement pas de grande résolution. Pas de décision définitive opposant l’IA à la création humaine.</p>
<p>Le scénario le plus probable : <strong>des filtres, une véritable transparence et des mécanismes d’incitation qui encouragent l’honnêteté</strong>.</p>
<h2>Les petits studios pris entre deux feux</h2>
<p>Les développeurs indépendants se sont retrouvés dans une position étrange.</p>
<p><strong>Les avantages sont réellement difficiles à ignorer :</strong>
Les itérations sont plus rapides. Les coûts diminuent quand quatre personnes doivent tout produire. Les développeurs solo peuvent désormais réellement terminer leurs projets. La localisation cesse d’engloutir la totalité du budget.</p>
<p><strong>Les inconvénients sont tout aussi réels :</strong>
Des créations qui semblent génériques. Des préoccupations relatives aux données d’entraînement et à la propriété intellectuelle que les juristes refusent encore d’aborder. La livraison de systèmes que l’on ne comprend pas entièrement — certains parlent de dette de compréhension. Le risque d’être assimilé au flot de sorties bâclées. Près de la moitié des développeurs interrogés estiment que l’IA dégrade globalement les jeux.</p>
<p>Le rapport 2025 de Unity indique que 79 % des développeurs ont une opinion positive des outils d’IA. Cela semble assez catégorique. La réalité est plus complexe. Les équipes qui s’en servent efficacement ont tendance à réserver l’IA aux tâches fastidieuses — travail répétitif, premières ébauches, corvées d’assurance qualité. La direction créative ? Elle reste humaine.</p>
<h2>La position de Cinevva : neutralité vis-à-vis de l’IA, exigence absolue en matière d’honnêteté</h2>
<p>Nous ne refusons pas les jeux qui utilisent l’IA. Nous ne leur accordons pas non plus de traitement privilégié.</p>
<p>La règle est simple : <strong>si l’IA a été utilisée, dites-le.</strong> Les joueurs décident de ce qui compte pour eux. Les filtres peuvent alors réellement fonctionner.</p>
<ul>
<li><a href="/fr/ai-content">Politique relative aux contenus générés par IA</a></li>
</ul>
<h2>Les filtres fonctionnent mieux que les débats</h2>
<p>Le débat sur l’IA dans les jeux ne débouchera pas de sitôt sur un consensus. Peut-être même jamais. Mais les préférences individuelles ? Elles sont suffisamment claires.</p>
<p>Certains joueurs recherchent spécifiquement une direction artistique humaine. Des textes écrits par une personne. Un savoir-faire perceptible.</p>
<p>D’autres s’en moquent sincèrement. Un jeu amusant reste un jeu amusant.</p>
<p>Les filtres permettent aux deux groupes de trouver ce qu’ils recherchent. Personne n’a besoin de gagner.</p>
<h2>Les modèles de rémunération comptent plus que l’opinion de chacun</h2>
<p>Lorsque les revenus dépendent du <strong>temps de jeu</strong> plutôt que des ventes à l’unité, vous gagnez de l’argent en :</p>
<p>Captivant rapidement les joueurs. Conservant leur attention. Tenant les promesses de votre communication.</p>
<p>La qualité devient la voie évidente. La manière dont le jeu a été créé compte moins.</p>
<p>Votre bande-annonce promet trop ? Les joueurs partent immédiatement. La rétention s’effondre. Vous leur montrez exactement ce qu’ils vont obtenir ? Ils restent. Le temps de jeu augmente. Les revenus suivent. L’économie fait naturellement le tri.</p>
<h2>Ce que 2026 nous réserve probablement</h2>
<p><strong>Davantage de règles</strong> — le règlement européen sur l’IA continue de s’étendre. Aux États-Unis, les protections de la voix et de l’image se renforcent, notamment après la SAG-AFTRA.</p>
<p><strong>Des outils spécialisés</strong> — une IA conçue spécifiquement pour les petites équipes. Pensée pour laisser les humains aux commandes.</p>
<p><strong>Des labels plus précis</strong> — « Créé avec l’IA » est une indication trop rudimentaire. Il faut s’attendre à une distinction entre les processus assistés par IA et les ressources finales générées par IA.</p>
<p><strong>Une fragmentation des publics</strong> — certains joueurs rechercheront délibérément des jeux créés de manière traditionnelle. D’autres n’y penseront même pas. Ces deux groupes sont suffisamment importants pour que l’on crée des jeux à leur intention.</p>
<p><strong>Une concurrence entre plateformes</strong> — la manière dont les boutiques gèrent la transparence et la découverte devient un véritable facteur de différenciation.</p>
<h2>Ce qui fait réellement avancer les choses</h2>
<p>Les débats ne trancheront pas la question de l’IA. Voici ce qui le fera :</p>
<ol>
<li><strong>Les joueurs</strong> choisissent en fonction de ce qui compte réellement pour eux</li>
<li><strong>Les créateurs</strong> font preuve d’honnêteté sur leurs méthodes de travail</li>
<li><strong>Les plateformes</strong> conçoivent des outils qui aident ces deux groupes à se rencontrer</li>
</ol>
<p>Voilà l’économie post-IA. Pas des camps en guerre. Mais la confiance qui maintient l’ensemble.</p>
<hr>
<p><strong>À lire également :</strong></p>
<ul>
<li><a href="/fr/ai-content">Politique relative aux contenus générés par IA</a></li>
<li><a href="/fr/creators">Pour les créateurs de jeux</a></li>
<li><a href="/fr/faq">FAQ</a></li>
<li><a href="/fr/guides/frontier-gen-ai-models">Modèles d’IA générative open source de pointe</a> — les modèles concernés et leur fonctionnement</li>
<li><a href="/fr/signals/2026-03-06-open-source-ai-pollution">L’open source a un problème de pollution par l’IA</a> — ce qui se passe lorsque les productions de l’IA inondent les projets open source</li>
</ul>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Le marché du travail se transforme — des diplômes aux compétences]]></title>
            <link>https://app.cinevva.com/fr/blog/2026-01-18-skills-over-degrees</link>
            <guid isPermaLink="true">https://app.cinevva.com/fr/blog/2026-01-18-skills-over-degrees</guid>
            <pubDate>Sun, 18 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[90 % des entreprises privilégient désormais les compétences aux diplômes. Voici ce que cela signifie pour les jeunes diplômés qui se lancent dans la tech et le développement de jeux.]]></description>
            <content:encoded><![CDATA[<h1>Le marché du travail se transforme — des diplômes aux compétences</h1>
<p><em>Par <a href="/fr/about">Oleg Sidorkin</a>, CTO de Cinevva</em></p>
<p>J’ai vu mon cousin obtenir son diplôme avec mention en 2023. Un diplôme d’informatique délivré par une bonne université publique. D’excellentes notes. Il a passé quatorze mois à postuler avant de décrocher un emploi. Pendant ce temps, un ami qui avait abandonné ses études en deuxième année pour créer des jeux indépendants a reçu trois offres en moins d’un mois après avoir décidé de chercher du travail.</p>
<p>Ce n’est plus une anomalie. C’est devenu la norme.</p>
<h2>Les chiffres confirment enfin ce que nous ressentions</h2>
<p>En 2025, <strong>90 % des responsables RH</strong> déclaraient recruter en dehors du parcours universitaire traditionnel en quatre ans (<a href="https://fortune.com/2025/07/01/90-percent-hr-leaders-looking-to-hire-outside-of-traditional-college-degrees-as-they-prioritize-skills/">Fortune</a>). Un quart des entreprises américaines ont complètement supprimé l’exigence d’un diplôme de premier cycle universitaire (<a href="https://www.hrdive.com/news/employer-eliminate-degree-requirements-2025/748998/">HR Dive</a>). Et voici la partie qui fait mal si vous venez tout juste de finir de rembourser vos prêts étudiants : <strong>94 % des employeurs affirment que les personnes recrutées sur la base de leurs compétences sont plus performantes que celles embauchées sur la base de leurs diplômes</strong> (<a href="https://www.forbes.com/sites/cynthiapong/2024/12/26/90-of-companies-make-better-hires-based-on-skills-over-degrees/">Forbes</a>).</p>
<p>Verizon affirme désormais que 99 % de ses postes ne nécessitent pas de diplôme. Sergey Brin a reconnu que Google embauche « énormément » de personnes sans diplôme universitaire de premier cycle. IBM a rendu le diplôme facultatif pour la moitié de ses postes à pourvoir aux États-Unis grâce à son programme « New Collar ».</p>
<p>Ce n’est pas de la générosité de la part des entreprises. Elles ont compris que les diplômes ne permettaient pas de prédire qui serait réellement compétent dans son travail.</p>
<h2>Pourquoi l’ancien système s’est effondré</h2>
<p>La réponse honnête ? Les diplômes sont devenus un filtre de facilité.</p>
<p>Lorsque vous recrutez et recevez 400 candidatures, exiger un diplôme universitaire réduit la pile de moitié. Cela ne vous indique pas qui est réellement capable de faire le travail. Cela vous indique qui avait l’argent, le temps et la stabilité familiale nécessaires pour passer quatre ans en cours. La corrélation avec les compétences a toujours été plus faible que nous voulions bien l’admettre.</p>
<p>Puis trois phénomènes se sont produits simultanément :</p>
<p><strong>Les compétences ont évolué trop vite.</strong> Moteurs de jeu, outils d’IA, frameworks web. Lorsqu’un comité pédagogique approuve enfin un cours sur une technologie, celle-ci est déjà dépassée. Un cursus de quatre ans vous enseigne la théorie d’il y a quatre ans. Dans la tech, c’est de l’histoire ancienne.</p>
<p><strong>Les portfolios sont devenus incontournables.</strong> Pourquoi essayer de deviner si quelqu’un sait programmer quand vous pouvez consulter son GitHub ? Pourquoi vous demander si cette personne est capable de publier un jeu lorsque sa page <a href="http://itch.io">itch.io</a> présente cinq projets terminés accompagnés d’avis de joueurs ?</p>
<p><strong>Les entreprises ont été poussées à bout.</strong> La pénurie de talents est bien réelle. Exclure toutes les personnes sans diplôme revient à écarter celles qui pourraient correspondre exactement à vos besoins. Certains responsables du recrutement l’ont compris à leurs dépens, après avoir vu des développeurs autodidactes surpasser largement leurs recrues diplômées.</p>
<h2>L’industrie du jeu l’avait vu venir</h2>
<p>Je pense que le secteur du jeu avait une longueur d’avance dans ce domaine, et il est utile de comprendre pourquoi.</p>
<p>Les studios ne se sont jamais vraiment souciés de l’établissement où vous aviez étudié. Ils voulaient savoir ce que vous aviez publié. Un jeu abouti créé pendant une game jam de 48 heures en dit davantage à un responsable du recrutement qu’un cursus de quatre ans en game design. Il prouve que vous êtes capable de terminer un projet sous pression. Il montre que vous avez pris des décisions difficiles concernant son périmètre. Il démontre que le jeu est jouable, et pas seulement théorique.</p>
<p>Si vous essayez de percer dans le jeu vidéo en 2026, voici ce qui compte réellement (<a href="https://www.dice.com/career-advice/aspiring-video-game-designers-in-2025-what-you-need-to-know">Dice</a>, <a href="https://combinegr.com/2025-global-gaming-employment-outlook-trends-talent-strategy/">CombineGR</a>) : deux ou trois démos jouables et soignées. Des études de cas expliquant ce que vous avez fait et pourquoi. Des preuves que vous menez vos projets à terme. La maîtrise d’au moins un grand moteur de jeu, démontrée par des projets concrets.</p>
<p>Les responsables du recrutement ne recherchent pas du potentiel. Ils recherchent des preuves.</p>
<h2>La partie dérangeante dont personne ne parle</h2>
<p>Voici quelque chose qui m’a frustré lorsque je me suis penché sur les études.</p>
<p>Une étude de Harvard et Burning Glass a constaté que les entreprises supprimant les exigences de diplôme ne recrutaient souvent pas davantage de candidats non diplômés. L’augmentation n’était que d’environ 3,5 points de pourcentage (<a href="https://www.forbes.com/sites/jenamcgregor/2024/02/14/companies-are-dropping-diploma-requirements-for-more-jobs-but-hiring-few-non-degreed-workers-to-fill-them/">Forbes</a>). La politique avait changé. Les pratiques de recrutement étaient à la traîne.</p>
<p>Cela signifie que si vous suivez une voie non traditionnelle, vous devez encore fournir davantage d’efforts. La porte est plus ouverte qu’avant, mais les conditions ne sont pas encore égales. Votre portfolio doit être irréfutable. Vos projets doivent parler plus fort que le diplôme de quelqu’un d’autre.</p>
<p>Ce n’est pas juste. Mais le savoir vous aide à vous préparer.</p>
<h2>Ce que je dirais à quelqu’un qui débute aujourd’hui</h2>
<p>Si vous faites des études, ne les arrêtez pas. Mais comprenez que le diplôme seul ne suffit plus. Créez des projets à côté. Participez à des game jams. Obtenez un <a href="https://grow.google/certificates/">certificat professionnel Google</a> ou une certification Unity. Considérez votre diplôme comme une qualification parmi d’autres, et non comme la seule qui compte.</p>
<p>Si vous ne préparez pas de diplôme, votre parcours sera différent, mais pas nécessairement plus difficile. Créez sans relâche. Publiez vos projets. Documentez tout. Votre <a href="https://github.com">GitHub</a>, votre page <a href="https://itch.io">itch.io</a>, votre site personnel. Ce sont désormais vos références.</p>
<p>Dans les deux cas, entraînez-vous à expliquer votre travail. Pas seulement ce que vous avez créé, mais aussi pourquoi vous avez fait ces choix. Ce que vous feriez différemment. Ce que vous avez appris. Les recruteurs se souviennent des personnes capables d’expliquer clairement leur raisonnement.</p>
<h2>Cela va au-delà du recrutement</h2>
<p>Ce qui se passe ici ne se limite pas à un changement dans la manière dont les entreprises pourvoient leurs postes. C’est une évolution de ce que nous valorisons collectivement.</p>
<p>Pendant des décennies, les diplômes ont servi de filtre. Coûteux, chronophages, mais faciles à interpréter. Lorsqu’une personne avait un diplôme, on pouvait faire certaines suppositions. C’était un indicateur, pas une mesure directe, mais il était jugé suffisant.</p>
<p>Cet indicateur perd désormais de sa pertinence. La technologie permet de voir ce qu’une personne sait réellement faire. Le travail à distance a prouvé que les résultats comptent davantage que l’endroit où l’on travaille. L’IA rend les connaissances théoriques moins précieuses que leur application pratique.</p>
<p>Je trouve cela sincèrement encourageant, même si la transition est chaotique. Autrefois, la question était : « Où avez-vous étudié ? » Aujourd’hui, c’est : « Que savez-vous créer ? »</p>
<p>C’est une meilleure question.</p>
<hr>
<h2>Sources</h2>
<ul>
<li><a href="https://fortune.com/2025/07/01/90-percent-hr-leaders-looking-to-hire-outside-of-traditional-college-degrees-as-they-prioritize-skills/">Fortune : 90 % des responsables RH recrutent en dehors des parcours diplômants traditionnels</a></li>
<li><a href="https://www.forbes.com/sites/cynthiapong/2024/12/26/90-of-companies-make-better-hires-based-on-skills-over-degrees/">Forbes : 90 % des entreprises recrutent de meilleurs profils en se fondant sur les compétences</a></li>
<li><a href="https://www.forbes.com/sites/jenamcgregor/2024/02/14/companies-are-dropping-diploma-requirements-for-more-jobs-but-hiring-few-non-degreed-workers-to-fill-them/">Forbes : les entreprises suppriment les exigences de diplôme, mais recrutent peu de travailleurs non diplômés</a></li>
<li><a href="https://www.hrdive.com/news/employer-eliminate-degree-requirements-2025/748998/">HR Dive : les employeurs suppriment les exigences de diplôme</a></li>
<li><a href="https://www.computerworld.com/article/1623286/no-degree-no-problem-tech-firms-move-away-from-college-requirement-for-new-hires.html">Computerworld : les entreprises technologiques délaissent l’exigence d’un diplôme universitaire</a></li>
<li><a href="https://www.dice.com/career-advice/aspiring-video-game-designers-in-2025-what-you-need-to-know">Dice : devenir game designer en 2025</a></li>
<li><a href="https://combinegr.com/2025-global-gaming-employment-outlook-trends-talent-strategy/">CombineGR : perspectives mondiales de l’emploi dans le jeu vidéo en 2025</a></li>
</ul>
<hr>
<p><strong>À lire également :</strong></p>
<ul>
<li><a href="/fr/guides/game-jams-hackathons">Comment réussir ses game jams</a></li>
<li><a href="/fr/guides/game-dev-courses">Cours de développement de jeux en ligne</a> — des parcours axés sur les compétences pour tous les budgets</li>
<li><a href="/fr/tutorials/agentic-code-tools">Outils de programmation fondés sur des agents d’IA</a> — les outils qui redéfinissent les compétences essentielles</li>
<li><a href="/fr/blog/2026-02-10-the-intuitive-mind">L’esprit intuitif</a> — pourquoi l’intuition compte davantage que les diplômes</li>
</ul>
]]></content:encoded>
        </item>
    </channel>
</rss>
