Dorian Fevrier's blog - Infographie 3D - BoulotJe m’appelle FEVRIER Dorian, je suis infographiste 3D passionné par mon métier, l’informatique en général, l’internet, la programmation et l’évolution de tout ce petit monde. Vous trouverez sur ce blog des tutoriaux, mes coups de cœurs, avis, etc.2024-01-02T23:48:05+01:00FEVRIER Dorianurn:md5:695d9c73474c33ce3dab043823509c4bDotclearLe gris moyenurn:md5:d346e1162251bbedc8a088965b701e8f2023-06-09T21:34:00+02:002023-06-11T17:30:47+02:00NarannInfographie 3D - Boulotacescouleurrendusrgb<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_06_09_gris_moyen/gris_moyen_002_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Bonjour, aujourd’hui on va parler de la notion de « gris moyen ».</p>
<p>Nous allons voir que, comme beaucoup de choses liées à la couleur, c’est un concept faussement simple, on essaiera de savoir comment le déterminer et pourquoi on ne se posait pas vraiment la question avant.</p>
<p> </p> <h3>Le gris moyen, c’est quoi ?</h3>
<p>Ce qu’on appelle couramment le gris moyen est une teinte perçue comme étant à mi-parcours entre le noir et le blanc.</p>
<p>Dans la 3D, cette valeur de gris est souvent utilisé comme intensité de référence (couleur de fond/du sol), pour faire valider ses tournettes ou ses lightings.</p>
<h3>Quelle est sa valeur ?</h3>
<p>On parle souvent d’un gris à 18 %. Ce pourcentage n’est pas vraiment un standard, mais plutôt une convention.</p>
<blockquote>
<p>Si vous faites de la peinture, vous pouvez fabriquer un gris moyen en prenant des pigments noires, des pigments blancs, et en faisant un mélange 18(noir)/72(blanc).</p>
</blockquote>
<p>Pourquoi 18 % exactement ? <a href="https://photo.stackexchange.com/questions/1048/what-is-the-18-gray-tone-and-how-do-i-make-a-18-gray-card-in-photoshop" hreflang="en">Il semblerait</a> que les « inventeurs » soient <a href="https://fr.wikipedia.org/wiki/Ansel_Adams" hreflang="fr" title="Ansel Adams">Ansel Adams</a> et <a href="https://en.wikipedia.org/wiki/Fred_R._Archer" hreflang="en" title="Fred Archer">Fred Archer</a>, deux photographes américains qui l’ont formalisé dans ce qui est appelé le <a href="https://fr.wikipedia.org/wiki/Zone_system" hreflang="fr" title="Zone system">Zone system</a>.</p>
<p>Je ne vais pas rentrer dans les détails. Ce qui est intéressant dans ce système est que le gris moyen est à 18 %, ce qui est très éloigné d’une valeur qu’on pourrait naïvement définir à 50 %, mais pourquoi ?</p>
<h3>Expérimentation</h3>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_06_09_gris_moyen/gris_moyen_001.png" style="margin: 0px auto; display: table; width: 500px; height: 500px;" /></p>
<p>Si votre moniteur est calibré en sRGB (80 cd/m<sup>2</sup> d’intensité lumineuse) et que vous êtes dans une pièce moyennement éclairée : Si je vous donne l’image ci-dessus et que je vous demande d’y mettre, à l’œil (sans regarder les valeurs), un rectangle dont l’intensité du gris se situerait à mi-parcours entre le noir et le blanc, il est fort probable que vous obteniez quelque chose comme ça :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_06_09_gris_moyen/gris_moyen_002.png" style="margin: 0px auto; display: table; width: 500px; height: 500px;" /></p>
<p>Pourtant…</p>
<p>Vous pensez que votre gris ainsi trouvé est à mi-chemin entre le blanc et le noir, mais si vous aviez les moyens de mesurer l’intensité lumineuse de ce gris sortant de votre écran (avec un luxmètre), vous vous rendriez compte qu’il n’est pas à 50 % de l’intensité lumineuse du blanc, mais aux alentour des 20 %.</p>
<p>Une autre façon de le dire, c’est que la perception de l’intensité lumineuse par l’œil humain n’est pas linéaire : Pour une dynamique donnée (un range d’intensité minimum, perçu comme, noir et maximum, perçu comme blanc), l’œil semble être à l’aise avec les informations tournant autour des 18-20 % de cette dynamique. C’est la raison pour laquel le Zone system la prend comme référence.</p>
<p>Avec un peu de chance, vous vous êtes déjà jeté sur votre <em>color picker</em> favori pour déterminer la valeur de gris d’un pixel de votre rectangle. Vous trouvez 118 sur 255, ce qui équivaut à peu près à 50 %. Là, vous vous dites que votre pixel est bien à 50 % et que je vous raconte des bêtises, mais si vous être à l’aise avec l’espace colorimétrique sRGB, vous savez surement déjà pourquoi.</p>
<p>La fonction de transfert électro-optique (pixel vers l’écran) sRGB est approximativement une courbe gamma 2.2 :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_06_09_gris_moyen/Srgb_eotf_tiny.png" style="margin: 0px auto; display: table; width: 640px; height: 480px;" /></p>
<p>Ce qui veut dire que quand on donne une valeur de 0.5 à notre moniteur calibré sRGB, il ne renvoie pas 50 % d’intensité lumineuse, mais à peu près 0.22 (22 %, ce qui est très proche de 18 %) :</p>
<pre>
<code class="language-python">>>> 0.5**2.2
0.217637640824031</code></pre>
<p>Et on arrive à la raison qui m’a poussé à écrire ce billet :</p>
<p>La raison pour laquelle on ne se prenait pas la tête avec le gris moyen « avant » (en sRGB), c’est parce que qu’une valeur de pixel à 50 % était traduite en 22 % d’intensité lumineuse, ce qui était <em>perçu</em> comme un gris moyen. Donc nos têtes de graphistes ont fait la relation : Gris moyen = pixels à 50 %, mais ça n’est (approximativement) vrai qu’en sRGB. C’est un accident du fait qu’on utilise principalement des moniteurs sRGB.</p>
<blockquote>
<p>Petit parenthèse pour chercher la petite bête : Pour avoir un gris 18 % (et non 22 %), il faut faire mettre un pixel à 45.9 %. Ça ne change pas le propos de mon billet.</p>
</blockquote>
<p>Mais alors se pose la question suivante : Quelle valeur de pixel faudrait-il pour avoir une intensité lumineuse à 50 % sur notre moniteur sRGB ?</p>
<p>En inversant la fonction gamma 2.2 :</p>
<pre>
<code class="language-python">>>> 0.5**(1/2.2)
0.7297400528407231</code></pre>
<p>Donc pour avoir une intensité lumineuse à 50 %, il faudrait des pixels à 73 %, 186/255. Vous pouvez le vérifier sur la courbe précédente.</p>
<p>Ça nous donne l’image suivante :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_06_09_gris_moyen/gris_moyen_003.png" style="margin: 0px auto; display: table; width: 500px; height: 500px;" /></p>
<p>Difficile de dire que vous voudriez faire valider vos persos dans un tel gris, pas vrai ?</p>
<p>Vous pouvez picker le gris, vous aurez 186.</p>
<h3>Pourquoi on a besoin de savoir ça maintenant ?</h3>
<p>Comme vu précédemment, la particularité des moniteurs sRGB faisant qu’on ne se prenait pas vraiment la tête avec ça, mais les choses changent assez vite avec OCIO, ACES et le HDR.</p>
<p>Si vous utilisez OCIO dans un espace sRGB, vous avez peut-être remarqué qu’une valeur de couleur à 0.5 est parfois correctement représenté dans vos logiciels et apparait clair, comme notre seconde image. OCIO définissant un certain nombre de règle pour interpréter les valeurs, il estime qu’une couleur avec des valeurs à 0.5 doit être interprété comme une couleur (et non des données) et la convertira vers l’espace colorimétrique de scène. Vous aurez alors peut-être le réflexe de la diminuer aux alentours des 20 %, ce qui est tout à fait normal.</p>
<p>ACES n’y échappe pas. Sur un moniteur sRGB :</p>
<ul>
<li>Une valeur de 0.31 dans ACES donne l’équivalent du 22 % sur un moniteur sRGB (le « 50 % sRGB »).</li>
<li>Une valeur de 0.262 dans ACES donne l’équivalent 18 % sur un moniteur sRGB.</li>
</ul>
<p>Est-ce que c’est ce qu’il faut utiliser ces valeurs ? La réponse est « ça dépend de votre moniteur de référence ». Si votre moniteur de référence est un moniteur sRGB, alors ces valeurs feront l’affaire. Si c’est un moniteur HDR vous devez vous poser pas mal de questions d’<a href="https://en.wikipedia.org/wiki/High-dynamic-range_television#Comparison_of_HDR_formats" hreflang="en" title="Comparison of HDR formats">espaces colorimétriques</a>, mais il est possible que 18 % de la valeur maximum soit beaucoup trop claire, tant les moniteurs HDR peuvent monter haut. J’avoue ne pas avoir assez creusé le sujet.</p>
<p>En espérant que ce billet vous aura appris quelques trucs.</p>
<p style="text-align: center;">:marioCours:</p>Les groupes de jobs par utilisateururn:md5:2d358298691f9d0775fc25614dc6ddfa2023-02-05T13:43:00+01:002023-04-25T20:44:48+02:00NarannInfographie 3D - Boulotgroupejobrenderfarmui<p><em><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_02_05_job_group/job_group2_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />…pour sauver ta prod et faire ton bonheur. </em>:siffle:</p>
<p>Dans ce billet, nous allons parler d’un système permettant de résoudre un problème récurent de suivi asynchrone, côté utilisateur.</p>
<p>Bon, une explication aussi générique que ça ne vous avance pas des masses… Donc on va le dire autrement :</p>
<p>Quand un graphiste publie sa scène, il n’est pas rare de vouloir lancer un certain nombre de choses immédiatement après la publication, sans le bloquer. S’ensuit un torrent de propositions plus archaïque les unes que les autres pour ne serait-ce que définir le problème : Bienvenue dans le monde magique de l’asynchronie ! :sauteJoie:</p> <blockquote>
<p>Mais putain, Dorian, tu vas encore faire un billet beaucoup trop long avec des termes super générique qui raconte rien ! J’ai des graphistes à aider, moi !</p>
</blockquote>
<p>Ne vous inquiétez pas, on va essayer d’aller à l’essentiel.</p>
<h4>Le problème</h4>
<p>On va être très concret, et lister quelques problèmes récurrents :</p>
<ul>
<li>Quand un modeleur publie son travail, on peut vouloir exporter ses modés/son décor dans la foulée, sans bloquer son Maya.</li>
<li>Quand un layouteur/animateur publie son travail, il est intéressant d’enchainer avec quelque chose derrière. Ça peut être un playblast ou un export alembic/USD, sans bloquer son Maya.</li>
</ul>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2023_02_05_job_group/graphiste_publish.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_02_05_job_group/graphiste_publish.png" style="margin: 0px auto; display: table; width: 427px; height: 258px;" /></a>Plus un pipeline fait de choses pour <s>détester</s> délester le graphiste, plus cette question se pose. :reflechi:</p>
<p>Il y a un paquet d’options disponibles ; Ouvrir un terminal, un subprocess Maya, passer par un microservice (qui ouvre Maya, bien joué, Roger ! :aupoil: ), etc.</p>
<p>Elles sont toutes assez limitées ; Sature la RAM locale, le message d’erreur dans le terminal n’est pas lu/remonté, Maya crash en fond et les microservices c’est opaque pour l’utilisateur.</p>
<p>Le problème est tellement difficile à gérer qu’il n’est pas rare que tout le monde abandonne et choisissent de bloquer le graphiste qui « en profitera pour faire une pause café ». Bon, je sais qu’un graphiste qui boit du café c’est important pour la productivité, mais un lead/sup peut être amené à publier énormément de choses sur des scènes problématiques, et ça serait vraiment con que votre lead modé fasse un arrêt en publiant sa cinquantième retake de brins d’herbe… :nevroz:</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_02_05_job_group/publish_cafe.png" style="margin: 0px auto; display: table; width: 345px; height: 202px;" /></p>
<blockquote>
<p>Abrège, Dorian…</p>
</blockquote>
<p>En gros soit le graphiste ne voit rien, soit il perd du temps. Et je ne parle même pas d’isoler un crash (je sais, je sais, les scripts ne sont pas supposés planter, mais dès que ton input c’est une scène faite par un humain, c’est dogmatique de penser que tout va rouler).</p>
<p>Notez que parmi les options disponibles, je n’ai pas parlé de la farm : On pourrait parfaitement publier la scène puis envoyer une série de job sur la farm pour faire le boulot. :dentcasse:</p>
<p>Le problème est que l’utilisateur ne voit pas ce qui s’y passe et les UI de farm sont souvent imbitables. Et c’est le début de la solution que je propose. :redface:</p>
<h4>La solution</h4>
<p>Le problème de la farm, on l’a vu, c’est que personne ne la regarde parce que :</p>
<ul>
<li>La UI est souvent lourde.</li>
<li>C’est le bordel.</li>
<li>La UI est souvent moche.</li>
<li>C’est le bordel.</li>
</ul>
<p>Pourtant, la farm est surement le moyen de plus simple de garder un job qui échoue tout en permettant de reproduire le problème (et si vos jobs ont des effets de bords, je vous invite à lire ces <a class="ref-post" href="https://www.fevrierdorian.com/blog/post/2018/08/18/La-ferme-de-rendu-premi%C3%A8re-partie-Les-jobs">deux</a> <a class="ref-post" href="https://www.fevrierdorian.com/blog/post/2020/05/16/La-ferme-de-rendu-seconde-partie-Les-jobs">billets</a> sur le sujet :papi: ).</p>
<p>L’approche présentée plus bas part du principe qu’on peut résoudre ces problèmes de façon pragmatique, sans demander un effort de dev trop important ; vous vous doutez que si j’en parle, c’est que j’ai déjà utilisé un mécanisme similaire et que ça fonctionnait. :laClasse:</p>
<p>L’idée est donc de permettre à l’utilisateur de suivre les jobs qu’il envoie, sans lui exposer le détail des jobs en question ; autres que wip/done/pending.</p>
<p>Quel que soit votre système de farm, chaque job a un identifiant. Quelle que soit la tache à faire, il est rare qu’un seul job suffise. Pour un simple playblast, vous aurez souvent la génération des images depuis Maya, l’ajout de cartons et la génération du fichier vidéo.</p>
<p>On va grouper tous ces jobs dans un ensemble qu’on va appeler un <em>job group</em>. Et il va falloir stocker cette entrée dans une base de donnée dédiée (et non, bordel non : ShotGrid n’est pas une base de donnée dédiée, mais <a href="https://www.youtube.com/watch?v=odiKJdjVNsM">d’autres en parlent mieux que moi</a>).</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_02_05_job_group/schema_graphiste_db_farm.png" style="margin: 0px auto; display: table; width: 784px; height: 323px;" /></p>
<p>Chaque graphiste ne voit donc que les <em>job group</em> qu’il a lancé, et qui ne sont que des agrégats de jobs de farm.</p>
<h4>Pseudo-implémentation</h4>
<p>Donc vous sortez votre base de donnée SQL préférée, et vous me créez une table dédiée :</p>
<ul>
<li>User id : Contiendra l’identifiant de l’utilisateur qui a envoyé le groupe de jobs.</li>
<li>Label : Un petit texte faisant office de nom du groupe de jobs.</li>
<li>Liste des job ids : Liste des identifiants des jobs de farm auquel ce groupe fait référence.</li>
</ul>
<p>Même pas besoin de mettre une clef primaire, c’est tellement magique !</p>
<blockquote>
<p>Notez que certains gestionnaires de farm permettent déjà de grouper les jobs, il peut alors être pertinent de pointer vers l’id du groupe de job de votre gestionnaire de farm plutôt que faire la liste vous-même.</p>
</blockquote>
<p>C’est bien, vous avez une table que vous commencez à remplir : Chaque fois qu’un graphiste fait une publication, on envoie une série de job en farm et on crée une entrée dans la table qui stocke la liste des jobs envoyés.</p>
<p>Il faut maintenant afficher ça aux graphistes.</p>
<p>Faites un <a href="https://doc.qt.io/qt-6/qtablewidget.html">QTableWidget</a> avec deux colonnes :</p>
<ul>
<li>Label</li>
<li>Progress</li>
</ul>
<p><em>Label</em> affichera un très court descriptif du <em>job group</em> : « Playblast anim sq9999sh001 », « Export Alembic toto_001 sq9999sh001 ».</p>
<p><em>Progress</em> affiche l’avancée générale des jobs : « WIP 2/5 », « DONE ».</p>
<p>Vous pouvez ajouter quelques boutons comme :</p>
<ul>
<li>Supprimer les jobs terminés.</li>
<li>Copier l’erreur du job sélectionné.</li>
<li>Ouvrir dans « gestionnaire de farm du studio ».</li>
<li>etc.</li>
</ul>
<p>Ça donne un truc comme ça :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_02_05_job_group/job_group_ui_001.png" style="margin: 0px auto; display: table; width: 588px; height: 308px;" /></p>
<p><a href="https://doc.qt.io/qt-6/qtdesigner-manual.html">Designer</a> est ton ami, mais j’y ai quand même passé beaucoup trop de temps. :slow_clap:</p>
<p>Et paf, vous avez une UI qui liste les jobs envoyés par l’utilisateur courant sous formes de groupes, les détails n’étant pas affichés. Ce n’est pas un bug, c’est une feature qui fait que votre UI n’est pas lourde ni repoussante ; elle est minimaliste.</p>
<p>Notez que ce n’est pas parce que cette UI est simple qu’elle doit être codée avec le cul. Au contraire, cette simplicité est supposée se retrouver dans son code. Si vous faites ça bien vous pourrez lancer cette UI depuis n’importe quel logiciel (e.g. juste après une publication).</p>
<p>Avec tout ça, le graphiste peut maintenant publier à la chaine avant d’aller prendre sa pause café (notre problème original), et surtout, a enfin un moyen précis de communiquer sur ce qui est bloqué ou non, sans être dans le noir, tout en permettant aux TD/Wrangler de l’aider rapidement. :banaeyouhou:</p>
<p>Comme nous passons par une base de donnée dédiée, les <em>job groups</em> ne sont pas perdus après un redémarrage. :zinzin:</p>
<p>Certains pourront me répondre que des gestionnaires de farm propose déjà une UI « artist » de leur produit. Notez que je n’ai pas parlé de rendre des images, mais bien de publier de modé/layout/anim. L’idée de ce système est de permettre à des opérations en sorties de tels départements de se faire de façon déportée, tout en restant visible pour l’utilisateur et en lui évitant la charge cognitive qu’implique de voir les jobs sous-jacents.</p>
<p>Exposé de la sorte, les <em>job group</em> sont une abstraction qui permette de faire le lien entre :</p>
<ul>
<li>Une tache côté utilisateur ; J’ai publié ma modé, mon layout, mon anim, etc.</li>
<li>Et des dizaines de jobs de farm ; export png, création du movie, etc.</li>
</ul>
<p>Je continue de penser que filer une grosse UI à un graphiste qui veut juste savoir si son travail a été exporté correctement est une erreur.</p>
<p>La UI va donc faire des requêtes aux jobs de la farm (en utilisant l’API de cette dernière). Ces requêtes peuvent être assez lourdes si on est bête (ce qui n’est surement pas votre cas, si vous lisez ce blog :petrus: ). Voici quelques idées pour améliorer l’implémentation :</p>
<ul>
<li>Ne pas demander à la farm le status des jobs terminé (un <em>flag</em> dédié peut être mis dans la table <em>job group</em> pour ne faire aucun accès à la farm sur un groupe donné, considéré comme déjà terminé).</li>
<li>Ne pas demander à la farm le status des jobs en erreur (un job peut rester des mois en erreur), mais ajouter un bouton « refresh erreur » pour permettre au graphiste de revérifier le job, après que le TD/wrangler ait résolu le souci.</li>
<li>Diminuer l’intervalle des requêtes des status des jobs en attente (<em>pending</em>).</li>
<li>Automatiquement supprimer les <em>job groups</em> terminés depuis X semaines (2, c’est pas mal). Ça peut être un <a href="https://fr.wikipedia.org/wiki/Cron">cron</a>. On peut faire la même chose, sur une durée plus longue pour les jobs en erreur.</li>
</ul>
<p>Autre truc important : Cette UI doit pouvoir être lancé pour n’importe quel utilisateur (par une variable d’environnement, comme JOB_GROUP_UI_USR=julie), pour simplifier le travail des TDs qui peut accéder directement aux problèmes d’un graphiste sans lui demander quoi que ce soit, et dans un élan de grâce qui les caractérise si bien, ils peuvent même cliquer sur le « refresh erreur » à sa place.</p>
<h4>Conclusion</h4>
<p>Si vous n’êtes pas convaincu, c’est que vous manquez simplement d’ouverture d’esprit, vous avez tort, vous aimez Node.js, le chocolat aux raisins, Dieu, et vous êtes une personne méprisable qui aurait surement vendu sa grand-mère en 40.</p>
<p>Je ne vous félicite pas…</p>
<p style="text-align: center;">:marioCours:</p>Geler les rigs en productionurn:md5:d346816af5966faf09e63db5c22d1bc92022-04-17T17:50:00+02:002023-06-09T12:01:49+02:00NarannInfographie 3D - Boulotpipelineproductionrigversion<p><img alt="dessin rig gele" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_02_05_rig_gele/rig_gele_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Ce billet aurait pu être sous-titré : « Se tirer une balle dans le pied, mais rester debout ». :trollface:</p>
<p>Le gèle des versions du rig (et du reste, d’une façon générale) est une question qui revient régulièrement au cours d’une production. Nous allons voir en détail pourquoi est-ce qu’il s’agit d’un problème insoluble (bah ouais… Sinon on en parlerait plus… :baffed: ) et avec de nombreuses ramifications qui nécessitent d’être comprises si on souhaite contenir leurs effets.</p> <h3>Qu’entend-on par « geler une version » ?</h3>
<p>Un paradigme récurrent dans un pipeline d’animation est la capacité à pouvoir gérer des versions « nommées ». Par exemple, on aurait des versions :</p>
<ul>
<li>toto_rig.v1.mb</li>
<li>toto_rig.v2.mb</li>
<li>toto_rig.v3.mb</li>
</ul>
<p>Mais également :</p>
<ul>
<li>toto_rig.latest.mb</li>
<li>toto_rig.release.mb</li>
<li>toto_rig.FIXURGENTSHOT10.mb</li>
</ul>
<p>Les versions numérotées n’ont, en principe, pas vocation à être modifié. La « v3 » dans six mois est <em>supposé</em> être le même fichier que lors de sa publication. :perplex:</p>
<p>Ce n’est pas le cas des versions « nommées » :</p>
<ul>
<li>« latest » serait logiquement défini comme pointant systématiquement sur la dernière version.</li>
<li>« release » sur la version « à utiliser en production ».</li>
<li>« FIXURGENTSHOT10 » une version spéciale visant à résoudre un problème bien spécifique (ce n’est pas propre, mais un tel mécanisme permet cela).</li>
<li>etc.</li>
</ul>
<p>Ces versions « nommées » sont, en quelque sorte, des définitions de ce qu’est la version :</p>
<pre>
<code>latest -> v6
release -> v3
OMAGADSHOT10IZDOWN -> v4</code></pre>
<p>Notez que ces concepts peuvent exister, même si les choses ne sont pas formalisées de la sorte. Par exemple, si un studio n’a pas d’outils pour gérer des versions nommées, il peut le faire en sauvegardant systématiquement la dernière version du rig sur la « v1 » (le fichier <em>toto_rig.v1.mb</em>). Ainsi, la « v1 » peut être considéré comme la « latest » et on ne créera une « v2 » que si le rig change au point de casser les scènes d’animations ; par mise à jour des noms et/ou comportements des contrôleurs.</p>
<p>Dans tous les cas, ces versions (dites « nommées ») ont pour vocation d’être mises à jour et modifiées, sans imposer à ceux qui en dépendent de repointer dessus ; si votre scène pointe vers <em>toto_rig.latest.mb</em>, vous aurez toujours la dernière version. C’est une propriété qui permet d’éviter une gestion granulaire des versions quand la balance entre les conséquences négatives d’une mise à jour de version et le temps économisé par le fait que cette mise à jour soit automatique et instantanée penche en faveur du second.</p>
<p>Traduisez : Si toutes les scènes utilisent « latest » et qu’on choisit de republier (donc, de modifier « latest ») une version qui n’a aucune conséquence sur le comportement du rig (au-delà de corriger, le bug), alors c’est une option valable pour corriger rapidement un rig partout où il est utilisé. Idem pour « v1 », si on choisit de la ré-écraser.</p>
<p>Ce que j’essaye de dire, c’est qu’en pratique il n’est pas forcément nécessaire de pointer vers des versions nouvellement publiées ; quand on se débrouille bien, avoir une version « latest » en référence des scènes d’animation est possible.</p>
<p>Toutefois, il vient forcément un moment ou ce mécanisme, si économique, joue en votre défaveur.</p>
<p>Il arrive un moment où cette version, ici « latest », doit pointer vers une version qui ne changera plus avec le temps, la version numérotée vers laquelle elle pointe. On parle alors de « geler la version ».</p>
<h3>Pourquoi a-t-on besoins de geler les rigs ?</h3>
<p>Même si la réponse arrive d’elle-même en cours de production, il est intéressant de regarder comment les choses se passent avec un exemple concret.</p>
<p>Les parties exposées du rig (ses contrôleurs et attributs) doivent avoir un comportement identique d’une version à l’autre.</p>
<p>Par « comportement identique », j’entends : À valeurs d’attributs et de positions égales entre les deux versions le rig, le résultat dans la scène doit être le même ; la géométrie des objets ne doit pas changer.</p>
<p>En cours de production, cela nous donne :</p>
<p>Les scènes d’animation utilisent une version d’un rig (en référence ou non). Pour cet exemple : La « release » au moment de la génération de la scène, qui pointe disons, sur la « v1 ».</p>
<p>Plusieurs animateurs commencent à animer leurs plans et à les faire valider.</p>
<p>Viens ensuite le moment que tout <em>rigger</em> redoute : Le comportement de base du rig est problématique, et les animateurs passent beaucoup de temps à se battre contre celui-ci pour obtenir ce qu’ils veulent. Il faut modifier le rig pour le bien du reste de la production.</p>
<p>À ce stade, des animations ont été commencées avec un rig donné (la version « release », qui pointe sur la « v1 ») et certaines animations sont peut-être déjà validées.</p>
<p>Il faut créer une nouvelle version du rig en s’assurant que le comportement (le résultat de la pose pour des attributs donnés) ne changent pas. :jdicajdirien:</p>
<p>Plusieurs approches sont possibles suivant la nature le la-dite mise à jour :</p>
<h4>La nouvelle version ne change rien au comportement du rig.</h4>
<p>Ce sont souvent des mises à jour mineurs ; ajout d’un tag sur une shape pour le rendu ou l’export, modification des couleurs des vertices, parfois même une correction d’UV, etc.</p>
<p>Dans une telle situation, vous pouvez changer la version du rig sans que cela influe sur la géométrie et/ou l’animation. Vous pourriez presque écraser la version…</p>
<p>C’est le cas le plus simple à gérer, mais aussi le plus rare.</p>
<h4>Une nouvelle version de rig peut aussi se retrouver à ajouter des fonctionnalités.</h4>
<p>Ce cas est la voie à privilégier lors de la création d’une nouvelle version de rig. C’est sûrement pour cela que c’est, d’après moi, le cas le plus fréquent.</p>
<p>L’idée de base est que cette nouvelle version ne modifie pas le comportement du rig, sauf si on active des choses (qui sont désactivées par défaut). Il s’agit parfois d’ajouter des petits contrôleurs optionnels qui n’était pas présent avant, pour donner plus de contrôle à l’animation ; modifier l’épaisseur des bras, du cou, etc.</p>
<p>Cette méthode peut avoir le désavantage de surcharger le rig, mais est largement compensé par le fait qu’elle ne nécessite pas de discussion avec les équipes d’animation : Comme le comportement est désactivé par défaut, il ne change rien au résultat de ce qui est déjà en prod, et peut être déployé partout sans conséquences.</p>
<p>On a alors « augmenté » le rig. Quand on a une modification à faire en cours de production, il peut être intéressant de privilégier cette approche, quitte à ce qu’elle impose plus de tests.</p>
<p>Optionnel : Suivant la nature de la modification (et si votre pipeline le permet), vous pouvez activer l’attribut (le nouveau comportement) lors de la création des nouvelles scènes d’animation, ce qui permet aux animateurs de ne pas avoir à le faire. Ils vous feront des bisous, vous enverront des chocolats et inonderont votre profil LinkedIn de chants à votre gloire.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_02_05_rig_gele/review_linkedin.png" style="margin: 0px auto; display: table; width: 615px; height: 145px;" />Dernier cas : La nouvelle version de rig casse l’animation.</p>
<p>Ça y est, on est dans le fond du problème : Le comportement des attributs a changé. Mettre cette version en production casserait l’animation. Le gel des versions du rig est votre seule sortie.</p>
<p>Arrivé ici, il est rare qu’on puisse s’en sortir sans conséquences désagréables, et c’est souvent la raison pour laquelle vous aurez à vous poser la question du gel des versions du rig en animation.</p>
<h3>Éviter le problème ?</h3>
<p>Avant d’aborder les stratégies pour minimiser l’impact d’une telle mise à jour, je dois aborder comment l’éviter.</p>
<p>Si vous lisez ce billet, il est possible que les quelques lignes qui suivent ne vous aident pas énormément, mais je suis obligé d’en parler. :redface:</p>
<p>La technique qui permet de diminuer drastiquement les problèmes de rig c’est déjà de les tester au maximum avant leur entrée en production. Si ce n’est pas systématique, les superviseurs anim ne sont pas forcément les plus amènes de remonter des problèmes pertinants. Du fait de leurs responsabilités, ils passeront plus de temps à organiser le travail des équipes, que le nez dans les scènes à animer des plans. Ils auront alors le réflexe de faire des remarques générales sur le comportement du rig. Il est donc important que des leads (ou personnes supposées devenir lead à la suite de la production) puissent également faire des retours. Ces derniers ayant une approche de support (de leurs graphistes), ils se poseront beaucoup plus la question de ce qui est fatiguant au quotidien, autant pour les équipes que pour eux.</p>
<p>Leur feu vert garantis à la production que le département de rig ne soit pas le seul à supporter la responsabilité d’un problème. Si le rig est considéré comme mauvais parce qu’on découvre un problème qui aurait pu être détecté par les animateurs avant le début de la production, le problème ne vient pas du rig, mais du manque de tests de l’animation. Un rigger n’est jamais un utilisateur de ses propres rigs, et malgré l’expérience, rien ne remplace un retour après plusieurs heures d’utilisation.</p>
<p>Un moyen très pratique de rentabiliser ce temps de travail/test est de mutualiser la fabrication des rigs (souvent via un auto-rig), afin qu’ils soient similaires et aient les mêmes options et comportement. Ce qui revient à doubler l’argument celons lequel les tests de rigs sont importants, car ils ne servent pas qu’à un seul rig, mais à tous les rigs à venir.</p>
<p>Mais c’est un autre sujet. :cayMal:</p>
<h3>Stratégies</h3>
<p>Il y a plusieurs façons d’éviter le drame d’une mise à jour de rig qui détruise l’animation. :neutral:</p>
<p>Si vos scènes d’animations pointent vers des versions nommées « release », ou « latest », il va falloir trouver un moyen de définir que ce n’est plus vraiment le cas.</p>
<p>Un gel de version a des implications importantes et c’est le choix de <em>quand</em> vous le faites qui est déterminant. Dis autrement : C’est le moment, dans votre pipeline, ou vous choisissez de geler une version qui va déterminer la façon dont vous allez vous y prendre.</p>
<p>Imaginons la situation suivante :</p>
<ul>
<li>Votre version « release » pointe sur la « v1 ».</li>
<li>Vous souhaitez publier un rig « v3 » que vous voulez passer en « release ».</li>
</ul>
<p>Notez qu’afin de ne pas complexifier mon exemple, je ne parlerais pas de la version « latest », mais la logique est similaire.</p>
<p>La méthode brute consiste à ouvrir toutes les scènes d’animation et changer la référence de « release » vers « v1 », puis de republier une scène de travail. Cette méthode vous garantit qu’un animateur ouvrant sa scène aura le même rig que ce qu’il avait au moment où il a arrêté son travail, même si « release » change entre-temps.</p>
<p>Cette méthode peut fonctionner dans des petits groupes, mais est difficile à mettre en place dans des grosses structures. :papi:</p>
<p>Une autre méthode consiste à stocker la version à laquelle correspondait la version « release » dans chaque publication. En gros, vous laissez la version d’animation pointer vers « release », mais vous mettez, en métadonnées de la version de la scène d’animation, la version vers laquelle « release » pointe. Ainsi, après ouverture de la scène avant export de l’Alembic vous forcerez la version du rig à utiliser.</p>
<p>Ceci a les effets suivants :</p>
<ul>
<li>L’Alembic exporté est garanti d’utiliser la version du rig qu’avait l’animateur au moment de sa publication.</li>
<li>Les scènes d’animations vont casser au moment de l’ouverture, laissant l’animateur la capacité de corriger l’animation. Ou, via un petit outil, un moyen de repointer vers la « v1 », en dur.</li>
</ul>
<p>Cette méthode assume que le problème du rig est bénin à corriger pour l’animateur et que ce dernier va immédiatement voir le problème. Or il est tout à fait possible que :</p>
<ul>
<li>L’animateur, réouvre sa scène pour un correctif en fin (de timeline) d’animation.</li>
<li>Ne remarque pas que la mise à jour a cassé le mouvement en début d’animation.</li>
<li>Republie sa scène pointant vers la dernière version actuelle (« release » en « v3 »).</li>
</ul>
<p>Ceci qui aura pour effet de publier, en métadonnée, le numéro de la version qui ne fonctionne pas en début d’animation (« v3 ») et de propager le problème dans l’Alembic.</p>
<p>Quand je vous disais qu’il n’y a aucune solution miracle… :vomit:</p>
<p>Voici ma solution, imparfaite, qui semble fonctionner « en pratique » :</p>
<ul>
<li>Le rig doit savoir quand une version cassera l’animation/le comportement de la précédente.</li>
<li>Le rig peut livrer sur n’importe quelle version (il n’est pas obligé de systématiquement publier une version <em>v+1</em>, mais peut écraser une version existante).</li>
<li>Les scènes d’animation pointent vers des versions absolues ; <em>v1</em>, <em>v2</em>, etc.</li>
<li>Les nouvelles scènes d’animation pointent vers la dernière version du rig au moment de leur création.</li>
</ul>
<p>Avec cette approche, c’est le rig qui est responsable de ce qu’il livre et de quand il choisit de faire monter une version.</p>
<p>Comme je le disais, ce système est imparfait, car il oblige une organisation plus fine du rig : Ce dernier peut être amené à faire un correctif non-destructif sur une <em>v1</em>, alors qu’on est sur une <em>v4</em> depuis des mois, d’où l’importance de votre pipeline à stocker et exposer facilement aux graphistes les dépendances de fichiers.</p>
<p>Dans tous les cas, ce système est loin d’être parfait. Certains pipelines sont conceptuellement incapables de réécrire sur une version au fil de temps. C’est au cas par cas.</p>
<h3>Le gel des versions, un problème plus vaste</h3>
<p>Vous l’aurez deviné, la problématique du gel de versions est un problème très vaste qui n’a aucune solution miracle :</p>
<ul>
<li>Modélisation : Peut-on republier une version d’un objet quand des plans sont déjà commencés avec, et sont calé de façon très fine ?</li>
<li>Décors : Peut-on republier une version d’un décor en déplaçant des objets qui <em>normalement</em> ne sont pas supposés être modifié par l’animation ?</li>
<li>Rendu : Est-ce que je gèle les versions de tout ce que j’ai en entré (c’est une bonne pratique) et si oui, quand ? En check assemblage ? À la preview ? Après la preview ? Et comment gérer les mises à jours parfois nécessaires ?</li>
</ul>
<p>Chaque département a ses propres logiques qui va dépendre de l’organisation du studio et de son pipeline qui tentent de trouver un équilibre entre la flexibilité et la granularité de la fabrication.</p>
<p>J’espère que ces lignes vous auront ouvert à la complexité de ce problème et vous aideront à proposer des solutions à vos équipes. J’ai mis énormément de temps à sortir ce billet, car comme vous vous en êtes rendu compte, les embranchements sont importants, mais il me semble que c’est le b.-a.-ba. d’un rigger que de gérer (c.à.d., limiter) ce genre de situations.</p>
<p style="text-align: center;">:marioCours:</p>Gérer des caméras sur-samplées dans Nukeurn:md5:896c795391d3f25659093390bf31d0482021-12-31T17:31:00+01:002022-01-03T10:49:02+01:00NarannInfographie 3D - Boulotalembiccameranukesampling<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/bad_shake_cam_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Avez-vous déjà eux des problèmes de rendu dans Nuke en utilisant des caméras ayant des samples entre les frames ? :reflechi:</p>
<p>Non ? Super ! Grace à ce billet, vous allez savoir comment gérer cette situation si un jour ça vous arrive ! :siffle:</p>
<p>On va parler de rendu, de Nuke, de <em>shake cam</em>, d’export Alembic, de sampling temporel, de <em>frame rate</em>, d’expressions, et toutes ses choses qui nous rappellent chaque jour à quel point nos vies sont formidables et méritent d’être vécues ! :petrus:</p> <p>Lors de l’import d’une caméra Alembic, Nuke <em>échantillonne</em> l’animation à la frame. C.à.d qu’il lit les valeurs de l’Alembic à chaque frame. C’est la raison pour laquelle il demande un <em>frame rate</em> :</p>
<p><img alt="Interface Nuke d’import de caméra" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/nuke_cam_abc_import_001.png" style="margin: 0px auto; display: table; width: 597px; height: 224px;" /></p>
<p>C’est parfait dans le cas d’un Alembic de caméra samplées à la frame, car chaque frame Nuke correspondra à un sample de votre Alembic. Il y aura donc une correspondance directe entre les samples de la caméra exportée et ce que Nuke lira.</p>
<p>En pratique on peut travailler comme ça sur la plupart des projets, alors pourquoi s’embêter ? :perplex:</p>
<h3>Pourquoi sur-sampler une caméra ?</h3>
<p>Quand vous avez régulièrement des mouvements de caméras très rapides dans vos plans, le fait de faire un export Alembic à la frame perds la profondeur du mouvement dans le motion blur, au rendu.</p>
<p>L’exemple le plus évident concerne les <em>shake cam</em>. Quand un <em>shake cam</em> un peu ample est samplé à la frame, chaque image se retrouve avec une sorte de flou directionnel dont la direction est aléatoire, ce qui n’est pas du plus bel effet.</p>
<p>Sur-sampler la caméra permet de garder la profondeur du mouvement de <em>shake</em>.</p>
<p>Bien entendu (et c’est trop souvent le cas en production) si la fréquence du <em>shake cam</em> entre les frames est trop importante, vos samples internes seront quasi-aléatoires, et vous aurez l’équivalent de micro-vibrations à la frame, ce qui n’est pas du plus bel effet :</p>
<p><img alt="Courbes de caméras superposées" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/bad_shake_cam_001.png" style="margin: 0px auto; display: table; width: 440px; height: 304px;" /></p>
<p>Ici, la belle courbe verte est le mouvement théorique du shake cam (le résultat de l’expression). On remarque que la fréquence est élevée entre les frames. La courbe orange est la courbe de l’Alembic exporté de cette caméra avec les samples (0, 0.125, 0.25, 0.375, 0.5) marqués en rouge. On remarque que la haute fréquence originale entraîne un sampling foireux du mouvement de caméra.</p>
<p>Par contre, un export de caméra sur-samplé d’un <em>shake cam</em> avec une fréquence faible entre les images (une demie période max) donnera une profondeur au motion blur.</p>
<p>Bref, on sur-sample pour les <em>shake cam</em>, mais uniquement si on arrive à garder leur fréquence a niveau de la frame… :tuComprendRien:</p>
<p>Un autre cas où le sur-sampling est justifié concerne les rotations de caméras rapides (un classique étant un objet qui passe devant la caméra très rapidement). Pour peu que cette rotation soit combinée à un déplacement et soyons fous, un <em>shake cam</em>, si vous ne sur-samplez pas, il y a fort à parier que le mouvement central soit une grosse bouilli, illisible.</p>
<p>Attention toutefois : Comme nous allons le voir, sur-sampler une caméra n’est pas gratuit et implique que tout ce qui utilisera ladite caméra soit capable d’interpréter correctement ces samples. Donc si votre projet n’a ni <em>shake cams</em>, ni mouvements rapides : Laissez tomber. Vous aurez sûrement d’autres problèmes à gérer.</p>
<p>Mais si vous êtes ici, c’est sûrement parce que vous avez déjà rencontré le problème, pas vrai ? :dentcasse:</p>
<h3>Comportement de Nuke</h3>
<p>Quand vous importez votre caméra Alembic dans Nuke avec le bon <em>frame rate</em>, ce dernier va créer une clef à chaque frame. Le comportement de Nuke est très con :</p>
<p>Il avance d’une frame. Prends la valeur des attributs dans l’Alembic, sans interpoler (nous y reviendrons), pose une clef pour ces attributs et passe à la frame suivante. C’est tout. :baffed:</p>
<h3>Le problème</h3>
<p>Une fois qu’on connaît le comportement de Nuke, vous comprenez aussi que si vous avez une caméra avec 5 samples (0, 0.125, 0.25, 0.375, 0.5), il ne lira et ne créera les clefs que sur le premier sample de chaque cycle (t=0). Il ne prendra ainsi pas compte des 4 samples intermédiaires (0.125, 0.25, 0.375, 0.5) ce qui peut être un vrai problème si vous souhaitez faire un rendu 3D dans Nuke, et si vous importez une caméra animée dans Nuke, c’est sûrement pour ça (je pense au <em>matte painting</em>).</p>
<h3>Contourner le problème</h3>
<p>Sachez qu’il existe un moyen détourné de forcer Nuke à lire tous les samples de notre Alembic et obtenir un mouvement de caméra sur-samplé (Merci à Vincent Glaize pour l’astuce). :gne:</p>
<p>Pour cela, il faut jouer sur la valeur de <em>frame rate</em> du nœud Nuke. L’idée est d’augmenter le <em>frame rate</em> de lecture de l’Alembic pour attraper tous les samples, pour ensuite scaler la vitesse de lecture suivant un ratio inverse.</p>
<p>Pas de panique, on va procéder par étapes. :hehe:</p>
<p>On a donc une caméra Alembic exporté en 24 fps, venant d’une scène en 24 fps (si je précise, c’est parce qu’il n’est pas rare de se faire avoir :gniarkgniark: ).</p>
<p>La caméra est exportée avec cette commande MEL :</p>
<pre>
<code class="language-cpp">AbcExport -j "-frameRange 1 2 -frameRelativeSample 0 -frameRelativeSample 0.125 -frameRelativeSample 0.25 -frameRelativeSample 0.375 -frameRelativeSample 0.5 -dataFormat ogawa -root |camera1 -file ma_super_cam.abc";
</code></pre>
<p>Voici la sortie de la commande <em>abcls</em> :</p>
<pre>
<code class="language-bash">$ abcls -t ma_supe_cam.abc
Time Samplings:
0 Uniform Sampling. Start time: 0 Time per cycle: 1
Max Num Samples: 1
1 Cyclic Sampling. Time per cycle:0.0416667
Start cycle times: 0.0416667, 0.046875, 0.0520833, 0.0572917, 0.0625
Max Num Samples: 10</code></pre>
<p>Au passage : Peu de gens le savent, mais un fichier Alembic stock ses samples en secondes, pas en frames.</p>
<p>Multipliez ses valeurs par le <em>frame rate</em> correspondant à votre export de caméra (ici 24) pour obtenir qu’équivalent en frame :</p>
<pre>
<code class="language-python">>>> samples = [0.0416667, 0.046875, 0.0520833, 0.0572917, 0.0625]
>>> [sample * 24 for sample in samples]
[1.0000008, 1.125, 1.2499992, 1.3750008, 1.5]</code></pre>
<p>Si on fait fi des problèmes de précision dûes à la sortie de la commande <em>abcls</em>, on obtient : 1.0, 1.125, 1.25, 1.375, 1.5. On a donc bien nos 5 samples aux bonnes positions.</p>
<p>Voici les courbes d’animation du <em>translate Y</em> de ma caméra original et son équivalent Alembic, en superposées. La courbe verte est l’animation originale de la caméra, la courbe orange, celle de l’Alembic :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/my_super_cam_sample_anim_001.png" style="margin: 0px auto; display: table; width: 513px; height: 284px;" /></p>
<p>Notez que cette animation va de l’image 1 à 1.5, la position des samples est en rouge.</p>
<p>Si on amène ça dans Nuke à un <em>frame rate</em> de 24, on ne verra rien, car la position du sample sur l’image 1 (et 2) est à 0 :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/my_super_cam_sample_nuke_24_001.png" style="margin: 0px auto; display: table; width: 322px; height: 81px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/my_super_cam_sample_nuke_24_002.png" style="margin: 0px auto; display: table; width: 231px; height: 129px;" /></p>
<p>Donc quelle valeur de <em>frame rate</em> mettre pour faire correctement apparaître ces samples dans Nuke ? :reflexionIntense:</p>
<p>L’écart entre chaque sample est : 0.125 × la durée d’une image. 1/0.125 = 8, il y a donc 8 samples par image. Il faut donc multiplier le <em>frame rate</em> original par 8 pour avoir tous les samples de façon précise : On va donc entrer une valeur de <em>frame rate</em> de 24 × 8 = 192 dans notre nœud Nuke :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/my_super_cam_sample_nuke_192_001.png" style="margin: 0px auto; display: table; width: 368px; height: 75px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/my_super_cam_sample_nuke_192_002.png" style="margin: 0px auto; display: table; width: 402px; height: 146px;" /></p>
<p>Vous l’aurez compris, on dit à Nuke qu’il n’y a pas des samples tous les 1/24 secondes, mais 1/192 secondes. Il va donc aller chercher, dans l’Alembic, la valeur des attributs à ces temps-là en pensant que c’est une frame. :laClasse:</p>
<h4>Aparté sur l’interpolation</h4>
<p>Je vous disais que lorsque Nuke cherchait les valeurs des samples, il n’interpolait pas. Ça veut dire qu’il faut que Nuke tombe à l’endroit exact où vos samples sont placés. Multipliez la valeur de <em>frame rate</em> par 5 (192 × 5 = 960), et regardez la courbe :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/my_super_cam_sample_nuke_960_001.png" style="margin: 0px auto; display: table; width: 288px; height: 59px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/my_super_cam_sample_nuke_960_002.png" style="margin: 0px auto; display: table; width: 609px; height: 194px;" /></p>
<p>Explication : Quand Nuke tombe sur un temps qui n’a pas de sample dédié, il remonte au sample précédent, sans interpoler, prends sa valeur, et mets la clef. Il fait une sorte d’écho du sample précédent.</p>
<p>D’où l’importance de tomber exactement sur les samples exportés. :nevroz:</p>
<p>Bon, on a réussi à avoir l’entièreté des sur-samples pris en compte par Nuke, mais notre animation de caméra est maintenant 8x plus lente… Il va falloir scaler le temps. Pour cela, nous allons recréer une caméra identique, mais nous allons reconnecter les attributs via une expression multipliant le temps par 8 pour l’attribut en question :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/nuke_node_setup.png" style="margin: 0px auto; display: table; width: 313px; height: 305px;" /></p>
<p>Ici, <em>Camera_originale</em> est mon nœud de caméra pointant sur l’Alembic et lu à un <em>frame rate</em> de 192.</p>
<p><em>Camera_mirror</em> est une caméra vide créée manuellement avec des expressions pour relier chaque paramètre :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/nuke_cam_mirror_setup.png" style="margin: 0px auto; display: table; width: 400px; height: 66px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/nuke_cam_mirror_setup_expr.png" style="margin: 0px auto; display: table; width: 495px; height: 89px;" /></p>
<p>Par exemple :</p>
<pre>
<code>Camera_originale.translate.y(t*8)</code></pre>
<p>Ça fait pas mal de paramètres à reconnecter, mais vous pouvez faire ça via un script où le faire manuellement une fois et garder votre setup.</p>
<p>Une fois cela fait, mettez un nœud de rendu, ajouter quelques samples, calez votre <em>shutter</em> (ici, 0.0 - 0.5) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/nuke_render_param.png" style="margin: 0px auto; display: table; width: 418px; height: 230px;" /></p>
<p>En enfin, rendez :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_10_nuke_alembic_cam_subsampling/nuke_render_final.png" style="margin: 0px auto; display: table; width: 383px; height: 209px;" /></p>
<p>Votre motion blur contient bien l’entièreté du mouvement. :banaeyouhou:</p>
<h3>Limitations</h3>
<p>Avant de finir il est important de garder quelques limitations en tête. Il est toujours assez difficile de faire des rendus cohérents à travers différents moteur, du fait des spécificités de chacun. Les moteurs de rendu de Nuke ont bien moins de features que les autres. Par exemple, votre contrôle sur la distribution des samples le long du <em>shutter</em> n’est pas pilotable ; dans la plupart des moteurs on peut alterner entre une distribution uniforme, en triangle, gaussienne, trapèze, etc.</p>
<h3>Conclusion</h3>
<p>Gardez à l’esprit qu’il n’est pas toujours nécessaire de s’embeter avec ça. Mais dès que ça bouge vite et que vous passez dans plusieurs logiciels de rendu pour un seul plan, vous risquez de ne pas pouvoir y couper.</p>
<p>J’espère que ce hack vous permettra de vous dépêtrer de quelques soucis fâcheux. :seSentCon:</p>
<p>À très bientôt !</p>
<center>:marioCours:</center>Lire les Light Stats dans Guerillaurn:md5:7eddc21e4a73a95f258b6fa8e4c6ae942021-12-14T23:25:00+01:002021-12-22T11:05:44+01:00NarannInfographie 3D - Boulotguerillalightlogrendustats<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/scene_mur_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Depuis la version 2.3.9, Guerilla dispose d’un log de rapport de contributions des lights de vos scènes. Il n’est pas évident d’interpréter correctement ces valeurs : Elles ne sont pas forcément simples à comprendre, et encore moins à mettre en relation avec l’image. :reflechi:</p>
<p>Nous allons donc commencer par expliquer ce qu’elles représentent, puis nous commenterons un petit rendu visant à pousser l’efficacité de ses statistiques dans leur retranchement.</p>
<p>Notez que cette version est sortie ce soir et que je n’ai pas pu m’empêcher de faire un billet… :baffed:</p>
<div id="simple-translate">
<div>
<div class="simple-translate-button isShow" style="background-image: url("moz-extension://dd0ffa57-a8f2-46fd-9898-40a206abc3b9/icons/512.png"); height: 22px; width: 22px; top: 47px; left: 939px;"> </div>
<div class="simple-translate-panel " style="width: 500px; height: 300px; top: 0px; left: 0px; font-size: 13px; background-color: rgb(255, 255, 255);">
<div class="simple-translate-result-wrapper" style="overflow: hidden;">
<div class="simple-translate-move" draggable="true"> </div>
<div class="simple-translate-result-contents">
<p class="simple-translate-result" dir="auto" style="color: rgb(0, 0, 0);"> </p>
<p class="simple-translate-candidate" dir="auto" style="color: rgb(115, 115, 115);"> </p>
</div>
</div>
</div>
</div>
</div> <h3>Activer le rapport</h3>
<p>Pour activer le rapport de statistique des lights, il faut aller dans <em>Preferences</em>, <em>Rendering</em>, <em>Logs & Diagnostics</em>, mettre <em>Verbosity</em> à « <em>Diagnostics</em> » et cocher <em>Render Statistics</em> :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/enable_light_stats.png" style="margin: 0px auto; display: table; width: 337px; height: 263px;" /></p>
<h3>Explication détaillée</h3>
<p>À la fin de votre rendu, vous aurez quelque chose qui ressemble à ça :</p>
<pre>
<code>Light Stats:
nSmp | TL | TL% | OL% | Eff% | Separate Lights
10.3M | 1065321.2 | 13.1 | 10.5 | 80.4 | Group9|SkyLight|Sky
nSmp | TL | TL% | OL% | Eff% | Shared Lights
5.46M | 405505.3 | 5.0 | 4.0 | 80.6 | Group8|SquareLight
5.45M | 880797.4 | 10.8 | 9.0 | 83.1 | Group9|SkyLight|Sun
1.80M | 307087.1 | 3.8 | 3.3 | 87.2 | Group7|SquareLight2
3.49M | 581026.5 | 7.1 | 6.3 | 87.9 | Group6|DistantSpotLight
2.10M | 372714.8 | 4.6 | 4.0 | 88.2 | Group7|SquareLight3
3.83M | 1379261.3 | 16.9 | 15.0 | 88.9 | Group5|DistantSpotLight1
2.37M | 103511.3 | 1.3 | 1.1 | 89.8 | Group3|SpotLight
2.53M | 243524.2 | 3.0 | 2.7 | 90.5 | Group2|SpotLight
4.31M | 709680.5 | 8.7 | 8.1 | 92.8 | Group7|SquareLight1
2.68M | 299954.9 | 3.7 | 3.4 | 92.9 | Group1|SpotLight
4.42M | 1806236.6 | 22.1 | 21.1 | 95.4 | Group4|DistantLight
-----------------------------------------------------------------------
48.7M | 8154621.0 | | | | Total</code></pre>
<p style="text-align: center;"><em>« Pas mal, non ? C’est français. »</em></p>
<p>Pas de panique, on va commenter tout ça ! :hehe:</p>
<p>Comme vous vous en doutez, <em>nSmp</em> est le nombre de samples envoyés sur la light. Je ne m’éternise pas. :redface:</p>
<p>Les deux autres termes sont en revanche, plus complexes, mais ne vous inquiétez pas, je vais écrire doucement pour ne pas vous perdre… :bete:</p>
<p><em>TL</em> est le total d’intensité lumineuse (<em>Total Luma</em>) émis par la lampe sans tenir compte de l’occlusion (c.à.d, sans prendre en compte le test d’ombre).</p>
<p>Je suis obligé de m’arrêter pour expliquer un peu comment fonctionne Guerilla (et je suppose que c’est le cas pour la majorité des ray tracer du marché). Je vais volontairement être très grossier, le but est de comprendre un log, pas de vous faire un cours sur les moteurs de rendu (si ça vous intéresse c’est <a href="https://pbr-book.org/" hreflang="en" title="Physically Based Rendering: From Theory To Implementation">par ici</a>).</p>
<p>Quand un rayon arrive sur une surface, le moteur calcul l’illumination de ce que nous allons appeler le <em>shading point</em> (en rouge sur le schéma ci-dessous). Pour cela il va utiliser la normale de la surface du <em>shading point</em> et en extraire un hémisphère (la normale étant la direction du pôle) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/schema.png" style="margin: 0px auto; display: table; width: 794px; height: 606px;" /></p>
<p>Avec cet hémisphère (en vert), le moteur peu déjà éliminer les lights étant « derrière l’hémisphère » (c.à.d, derrière la normale du <em>shading point</em>, light 1) ainsi que celles « dos au <em>shading point</em> » (c.à.d, qui montrent leurs fesses au <em>shading point</em>, light 3) car elles ne contribueront pas à l’illumination du <em>shading point</em>.</p>
<p>Reste les autres lights, celles qui sont face au <em>shading point</em> : Comment déterminer la light ayant la meilleure contribution ? :petrus:</p>
<blockquote>
<p>Dans notre exemple, il n’y en a qu’une (light 2), mais ce schéma ne servait à qu’à illustrer le mécanisme d’élimination des lights.</p>
</blockquote>
<p>On pourrait lancer des rayons au hasard sur toutes les lights, mais en pratique, seules quelques-unes contribueraient vraiment à l’illumination du <em>shading point</em>. La question est donc de savoir s’il existe un moyen de déterminer quelles sont les lights qui pourraient contribuer le plus si, en effet, elles étaient visibles du <em>shading point</em>.</p>
<p>Et c’est là que c’est contre-intuitif : Pour déterminer s’il est pertinent de faire un coûteux test de visibilité de la light par le <em>shading point</em>, on va prendre chaque light et déterminer sa contribution théorique, c.à.d l’intensité lumineuse qu’elle « pourrait » générer sur le <em>shading point</em> si elle est visible par ce dernier.</p>
<p>Dis autrement, avant d’envoyer notre unique et coûteux rayon d’occlusion (test de visibilité) sur une light, on détermine l’illumination théorique de chaque light sur le <em>shading point</em>.</p>
<p>Mais comment on détermine la contribution théorique d’une light sans traverser la scène ? On calcule sa contribution (ou une approximation) comme s’il n’y avait rien d’autre que la light qui nous intéresse. On fait ça pour toutes les lights pouvant contribuer au <em>shading point</em>, et c’est seulement une fois qu’on a isolé la lampe la plus pertinente qu’on teste sa visibilité (c.à.d, qu’on lance un rayon dessus pour vérifier si un mur/objet est devant ou non). Encore une fois : On procède ainsi car calculer la contribution d’une light sans lancer de rayon est beaucoup (beaucoup) plus rapide que le coût du rayon pour déterminer si elle est visible au <em>shading point</em>.</p>
<p>Mais l’effet de bord est évident : Si vous avez une light derrière un mur, mais en direction de votre <em>shading point</em>, le moteur privilégiera cette lampe.</p>
<p>C’est ça <em>le</em> problème des path tracers. On peut tenter d’y répondre par du bidir, du vertex merging, etc. Mais le path tracing a des avantages que ces solutions ne gèrent pas facilement (ray differential pour le mip mapping).</p>
<p>Comprenez, on l’a dans l’os, lapin l’choix, et c’est pour ça que dès qu’on a du mal à garder le contrôle sur un lighting (comprenez, qu’on ne fait pas un lighting aux petits oignons par plan), on crée forcément des situations où le path tracer se fait avoir. :IFuckTheWorld:</p>
<p>Une fois que vous avez compris ça, vous êtes en mesure d’expliquer la relation entre TL% et OL%.</p>
<p>Première chose : <em>TL%</em> est le pourcentage d’intensité lumineuse sans tenir compte de l’occlusion (comprenez, la contribution « théorique ») de la light par rapport à toutes les lights de la scène. La somme du <em>TL%</em> de toutes les lights est 100 % (vous pouvez faire le calcul :siffle: ).</p>
<p>Et enfin : <em>OL%</em> est le pourcentage d’intensité lumineuse en prenant en compte l’occlusion (comprenez, après un test de visibilité réussi) de la light par rapport à toutes les lights de la scène.</p>
<p>On a donc <em>OL%</em> / <em>TL%</em> = Ratio d’efficacité d’une light. Exemple dans notre cas sur la light la moins efficace :</p>
<pre>
<code class="language-python">>>> 4.0/5.0
0.8 # 80 %</code></pre>
<p>Et cela correspond bien à notre valeur de <em>Eff%</em> (moins les imprécisions). :youplaBoum:</p>
<p>Vous avez donc 20 % de perte (tests de visibilité échoué) pour cette light, sur votre image.</p>
<p>En pratique, la valeur <em>TL</em> n’est pas d’une grande aide, ce sont les valeurs <em>TL%</em>, <em>OL%</em> et <em>Eff%</em> qui sont importantes. Un <em>OL%</em> à 0.0 indique que la light n’amène pas grand-chose à l’image :</p>
<ul>
<li>Si ce <em>OL%</em> à 0.0 est combiné à un <em>TL%</em> élevé, ça veut dire que le path tracer se fait avoir.</li>
<li>Si le <em>TL%</em> est également proche de 0.0, l’<em>Eff%</em> peut être élevé, mais on peut se poser la question de l’intérêt de cette light qui ne semble pas énormément contribuer au rendu final.</li>
</ul>
<h3>Relation entre <em>separate sampling</em> et <em>shared sampling</em></h3>
<p>Comme vous avez pu le remarquer sur le log d’exemple, les lights sont divisées en deux groupes :</p>
<ul>
<li><em>Separate lights</em></li>
<li><em>Shared lights</em></li>
</ul>
<p>Sauf quelques exceptions (que nous verrons plus loin), une light est en <em>shared sampling</em> par défaut. Cela veut dire qu’elle fera partie du paquet de lights servant à déterminer laquelle est la plus pertinente à tester pour chaque <em>shading point</em>. C’est-à-dire que pour chaque <em>shading point</em> on fera une estimation de la contribution de chaque light du paquet et on en choisira une pour tester sa visibilité au <em>shading point</em>.</p>
<p>Guerilla permet de marquer une light comme <em>separate sampling</em>. Cela veut dire que cette light sera exclue de la boucle d’évaluation de la contribution théorique (en gros, elle ne fera pas partie des <em>shared lights</em>). Guerilla estimera qu’une telle light aura toujours une contribution significative et fera un test de visibilité dans tous les cas (sauf si elle est derrière le shading point ou dos à sa normale, mais ça, c’est logique). En gros, vous dites au moteur de vous faire confiance et de systématiquement utiliser cette light en plus de la light qu’il va choisir (notez le « en plus »). Il est donc important que l’attribut <em>separate sampling</em> ne soit coché que sur les lights qui apportent quasi systématiquement quelque chose à l’image.</p>
<p>C’est la raison pour laquelle les <em>env lights</em> ont l’attribut <em>separate sampling</em> par défaut.</p>
<h3>Le test du mur</h3>
<p>Comme je suis taquin, je vais tenter de piéger Guerilla et surtout de voir si les statistiques des lights me remonte cette information. :reflexionIntense:</p>
<p>Voici la scène :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/scene_mur_001.png" style="margin: 0px auto; display: table; width: 297px; height: 443px;" />Un plan, une sphère, deux lights positionnées à 180 l’une de l’autre et un mur sur celle de droite :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/scene_mur_002.png" style="margin: 0px auto; display: table; width: 249px; height: 161px;" />Le rendu :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/scene_mur_003.png" style="margin: 0px auto; display: table; width: 512px; height: 384px;" />Clairement la light derrière le mur ne contribue pas au rendu, et les statistiques le confirme :</p>
<pre>
<code>Light Stats:
nSmp | TL | TL% | OL% | Eff% | Separate Lights
nSmp | TL | TL% | OL% | Eff% | Shared Lights
15.8M | 4436347.0 | 42.2 | 0.0 | 0.0 | derriere_mur|SquareLight
23.2M | 6064878.0 | 57.8 | 48.7 | 84.4 | devant_mur|SquareLight
-----------------------------------------------------------------------
39.0M | 10501225.0 | | | | Total</code></pre>
<p>On remarque immédiatement l’<em>Eff%</em> de la light derrière le mur à 0. Cette light est inefficace et peut être retirée du rendu sans risque. :bravo:</p>
<p>La raison pour laquelle le <em>TL%</em> de la light derrière le mur n’est que de 42 % et non de 50 % comme on pourrait s’y attendre (la scène étant très symétrique d’un point de vue lighting) c’est parce que les <em>shading points</em> de la caméra vers le mur excluent de facto la light derrière le mur (rappelez-vous, l’hémisphère). Le mur représentant une partie non négligeable de l’image, il est logique que le moteur estime, à juste titre, que la light de gauche (devant le mur) contribue plus à l’image.</p>
<p>Regardons les temps de rendu. Ma <em>RenderPass</em> est à 256 samples, threshold à 0.03 et min samples à 16. Le rendu original se fait en 26 secondes sur mon modeste PC. Si je prune la light derrière le mur, je tombe à 19 secondes.</p>
<p>Allons encore plus loin et regardons les light stats de ce « lighting parfait » :</p>
<pre>
<code>Light Stats:
nSmp | TL | TL% | OL% | Eff% | Separate Lights
nSmp | TL | TL% | OL% | Eff% | Shared Lights
22.8M | 3876291.8 | 100.0 | 77.4 | 77.4 | devant_mur|SquareLight
-----------------------------------------------------------------------
22.8M | 3876291.8 | | | | Total</code></pre>
<p>Notez le 77.4 % de l’efficacité de la light. Elle semble diminuer par rapport au 84.4 % du rendu précédent. Rien d’anormal pour autant : Sur le premier rendu, le moteur avait le choix entre deux lights. Si, par exemple il tombait sur un <em>shading point</em> de la partie droite de la sphère (pointant vers la droite de la scène), il choisissait la light derrière le mur et c’est elle qui ratait son test de visibilité. L’efficacité de la light de droite était donc conservée.</p>
<p>Je soupçonne que quand il n’y a aucune light à choisir, Guerilla considère automatiquement le test de visibilité comme ayant échoué. C’est donc les ombres de la partie de droite de l’image qui entraînent un échec du test de visibilité.</p>
<p>Notez aussi le nombre total de samples lancé avant de converger : Un tiers de moins. Une information confirmée par l’AOV de heat.</p>
<p>Rendu avec les deux lights :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/scene_mur_heat_001.png" style="margin: 0px auto; display: table; width: 510px; height: 384px;" />Rendu avec une seule light, à gauche :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_12_18_guerilla_light_stats/scene_mur_heat_002.png" style="margin: 0px auto; display: table; width: 510px; height: 384px;" />Sur la première image, on remarque que Guerilla n’arrive pas à sampler une partie de l’image (en particulier partie droite de la sphère). Et pour cause, aucune light n’illumine ces zones. :cayMal:</p>
<p>Sur la seconde image, Guerilla ne se fait plus « avoir » en croyant que la light derrière le mur va illuminer le dos de la sphère.</p>
<h3>Conclusion</h3>
<p>L’analyse du sampling des lights est très complexe, car la frontière entre l’ombre d’un objet (test de visibilité raté, mais légitime) et le fait qu’une light particulière apporte peu contribution (voir aucune), est difficile à déterminer. Avec son log, Guerilla propose un moyen de mettre le doigt sur les lights étant réellement inefficace. Une light avec un <em>Eff%</em> à 0 peut être caché sans aucun risque pour vos rendus. :sauteJoie:</p>
<p>J’espère que cette brève présentation vous a plu et que cette nouvelle feature vous permettra d’identifier les lights parasites de vos rendus, avec, pourquoi pas, un parsing automatique des logs… :siffle:</p>
<p>À très bientôt !</p>
<center>:marioCours:</center>Faire un AOV d’occlusion avec la DiffuseColor dans Guerillaurn:md5:9eabbc909fb9e257a5159df830d120062021-10-09T23:29:00+02:002021-11-18T10:24:26+01:00NarannInfographie 3D - Boulotambientaovguerillaocclusion<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Il y a plein de façons de faire de l’<em>ambiant occlusion</em>. :hehe: Certains sortent cette passe au moment du lighting pour donner de la flexibilité au compo ; pour assombrir les creux. D’autres sortent cette passe avant l’étape de rendu, au moment de l’animation, voir du layout ; pour du contrôle qualité. Dans chaque situation, le contenu de la passe d’<em>ambiant occlusion</em> est adapté au besoin ; pour du compo il faut qu’elle soit en niveaux de gris, pour du contrôle qualité on peut afficher chaque objet avec une couleur particulière, etc.</p>
<p>Quand le lookdev des assets est (enfin) disponible, il peut être intéressant d’avoir une <em>ambiant occlusion</em> générée avec les textures de lookdev plutôt que des couleurs uniformes. C’est ce que nous allons faire dans ce billet. :bravo:</p> <h3>Méthode de base</h3>
<p>Avant de faire des choses compliquées, on peut s’appuyer sur ce que Guerilla propose, à savoir les AOVs <em>Occlusion</em> et <em>Albedo</em>. Voici une scène éclairée avec amour :petrus: :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_001.png" style="margin: 0px auto; display: table; width: 261px; height: 203px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_002.png" style="margin: 0 auto; display: table;" /></p>
<p>Le lookdev des objets, c’est juste une texture colorée différente mise dans l’attribut « DiffuseColor » de leur « Surface2 » respectif. :siffle:</p>
<p>On lui ajoute les deux AOVs, <em>Occlusion</em> et <em>Albedo</em> :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_003.png" style="margin: 0 auto; display: table;" /></p>
<p>Utiliser cette méthode implique de devoir recompositer les deux images en sortie de Guerilla (via un précomp, par exemple), mais ça nous oblige à gérer un processus externe. Ce serait quand même plus simple si Guerilla pouvait sortir l’ambiant occlusion en utilisant directement l’Albedo. :reflechi:</p>
<p>Ce n’est pas possible directement (on ne peut pas utiliser un AOV dans un autre AOV), mais on peut s’en rapprocher de façon détournée (disons).</p>
<h3>Logique de récupération des shaders dans Guerilla</h3>
<p>Petit rappel au cas où vous ne seriez pas au courant. :mayaProf:</p>
<p>Quand vous assignez un material dans un RenderGraph, Guerilla n’assigne en fait qu’un nom (ici, « Surface2 ») à l’attribut <em>shader.surface</em>. Pour vous donner une idée, sélectionnez un objet, faites « Shift+D » et regardez votre console pour voir la liste des attributs qui seront envoyées au moteur :</p>
<pre>
<code class="language-markdown">...
shade.opacity: 1
shade.opacitybakeres: {256,256}
shader.displacement: ""
shader.surface: "Surface2" << Notre assignation de shader de surface, ici.
shader.volume: ""
shade.scale: 1
shade.subshadersuseexternal: false
...</code></pre>
<p>Au moment de l’envoi au moteur, Guerilla va chercher le material auquel correspond ce nom en suivant une logique assez particulière :</p>
<ul>
<li>Votre scène.</li>
<li>La librairie.</li>
</ul>
<p>Pour faire court, si un material nommé « Surface2 » est présent à la racine de votre scène, il sera utilisé à la place du <em>Surface2</em> de la librairie :</p>
<p><img alt="Guerilla surface2 scene vs library" class="media" height="242" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/guerilla_ao_diffuse_color_001.png" style="margin: 0px auto; display: table;" width="405" /></p>
<p style="text-align: center;"><em>Ici, c’est le Surface2 à gauche qui sera envoyé au moteur, prenant le pas sur celui de la librairie, à droite. </em>:tuComprendRien:</p>
<p>Et comme vous le savez sûrement, il est possible de modifier le material importé dans votre scène. Ces deux particularités permettent, finalement, d’éditer le comportement d’un shader pour toute la scène. :idee:</p>
<p>Donc si on modifie notre <em>Surface2</em> importé localement afin que sa seule sortie soit une <em>occlusion</em> multipliée par son attribut <em>DiffuseColor</em>, le calcul d’illumination de tous les objets sera l’ambiant occlusion utilisant ses propres textures.</p>
<p>Faisons donc ça ! :youplaBoum:</p>
<h3>Méthode avancée</h3>
<p>Créez un shader Surface2 à la racine (« Ctrl+Espace », « surface2 », « Entrée ») :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_004.png" style="margin: 0px auto; display: table; width: 286px; height: 94px;" /></p>
<p>Le shader s’importe à la racine de votre scène :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_005.png" style="margin: 0px auto; display: table; width: 624px; height: 188px;" /></p>
<p>Entrez à l’intérieur et supprimez tout ce que vous y trouvez !</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_006.png" style="margin: 0 auto; display: table;" /></p>
<blockquote>
<p>Notez qu’un bug est présent en version 2.3.0, il faut laisser le nœud « Surface » pour avoir un rendu correct. Ce bug est corrigé en version 2.3.1.</p>
</blockquote>
<p>Ajouter un nœud AOV (« Ctrl+Espace », « aov », « Entrée ») :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_007.png" style="margin: 0px auto; display: table; width: 125px; height: 134px;" />Renommer « Value » par un nom perso « ColoredAO » :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_008.png" style="margin: 0px auto; display: table; width: 446px; height: 251px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_009.png" style="margin: 0 auto; display: table;" /></p>
<p>Mettre un nœud « Occlusion » et le connecter à « ColoredAO » :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_010.png" style="margin: 0px auto; display: table; width: 231px; height: 192px;" /></p>
<p>Dans la RenderPass, créez un AOV. Vous pouvez le nommer comme vous voulez :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_011.png" style="margin: 0px auto; display: table; width: 249px; height: 139px;" /></p>
<p>Dans cet AOV, mettez <em>Accepted expression</em> à <em>[Technical] Primary</em> et <em>Shader Color</em> à <em>ColoredAO</em> :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_012.png" style="margin: 0px auto; display: table; width: 439px; height: 277px;" /></p>
<p>Si vous rendez à ce stade, vous aurez une occlusion tout simple :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_013.png" style="margin: 0px auto; display: table; width: 512px; height: 384px;" /></p>
<p>Mais ce qu’on veut, c’est de la couleur ! :perplex:</p>
<p>Pour ça, il faut renommer le paramètre responsable de la couleur (ici, blanche) pour qu’il utilise la « DiffuseColor ». Sélectionnez votre nœud « Occlusion » et renommez l’attribut « SkyColor » en « DiffuseColor » :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_014.png" style="margin: 0px auto; display: table; width: 269px; height: 457px;" /></p>
<p>Puis cochez « Exposed » :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_015.png" style="margin: 0px auto; display: table; width: 320px; height: 138px;" /></p>
<p>Notez que vous pouvez le faire en cliquant sur l’attribut du nœud, directement dans le graph :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_016.png" style="margin: 0px auto; display: table; width: 132px; height: 207px;" />Et sous vos yeux ébahis :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_017.png" style="margin: 0px auto; display: table; width: 512px; height: 384px;" /></p>
<h3>Que pour les « Surface2 » ?</h3>
<p>Si vous n’overridez que le <em>Surface2</em> sur votre scène de production, vous allez vite vous rendre compte que ça ne fonctionne pas sur beaucoup de surfaces. En effet, il faut faire cet override sur tous les types de shader que vous utilisez dans vos scènes. Avec un peu de chance ça devrait se résumer aux <em>Hair</em>, <em>Curve</em> et <em>Eye</em>.</p>
<p>Il faut adapter le nom de l’attribut pour chaque type de shader. Si dans le cas du « Surface2 », l’attribute « DiffuseColor » fera très certainement l’affaire, dans le cas du shader de <em>Hair</em> ou de <em>Curve</em>, vous utiliserez sûrement la « Color » ou la « RootColor », suivant votre workflow. :pasClasse:</p>
<p>Notez que vous pouvez aussi multiplier les couleurs vous-même si le cœur vous en dit (je n’ai jamais testé) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_019.png" style="margin: 0px auto; display: table; width: 445px; height: 251px;" /></p>
<h3>Le cas du shader de Eye</h3>
<p>Le cas du Eye shader est plus compliqué, car il ne dispose pas d’attribut de shader défini, comme le « DiffuseColor » du « Surface2 ». :bete:</p>
<p>Pour que ça fonctionne, il faut l’évaluer totalement. Pour cela, connectez directement l’Albedo à occlusion, comme ici :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_10_09_ambiant_occlusion_aov_diffuse_color/scene_cube_and_sphere_018.png" style="margin: 0px auto; display: table; width: 405px; height: 251px;" /></p>
<p>Ceci évalue tout le shader des yeux, mais comme ils ne sont pas particulièrement coûteux, on peut se le permettre.</p>
<p>Voilà ! En espérant que ça serve à quelqu’un !</p>
<p>À très bientôt !</p>
<center>:marioCours:</center>Les variations de lookdev par tags dans Guerillaurn:md5:82a7c9a4470a1e19db42755b8954104d2021-10-02T23:32:00+02:002021-10-02T23:32:00+02:00NarannInfographie 3D - Boulotguerillalookdevtagvariation<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_21_var_lookdev_guerilla_tag/variation_lookdev_tag_01_001_tn.png" style="float: left; margin: 0px 1em 1em 0px;" />Dans ce billet, je vous propose une méthode pour gérer les variations de lookdev. Nous allons voir que la notion de « variation » est un concept assez flou tant il peut rapidement impacter tous les départements. En pratique, il peut y avoir plusieurs méthodes, chacune ayant ses spécificités. :sourit:</p>
<p>Cette méthode est une des plus simples à mettre en place. Vous allez voir que la partie dans Guerilla est assez rapide, mais ce sera surtout un prétexte pour réfléchir à comment organiser son travail dans le cadre d’une production. :sauteJoie:</p>
<p>La conclusion restera ouverte…</p> <h3>La variation, un concept flou</h3>
<p>S’il y a bien un truc difficile à exprimer correctement en production, c’est le concept de « variation ». J’ai tendance à éviter d’utiliser ce mot en réunion, car il dispose d’un pouvoir de confusion assez important (au prorata du nombre de personnes dans la discussion). Et si la confusion n’est pas instantanée (chacun comprenant ce qu’il veut) elle le devient dès que le mécanisme entre concrètement dans le pipeline et que les départements commencent à interagir avec lui. C’est à ce moment-là que <strong>tout le monde</strong> comprend que <strong>personne</strong> ne voyait la même chose. :tuComprendRien:</p>
<p>Mais pourquoi ? D’où vient le problème ? :reflexionIntense:</p>
<p>Chacun peut y aller de sa petite théorie, la mienne étant qu’il y a un écart énorme entre l’idée de « variations », qui est un concept très large, et sa réalité en production qui est contrainte par des choses très concrètes, elles-mêmes dépendante de ce qu’on cherche à faire varier. :redface:</p>
<p>Intégrer un concept de variation nécessite de se poser pas-mal de questions. En vrac :</p>
<p>Si j’ai « toto » et sa variation « sale ». Est-ce que « sale » est une surcouche/override (et donc dépends) de « toto » ? Si oui, quelles sont les implications pour « toto » ? Sa hiérarchie, ses attributs, ses tags, etc. Et si ces choses sont modifiées, comment réagit la variation ? N’est-ce pas à la variation de s’ajuster à « toto » et non l’inverse ? Y a-t-il un risque, une fois en production qu’un plan soit fait avec une version de « toto » et de « sale », mais que la nouvelle version de « toto » casse la variation ? Comment gérer ça ? Dois-je bloquer les versions une fois un plan commencé ? OK, mais si la gestion du blocage des versions au plan est trop chronophage pour les gens qui doivent maintenant constamment mettre des choses à jours des choses, car on est en flux tendu, alors qu’avant, ils ne se posaient pas la question, est-ce que le coût de gestion des variations n’est pas en trains de beaucoup trop complexifier le pipeline ?</p>
<p>Finalement, pourquoi s’embêter à gérer « toto » et « sale » de façon indépendante, ne serait-il pas plus simple de combiner « toto » et « sale » pour avoir un asset « toto_sale » et en faire ce que je veux ? Mais si les UVs de base de « toto » changent avec sa texture, ai-je un mécanisme de propagation des modifications ? Mais est-ce que ce n’est pas trop lourd si j’ai une variation « propre » qui apparaît…</p>
<p>Il est impossible de trouver un système qui permet de résoudre tous ces problèmes d’un coup, et ce n’est pas le but. :cayMal:</p>
<p>Ce qu’il faut comprendre, c’est que si vous n’êtes pas au clair sur ce que le système que vous mettez en place pourra faire ou non, vous prenez le risque de mettre des gens en difficulté (en imposant un suivi supplémentaire aux équipes). :triste:</p>
<p>Mon humble conseil est donc de ne jamais utiliser le terme « variation » sans y coller un terme caractérisant « son implémentation technique » (une expression pompeuse pour dire « la méthode utilisée dans le logiciel »), par exemple :</p>
<ul>
<li>Variation par tag.</li>
<li>Variation par set.</li>
<li>Variation par groupe.</li>
<li>Variation par attribut.</li>
<li>Variation par PrimVar.</li>
<li>Variation par tag d’asset.</li>
<li>Variation par namespace.</li>
<li>etc.</li>
</ul>
<p>Vous remarquez qu’il n’y a pas un, mais des mécanismes de variations. En utilisant de tels termes, vous imposerez implicitement à tout le monde de s’accorder sur la méthode et vous simplifierez pas-mal de discussions avec et entre les superviseurs. :titille:</p>
<p>Gardez à l’esprit qu’il n’est pas utile que tout le monde comprenne toutes les subtilités de ces mécanismes, juste que les gens communiquent avec des termes différents pour désigner des choses différentes.</p>
<p>Fini le pâté de texte, maintenant on rentre dans le vif du sujet ! :enerve:</p>
<h3>Présentation de la méthode par tag</h3>
<p>C’est sûrement la plus simple, et celle que vous avez instinctivement en tête quand vous utilisez Guerilla, tant les tags y sont omniprésents. Pourtant, vous allez voir que même dans ce cas il y a plusieurs façons de les utiliser. :joue:</p>
<h3>À l’intérieur du RenderGraph</h3>
<p>Vous pouvez embrancher votre lookdev directement dans le RenderGraph de lookdev, via l’utilisation d’un tag :</p>
<p><img alt="Variation par tag dans un RenderGraph Guerilla" class="media" height="372" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_21_var_lookdev_guerilla_tag/variation_lookdev_tag_01_001.png" style="margin: 0px auto; display: table;" width="665" /></p>
<p>Ici, « var1 » et « var2 » applique un override sur le shader « Surface2 ».</p>
<h3>Via un RenderGraph dédié</h3>
<p>L’idée est d’avoir un RenderGraph s’appliquant <em>après</em> celui du lookdev (attribut <em>order</em> plus élevé) et dédié à l’override d’attributs propres à cette variation :</p>
<p><img alt="Variation par RenderGraph dans Guerilla" class="media" height="321" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_21_var_lookdev_guerilla_tag/variation_lookdev_tag_02_001.png" style="margin: 0px auto; display: table;" width="689" /></p>
<p>Remarquez comme on a juste découpé le RenderGraph précédent.</p>
<p>Ensuite, on configure les deux RenderGraph (de droite) pour qu’ils s’appliquent sur leur tag respectif (et un <em>order</em> suivant celui du RenderGraph de base) :</p>
<p><img alt="" class="media" height="64" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_21_var_lookdev_guerilla_tag/variation_lookdev_tag_02_002.png" style="margin: 0px auto; display: table;" width="234" /></p>
<p>Au passage, en faisant ceci, c’est le RenderGraph qui défini le tag sur lequel il s’applique (« var1 » sur l’image), il est donc inutile d’utiliser un nœud de tag (« var1 » ou « var2 ») à l’intérieur du RenderGraph.</p>
<p>S’arrêter à ce stade pose pas mal de problèmes :</p>
<p>Ici, j’assigne uniquement le tag de variation (« <em>var1</em> ») au RenderGraph, mais ça veut dire que si deux assets ont ce tag, ils passeront tous les deux dans ce RenderGraph. Ça veut dire ce RenderGraph devra gérer la « <em>var1</em> » d’assets très différents (chaise, maison, personnages, montagnes) et que ça fonctionne tout le temps. Même si cela peut être une méthode en soit (si on souhaite utiliser cette méthode de façon générale, sur son pipeline) il y a peu de chance que ce soit vraiment ce que vous souhaitiez faire. En effet, overrider globalement des attributs artistique de lookdev, c’est le risque de se retrouver avec une chaîne de RenderGraphs qui applique trop des choses. :perplex:</p>
<p>Au lieu d’assigner un seul tag « <em>var1</em> » à ce RenderGraph, on peut également lui coller un second tag, lié à l’asset. Dans le cas de l’asset chaise, on aurait : « <em>asset_chaise, var1</em> ». Faire cela résout notre problème d’assignation trop « large », mais implique que le reste de votre pipeline passe par l’assignation par tag. Si c’est ce que vous voulez, tant mieux. Mais sachez que vous n’aurez pas toujours cette option (si votre pipeline utilise «<em> prefix</em> » où « <em>reference</em> », par exemple).</p>
<p>La première méthode (À l’intérieur du RenderGraph) à l’avantage de supporter toutes les méthodes d’assignation de RenderGraph :laClasse: , mais peut devenir limitante si vos variations sont importantes.</p>
<h3>Un nœud SetTags, pour les chambouler tous !</h3>
<p>J’en profite pour dire qu’il existe un nœud <em>SetTags</em> qui permet d’assigner dynamiquement des tags à des objets (via des <em>regex</em> dans des <em>Path</em>, par exemple). La seule contrainte étant que ce nœud doit être dans un RenderGraph <em>précédent</em> le RenderGraph utilisant le tag :</p>
<p>Si un RenderGraph « A » utilise le nœud <em>SetTags pour </em>appliquer le tag « toto » sur des objets, ce tag ne pourra être utilisé que dans RendreGraph « B », dont l’attribut <em>order</em> est plus grand que celui de « A ». On aurait donc une chaîne de RenderGraph ressemblant à ça :</p>
<p><img alt="" class="media" height="345" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_21_var_lookdev_guerilla_tag/variation_lookdev_tag_03_set_tag_01.png" style="margin: 0px auto; display: table;" width="608" /></p>
<p>L’ubiquité des tags dans Guerilla fait qu’un tel nœud offre des possibilités intéressantes, mais gardez à l’esprit que trouver une méthode qui fonctionne, ce n’est que 50 % de la réflexion. Il vous faut identifier ce que vous ne pouvez pas faire et estimer si c’est pénalisant. :neutral:</p>
<p>Ce besoin de passer par deux RenderGraph n’est pas une contrainte négligeable, en particulier quand plusieurs départements relatifs au rendu (lookdev, lighting, compo) veulent aussi pouvoir le faire.</p>
<h3>La limitation de la méthode par tag</h3>
<p>Nous avons vu plusieurs façons d’utiliser les tags pour faire des variations de lookdev. L’objectif n’était pas tant de vous apprendre des choses sous Guerilla que de vous ouvrir aux questions que pose l’organisation d’un workflow de variations. :bete:</p>
<p>Ces mécanismes de gestion des tags ont en commun de s’appuyer sur le <em>SceneGraph</em> (la <em>Node List</em>), c.-à-d. sur les nœuds de la scène. Si c’est cette particularité qui donne de la flexibilité au système (en permettant des modifications directement dans Guerilla), cela devient limitant quand on cherche à travailler « sous » l’objet. Par exemple, pour un système de particule dont on souhaite faire varier les instances et leur lookdev.</p>
<p>Cela pourra faire l’objet d’un billet dédié… :jdicajdirien:</p>
<p>Les petits malins, adeptes de Houdini, pourraient être tentés de sortir autant de fichier Alembic qu’il y a de variation, chaque Alembic ne contenant <em>que</em> les objets d’une seule variation et d’appliquer un tag sur chacune des références. Ça fonctionne (je confirme ! :aupoil: ), mais c’est un cas classique de « contorsion du pipeline » : Ont fait rentrer un nouveau type de problèmes en se servant d’un mécanisme existant et fiable, mais de façon peu élégante. Ce genre de bricolage peut être courant sur des gros pipelines de production (avec parfois, des outils dédiés !).</p>
<p>En espérant vous avoir donné des éléments de réflexion sur le sujet. :youplaBoum:</p>
<p>À bientôt !</p>
<center>:marioCours:</center>Faire un AOV de translucence avec Guerillaurn:md5:593d9f409a9e93b5eef19fae39da14832021-09-19T18:25:00+02:002021-09-20T16:06:01+02:00NarannInfographie 3D - Boulotaovguerillarendutranslucence<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_18_guerilla_translucence/guerilla_translucence_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />La <em>translucence</em> est un effet couramment utilisé en rendu pour les surfaces fines ; feuilles, papiers, etc. Il permet de récupérer l’illumination et les ombres projetées d’un côté pour les projeter de l’autre.</p>
<p>Si vous utilisez Guerilla (ce que vous devriez faire… :siffle: ) vous avez peut-être remarqué que la <em>translucence</em> n’est présente dans aucun AOV de base.</p> <h3>Réponse courte</h3>
<p>Vous avez des plans à sortir, pas l’temps d’niaiser. :grenadelauncher:</p>
<ul>
<li>Créez un AOV.</li>
<li>Mettez <strong>C<TD>.*#</strong> dans <em>Accept Expression</em>.</li>
<li>Rendez.</li>
<li>C’est gentil d’être passé. :baffed:</li>
</ul>
<h3>Réponse illustrée</h3>
<p>Si vous regardez du côté de <a href="http://guerillarender.com/doc/2.3/User%20Guide_Rendering_Light%20Path%20Expression.html">la documentation</a> des <em>Light Path Expression</em> (LPE), vous verrez qu’il est explicitement stipulé que la <em>translucence</em> ne fait pas partie de l’AOV de Diffuse :</p>
<blockquote>
<p>Note that this expresion <b>does not</b> include the translucence, as it is categorized as <strong class="lpe_tag"><TD></strong> for Transmitted Diffuse.</p>
</blockquote>
<p>Ça me fait penser qu’il faudra vraiment que je fasse un vrai billet sur les LPE… :pasClasse:</p>
<p>Si votre boulot c’est de sortir des images, je vous invite à regarder et vous familiariser avec ce tableau qui liste ce que les AOVs de base représentent en termes de LPE.</p>
<p>Je vais prendre la scène d’exemple livré avec Guerilla dans : Help/Samples/Surface.</p>
<p>Créez l’AOV. Nommez-le comme vous voulez (Utilisez « Translucence » si vous n’êtes pas inspiré… :jdicajdirien: ) et mettez le <em>light path expression</em> <strong>C<TD>.*#</strong> dans <em>Accept Expression</em> :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_18_guerilla_translucence/guerilla_translucence_panel.png" style="margin: 0px auto; display: table; width: 404px; height: 255px;" /></p>
<p>Note : Vous devriez pouvoir mettre « Caustics » dans <em>Ignore Expression</em> pour être cohérent avec les AOVs de base, mais je ne suis pas sûr de moi, à vérifier. :reflexionIntense:</p>
<p>Arrêtons-nous un peu sur cette expression. :reflechi:</p>
<p>Les LPE sont un peu au lancer de rayon ce que <a href="https://fr.wikipedia.org/wiki/Expression_r%C3%A9guli%C3%A8re" hreflang="fr" title="page wikipédia sur les expressions régulières">les expressions régulières</a> (<em>regex</em>) peuvent être aux chaînes de caractères. :redface:</p>
<p>Oui, donc il faut savoir ce que sont les <em>regexs</em>, mais si vous faites du lighting, je pars du principe que vous en avez déjà entendu parler. :youplaBoum:</p>
<p>Il y a trois types de propagation de rayons, chacune représenté par une lettre :</p>
<ul>
<li>Spéculaires « <strong>S</strong> ».</li>
<li>Glossy « <strong>G</strong> ».</li>
<li>Diffuse « <strong>D </strong>».</li>
</ul>
<p>Et quatre types de rayons :</p>
<ul>
<li>Réflexion « <strong>R </strong>» : Les rayons rebondissants sur la surface ; miroirs, parquets, métaux brossés, etc.</li>
<li>Transmission « <strong>T </strong>» : Ou « Réfraction », Les rayons entrants dans la surface ; verre, feuilles fines (nos surfaces translucides, justement).</li>
<li>Volume « <strong>V </strong>» : Les rayons entrants dans un volume ; fumée, nuages, etc.</li>
<li>Intra-objet « <strong>O </strong>» : Les rayons se propageant dans l’objet lui-même (SSS).</li>
</ul>
<p>Note : On pourrait caler un cinquième type pour les rayons de caméras « <strong>C</strong> », mais ce dernier ne peut pas se combiner à un type de propagation (c’est un rayon de caméra, point barre… :redface: ).</p>
<p>Et tout ça peut se mélanger :</p>
<ul>
<li><strong>RS</strong> les réflexions brutes (miroirs).</li>
<li><strong>RG</strong> les réflexions douces (parquets, métaux brossés).</li>
<li><strong>RD</strong> les réflexions de diffuse (illumination indirect).</li>
</ul>
<p>Pareil pour <strong>VS</strong>, <strong>VG</strong>, <strong>VD</strong>, etc. :hehe:</p>
<p>L’expression <strong>C<TD>.*# </strong>prend tous les rayons partant de la caméra (<strong>C</strong>), de type transmission diffuse (<strong>TD</strong>), quel que soit le type de rayon « après » <strong><TD></strong>.</p>
<p>Le « <strong>.*</strong> » est un caractère (le <em>point</em>) et un quantificateur (l’<em>astérisque</em>) :</p>
<ul>
<li>Le <em>point</em> est un caractère spécial voulant dire « n’importe quel rayon ».</li>
<li>L’<em>astérisque</em> est un quantificateur s’appliquant au caractère qu’il précède et veut dire « aucune fois, 1 fois ou plus ».</li>
</ul>
<p>Le dernier caractère « <strong>#</strong> » bloque l’expression qui ne peut aller plus loin (ça ne veut pas dire que le moteur s’arrête, juste que c’est cette partie du rayon qui sera stocké en AOV).</p>
<p>En gros, l’expression <strong>C<TD>.*# </strong>prends n’importe quel rayon venant de la <em>translucence</em>.</p>
<p>On a donc :</p>
<p>La Translucence direct : <strong>C<TD>#</strong><br />
La Translucence indirect : <strong>C<TD>.+#</strong><br />
Les deux (celle qu’on utilise ici) : <strong>C<TD>.*#</strong></p>
<p>Si vous êtes habitué aux regex, vous devriez comprendre ces expressions, mais ça ne nous dit pas vraiment ce qu’il y a dedans…</p>
<p>La « Translucence direct », c’est l’illumination <em>direct</em> sur la surface opposée à ce qu’on voit. C.-à-d. la lumière (direct) et les ombres projetées sur la face opposée à notre regard.</p>
<p>La « Translucence indirect » c’est l’illumination <em>indirect</em> (la diffuse) sur la surface opposée à ce qu’on voit. En gros, l’équivalent à ce que sortirait l’AOV de <em>Diffuse Indirect</em> si on regardait la surface de l’autre côté.</p>
<p>Mettez une couleur de translucence à l’une des sphères (un vert pur, pour montrer que vous avez du goût :vomit: ) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_18_guerilla_translucence/guerilla_translucence_attribute.png" style="margin: 0px auto; display: table; width: 400px; height: 118px;" /></p>
<p>Puis rendez.</p>
<p>La Beauty :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_18_guerilla_translucence/guerilla_translucence_result_diffuse.png" style="margin: 0px auto; display: table; width: 241px; height: 160px;" />L’AOV <em>Translucence</em> :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_09_18_guerilla_translucence/guerilla_translucence_result_aov.png" style="margin: 0px auto; display: table; width: 279px; height: 177px;" /></p>
<p>Et voilà ! En espérant que ça serve à quelqu’un !</p>
<p>À très bientôt !</p>
<center>:marioCours:</center>Les foules de personnages en volumétriques de Soul chez Pixarurn:md5:1f9e48459585d01d2f8168f6694dcb152021-06-13T22:56:00+02:002022-04-21T13:38:25+02:00NarannInfographie 3D - Boulotpixarrendermanrenduvolumetrique<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/soul_world_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />N’ayant plus beaucoup de temps pour lire des publications, je suis passé à côté d’un papier de Pixar sorti en juillet 2020 : <a href="https://graphics.pixar.com/library/SoulRasterizingVolumes/paper.pdf" hreflang="en">Rasterizing Volumes and Surfaces for Crowds on Soul</a>. De la rastérisation dans un papier de 2020 ? Intéressant… :reflechi:</p>
<p>Quand j’ai ouvert le papier, je m’attendais à trouver des équations mathématiques sur une nouvelle méthode ou que sais-je, mais c’est en fait la description détaillée d’un problème précis sur un plan. Tout ce que j’aime ! :baffed:</p>
<p>Vous allez voir que la situation que Pixar a rencontré sur ce plan a pas mal de similitude avec les problèmes qu’on peut rencontrer, dès qu’un plan un peu complexe se pointe. La différence c’est que Pixar n’est pas aussi limité que nous dans ses méthodes. Et c’est là où ce genre de document prend de la valeur : Quand on se retrouve face à des choses difficiles à sortir, on peut parfois se laisser aller à de la pensée magique comme : « À Pixar, ils auraient tout envoyé en farm ! » (mais bien sûr…). Ce papier nous prouve que non, et surtout, qu’ils n’ont pas peur de revenir sur des vieux paradigmes pour sortir leurs plans quand la contrainte (ici technique) l’impose. :redface:</p>
<p>Ce billet sera donc l’occasion de râler comme un vieux con, puis on va essayer de comprendre comment Pixar a géré ce plan. :banaeyouhou:</p> <h3>Perception de la technique</h3>
<p>Ceux qui me connaissent savent que je ne suis pas super dogmatique vis-à-vis de la technologie et que chaque paradigme contenant le mot « moderne » oublies souvent de lister les inconvénients et marges de manœuvre perdues du paradigme qu’il cherche à remplacer. De plus, le fait que ces paradigmes soient poussés par des intérêts économiques rend difficile les discussions et la recherche de ce qui est perdu (c’est un peu comme si, dans une discussion sur la colorimétrie, une personne parle de « <em>smart color</em> », parce que c’est ce qui est écrit sur la boite de son nouveau moniteur…) :</p>
<ul>
<li>« Le temps réel, le GPU <em>path-tracing</em>, le GPU machin <em>computing</em>, c’est l’avenir. » − Nvidia</li>
<li>« Le <em>ray-tracing</em>, c’est l’avenir. » − SolidAngle</li>
<li>« Les UDIMS, c’est l’avenir. » − Mari</li>
<li>« Le <em>deep</em> compo, c’est l’avenir. » − Weta</li>
<li>« Les UVs, c’est le passé. » − Ptex</li>
<li>« Les NURBS, c’est le futur. » − Une publication Siggraph de 98</li>
<li>« La soupe aux épinards, c’est l’avenir » − Tes nobles parents qui essaient de te refourguer des légumes à toi, le couillon·ne ingrat·e qui leur sert de môme… :gne2:</li>
</ul>
<p>Vous voyez l’idée…</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/brick_map.jpg"><img alt="" class="media" height="296" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/.brick_map_m.jpg" style="margin: 0px auto; display: table;" width="680" /></a></p>
<p>Bien souvent ces propositions ne sortent pas de nulle-part. Elles sont cohérentes et résolvent beaucoup des problèmes des paradigmes précédents. C’est la raison pour laquelle il faut les prendre au sérieux. :nannan:</p>
<p>Mais comme n’importe quel paradigme, ils viennent avec leur lot de situations difficiles à gérer. Comme personne ne vous en parlera, il faut souvent aller les chercher sous le capot, dans la documentation des logiciels, dans des interviews, des VFX breakdown, par la pratique, dans les bars (quand vous avez la chance de tomber sur une personne qui s’en est servi), parfois en regardant les images.</p>
<p>Vous pourriez découvrir que :</p>
<ul>
<li>Pour être efficace, le GPU <em>path-tracing</em> a son lot de contraintes auxquelles il faut faire attention (<em>instancing</em> et taille de textures).</li>
<li>Le deep compo est tellement lourd (en poids et en traitement) que même des studios de taille importante ne peuvent pas généraliser son utilisation.</li>
<li>Les UVs sont toujours là et le filtrage qu’implique le Ptex n’est pas efficace sur des accès incohérents.</li>
<li>Les NURBS, c’est d’la merde. :grenadelauncher:</li>
<li>La soupe aux épinards, c’est loin d’être dégueulasse, surtout si on met un peu d’ail et du beurre. :petrus:</li>
</ul>
<p>En gros, à l’exception de quelques-uns qui arrivent à s’imposer, les autres ne deviennent que des options dans la boite à outil dont on dispose pour faire nos productions, et certains disparaissent tout simplement. C’est très bien d’avoir plus d’options pour résoudre un problème, mais ce n’est pas la même chose que sauter aveuglément dedans. Si on est incapable de lister clairement ce qu’on gagne et ce qu’on perd quand on amène une solution pour en remplacer une autre, c’est risqué. Et tout le discourt dogmatique qui peut accompagner ses solutions vise à faire sauter la question du risque, à ne pas répondre à la question. Quand tu pars sur 1 an et demi de prod comme ça, ça peut mal se passer…</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/rendu_gpu.jpg"><img alt="" class="media" height="680" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/.rendu_gpu_m.jpg" style="margin: 0px auto; display: table;" width="551" /></a></p>
<p>Et la fameuse, répétée à outrance :</p>
<blockquote>
<p>« Le temps humain, c’est plus cher que le temps machine. »</p>
</blockquote>
<p>Waow… Je n’y avais <em>vraiment</em> pas pensé, merci d’éclairer mon chemin, je m’en vais méditer là-dessus et penser à mon avenir. :bete:</p>
<p><img alt="" class="media" height="538" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/pc_einstein.jpg" style="margin: 0px auto; display: table;" width="437" /></p>
<p>Après ce énième vomit de tonton grincheux et condescendant, vous pouvez rebrancher votre cerveau, on peut y aller ! :papi:</p>
<h3>Le plan</h3>
<p>Le papier tourne autour d’un plan précis du film, le voici :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/soul_worl_le_plan.jpg"><img alt="" class="media" height="284" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/.soul_worl_le_plan_m.jpg" style="margin: 0px auto; display: table;" width="680" /></a></p>
<p>Regardez bien les personnages de la foule. On pourrait faire quelques remarques artistiques, mais ce n’est pas le sujet. Mettez cette image de côté, et gardez-la sous les yeux durant la lecture de ce billet.</p>
<h3>Avant-propos</h3>
<p>J’aimerais préciser que le terme « <em>pose</em> » utilisé dans le papier est assez difficile à traduire. Littéralement, il veut dire « prendre la pose ». Et dans le cas du rendu, il s’approche d’une idée de pré-calcul, un « <em>bake</em> d’information » qui semble être un process important de Pixar. J’ai pensé à utiliser le terme <em>bake</em>, mais il aurait prêté à confusion. Dans le cas du papier, il s’agit de <em>baker</em> les choses au moment du rendu (<em>pose at render-time</em>) de sorte que le moteur puisse s’éviter de lourds calculs au moment de tracer les rayons ; c’est très courant en temps-réel et pas du tout <em>path-tracing</em>, dans l’idée. Je vais conserver l’utilisation du terme <em>pose</em>, en italique, mais je voudrais que vous gardiez cette idée de « <em>bake</em> au moment du rendu » en tête. :sourit:</p>
<p>Autre point que je dois expliquer. Le terme « <em>hero</em> » fait référence à tout ce qui à trait aux personnages principaux. « <em>Hero shader</em> » et « <em>Hero volume</em> » sont respectivement des shader et des volumétriques de personnage principaux, souvent lourd et prévu pour les gros plans. Sur les grosses productions, il n’est pas rare d’avoir des morceaux de pipeline dédiée à des personnages dans le but d’augmenter sa qualité finale, mais qui ne sont pas appliqués au reste des personnages pour des raisons de coût.</p>
<p>Si vous n’aimez ni le mot « <em>rasterize</em> », ni les anglicismes, vous allez vomir en lisant ce billet, et j’en suis le premier désolé. J’ai vraiment fait ce que j’ai pu… :vomit:</p>
<p>Autre chose : J’alternerai entre ma traduction des paragraphes du papier (qui sera préfixée d’un gros « <strong>Traduction :</strong> ») et mes remarques.</p>
<p>Pour finir, gardez à l’esprit que je ne suis pas un expert de RenderMan, en particulier sur certaines notions pointues, comme les <em>Implicit Field Plugin</em>, les modifications de scène « <em>at render-time</em> », et le mécanisme de moteur embarqué (dont je soupçonne qu’aucune intégration publique n’y fait référence). Bref, je fais au mieux pour expliquer ce que je comprends des notions utilisées, mais il y aura pas mal de spéculations, pas taper… :seSentCon:</p>
<h3>Traduction</h3>
<p>Et on commence avec la partie <em>abstract</em> :</p>
<p><strong>Traduction :</strong> Pour sortir les plans de <em>Soul</em>, contenant une foule de centaines de personnages composés de plusieurs volumes, on ne pouvait pas s’appuyer sur le pipeline de cache de <em>pose</em> utilisé sur les personnages principaux [Coleman et al.2020], trop lourd en IO. Nos équipes rendues et systèmes ont évalué le stockage total nécessaire au « monde des âmes » à plus de 100 To, pour une moyenne de deux personnages principaux par plans. Pour pouvoir gérer les larges foules de personnages ayant le même aspect volumétrique que les personnages principaux, tout en évitant d’atteindre cette limite d’IO, deux nouvelles techniques « <em>at render-time</em> » furent développées. La première s’appuie sur un <em>rasterizer</em> de volumétrique existant pour « <em>poser</em> » les volumes au moment du rendu en utilisant leur déformeur lattice. La seconde technique permettait aux <em>primvars</em> rasterizées de la surface d’être utilisé par le shader de volumétrique.</p>
<p>Voilà pour la partie <em>abstract</em>. :bravo:</p>
<p>En gros, il y a des limites/quota d’IO à respecter, et ce plan partait pour les <s>dépasser</s> éclater, donc il fallait trouver une solution. C’est le genre de problèmes qui doit vous parler. Il est probable que vous n’ayez pas de quotas imposés (« <em>segfault is the limit</em> » :smileFou: ), mais si vous avez un minimum de conscience professionnelle, anticiper des plans complexes est un classique des débuts de productions.</p>
<p>La première partie concerne l’économie de la bande passante des fichiers de volumétrique :</p>
<p><strong>Traduction :</strong> En plus des limitations d’IO qu’aurait généré l’utilisation du pipeline de cache de <em>pose</em>, le temps de génération des « <em>hero volumes</em> » dans Houdini appliqué à la foule aurait eut un coût important en farm. Avec notre librairie maison de rasterization de volume REVES (une implémentation de l’algorithme REYES pour les volumétriques) [Wrenninge 2016], nous avons fait un pipeline autour de la gestion des volumes en <em>rest pose</em> et des lattices déformées. Cela nous permit de générer des volumes de façon implicite au moment du rendu pour ne pas atteindre la limite de stockage réseaux.</p>
<p>En gros, seuls les fichiers de <em>rest pose</em> des volumétriques ainsi que les lattices de déformation étaient envoyés au moteur, et c’est ce dernier qui s’occupait de générer le volume final. :petrus:</p>
<p>Un peu comme si, dans le cas de la géométrie, on envoyait le personnage en T-pose avec ses joints et les informations d’influence et que le moteur s’occupait de faire le <em>skinning</em> au moment du rendu (au passage, c’était la technique utilisée par <em>Massive</em>, et il me semble que c’est encore la méthode utilisée par les différents moteurs de foule, mais je ne veux pas m’avancer :jdicajdirien: ).</p>
<p>Notez aussi que REYES (ou du moins, son principe) semble toujours de la partie, ce qui ne me surprend qu’à moitié. :reflechi:</p>
<p>Ce passage sous-entend aussi que la génération des personnages principaux au rendu est faite dans Houdini.</p>
<p><strong>Traduction :</strong> Au moment du build de personnage des foules, un lattice est généré pour chaque volume d’un personnage ; body, hair, face, details, et accessoires (La résolution de chaque lattice était de 15 × 15 × 15, permettant d’équilibrer la préservation des détails générés lors de la rastérization, et les performances). Le lattice est déformé par le pipeline de foules <em>UsdSkel</em> [Yen et al.2018], qui génère les <em>motion samples</em> des points de la lattice et les envois à un plugin <a href="https://rmanwiki.pixar.com/display/REN23/Implicit+Surfaces#ImplicitSurfaces-ImplicitFieldPlugins" hreflang="en" title="Implicit Field Plugins">Implicit Field</a> de RenderMan. Muni de la lattice déformée et du volume en <em>rest pose</em> stoqué sur le disque, le plugin voxelise chaque <em>field</em> au moment du rendu, dont le <em>field</em> de vélocité calculée depuis les vecteurs de vélocité des points. Le <em>field</em> de vélocité est ensuite samplé par le moteur pour générer le motion blur des volumes. À cela s’ajoute la génération de <em>fields</em> min-max afin d’optimiser le tracé de rayon après la rasterization, via les algorithmes de volume de RenderMan [Fong et al. 2017]. Rasterizer la <em>rest pose</em> d’entrée à sa densité de voxel maximum avait un haut coût de calcul pré-pixel. Cela ralentissait le temps d’itération des lighters, nous obligeant à utiliser la technologie de LOD stochastique pour ajuster la résolution du <em>field</em> utilisée [Cook et al. 2007].</p>
<p>Il y a beaucoup de choses là-dedans. Je pense que vous avez compris l’idée. Ce qui m’a vraiment surpris, c’est la présence d’une référence à un papier de 2007 de <a href="https://graphics.pixar.com/library/StochasticSimplification/" hreflang="en">simplification stochastique</a>. J’ai le souvenir que ce type de mécanisme n’était pas très pratique en <em>path-tracing</em>, car il cassait les instances (chaque objet devient unique), et était utilisé sur <em>Cars</em>, à l’époque ou RenderMan était encore full REYES. On peut donc spéculer… :youplaBoum:</p>
<p>Soit le REYES est encore énormément ancré dans le workflow de Pixar, ce qui encore une fois, ne serait pas étonnant : Quand un pipeline est tourné vers du <em>bake</em> à outrance, il est difficile d’obtenir plus efficace. Comprenez que <em>baker</em> des PTC à la main est un problème de paysan du rendu et qu’un pipeline optimisé fait ça en sorti d’anim sans intervention humaine.</p>
<p>La dernière option est que cette simplification stochastique (et tous les mécanismes « <em>at render-time</em> ») soient fait lors d’une première passe de rendu. À titre personnel, j’ai déjà eux à implémenter un mécanisme pour déterminer la densité optimale de poils. Je passais par Maya, j’importais l’Alembic du perso et de caméra. Ensuite, pour chaque image (et <em>motion step</em>) je déterminais le vertex le plus proche et dans le frustrum, y plaçait une sphère de taille 1 et calculait le ratio de sa projection (sa taille) sur l’axe Y de la caméra. Je suppose qu’à Pixar, ce genre de chose est fait via des plugins du moteur, au moment du rendu. Dans le cas de la simplification stochastique, ça permettrait de simplifier une fois, pour lancer le rendu ensuite (mais ça casse quand même les instances, donc je ne sais pas… :pasClasse: ).</p>
<p>Vient ensuite la partie <em>geometry rasterizer</em>… Mais ! De la géométrie ? N’est-on pas supposé calculer des volumétriques ? Nous aurait-on menti ? :reflechi:</p>
<p>C’est le moment de vous resservir du thé, car on entame la partie qui saigne, celle qui fait qu’à mes yeux, les ingés de Pixar ont bien plus de jugeote que ceux qui affirment sans sourciller qu’ils envoient tout en farm.</p>
<p><img alt="" class="media" height="183" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/.tout_en_scene_tout_en_farm_m.jpg" style="margin: 0px auto; display: table;" width="680" /></p>
<p>Détendez-vous, prenez une inspiration, ça va chier ! :enerve:</p>
<p><strong>Traduction :</strong> Bien que les personnages de la foule étaient principalement composés de volumes, les yeux étaient en meshs. L’opacité des faces volumétriques n’étant pas géré de la même manière que les surfaces, on avait l’impression que les yeux « flottaient ». L’occlusion des yeux pouvait facilement se faire via un Z-buffer, mais comme nos personnages n’étaient pas opaques et se chevauchaient en <em>screen space</em>, il fallait générer un buffer unique par personnage. Cette exigence nous empêchait d’utiliser RenderMan pour générer ces signaux, ce dernier n’ayant qu’un unique contexte de rendu par process, et non des centaines. Nous avons développé un rasterizer CPU (en non GPU, car notre farm est principalement en CPU) afin de générer des textures en mémoire (<em>In-Memory Textures</em>). Muni de ce moteur de rendu embarqué (sic), la Z-depth est rasterizé pour ensuite pouvoir tester la présence de la surface des yeux.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/perso_yeux_soul.jpg" style="margin: 0px auto; display: table; width: 168px; height: 91px;" /></p>
<p>Je ne suis pas un expert RenderMan, mais la question de l’occlusion d’une surface à l’intérieur d’un voxel volumétrique est en effet un problème assez pointu si on veut conserver de bonnes performances. Je soupçonne que l’utilisation d’un <em>Implicit Field Plugin</em> empêche le moteur de pouvoir tracer l’occlusion correctement (ou bien ils sont en REYES pûr, ce qui, encore une fois, reste une option). Dans tous les cas, les yeux semblent ne pas s’intégrer correctement dans les volumétriques et ils ont dû coder un <em>rasterizer</em> CPU embarqué à l’intérieur de RenderMan (un moteur dans le moteur) pour générer, par personnage (et <em>at render-time</em>, Monsieur !), une image permettant de savoir s’il faut afficher l’œil ou non.</p>
<p>Le fait qu’ils aient utilisé un <em>rasterizer</em> donne une information intéressante, notamment le fait qu’ils ne rendent pas avec un <em>lens shader</em>. :reflechi:</p>
<p>Vous allez me dire : « Putain, mais Dorian ! Personne rends avec un <em>lens shader</em>, et puis c’est quoi le rapport avec le papier ?!!! :injures: ». Et bien, comme il m’est arrivé de me retrouver dans une réunion très sérieuse (avec prodex et tout le bordel) sur le fait qu’il fallait <em>aaaabsolument</em> rendre avec un <em>lens shader</em>, je pose ça là… Si un jour ça vous arrive, l’argument Pixar avec une petite démonstration via ce papier peut vous sauver les miches, et celles de votre équipe compo…</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/pixar_american_chopper.jpeg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_soul_word_pixar_paper/.pixar_american_chopper_m.jpg" style="margin: 0px auto; display: table; width: 241px; height: 680px;" /></a></p>
<p>Bref, ils ne doivent pas être bien nombreux les ingés qui ont on jour codé un moteur dans un moteur pour générer puis injecter une texture en attribut géométrique pour ensuite la sampler dans le shader… :smileFou:</p>
<p><strong>Traduction :</strong> De plus, certains <em>fields</em> du shading des volumes de <em>rest pose</em> ne pouvaient pas être facilement rasterizé par REVES. Les <em>fields</em> indépendants de la caméra (c.à.d la densité ou les <em>fields</em> de mask) ne posaient pas de problèmes, mais certains signaux (comme les <em>gradient fields</em>) étaient alignés sur le frustrum. À cela s’ajoutait des inquiétudes montantes concernant le temps d’attente du premier pixel (<em>time-to-first-pixel</em>) généré par le coût de rastérization REVES de ces <em>fields</em>. Nous avons utilisé le <em>geometry rasterizer</em> pour rendre, en texture, les propriétés de surface de chaque mesh représentant un volume. Ces textures pouvaient ensuite être connectées aux shaders de volume, comme si elles provenaient des <em>fields</em>. Cela a permis à nos <em>shading artists</em> de construire un <em>shading network</em> qui pouvait facilement être utilisé sur les personnages <em>hero</em> et foule, quelle que soit la provenance des signaux. De plus, un shader de surface séparé émulant le look de la volumétrique tirait parti d’une texture d’alpha généré par le rasterizer.</p>
<p>Ici, on aborde les effets sur la production. Leur système de <em>rest pose</em> + lattices permettait de rester sous la barre des quotas d’IO, mais intégrait des <em>fields</em> dont le contenu dépendait de la caméra et que REVES avait du mal à les évaluer (trop lent, je suppose). Ceci allongeait la durée de démarrage du rendu, ce qui gênait les équipes qui devait attendre avant de voir le résultat, chaque fois qu’ils modifiaient un potard. Pour résoudre le problème, ils ont dû <em>baker</em> les informations de volumétrique en se servant d’un mesh qu’ils ont rasterizé. De ce que j’en comprends, les volumétriques avaient une représentation en mesh (un peu comme un « <em>volume to mesh</em> ») et c’est ce mesh qui a été utilisé (c.à.d, rendu via le <em>geometry rasterizer</em>) pour générer un <em>bake</em> d’information (apparemment, les dégradés de couleur) qui ont été <em>baké</em> dans des textures (screen-space ? brickmap ? Ce papier ne le précise pas). Le shader, au lieu d’évaluer le fields, allait directement sampler la texture en question. :neutral:</p>
<p><strong>Traduction :</strong> Le fait de rasterizer à la volée évitait les problèmes de synchronisation du pipeline de foule. De plus, ces signaux étaient nécessaires au light shaping, d’où le besoin de rasterizer <em>avant</em> le rendu, plutôt que de laisser cette responsabilité au compo. Ce <em>geometry rasterizer</em> dispose d’un ensemble limité de <em>features</em> et n’a pas pour but de remplacer les possibilités d’un moteur REYES ; C’est un <em>rasterizer</em> à un sample, simpliste et pas cher, conçu pour fournir des entrées à d’autres partis de la scène.</p>
<p>Je crois que tout est dit… :baffed:</p>
<p>Je comptais sauter la section suivante, « In-Memory Textures », car elle était très spécifique, mais il n’aurait manqué que ça pour que tout le papier soit traduit, donc on va essayer de faire ça bien :</p>
<p><strong>Traduction :</strong> Le système (<em>In-Texture Memory</em>) devait gérer plusieurs textures en 512 × 512 par personnage, par frame (Il s’agit d’une résolution moyenne pour des personnages à mi-distance. Nous avons utilisé la taille du personnage en <em>screen-space</em> pour déterminer procéduralement la résolution nécessaire, sur un écart partant de 2048 × 2048 à 256 × 256). Afin d’éviter le coût d’écriture et de lecture de plusieurs centaines de textures par plan, les textures rasterizées étaient gardées en mémoire sous la forme d’attributs de géométries, qui pouvaient être passés à l’interface de texture <em>Rtx</em> de RenderMan. Nous avons écrit un plugin <em>Rtx</em> pour répondre aux requêtes de <em>tile fill</em> des buffers. Il implémentait aussi le support de la génération de mipmaps pour les accès aux textures floutées. Le sampling de texture sur les bords du volume nécessitait une dilatation du signal rasterizé. Nous compositions des mips de plus haute résolution au-dessus des mips de plus basses résolutions pour les faire « déborder ». Ce mécanisme avait l’avantage d’être rapide et temporellement plus cohérent que l’algorithme de <em>push-pull</em> d’OpenImageIO.</p>
<p>Comme vous pouvez le voir, c’est très spécifique, si vous êtes arrivé jusqu’ici, je ne vois pas trop quoi développer. :hehe:</p>
<p>Et arrive la conclusion du papier ! :banaeyouhou:</p>
<h3>Conclusion</h3>
<p><strong>Traduction :</strong> À travers l’intégration des techniques de rasterization à l’initiation de scène de notre moteur de rendu <em>path-tracer</em>, nous avons été en mesure de gérer une grande quantité de personnages de foule en volumétrique dans <em>Soul</em>. REVES nous a permis de <em>poser</em> des foules de volumes <em>at render-time</em>, tandis que le <em>geometry rasterizer</em> générait les entrées de shader permettant de matcher le look d’un <em>hero</em>, à une fraction de son coût.</p>
<h3>Conclusion 2, le retour du fils de la première conclusion que tu croyais qu’il y en avait qu’une seule alors qu’en fait non</h3>
<p>On y est arrivé ! Si vous êtes encore à lire ces lignes, félicitation ! :bravo:</p>
<p>J’espère avoir réussi à rendre ça le plus clair possible et vous avoir motivé à jeter un œil par vous-même dans <a href="https://graphics.pixar.com/library/" hreflang="en" title="Publications Pixar">leurs publications</a>, certaines devraient forcément vous intéresser ! :joue:</p>
<p>Un gros merci à Pixar de sortir ce genre de papier qui a le mérite d’attaquer un problème précis et de donner pas mal de détails en deux pages. Ça peut aussi rassurer pas mal de personnes de voir qu’il faut parfois contorsionner nos outils pour en sortir ce qu’on veut. :sourit:</p>
<p>Bon, je sais pas pour vous, mais quand je lis ça, je me dis qu’il est loin le <em>path-tracing</em> qui résout tous les problèmes de rendu et la faim dans le monde… :nannan:</p>
<p>On voit à quel point la réalité de Pixar est très loin de ce qu’on pourrait appeler « l’idéal RenderMan », tout de <em>XPU path-tracing</em> vêtu, qui ne fait pourtant que suivre et tenter de dépasser la tendance. D’un côté on a un RenderMan qui suit les tendances de ses concurrents (car c’est sur ce marché qu’il évolue) et de l’autre, un RenderMan qui sort des longs. :reflexionIntense:</p>
<p>C’est à la lecture de ce genre de papiers que je me dis qu’il serait vraiment intéressant que les moteurs de rendu commerciaux proposent un paradigme pré-rendu (ou préanalyse). En pratique, on le fait déjà avec les moyens du bord :</p>
<ul>
<li>Évaluation de ce qui est visible où non afin de lui appliquer des paramètres précis (subdivision, désactivation du spéculaire, etc).</li>
<li>Évaluation de densité de poils.</li>
<li>etc.</li>
</ul>
<p>Ces mécanismes sont souvent lourds à mettre en place à la main. :reflexionIntense:</p>
<p>Un moteur capable de lancer un sample (ou plus) par pixel et me sortir un JSON (dixit le mec qui écrit la moitié d’un billet sur l’inefficacité des technologies modernes) de ce qui est le plus, et le moins samplé (objet ET composants de shaders), avec un <em>ray differential</em> moyen (ou médian), serait déjà énorme en termes d’optimisation possible. :hehe:</p>
<p>On arrive dans un monde sous contrainte. La question de la sur-optimisation (et son automatisation) va revenir à grands pas. On a une industrie de l’animation, on est capable de profiter de ces paradigmes, pour peu qu’ils soient exposés par les moteurs. Un tel papier ne fait que prouver qu’en pratique, le contrôle reste la mère des belles images. (Putain, c’est beau ! :neutral: )</p>
<p>Je reste persuadé que l’avenir du <em>path-tracing</em> passe par l’analyse de scène par le moteur lui-même. L’analyse de scène est le pilier du temps-réel : Plus il dispose d’informations, plus il peut optimiser et <em>baker</em> de choses. Si le temps réel est l’avenir, les moteurs <em>path-tracing</em> devraient s’en inspirer, au moins pour résoudre les problèmes les plus récurrents et évidents. :redface:</p>
<p>Oui, bon, à essayer de faire une conclusion un peu sérieuse je commence à dire vraiment n’importe quoi, je m’arrête là… :slow_clap:</p>
<p>À bientôt !</p>
<center>:marioCours:</center>Guerilla, Hair, et Back specularurn:md5:e76a2ab68fe302c89c0cd8f23e5b93582021-06-06T14:34:00+02:002021-09-21T14:41:19+02:00NarannInfographie 3D - Boulotaovguerillapoilrenduspéculaire<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/in_diffuse_wip_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Aujourd’hui un billet inutile et vraiment spécifique pour vous parler d’un problème que vous ne rencontrerez sûrement jamais. :laClasse: En fait il est probable que vous ne rencontriez ce problème que sur de la série, où la contrainte de la puissance de calcul est importante. :pafOrdi:</p>
<p>Quand on rend des poils avec le shader <em>Hair</em> de Guerilla (et je suppose avec n’importe quel autre moteur de rendu) il peut être intéressant de supprimer les-dit poils du trace set <em>« Diffuse</em> » pour économiser leur coût de calcul dans l’indirect (BIM ! Direct dans le bain sans politesse ni respect pour son lecteur :grenadelauncher: ), mais dans ce cas précis, on risque d’obtenir un effet bizarre, presque esthétique, et je vous propose de voir ce dont il s’agit. :hehe:</p> <h3>Le <em>back</em> dans le shader <em>Hair</em></h3>
<p>Dans la vraie vie vivante qu’on partage avec ces foutus moustiques si propice à ruiner cette si chouette période de l’année :fuck: , les poils ont des propriétés physiques difficiles à modeler. Les shaders de poils ne sont pas les trucs les plus simples à comprendre et à coder :perplex: . Un petit coup d’œil sur le papier <a href="https://www.graphics.stanford.edu/papers/hair/hair-sg03final.pdf" hreflang="en">Light Scattering from Human Hair Fibers</a> devrait vous convaincre. <a href="https://www.cemyuksel.com/research/dualscattering/dualscattering.pdf" hreflang="en">Et</a> <a href="https://www.researchgate.net/profile/Eugene-Deon/publication/220506677_An_Energy-Conserving_Hair_Reflectance_Model/links/59dc2874a6fdcc1ec89fb0c0/An-Energy-Conserving-Hair-Reflectance-Model.pdf" hreflang="en">si</a> <a href="https://graphics.pixar.com/library/DataDrivenHairScattering/paper.pdf" hreflang="en">vous</a> <a href="https://benedikt-bitterli.me/pchfm/" hreflang="en">êtes</a> <a href="https://www.pbrt.org/hair.pdf" hreflang="en">motivé</a>…</p>
<p>Au passage, je pense que le rendu temps-réel est complètement à la ramasse sur ce sujet. <em>« Le temps-réel, c’est le futur »</em>, sauf si on a encore des cheveux, sinon : :vomit:</p>
<p>Si vous utilisez le shader <em>Hair</em> de Guerilla, dont la documentation se trouve <a href="https://guerillarender.com/doc/2.3/Library_Materials_Hair.html" hreflang="en" title="Page de documentation du shader Hair de Guerilla">ici</a> (avec des potards à bouger comme dans vos logiciels préférés ! :bravo: ), vous savez qu’il se compose de trois composants de spéculaires ; <em>primary</em>, <em>secondary</em> et <em>back</em> (nous n’aborderons pas les paramètres <em>Multiple Scatter</em> et <em>Diffuse</em> :nannan: ).</p>
<p>Quand on regarde les images de la documentation officielle, on pourrait être tenté de confondre le <em>back</em> avec un effet de <em>rim</em> de light (une light, souvent forte, venant quasiment de derrière un modèle) :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/marschner.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/guerilla_hair_back.png" style="margin: 0px auto; display: table;" width=200 height=200 /></a></p>
<p>Nous allons voir que le <em>back</em> n’a rien à voir avec une <em>rim</em>.</p>
<p>Cette image dérobée sur <a href="https://guerillarender.com/doc/2.3/Library_Materials_Curves.html" hreflang="en" title="Page de documentation du shader Guerilla Curve">la page de documentation</a> du shader <em>Curve</em> permet de simplifier le comportement qu’on cherche à reproduire avec ces trois composants :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/marschner.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/.marschner_m.png" style="margin: 0px auto; display: table;" width=680 height=309 /></a></p>
<p>Nous avons donc trois composants :</p>
<ul>
<li>La primaire correspond aux réflexions à la surface du poil (en rouge sur le schéma).</li>
<li>La secondaire correspond aux réflexions internes à l’intérieur du poil (en violet).</li>
<li>Et une dernière, le <em>back</em> correspond à la transmission à travers le poil (en vert).</li>
</ul>
<p>Notez à que le <em>back</em> est dans l’alignement de la <em>light</em>.</p>
<p>C’est ici qu’on comprend quelle différence il y a avec une <em>rim</em> light et que ça devient visuellement intéressant. Là où on s’imagine assez bien le comportement des deux premiers spéculaires, qui réagissent à peu près comme sur un shader de base, en miroir (c.à.d, réfléchissent la lumière en face d’eux), l’intensité du spéculaire <em>back</em> dépend de la lumière qui se trouve derrière. :reflechi:</p>
<p>En gros : Plus la lumière <em>derrière</em> le poil (donc devant vous) est importante, plus le spéculaire de <em>back</em> apparaît. C’est contre-intuitif, et c’est pourquoi je vous demande de bien observer ce schéma et de l’avoir en tête pour la suite de ce billet.</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/maurane_back.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/.maurane_back_m.jpg" style="margin: 0px auto; display: table;" width=680 height=255 /></a></p>
<p>Vous l’aurez compris, c’est ce que produit le <em>back</em> qui va nous intéresser.</p>
<h3>Rendu</h3>
<p>Tout ça c’est bien joli, mais ça donne quoi en image ? :petrus:</p>
<p>Voici six AOV d’un rendu :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/out_diffuse_wip.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/out_diffuse_wip.png" style="margin: 0px auto; display: table;" width=600 height=400 /></a></p>
<p>La scène est composée :</p>
<ul>
<li>D’un plan avec <a href="https://drive.google.com/drive/folders/1K_G_hbFyohR8-xCCAlYx8xhsd_a7Ir7G" hreflang="en">une texture</a> de Maurus Loeffel (merci à lui) en <em>DiffuseColor</em>.</li>
<li>D’une sphère (rouge pur) faisant office de scalp à une procédurale de poils.</li>
<li>Sur les poils, un shader <em>Hair</em> par défaut, avec une <em>DiffuseColor</em> gris fonçé.</li>
<li>Une grosse <em>SquareLight</em> au-dessus (une « douche »).</li>
</ul>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/test_scene_guerilla.png" style="margin: 0px auto; display: table; width: 323px; height: 225px;" /></p>
<p>Dans l’ordre des AOVs, nous avons :</p>
<ul>
<li>Beauty, Albedo, Diffuse.</li>
<li>Specular, Reflection, IndDiffuse.</li>
</ul>
<p>Notez que les poils cachent totalement la sphère rouge (son scalp).</p>
<p>On remarque que le <em>back</em> est sur l’AOV de <em>Reflection</em> et semble visuellement jouer son rôle.</p>
<p>Mais pourquoi l’AOV de <em>Reflection</em> ?</p>
<p>Rappelez-vous : Le <em>back</em> simule le spéculaire de la lumière venant de derrière le poil. Pour faire cela, le moteur va lancer un rayon comme il le ferait pour un spéculaire standard, mais il va le faire suivant la logique du spéculaire <em>back</em>, tout droit, devant lui (voir schéma). Le <em>back</em> est donc une réflexion qui part dans le sens du rayon original. Le moteur va simplement traverser le poil et récupérer ce qui se trouve derrière. S’il tombe sur d’autres poils il s’arrête, sinon, il touche le plan texturé (d’où la teinte colorée du <em>back</em>).</p>
<p>On comprend pourquoi cet effet n’est visible que sur les bords des poils : Sur les bords, un rayon a peu de chances de croiser un autre poil sur sa route lorsqu’il trace le <em>back</em>, mais quand on se situe au milieu et qu’on trace le <em>back</em>, on tombe rapidement sur un ou plus de poils, ce qui arrête son tracé. On comprend aussi que par extension, le <em>back</em> a un coup de calcul important, par rapport à ce qu’il apporte ; il est tracé partout, mais n’apparaît que sur les bords.</p>
<p>Donc si vous avez des poils très courts et très denses, il faut sérieusement se poser la question de son apport artistique avant de l’activer, car son coût est important. :redface:</p>
<p>En effet, le temps de rendu de ma petite scène de sphère est de 1 min 43 sec (ça aura son importance dans la suite de ce billet).</p>
<p>Maintenant faisons notre super <em>trick</em> de la mort qui va nous permettre de sauver du temps de rendu, la production, notre salaire et draguer en discothèque : Supprimons les poils du trace set <em>« Diffuse »</em> (ici, via le nœud <em>« -Diffuse »</em>) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/fur_out_diffuse.png" style="margin: 0px auto; display: table; width: 381px; height: 146px;" /></p>
<p>(Au passage, je ne vais pas en discothèque, parce que tout le monde sait bien que<a href="https://youtu.be/antqm4BdEgg?t=4" hreflang="fr">…</a>)</p>
<p>Bref, on vire les poils du trace set <em>« Diffuse »</em> et on rend :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/in_diffuse_wip.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/in_diffuse_wip.png" style="margin: 0px auto; display: table;" width=600 height=400 /></a></p>
<p>On remarque immédiatement que l’AOV de <em>Reflection</em> est « bizarre » et ressemble plus à une sorte de verre trouble qu’une réflexion. Si vous avez bien suivi ce qu’on a dit plus haut, vous avez peut-être compris ce qui se passe. :sauteJoie:</p>
<p>Je vous disais que pour calculer le <em>back</em>, le moteur lance un rayon de réflexion, mais orienté dans son sens original (c’est le spéculaire de <em>back</em> qui veut ça, voir le schéma). S’il touche un autre poil, il s’arrête, s’il ne touche pas de poil, il touche le plan (ou parfois ici, la sphère faisant office de scalp). C’est pour ça qu’en principe, ça ne fonctionne que sur les bords, le rayon ayant peu de chances de taper d’autres poils.</p>
<p>Sauf qu’ici, il semble que tout le spéculaire de <em>back</em> se soit comporté comme s’il n’avait rencontré aucun poil… Comme si, le trace set utilisé pour tracer le <em>back</em> ne contenait pas les poils… D’ailleurs, il utilise quoi comme trace set Guerilla pour tracer ses poils ? :petrus:</p>
<p>Allez dans <em>« Hair / Advanced / Trace Set »</em> :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/guerilla_hair_trace_set_diffuse.png" style="margin: 0px auto; display: table; width: 405px; height: 226px;" /></p>
<p>Mais, les bras m’en tombent ! :bete:</p>
<p>Alors forcément, là vous êtes pépouze, le cul posé dans votre siège à lire un billet de blog écrit avec amour et vous comprenez peut-être ce qui se passe, mais je vous assure que quand un rendu comme ça tombe dans votre boite mail avec pour objet : « UrGeNt ! A rEnDrE pOuR aVaNt Yèr !!11! », vous envisagez sérieusement une reconversion dans le recyclage de mugs usagés au tréfonds de la Sibérie. :casseTeteMur:</p>
<p>Vous l’aurez compris, en virant les poils du trace set <em>« Diffuse »</em>, vous avez purement et simplement supprimé les poils de leur propre réflexion… :slow_clap:</p>
<p>On peut se demander ce qu’on y gagne réellement : Après tout, il suffirait de remettre les poils dans le trace set <em>« Diffuse »</em> et on pourrait rendre sa production heureuse… :sourit:</p>
<p>C’est là qu’interviennent les temps de rendu. La dernière image, avec le poil supprimé du trace set <em>« Diffuse »</em> met 48 secondes à se calculer. Pour rappel, le rendu original met 1 min 43 sec, plus du double (l’écart n’est pas aussi important sur des rendus de production, mais pas loin). Ni l’un, ni l’autre n’est une solution acceptable pour la production.</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/maurane_rendu.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/.maurane_rendu_m.jpg" style="margin: 0px auto; display: table;" width=680 height=255 /></a></p>
<h3>Comment on fait ?</h3>
<p>Donc je disais : La Sibérie… :seSentCon:</p>
<p>OK, à partir d’ici il faut commencer à manipuler des trace set :enerve: , donc si vous n’êtes pas à l’aise avec ce concept, vous pouvez lâcher ce billet en choisissant :</p>
<ul>
<li>Soit vous avez une grosse ferme de calcul, vous laisser les poils dans la <em>Diffuse</em>. :grenadelauncher:</li>
<li>Soit vous êtes limité par votre puissance de calcul, vous pouvez proposer à la production de <s>niquer</s> virer l’<em>back</em>. :siffle:</li>
</ul>
<p>Et c’est gentil d’être passé ! :IFuckTheWorld:</p>
<p>Pour les autres, voici le moment que vous attendiez secrètement en commençant la lecture de ce billet, ce plaisir coupable qui vous prend quand vos temps de rendu foutent le camp, ce truc qui crée chez certains leads des envies de meurtre vis-à-vis de leurs graphistes partis en vacances, ce machin réfléchi et posé dans le wiki du studio avec plus grand sérieux du monde par le sup lighting en début de prod et qui n’a plus aucune réalité à la seconde où les premiers plans commencent à sortir. J’ai nommé : Les trace set. :youplaBoum:</p>
<p>En vrai, je taquine, car c’est loin d’être ingérable. :trollface:</p>
<p>En effet, comme le trace set par défaut, <em>« Diffuse »,</em> est celui contenant tout ce qui est tracé par les rayons d’indirect (et c’est bien la raison pour laquelle on vire les poils de ce trace set), il suffit que les poils aient leur propre trace set. :idee:</p>
<p>On peut imaginer un trace set <em>« Hair »</em> (ou <em>« Char_Hair »</em>, tant qu’à faire) contenant toutes les primitives de poil, et l’ajouter à la liste des trace set tracé par le (ou les) shader de <em>Hair</em> :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_06_guerilla_hair_back_spec_diffuse/trace_set_diffuse_hair.png" style="margin: 0px auto; display: table; width: 379px; height: 89px;" /></p>
<p>Avec une tel approche, les poils sont bien retirés du trace set <em>« Diffuse »</em> (c.à.d qu’ils ne sont pas pris en compte dans le calcul de l’indirect du reste de votre scène), mais ils sont pris en compte par le shader de <em>Hair</em>, et donc le <em>back</em> est calculé correctement. :papi:</p>
<p>Avec une telle approche le rendu est similaire à la première image (le <em>back</em> est joli tout pleins), à l’exception de l’AOV d’indirect qui devient totalement noir (les poils n’étant plus dans la diffuse) et le temps de rendu passe à 1 min 10 sec.</p>
<p>À vous de voir si c’est justifié. :sourit:</p>
<h3>Conclusion</h3>
<p>Comme le disait le poète :</p>
<blockquote>
<p>Sauve ta tournette, créé un trace set.</p>
</blockquote>
<p>À bientôt !</p>
<center>:marioCours:</center>Convertir des primaires sRGB en primaires ACESurn:md5:545a7ef3c32086a633d4e8c7f5ce62ae2021-05-20T23:36:00+02:002021-09-21T14:43:03+02:00NarannInfographie 3D - Boulotacescolorimétriematricepythonsrgb<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/diagram_chroma_piramides.jpg" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Bonjour à tous, dans ce petit billet nous allons voir comment convertir des primaires <em>sRGB</em> en primaire <em>ACEScg</em>. :reflechi:</p>
<p>Ceci vous sera peut-être utile pour convertir des lookdevs historiquement créés en <em>sRGB</em>, vers votre workflow l’ACES. Pour les textures, il suffit de changer la transformation d’entrée (<em>« input device transform »</em>), mais les couleurs stockées en dur dans les attributs de vos logiciels et matériaux n’ont pas d’espace qui leur soit assigné, donc pour conserver le même résultat visuel, il faut les convertir « manuellement ».</p>
<p>Ce sera surtout un prétexte à un quelques explications sur ce qu’est une primaire, la différence entre celles de <em>sRGB</em> et <em>ACEScg</em>, dans quel cas précis on doit le faire et quand ne surtout pas le faire. :tuComprendRien:</p>
<p>Notez qu’il y aura du Python à la fin, toutefois le code est très simple et je pense que la première partie du billet est suffisamment intéressante pour être lu par tout le monde. :hehe:</p> <h3>Qu’est-ce qu’une primaire ?</h3>
<p>Il y a plusieurs façons de décrire une couleur, mais celle qu’on utilise le plus dans notre boulot ce fait via trois composants : Rouge, vert, et bleu. :neutral:</p>
<p>Ce qu’on appelle une « primaire » (ou « <a href="https://fr.wikipedia.org/wiki/Couleur_primaire" hreflang="fr">couleur primaire</a> »), c’est la relation qu’il existe entre la valeur extrême de chaque composant (ici, rouge, vert, bleu) et la longueur d’onde de son <a href="https://fr.wikipedia.org/wiki/Rayonnement_%C3%A9lectromagn%C3%A9tique" hreflang="fr">rayonnement électromagnétique</a> (qu’on va abréger <em>REM</em>, comme le groupe à succès des années 80… :slow_clap: ).</p>
<p>Le REM est un phénomène physique complexe (mais passionnant). Succinctement, il y a plusieurs façons de décrire un REM et on va simplement dire que c’est une onde électromagnétique, qui a donc une longueur d’onde. Nos yeux sont réceptifs à certaines longueurs d’ondes (le spectre visible).</p>
<p>Une couleur est donc déterminée par la longueur d’onde de son REM.</p>
<p>En gros, « rouge pur » ça ne veut rien dire en physique optique. Le rouge du pinard n’a rien à voir avec le rouge-sang de mes yeux quand ils <s>lisent</s> du JS (<em>Le monsieur dit qu’il code en Python et qu’il ne voit pas le problème</em> :petrus: ).</p>
<p>Il faut donc déterminer à quelle longueur d’onde correspond chacun des composants numériques :</p>
<ul>
<li>Primaire rouge (1, 0, 0)</li>
<li>Primaire verte (0, 1, 0)</li>
<li>Primaire bleue (0, 0, 1)</li>
</ul>
<p>Le standard <em>sRGB</em> <a href="https://fr.wikipedia.org/wiki/SRGB#Caract%C3%A9ristiques_principales" hreflang="fr">spécifie</a> que le rouge pur (la primaire rouge) à une longueur d’onde de 612 nm.</p>
<p>Ça veut donc dire que quand votre moniteur (qu’on suppose calibré en <em>sRGB</em>) affiche un pixel rouge pur (1, 0, 0), ce pixel émet en fait un REM d’une longueur d’onde de 612 nm.</p>
<p>Il en va de même pour le vert et le bleu, respectivement 547 et 464,5 nm.</p>
<p>Avec ses trois primaires, on peut donc connaître l’ensemble des couleurs (longueurs d’ondes) que peut afficher le standard. Cet ensemble s’appelle le <a href="https://fr.wikipedia.org/wiki/Gamut" hreflang="fr">gamut</a>. :sourit:</p>
<p>Bien entendu, visualiser une longueur d’onde ce n’est pas pratique. Ainsi, il existe un système, qu’on appelle « diagramme de chromaticité » qui permet de visualiser les longueurs d’onde du spectre visible dans un plan 2D. Ce diagramme, vous l’avez sûrement déjà vu quelque part, on l’appelle parfois le « <em>CIE xy</em> » :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/ACEScg_03_tiny.jpg" style="margin: 0px auto; display: table;" width=600 height=724 /></p>
<p style="text-align: center;">Image récupérée nonchalamment sur l’article <a href="https://www.chaosgroup.com/blog/everything-you-need-to-know-about-acescg" hreflang="en">Everything you need to know about ACEScg</a>, du blog de Chaos Group. :gniarkgniark:</p>
<p>Sans rentrer dans les détails, la magie du truc réside dans la capacité à convertir une longueur d’onde en coordonnée 2D (en passant par CIE xyz, mais on va faire l’impasse là-dessus). Dans le cas du rouge pur <em>sRGB</em>, 612 nm se convertit en coordonnées (0,64, 0.33). Vous pouvez faire la conversion vous-même <a href="https://www.luxalight.eu/en/cie-convertor" hreflang="en">ici</a>.</p>
<p>Le standard <em>sRGB</em> défini donc une longueur d’onde pour chaque composant, tout comme <em>ACEScg</em>, et le schéma ci-dessus superpose ces deux gamuts où l’on peut voir que le décalage est important. :laClasse:</p>
<p>Il est maintenant temps d’expliquer le problème qu’on cherche à résoudre. :perplex:</p>
<h3>Le problème</h3>
<p>Comme expliqué précédemment, si vous prenez une photo de moi qui <s>lis</s> du JS (ça marche avec n’importe quel codeur ayant un minimum de bon goût), vous aurez donc des pixels en rouge pur (1, 0, 0) au niveau des yeux :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/lire_du_javascript.png" style="margin: 0px auto; display: table;" width=550 height=270 /></p>
<p>Si on compare le gamut <em>sRGB</em> à celui de <em>ACEScg</em> (cf. le diagramme <em>CIE xy</em>), on voit que les primaires rouges ne sont pas aux mêmes coordonnées :</p>
<ul>
<li>Un rouge pur (1, 0, 0) en <em>sRGB</em> est aux coordonnées (0,64, 0,33)</li>
<li>Un rouge pur (1, 0, 0) en <em>ACEScg</em> est aux coordonnées (0,713, 0,293).</li>
</ul>
<p>Il ne s’agit donc pas de la même longueur d’onde, donc il ne s’agit pas du même REM, donc ce n’est pas le même rouge ! :papi:</p>
<p>Convertir des primaires revient simplement à changer de gamut.</p>
<p>Pour information les coordonnées <em>CIE xy</em> sont disponibles à la page 7 du <a href="https://www.oscars.org/science-technology/aces/aces-documentation" hreflang="fr">document S-2014-004</a>. :redface:</p>
<h3>Pourquoi convertir manuellement ses primaires ?</h3>
<p>Il est important de comprendre que ce besoin est très spécifique. En situation normale, vos logiciels (Guerilla, Nuke, Mari, RV, etc.) sont correctement configuré via OpenColorIO (viewer en « ACES - sRGB ») :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/acescg_guerilla_viewer.png" style="margin: 0px auto; display: table; width: 254px; height: 72px;" />Et quand vous mettez une texture dans votre moteur de rendu, vous spécifiez l’espace colorimétrique en « Input - sRGB - Texture » :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/acescg_guerilla_texture.png" style="margin: 0px auto; display: table; width: 323px; height: 55px;" />En faisant ça, votre moteur va passer les valeurs de vos textures dans une LUT <em>ACES</em> de OpenColorIO pour, convertir les valeurs de l’espace <em>sRGB</em> vers l’espace <em>ACES</em> :</p>
<p><a href="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/texture_srgb_aces_render.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/.texture_srgb_aces_render_m.png" style="margin: 0px auto; display: table;" width=680 height=184 /></a>D’autres informations sont disponibles <a href="https://www.fevrierdorian.com/carnet/pages/notes-concernant-guerilla-2.html#configurer-aces-dans-guerilla-23" hreflang="fr" title="Configurer ACES dans Guerilla 2.3">ici</a>.</p>
<p>Et quand vous n’utilisez pas de textures, mais directement les couleurs, le <em>picker</em> affiche correctement la couleur, mais surtout, c’est le rendu qui fait foi, comme quand on travaille en <em>sRGB</em>, sans <em>ACES</em> : On modifie un potard, on rend, on ajuste, etc. :hehe:</p>
<p>Le seul moment où vous aurez à vous poser cette question c’est quand vous récupérez un lookdev/lighting fait en <em>sRGB</em> et qu’il faut passer en <em>ACEScg</em>, en gardant le même résultat. Chose qui peut arriver en série. :mechantCrash:</p>
<p>Quand vous récupérez le lookdev d’un asset fait en <em>sRGB</em> (sans <em>ACES</em>, donc), la première chose à faire est de définir le bon espace colorimétrique d’entrée de vos textures (souvent « Input - sRGB - Texture »).</p>
<p>C’est plus compliqué dans le cas des couleurs, car il faut prendre en compte plusieurs choses : En effet, à l’inverse des textures, dans les logiciels, les attributs de couleurs n’ont pas d’espace qui leur sont assignées. C’est juste 3 valeurs (rouge, vert, bleu) sans espace pour les interpréter.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/acescg_guerilla_color_doute.png" style="margin: 0px auto; display: table; width: 359px; height: 65px;" /></p>
<p style="text-align: center;">Les valeurs de cette couleur sont-elles en <em>sRGB</em>, ou <em>ACEScg</em> ? :reflexionIntense:</p>
<p>Les valeurs sont interprétées telles quelles : Le moteur prend les trois chiffres et met ça dans son <em>shader</em>. Mais comme nous l’avons vu plus haut, ces trois valeurs font références à une couleur (un REM) différente suivant l’espace colorimétrique utilisé. Il faut donc modifier ses valeurs pour qu’elle renvoie la même couleur qu’avant ; le même REM.</p>
<p>En gros, les valeurs de notre rouge pur <em>sRGB</em> (1, 0, 0) vont devoir être modifiées pour s’afficher « correctement » (c.à.d, avec une longueur d’onde de 612 nm) en <em>ACES</em>.</p>
<p>J’ouvre une parenthèse : Vous pourriez être tenté de faire un script qui prends <em>tous</em> les attributs de couleurs et appliquer la conversion de primaire que nous allons voir plus bas, mais il y a un risque de convertir une couleur qui n’a pas vocation à être utilisée comme telle ; les attributs de type « couleur », c.à.d, trois valeurs, mais qui sont utilisés sur des masques (oui, c’est moche, mais la vie c’est d’la merde, il va falloir s’y habituer)… :seSentCon:</p>
<p>Dès lors, c’est du bricolage, mais si vous connaissez les entrées de vos matériaux, vous pouvez vous appuyer dessus. Sur le <a href="http://guerillarender.com/doc/2.3/Library_Materials_Surface2.html" hreflang="en">Surface2</a> de Guerilla, l’attribut de couleur de la diffuse est <em>DiffuseColor</em>. Si vous ouvrez un lookdev d’asset fait en <em>sRGB</em>, vous pouvez en déduire que les primaires de la <em>DiffuseColor</em> doivent être convertis pour s’afficher correctement.</p>
<p>Fin de la parenthèse. :siffle:</p>
<p>Nous allons maintenant convertir un rouge pur <em>sRGB</em> (1, 0, 0) en <em>ACEScg</em>.</p>
<h3>Comment on fait ?</h3>
<p>Bien que ça y ressemble, il ne s’agit pas d’une simple interpolation de coordonnée dans le plan <em>CIE xy</em>. On doit passer par une matrice. Le site de Colour-Science propose <a href="https://www.colour-science.org/apps/" hreflang="en">un générateur de matrice</a> de conversion de primaires. Allez-y et sélectionnez :</p>
<ul>
<li>Input Colourspace : sRGB</li>
<li>Output Colourspace : ACEScg</li>
<li>Chromatic Adaptation Transform : Bianco 2010</li>
<li>Formatter : str</li>
<li>Decimals : Autant que vous voulez.</li>
</ul>
<p>Notez que <em>Chromatic Adaptation Transform</em> change légerement la matrice, c’est la méthode utilisée pour « conserver » la chromaticité (la couleur) lors de la transformation. Mettez Bianco 2010, c’est suffisant. :redface:</p>
<p>Vous aurez cette matrice :</p>
<pre>
<code class="language-python">[[ 0.612494198536835 0.338737251923843 0.048855526064502]
[ 0.070594251610915 0.917671483736251 0.011704306146428]
[ 0.020727335004178 0.106882231793044 0.872338062223856]]</code></pre>
<p>Je vous passe la leçon d’algèbre linéaire. Le code suivant « déroule » l’équation :</p>
<pre>
<code class="language-python">def srgb_to_acescg(r, g, b):
return r * 0.612494198536835 + g * 0.338737251923843 + b * 0.048855526064502, \
r * 0.070594251610915 + g * 0.917671483736251 + b * 0.011704306146428, \
r * 0.020727335004178 + g * 0.106882231793044 + b * 0.872338062223856</code></pre>
<p>La fonction <em>srgb_to_acescg()</em> prend les trois composant d’une couleur <em>sRGB</em> et renvoi cette couleur avec les primaires converties. On a donc :</p>
<pre>
<code class="language-python">>>> srgb_to_acescg(1.0, 0.0, 0.0) # rouge pur sRGB
(0.612494198536835, 0.070594251610915, 0.020727335004178) # equivalent ACEScg</code></pre>
<p>Si vous prenez ces valeurs et que vous les mettez dans votre <em>DiffuseColor</em>, vous retombez sur vos pattes.</p>
<h3>Vérification</h3>
<p>Comme on parle de couleurs et d’images, il est dommage de s’arrêter là… C’est parti pour un petit rendu dans notre logiciel favoris ! :banaeyouhou:</p>
<p>J’ai fais une simple sphère, éclairée par une <em>square light</em> blanche. Seul la <em>DiffuseColor</em> de sa sphère change.</p>
<p>Voici trois images :</p>
<ul>
<li>La première est la <em>DiffuseColor</em> rouge pur (1.0, 0.0, 0.0) en <em>sRGB</em> (sans <em>ACES</em>, Guerilla par défaut).</li>
<li>La seconde est la <em>DiffuseColor</em> rouge pur (1.0, 0.0, 0.0) en <em>ACES</em> 1.2 (le profil OpenColorIO disponible <a href="https://github.com/colour-science/OpenColorIO-Configs/releases/tag/v1.2" hreflang="en" title="OpenColorIO Config ACES 1.2">ici</a>).</li>
<li>La troisième est la <em>DiffuseColor</em> rouge corrigée (0.6124, 0.0705, 0.02072) en <em>ACES</em> 1.2.</li>
</ul>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/guerilla_color_001.jpg" style="margin: 0px auto; display: table; width: 400px; height: 400px;" /><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/guerilla_color_002.jpg" style="margin: 0px auto; display: table; width: 400px; height: 400px;" /><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/guerilla_color_003.jpg" style="margin: 0 auto; display: table;" />Sur le second rendu, on remarque que le spéculaire se fait absorber par la profondeur du rouge. :reflechi:</p>
<p>L’assombrissement des deux dernières images est dû à <em>ACES</em> qui garde une portion de la plage du moniteur (0.8 à 1.0) pour les hautes valeurs (1.0 à 16). C’est la couleur qui nous intéresse : Suivant votre moniteur, vous remarquerez que l’image du milieu tire vers le mauve. C’est encore plus flagrant quand on augmente la puissance de l’éclairage, et c’est ce que nous allons faire ! :grenadelauncher:</p>
<p><em>ACES</em> étant fait pour dépasser la dynamique « de base » (0.0 à 1.0) et gérer les dynamiques larges (de 0.0 à 16.0), il est courant d’augmenter la puissance de ses lumières. Si on augmente la puissance des lumières sur les rendus <em>ACES</em> (les deux derniers) pour se rapprocher de l’intensité du rendu original, on obtient les trois images suivantes (le premier rendu est le même qu’avant, <em>sRGB</em> sans <em>ACES</em>, il n’est mis qu’à titre de référence) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/guerilla_color_001.jpg" style="margin: 0 auto; display: table;" /><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/guerilla_color_002_boost.jpg" style="margin: 0 auto; display: table;" /><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2021_05_20_primaire_srgb_en_acescg/guerilla_color_003_boost.jpg" style="margin: 0 auto; display: table;" /></p>
<p>On remarque immédiatement que le rouge non corrigé « éclate ». :baffed:</p>
<h3>Conclusion</h3>
<p>La colorimétrie est un sujet sans fin, ce qui le rend complexe aux non-initiés, mais comme on fait de l’image, il faut s’y coller. :pasClasse:</p>
<p>J’espère que ce billet vous aura plu et qu’il vous aura apporté un éclairage sur les conversions de primaires.</p>
<p>À bientôt !</p>
<center>:marioCours:</center>
<p>EDIT : Barrage du verbe « lire » dans les références au JS, car on ne lit pas du JS, on le… En fait, on le rien-du-tout, on le fout à la poubelle et on change de boulot. :vomit:</p>La ferme de rendu (seconde partie) : Les jobsurn:md5:4087ae2ba44dd0f1c4a520ba193b1c972020-05-16T17:48:00+02:002020-05-21T12:33:43+02:00NarannInfographie 3D - Boulotfrjobrenderfarmversion<p>Dans la <a href="https://www.fevrierdorian.com/blog/post/2018/08/18/La-ferme-de-rendu-premi%C3%A8re-partie-Les-jobs" hreflang="fr">première partie</a>, nous avons abordé les différents jobs qu’on peut retrouver sur une ferme de calcul. Ici nous allons parler de certains aspects tel que la notion de <em>prédiction</em> de données, relation aux versions, gestion du temps de chargement des scènes et des versions des logiciels et plugins.</p> <h3>Prédiction des données</h3>
<p>Si vous ne deviez retenir qu’un point de ce billet ce serait celui-là ! :hehe:</p>
<p>Pas forcément connu, ce concept me semble crucial pour gérer les ressources de sa ferme correctement.</p>
<p>Le principe est de savoir exactement quelles données une chaîne de jobs va produire avant de générer les-dites données. Avant de vous expliquer ce que c’est, on va imaginer une gestion de ferme de calcul sans ce principe.</p>
<p><img alt="dessin_prediction" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2020_05_16_render_farm/prediction.png" style="margin: 0px auto; display: table; width: 490px; height: 400px;" /></p>
<p>Si vous débutez en gestion de ferme, vous aurez tendance à exécuter des gros scripts qui s’occupent de tout faire à la volée, sans vraiment savoir sur quoi ils vont tomber :</p>
<ul>
<li>Ouvrir la scène d’animation Maya.</li>
<li>Choper des trucs dedans (persos, props, etc.).</li>
<li>Créer des versions pour les trucs qu’on va exporter en fichiers Alembic.</li>
<li>Exporter les Alembic.</li>
<li>Créer des versions pour les trucs qu’on va exporter en fichiers Yeti.</li>
<li>Exporter les poils Yeti.</li>
</ul>
<p>Dans le cas de Guerilla :</p>
<ul>
<li>Ouvrir le projet Guerilla.</li>
<li>Choper les trucs dedans (Passe de rendu, AOV, etc.).</li>
<li>Exporter des RIB.</li>
<li>Rendre les RIB.</li>
</ul>
<p>Bim ! Comme ça, d’un coup. :grenadelauncher:</p>
<p>Quand tu as trois personnages légers, c’est cool et plutôt rapide, mais le gros souci de cette méthode c’est que la difficulté à gérer vos jobs augmentera de façon exponentielle au fil de la montée en complexité de vos scènes. Bah oui, si à chaque exécution du job ça recrée des versions, tu vas le sentir passer l’export qui plante et que tu dois relancer dix fois…</p>
<p>Vous l’aurez compris, c’est moisi. Mais qu’est-ce qu’on entend par « gérer vos jobs » ?</p>
<p>Déjà leur utilisation au quotidien : Plus un job est lourd et fait plein de choses, moins il est facile pour les équipes de savoir ce qui est sorti et ce qui ne l’est pas. Sans compter que la moindre chose qui pourrait faire planter le job l’oblige à recommencer entièrement (Et nous savons tous que c’est la <em>dernière</em> partie du job d’export du plan le plus long du projet qui plante, toujours…).</p>
<p>Ensuite le test par les TDs. La facilité à tester un job permet de le développer rapidement (mais ça on s’en fout, parce qu’on sait tous qu’un <em>vrai</em> TD ça fait des cubes et des sphères) et surtout, de le déboguer <s>rapidement</s> moins lentement (Et ça, on s’en fout pas…).</p>
<p>« C’est plus facile de faire un gros script monolithique » diront certains, et ils auront raison, mais je répondrais que « c’est plus facile à déboguer » d’en faire plusieurs petits. Le temps économisé à l’écriture du code se paie par la lourdeur des jobs. En gros, la question n’est pas d’aller vite, mais d’aller loin (C’est beau… :petrus: ).</p>
<p>Le pire, c’est si vos jobs créent des jobs à la volée (création dynamique). Encore une fois, tout ceci est pratique (et parfois nécessaire), mais il n’est pas toujours simple de déboguer de tels jobs.</p>
<p>Je le crie sur tous les toits mais l’argument « c’est plus facile à déboguer » est vraiment fondamental dans un pipeline. En particulier sur les jobs de fermes qu’il est difficile de garder rigides tant ils tendent à évoluer au fil des projets en ouvrant tout et n’importe quoi ; scènes d’animateurs™, scène de rendu, versions des logiciels différentes suivant les projets, etc. Sans compter qu’il faut, pour tester, que la ferme puisse exécuter votre code à vous (La méthode la plus simple consistant à faire passer sa machine pour un <em>worker</em> de la ferme).</p>
<p>Bref, pour pouvoir développer, tester et déboguer des jobs efficacement, il faut qu’ils soient le plus légers et rapide possible (à démarrer et à s’exécuter) et ne fasse qu’une seule et unique chose. La capacité de mettre votre ferme à l’échelle avec des projets de plus en plus gros dépends de ça.</p>
<p>Si vous gardez ça à l’esprit quand vous structurez vos jobs, vous allez vite vous rendre compte que le meilleur (seul ?) moyen d’avoir des jobs précis et granulaires, c’est de savoir à l’avance ce qu’il y a dans vos scènes, c’est-a-dire au moment où vous générez le graphe de dépendance de jobs.</p>
<p>En effet, si vous savez <em>avant</em> d’ouvrir une scène d’animation ce qu’il y a dedans, vous allez pouvoir faire autant de job d’export qu’il y a de personnages (ou <em>prop</em>) et ainsi gagner en temps.</p>
<p>Si vous avez trois personnages avec, pour chaque personnage, un export Alembic et une simulation automatique de <em>cloth</em>, vous aurez une chaîne de six jobs :</p>
<pre>
toto_001_abc -> toto_001_auto_cloth
tata_001_abc -> tata_001_auto_cloth
tutu_001_abc -> tutu_001_auto_cloth</pre>
<p>Le petit malin du fond me fera remarquer qu’il suffit d’ouvrir la scène Maya et de générer la chaîne de job depuis cette dernière pour savoir ce qu’il y a à générer. Après lui avoir rappelé que les petits malins du fond dans son genre finissent seul, alcoolique et au chômage, je lui expliquerai qu’on a pas que ça à faire d’ouvrir des scènes Maya et que même si on le fait dans un job qui se charge d’ouvrir les scènes Maya pour générer les jobs, on ne sait pas, au moment où on génère ce fameux job ce qu’il va générer. En faisant ça, vous avez simplement décalé le problème. :reflechi:</p>
<p>Bref, pas le choix, il faut connaître à l’avance, au moment où on fait toute la chaîne de job.</p>
<p>Il y a plusieurs méthodes pour y arriver suivant votre pipeline. Voici quelques pistes :</p>
<ul>
<li>Écrire un fichier à côté de votre scène (<em>side car</em>) contenant les instances présentes dedans ; toto001, tata001, etc.</li>
<li>Sauvegarder les informations en métadonnées de votre version dans la base de donnée que vous utilisez.</li>
<li>Si vous utilisez un pipeline s’appuyant sur des notions de connexions et de nœud, vous pouvez faire un nouveau type de connexion.</li>
<li>etc.</li>
</ul>
<p>Ces méthodes ont en commun de s’exécuter au moment de la publication afin d’avoir les informations nécessaires à disposition.</p>
<p>Cela implique aussi que ces informations doivent être accessible et lu au moment de la création du graph de job.</p>
<h3>Le <em>versionning</em></h3>
<p><img alt="dessin_versioning" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2020_05_16_render_farm/versioning.png" style="margin: 0px auto; display: table; width: 215px; height: 292px;" /></p>
<p>Comme je le disais, un des risques qu’il y a à relancer une chaîne de job non prédictible est de se retrouver avec autant de versions créées que de fois où la chaîne est relancé. Une manière d’éviter ça consiste à créer les versions au moment de la soumission du job. Ceci permet d’avoir une gestion <em>synchrone</em> des versions, mais c’est la ferme, <em>asynchrone</em> par nature, qui va les remplir, puis les « fermer ».</p>
<p>Un problème se pose toutefois rapidement : En effet, quand on crée une version, elle est souvent accessible immédiatement. Ainsi, si vous assemblez un plan dans la minute qui suit la publication d’un asset qui le compose (e.g. l’export d’une animation d’un personnage), le plan assemblé utilisera cette version, mais si la sauvegarde du fichier se fait en décalé (de façon asynchrone, par la ferme), vous risquez d’avoir des problèmes, car l’assembleur de scène ne comprendra pas pourquoi une version existe mais le fichier n’est pas présent et/ou invalide.</p>
<p>Une façon de résoudre ce problème passe par la possibilité d’activer ou de désactiver d’une version. Ainsi, une version crée mais non « fini » (le fichier n’est pas encore là), sera « désactivée » aux yeux du pipeline, et ne sera activé (et ses fichiers mis en lecture seule) qu’une fois la génération du fichier (export Alembic, rendu, playblast, etc.) terminé.</p>
<p>Toute version visant à être « remplis » sur la ferme est donc désactivée à sa création, et un job dédié s’occupe de l’activer quand la chaîne de job est terminé.</p>
<p>Dernier problème (promis) : Si on relance une chaîne de job qui s’est totalement terminé, et dont, implicitement, les fichiers sont « fermés » à la modification, il est donc nécessaire de les rouvrir. Une étape (un job) est donc également nécessaire <em>avant</em> la modification d’une version.</p>
<p>On a donc :</p>
<ul>
<li>Créations des versions : Localement, au moment de la soumission de la chaîne de job (synchrone).</li>
<li>Ouverture/modification/fermeture des versions : Sur la ferme, par chaque worker (asynchrone).</li>
</ul>
<p>Il n’est pas nécessaire de passer par un job dédié pour l’ouverture et la fermeture des versions, cela peut se faire directement dans le code qui s’exécute sur la ferme. À titre personnel, je préfère des jobs dédiés, car la modification d’une version peut nécessiter que deux jobs (ou plus) lui passe dessus.</p>
<p>J’appelle l’ensemble de cette approche « l’allocation de versions » : À un moment <em>t</em>, vous demandez au pipeline des « espaces » (ici, des versions) qui vont être modifié, plus tard, par différentes machines de la ferme qui s’occuperont ensuite de les libérer. Si aucune erreur ne s’est produite lors de l’exécution de votre chaîne de job, les versions créées par la ferme sont remplis, disponibles pour le reste du pipeline et les permissions y sont correctement appliquées.</p>
<h3>Durée d’ouverture des scènes d’animation</h3>
<p>Je vous invite à garder à l’esprit que la durée d’ouverture des scènes d’animation peut être importante. Certains plans de <em>certains</em> projets sur lesquelles j’ai pu travailler mettaient quasiment 30 minutes à s’ouvrir. C’est souvent un problème à résoudre en amont (car c’est l’animateur qui perd du temps), mais comme souvent en production, ce n’est pas possible pour le projet en cours. Si vous exportez vos Alembics directement depuis la scène d’animation, ça veut dire qu’une mise à jour du <em>rig</em> d’un seul personnage nécessite la réouverture complète de la scène que ce soit par l’animateur ou un job de ferme, c’est du gâchis.</p>
<p><img alt="dessin_toutessoukontrolle" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2020_05_16_render_farm/toutessoukontrolle.png" style="margin: 0px auto; display: table; width: 640px; height: 400px;" /></p>
<p>Une méthode pour éviter ça consiste à n’exporter que les courbes d’animation et de reconstruire l’ensemble en partant d’une scène vide.</p>
<p>Il y a plusieurs façons d’exporter des courbes d’animation, je vous en donne deux, en vrac :</p>
<ul>
<li>Exporter les nœuds (principalement les courbes d’animations) seuls en format .mb/.ma ainsi qu’un fichier texte avec les informations de reconnexions de ces nœuds. Cette méthode permet de conserver l’animation originale de Maya, mais nécessite pas mal de développement et ne résout pas le problème des contraintes.</li>
<li>Exporter un fichier ATOM, ce dernier comprenant une version baké des courbes d’animations. C’est ma méthode préférée, car elle permet de n’importer qu’un range précis d’une animation sur un autre range, permettant ainsi de reconstruire une animation en y ajoutant un <em>preroll</em> automatique pour nos amis du cloth. En passant par le bake, cette méthode résout le problème des contraintes qui ne sont plus utiles.</li>
</ul>
<p>Une fois les courbes exportées, vous n’avez plus qu’à ouvrir une scène vide, importer la dernière version du <em>rig</em> et y coller vos courbes pour exporter l’Alembic.</p>
<p>Ces mécanismes, qui, mine de rien, ajoutent des étapes d’export, permettent de gérer des exports complexes car chaque asset est exporté individuellement.</p>
<h3>Un dossier temporaire pour votre ferme</h3>
<p>Il peut être pratique de disposer la ferme d’un système de fichier temporaire partagé par tous les <em>worker</em>. L’avantage immédiat étant de ne plus s’embêter avec des problématiques de pipeline et de version. Vous créez un dossier /farm/tmp/mon_id/ et vous y mettez ce que vous voulez. Les différents jobs liront et écriront à l’intérieur, avec, éventuellement, un job de nettoyage en fin de chaîne.</p>
<p>Un exemple d’utilisation que j’ai souvent observé tourne autour de l’assemblage de scènes :</p>
<ul>
<li>On exporte/assemble les scènes dans leur format de rendu (RIB, ass).</li>
<li>On les rend.</li>
<li>On sépare, parfois, les layers des images.</li>
<li>On supprime les fichiers de rendu ; RIB, ass, voir les images brutes qui ne sont plus utiles (sauf pour du débogage).</li>
</ul>
<p>On pourrait grossir cette liste d’étapes avec du précalcul de fichier (bake d’illumination, simulation et génération de poils) qui vient se faire <em>avant</em> le rendu. Bien que cela se fasse moins qu’il y a quelques années, le précalcul peut se révéler nécessaire pour rendre des plans qui sortent de l’ordinaire ; transitions bizarres, plans séquences, etc.</p>
<p>Les graphistes utilisant Houdini et ayant la responsabilité de plans complexes ainsi que les studios travaillant sur plusieurs projets de natures différentes (e.g. La publicité) s’appuient beaucoup sur ce système.</p>
<p>Plus un plan est complexe et spécifique, plus il est difficile de mettre en place un mécanisme de prédiction poussé. Le temps passé à généraliser une solution étant trop important et/ou le nombre de plan concerné étant trop faible.</p>
<p>Il y a un dernier cas où vous n’avez pas beaucoup le choix : Si la taille de votre ligne de commande est trop importante, vous devez sérialiser les arguments. Ça semble absurde, mais pour m’être déjà retrouvé dos au mur (une ligne de commande dont le nombre d’argument a augmenté au fil des semaines), écrire un JSON dans un dossier temporaire plutôt que d’avoir à repasser sur toute la gestion de la commande (on était en fin de production) peut vous sauver la mise.</p>
<p>Passer par un dossier partagé a le mérite de proposer une solution simple à comprendre pour tout le monde. On est clairement sur une approche artisanale de la ferme, tout en gardant une distinction claire entre ce qui est dans le pipeline et à l’extérieur.</p>
<p>Malgré ça, ne pas avoir de suivi granulaire des données générées par la ferme peut entraîner son lot de complication. Ainsi, une ferme multi-site devient un calvaire à gérer. Les dossiers temporaires n’étant pas au même endroit, soit vous faites un partage direct (ce qui implique une occupation continue de la bande passante), soit vous calez des jobs de synchro entre les jobs utilisant des machines de sites différents. Dans les deux cas, cela ajoute de la complexité à l’ensemble et il faut peser le pour et contre.</p>
<p>À titre personnel, j’ai pu remarquer que, dès lors qu’il faut synchroniser des choses, avoir un suivi précis est plus efficace : Gardez un suivi des choses à l’échelle du pipeline (asset, version, etc.) pour tout ce que vous pouvez anticiper et ne sortez la solution des fichiers temporaires de ferme que pour les exceptions.</p>
<p>D’un point de vue code, une petite API distribuée aux superviseurs leur évite d’avoir à gérer ça eux-mêmes : Une fonction qui crée le dossier temporaire, renvoi son chemin ainsi que le job chargé de supprimer de dossier en question est un bon début. Pour le nom du dossier temporaire généré par cette API, lui préfixer la date (2020_04_12) permet d’avoir rapidement un coup d’œil sur ce qui ne semble plus nécessaire.</p>
<p>Faites un suivi régulier de la taille de ces dossiers et impliquez les superviseurs qui l’utilisent, ils sont bien plus en mesure de savoir ce qui est nécessaire ou non.</p>
<p>Il va de soi qu’aucun autre script ne doit s’appuyer sur ce dossier. <em>Certains</em> pourraient avoir l’idée de s’en servir comme dossier d’échange dans le studio.</p>
<h3>Nettoyage des jobs</h3>
<p>On en a déjà un peu parlé, mais le nettoyage des jobs concerne plusieurs choses qui peuvent se faire à plusieurs moments du <em>cycle de vie</em> d’un job.</p>
<ul>
<li>Il y a le nettoyage qui se fait immédiatement après le job ou la chaîne de job. C’est celui qu’on fait naturellement quand le job est supposé avoir fini son travail (nettoyage des RIB).</li>
<li>L’autre est plus subtile et se fait au moment de la suppression du job du manager. En pratique, on supprime rarement le job, mais on procède à son archivage. Cela faisant, les fichiers de logs sont également supprimés du système de fichier.</li>
</ul>
<p>La plupart du temps, on aura un job dédié au nettoyage, mais certains jobs managers intègrent le concept de nettoyage en tant que paramètre du job. Cela peut prendre la forme de clef comme <em>clean_dir</em>, <em>clean_file</em>, <em>clean_files</em>, etc.</p>
<h3>Des jobs génériques, ou spécifique ?</h3>
<p>Au moment de l’écriture d’un job, une balance tend à apparaitre : Doit-on écrire un job qui fonctionnera pour tout type de tâche ? Ou doit-on écrire un job qui fonctionnera pour un besoin précis ? Doit-on gérer les choses de façon abstraite ou de façon concrète ? Ma réponse ne va pas énormément vous aider : Les deux mon capitaine !</p>
<p>Prenons un exemple : Vous souhaitez faire les jobs « d’export des personnages ». Exprimé de la sorte, c’est un besoin abstrait. En effet, « exporter un personnage » se fait, à priori, en plusieurs étapes :</p>
<ul>
<li>Exporter des courbes d’animation.</li>
<li>Exporter un ou plusieurs Alembics.</li>
<li>Calculer une ou plusieurs simulations de poils (cheveux, poils).</li>
<li>Baker un ou plusieurs systèmes de poils.</li>
<li>Calculer une, ou plusieurs simulations de vêtements.</li>
<li>Calculer et exporter des simulations au sol (zones de contact pour le FX).</li>
</ul>
<p>J’ai donné une liste aussi exhaustive que possible, mais il est évidant que peu de projets cochent toutes les cases et qu’il n’est pas forcément optimal de faire un export de personnage aussi granulaire que ça (tout dépend de votre budget).</p>
<p>La question de savoir si ont fait des jobs pour « exporter un personnage » ou « exporter un Alembic » se pose. Si on choisit de faire un job « d’export de personnages », vous vous rendrez vite compte qu’il doit être séparé en petits jobs. Ensuite, si on choisit de faire des jobs qui « exportent un Alembic », on risque de buter, plus loin, sur des problématiques <em>spécifiques</em> : Est-ce qu’on exporte un Alembic de la même façon pour un personnage que pour une simulation ? Dès lors, un besoin de faire un job qui « export un Alembic <em>FX</em> » apparaît et il est probable qu’il n’ait pas été anticipé. Dans le pire des cas, vous vous retrouvez avec un département FX complet à qui on a dit que les jobs « d’export d’Alembic » étaient près et qu’il pouvait commencer le travail.</p>
<p>Si tout ceci peut sembler relever du domaine académiques, c’est parce qu’il s’agit rarement de questions qui se posent en début de production, quand on met notre ferme en place, mais plutôt quand la nature de ce que fait votre ferme évolue (milieux de production ou entre deux saisons d’une série). Ces questions, mise de côté, agissent comme un ressort.</p>
<p>La raison pour laquelle j’insiste sur cette distinction, c’est pour que vous soyez en mesure de la « détecter » en production, au risque de perdre du temps en enchevêtrement de paradigmes et/ou discussion avec la production.</p>
<p>Quand des graphistes ou des superviseurs vous parleront, ils ne feront pas cette distinction. C’est vous le codeur, c’est votre travail de la faire et de l’expliquer. Dans notre exemple, si un superviseur pense qu’il va pouvoir exporter des Alembic de FX parce que vous lui avez fait un job d’export d’Alembic sans en définir correctement le contour, vous êtes en parti responsable de cette confusion. En gros, ne « pensez » pas les jobs suivant leur nom, mais suivant ce qu’ils font, et assurez-vous que vous n’êtes pas le seul.</p>
<p>On peut, légitimement, se demander s’il est utile d’avoir des jobs génériques qui font des choses dont la définition n’est pas (encore ?) clair aux yeux de la production. Quand un besoin est « ballant » (comprenez, on sait qu’on en a besoin, mais on ne sait pas si c’est la bonne façon de faire), il me semble que faire un job qui fait « le truc » est la solution.</p>
<h3>Gestion des différentes versions des logiciels sur la ferme</h3>
<p>Suivant la nature des projets que vous avez à sortir, il est fort probable que vous ayez à gérer différentes versions de logiciels et des plugins qui les compose.</p>
<p>Vous pouvez définir des versions « à la machine », mais cela vous prive de puissance de calcul, car vous risquez de ne pas pouvoir utiliser une machine au simple prétexte qu’elle n’a pas la bonne version de Maya d’installée.</p>
<p>On serait donc tenté d’avoir plusieurs versions d’un logiciel, disponible sur chaque machine. Dès lors qu’une machine rend disponible plusieurs versions d’un logiciel, vos jobs doivent pouvoir appeler la bonne version.</p>
<p>Les méthodes pour y arriver sont nombreuses aucune n’est parfaite et leur utilisation dépend beaucoup de l’organisation de votre studio (rien que ça). La plus naïve consiste à appeler l’exécutable d’un logiciel par un nom précis. Dans le cas de Maya, on aurait donc une ligne de commande :</p>
<pre>
<code class="language-bash">maya2019 -c "print 'toto'"</code></pre>
<p>J’ai quelque à priori vis-à-vis de cette méthode. Le principal étant la gestion des versions des plugins. En effet, si vous utilisez un plugin (e.g. Yeti pour Maya), il est probable que vous ayez à <em>glisser</em> de version en cours de production (vous commencez les nouveaux plans avec la nouvelle version, vous finissez les anciens plans avec l’ancienne).</p>
<p>Je rappelle l’évidence : Il est dangereux de changer de version d’un logiciel en cours de production. En revanche, certains bugs peuvent être tellement bloquants qu’ils remettent en question ce dogme. Il peut s’agir de problèmes corrigés dans une version mineure (2.5.0 -> 2.5.1), comme de problèmes plus graves qui remettent en cause l’utilisation de l’outil. C’est une situation qui peut apparaître quand on utilise un tout nouvel outil en production et qu’on réalise, trop tard, que ce qui était prévu de faire avec n’est pas possible à cause d’un bug corrigé dans une version majeure (2.5.0 -> 3.2.0).</p>
<p>À cela vient s’ajouter un dernier point, pratique, qui est le test : Vous êtes en 2.5.0 et que la version 2.5.1 corrige le problème, vous passez tout le plancher en 2.5.1. Plus tard, en production, un problème, différent mais qui semble lié apparait. Vous avez un doute : Est-ce que ce problème apparaissait en 2.5.0 ?</p>
<p>Et là on parle d’un seul plugin, mais certains logiciels sont amenés à faire tourner pas mal de plugins différents. Dès lors, vous voyez une matrice se dessiner : Version du logiciel/Version du plugin A/Version du plugin B/etc. Et si vous utilisez des plugins compilés « maison », c’est encore pire, car vous pouvez être amené à livrer une version plusieurs fois par jour.</p>
<p>Il est tout à fait possible de ne s’appuyer que sur le nom de l’exécutable dans la ligne de commande :</p>
<pre>
<code class="language-bash">maya2019-yeti2.5.0-rigNodeMaison3.6.5 -c "print 'toto'"
maya2019.sp1-yeti2.5.0-rigNodeMaison3.6.5 -c "print 'toto'"
maya2019.sp1-yeti2.5.1-rigNodeMaison3.6.4 -c "print 'toto'"
...</code></pre>
<p>Notez que <a alt="PeregrineLabs Ecosystem github repository" href="https://github.com/PeregrineLabs/Ecosystem">Ecosystem</a> de PeregrinLabs fonctionne un peu suivant ce principe, mais je suis dubitatif quant à la taille de tels lignes de commandes quand vos graphistes deviennent mordu de plugins maisons. Je lui préfère une approche via variables d’environnement :</p>
<pre>
<code class="language-bash">export MYENV_MAYA_VERSION=2019.sp1;
export MYENV_YETI_VERSION=2.5.0;
export MYENV_RIGNODEMAISON_VERSION=3.6.5;
myenv -- maya -c "print 'toto'"</code></pre>
<p>Ici, <em>myenv</em> est un exécutable qui consomme les variables d’environnement commençant par <em>MYENV_</em> pour en faire des environnements dans lequel la commande qui suit « -- » est exécuté.</p>
<p>Bien entendu, les commandes <em>export</em> ne sont pas exécutées par vous, mais envoyé avec le job au moment de la soumission :</p>
<pre>
<code class="language-python">my_env = {'MYENV_MAYA_VERSION': '2019.sp1',
'MYENV_YETI_VERSION': '2.5.0',
'MYENV_RIGNODEMAISON_VERSION': '3.6.5'}
job = {'command': 'myenv -- maya -c "print \'toto\'",
'env': my_env}
farm.submit(job)</code></pre>
<p>Je suis un adepte de cette méthode.</p>
<p>Arrivé ici, je ne peux pas ne pas vous parler de <a alt="Rez github repository" href="https://github.com/nerdvegas/rez">Rez</a>. Je ne l’ai jamais utilisé personnellement, mais j’en ai entendu beaucoup de bien.</p>
<p>Rez résout des environnements via un système de dépendance. La charge cognitive initiale due à son apprentissage est importante, mais il est considéré comme une brique solide d’un pipeline avec une bonne <a alt="Documentation de Rez" href="https://github.com/nerdvegas/rez/wiki">documentation</a>.</p>
<p>La gestion des environnements étant souvent central dans un studio, vous pouvez aussi être tenté d’écrire le vôtre. Ce n’est pas particulièrement difficile à faire (pour tout vous dire, je n’ai encore jamais fait autrement), mais c’est une maintenance en continue. L’avantage étant que ce gestionnaire s’en tiendra à faire <em>exactement</em> ce que vous voudrez, pas plus, pas moins.</p>
<p>Si vous choisissez de le faire en Python, gardez le module aussi isolé et indépendant que possible. S’il vous vient à l’idée de questionner la BDD sur les métadonnées d’un shot pour résoudre un environnement, vous fonctionnez peut-être à l’envers (et si vraiment vous n’avez pas le choix, passez par une variable d’environnement : MYENV_SHOT_NUMBER)</p>
<h3>Data mining</h3>
<p>C’est le sujet sexy du moment et il est difficile d’avoir un avis tranché tant le secteur « tâtonne » à déterminer si le temps de compréhension et d’interprétation des données justifie d’y dépenser de l’énergie.</p>
<p>La première erreur consisterait à utiliser des données de ferme pour répondre à des questions de production : Si vous voulez répondre à des questions de production (e.g. nombre de validation par semaine), analysez des données de production (e.g. Shotgun). En revanche il est très utile de comparer les données de ferme à des données de production, cette dernière influençant la première. Vous serez peut-être en mesure d’y trouver des corrélations intéressantes, voir, surprenantes.</p>
<p>Pour l’anecdote, sur un projet, on avait pu remarquer que plus on se rapproche de la fin du projet, plus le temps de rendu <em>moyen</em> par image diminuait, en parallèle de quelques plans qui voyaient leur temps de rendu augmenter drastiquement. On avait mis ça sur le compte du fait que, la <em>deadline</em> arrivant, les graphistes ne prenaient plus le risque d’augmenter les temps de rendu et forçait sur l’optimisation de leurs scènes pour éviter de les voir « revenir » de la ferme au motif d’un temps de rendu trop long, ce qui, par effet de débordement, diminuait la qualité générale. La pression qui peut apparaître en fin de production peut entraîner, à tous les niveaux de la hiérarchie, une diminution du zèle (que je crois) nécessaire à la sortie de belles images. Les superviseurs et les <em>leads</em> doivent alors se focaliser sur ce qui est essentiel à la qualité d’un plan. C’est d’ailleurs le moment ou le ratio qualité/temps de travail graphiste est le meilleur, les gens ayant pris l’habitude des erreurs à ne surtout pas faire et de ce qui plaît. Quant aux quelques rendus qui explosent ? C’est les laissés pour compte, les rendus qui ont un problème, mais qu’on a plus le temps de régler.</p>
<p>Mais je digresse…</p>
<p>Les données de ferme ne répondront qu’à des questions de gestion de ferme : Alimentation, charge, licence. Ces données sont très utiles pour élaborer correctement des budgets par juxtaposition. Il est, en effet, plus simple de définir les besoins d’une production quand on a déjà une base chiffrée de l’utilisation d’une ferme sur un projet.</p>
<h3>Avoir des <em>logs</em> explicites</h3>
<p>Isoler un job pour reproduire un bogue prend du temps, et ce, malgré le travail effectué pour simplifier cette démarche. Quand un problème apparaît, vous aurez rapidement le réflexe de lire le <em>log</em> du-dis job en le comparant avec les appels à <em>print()</em> de votre code. En faisant cela vous aurez rapidement une idée de <strong>où</strong> votre script a planté. Et si vous avez affiché les valeurs des différentes variables, vous serez potentiellement en mesure de reproduire le problème avec une simple commande.</p>
<p>Il est donc important d’avoir des <em>logs</em> descriptif et suffisamment claires. Il ne faut pas hésiter à être verbeux, car si vous n’affichez que quelques informations, vous risquez de vous retrouver face à un gros bloc de code dans lequel le problème apparaît, sans que vous puissiez être en mesure de déterminer ce qu’il fait.</p>
<p>Je vous invite également à ajouter des informations de temps afin de déceler les écarts qui peuvent se produire entre deux lignes :</p>
<pre>
<code>2018-01-22 10:53:00,500|CMD |INFO|...
2018-01-22 10:53:01,589|CMD |INFO|...
2018-01-22 10:53:01,589|CMD |INFO|...
2018-01-22 10:53:02,351|MAYA|INFO|...
2018-01-22 10:53:02,351|MAYA|INFO|...
2018-01-22 10:53:02,605|MAYA|WARN|...
2018-01-22 10:53:02,605|MAYA|INFO|...</code></pre>
<h3>Conclusion</h3>
<p>J’aurais pu aborder le cloud, mais le sujet est très large et je ne pense pas avoir assez d’expérience pour donner des conseils pertinents.</p>
<p>J’espère que ces deux billets vous auront plus.</p>
<p>À bientôt !</p>
<center>:marioCours:</center>Guerilla, le shutter et Yetiurn:md5:88bee7da37bd347ece0a511cad6ec3262019-08-19T22:55:00+02:002019-08-21T14:17:48+02:00NarannInfographie 3D - Boulotguerillarendermanshutteryeti<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/guerilla_yeti_node_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Dans ce billet on va encore parler de poils et tout particulièrement du <em>motion blur</em> de Yeti dans Guerilla.</p>
<p>Rien de révolutionnaire, on va juste faire le tour des choses à savoir quand on doit gérer cette combinaison. :reflechi:</p> <p>À l’époque, calculer le <em>motion blur</em> était très coûteux pour un <em>ray tracer</em>. Muni de nos passes de <em>motion vector</em> (en <em>.tiff</em>…), on bricolait au compo pour éviter de provoquer des crises d’épilepsie chez le spectateur (car à par les animateurs qui trouvent que l’effet stroboscopique d’un <em>playplast</em> est trop cool, tout le monde à mal au crâne).</p>
<p>C’était l’époque ou une roue ou une sphère tournant rapidement sur un plan n’augurait rien de bon… :triste:</p>
<p>S’il est intéressant de voir que les techniques à base de passes de <em>motion vector</em> au compo n’ont jamais complètement disparues, on ne peut, en revanche, pas ignorer que le <em>motion blur 3D</em> (c.à.d, rendu) arrive avec son lot de complications.</p>
<p>Ce billet part du principe que vous savez manipuler Yeti et Guerilla. :cayMal:</p>
<h3>Guerilla et le motion blur</h3>
<p>Il faut bien comprendre que Guerilla n’est pas un logiciel comme Maya qui va « sampler » la scène à des valeurs temporelles données. Il n’assume rien du sampling interne des éléments de la scène (<em>Alembic</em>, <em>Realflow</em>, etc.). Il récupère tout ce qui peut pour un <em>shutter</em> donné.</p>
<p>Ce mécanisme permet de faire coexister, dans une même scène, des <em>Alembic</em> exportés avec différentes valeurs de <em>step</em> (e.g. la majorité en 2 <em>samples</em> : 0.0-0.2 et les personnages rapides en 5 <em>samples :</em> 0.0-0.05-0.1-0.15-0.2, etc.).</p>
<h3>Les procédurales RenderMan</h3>
<p>Guerilla supporte <a href="https://rmanwiki.pixar.com/display/REN22/Procedural+Primitives" hreflang="en" title="Procedural Primitives">les procédurales</a> au format <em>RenderMan</em>. Une procédurale est un objet (une <em>primitive</em> plutôt) de votre scène de rendu qui déploie son contenu quand le moteur le demande (souvent, quand un rayon touche sa <em>bounding box</em>). À ce moment, le moteur appelle la procédurale avec ses paramètres. Chaque procédurale dispose de ses propres arguments, un peu comme une ligne de commande.</p>
<h3>La procédurale Yeti</h3>
<p>Pour que Yeti crache ses poils au moteur, il nécessite un fichier .fur généré par vous au moment de l’export Maya. Ce fichier contient tout ce qu’il faut à Yeti pour générer des poils :</p>
<ul>
<li>Le graph Yeti (celui que vous manipuler dans Maya).</li>
<li>Les guides (courbes).</li>
<li>Le groom.</li>
<li>La géométrie animée (le <em>scalp</em> de votre personnage) et samplé (à 0.0 - 0.2 par exemple).</li>
<li>La géométrie en <em>rest pose</em>, utilisé comme référentiel pour la distribution des poils sur la surface. Il faut que sa topologie soit identique à celle du <em>scalp</em>.</li>
</ul>
<p>La documentation du format de cache (.fur) est <a href="http://documentation.peregrinelabs.com/yeti/workingwithcaches.html" hreflang="en" title="Yeti - Working With Caches">ici</a> si vous voulez un peu plus d’information.</p>
<p>Au moment de l’appel de la procédurale, on lui donne ce fichier, ainsi que certain paramètres (<em>density</em>, <em>width</em>, <em>thread</em>, etc.) et elle fait ensuite son travail. :mechantCrash:</p>
<p>La documentation des paramètres de la procédurale Yeti se trouve <a href="http://documentation.peregrinelabs.com/yeti/commandlineandrendering.html#rendering" hreflang="en" title="Yeti procedural parameters">ici</a>. Si vous la regardez, vous remarquerez la présence de l’argument <em>--samples</em>. Comme expliqué, Yeti va cracher les samples de <em>motion blur</em> aux valeurs données dans cet argument.</p>
<h3>Yeti dans Guerilla</h3>
<p>Avant qu’un nœud Yeti d’interfaçage n’existe dans Guerilla, il fallait faire une <em>RIB box</em> contenant l’appel de la procédurale. Ce n’était pas super pratique, mais on avait un contrôle absolu sur l’appel (c’est nous qui donnions les arguments).</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/guerilla_rib_box_001.png" style="margin: 0px auto; display: table; width: 244px; height: 93px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/guerilla_rib_box_002.png" style="margin: 0px auto; display: table; width: 144px; height: 100px;" /></p>
<p style="text-align: center;"><em>Le manque d’icône devrait vous donner une idée du côté user friendly…</em></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/guerilla_rib_box_003.png" style="margin: 0px auto; display: table; width: 388px; height: 160px;" /></p>
<p style="text-align: center;"><em>Et toto.rib est le fichier ouvert par Guerilla.</em></p>
<p>Pour vous donner une idée de ce qu’on écrivait dans ce fichier :</p>
<pre>
<code class="language-bash">Procedural "DynamicLoad" ["pgYetiPrmanRender" --density 1 --length 1 --width 1 --frame 1 --samples 0.0 0.2 --file "/path/to/my.fur"]</code></pre>
<p><a href="https://renderman.pixar.com/resources/RenderMan_20/proceduralPrimitives.html#procedural-primitive-dsos" hreflang="en" title="Procedural Primitives">Du bonheur en boite</a> ! :petrus:</p>
<p>Comme c’était <em>un brin</em> casse-pieds, un nœud Yeti a vu le jour :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/guerilla_yeti_node.png" style="margin: 0px auto; display: table; width: 389px; height: 272px;" /></p>
<p style="text-align: center;"><em>Documentation du nœud <a href="http://guerillarender.com/doc/2.2/TD%20Guide_Third%20Party%20Tools_Yeti.html" hreflang="fr" title="Documentation du nœud Yeti dans Guerilla">ici</a>.</em></p>
<p>Ce nœud encapsule la procédurale, ce qui lui permet de bénéficier du <em>workflow</em> de Guerilla (<em>overrides</em> notamment). Dès lors, il faut se demander quelles sont les valeurs que Guerilla passe à <em>--samples</em> ?</p>
<p>Et bien, par défaut, Guérilla demande deux samples, un au <em>shutter open</em> et l’autre au <em>shutter close</em> du projet. Rappelez-vous :</p>
<ul>
<li>Guerilla ne peut pas savoir quels sont les samples présents dans le .fur.</li>
<li>La procédurale Yeti demande explicitement les samples à générer et ne permet pas de générer tout ce qu’elle a (ce pourrait être une demande légitime à faire au développeur).</li>
</ul>
<p>Pour pallier à ça vous pouvez passer une liste explicite de samples dans l’attribut <em>Motion Samples</em> du nœud Yeti de Guerilla. À vous, toutefois, de vous assurer que ces valeurs sont cohérentes avec ce qu’il y a dans votre <em>.fur</em> ainsi qu’avec le <em>shutter</em> fourni à Guerilla.</p>
<p>J’espère avoir rendu tout ça moins opaque. :pasClasse:</p>
<p>Un tout petit écart pour vous dire que vous pouvez baker la procédurale en <em>.ghostdata</em>. Guerilla va générer le contenu de la procédurale et l’enregistrer, vous pouvez ensuite l’importer dans et Yeti n’est plus nécessaire. Ce sera lourd, mais dans le cas d’éléments statiques c’est très pratique.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/guerilla_yeti_node_bake_001.png" style="margin: 0px auto; display: table; width: 355px; height: 169px;" /></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/guerilla_yeti_node_bake_002.png" style="margin: 0 auto; display: table;" /></p>
<p>Et bien entendu, vous pouvez scripter ça :</p>
<pre>
<code class="language-python">import guerilla
guerilla.Procedural.baketoarchive("/path/to/my.ghostdata")</code></pre>
<p>Pensez-y si vous êtes ric-rac coté licences (et que vous avez <em>vraiment</em> beaucoup de stockage).</p>
<p>Fin de la parenthèse. :hehe:</p>
<h3>Jouer avec les paramètres Yeti</h3>
<p>Cette seconde partie s’apparente plus à du partage d’expérience sur le comportement de la procédurale Yeti elle-même.</p>
<p>Détail comme ça : Assurez-vous toujours que les valeurs de <em>FPS</em> sont identiques entre Maya et Guerilla. Évidant, mais comme un TD est amené à faire du support sur plusieurs projets, ce n’est pas inutile de le rappeler. Je me suis fait avoir lors d’un débogage sur un projet parallèle qui n’avait pas les mêmes <em>FPS</em> que le projet principal. :ideenoire:</p>
<p>Passons… :slow_clap:</p>
<p>Envisageons le scénario suivant : Le <em>.fur</em> Yeti est exporté sur 2 samples (0.0, 0.2). La trajectoire est donc linéaire :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/yeti_2_samples_001.png" style="margin: 0px auto; display: table; width: 283px; height: 148px;" /></p>
<p>Si Guerilla demande 0.0, 0.1, Yeti interpole correctement :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/yeti_2_samples_002.png" style="margin: 0px auto; display: table; width: 283px; height: 148px;" /></p>
<p>En revanche, si Guerilla demande 0.0, 0.3, Yeti « stop » le mouvement :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_17_mb_yeti/yeti_2_samples_003.png" style="margin: 0px auto; display: table; width: 381px; height: 148px;" /></p>
<p>D’un côté, c’est logique, Yeti ne peut vraiment fournir l’information. Gardez ça à l’esprit si vous augmentez vos <em>shutters</em> sur un plan pour ajouter un peu de <em>motion blur</em>. et que vos poils sont en décalage de vos <em>scalps</em>.</p>
<h3>Générer des bounding box pour ses .fur</h3>
<p><em>pgYetiCacheInfo</em> est une ligne de commande qui permet d’extraire quelques informations d’un <em>.fur</em>. La plus intéressante étant la <em>bounding box</em>.</p>
<p>En effet, <a href="http://guerillarender.com/doc/2.2/TD%20Guide_Third%20Party%20Tools_Yeti.html" hreflang="en" title="Yeti Guerilla">comme le dit clairement la doc</a>, Guerilla lui-même utilise <em>pgYetiCacheInfo</em> pour extraire la <em>bounding box</em>, mais de mécanisme par défaut est tellement lourd qu’un autre système est proposé.</p>
<p>Si un fichier <em>.bbox</em> existe à coté d’un <em>.fur</em>, Guerilla lira ce dernier pour en extraire la <em>bounding box</em>. L’idée est donc de générer ces fichiers au moment (enfin juste après) de la génération des .fur. Vous pouvez faire ça en farm. Gardez tout de mème à l’esprit que, sous le capot, <em>pgYetiCacheInfo</em> va générer le système de fur au complet. Donc les machines qui s’occupent de ça doivent en avoir dans le pantalon. :banaeyouhou:</p>
<p>Vous pouvez aussi bricoler cette <em>bounding box</em> vous-même depuis Maya en prenant celles des <em>scalps</em> que vous grossissez un peu, mais sur des systèmes de fur un peu complexe, vous risquez d’en faire une trop petite et payer la note au rendu.</p>
<p>Sachez également que l’argument –samples permet, comme pour la procédurale de prendre en compte les samples donnés pour la génération de la <em>bounding box</em>.</p>
<h3>Conclusion</h3>
<p>J’espère que ce billet vous aura plus, et que vous allez, etc. etc.</p>
<p style="text-align: center;">:marioCours:</p>LOD, suite et fin v2 (les poils)urn:md5:f60266c84b48fb9f0f29810ca97b35de2019-08-17T17:52:00+02:002019-08-19T19:46:42+02:00NarannInfographie 3D - Boulotautomatisationfurguerillalodmathpoil<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_fur_v2_tn.png" style="float: left; margin: 0 1em 1em 0;" />J’espère qu’avec ce billet je vais enfin terminer cette longue et indigeste série sur les LOD. :vomit:</p>
<p>Attaquons enfin la densité des poils au plan. Ce dernier billet va être un peu technique. On va faire un peu de trigonométrie (espace caméra, toussa) et parler de <em>farm</em>.</p> <p>Si vous avez suivi <a href="https://www.yumpu.com/en/document/read/43603224/stochastic-simplification-pixar-graphics-technologies" hreflang="en" title="Stocastic Simplification">un des liens</a> de <a class="ref-post" href="https://www.fevrierdorian.com/blog/post/2019/08/10/LOD-suite-et-fin">mon premier billet</a>, vous avez peut-être remarqué que la méthode stochastique est utilisée pour diminuer la densité des poils sur <em>Ratatouille</em> à la volée, au moment du rendu, en prenant en compte la distance, le <em>motion blur</em> et le <em>DOF</em>. Je ne reviendrais pas sur les raisons pour lesquelles ce n’est plus facilement faisable de nos jours (il faut lire le billet :IFuckTheWorld: ), mais l’idée générale reste inspirante et on peut tenter de se l’approprier pour bricoler quelque chose. :dentcasse:</p>
<blockquote>
<p>Cette méthode est un peu brouillonne et les habitués de trigonométrie auront sûrement une approche plus fine du problème.</p>
</blockquote>
<p>Sur un plan, on cherche à calculer la densité et l’épaisseur optimale des poils d’un personnage sur ce plan.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_density_width_proc.png" style="margin: 0px auto; display: table; width: 310px; height: 205px;" /></p>
<p style="text-align: center;"><em>C’est ça qu’on cherche à calculer.</em></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_fur_compare.png" style="margin: 0px auto; display: table; width: 352px; height: 267px;" /></p>
<p style="text-align: center;"><em>Un même système de poil, différentes densités et épaisseurs.</em></p>
<p>Ces valeurs ne peuvent pas être animées au risque d’engendrer des inconsistances de nombre de poils entre deux échantillons temporels, ce qui mettrait le moteur dans une bien mauvaise passe au moment où ce dernier calcule les vecteurs de mouvement des poils. D’après mes tests (sous <em>Guerilla</em> et <em>Arnold</em>, je suppose que <em>Renderman</em> fait pareil) les moteurs désactivent simplement les vecteurs de mouvement du système de poil incriminé et lèvent un <em>warning</em>.</p>
<p>Il va donc falloir déterminer ces deux valeurs pour chaque système de poil <u>sur l’ensemble du plan</u>.</p>
<p>Il nous faut deux choses (suivant son pipeline, on peut utiliser de l’<em>Alembic</em> ou la combinaison <em>Rig</em> + <em>ATOM</em>) :</p>
<ul>
<li>La caméra du plan.</li>
<li>La géométrie du personnage à exporter.</li>
</ul>
<p>Une fois ces deux éléments dans notre scène, le bonheur est à notre porte. En effet, les mathématiques commencent. :baffed:</p>
<p>Pour chaque image du plan (et pour chaque <em>step</em> de <em>motion blur</em>), nous allons identifier le sommet géométrique qui soit à la fois le plus proche de la caméra et présents en son tronc (<em>frustrum</em>).</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_cam_frustum_2.png" style="margin: 0px auto; display: table; width: 259px; height: 254px;" /></p>
<p>Finalement, nous calculons le diamètre, en espace caméra, du disque projeté d’une sphère de 1 unité de diamètre.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_cam_frustum_1.png" style="margin: 0px auto; display: table; width: 287px; height: 154px;" /></p>
<p>Nous récupérons ainsi le plus grand diamètre du plan.</p>
<p>Notez que quand, comme moi, on ne sait pas gérer ce mécanisme de façon totalement mathématique ( :seSentCon: ), on peut fabriquer une sphère et projeter chaque vertex via ce bout de code (honteusement inspiré de <a href="https://gizmosandgames.com/2017/05/21/frustum-culling-in-maya/" hreflang="en" title="Frustrum culling in Maya">ce billet</a>) :</p>
<pre>
<code class="language-python">import maya.cmds as cmds
import maya.OpenMaya as om
# retrieve MObject from node
sel = om.MSelectionList()
sel.add("myCamera")
sel.add("myGeo")
cam_dag_path = om.MDagPath()
geo_dag_path = om.MDagPath()
sel.getDagPath(0, cam_dag_path)
sel.getDagPath(1, geo_dag_path)
# generate projection matrix
cam_inv_world_mtx = cam_dag_path.inclusiveMatrixInverse()
fn_cam = om.MFnCamera(cam_dag_path)
proj_mtx = fn_cam.projectionMatrix()
proj_mtx = om.MMatrix(proj_mtx.matrix) # convert MFloatMatrix te MMatrix
post_proj_mtx = fn_cam.postProjectionMatrix()
post_proj_mtx = om.MMatrix(post_proj_mtx.matrix)
view_proj_mtx = cam_inv_world_mtx * proj_mtx * post_proj_mtx
# iterate over each geo vertex
vtx_iter = om.MItGeometry(geo_dag_path)
# init 2D bounding box values
proj_x_min = 999999.0
proj_x_max = -999999.0
proj_y_min = 999999.0
proj_y_max = -999999.0
while not vtx_iter.isDone():
# get vertex position in world space
geo_pos = vtx_iter.position(om.MSpace.kWorld)
# compute projected position on cam
proj_pos = geo_pos * view_proj_mtx
proj_pos_x = (proj_pos.x / proj_pos.w) / 2.0 + 0.5
proj_pos_y = (proj_pos.y / proj_pos.w) / 2.0 + 0.5
proj_x_min = min(proj_pos_x, proj_x_min)
proj_x_max = max(proj_pos_x, proj_x_max)
proj_y_min = min(proj_pos_y, proj_y_min)
proj_y_max = max(proj_pos_y, proj_y_max)
vtx_iter.next()</code></pre>
<p>À ce stade, on a, pour un plan et un personnage, un diamètre de la partie la plus proche. :sourit:</p>
<p>Mais vous vous doutez qu’il va falloir la comparer avec une autre valeur. Cette valeur, c’est le diamètre du disque projeté de la sphère sur une caméra donnée quand l’asset est a une densité de 1 (densité nominale : 100 % de poils).</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_cam_frustum_3.png" style="margin: 0px auto; display: table; width: 493px; height: 326px; text-align: center;" /></p>
<p style="text-align: center;"><em>Ce diamètre se récupère au niveau de la modé/lookdev.</em></p>
<p>On se retrouve donc avec deux diamètres. On fait ensuite un simple ratio qui peut être utilisé pour calculer la valeur de densité.</p>
<p>Super : Reste l’épaisseur : En admettant que <em>facteur</em> soit la valeur de densité (1.0 pour 100 %, 0.5 pour 50 %, etc.), j’applique la formule suivante :</p>
<p>Épaisseur nominale * (1 + (−ln(facteur)÷ln(2)))^2</p>
<blockquote>
<p>Neuh, que, nan, mais, de quoi ? Mais… Mais… Mais… Dorian ! Ça n’a aucun putain d’sens !!!</p>
</blockquote>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_je_sais.png" style="margin: 0px auto; display: table; width: 500px; height: 500px;" /></p>
<blockquote>
<p>T’es mauvais ! Je savais bien qu’il n’y avait pas que dans les yeux que tu avais de la merde !!!</p>
</blockquote>
<p>Je ne vais pas risquer de me vautrer en démonstrations. Les plus matheux d’entre vous auront peut-être remarqué la présence de X = ln(A)/ln(2) pour résoudre l’équation 2<sup>x </sup>= A. L’idée est de calculer une puissance, plus d’information <a href="https://www.ilemaths.net/sujet-commen-t-calculer-la-puissance-d-un-nombre-109026.html" hreflang="fr">ici</a>.</p>
<p>En gros j’avais remarqué que la formule de calcul d’épaisseur suivante fonctionnait bien :</p>
<ul>
<li>Exposant à 7 (100 % de densité), Épaisseur nominale * <strong>1</strong>^2 = 0.002</li>
<li>Exposant à 6 (50 % de densité), Épaisseur nominale * <strong>2</strong>^2 = 0.008</li>
<li>Exposant à 5 (25 % de densité), Épaisseur nominale * <strong>3</strong>^2 = 0.018</li>
<li>Exposant à 4 (12.5 % de densité), Épaisseur nominale * <strong>4</strong>^2 = 0.032</li>
<li>Exposant à 3 (6.25 % de densité), Épaisseur nominale * <strong>5</strong>^2 = 0.05</li>
<li>Exposant à 2 (3.125 % de densité), Épaisseur nominale * <strong>6</strong>^2 = 0.072</li>
<li>Exposant à 1 (… % de densité), Épaisseur nominale * <strong>7</strong>^2 = 0.098</li>
</ul>
<p>Pour info, à chaque fois qu’on baisse l’<em>Exponent</em> de un, on divise la densité par deux.</p>
<p>Il fallait donc que je réussisse à trouver les valeurs en gras pour chaque facteur de densité. En gros, il faut trouver une formule pour convertir 0.03125 en 6… J’avoue ne pas avoir trop le niveau et je ne me fais aucun doute sur le fait qu’une méthode plus sérieuse existe. Le graph est <a href="https://www.desmos.com/calculator/gmg1bzv6ix" hreflang="en" title="LOD Fur density graph">là</a> pour ceux qui veulent faire mumuse.</p>
<p>Il n’empêche que je voudrais quand même vous montrer les résultats en rendu. :redface:</p>
<p>On part sur ces valeurs :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_guerilla_proc_values.png" style="margin: 0px auto; display: table; width: 335px; height: 272px;" /></p>
<p>Et le rendu de référence :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_test_density_guerilla_001.jpg" style="margin: 0px auto; display: table; width: 341px; height: 256px;" /></p>
<p style="text-align: center;"><em>Density 50, Exponent 7, Width 0.002</em></p>
<p>Et en commence à descendre :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_test_density_guerilla_002.jpg" style="margin: 0px auto; display: table; width: 341px; height: 256px;" /></p>
<p style="text-align: center;"><em>Density 50, Exponent 6, Width 0.008</em></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_test_density_guerilla_003.jpg" style="margin: 0px auto; display: table; width: 341px; height: 256px;" /></p>
<p style="text-align: center;"><em>Density 50, Exponent 5, Width 0.018</em></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_test_density_guerilla_004.jpg" style="margin: 0px auto; display: table; width: 341px; height: 256px;" /></p>
<p style="text-align: center;"><em>Density 50, Exponent 4, Width 0.032</em></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_test_density_guerilla_005.jpg" style="margin: 0px auto; display: table; width: 341px; height: 256px;" /></p>
<p style="text-align: center;"><em>Density 50, Exponent 3, Width 0.05</em></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_test_density_guerilla_006.jpg" style="margin: 0px auto; display: table; width: 341px; height: 256px;" /></p>
<p style="text-align: center;"><em>Density 50, Exponent 2, Width 0.072</em></p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_test_density_guerilla_007.jpg" style="margin: 0px auto; display: table; width: 341px; height: 256px;" /></p>
<p style="text-align: center;"><em>Density 50, Exponent 1, Width 0.098</em></p>
<blockquote>
<p>« Le monsieur fait mine que ça marche, mais il dit qu’il sait pas comment… » :neutral:</p>
</blockquote>
<h3>Mouaih…</h3>
<p>Comme vous vous en rendez compte, cette méthode est limitée (pas de prise en compte du <em>DOF</em>, ni du <em>motion blur</em>, ni de la réfraction), mais avant de crier au scandale, il faut bien comprendre qu’on ne peut, de toutes façons, pas arriver à un résultat parfait sans que ce soit le moteur qui nous donne lui-même les informations. C’est sûrement faisable en calculant un mini rendu d’une AOV de l’aire de la surface au pixel combiné à une AOV de <em>motion vector</em>, mais je n’ai jamais mis cela en place. :seSentCon:</p>
<p>Le but est d’arriver à proposer une valeur au graphiste qu’il pourra, de toutes façons, modifier si les besoins du plan l’impose. Il y a fort à parier que la plupart des plans sur lesquels vous travaillerez n’auront rien d’exotique et avoir une valeur cohérente sous le coude vous évite de perdre du temps en essais/erreurs. :laClasse:</p>
<h3>Toujours plus loin ?</h3>
<p>On doit pouvoir améliorer ce mécanisme. Par exemple, si votre logiciel de <em>lookdev</em> supporte les valeurs animées, vous pourriez extraire les valeurs par <em>frame</em> (en regroupant les <em>steps</em>)et animer la densité. Je n’ai jamais testé, je ne peux donc pas vous dire si la « saute » de densité à chaque frame est visible à l’écran. :reflechi:</p>
<h3>Conclusion</h3>
<p>Les LOD en début de prod :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_napoleon.png" style="margin: 0px auto; display: table; width: 825px; height: 437px;" /></p>
<p>Vous, à deux semaines de la livraison :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/Berezyna.jpg" style="margin: 0px auto; display: table; width: 800px; height: 463px;" /></p>
<p>J’espère que cette suite de billets, bien trop verbeux, j’en ai conscience, vous auront tout de même intéressés et élargie vos horizons techniques sur le sujet.</p>
<p style="text-align: center;">:marioCours:</p>LOD, suite et fin (automatique VS manuel)urn:md5:0ae280c172085b93cc6a8c9e456e32852019-08-10T21:25:00+02:002019-08-17T16:59:57+02:00NarannInfographie 3D - Boulotfurguerillahairlod<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_suite_tn.png" style="float: left; margin: 0 1em 1em 0;" />Je me suis rendu compte que j’ai oublié de parler de deux-trois choses dans mon billet précédent et je suis parfois passé trop rapidement sur certains points.</p>
<p>Je vous propose un billet d’appoint pour corriger le tire, ce sujet aux branchements infinis le mérite bien. :sourit:</p>
<p>Aujourd’hui, je comparerai l’approche automatique et semi-manuelle pour gérer les LOD géométriques, j’aborderai la relation entre le département de <em>modeling</em> et de <em>lookdev</em> puis on finira par les poils.</p> <h3>Système automatique</h3>
<h4>Avant propos</h4>
<p>Si c’est la production ou la supervision qui met le sujet sur le tapis, il est probable que ce soit avec l’idée d’en faire un truc « automatique et transparent pour l’utilisateur », ce qui, ironiquement, correspond à la définition d’un truc « magique et opaque ».</p>
<p>Il va de soi que plus le projet est ambitieux, plus l’automatisation est importante, personne ne veut voir ses graphistes passer leurs journées à faire des opérations rébarbatives à la main. Et pourtant…</p>
<p>En pratique l’ajout de LOD géométriques dans vos projets veut dire que vous faites intervenir, dans vos tuyaux/scènes, une « particularité » de vos assets. Ainsi, même si vous automatisez la gestion des LOD, ces derniers ne disparaissent pas, vous ne les voyez simplement plus, jusqu’à ce qu’ils posent problèmes. Vous me répondriez : Pourquoi diable voulez-vous qu’ils posent problèmes, car leur gestion est « automatique et transparent pour l’utilisateur » ? :bete:</p>
<p>Cette perception nie la nuance. En effet, on ne fabrique pas des plans comme on fabrique des objets dont l’essence est d’être identique (agroalimentaire, informatique, jouets, etc.). La « nature » des plans varient. Par nature, j’entends ce qui en détermine sa fabrication ; un plan de visage a ses propres problématiques de fabrication (<em>SSS</em> des yeux/cou/vêtements, cheveux, <em>Rim light</em>, arrière-plan, etc.), de même qu’un plan de décor (nombre d’objets, <em>lookdev</em> d’ensemble, <em>matte painting</em>, etc.). Les deux ne se fabriquent pas de la même manière et la pertinence d’utiliser des LOD ou non en est très dépendante. D’une manière générale, plus on pousse la qualité vers le haut, plus des contrastes assez net s’opèrent dans la façon dont les plans sont fabriqués.</p>
<p>On commence à comprendre pourquoi les LOD, prétendument automatiques, reviennent toujours sur le tapis. Les graphistes travaillent avec un ensemble de choses simples (Alembic d’animation, assignation de <em>lookdev</em>, surcharge des positions/attributs, etc.) qui s’entremêlent, formant un tout complexe. Le rendu est le plus touché car c’est le département qui rassemble tout. Si la gestion des LOD est automatique et qu’ils ne sont donc pas explicitement « choisis » par les graphistes, ces derniers devront les ajouter aux potentielles raisons des problèmes qu’ils ont sous les yeux.</p>
<p>La production (et dans une certaine mesure, la supervision) a parfois du mal à juger de cet effet indirect.</p>
<p>Après ce (trop) long aparté, rentrons dans le vif du sujet : À quoi faut-il penser quand on automatise la gestion des LOD ? :reflechi:</p>
<h4>Concrètement</h4>
<p>Je pars du principe que vous êtes un TD à qui on a refilé la patate chaude et que ni le <em>modeling</em>, ni le <em>lookdev</em> ne veut intervenir (« transparent pour l’utilisateur » toussa).</p>
<p>L’approche naïve consiste à prendre toutes les géométries de sa scène et de leur appliquer un gros <em>Reduce</em>. Le résultat est très moche, mais a priori, c’est pas le sujet. :vomit:</p>
<p>Comme dit dans mon billet précédent, il faut s’assurer que les UV de la géométrie réduite ne soit pas détruits et correspondent à la géométrie originale.</p>
<p>Le plus gros problème c’est taille de la hiérarchie. Si elle est trop grosse, vous pourriez vous retrouver avec des milliers d’objets qui ne font chacun que deux ou trois polygones. Suivant vos méthodes de travail il est fort probable qu’une fois instancié des milliers de fois, vos logiciels de <em>lookdev</em> pédalent à traverser la hiérarchique de vos LOD.</p>
<p>On pourrait être tenté de supprimer les petits objets (petites <em>bounding boxes</em>) mais si c’est ce qui compose l’ensemble de votre asset (e.g. un arbre, une plante), vous risqueriez d’en supprimer une partie visible importante et vous retrouver avec un asset tout nu ce qui peut mettre le modeleur mal à l’aise devant son superviseur :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/20190807_pas_de_talent.png" style="margin: 0px auto; display: table;" /></p>
<p>Si vos superviseurs <em>modeling</em> et <em>lookdev</em> sont de connards qui ne veulent rien entendre, il vaut mieux éviter les LOD. Si vous ne pouvez pas intervenir sur cette décision, vous êtes foutu. Vous pouvez mettre à jour votre <em>Linked’In</em> et commencer à postuler ailleurs, car les départements rendus vont pleurer du sang.</p>
<p>Mais on est dans le vrai monde, celui des Bisounours et des glaces à la vanille, celui ou les superviseurs comprennent les intérêts des solutions que vous leur proposez, pour peu que vous leur parliez lentement.</p>
<p>Ce qui m’amène au second point : La méthode semi-manuelle. :sauteJoie:</p>
<h3>Système semi-manuel</h3>
<p>Au passage, appelez-la semi-<em>automatique</em>, c’est plus facile à vendre à la production… :trollface:</p>
<h4>Aider les graphistes par « contrat »</h4>
<p>Cette approche vise à résoudre les problèmes précédents par « contrat » avec les graphistes. Dans le cas de la hiérarchie trop dense, vous pouvez proposer la chose suivante à votre graphiste :</p>
<p>« Si ton groupe de nœuds porte le suffixe « _combined », le contenu sera combiné à l’export et le suffixe sera supprimé. »</p>
<p>En gros, vous garantissez que la hiérarchie suivante :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_suite_maya_hierarchy_1.png" style="margin: 0 auto; display: table;" /></p>
<p>Sera converti en ça :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_suite_maya_hierarchy_2.png" style="margin: 0 auto; display: table;" /></p>
<p>La confiance n’excluant pas le contrôle, vous n’aurez qu’à ajouter un <em>sanity check</em> s’assurant que chaque groupe suffixé « _combined » ne contient que des géométries et sans attributs, ces derniers étant perdu lors du <em>combine</em>.</p>
<p>Cette approche à l’avantage de laisser le graphiste contrôler la livraison de son travail par simple renommage des nœuds, directement dans Maya et c’est le code qui s’occupe de la partie rébarbative, le sanity check s’étant assuré qu’il ne puisse pas y avoir de surprise.</p>
<p>Il y a fort à parier que les personnes au <em>lookdev</em> y trouveront un intérêt en proposant des modifications au <em>modeling</em>, voir, en les faisant elles-mêmes. :laClasse:</p>
<p>Cette approche par contrat implique de rester aussi simple que possible. La raison est que le graphiste peut se débrouiller tout seul en cas de problème sans que les outils d’automatisation ne soient un obstacle à la compréhension. Un réflexe que peuvent avoir les TD qui ne sont pas graphistes de formation est de trouver dommage de limiter le graphiste à ses propres compétences, là ou des supers outils, tout d’héritage de classes vêtus, pourraient l’aider à une échelle supra luminique. C’est vrai, jusqu’à ce que les TD deviennent les seules personnes à comprendre le travail des graphistes. Si vous ne laissez qu’un contrôle partiel à ce genre de contrat, vous « prenez » l’autonomie du graphiste sur des taches qui devraient rester sous sa responsabilité et que, par conséquent, il doit parfaitement comprendre, en vu de l’assimiler en tant que méthode. Vous voyez que vous êtes sur la bonne voie quand les graphistes développent de nouvelle façon de travailler tout seul, en s’appuyant sur ces « contrats ». Pour cela, il faut que ces derniers soient simples. :petrus:</p>
<p>Dans un pipeline, certaines choses sont humainement infaisables, c’est là-dessus que votre cerveau doit être créatif. Accélérer le travail du graphiste implique de se mettre sous sa perspective. Ce sont des erreurs d’estimation qui peuvent coûter cher. :neutral:</p>
<p>Il est plus facile de faire tout ça via des jobs d’export Alembic, car vous n’avez pas à mettre une logique de « retour à l’état précédent » une fois la scène modifiée ; vous ouvrez, vous modifiez, vous exportez, vous quittez.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_pwalone.png" style="margin: 0 auto; display: table;" /></p>
<p>Mais ne nous arrêtons pas en si bon chemin…</p>
<h4>Les <em>close up</em></h4>
<p>Au même titre qu’une version basse définition, il peut arriver que des projets nécessitent des versions « high » (ou <em>close up</em>). Les toits des bâtiments par exemple. Vous pouvez « résoudre » la hiérarchie du graphiste suivante :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_closeup.png" style="margin: 0 auto; display: table;" /></p>
<p>Vous pouvez procéder de plusieurs façons. La méthode bourrine consiste à tout exporter dans un seul Alembic. C’est l’assembleur (ou le graphiste au rendu) qui s’occupera de cacher l’un ou l’autre au besoin (via une petite expression par exemple), la version <em>close up</em> est caché par défaut.</p>
<p>Disons que c’est mieux que rien, mais ce n’est pas comme ça que l’on brille en société.</p>
<p>En effet, il est dommage de se trimbaler de la géométrie <em>close up</em> dans tout le pipeline, même si cette dernière est cachée. Il nous faut donc procéder à deux exports. :reflechi:</p>
<p>Vous pouvez proposer un contrat en deux clauses :</p>
<ul>
<li>« Durant un export standard, tout groupe contenant le suffixe « _closeUp » est supprimé. » (on supprime les géométries de <em>close up</em> de l’export de base)</li>
<li>« Durant un export <em>close up</em>, toute géométrie ayant un homologue portant le suffixe « _closeUp » est supprimé. » (non, non, je n’ai rien oublié, lisez la suite)</li>
</ul>
<p>Le résultât d’un export standard sera :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_methode_2_standard.png" style="margin: 0 auto; display: table;" /></p>
<p>En celui d’un export <em>close up</em> sera :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_methode_2_closeUp.png" style="margin: 0 auto; display: table;" /></p>
<p>Pourquoi ne pas avoir renommé « toiture_closeUp » en « toiture » ? Ce serait, en effet, plus logique, mais, dépendant de votre façon de gérer les assignations, cela risquerait de couper l’herbe sous les pieds du <em>lookdev</em> qui peut avoir besoin de faire la distinction entre l’objet « toiture » et le contenu de « toiture_closeUp ».</p>
<p>Mais ça, c’est quand votre logiciel de <em>lookdev</em> c’est du caca (ou que vous ne savez pas l’utiliser :gniarkgniark: ).</p>
<p>Vous pouvez contourner ce problème en s’appuyant sur les rangées des UDIM :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_toiture_udim.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/.lod_toiture_udim_m.png" style="margin: 0 auto; display: table;" /></a></p>
<p>Ainsi, vous gardez le contrôle via textures, indépendamment de la hiérarchie. Gardez toutefois à l’esprit que compenser l’accès à la géométrie par des UDIM implique que la moindre <em>retake</em> de <em>lookdev</em> nécessitera un export de texture, ce qui amène à des situations qui défient l’entendement : Là ou diminuer le <em>spec</em> ou la <em>diffuse</em> pourrait se faire en un <em>override</em> sur l’objet en question, vous devez ouvrir votre projet Mari/Designer/Toshop, faire la modification et exporter avant de pouvoir tester. La limite entre une gestion par <em>override</em> et une gestion 100 % texture est un sujet à part qui mériterait son propre billet, mais je vais fermer la parenthèse. :dentcasse:</p>
<p>Si votre logiciel de <em>lookdev</em> vous permet de faire la distinction d’assignation entre « toiture » et « toiture|poutre17 », alors il ne faut pas se priver et faire en sorte que la version <em>close up</em> prenne la place de la hiérarchie originale :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_methode_2_closeUp1.png" style="margin: 0 auto; display: table;" /></p>
<p>Comme ça, si le rendu fait une expression pour modifier « toiture », cela fonctionnera, que le modèle soit en <em>close up</em> ou non.</p>
<p>Mais, bordel de m* :injures: , on peut aller encore plus loin !!! :grenadelauncher:</p>
<h4>Le <em>merge</em> de hiérarchie</h4>
<p>Si vous avez Katana, vous pouvez opérer un <em>merge</em> exclusif entre deux hiérarchies. Si <em>A</em> est la hiérarchie <em>standard</em> et <em>B</em> la hiérarchie <em>close up</em>, vous pouvez demander à Katana de « superposer » les hiérarchies en choisissant la hiérarchie <em>B</em> en cas de conflit.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_merge_hierarchy.png" style="margin: 0 auto; display: table;" /></p>
<p style="text-align: center;"><em>À gauche, arbre.abc, au milieu, arbre_closeup.abc et à droite, la hiérarchie résolu.</em></p>
<p>Ça veut dire que votre Alembic de <em>close up</em> ne contient plus que les nœuds ayant précédemment le suffixe<em> </em>« _closeUp », sans les géométries <em>standards</em>, car la hiérarchie de <em>close up</em> est tout le temps combiné (<em>mergé</em>) à la hiérarchie <em>standard</em>.</p>
<p>Je ne sais plus du tout comment on fait ça par contre, va falloir me croire sur parole. :slow_clap:</p>
<p>Autre point important qui rejoint le sujet des <em>combines</em>,</p>
<p>Sans aller jusque-là, je pense avoir pas mal poussé le truc pour vous donner matière à réflexion. L’objectif étant toujours de rendre les graphistes autonomes tout en leur proposant des « outils » (ce n’est peut-être pas le bon mot) qui correspondent à ce qu’ils font.</p>
<h3>Dans tous les cas</h3>
<p>Quelle que soit la méthode utilisée, tout ceci demande un travail supplémentaire, et avec les moteurs de rendu actuel, ce n’est pas toujours justifié.</p>
<p>Faire l’économie de la vérification de ce qui sort des tuyaux peut se payer cher plus tard. Il vaut mieux vérifier en amont (via tournettes) si les données générées sont propres et « rendent » bien, mais c’est encore un temps à passer à regarder des assets. Les LOD sont des objets internes à la fabrication. Jouez pas au con et ne présentez pas ça au réal…</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_upload_shotgun.png" style="margin: 0 auto; display: table;" /></p>
<h3>Note sur le transfert d’UV</h3>
<p>Là, on passe en manuelle ! Donc nous allons parler de techniques à ajuster au cas par cas.</p>
<p>C’est une technique a priori un peu bancale mais c’est parce que je l’ai vu fonctionner que je vous la partage.</p>
<p>Des studios font faire les UV directement au <em>lookdev</em> au motif, tout à fait justifié, qu’il est le premier concerné par le résultat. Dès lors, vous avez un croisement entre les données générées par vos graphistes et l’ordre dans lesquels vous les produisez : Si le <em>lookdev</em> est censé faire les UV, mais que les LOD sont déjà fait par le <em>modeling</em>, comment assurer que les UV des LOD sont cohérents ?</p>
<p>Par le transfert d’UV pardi ! :banaeyouhou:</p>
<p>Je suis sûr qu’il y a des cas où ça foire (les arbres), mais les résultats sont convaincants si la géométrie des modèles basses définitions correspond à leur homologue en hautes définitions.</p>
<p>Ça nécessite quelques nuances dans l’organisation : La modé ne fait aucun UV, le <em>lookdev</em> qui les prends tous à sa charge. Il est important que la production ait cette information car ça nécessite un glissement des ressources vers le <em>lookdev</em>, ces derniers auront plus de travail par asset et mettront plus de temps à travailler. En revanche vous pouvez séparer le travail intelligemment en laissant le <em>lookdev</em> faire les UV des assets compliqués (personnages, bâtiments de premier plan, etc.) et laisser le <em>modeling</em> faire les UV d’asset de second plan (<em>props</em>, etc.).</p>
<p>Vous en voulez encore ? :gniarkgniark:</p>
<h3>Le TriPlanar</h3>
<p>Celle-là je la sors de Guerilla, mais je suis sûr qu’on peut la déporter sur d’autres moteurs. Le principe est de faire plusieurs rendus de trois caméras (face, côté, dessus). Pour chaque rendu dont on exporte l’albédo, le masque le spéculaire et surtout, la normale. Guerilla permet de faire ça assez simplement (doc <a href="http://guerillarender.com/doc/2.1/User Guide_Rendering_Light Path Expression.html" hreflang="en" title="2.1 User Guide Rendering Light Path Expression">ici</a>).</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_suite_guerilla_aov.png" style="margin: 0 auto; display: table;" /></p>
<p>On fait ensuite un <em>shader</em> très simple (Couleur de diffuse + une couche de spéculaire grossière + la normale), une géométrie très simple (des cubes + un toit = 6 faces) et on a un asset très léger qui a un peu de gueule quand il y en a (vraiment) énormément. On est très proche du jeu vidéo (peu de poly, tout dans la texture).</p>
<p>Cette technique marche assez bien sur les bâtiments lointains (genre l’horizon d’un Paris…). Vous devriez pouvoir étendre le principe aux arbres (la vielle technique des plans superposées), mais je n’ai jamais testé.</p>
<h3>Les transitions</h3>
<p>Si vous utilisez un système qui permet de permuter un LOD suivant la distance et la caméra, et que vous faites ça sur une foule ou un amas (une forêt), il est probable que ce passage d’un LOD à l’autre entraîne un « saut » inesthétique entre les changements de LOD. :perplex:</p>
<p>Si vous avez la main sur le mécanisme de distribution, vous pouvez définir une zone, assez large dans laquelle les LOD sont mélangés au prorata de la distance. Admettons que vous voulez que la transition se fasse entre 100 m et 200 m. Pour un arbre à X m, vous calculez un <em>random</em> (entre zéro et un) multiplié par le ratio de X par rapport à 100 m et 200 m :</p>
<pre>
<code class="language-python">ratio = (X-100)/(200-100)
if ratio <= 0.0:
# high resolution
elif ratio > 1.0:
# low resolution
else:
# pseudo random choice
if random * ratio <= 0.5:
# high resolution
else:
# low resolution</code></pre>
<p>La documentation <a href="https://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/Nodes/setRange.html" hreflang="en" title="Maya Node setRange">du nœud le setRange</a>. Je la regarde à chaque fois que j’ai un doute sur l’équation.</p>
<p>Faire une transition de LOD implique tellement de choses qu’il vaut mieux s’en passer autant que possible. Si votre plan est animé ? Tentez de gérer le tout en haute définition. En effet, le grain que vous souhaitez chasser via vos LOD (c’est le but après tout) risque de ne jamais être visible du fait du mouvement de caméra.</p>
<p>Si vous pouvez les éviter, alors faites-le. :papi:</p>
<h3>Subdivision et displacement</h3>
<p>C’est tellement basique, mais je soupçonne que les logiciels permettant de gérer des <em>lookdev</em> en masse et le fait que les <em>path tracer</em> mangent du poly nous pousse à ne trop penser à la subdivision est au <em>displacement</em>. Pourtant, ça ne coûte pas grand-chose de couper sa <em>subdivision</em> et son <em>displacement</em> sur tout ce qui est éloigné ou peu visible de la caméra. Vous pouvez chercher toutes les méthodes de LOD du monde, si vous ne faites pas ça, ça ne sert à rien. :redface:</p>
<h3>Les poils</h3>
<p>Et nous arrivons à la raison pour laquelle je voulais vous faire ce second billet : J’avais oublié de vous parler des poils (fur/hair/Yeti/XGen, j’utiliserais le mot <em>poil</em> pour désigner cet ensemble). J’y vois deux gros cas de figures : Les poils des personnages principaux et les poils « statiques » (par arbre, par buisson, etc.).</p>
<h4>Les poils « statiques »</h4>
<p>Ils sont moins fréquents, mais le plus simple à gérer, car vous les considérez comme de la modé : Vous les générez une seule fois pour votre asset et vous l’instanciez comme une géométrie. Comme sortir un LOD de poil est assez facile, vous pouvez générer plusieurs versions et laisser les graphistes afficher l’une ou l’autre, l’idée étant d’éviter de générer ces poils par plan et par asset.</p>
<p>J’ai vu des graphistes utiliser cette méthode pour des buissons et arbustes d’un décor visible sur peu de plans. Plutôt que de s’embêter à faire une modélisation dédiée (quand on sait à quel point placer des feuilles est casse-pied), le superviseur a proposé de faire une géo de branche, instancié de gros poils dessus, cuire ça (via les <em>ghostdata</em> de Guerilla) puis l’instancier. C’est malin et terriblement efficace. Car ils ont pu faire plusieurs variations de formes de buisson assez différentes (rendu totalement différente par les couleurs du <em>lookdev</em>), mettre ça des milliers de fois dans un décor et ça tournait sans trop de difficultés.</p>
<p>Je vais aussi vous parler d’un problème difficile à déboguer. Il est peu probable que cela vous arrive, mais ce dernier ne sautant pas aux yeux, je trouve intéressant de vous en parler : Suivant le système de poil que vous utilisez (ici, Yeti, mais je pense que d’autres pourraient avoir le même problème), il est possible de l’utiliser pour générer la végétation de tout un décor. Votre moteur peut se faire avoir en assignant une <em>bounding box</em> unique à tout ce que la procédurale génère. En pratique, l’impact est négligeable, car les moteurs sont rapides. Mais il est toujours difficile de gérer tout un décor et les graphistes peuvent être tenté de faire leur dispersion d’éléments en couches (cailloux au sol, vielles branches, buissons, arbustes et enfin, arbres), voir par section du décor. Si vous avez six couches, vous avez six <em>bounding box</em> de la taille du décor qui vont être questionné inutilement pour chaque rayon. Et là ça commence à se sentir sur les performances.</p>
<p>Encore une fois, il est peu probable que vous n’ayez jamais à gérer un tel problème, mais c’est aussi un rappel à garder ses <em>bounding box</em> cohérentes.</p>
<h4>Les poils « dynamiques »</h4>
<p>Voila pour les poils statiques. Les poils dynamiques sont plus compliqués à gérer. Compliqués parce qu’ils sont souvent sur les personnages principaux, ce qui en nécessite beaucoup, à cela s’ajoute un <em>shading</em> complexe. Ces plans rentrent dans la <em>farm</em> comme un barbare dans un couvent de nonnes.</p>
<p>Je pense qu’il n’y a pas de solutions qui marchent à tous les coups, mais on peut s’éviter du sang et des larmes en gardant quelques réflexes dont je vais essayer de faire le tour.</p>
<p>Ce qui prend de la mémoire, c’est la géométrie, mais il est important de ne pas s’arrêter au nombre de poil, car la complexité de chacun peut drastiquement peser dans la balance.</p>
<pre>
<code>Pour chaque poil:
Pour chaque point par poil (3 minimums pour une courbe):
la position (3 float) * step de motion blur (2 minimums)
la couleur (3 float)</code></pre>
<p>Il est difficile de donner une équation parfaite, car les moteurs ont différentes stratégies que vous pouvez, plus ou moins, contrôler. Par exemple, vous pouvez (ou non) avoir des <em>step</em> de <em>motion blur</em> pour la couleur, voir ne pas avoir de couleur du tout, ou une seule couleur par poil, etc.</p>
<p>De plus, vous pouvez activer la compression ce qui influe encore sur le résultat. Pas de secret, il faut tester.</p>
<p>Pour ce qui est du temps de rendu, c’est le <em>shading</em> qui est le plus influant et autant on dispose de marge de manœuvre avec la géométrie (couleur, <em>step</em> de <em>motion blur</em>), autant le <em>shading</em> est plus capricieux car visible et toute modification visuelle risque d’entraîner des discussions difficiles. Si vous êtes superviseur, vous avez un rôle à jouer. Ne laissez pas vos graphistes les gérer seul, car ils peuvent baisser les bras et vous prenez le risque de partir en production avec des choses inrendables.</p>
<p>En vrac :</p>
<ul>
<li>La transparence coûte cher. Il vaut mieux biaisé en montant le nombre de poil que l’activer.</li>
<li>Certains modèles d’illumination fonctionnement mieux avec les poils longs (cheveux) que les poils courts. Lisez les détails d’implémentation dans la documentation, parfois votre moteur propose d’autres modes.</li>
<li>Vos poils ne pardonneront pas les inconsistances d’éclairage de vos scènes. Si votre <em>lighting</em> fait n’importe quoi, il y a fort à parier que vos poils y réagiront plus vivement que le reste des matériaux de votre scène.</li>
<li>Et réciproquement, les bricolages de <em>trace set</em> peuvent être plus difficiles à généraliser.</li>
<li>Éviter le <em>SSS</em> sous les poils, suivant vos optimisations, il est possible qui le calcul du <em>SSS</em> ne prennent pas en compte l’ombrage des poils, ces derniers étant supprimé du <em>trace set</em> de <em>SSS</em>. Vous avez le risque qu’un sample traverse les poils et renvoi un calcul d’illumination de la peau complètement biaisé qui formera une mouche.</li>
<li>Les <em>shader</em> de poil <a href="https://benedikt-bitterli.me/pchfm/" hreflang="en" title="A Practical and Controllable Hair and Fur Model for Production Path Tracing">sont</a> <a href="https://www.pbrt.org/hair.pdf" hreflang="en" title="THE IMPLEMENTATION OF AHAIR SCATTERING MODEL">complexes</a>, évitez de mettre des textures pour tout et n’importe quoi, cela rend les problèmes d’illumination difficile à déboguer.</li>
<li>Suivez les conseils de la documentation de votre moteur qui donne souvent de précieuses informations. Vous devez comprendre chaque paramètre et les privilégier.</li>
<li>Faites des tournettes et ne laissez rien passer. Les tournettes ont souvent des éclairages très simple, si petit artefact apparrait, il est probable que vous ayez à vous le coltiner toute la production.</li>
<li>Les env ball, c’est la vie, ne l’oubliez pas.</li>
<li>Lisez la putain de doc ! :RTFM:</li>
</ul>
<p>D’une manière générale, vous devez maîtriser les <em>shaders</em> que vous utilisez en production, c’est encore plus vrai en ce qui concerne les <em>shader</em> de poil pour éviter de s’arracher les cheveux.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_10_lod_suite_fin/lod_lol.png" style="margin: 0 auto; display: table;" /></p>
<p>Croyez-le ou non : J’ai créé ce second billet avec l’ambition de vous parler de l’automatisation de la densité des poils au plan, mais le fil de mes développements lui ont donné une taille conséquente et ce sujet me semble suffisamment complexe pour mériter son propre billet, je vais donc m’arrêter là et je vous dis à bientôt !</p>Aperçu du concept de LODurn:md5:12692765fef9efd58850db9695ed723a2019-08-04T16:44:00+02:002019-08-07T18:56:20+02:00NarannInfographie 3D - Boulotguerillalodmayarendu<p><img alt="lod_jeu_de_mot" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/elle_aude.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Bonjours, dans ce billet je vous propose de faire le tour de ce qu’on entend par LOD (<em>Level Of Detail</em>), en quoi ça consiste, à quoi ça sert, quand faut-il l’utiliser et quand vaut-il mieux s’en éloigner.</p>
<p>Vous vous rendrez compte que derrière ce concept simple se cache des réalités techniques assez nuancées. :reflechi:</p> <p>La question des LOD est récurrente dans les différentes disciplines liées à l’informatique graphique. Le principe est de décomposer la complexité de ce qu’on souhaite visualiser (souvent, de la géométrie) dans le but d’accélérer son calcul/affichage.</p>
<p>Et comme ce concept peu sembler simple à comprendre, il peut apparaître comme une solution séduisante quand tes rendus n’avancent pas et que ta prod prend l’eau :</p>
<p><img alt="Fais des LOD" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/.faire_des_lod_m.png" style="margin: 0px auto; display: table;" /></p>
<h3>Les différents types de LOD</h3>
<p>Le type de LOD le plus connu est la diminution de la géométrie de l’objet.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/.lod_visage_maya_m.png" style="margin: 0px auto; display: table;" /></p>
<p>Ce mécanisme est très utilisé en jeu vidéo où l’on privilégiera plutôt des grosses textures de <em>normal map</em>s pour l’ajout de détails plutôt que des triangles. En effet, moins il y a de triangles, plus la <a href="https://fr.wikipedia.org/wiki/Rast%C3%A9risation" hreflang="fr" title="Rastérisation">rastérisation</a> est rapide. Les performances des GPU tombent également quand la taille du triangle à calculer est plus petite que la taille du pixel.</p>
<p>Cette méthode oblige le stockage d’une seconde géométrie (voir plus) en mémoire, là ou stocker des milliers d’instances d’arbres en haute définition ne pose plus de problèmes à nos moteurs. C’est un énorme désavantage, car le <em>switch</em> entre la version haute et basse géométrie doit se faire avant le rendu pour pouvoir être efficace.</p>
<p>Il y a des dizaines de façons de gérer ce type de LOD, la « bonne » va grandement dépendre du degré de flexibilité requis par la production et il est difficile de rester générique quant aux solutions à proposer à ce problème.</p>
<h3>Le <em>shading</em></h3>
<p>Une autre façon de faire du LOD consiste à diminuer le <em>shading</em> en fonction de la distance et/ou du nombre d’objet à rendre. Cela peut être une diminution des spéculaires afin d’éviter que des samples de hautes lumières ne sois récupérés par le raytracer pour des petits objets au fond le l’image (des cheminées/antennes métalliques sur des plans large de villes) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/.lod_ville_m.png" style="margin: 0px auto; display: table;" /></p>
<p>Voici un plan magnifique de villes tout d’instances vétu. Quand on regarde ce qu’il y a dans un pixel, on se rend compte que de nombreux détails apparaissent. Ici, neuf samples sont lancés (3 × 3), mais l’un d’entre eux vient taper l’antenne qui a un haut spéculaire et une très forte valeur. Même une fois moyenné, la valeur du pixel restera très importante (vous aurez des mouches). Si vous avez un problème de la sorte, il y a fort à parier que le problème vient de votre <em>shading</em>, mais quand on a passé 3 jours à éclairer son plan et que quelques mouches cassent les pieds, on prend la solution la plus simple : Désactiver le spéculaire sur l’antenne à partir d’une certaine distance. :grenadelauncher:</p>
<p>Ce type de LOD est vraiment mis à contribution dans le rendu en lancé de rayon où quelques samples peuvent récupérer des valeurs très disparates comparé à leurs voisins du fait d’objets denses en petites géométries et lointains, ce qui occasionne du grain. Idem pour le <em>SSS</em>/<em>bump</em>/<em>normal map</em> qui peut-être volontairement désactivé sur les objets lointains.</p>
<p>C’est au cas par cas, suivant ce qu’on cherche à rendre, sachant que les moteurs gèrent parfois les optimisations de <em>shading</em> en interne, la logique étant toujours de diminuer la variation (variance) des samples en vu de diminuer le grain :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/.lod_variance_m.png" style="margin: 0px auto; display: table;" /></p>
<center><em>Sur le graphique de droite, la moyenne sera beaucoup moins « stable » suivant les pixels.</em></center>
<h3>Les <em>mipmaps</em></h3>
<p>L’autre mécanisme utilisé à la fois en jeu vidéo et en rendu est l’utilisation de <em>mipmaps</em>. Ce mécanisme est tellement répandu qu’il est souvent géré directement par le moteur de rendu sans manipulations explicite de l’utilisateur. En pratique, vous utilisez l’outil <em>maketx</em> (<em>render --buildtex</em> sous Guerilla) pour convertir les textures dans un format prévu pour le moteur (souvent en <em>.tex</em>). L’outil se charge de générer les <em>mipmaps</em> et le moteur ira piocher à la profondeur nécessaire aux vues de la distance et des surfaces (flous ou nets) parcourues par le rayon. Plus d’informations <a href="https://graphics.stanford.edu/papers/trd/" hreflang="en" title="Tracing Ray Differentials">ici</a> et <a href="https://renderman.pixar.com/resources/RenderMan_20/integratorRef.html" hreflang="en" title="Writing Integrators">ici</a>.</p>
<h3>Le Stochastic Pruning</h3>
<p>Une autre méthode assez impressionnante mais très compliqué à mettre en place : Le <a href="https://graphics.pixar.com/library/StochasticPruning/index.html" hreflang="en" title="Stocastic Pruning">Stochastic Pruning</a>. Je vous invite à regarder <a href="http://graphics.pixar.com/library/StochasticPruning/paper.pdf" hreflang="en" title="Stocastic Pruning PDF">le PDF</a>, il est très bien illustré (comme tous les PDF de Pixar :siffle:).</p>
<p>Le principe ne peut fonctionner que sur des objets composé de petits objets assez similaires. Les arbres sont un parfait candidat et c’est une (la ?) technique de simplification par excellence des arbres dans le jeu-vidéo. Le fonctionnement est très bien expliqué dans <a href="https://blog.mmacklin.com/2010/01/12/stochastic-pruning-for-real-time-lod/" hreflang="en" title="Stocastic Pruning for Real-Time LOD">ce billet</a>. Je vous le traduis ici :</p>
<blockquote>
<ol>
<li>Construisez votre objet de N éléments (les feuilles dans le cas d’un arbre, habituellement représenté par des quads).</li>
<li>Ranger les éléments dans un ordre aléatoire (Une manière robuste de le faire consiste à utiliser le <a href="https://fr.wikipedia.org/wiki/M%C3%A9lange_de_Fisher-Yates" hreflang="fr" title="Mélange de Fisher-Yates">mélange de Fisher-Yates</a>).</li>
<li>Calculez U, la portion d’éléments à rendre en se basant sur la distance de l’objet.</li>
<li>Rendez N*U éléments « non-taillé » (<em>unpruned</em>) avec une aire mise à l’échelle par 1/U.</li>
</ol>
</blockquote>
<p><a href="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/PrunedCloseup.mp4" title="Stocastic Pruning Pruned Closeup">Une petite vidéo</a> du résultat (Rendu rapproché d’un buisson alors que la camera se recule). Et si vous voulez encore plus d’images, c’est <a href="https://www.yumpu.com/en/document/read/43603224/stochastic-simplification-pixar-graphics-technologies" hreflang="en" title="Stochastic Simplification Pixar Graphics Technologies">par ici</a>.</p>
<p>Comme je le disais, ce système est assez difficile à mettre en place, car il nécessite une relation forte entre le moteur et ce qu’il rend. Je soupçonne qu’il tende à résoudre un problème apparaissant principalement sur un <a href="https://en.wikipedia.org/wiki/Reyes_rendering" hreflang="en" title="Reyes rendering">REYES</a> (Vieux Renderman) qui, lui aussi, pédale quand le nombre de triangles par pixel augmente, et à ma connaissance, seul <em>Pixar</em> et <em>Weta</em> l’ont utilisé (sur <em>Cars</em>, <em>Ratatouille</em> et <em>Avatar</em>). Pour être honnête, je pense que cette technique est désuète pour les <em>path tracers</em> (j’explique plus loin pourquoi) mais elle m’a toujours très impressionné alors je partage. :joue:</p>
<h3>L’env ball</h3>
<p>Celle-là, vous l’avez peut-être déjà utilisée. C’est la bonne vielle technique qui consiste à calculer une <em>env ball</em> à l’endroit ou se trouve le personnage (ou le centre d’intérêt) et d’exclure ce dernier de l’illumination du décor pour ne l’illuminer qu’avec l’<em>env ball</em> (ce qui diminue la variance des <em>samples</em>, et donc, le grain) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/.lod_env_ball_m.png" style="margin: 0px auto; display: table;" /></p>
<center><em>À gauche, le plan. En haut, à droite, on calcule l'env ball (en bleu) à la place de la tête du personnage. En bas, à droite, on illumine uniquement le personnage (en rouge) avec cette env ball en ayant pris soin de l’exclure des autres éclairages.</em></center>
<p>Cette technique est très efficace sur les plans où le centre d’intérêt est complexe à sampler (<em>SSS</em> dans le cou, cheveux, etc), où il ne bouge pas trop et ou la zone de contact entre l’objet à illuminer et le reste est caché (personnage qui parle au premier plan). Bien entendu, en cas de changement drastique d’éclairage, il faut recalculer l’<em>env ball</em>. Ça fait un certain nombre de contraintes, mais si vous cochez toutes les cases, vous êtes bon ! :banaeyouhou:</p>
<p>Dernière contrainte : Comme le personnage est illuminé par une seul light, l'<em>env ball</em>, certaines AOV (réflexions) seront difficile à obtenir.</p>
<h3>Les harmoniques sphériques</h3>
<p>J’hésitais à vous en parler, car elles ne sont plus vraiment utilisées en lancé de rayon, mais le principe s’apparente un peu à celui expliqué précédemment. L’idée est de calculer des minis <em>env balls</em> (un terme plus adapté serait plutôt <em>light probes</em>) un peu partout dans un décor. De cette façon, on stocke l’illumination à plusieurs endroits et on interpole en fonction de la distance entre chaque <em>env ball</em>.</p>
<p>Sauf qu’on n’appelle pas ça harmoniques sphériques pour rien… La compréhension du mécanisme nécessitent de bonnes bases en mathématique, mais le principe, plutôt qu’une <em>env ball</em> (qui nécessite du sampling de texture), est d’utiliser des coefficients désignant, grosso modo, la couleur suivant des directions. Voici une image honteusement tirée de <a href="https://gen-graphics.blogspot.com/2017/11/spherical-harmonics-in-graphics-brief.html" hreflang="en" title="Spherical Harmonics in Graphics A Brief Overview">ce très instructif billet</a> :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/Spherical_harmonics.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/.Spherical_harmonics_m.jpg" style="margin: 0px auto; display: table;" /></a></p>
<p>C’est rapide à calculer et comme vous vous en doutez, c’est très utilisé en jeu-vidéo, mais, comme les <em>env balls</em>, la gestion des zones de contact nécessite son propre mécanisme.</p>
<h3>Les LOD et la production</h3>
<p>Comme tout mécanisme que vous souhaitez utiliser dans vos productions, son fonctionnement doit avoir une réalité technique ou vos graphistes risquent de se battre contre leurs outils pour faire ce que vous avez décidé.</p>
<p>Le truc qu’un responsable (production et supervision) doit savoir, c’est que soit l’usage des LOD est exceptionnel (c-à-d. au cas par cas) et c’est la compétence du graphiste qui prend le relais. Dans ce cas, l’utilisation de LOD est justifié et son usage est adapté, car la portée de sa mise en place est connu ; eg. un simple plan. Les graphistes qui maîtrisent différentes méthodes et savent gérer leurs LOD au plan sont du pain béni pour la fabrication, surtout quand le projet est complexe et que le support est congestionné par des demandes générales. Soit le projet implique une utilisation des LOD plus fréquente/constante, ce qui implique de l’automatisation (le mot magique des incantations occultes) et les discussions sur comment ils doivent être gérés (les « choix techniques » au fond) doivent être mené entre la production, la supervision et la technique. Le but étant d’anticiper les besoins et d’éviter de se retrouver, plus tard, dos au mur ; « Mais on ne peut pas faire ça ? », « Non, tu ne l’as jamais demandé… », « Mais enfin, c’est <em>évidant</em> ! ».</p>
<p>Bref, c’est un sujet complexe alors soyez pro. :hehe:</p>
<h3>Conclusion</h3>
<p>Voici pour ce billet qui ne répond, finalement, pas à grand-chose, mais que j’espère instructif. :smileFou:</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/lod_001.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_08_04_lod/lod_001.png" style="margin: 0px auto; display: table;" /></a></p>
<center>:marioCours:</center>Le unités de mesure des logicielsurn:md5:fe96f46d7e3b6bef30e736ee3252f2a12018-08-11T22:43:00+02:002018-08-11T22:43:00+02:00NarannInfographie 3D - Boulotmayaprécisionunité<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2018_08_09_unite_logiciels/houdini_unit_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Un billet pour expliquer un truc assez bête mais qui fait perdre la tête à pas mal de gens tant il est obscur. Je vais parler de la notion d’unité de mesure dans les logiciels, ou pourquoi elle n’aurait peut-être jamais dû exister. :grenadelauncher:</p>
<p>En début de production on tente souvent de se mettre d’accord sur un référentiel de mesure. La grande question est : « Quelle distance fait 1 ? », C’est-à-dire, quand un objet se déplace de 1 dans mon logiciel, à combien cela correspond-il dans le monde réel ? :gne:</p> <p>Avant de rentrer dans le vif du sujet, voici un topo rapide des différents référentiels d’unité utilisés.</p>
<h3>1 Unité = 1 mètre</h3>
<p>Cette mesure permet de gérer de grands espaces mais fait souvent pester l’équipe <em>modeling props</em> qui, dans le cas d’un projet manipulant des dimensions de taille humaine, va se retrouver avec des 0.15, 0.47, etc pour dimensionner ses objets.</p>
<h3>1 Unité = 1 centimètre</h3>
<p>Cette mesure, pratique pour l’équipe <em>modeling</em> devient délirante lors de décors de plusieurs km. 1 km = 100 000 cm… À même pas 500 m vous risquez d’avoir des erreurs dû à la perte de précision des chiffres flottants dans les grandes valeurs (en version courte : Plus le chiffre avant la virgule est grand, moins il y a de place pour le chiffre après la virgule :reflechi: ).</p>
<h3>Le meilleur des deux mondes ?</h3>
<p>Un référentiel que j’ai souvent observé et qui est un bon compromis est 1 unité du logiciel = 10 cm dans le monde réel (1 dm).</p>
<p>Ça permet d’avoir un bon équilibre entre des unités proches de ce qui est manipulable par les humains, 1 mètre du monde réel correspondant à 10 unités, et des unités larges de décor.</p>
<h3>Chouette ! On va tous configurer nos logiciels !</h3>
<p>Et bim ! Dans le panneau ! :hehe:</p>
<p>La chose à comprendre est que, dans de nombreux cas, les nombres sont stockés <em>sans</em> référentiel. Ainsi, si vous avez un Alembic avec un cube de 1 sur 1, il est impossible de dire s’il s’agit de mètres ou de centimètres. C’est juste un cube de 1 sur 1.</p>
<p>Alors forcement, vous me répondez : « Bah si ! J’ai dit à mes équipes que 1 unité = 1 m, donc le cube est à 1 m. »</p>
<p>Vous en êtes sur ? :hihi:</p>
<ul>
<li>Ouvrez votre Maya</li>
<li>Passez l’unité de mesure en mètre (Dans <em>Preferences</em>/<em>Settings</em>, voir image)</li>
<li>Faites un cube de 1 sur 1</li>
<li>Exportez-le en Alembic</li>
</ul>
<p><img alt="unite dans maya" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2018_08_09_unite_logiciels/maya_unit_cm.png" style="margin: 0px 1em 1em 0px; width: 589px; height: 377px;" /></p>
<p>Si vous aviez les capacités d’inspecter les valeurs à l’intérieur de votre Alembic, vous verriez… 100 sur 100.</p>
<p>« Mais pourquoi ?! :injures: » me demandez-vous ? Après tout, vous avez créé un cube de 1 sur 1, pas de 100 sur 100, vous l’avez vu, de vos yeux vu ce « 1 ». :casseTeteMur:</p>
<p>Et bien tout simplement parce que l’unité <em>native</em> de Maya c’est le <em>centimètre</em>. :youplaBoum:</p>
<p>Si vous le réimportez dans Maya (configuré en mètre), il fera 1 sur 1, bien que les valeurs stockées soit 100 sur 100.</p>
<p>On a donc quelque chose d’assez déroutant et un peu dangereux : Si vous modifiez la mesure par defaut, <strong>les unités vues ne sont pas les unités stockées</strong>. Ainsi, si vous faites un décor de 1 000 m, vous aurez des soucis de précision, car les valeurs stockées dans l’Alembic et la géométrie seront en fait beaucoup plus importantes.</p>
<p>Vous l’aurez compris, dans Maya, 1 unité est <strong>toujours</strong> égale a 1 cm, le reste, ce n’est que de l’affichage. Dès que vous changez la mesure, vous entrez dans des prises de tête sans fin entre vos graphistes : « Mais puisque je te dis que j’ai exporté un cube de 1 sur 1 regarde ! Là dans Maya c’est 1 sur 1 ! C’est ton logiciel qui fait n’importe quoi en important un cube de 100 sur 100 ! Faut que t’appelles le support ! Moi c’est bon et toi t’as tort ! ». Vous voyez le genre. :perplex:</p>
<p>Quand on quitte son unité native, Maya créé aussi toutes sortes de nœuds <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-Tech-Docs/Nodes/unitConversion.html" hreflang="en">unitConversion</a>, et bon nombre de valeurs par défaut deviennent absurdes.</p>
<h3>Ne touchez à rien !</h3>
<p>La plupart des logiciels gèrent très mal les unités de mesure qui ne sont pas les leur. Il est donc conseillé de ne <strong>jamais</strong> toucher aux unités de mesure native d’un logiciel, par contre, il faut connaître la différence de référentiel entre les logiciels car, devinez quoi ?… Il n’y a pas de standard dans l’industrie ! :smileFou:</p>
<p>Par exemple :</p>
<ul>
<li>L’unité native de Maya est le <em>cm</em> (1 unité dans Maya, vaut 1 cm dans le monde réel).</li>
<li>L’unité native de Houdini est le <em>m</em> (1 unité dans Houdini, vaut 1 m dans le monde réel).</li>
</ul>
<p>(Tu sens la prod qui va <em>très</em> bien se passer. :slow_clap: )</p>
<p>Ça implique une chose : Toujours avoir en tête à quoi correspond « 1 » quand on le voit dans les Alembics, les AOV de position, les fichiers textes de position, etc. En gros, combien vaut une distance de « 1 » dans les données que vous produisez.</p>
<p>Il vaut mieux penser les unités comme des données et non comme des mesures, chaque logiciel ayant sa propre façon de percevoir cette unité.</p>
<p>En pratique, Houdini ingurgite principalement des Alembics :</p>
<ul>
<li>Vous mettez un nœud de <em>scale</em> <strong>après</strong> votre nœud de lecture Alembic (récupération de l’animation)</li>
<li>Vous mettez un nœud de <em>scale</em> inversé <strong>avant</strong> votre nœud d’écriture d’Alembic (livraison des FX)</li>
</ul>
<p>Et vous êtes tranquille. :petrus:</p>
<p>Notez que vous pouvez gérer l’unité de mesure de Houdini dans les préférences : <em>Hip File Options</em>. Il y est précisé que seul certains paramètres des <em>DOPs</em> (<em>Dynamic OPerators</em>) semble se soucier de l’unité de mesure. :redface:</p>
<p>C’est donc souvent autour de Maya que les choses tournent. Quand vous vous dites « 1 unité = 1 trucomètre », pensez « 1 cm Maya (mesure native) = 1 trucomètre ».</p>
<p>Houdini va pouvoir se débrouiller et bien souvent le problème viendra des logiciels qui fabriquent des objets en dehors de Maya (ZBrush, etc.). Une fois dans Maya ses objets permettront de définir la différence de référentiel par rapport à Maya (et donc, la différence de référentiel par rapport à votre unité).</p>
<p>TL;PL : Laissez Maya dans son unité de mesure native et travaillez comme si 1 unité Maya = 10 cm.</p>
<p style="text-align: center;">:marioCours:</p>L'instanciation implicite du format Alembicurn:md5:88b132d883e85fe0aee35fcca778bc062017-06-23T23:10:00+02:002023-05-10T11:11:50+02:00NarannInfographie 3D - Boulotalembicapicodeinstancemayapython<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_tn.png" style="float: left; margin: 0 1em 1em 0;" />Suite à la publication de <a class="ref-post" href="https://www.fevrierdorian.com/blog/post/2017/06/11/Post-mortem%3A-Ballerina">mon post mortem sur Ballerina</a>, certains d’entre vous semblaient intrigués par le paragraphe concernant l’instanciation implicite des fichiers Alembics.</p>
<p>Dans ce billet, je vous propose d’aller un peu plus loin avec une explication théorique, un peu de pratique et un peu de code (la recette du bonheur en somme :petrus: ).</p> <h4>Le principe</h4>
<p>Dans un fichier Alembic, les géométries sont stockées sous forme de tableaux.:</p>
<ul>
<li>Les positions des sommets : [point1.x, point1.y, point1.z, point2.x, point2.y, point2.z, point3.x, …]</li>
<li>Les nombres de « points par face » (souvent quatre, comme vous pouvez vous en douter): [4, 4, 4, 4, 4, 4, 4, …]</li>
<li>Les indices de positions par face : [0, 1, 2, 3, 3, 2, 4, 5, …]</li>
</ul>
<p>Le dernier est le plus subtil à comprendre : Combiné au second, il permet de construire les faces : La face 1 est compose de 4 indices. On prend donc, dans le tableau de position, les indices de position (dernier tableau): 0, 1, 2, 3. La face 2, est compose de 4 indices. On prend donc, dans le tableau d’indice de position, les 4 indices suivants qu’on va chercher dans le tableau des positions : 3, 2, 4, 5. Et ainsi de suite.</p>
<p>C’est un peu bizarre si on n’est pas habitué, mais on stocke très souvent les données géométriques de cette façon et je vais tenter de vous expliquer pourquoi. :hehe:</p>
<p>Vous l’aurez compris, le premier tableau ne concerne que les positions des sommets (<em>vertices</em> en anglais) et les deux seconds tableaux, la topologie de la géométrie. Quand un objet est animé, ce ne sont souvent que ses sommets qui bougent. Sa topologie (l’ordre de ses faces, arêtes et sommets) ne change pas.</p>
<p>Mais comment, quand on exporte de la géométrie image après image, la lib Alembic sait-elle que la topologie n’a pas changée ? C’est la magie des <a href="https://fr.wikipedia.org/wiki/Fonction_de_hachage" hreflang="fr">fonctions de hachage</a>.</p>
<p>Histoire de vous éviter la lecture de la page Wikipédia, une fonction de hachage sert, grosso modo, à générer une signature numérique (qui ressemble vaguement à « 867fc32883baaa34 ») depuis une suite de bit.</p>
<p>Comme vous vous en doutez, la suite de bit en question, ce sont nos tableaux. En langage bas niveau (C++ en l’occurrence), un tableau est une suite de valeur fortement typées. Un chiffre flottant <a href="https://fr.wikipedia.org/wiki/IEEE_754#Format_simple_pr.C3.A9cision_.2832_bits.29" hreflang="fr">se stocke sur 32 bits</a>. Une position se stocke sur 3 chiffres flottants (x, y, z), soit 3x32=96bits. Pour 8 positions (un cube) il faut donc 8x96=768bits. Ce sont ces 768bits que la fonction de hachage (<a href="https://github.com/alembic/alembic/blob/d75360416b1b20df85c4f58195d0898695ceb525/lib/Alembic/Util/SpookyV2.h#L37" hreflang="en">Spooky</a> de son petit nom) va ingurgiter pour nous sortir une valeur bizarre (eg. « 867fc32883baaa34 »): La signature numérique du tableau de position. Si on renvoie le même tableau (avec des positions parfaitement identiques), on a la même signature.</p>
<p>Vous venez juste de vous farcir un cours de science informatique en vitesse de la lumière la ! :hihi:</p>
<p>Dans un Alembic, chaque tableau possède donc sa signature numérique.</p>
<p>À chaque fois que vous envoyez un nouveau tableau à Alembic (pour chaque image en fait), ce dernier calcule sa signature numérique (son hash). S’il est déjà présent dans le fichier, il ne l’ajoute pas au fichier mais précise simplement que l’image en question utilise le tableau avec le hash que vous venez de calculer.</p>
<p>Avec ce système on peut avoir plusieurs tableaux contenant la position des sommets animés (un par image en fait) tout en gardant les deux tableaux de topologie unique pour tout le fichier.</p>
<p>Pour résumer, si vous exporter l’animation d’un simple cube déformé sur 10 images vous aurez :</p>
<ul>
<li>10 tableaux de position (un par image)</li>
<li>1 tableau de point par face (celui de la première image, réutilise sur toutes les images)</li>
<li>1 tableau de position par face (celui de la première image, réutilise sur toutes les images)</li>
</ul>
<p>Pour les plus curieux d’entre vous, voici <a href="https://github.com/alembic/alembic/blob/895a7c6a8b3396a21c6ba03ddf91ed91514d7b93/maya/AbcExport/MayaMeshWriter.cpp#L983" hreflang="en">la ligne de code</a> de l’exporteur Alembic de Maya qui s’occupe d’envoyer les différents tableaux que je vous ai présenté ci-dessus dans un fichier Alembic. Notez que l’exporteur ne fait aucune distinction. Pour chaque image, il envoie tout à l’Alembic et c’est ce dernier qui décide de stocker les tableaux dans le fichier ou non.</p>
<p>Une fois qu’on a un beau fichier tout optimisé qu’est ce qui se passe ?</p>
<p>Et bien quand l’application (un moteur de rendu par exemple) demande, les tableaux de l’image 5 à un fichier Alembic, ce dernier (enfin le code de la lib Alembic) renvoi le tableau de position de l’image 5, puis les deux tableaux de la topologie de la première image.</p>
<blockquote>
<p>Notez que je ne vous ai pas parlé des UVs, normals, vertex color, etc. Mais sachez que le concept est le même que pour les positions et la topologie.</p>
</blockquote>
<p>Et les instances implicites là-dedans ?</p>
<p>J’arrive au dernier point, celui qui devrait vous faire tilter. :idee:</p>
<p>En plus de stocker de la géométrie, Alembic stock aussi la hiérarchie. Un <em>transform</em> est un objet présenté sous la forme de translation, rotation, échelle et dont la représentation mathématique est une matrice 4 × 4 (je ne rentre pas dans les détails mais sachez que quand vous manipulez un <em>transform</em>, vous manipulez en fait une matrice). Une hiérarchie de <em>transform</em> est donc une hiérarchie de matrice.</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_022.png" style="margin: 0 auto; display: table;" /></p>
<p style="text-align: center;">Derrière les paramètres que vous manipulez tous les jours se cache un objet mathématique bien cool : La matrice ! :youplaBoum:</p>
<p>Au même titre que pour les tableaux de position et de topologie, les matrices peuvent être animée par image (quand on anim un simple déplacement qui ne déforme pas la géométrie de l’objet)</p>
<p>Notez que dans mon exemple précédant, j’ai précisé qu’il s’agissait d’un cube déformé. L’animation ne se situait donc pas sur le <em>transform</em> du cube mais directement sur les sommets (c’est la géométrie qui bouge à chaque image, comme un personnage skinné en fait).</p>
<p>Sauf que si, au lieu de déformer l’objet, vous n’animer que son <em>transform</em> (translation, rotation, échelle), le tableau des positions des sommets ne change pas d’une image à l’autre, seule le <em>transform</em> parent de la <em>shape</em> (la matrice parent) change.</p>
<p>Ce qui veut dire (arriver ici vous devriez l’avoir compris) que si un modeleur duplique des centaines d’objets sans les modifier puis exporte un Alembic, les sommets et topologies des objets ne sont stockés qu’une seule fois dans le fichier et seul la position des matrices (différentes pour chaque objet) sont stockés de manière individuelle. Et ça, <em>c’est</em> la définition d’une instance géométrique !</p>
<p>Je reprends un schéma que <a class="ref-post" href="https://www.fevrierdorian.com/blog/post/2017/03/25/Pourquoi-les-instances-Maya-sont-si-lourdes">j’avais utilisé</a> pour expliquer le principe des instances Maya (c’est pas super adapté mais ça représente bien le principe):</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_26_instance_lourde_maya/instance_001.png" style="margin: 0 auto; display: table;" /></p>
<h4>Un exemple concret</h4>
<p>Comme je sais que vous ne me croyez pas, je vous propose un exemple concret à l’aide de Maya. :siffle:</p>
<p>Créez une sphère :</p>
<p><img alt="instance_implicite_alembic_001" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_001.png" style="margin: 0px auto; display: table; width: 404px; height: 101px;" /></p>
<p><img alt="instance_implicite_alembic_002" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_002.png" style="margin: 0px auto; display: table; width: 254px; height: 207px;" /></p>
<p>Subdivisez-la histoire qu’on puisse voir des différences de poids facilement lors de l’export :</p>
<p><img alt="instance_implicite_alembic_003" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_003.png" style="margin: 0px auto; display: table; width: 192px; height: 243px;" /></p>
<p><img alt="instance_implicite_alembic_004" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_004.png" style="margin: 0px auto; display: table; width: 201px; height: 184px;" /></p>
<p>Quatre, c’est très bien :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_005.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/.instance_implicite_alembic_005_m.png" style="margin: 0px auto; display: table; width: 560px; height: 246px;" /></a></p>
<p><img alt="instance_implicite_alembic_006" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_006.png" style="margin: 0px auto; display: table; width: 434px; height: 429px;" /></p>
<p style="text-align: center;">:trollface:</p>
<p>Détruisez l’historique afin de ne garder que la <em>shape</em> :</p>
<p><img alt="instance_implicite_alembic_007" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_007.png" style="margin: 0px auto; display: table; width: 393px; height: 254px;" /></p>
<p><img alt="instance_implicite_alembic_008" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_008.png" style="margin: 0px auto; display: table; width: 455px; height: 121px;" /></p>
<p>C’est parti ! Dupliquez ça plusieurs fois :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_009.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/.instance_implicite_alembic_009_m.png" style="margin: 0px auto; display: table; width: 560px; height: 137px;" /></a></p>
<p>Notez qu’il ne s’agit en aucun cas d’instances Maya au sens propre. Ce sont de simples duplications. Notez aussi comment, malgré le poids de la géométrie, Maya reste réactif. Je soupçonne en effet que ce dernier utilise aussi l’instance implicite quand on fait des duplications et ne duplique la géométrie de chaque objet en mémoire qu’une fois qu’on commence à modifier l’objet. :reflechi:</p>
<p>Mais on ne s’arrête pas ! :grenadelauncher:</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_010.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/.instance_implicite_alembic_010_m.png" style="margin: 0px auto; display: table; width: 560px; height: 316px;" /></a></p>
<p>C’est pas mal, maintenant on exporte tout ça :</p>
<p><img alt="instance_implicite_alembic_011" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_011.png" style="margin: 0px auto; display: table; width: 327px; height: 160px;" /></p>
<p>Une seule image (vous pourrez refaire le test sur un range plus large, ça ne changera pas grand-chose):</p>
<p><img alt="instance_implicite_alembic_012" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_012.png" style="margin: 0px auto; display: table; width: 417px; height: 199px;" /></p>
<p>Pas besoin des normales ni des UVs :</p>
<p><img alt="instance_implicite_alembic_013" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_013.png" style="margin: 0px auto; display: table; width: 381px; height: 281px;" /></p>
<p>Puis validez (ou exécutez cette commande MEL):</p>
<pre>
<code class="language-bash">AbcExport -j "-frameRange 1 1 -dataFormat ogawa -file /home/narann/test/test1.abc";</code></pre>
<p>L’export devrait être assez rapide. Chez moi, le fichier fait 3.12Mo. Clairement, toute la géométrie n’est pas stockée. C’est parce qu’Alembic a reconnu que toutes les sphères étaient identiques. Les données qui composent sa géométrie (tableau de position des sommets et topologie) ne sont donc stockées qu’une seule fois, le reste étant des <em>transforms</em> (matrices) pointant vers la même géométrie. :redface:</p>
<p>Mais peut-être qu’avec 3.12Mo vous n’êtes toujours pas convaincu. Peut-être que 105 sphères subdivise à 4 ça ne pèse que 3.12Mo après tout… :perplex:</p>
<p>On va donc faire un truc qui est très souvent fait en production, un truc souvent demande par le <em>rig</em> pour plein de bonnes raisons : On va réinitialiser les <em>transforms</em>. Sélectionnez tout :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_014.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/.instance_implicite_alembic_014_m.png" style="margin: 0px auto; display: table; width: 560px; height: 320px;" /></a></p>
<p>Faites un "<em>Freeze Transformations</em>":</p>
<p><img alt="instance_implicite_alembic_015" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_015.png" style="margin: 0px auto; display: table; width: 201px; height: 100px;" /></p>
<p>Les valeurs des positions sont donc revenus à 0 mais les centres géométriques des objets n’ont pas bouge. C’est dû au fait que Maya permet de désolidariser le point de pivot de l’objet par rapport au centre géométrique.</p>
<p><img alt="instance_implicite_alembic_016" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_016.png" style="margin: 0px auto; display: table; width: 321px; height: 291px;" /></p>
<p>On va donc faire un "<em>Reset Transformations</em>" :</p>
<p><img alt="instance_implicite_alembic_017" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_017.png" style="margin: 0px auto; display: table; width: 208px; height: 91px;" /></p>
<p>Celui-ci vient modifier les valeurs géométriques de tous les sommets pour qu’ils correspondent au point de pivot. Dans notre cas, chaque objet a maintenant son <em>transform</em> ainsi que son centre géométrique au centre de la scène :</p>
<p><img alt="instance_implicite_alembic_018" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_018.png" style="margin: 0px auto; display: table; width: 350px; height: 244px;" /></p>
<p>Cela veut dire que chaque sommet de chaque sphère possède une position identique à sa position dans le monde (0, 0, 0). Chaque sommet ayant une position unique par rapport à son centre géométrique, on a donc perdu toute forme d’instanciation implicite pour Alembic. Mais qu’a cela ne tienne, testez pas vous-même :</p>
<pre>
<code class="language-bash">AbcExport -j "-frameRange 1 1 -dataFormat ogawa -file /home/narann/test/test2.abc";</code></pre>
<p>Chez moi, le fichier fait 121Mo. :siffle: Chaque sphère possède ses propres tableaux de position de sommet. En principe les tableaux de topologie sont instanciés car notre petite manipulation des points de pivot n’a pas change la topologie. Si on modifiait la topologie aléatoirement pour chaque sphère, le fichier aurait été encore plus gros.</p>
<p>Bon, on a deux fichiers, il serait peut-être temps de les tester dans nos moteurs de rendu favoris pour savoir ce qu’il en est.</p>
<h4>Dans Guerilla</h4>
<p>Je vais tester dans Guerilla car c’est avec lui que je suis le plus à l’aise et il dispose d’un bon retour pour savoir si l’Alembic est correctement interprété.</p>
<p>Importez votre premier Alembic. Les applications étant souvent friandes de moyen d’optimiser le chargement des fichiers, elles s’appuient sur l’instanciation implicite que leur propose Alembic (comme expliqué dans le post mortem, ce fut flagrant sur Mari). Guerilla n’y échappe pas et c’est assez rapide :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_019.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/.instance_implicite_alembic_019_m.png" style="margin: 0px auto; display: table; width: 560px; height: 335px;" /></a></p>
<p>Avant de faire le premier rendu, activez la <em>Verbosity</em> à <em>Diagnostics</em> puis cochez <em>Diagnostic Shapes</em> et <em>Diagnostic Accelerator</em>:</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_020.png" style="margin: 0px auto; display: table; width: 236px; height: 515px;" /></p>
<p>Puis faites un rendu. Voici le log :</p>
<pre>
<code>06/19/2017 15:36:48 RNDR DIA: hash for 'test:pSphere61|test:pSphereShape61' is 867fc32883baaa34:60c4e1c779f31ee
06/19/2017 15:36:48 RNDR DIA: build accel 'test:pSphere61|test:pSphereShape61'
06/19/2017 15:36:48 SHAP DIA: loaded shape '/home/narann/test/test1.abc' '/pSphere61/pSphereShape61.RenderGeometry'
06/19/2017 15:36:48 SHAP DIA: P float3[99842] min=(-0.979728,-0.997817,-0.979728) max=(0.979728,0.997817,0.979728)
06/19/2017 15:36:48 SHAP DIA: N float3[99842] min=(-1.000000,-1.000000,-1.000000) max=(1.000000,1.000000,1.000000)
06/19/2017 15:36:48 MBVH DIA: Building triangle accelerator for 'test:pSphere61|test:pSphereShape61'
06/19/2017 15:36:48 MBVH DIA: Built accelerator for 'test:pSphere61|test:pSphereShape61', 199680 triangles, 8.81M (geo 3.81M, tree 5.00M)
06/19/2017 15:36:48 RNDR DIA: hash for 'test:pSphere62|test:pSphereShape62' is 867fc32883baaa34:60c4e1c779f31ee
06/19/2017 15:36:48 BRDF DIA: hash for 'test:pSphere76|test:pSphereShape76' is 867fc32883baaa34:60c4e1c779f31ee
06/19/2017 15:36:48 BRDF DIA: hash for 'test:pSphere49|test:pSphereShape49' is 867fc32883baaa34:60c4e1c779f31ee
06/19/2017 15:36:48 BRDF DIA: hash for 'test:pSphere33|test:pSphereShape33' is 867fc32883baaa34:60c4e1c779f31ee
06/19/2017 15:36:48 BRDF DIA: hash for 'test:pSphere18|test:pSphereShape18' is 867fc32883baaa34:60c4e1c779f31ee
06/19/2017 15:36:48 BRDF DIA: hash for 'test:pSphere48|test:pSphereShape48' is 867fc32883baaa34:60c4e1c779f31ee
...</code></pre>
<p>Comme vous pouvez le constater, la <em>shape</em> n’est chargée qu’une seule fois (<em>loaded shape</em> dans le log) puis Guerilla s’appuie sur le hash, toujours identique, pour placer les autres sphères.</p>
<p>Si vous changez de fichier Alembic et que vous relancez le rendu, vous constaterez que le message de chargement de la <em>shape</em> « <em>loaded shape</em> » s’applique pour chaque sphère du fichier et que le rendu met plus de temps avant de démarrer.</p>
<p>Là ou c’est intéressant (et je suis sûr que tous les autres moteurs le font) c’est que quand Guerilla charge plusieurs Alembic, il instancie entre fichier. Si un modeleur a utilisé deux objets identiques dans deux Alembic différents, Guerilla le remarque et ne le charge qu’une fois en mémoire. Forcément, quand tes bâtiments ne sont que des variantes de silhouette utilisant des objets géométriques identiques c’est du pain béni pour le moteur.</p>
<h4>Comment permettre aux modeleurs de savoir quand deux objets vont être instancie dans un Alembic ?</h4>
<blockquote>
<p>Si vous ne connaissez pas Python, vous risquez d’être un peu perdu sur cette dernière partie, je préfère vous prévenir. :)</p>
</blockquote>
<p>Sur Ballerina nous avions une commande développée en externe qui nous permettait d’avoir le même hash que ceux qui allaient être mis généré par l’Alembic. C’est assez difficile à faire et mon but c’est de vous mettre le pied à l’étrier.</p>
<p>Je vous propose deux code :</p>
<ul>
<li>Un très simple, à base de commande Python qui ne s’occupe que des sommets.</li>
<li>Un autre, plus compliqué, faisant des appels à l’API Maya en Python mais qui prend en compte les UVs.</li>
</ul>
<p>Bien entendu, ce sont des codes que j’ai fais chez moi sur des scènes cubes et sphère mais absolument pas teste en production. À vous de voir ce qu’ils valent.</p>
<h4>En commande Maya</h4>
<p>Voici le premier code :</p>
<pre>
<code class="language-python">import collections
import maya.cmds as mc
h_vtx = collections.defaultdict(set)
for shp in mc.ls(type='mesh'):
h = hash(tuple(mc.xform(shp+'.vtx[*]', query = True, objectSpace = True, translation = True)))
h_vtx[h].add(shp)</code></pre>
<p>Et l’explication ligne à ligne :</p>
<pre>
<code class="language-python">h_vtx = collections.defaultdict(set)</code></pre>
<p>On crée d’abords un dictionnaire (<a href="https://docs.python.org/2/library/collections.html#collections.defaultdict" hreflang="en">defaultdict</a>, qui permet d’ajouter un objet, ici un set, à la volée):</p>
<ul>
<li>Les clefs seront les hash des positions des sommets (comme <em>14653146579</em>)</li>
<li>Les valeurs un <em>set()</em> de <em>shape</em> correspondant au hash en clef (comme <em>set('|pSphere1|pSphereShape1', '|pSphere2|pSphereShape2')</em>)</li>
</ul>
<pre>
<code class="language-python">for shp in mc.ls(type='mesh'):</code></pre>
<p>Via cette boucle nous allons traverser toutes les <em>shapes</em> de type <em>mesh</em> de la scène.</p>
<pre>
<code class="language-python"> h = hash(tuple(mc.xform(shp+'.vtx[*]', query = True, objectSpace = True, translation = True)))</code></pre>
<p>Il y a plusieurs commandes empaquetées ici :</p>
<ul>
<li><a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-Tech-Docs/CommandsPython/xform.html" hreflang="en">mc.xform()</a> renvoi la position des sommets en espace objet.</li>
<li><a href="https://docs.python.org/2/tutorial/datastructures.html#tuples-and-sequences" hreflang="en">tuple()</a> permet d’avoir une séquence immutable ordonnée à partir de laquelle on pourra générer un hash (plus d’info <a href="https://stackoverflow.com/questions/7027199/hashing-arrays-in-python" hreflang="en">ici</a>).</li>
<li><a href="https://docs.python.org/2/library/functions.html#hash" hreflang="en">hash()</a> permet de générer une signature depuis une variable (la liste des positions des sommets).</li>
</ul>
<p>On stocke le hash dans la variable… « <em>h</em> ».</p>
<pre>
<code class="language-python"> h_vtx[h].add(shp)</code></pre>
<p>Ici on ajoute la <em>shape</em> à la liste des <em>shapes</em> ayant le même hash que celui trouvé précédemment.</p>
<p>Imaginons une scène qui ressemble à ça (juste des sphères dupliquées avec une, au centre, dont j’ai bouge un vertex):</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/instance_implicite_alembic_021.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/.instance_implicite_alembic_021_m.png" style="margin: 0px auto; display: table; width: 560px; height: 401px;" /></a></p>
<p>Si on exécute ce bout de code et qu’on print "<em>h_vtx</em>" on obtient :</p>
<pre>
<code class="language-python"># Result: defaultdict(<type 'set'>, {-1145497079: set([u'pSphereShape9', u'pSphereShape3', u'pSphereShape2', u'pSphereShape1', u'pSphereShape10', u'pSphereShape7', u'pSphereShape6', u'pSphereShape5', u'pSphereShape4', u'pSphereShape12', u'pSphereShape11']), 1873436783: set([u'pSphereShape8'])}) #
</code></pre>
<p>Le dictionnaire montre deux hashes (<em>-1145497079</em> et <em>1873436783</em>), le dernier n’ayant qu’une sphère, celle dont le vertex a été bouge. Vous pouvez sélectionner les sphères du premier groupe :</p>
<pre>
<code class="language-python">mc.select(list(h_vtx[h_vtx.keys()[0]]))</code></pre>
<p>Modifiez <em>0</em> par <em>1</em> pour sélectionner la sphère du second groupe.</p>
<p>Dès lors, vous pouvez commencer à expérimenter : Dupliquer la sphère du centre (celle avec un vertex en vrac) plusieurs fois puis réexécutez le code et voyez comment il reconnait, dans le dictionnaire « h_vtx », les sphères identiques. Ensuite, faite une autre modification sur une sphère puis réexécutez le code et voyez comment cette sphère dispose maintenant de son propre hash.</p>
<p>Dans tous les cas, vous remarquerez que le script les regroupe bien qu’il ne s’agisse pas d’instances réelles Maya.</p>
<p>Arrivez ici.</p>
<h4>En utilisant l’API Maya</h4>
<p>Ici c’est un peu plus compliqué, mais on se rapproche beaucoup plus de ce que fait Alembic :</p>
<pre>
<code class="language-python">import maya.OpenMaya as om
sel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(sel)
fn_meshes = []
for i in xrange(sel.length()):
dag_path = om.MDagPath()
sel.getDagPath(i, dag_path)
fn_mesh = om.MFnMesh(dag_path)
fn_meshes.append((fn_mesh.fullPathName(), fn_mesh))
# on aurait pu ajouter les normals, les crease edges, les colors mais osef
h_pt = {}
h_vtx_counts = {}
h_vtx_ids = {}
h_uv_counts = {}
h_uv_ids = {}
h_uvs = {}
for full_path, fn_mesh in fn_meshes:
# vertex positions
pts = om.MPointArray()
fn_mesh.getPoints(pts)
h = hash(tuple((pts[i].x, pts[i].y, pts[i].z) for i in xrange(pts.length())))
h_pt[full_path] = h
# vertex topology
vtx_counts = om.MIntArray()
vtx_ids = om.MIntArray()
fn_mesh.getVertices(vtx_counts, vtx_ids)
h = hash(tuple(vtx_counts[i] for i in xrange(vtx_counts.length())))
h_vtx_counts[full_path] = h
h = hash(tuple(vtx_ids[i] for i in xrange(vtx_ids.length())))
h_vtx_ids[full_path] = h
# uv positions
uv_us = om.MFloatArray()
uv_vs = om.MFloatArray()
fn_mesh.getUVs(uv_us, uv_vs)
assert uv_us.length() == uv_vs.length()
h = hash(tuple((uv_us[i], uv_vs[i]) for i in xrange(uv_us.length())))
h_uvs[full_path] = h
# uv topology
uv_counts = om.MIntArray()
uv_ids = om.MIntArray()
fn_mesh.getAssignedUVs(vtx_count, vtx_list)
h = hash(tuple(uv_counts[i] for i in xrange(uv_counts.length())))
h_uv_counts[full_path] = h
h = hash(tuple(uv_ids[i] for i in xrange(uv_ids.length())))
h_uv_ids[full_path] = h
# the hash of the hashes
h_total = {}
for full_path in h_pt.keys():
h_total[full_path] = hash((h_pt[full_path],
h_vtx_counts[full_path],
h_vtx_ids[full_path],
h_uvs[full_path],
h_uv_counts[full_path],
h_uv_ids[full_path]))
path_per_h = collections.defaultdict(set)
for full_path, h in h_total.iteritems():
path_per_h[h].add(full_path)
print path_per_h
mc.select(list(path_per_h[path_per_h.keys()[1]]))</code></pre>
<p>Pas de panique, voici l’explication ligne à ligne.</p>
<pre>
<code class="language-python">import maya.OpenMaya as om
sel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(sel)</code></pre>
<p>Comme je n’aime pas les longs espace de nom, j’importe <a href="http://help.autodesk.com/view/MAYAUL/2017/ENU/?guid=__cpp_ref_group___open_maya_html" hreflang="en">OpenMaya</a> sous l’espace de nom "<em>om</em>". :seSentCon:</p>
<p>Ensuite, on fabrique une <a href="http://help.autodesk.com/view/MAYAUL/2017/ENU/?guid=__cpp_ref_class_m_selection_list_html" hreflang="en">MSelectionList</a> qui est une sorte de « liste spécialement adaptée à la sélection ». Et on appelle une <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_global.html#a6d81d38246555884897fb153c93aaf42" hreflang="en">commande globale bien pratique</a> qui récupère la sélection.</p>
<p>TL;DR: On fait l’équivalent de <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-Tech-Docs/CommandsPython/ls.html" hreflang="en">mc.ls()</a>. avec plus de lignes. :baffed:</p>
<pre>
<code class="language-python">fn_meshes = []
for i in xrange(sel.length()):
dag_path = om.MDagPath()
sel.getDagPath(i, dag_path)
fn_mesh = om.MFnMesh(dag_path)
fn_meshes.append((fn_mesh.fullPathName(), fn_mesh))</code></pre>
<p>Avec cette boucle, on va récupérer les <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_fn_mesh.html" hreflang="en">MFnMesh</a> de chacun des mesh de notre sélection. Un MFnMesh est un « ensemble de fonction » (Function set, préfixé <em>MFn</em> dans l’API Maya) qui permet de lier des fonctions sur des données (C’est un peu technique mais dans l’API Maya, les nœuds sont simplement des données compatibles avec certains ensemble de fonction).</p>
<pre>
<code class="language-python"> dag_path = om.MDagPath()
sel.getDagPath(i, dag_path)</code></pre>
<p>On crée un <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_dag_path.html" hreflang="en">MDagPath</a> vide qu’on remplit avec l’item de la sélection (« i » de la boucle). Un MDagPath est un « chemin vers un nœud hiérarchisé ».</p>
<pre>
<code class="language-python"> fn_mesh = om.MFnMesh(dag_path)</code></pre>
<p>Maintenant qu’on a un chemin direct, on récupère l’ensemble de fonction.</p>
<pre>
<code class="language-python"> fn_meshes.append((fn_mesh.fullPathName(), fn_mesh))</code></pre>
<p>Enfin, on l’ajoute à la liste sous la forme un tuple de deux éléments (le chemin du nœud et l’ensemble de fonction).</p>
<p>On avance dans le script pour la seconde boucle :</p>
<pre>
<code class="language-python"># on aurait pu ajouter les normals, les crease edges, les colors mais osef
h_pt = {}
h_vtx_counts = {}
h_vtx_ids = {}
h_uv_counts = {}
h_uv_ids = {}
h_uvs = {}
</code></pre>
<p>Ici on prépare simplement des dictionnaires de hash. Ils sont tous préfixés d’un « <em>h_</em> » parce qu’ils contiennent des…? Hash bien-sur ! Vous regrettez déjà de ne pas avoir fais math sup’ math spé’ je le sais. Que voulez-vous, certains réussissent et d’autres écrivent un blog. :baffed:</p>
<p>Bref, la clef de chacun des dictionnaires sera le chemin complet d’un nœud, et la valeur, sa valeur de hash. Un peu comme ceci :</p>
<pre>
<code class="language-python">h_pt = {'|pSphere1|pSphereShape1': 1574633,
'|pSphere2|pSphereShape2': 1574633,
'|pSphere3|pSphereShape3': 1657615,
...}</code></pre>
<ul>
<li>« <em>h_pt</em> » contiendra les hash des tableaux de la position des sommets</li>
<li>« <em>h_vtx_counts</em> » contiendra les hash des tableaux du nombre de sommet par face</li>
<li>« <em>h_vtx_ids</em> » contiendra les hash des tableaux des indices des sommets</li>
</ul>
<p>Et pareil pour les uvs… :sourit:</p>
<blockquote>
<p>Notez que je me suis arrêté à la géométrie et aux UVs, mais on aurait pu ajouter les normales, les couleurs par sommet, etc. Simplement que comme on ne les exporte pas avec l’Alembic : On s’en fout ! :dentcasse:</p>
</blockquote>
<p>C’est parti pour la boucle principale (qui est en fait compose de plusieurs blocs assez similaires.</p>
<pre>
<code class="language-python">for full_path, fn_mesh in fn_meshes:</code></pre>
<p>On déroule la boucle, pour chaque chemin complet d’un nœud on a son ensemble de fonction.</p>
<pre>
<code class="language-python"> # vertex positions
pts = om.MPointArray()
fn_mesh.getPoints(pts)
h = hash(tuple((pts[i].x, pts[i].y, pts[i].z) for i in xrange(pts.length())))
h_pt[full_path] = h</code></pre>
<p>On fabrique un <a href="http://help.autodesk.com/view/MAYAUL/2017/ENU/?guid=__cpp_ref_class_m_point_array_html" hreflang="en">MPointArray()</a> (un tableau de… <a href="http://help.autodesk.com/view/MAYAUL/2017/ENU/?guid=__cpp_ref_class_m_point_html" hreflang="en">MPoint()</a>) nommé "<em>pts</em>", qu’on remplit avec les points du mesh via la méthode <a href="http://help.autodesk.com/view/MAYAUL/2017/ENU/?guid=__cpp_ref_class_m_fn_mesh_html" hreflang="en">getPoints()</a> de l’ensemble de fonction "<em>fn_mesh</em>".</p>
<p>Ensuite, on déroule les valeurs de chaque point dans un itérateur qu’on déroule à son tour, comme le script précédant, dans un tuple() dont on génère le hash.</p>
<p>La raison pour laquelle on déroule la position des points c’est qu’un MPointArray() n’est pas hashable par python. Il faut donc générer une structure en pur python sinon, dans mon cas, hash renvoi toujours la même valeur, indépendamment du contenu du MPointArray(). :slowclap:</p>
<p>Et la dernière ligne stock le hash pour le chemin complet du nœud.</p>
<p>Et le reste de la boucle c’est tout pareil ! :hehe:</p>
<p>Ne change que le type des tableaux (<a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_int_array.html" hreflang="en">MIntArray()</a> et <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_float_array.html" hreflang="en">MFloatArray()</a>) ainsi que les méthodes pour récupérer les informations (<a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_fn_mesh.html#a2f2e041f01dc1ec3648002e2524fbfbc" hreflang="en">getVertices()</a>, <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_fn_mesh.html#aa41349e47d082451f1744cff85412159" hreflang="en">getUVs()</a>, <a href="http://help.autodesk.com/cloudhelp/2017/ENU/Maya-SDK/cpp_ref/class_m_fn_mesh.html#abe6b4afaec85f867401eb3658c3e5459" hreflang="en">getAssignedUVs()</a>).</p>
<p>Juste un petit <em>assert</em> (que j’utilise souvent) pour expliquer que je m’attends à ce que le tableau contenant les valeurs de U et de V fassent la même taille.</p>
<p>On passe à la suite :</p>
<pre>
<code class="language-python"># the hash of the hashes
h_total = {}
for full_path in h_pt.keys():
h_total[full_path] = hash((h_pt[full_path],
h_vtx_counts[full_path],
h_vtx_ids[full_path],
h_uvs[full_path],
h_uv_counts[full_path],
h_uv_ids[full_path]))</code></pre>
<p>Viens l’avant-dernière boucle qui consiste, comme le commentaire l’indique, à générer le « hash des hash ». En effet, bien qu’on ait séparé les hashes par type de tableau (position des sommets, topologie, UVs), ce qui peut être très utile pour mettre le doigt sur les parties qui ne s’accommode pas aux autres, je vous propose de générer un hash final, par nœud.</p>
<p>On génère donc un itérateur avec tous les hash, qu’on envoie dans un hash.</p>
<p>Et pour finir :</p>
<pre>
<code class="language-python">path_per_h = collections.defaultdict(set)
for full_path, h in h_total.iteritems():
path_per_h[h].add(full_path)</code></pre>
<p>On inverse notre dictionnaire avec, en guise de clef, le hash et en guise de valeur, un <em>set()</em> des chemins des nœuds avec ce hash. Ce qui nous donne un dictionnaire qui ressemble à ça :</p>
<pre>
<code class="language-python">h_pt = {1574633: set(['|pSphere1|pSphereShape1',
'|pSphere2|pSphereShape2']),
1657615: set(['|pSphere3|pSphereShape3',
...}</code></pre>
<p>Et on peut sélectionner les nœuds par hash comme ça :</p>
<pre>
<code class="language-python">mc.select(list(path_per_h[path_per_h.keys()[1]]))</code></pre>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_06_17_instance_implicite_alembic/sanity_check_modeleur_hash.jpg" style="margin: 0px auto; display: table; width: 480px; height: 601px;" /></p>
<p>Conclusion</p>
<p>J’espère que le principe des instances implicites des fichiers Alembics est plus clair pour vous maintenant. Si vous êtes à l’aise en script, je vous invite à essayer de structurer ces informations dans une petite interface de sélection pour aider vos modeleurs. Ce n’est pas un petit boulot mais sur un projet un peu ambitieux ça peut valoir le coup.</p>
<p style="text-align: center;">:marioCours:</p>
<p>Édit du 25 juin 2017 : J’ai bien conscience que ce billet est assez technique, surtout sa première partie. S’il y a des points qui vous semblent mal expliqués, n’hésitez pas à m’en faire part dans les commentaires, j’essaierai de peaufiner mes explications.</p>Post mortem: Ballerinaurn:md5:107657fad4613eb3c9236696f77dd6812017-06-11T16:26:00+02:002020-12-04T18:14:31+01:00NarannInfographie 3D - Boulotanimationballerinaguerillalong métragepipelineqube<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/post_mortem_ballerina_tn.jpg" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Je viens enfin de finir le post mortem de mon travail sur le long-métrage Ballerina. Je vous préviens, c’est long.</p>
<p>En espérant que ça vous plaise. :popcorn:</p> <h3>La bande annonce</h3>
<p>Ce post mortem contient évidemment son lot de <em>spoil</em>. Je considère que vous avez déjà vu le film. Si vous ne l’avez pas fait, franchement faites-le. Le montage est rapide et on ne s’ennuie pas et vous lâcherez sûrement une petite larme. Dans tous les cas, voici la bande annonce :</p>
<p style="text-align: center;"><iframe allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/M5-LjE4LSLA" width="560"></iframe></p>
<h3>Mon poste</h3>
<p>J’ai eu plusieurs casquettes durant le projet (comme d’hab’ en fait…). C’était du pip’, du pip’, du pip’. Voici une liste non exhaustive de ce que j’ai fait :</p>
<ul>
<li>Lookdev (<a href="http://www.guerillarender.com/" hreflang="en">Guerilla</a>)</li>
<li>Lighting (<em>Guerilla</em>)</li>
<li>Renderfarm (<em><a href="http://www.pipelinefx.com/" hreflang="en">Qube</a></em>)</li>
<li>Hair/Végétation (Maya/<em><a href="http://peregrinelabs.com/yeti/" hreflang="en">Yeti</a></em>)</li>
</ul>
<h3>Un démarrage difficile</h3>
<p>Sans rentrer dans les détails, le studio appartenait aux producteurs du film. C’était leur premier long-métrage. Le projet a connu un certain nombre de difficultés et a dû “recommencer” en cours de route. Je suis arrivé alors que le projet avait été mis en pause et une bonne partir des équipes, mise <span class="st">à</span> pied. Il n’y avait pas (plus en fait) d’outils, pas de pipeline. Nous n’étions pas nombreux dans le studio (moins de dix). La R&D commençait le pipeline et le département artistique continuait ses recherches. Il était d’ailleurs difficile de se dire, en arrivant le matin, qu’un film allait sortir de tout ça. La masse de travail abattu lors des premiers mois fut importante.</p>
<h3>Shotgun</h3>
<p>Le temps et les ressources disponibles étant limitées, nous avons opté pour <em><a href="http://shotgunsoftware.com/" hreflang="en">Shotgun</a></em> pour gérer l’intégralité de notre base de données (comprenez qu’il n’y avait aucune autre base de données que <em>Shotgun</em>, même pas un petit <a href="https://www.mysql.com/" hreflang="en">MySQL</a>, planqué sur un serveur en loucedé près des toilettes, rien !). C’est (très) cher mais au vu de la configuration du studio et de ce que <em>Shotgun</em> propose, je pense, avec du recul, que c’était un bon choix.</p>
<p>Une grosse partie du travail de départ concernait les <em>template</em> de chemin de fichier de <em>Shotgun</em> pour réussir à publier en utilisant <em>Shotgun</em> mais notre propre structure de fichier.</p>
<p>Viens ensuite la suppression des step/task de <em>Shotgun</em> au profit d’un système maison. C’est vraiment un truc idiot, dans <em>Shotgun</em>, que les steps et les tasks puissent être créées et modifiées par les coordinateurs, car on ne peut, du-coup, pas s’appuyer dessus en termes d’infrastructure. Il fallait donc les remplacer pour faire la même chose mais en plus robuste.</p>
<p>Comme tout changeait tout le temps, les chemins de fichier n’utilisaient pas les noms des assets et des plans mais leurs ids, donnant des chemins de fichier difficile à lire. Bien que ça puisse paraître dangereux, cela n’a posé aucun souci majeur en pratique car tout était fait pour éviter aux personnes d’avoir à naviguer dans la structure des dossiers.</p>
<p><em>Shotgun</em> est lent. Quand le builder est arrivé (voir plus bas) on s’est retrouvé à littéralement mettre <em>Shotgun</em> à terre avec des requêtes importantes (en gros, notre hiérarchie de scène c’était <em>Shotgun</em>…). Après pas mal d’optimisation de leur part, on a aussi mis un système de cache basé sur <em><a href="https://redis.io/" hreflang="en">Redis</a></em>. <em>Redis</em> est un système de cache sur serveur. Le principe est de stocker, dans <em>Redis</em>, le résultat des requêtes demandées à <em>Shotgun</em> pour éviter d’avoir à les refaire :</p>
<ul>
<li>Notre code fait une requête.</li>
<li>Il vérifie, dans <em>Redis</em>, si le résultât de la requête existe.</li>
<li>S’il existe, il l’utilise.</li>
<li>S’il n’existe pas, notre code fait la requête dans <em>Shotgun</em>, attends (looooooooongtemp…), récupère le résultât de la requête, le met dans Redis, puis l’utilise.</li>
</ul>
<p>Vous l’aurez compris : <em>Redis</em> c’est bon, mangez-en ! :sourit:</p>
<h3>Modeling, Rigging et Lookdev commencent</h3>
<p>Ces trois départements ont commencé rapidement et fonctionné en parallèle presque immédiatement, ce qui nécessitait, du fait de l’impossibilité d’anticiper, un nombre conséquent d’aller-retour. Il fallait être continuellement vigilant sur ce que chaque département livrait/récupérait. Ce fut épuisant pour tout le monde mais, et c’est peut être du fait des Québécois, personne ne râlait quand il fallait repasser sur un certain nombre de choses (chapeau aux modeleurs pour le nombre incroyable de retake techniques). Souvent nous comprenions tous pourquoi tel ou tel chose devait être modifié. L’ambiance générale était très bonne malgré les journées de travail éreintantes (c’est peut-être lié). Ça été ça pendant vraiment longtemps, doute-investigation-stresse-correction-test-avance.</p>
<h4>Tout override !</h4>
<figure style="margin: 0 auto; display: table;"><img alt="guerilla.png" class="media" src="https://www.fevrierdorian.com/blog/public/logos/guerilla.png" /></figure>
<blockquote>Note : Il faudra que je prenne le temps, un jour, d’expliquer le principe du lighting par override. Bien que cette pratique soit très utilisée en long-métrage et série, tout le monde ne la connaît pas nécessairement.</blockquote>
<p><em>Guerilla</em> est un superbe outil, je le crie à qui veut l’entendre. La logique mise en place sur <em>Ballerina</em> a été la suivante :</p>
<p>Un RenderGraph initial pour mettre les paramètres par défaut. Par exemple, appliquer un shader gris-neutre sur tous les objets, assigner un shader de curve sur les objets de type curve, modifier des paramètres en fonction des tags assignés sur les objets, etc.</p>
<p>Le second RenderGraph était appliqué par asset. Ce sont les RenderGraphs que les artistes du lookdev faisaient et publiaient. Il assignait des séquences d’UDIM par attribut du shader (spéculaire, couleur diffuse, etc.) et modifiait les paramètres ne nécessitant pas de textures. Le tout en s’accrochant aux tags des objets.</p>
<p>Enfin venaient les RenderGraphs du Lighting séparés en trois : Un pour la séquence, un pour un groupe de plan et le dernier pour le plan.</p>
<p>Chacun de ces RenderGraph changeait (overridait) les paramètres du RenderGraph précédent.</p>
<p>Cette liste ne s’est pas faite d’un coup. Par exemple, les lighters ont longtemps travaillé qu’avec un RenderGraph de plan unique, les RenderGraph de séquence sont arrivés plus tard, tout comme les RenderGraph de groupe de plan arrive quasiment à la fin.</p>
<h3>Recrutement des TDs</h3>
<figure style="margin: 0 auto; display: table;"><img alt="modus_fx_logo.jpg" class="media" src="https://www.fevrierdorian.com/blog/public/logos/.modus_fx_logo_m.jpg" /></figure>
<p style="text-align: center;">Ceci n’est pas un fournisseur d’accès internet. :trollface:</p>
<p>On a eu de la chance (dira-t-on), Modus FX a <a href="http://www.3dvf.com/actualite-9002-modus-fx-ferme-ses-portes-une-centaine-d-employes-licencies.html" hreflang="fr" title="Modus FX ferme ses portes, une centaine d'employés licenciés">fermé ses portes</a> au moment où on cherchait des TDs. On s’est donc récupéré énormément de TD expérimentés. Avec le recul, il aurait été difficile de sortir le pipeline sans leur aide, ils ont vraiment fait du bon boulot. Chaque TD avait la responsabilité d’un (ou plusieurs) département, mais nous codions dans le même repo git avec les mêmes standards de code. Il était d’ailleurs vraiment impressionnant de pouvoir sauter dans le code de ses collègues sans avoir l’impression d’être largué. Cette approche (très rigide à l’entrée) a réussi à créer une cohésion entre les TD qui a vraiment permis une « avancée de front ». Au passage : <a href="https://www.python.org/dev/peps/pep-0008/" hreflang="en">PEP 8</a> est un compromis, pas un standard.</p>
<p>Mon travail concernait principalement le lookdev. Nous avons utilisé <em><a href="https://www.foundry.com/products/mari" hreflang="en">Mari</a></em> et <em>Guerilla</em>. Comme la hiérarchie n’était pas fixe, nous nous sommes appuyé sur les tags. C’est un peu fastidieux et contre productif au premier abord, car les graphistes doivent placer leurs tags dans Maya, exporter leur abc et faire leur surfacing dans <em>Guerilla</em>. Mais il faut bien garder à l’esprit que la hiérarchie changeait constamment à cette période (rien n’était encore décidé en rig) il fallait donc trouver un moyen au lookdev de livrer sans dépendre d’autres départements. Ça été, je pense, une bonne décision.</p>
<p>Le travail consistait grossièrement à travailler dans <em>Mari</em>, exécuter un script qui sortait en <em>.tif</em> les UDIMs des channels sélectionnés, puis faire un Update dans <em>Guerilla</em> qui transformait ses mêmes <em>.tif</em> en <em>.tex</em> pour le rendu. C’était fastidieux et nous aurions pu améliorer la détection des updates pour éviter beaucoup de temps perdu, notamment lors de petites retakes. Notez que seul les <em>.tex</em> étaient publiés. Cela n’a pas posé de soucis en pratique.</p>
<h3>Builder</h3>
<p>Le builder (layout), c’est la colonne vertébrale d’un pipeline d’un film. Il permet la relation entre les informations de plan en base de donne et les logiciels. Un des gros chantiers de tout long-métrage c’est la capacité à construire les plans, si possible dans n’importe quel logiciel. Je ne me suis pas occupé du Builder du côté de la base de donne, mais de son intégration dans <em>Guerilla</em>. C’est d’ailleurs quand les plans ont commencé à se builder sans trop de soucis que j’ai réellement pris confiance en notre capacité à sortir le projet. Tant que vous n’avez pas le moyen de reconstruire des plans, à jour, en partant de rien, vous êtes dans une situation difficile (et si vous avez des équipes qui travaillent sur des plans avant d’avoir cet outil vous êtes franchement dans la m… Ouai nan… Faites un builder avant toute chose…). Du builder découla les quality check d’animation (Ambiant Occlusion) et leur automatisation sur la render farm.</p>
<h3>Instanciation implicite</h3>
<p>Une fois que vous avez un builder, vous construisez des plans de plus en plus gros, jusqu’à ce qu’ils ne rendent plus. L’intérieur de l’opéra (entre autres) montait anormalement en mémoire. On a donc creusé un peu pour se rendre compte que le modeling appliquait systématiquement un combo <em>reset transforms</em>/<em>freeze transforms</em> sur tous les objets, garantissant au rig que tous les objets étaient au centre du monde, ce qui simplifiait leur travail. Cela avait pour effet, lors de l’export en alembic, de casser les instances implicites.</p>
<h4>C’est quoi</h4>
<p>Le principe est simple : Quand deux shapes sont identiques, on considère qu’elles sont en instance. Ce qui définit une shape c’est (entre autres) la position de ces points par rapport à son centre. Si chaque objet a un centre différent, leurs points, une fois dans l’alembic, sont à des positions différentes. Ainsi, si vous dupliquez mille fois un objet, que vous écrasez les transforms de tous les objets, chaque objet a son centre au centre du monde, et donc, chaque objet a un centre différent de celui du voisin, et donc, tous les point qui compose un objet sont différents de ceux du voisin.</p>
<blockquote>
<p>Note : Maya fait une distinction entre le centre géométrique et le point de pivot. Il est donc possible de déplacer le point de pivot sans déplacer le centre géométrique.</p>
</blockquote>
<h4>Comment résoudre ça</h4>
<p>On était très avancé en modeling, il fallait une solution simple sans trop d’intervention humaine. L’approche prise est pragmatique et pas super mathématique : On avait remarqué que la topologie des objets étaient conservés. Nous avions donc la garantie que les deux premières arêtes connectées au premier point étaient les même sur tous les objets. S’ensuit un peu d’algèbre linéaire (produit vectoriel, scalaire, merci PyMEL) et on avait un algo capable de récupérer les objets qui pouvaient être implicitement instancié et récupérer un centre identique. Une UI plus tard les modeleurs pouvaient repasser sur les gros sets pour récupérer l’instanciation implicite des objets en quelques cliques.</p>
<p>Il n’empêche que malgré cet outil, il fallait quand même rouvrir tous les assets pour repasser dessus. Un travail assez laborieux et ingrats. Il y eut quelques tensions autour de ça (parce qu’il a déjà fallu repasser sur presque tous les assets auparavant pour des histoires de hiérarchie). J’avoue avoir pas mal forcé auprès de la prod pour que ce soit fait. Ce n’est jamais très apprécié de faire du forcing de la sorte, mais je pense que tout le monde a bien compris qu’on allait avoir de gros soucis si on ne le faisait pas.</p>
<h4>Effet indirect</h4>
<p>On a remarqué qu’en plus de diminuer drastiquement la consommation mémoire de <em>Guerilla</em>, les gros assets, en particulier les buildings, se chargeaient bien plus vite et étaient bien plus fluide à la navigation dans <em>Mari</em>.</p>
<h3>Hiérarchie des assets</h3>
<p>Comme je vous le disais, le rig n’était pas défini. Il a donc fallu repasser sur tous les assets pour conformer leur hiérarchie. On a donc fait des petites boucles qui ouvraient tous les assets du film pour passer un sanity check et savoir exactement quels assets devaient être rouverts. Mais on est allé plus loin : Avec les modeleurs, on a identifié les cas de “correction scriptable” pour le faire automatiquement. Après que les modeleurs aient teste la méthode sur quelques assets, on envoyait ça sur tous les assets de la prod. Si envoyer une boucle qui ouvre tous les assets pour faire passer un sanity check n’engage à rien. Le fait de modifier, en batch, des centaines d’assets est extrêmement risqué. On a fait la procédure et le suivi à deux, l’un à côté de l’autre pendant plusieurs jours ce qui permettait une réflexion et une remise en question continue (ne jamais laisser une seule personne faire du batch qui modifie les scènes toute seul). Car derrière l’apparente simplicité, le risque de casser des centaines d’asset est réel. On a donc fait ça avec le sérieux et le professionnalisme qui s’imposait, on a relu plusieurs fois notre code, on a fait des tests et forcement, on a tout pété! :baffed:</p>
<p>Bon, on a été malin, le commentaire de publication utilisé pour mettre à jour les assets contenait un mot très particulier. Il a donc été assez simple de rechercher les publications puis de les omettre dans Shotgun. Mais bon, je me rappelle qu’à ce moment, l’état d’esprit de la production c’était : :mitraille: :enerve: :tuComprendRien: :injures: :casseTeteMur: :nervous: :grenadelauncher:</p>
<p>Un petit fix, on a relancé la boucle et c’était bon… :siffle:</p>
<h3>Hair</h3>
<p>Les hairs ont été faits avec <em>Yeti</em>. Du fait des autres chantiers, les hairs sont restés longtemps sans personne (en R&D) pour leur assurer un pipeline propre. On savait qu’on allait le payer et ça n’a pas raté. La liste des problèmes rencontrés est très longue et, il faut bien l’avouer, assez misérables. Le rig n’était pas recentre, c’est-à-dire que plus les personnages étaient loin du centre, plus la géométrie avait des problèmes de précision (J’entends souvent que « c’est le B-A BA » mais je n’ai jamais vu son application sur un long. Le rigs étant souvent trop complexe).</p>
<p>En pratique les soucis de précision n’auraient pas dû être un problème car dans tous les cas, nous recentrions les scènes. C’était sans compter sur la boite noire qu’était <em>Yeti</em>. Nous n’avons jamais eu la confirmation de quoi que ce soit (les échanges de mail étant assez improductif) mais je soupçonne que <em>Yeti</em> essai de se rattacher à la surface d’une distance au prorata de la densité de point. C’est-à-dire que plus il y a de point, moins <em>Yeti</em> va chercher la surface. Le problème que cela posait était le suivant : Afin de pouvoir générer un vecteur de motion blur, chaque point de chaque courbe qui compose les hairs doit être présent deux fois à t et t+0.5. Sauf que si <em>Yeti</em> n’arrivait pas à retrouver une surface lors d’un des samples, il ne calculait pas la courbe, le nombre de point devenait donc inconsistant entre t et t+0.5. Sans un nombre consistant de point, <em>Guerilla</em> ne pouvait générer les <em>motion vectors</em>, il ne lui restait donc qu’à cacher la partie des hairs qui posait problème en affichant un warning. La solution ? Recentrer les hairs avant l’export de la simulation, sauvegarder l’offset et l’appliquer dans <em>Guerilla</em> avant rendu B-D. Je vous passe les détails mais ça été un beau bordel. Toutefois, le truc cool en code c’est qu’à force d’effort et d’acharnement, ça finit par marcher mais ça été long et fastidieux.</p>
<h3>La végétation</h3>
<p>Une autre de mes taches sur le projet a été de faire un outil pour gérer la végétation, en particulier les arbres et les buissons. La première question est pourquoi ne pas utiliser <em>Yeti</em> ? C’est difficile à expliquer si on ne sait pas comment fonctionne un pipeline. <em>Yeti</em> génère des <em>.fur</em> tout-en-un qui sont “déroulés” dans <em>Guerilla</em> au moment du rendu. Cette étape est très longue, la mémoire prise par <em>Yeti</em> lors de la génération des primitives est énorme, la bounding box est globale, l’instanciation compliquée et surtout : Une mise à jour d’un des assets nécessite un reexport du système de <em>.fur</em> entier. Dans une logique de pipeline d’animation, chaque département n’est supposé publier que le minimum de ce qui est nécessaire. Dans le cas de la végétation seul des informations de position/rotation/échelle ainsi que l’id de l’asset à placer (avec ses attributs de variations) sont nécessaires. Ainsi, dans <em>Guerilla</em>, on amène l’alembic contenant uniquement les positions des assets, la géométrie source de l’asset (que l’on cache), un script pour lier chaque position à la source et c’est terminé. On avait quelques soucis de performances, isolé dans ticket, résolu par les gars de <em>Mercenaries</em> (les développeurs de <em>Guerilla</em>) dans la semaine et on avait un système d’instance très léger, qui rendait immédiatement.</p>
<p>L’outil a été un peu long à écrire, mais on l’a étendu à un certain nombre de plans, notamment pour placer des personnages en instance sur les vues d’ensemble.</p>
<p>Mon seul regret de technophile à poil dur fut la demande de diminuer le nombre d’arbres “parce qu’il y en avait trop” alors qu’on avait virtuellement aucune limite.</p>
<h3>Lighting</h3>
<p>La production a fait du bon boulot, car Le Petit Prince venait de se terminer et on a pu embaucher bon nombre de lighters déjà formé sous <em>Guerilla</em>. Je me rappelle avoir présenté le pipeline à une juniore qui, après m’avoir écouté déclarer mon amour pour les overrides, me fit comprendre qu’elle connaissait déjà tout ça. Tous les lighters étaient à l’aise avec <em>Guerilla</em>. Ils avaient travaillé avec une ancienne version pendant un long-métrage entier et découvrait également que beaucoup de leurs soucis avaient été résolus depuis.</p>
<h4>Publier</h4>
<p>On a choisi de versionner les séquences d’image de manière granulaire, à l’AOV (pass/layer/aov/<version>). Comme nous avions beaucoup d’AOV, il y avait énormément de versions à publier à chaque fois. On a fait une UI tout-en-un pour simplifier le travail de gestion des rendus des lighter (et aussi éviter qu’ils aillent se perdre et tout casser dans la hiérarchie de dossier :P). Sous le capot, l’interface était threadé à mort. Il fallait, en effet, s’assurer que le lighter voit, quand une séquence était incomplète, quelles images manquaient (et il y avait beaucoup de séquences). Le gros problème venait de <em>Shotgun</em> qui lâchait parfois prise et bloquait les publications sans raisons apparente, il fallait donc reprendre là ou il s’était arrêté (heureusement les cas d’arrêt pendant la copie d’une séquence d’image était rare).</p>
<h4>Override à la séquence, par groupe de plan, puis par plan</h4>
<p>Il est intéressant de constater qu’une des feature du pipeline qui permet d’économiser énormément de temps n’est arrivé que lors des six/sept derniers mois du projet. C’est quelque chose qui a été discuté très tôt, avant même l’arrivée du modeling. C’est un des trucs dont je suis le plus fier et qui a réellement augmenté la vitesse de sortie des plans. Le principe est de pouvoir overrider à la séquence mais surtout par groupe de plan. Dans la pratique, le lighter pouvait grouper les plans d’une même séquence similaires (exemple : champ/contre-champ) et appliquer des overrides pour chacun des groupes puis de propager sur toute la séquence. Ainsi, les lighter ne travaillaient plus au plan mais à la séquence. Les seniors ont rapidement sauté dessus. De manière surprenante, certains juniors ont également vite emboîté le pas. Ce n’était pas parfait, les rigs de light devaient toujours être propage manuellement et la UI de gestion des groupes n’était pas terrible, mais les séquences passaient en revu en entier très rapidement après avoir été commencé. La vitesse gagne en lighting sur certaines séquences simples était vraiment énorme.</p>
<h4>Shot group</h4>
<p>Il y avait deux façons de publier un rendergraph de lighting : À la séquence, et au plan. Le rendergraph de plan overridant le rendergraph de séquence. L’idée était que, quand un graphiste commençait une séquence, il remonte toutes les modifications nécessaires sur toute la séquence dans le rendergraph de séquence pour ne laisser dans le rendergraph de plan que les modifications spécifiques au plan. C’est une approche assez conventionnelle quand on travaille avec des overrides, car bien souvent, le pipeline permet de publier des choses à la séquence ou au plan. Mais elle laisse un trou énorme : Que faire quand on souhaite éclairer deux plans similaires. :reflechi:</p>
<p>Si par exemple votre séquence se compose de quelques plans d’intro (souvent large), puis d’un enchainement de champ/contre champ, identique en termes de lighting, puis trois plans de sortie, vous avez 4 groupes de plan :</p>
<ul>
<li>intro (2 plans)</li>
<li>part_1_char_1 (4 plans)</li>
<li>part_2_char_2 (5 plans)</li>
<li>outro (3 plans)</li>
</ul>
<p>Chaque studio semble faire sa tambouille mais après quelques semaines de travail, on a commencé à réfléchir a un moyen de publier des choses « par groupe de plan ». Et ne croyez pas que c’est évidant. Sur un plan purement ingénierie, cette approche est « anti-hiérarchique » dans un pipeline, car si la hiérarchie « studio/projet/séquence/plan/cam » est fixe, les groupes de plan peuvent être défini différemment suivant qu’on est en lighting ou en animation (ou autre). Il s’agissait donc d’une structure par séquence (il fallait bien la mettre quelque part) non connu à l’avance, donc difficilement structurable.</p>
<p>Les lighters avait donc moyen, par séquence, de faire des groupes de plan. Ils visualisaient la séquence et commençait déjà à réfléchir comment ils allaient publier chacun de leur rendergraph.</p>
<p>Au moment de construire le plan, on récupérait le rendergraph publié au plan, puis on demandait le groupe dans lequel était le plan en cours, puis on récupérait le rendergraph publié dans ce groupe. Les rendergraph s’enchainaient dans cet ordre : « sequence/shot group/shot ».</p>
<p>Cette méthode est arrivée très tard sur le film (je dirais le dernier quart en termes de planning) mais elle a permis quelque chose d’énorme : Les lighteurs pouvaient prendre et éclairer des séquences complètes. Ça prenait un peu plus de temps à démarrer (comparé à un éclairage au plan) mais quand ils avaient fini, ils envoyaient toute leur séquence en rendu et l’itération se faisait par séquence.</p>
<p>Un autre effet fut que l’éclairage était beaucoup plus cohérent entre les plans. Très souvent, les éclairages au plan tendent à casser l’homogénéité de la séquence. Ce sont les lead qui veillent constamment a l’homogénéité de l’ensemble (éclairage, couleur, etc.), mais malgré ça, ça se voit toujours. Le fait de repousser la modification par plan aussi loin que possible permettait de sucrer une bonne partie des retakes d’homogénéité. Au final, le rendergraph de plan était quasiment vide d’information d’éclairage.</p>
<p>J’avoue ne jamais avoir vu de système semblable aussi pousse. Bien entendu, ce n’est pas parce que je n’en ai pas vu que ça n’existe pas. Mais bien souvent ce sont des outils de propagation qui permettent d’envoyer l’éclairage d’un plan dans un autre plan, pas une publication entre la séquence et le plan.</p>
<h3>Render farm</h3>
<p>Disons-le clairement, la gestion de la render farm n’était vraiment (mais alors vraiment) pas quelque chose qui m’attirait. Et pourtant, ça été très intéressant. Et pour cause, la render farm ne concerne pas uniquement le rendu. C’est de la puissance disponible pour de l’automatisation de tâche. Et on en a bien usé:</p>
<ul>
<li>Génération des alembic en sortie du modeling, de l’animation et des fx.</li>
<li>Export automatique des séquences de <em>.fur</em> (<em>Yeti</em>) en sortie du département d’animation.</li>
<li>Build et calcul des ambiant occlusion pour le contrôle de qualité des animations.</li>
<li>Trigger lors des validations.</li>
<li>Mise à jour automatique des plans de lighting avant rendu.</li>
<li>Build et rendu des precomps.</li>
<li>Et bien entendu : Rendu des images</li>
</ul>
<p>On a utilisé <em>Qube</em>. Un truc qu’il faut savoir concernant les gestionnaires de ferme de rendu c’est le “bruit réseaux” qu’ils produisent. Le nombre de machine augmentant (certaines n’étaient pas situe dans la boite mais à plusieurs une centaine de mètres du studio), il peut arriver qu’une quantité non négligeable des accès réseau ne soit prise que par le gestionnaire de ferme de rendu qui communique avec ses clients. Le problème apparaît lorsqu’il y a congestion ou chaque client a besoin de contacter le serveur sans que ce dernier ne soit capable de répondre, s’ensuit un effondrement des performances générales. Certains gestionnaires, plein de fonctionnalités qui se veulent pro-actifs (vérifier que toutes les images sont sorties, qu’elles ne sont pas corrompues, etc.) sont capables, de par leur activité, minime mais intrusive, de mettre à terre un réseau déjà bien occupé à charger des gigas de données. C’est un détail à prendre en compte quand on commence à monter en charge. Au-delà de ça, Qube a une API assez soviétique sur les bords, on sent le logiciel mature, stable et tu le payes par une API complexe.</p>
<h3>Matte Painting</h3>
<p>On ne peut pas vraiment parler de pipeline pour le département de matte painting, mais de support. C’était rafraîchissant : Transfert de caméras de projection entre <em>Guerilla</em> et <em>Nuke</em> (avec ce sentiment de satisfaction quand on attend enfin le <em>pixel-perfect</em>). J’ai également pu constater à quel point <em>Nuke</em> était mauvais en 3D et à quel point un “vrai” logiciel de matte painting manquait. Je ne parle bien évidemment pas pour la peinture digitale mais pour la gestion de la 3D (projection et manipulation de la géométrie).</p>
<h3>Commentaire de plan</h3>
<p>Sur un long-métrage, chaque plan a une histoire. Entre le nombre de département qui passe dessus (modeling, layout, animation, rendu, compositing) et les allez-retours de validation entre chaque étape, chaque personne ayant travaillé sur le projet a une d’histoire à raconter. En revoyant le film, je me suis dit que j’avais envie de commenter les plans. C’est parti !</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_001.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_001_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Voici un plan typique d’arbres et d’herbe. Je pense qu’il y a eu une difficulté de communication autour du placement des arbres. Aucun décor breton (et français d’une manière générale) ne ressemble a ça, les arbres sont supposés délimiter les champs alors qu’ils sont placés de manière très aléatoire ici. Je soupçonne que l’esthétisme a été privilégié mais du-coup ça manque un peu de vie. Autre remarque sur Yeti : On l’a aussi utilisé pour placer des branches et des cailloux, mais on se retrouvait avec des bounding box énormes pour finalement peu de géométrie, ce qui a pas mal ralenti le rendu.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_002.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_002_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Le décor de l’orphelinat, l’un de mes préférés.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_003.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_003_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Il est à noter que cette pièce est utilise à la fois pour la cuisine et pour le dortoir. Les lits remplaçant les tables. Ça passe super bien dans le film et ça a permis de se focaliser sur une seule pièce.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_006.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_006_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Je ne peux plus vérifier, mais je crois que ce plan a été rendu dans les derniers, quand la farm était quasi vide et mettait 14 h l’image :aupoil: . Si je me rappelle bien, on savait que c’était lié à tous les rochers et branches par terre, mais on a décidé de ne pas trop chercher plus loin.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_008.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_008_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>La boite à musique, ou comment un plan totalement anodin peut rendre fou. Dans Maya les sous frames de l’animation étaient parfaites, mais une fois exporte en alembic puis réimporte, le motion blur explosait certaines pièces (notamment le <a href="https://fr.wikipedia.org/wiki/Trisk%C3%A8le" hreflang="fr">triskèle</a>). C’était devenu un <em>running gag</em>… Je ne sais même plus comment on a fini par résoudre le souci, mais je ne serais pas surpris qu’on ait totalement désactivé le motion blur 3d sur ce plan.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_009.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_009_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Les escaliers sont souvent une tanne à rendre, mais je trouve que l’équipe lighting s’en est sorti à merveille.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_010.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_010_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>J’adore cette séquence de nuit. Le fait d’avoir une seule source principale de lumière (celle de la moto) donne à la séquence un aspect dramatique. Encore une fois, je pense qu’on a trop négligé le placement des arbres, ça manque de vie.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_011.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_011_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Notez le renfoncement de la route sur toute cette séquence. L’objectif, vous vous en doutez, est d’éviter d’avoir à travailler l’horizon sur l’ensemble des plans.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_012.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_012_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Et voila un keyshot. Le principe d’un « keyshot » (ou « plan-clef ») sur un long-métrage (et en animation 3d d’une manière générale) est de valider l’éclairage type d’une séquence (couleur, ombres et tout ce qu’il est possible de définir dès cette étape en fait). Ils sont donc souvent plus travaillés que les autres et permettent aussi de mettre le doigt sur les problèmes techniques avant d’entamer le travail à la séquence. Ce sont souvent les seniors qui font ses plans. L’objectif est de se représenter l’intégralité de la séquence et comment tous les plans vont être fait et de « passer la main » aux graphistes juniors qui (en principe mais là on rêve un peu) ajuste un éclairage donné sans se prendre la tête.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_013.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_013_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Le train a été utilisé pour faire quelque chose d’assez peu courant (longue explication en approche): Sur ce projet, le layout avait la possibilité de bouger les contrôleurs de certains objets des assets pour éviter la répétition. Un bon exemple sont les volets des maisons, les portes, les tables, etc. Le problème c’est que du-coup, les assets en questions ne sont plus vraiment en position d’origine ce qui est assez problématique, car vous ne pouvez plus simplement considérer, dans votre base de donne, qu’un shot contient une instance d’asset place a tel endroit, il faut aussi avoir la liste des modifications de placement quelque part. Très souvent (enfin j’ai vu ça partout jusqu’à présent), la solution retenue face à ce dilemme est : « Si ce n’est pas juste un déplacement du contrôleur global, c’est de l’animation » et donc, c’est exporté avec l’alembic d’animation du plan (qui contient les personnages et les props déplacés). Mais avouez que c’est bête d’imposer l’export par plan d’un asset au complet quand seul un des objets qui le compose (un volet, une porte, etc.) est modifie… On a donc décidé de se lancer dans une quête un peu folle consistant à récupérer les modifications de placement des sous objets au layout pour pouvoir les appliquer sur l’asset original lors de la construction du plan au rendu. Pour être honnête, on eut pas vraiment le choix, le layout avait fait un gros travail de placement (il y a des contrôleurs, ils les bougent, c’est normal) quasiment plus aucun asset ne correspondait a sa position d’origine… Il fallait donc, pour les assets en questions, stocker la position des objets qui ne sont pas à leur placement d’origine. Sauf qu’il peut y avoir une différence entre le placement d’un contrôleur (que le layout déplace) et le placement de la géométrie qu’il contrôle. Le rig garantie (en principe, mais ce n’est pas le problème ici) que la relation de position entre un contrôleur et la géométrie qu’il contrôle est toujours la même. Mais il ne garantit pas que l’origine de la géométrie en question soit constante entre les versions de rig. Pas grave me diriez-vous, c’est pas le truc qui change souvent… Et devinez quoi ? Oui, c’est ce qui c’est passé, l’origine des géométries bougeait entre les versions de rig :smileFou: . Rappelez-vous, le rig n’a été défini que très tard sur le projet et chaque choix se confrontait a ce qui était déjà en place. On ne pouvait donc pas s’appuyer sur la position des géométries mais uniquement sur celle des contrôleurs… Bon, on a les positions des contrôleurs modifiant un asset au plan, mais comment je connais la relation avec la géométrie ? C’est ici que les histoires sérieuses de sanity check on commencées. Il a fallu être extrêmement rigoureux sur la façon dont les contrôleurs « non-déformant » étaient connectées aux objets qu’ils contrôlaient. De sorte qu’il n’y eut qu’une simple matrice (intitulée sobrement <em>offset matrice</em> ou « matrice de décalage ») à appliquer sur la position en espace monde du contrôleur pour obtenir la position en espace monde de la géométrie (matrice du contrôleur + offset matrice = position identique dans Maya et Guerilla !). Il fallait donc stocker, par publication de rig, l’offset matrice de chaque contrôleur « non-déformant » puis l’utiliser lors de l’import d’un asset dans un plan. Mais attendez ! Attendez ! C’est même pas fini en plus lol ! Certains objets nécessitaient une hiérarchie de contrôleurs non-déformant et il fallait que mon bordel fonctionne avec eux aussi ! XD Il fallait donc stocker des numéros indiquant la profondeur du contrôleur pour appliquer les offset matrice dans le bon sens ! :aupoil: Le wagon était le premier assez du genre. La porte du wagon étant coulissante et compose d’un second objet (la serrure de la-dite porte). Je me rappelle de ma scène Guerilla que je passais mon temps a builder en tournant mon wagon et les contrôleurs de la porte et de la serrure dans tous les sens, a tous les niveaux de la hiérarchie en croisant les doigts pour obtenir le même résultât. J’ai du y passer une ou deux semaines à plein temps en mode chien méchant mais ça été super robuste car c’est une partie du code que je n’ai plus du tout touche par la suite.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_015.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_015_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Cette séquence dans le wagon me fait toujours rire car Victor allume et éteint une lampe à huile en la touchant vaguement et certaines pommes sont posées sur des surfaces plates sans bouger alors que les plans subissent un <em>shake cam</em> pour simuler le mouvement des wagons :sourit: .</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_017.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_017_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Éclairer le contenu d’une boite fermée… Tout un programme. Si je me souviens bien les planches du fond et du dessus n’étaient pas visible des lights. Vous pouvez le remarquer sur les cheveux et le bras de Félicie. Les pommes étaient faites en particule. Je me rappelle avoir bossé sur un moyen de faire des variations de teinte sur le lookdev des pommes suivant un attribut de particule. Un effet subtil de variation de teinte.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_018.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_018_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Un des <em>money shot</em> du film. Un <em>money shot</em> (qui peut se traduire de deux façons suivant qu’on l’achète ou suivant qu’on le budget :hihi: ) est un plan qui est caractérisé par le cout nécessaire a sa fabrication et par l’effet <em>Woaw !</em> qu’il est supposé engendrer chez le spectateur. Celui-ci part du <a href="https://fr.wikipedia.org/wiki/Pont_d%27I%C3%A9na" hreflang="fr">pont d’Iéna</a>, passe entre les fondations de la tour Eiffel puis fini sur une vue d’ensemble. C’était LE plan test du builder. Beaucoup de bâtiment, beaucoup de géométrie, beaucoup de personnages, beaucoup de spéculaire, beaucoup de textures, beaucoup de matte painting, etc. Paradoxalement, le fait d’anticiper ces plans très tôt (dès le layout tu te fais une idée de la difficulté) fait qu’au final il n’a pas pose de problèmes majeurs. Les problèmes vraiment contraignant arrivent souvent sur des plans totalement anodins.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_020.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_020_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Celui-là je ne mets pas parce qu’il est joli ( :trollface: ) mais pour la petite histoire : Pour une raison qu’on ignore totalement, la texture des bouées (sur les bords du bateau) popait au rendu. On a jamais compris pourquoi et malgré tous nos tests (hashing des textures, hashing des uvs, compression, etc.) on a pas eu le temps de régler le souci suffisamment rapidement. Du-coup (et après discussion) elles ont été supprimées du bateau. Notez que ce n’est pas la règle. On ne supprime pas un truc à chaque fois qu’il nous embête, simplement qu’il faut constamment prioritiser les choses à faire, et parfois il faut trancher. Mais ce n’est jamais fait de gaité de cœur.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_021.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_021_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Ce plan (et la séquence d’une manière générale) fut le plan de test. On y est reste longtemps pour affiner nos méthodes d’exports d’alembic, de rendu sur la farm, déterminer quels AOVs on sort… Bref, pousser le pipeline pour faire, ce que j’appelle, la première boucle (un pipeline qui marche du début jusqu’au plan final composite). À l’époque il n’y avait pas de lighters ni de compositeurs, seuls les superviseurs de ces deux départements étaient présents. C’est, je trouve, un des plans les plus travaillé du film. La subtilité du rendu du sol ne se retrouve sur aucun plan. Il est aussi moins sature que le reste des plans.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_022.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_022_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Le plan qui suit. Notez la qualité du SSS de la peau, assez différente du reste du film.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_023.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_023_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Juste pour préciser qu’il n’existe aucun axe dans Paris qui permet cette vue sur l’opéra. :jdicajdirien:</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_024.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_024_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Encore un plan de cette séquence que je trouve magnifique. C’est exactement comme Paris : Des pavés luisants avec un petit brouillard de fond. Ce cadrage est repris plusieurs fois dans le film mais le rendu n’a rien à voir.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_025.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_025_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Les pavés sont 100 % displacement. On a essayé en normal map et tout mais vers la fin du plan, le sol apparaissait beaucoup trop plat.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_026.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_026_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Anecdote intéressante, le temps de rendu de l’AOV des pavés sur ce plan prenait énormement de temps car, étant en displacement ET de biais, les rayons de la caméra faisaient des tests d’intersections sur beaucoup de triangles avant d’en toucher un. Ce n’est pas forcément une bonne tactique d’avoir de gros temps de rendu sur des plans comme ceux-là qui risquent d’exiger beaucoup d’itérations avant d’être validé.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_027.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_027_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Ce décor a été fait sur plan. On avait énormément de livres au studio sur l’Opéra de Paris et même une petite maquette carton. Ce fut aussi un des candidats pour l’outil de récupération d’instance implicite car nombre d’objets se répètent. J’ajoute qu’il ne fut pas simple du tout à éclairer. Sur des plans rapprochés, on remplaçait les lights hors cadre chacun des lampadaires (4-5 par lampadaire, sans compter les bougies) par une seule light, plus grosse, mais a l’intensité et la couleur globale similaire. Cette méthode de « dégrossissement » du lighting permettait de diminuer le nombre de lights sur un plan (et donc diminuer le temps de rendu et le grain) sans modifier le retour visuel. Donc oui, gros travail sur ce décor.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_031.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_031_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_034.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_034_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Je vous présente mon personnage favori sur ce film : Odette. J’avais très peur que ses vêtements à base de laine ne passent pas ou mal à l’écran. Au final je trouve que ce sont les vêtements les mieux réussi du film. L’écharpe et le petit drape sur les épaules sont faits en displacement pour l’épaisseur de la laine. Ça fait toute la différence.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_035.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_035_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_074.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_074_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>J’ai remarqué un truc sur les longs-métrages : L’attachement des différentes personnes travaillant sur le projet aux personnages du film est réel et souvent révélateur de la profondeur du film. Si sur un projet, aucun personnage ne semble intéresser les gens ou donner envie d’en savoir plus, c’est que quelque chose n’est pas bien passe. Je ne parle pas d’animation, mais de chara-design : Le faciès, l’attitude, la voix et l’histoire du personnage doivent questionner, donner envie que le personnage réussisse (ou perde dans le cas du méchant). C’est assez caractéristique dans les films « commerciaux » ; il faut réussir à développer suffisamment de personnage pour qu’une cible de spectateurs puisse s’identifier. Merante avait aussi une histoire très développée.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_033.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_033_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_062.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_062_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Les plans de nuit sont rares dans les films d’animation. Pourtant, une grande partie de Ballerina se passe la nuit ce qui donne des effets super intéressants. L’éclairage du visage d’Odette par la lampe (blanche et froide vous aurez remarqué) de la grande méchante, ainsi que l’alternance classique bleu marin/orange cache une décision plus rationnelle : À l’origine, énormément de plans devait se dérouler dans les rues de Paris. Une des premières décisions fut de s’arranger pour que ces plans se passent de nuit de manière à diminuer le nombre de personnage secondaires. C’est encore plus tard que presque tous les plans de Paris disparurent au profit de plans dans la cour intérieure.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_036.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_036_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>L’éclairage des escaliers se révèle toujours assez compliqué. En effet, il faut beaucoup de lumières éclairant différentes parties distinctes, mais le <em>path tracer</em> a souvent du mal à deviner quelle lumière éclaire quelle partie, ce qui amène facilement du grain.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_037.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_037_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Ce plan fut assez compliqué à faire en raison du nombre de miroir et d’AOV nécessaire a la recomposition de l’image. En effet, quand on a une réflexion pure (un miroir) il faut trouver un moyen de propager les AOV dans la réflexion pour pouvoir les sortir séparément. Ensuite, il faut recompositer chacun des miroirs pour finalement les intégrer au plan. Bon, le problème vient du fait qu’il fallait aussi fournir une Z de qualité pour la version stéréo. Il fallait donc aussi propager la Z.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_038.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_038_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Ce plan fut mon plan de test pour les outils de projection en matte painting. J’avoue ne plus me rappeler du tout pourquoi la projection était nécessaire mais que j’étais trop fier quand mes petits calculs me rendaient une projection « pixel-perfect » du rendu dans Nuke !</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_039.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_039_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Le directeur de l’Opéra fut le premier adulte en lookdev. Il a été utilisé pour par mal d’expérimentation concernant le rendu des visages et en particulier le nez qui devait avoir des bords bien définis. L’origine remonte a une décision de rig qui avait demande au modeling de diminuer la complexité des maillages des visages, cassant ainsi les bordures du nez. Je pense qu’on aurait pu faire différemment, mais je n’avais pas mon mot à dire. Bref, donc : À la charge du rendu d’afficher quelque chose de convaincant. On est naturellement parti sur des normales mais le problème du <em>normal mapping</em> basse fréquence c’est qu’il commence à devenir bizarre en gros plan, quand l’axe de la caméra devient tangent à la surface (en faisant apparaitre une sorte de liseré sur la couche de réflexion). J’ai donc dû me pencher sur un peu de math pour proposer un moyen de switcher du normal map au displacement suivant les plans. Pour être honnête, je ne suis pas sûr que le displacement fut utilise sur beaucoup de plans.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_040.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_040_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Il a eu un truc de vraiment problématique avec les intérieurs de l’Opéra de Paris et en particulier ce plan : Tout est plat et répétitif. Je me rappelle d’un couloir tellement basique que le DA était assis près du superviseur ligthing pendant plusieurs jours pour réussir a faire un truc « regardable ». Au final, la réalisation et l’animation des personnages prends le dessus et on y attache peu d’importance mais oui, éclairer des murs plats ce n’est pas la joie.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_041.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_041_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_064.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_064_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Cette pièce est le premier « gros » intérieur qui a été préparé pour le rendu. Il apparait en effet sur beaucoup de plans du teaser. Lui aussi était dur à aborder car si on enlève les parquets, il y a peu de zones vraiment intéressantes en termes d’éclairage. Notez aussi que tout l’éclairage est diffus et il y a peu d’éclairage direct. Anecdote intéressante : Un grand miroir se situe au centre de la pièce mais posait pas mal de soucis autant techniques que narratifs. Au final, un grand drap a été mis dessus ni vu ni connu.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_043.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_043_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Ici, la manche de Victor est en gros plan. Le lighting a donc choisi d’appliquer du displacement pour ajouter du détail et de la nuance dans la forme. Mais comme le rig n’était pas recentrable, les vertices de la géométrie vibraient légèrement. En soi, ce n’est pas trop problématique quand les vertices sont éloignés les uns des autres. Mais quand on displace, il y a un vertice tous les deux/trois pixels, du-coups le flickering commence à se voir (sous la forme d’un léger « bourdonnement » de la manche). Comment régler ce souci comme un vrai pro ? On prend la scène d’anim, on décale le personnage d’une distance particulière en vu de le rapprocher du centre (2000 en x par exemple), on ré-exporte l’alembic, on met à jour l’alembic dans Guerilla, puis on décale le personnage de 2000 en x dans l’autre sens. :aupoil:</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_044.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_044_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Ce plan séquence fut surement le plus compliqué du film. Il a pris plus d’un mois a un graphiste senior à plein temps pour en venir à bout. Chaque personnage était présent deux à trois fois dans la scène et le layout les a faits s’alterner pour donner l’impression d’une continuation. L’éclairage était un cauchemar (extérieur, intérieur puis extérieur) du-coup beaucoup de choses ont été séparé ce qui est très risqué car cela entraine souvent de la mauvaise intégration et une difficulté à garder un éclairage cohérent (c’est souvent le cas sur les plans « sur-composités »). Et pourtant, j’avoue ne pas lui trouver de défauts. Franchement le boulot est impeccable de bout en bout.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_057.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_057_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Regardez les variations de positionnement des volets. C’est à ça qu’a servi le travail que je vous exposais plus haut avec l’histoire de la porte du wagon. Vous allez me dire que tout le monde s’en fout mais tous les objets ont bénéficié d’un travail de cadrage (on parle souvent de « set-dressing ») de la part du layout et je pense vraiment que ça rend l’image plus naturelle.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_059.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_059_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_060.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_060_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_061.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_061_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Cette séquence fut une des premières à être éclaire en utilisant le principe des shot groups, par un seul graphiste. L’ensemble est hyper homogène.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_063.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_063_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Hahaha ! Je me rappelle bien cette séquence : L’échelle du projet était de 1 unité = 10 cm. C’est peut-être peu commode mais ça résolvait pas mal de soucis. C’était un bon compromis. Le FX guy avait mal configuré son échelle de décimation. Du-coup, le premier export des volumétriques de ce plan était monstrueusement énorme, genre un triangle par pixel ! Mais le pire c’est qu’on avait mis en place un système qui appliquait une subdivision de 1 a tous les mesh :IFuckTheWorld: . <a class="media-link" href="https://www.fevrierdorian.com/blog/public/gif/atomic_bomb.gif">Resultat</a>.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_065.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_065_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Le toit de l’opéra était un des assets les plus massifs en termes de travail pour les textures. Les instances implicites ont eu un gros impact sur le quotidien des graphistes travaillant sur cet asset.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_067.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_067_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>L’atelier fut le plan de test ultime pour la builder. Il y a des objets partout, dans toutes les positions. Si ça build correctement, tout le film devrait marcher (pas vrai ? :siffle: ).</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_070.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_069_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_070_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Dans le bar breton, un des drapeaux fait référence à l’endroit où ce film a été fabriqué. Saurez-vous trouver lequel?</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_072.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_072_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Un plan pour lequel je me serai beaucoup battu, mais qui aura eu raison de moi. Je ne sais pas si vous avez déjà fait des soirées (nuits ?) à Paris mais la réflexion des éclairages publiques sur les pavés (très sombres) est très présent, quand bien même il n’a pas plu. Sur ce plan la réflexion des pavés était trop plate, car la normal map semblait devenir neutre après quelques mètres, donnant un effet tout lisse. Malgré mes tentatives pour redonner un peu de consistance au sol, la réflexion a été très atténuée pour pouvoir livrer à temps. Au final il passe très bien dans le film. J’ai juste un blocage personnel dessus.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_079.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_075_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_079_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>OK, j’avoue, ce projet a développé en moi un fétichisme pour les vieux parquets bien lustrés… :pasClasse:</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_080.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_080_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Pour faire valider les diamants de la couronne de ce personnage, les lookdev a dû honteusement tricher en augmentant l’intensité de ces derniers aux points qu’ils créaient de la lumière. Path tracer oblige, on se retrouvait avec des points blancs sur les plans ou ce personnage apparaissait. On a mis un peu de temps à trouver, mais des petits points blancs dans un plan (appelés <em>fireflies</em>) sont souvent dû à un shader qui renvoi une haute valeur.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_084.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_084_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Comme bien souvent avec les « trucs à plume », la poule est un asset qui a demande beaucoup de travail, avec du Yeti et quelques morceaux de shader écris pour l’occasion. Un des running gag original du film consistait à la faire revenir plusieurs fois a l’écran tout au long du film.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_085.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_085_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Ce plan est très différent des autres en termes de colorimétrie. On est beaucoup plus sur des teintes Pixar/Illumination. Au passage, le personnage de la mère de Félicie fut la favorite d’une partie de l’équipe pendant longtemps mais le fait qu’elle apparaisse qu’une seule fois (sur un plan fait vers la fin de la production), n’a, je pense, pas permis aux différentes personnes intervenants dessus d’y passer le temps nécessaire.</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_086.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_086_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Tu passes une prod à régler les soucis de hairs des personnages, les uns après les autres. Au bout d’un moment tout marche, tu penses que les problèmes de hair c’est fini, tu as déjà fêté ça il y a deux mois et BAM ! Tu as un nouveau plan qui tombe un peu avant la fin du projet. Avec au programme, un nouveau perso, visible uniquement sur ce plan, en gros plan avec les cils et les sourcils qui flickent !</p>
<p style="text-align: center;"><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_088.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_088_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/post_mortem_ballerina_comment_096.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_03_27_post_mortem_ballerina/commentaire_plan/.post_mortem_ballerina_comment_096_m.jpg" style="margin: 0px auto; display: table; width: 560px; height: 236px;" /></a>Je vais finir ce commentaire avec deux plans et un petit mot concernant l’animation. On l’entend souvent, l’animation est le truc le plus important et c’est vrai : Si les personnages ne sont pas « vivants », c’est terminé. Le lighting et le compo n’y pourront pas grand-chose. Le <em>chara-design</em> de Ballerina c’est pas le plus simple à aborder. Pour une raison qui m’échappe (je ne m’occupais pas du département anim), je trouve l’animation sur le projet inégal, mais il faut avouer que certains plans sont de belles réussites. Le plan du dessus est le tout dernier d’une séquence qui monte en puissance ou Félicie, malgré n’avoir plus aucune chance de devenir ballerine enfile ses chaussons, commence quelques pas puis fait des gestes de plus en plus complexe pour finir sur une pirouette plein plan le regard vers le dos de la caméra (Paris) avec deux sentiments en même temps sur son visage. Un sentiment clair, c’est bien pour du cartoon. Mais ce qui rend un personnage en 3d humain c’est en grande partie la manière dont il exprime (ou tente de cacher) l’ambivalence de ses sentiments. Ce sont des attitudes qu’on retrouve fréquemment dans le film. Disney excellait dans sa capacité à le faire en 2d et peu de films y arrivent vraiment. Je trouve que Ballerina s’en sort très bien là-dedans.</p>
<h3>Conclusion</h3>
<p>Il est bien évident que tout ceci est un effort collectif, nous n’étions jamais seuls à faire des choix et c’est sûrement ce qui a permis au projet d’aboutir. Je n’ai pas nommé les personnes, mais il me semble important de rappeler que c’est pourtant avec de l’humain que tout se joue, du haut de la hiérarchie jusqu’au graphiste parfois derrière des tableaux à vérifier que l’intégralité des retakes des plans ont été faites (et pour qui je peux vous garantir que ce n’est pas la tasse de café). Si personne ne le veut, le film ne sort pas, c’est aussi simple que ça.</p>
<p>Je ne parle pas d’aller se tailler les veines à faire des heures supplémentaires pendants des mois pour le plaisir de montrer qu’on souffre à la tâche mais au contraire d’une volonté farouche que tout ce passe pour le mieux à tous les niveaux et sur la longueur. Je n’ai dû faire qu’une vingtaine d’heures supplémentaires (pour finir et peaufiner le builder si ne me souvient bien car « Pas d’builder : Pas d’projet… »). Le fait de voir qu’il y a une volonté générale de réussite du projet et de bienveillance mutuel (OK je m’enflamme peut-être un peu sur le dernier mais quand même c’est l’idée) pousse les gens à fournir l’effort supplémentaire nécessaire à la sortie d’images propre et livrées dans les temps. La peur de se faire virer n’est pas déterminante, chacun sait qu’une fois le projet fini on passe (presque tous) à la porte. À partir de là, ce qui compte pour chacun c’est de faire un travail dont il est fier. À quoi bon livrer des images, sensé divertir les gens qui les regarde, faites dans la tension et dont tous le monde garde un gout amer ? À mes yeux, et malgré un double démarrage difficile, Ballerina semble également avoir réussi sur ce terrain et pour un premier long-métrage, c’est la classe ! :laClasse:</p>
<p>À bientôt !</p>
<p>Dorian</p>
<p style="text-align: center;">:marioCours:</p>Personnaliser ses AOV dans Guerillaurn:md5:c4b7ef687147cc53a32e497dc6c447462017-05-28T18:20:00+02:002017-06-01T10:56:11+02:00NarannInfographie 3D - Boulotaovguerillarendu<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov_tn.png" style="float: left; margin: 0 1em 1em 0;" />Ça fait un moment que je n’ai pas fais de tutoriel. En voici un tout petit principalement à destination des étudiants utilisant ou voulant (les biens inspirés :hehe: ) utiliser Guerilla pour leur projet de fin d’année.</p>
<p>L’idée étant de faire un tuto pour sortir un AOV personnalisé assignant une couleur suivant les objets. Nous allons ici utiliser de simples couleurs (rouge et bleu dans le cas d’un masque) mais sachez que vous pouvez remplacer cette couleur par une texture, de l’occlusion ou pleins d’autres choses.</p>
<p>En avant ! :enerve:</p> <p>Ouvrez Guerilla. La première chose à faire c’est une petite scène de test. Un plan, une sphère c’est parfait :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov_001.png" style="margin: 0 auto; display: table;" /></p>
<p style="text-align: center;">Avec ça, Votre projet de fin d’étude semble bien partis. Encore un peu de boulot et Pixar pleurera devant vos rendus. :gniarkgniark:</p>
<p>Ouvrez le <em>rendergraph</em> par défaut, glissez-déposer la sphère et le plan depuis la <em>Node List</em> pour créer un <em>path</em> puis créez un <em>material override</em> (Ctrl+Espace dans le <em>rendergraph</em>, tapez « mat », puis Entrer). Enfin connectez-moi tout ça sous cette forme :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov_002.png" style="margin: 0 auto; display: table;" /></p>
<p>Je traduis le graph ci-dessus : L’objet <em>Sphere</em> va se faire <em>overrider</em> un paramètre par le nœud <em>MaterialOverride</em>. Cette opération est ensuite fusionnée aux modifications de la branche principale via un nœud union. Il en va de même pour l’objet <em>Plane</em>. :zinzin:</p>
<p>Ensuite, on clique sur <em>MaterialOverride</em>, on tape le nom de l’<em>override</em> que l’on souhaite créer. Ici (par exemple) « MyMask1 » de type <em>Color</em>. On assigne la couleur rouge. On fait de même pour le <em>MaterialOverride</em> du <em>Plane</em> mais de couleur bleu :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov_003.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/.guerilla_custom_aov_003_m.png" style="margin: 0 auto; display: table;" /></a></p>
<p>Information intéressante à cette étape : Si vous cliquez sur le petit « M » à droite, vous pourrez sélectionner un sous-shader, notamment <em>Texture</em>, <em>Occlusion</em> et <a href="http://guerillarender.com/doc/1.4/Library_Attributes_Cloud.html" hreflang="en">Cloud</a>. Si vous êtes joueur et que vous souhaitez rendre des attributs présents dans vos alembic vous pouvez regarder du côté de <em>PrimVar</em> :hehe: ).</p>
<p>Maintenant nous avons des <em>overrides</em> sur des objets. Il faut pouvoir les rendre ! :grenadelauncher:</p>
<p>Créez un nouvel AOV (Cliquez sur le + à cote de l’AOV de <em>Beauty</em>). Renommez-le comme vous le voulez (je l’ai appelé « MyMask1 » mais en fait ce n’est pas important ici):</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov_004.png" style="margin: 0 auto; display: table;" /></p>
<p>Cliquez dessus puis, dans le champ <em>Shader Color</em>, mettez « MyMask1 » (c’est là qu’il faut mettre le même nom que les noms des <em>overrides</em> créés précédemment):</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov_005.png" style="margin: 0 auto; display: table;" /></p>
<p>Faites un rendu (Ctrl+R) puis sélectionnez l’AOV « MyMask1 » (dans le menu déroulant en bas à gauche de la <em>RenderView</em>) :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov_006.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/.guerilla_custom_aov_006_m.png" style="margin: 0 auto; display: table;" /></a></p>
<p>Et une petite image finale pour avoir une vision d’ensemble. :popcorn:</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/guerilla_custom_aov.png"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2017_05_27_aov_personalise_guerilla/.guerilla_custom_aov_m.png" style="margin: 0 auto; display: table;" /></a></p>
<p>En espérant que ça vous mette le pied à l’étrier, n’hésitez pas à creuser un peu et à expérimenter. :joue:</p>
<p>À bientôt!</p>
<p>Dorian</p>
<p style="text-align: center;">:marioCours:</p>