Dorian Fevrier's blogJe 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>Crash du viewport Maya : L’option Consolidate Worldurn:md5:1f013f97cc7aa87095994d3998d9c1922023-02-26T17:55:00+01:002023-02-28T19:13:29+01:00NarannCrashs et bugsanimationconsolidate worldcrashgpumayaviewport<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_02_26_consolidate_world/consolidate_world_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 147px;" />Aujourd’hui on va parler d’une option bien spécifique du viewport de Maya : <em>Consolidate World</em>.</p>
<p>On va d’abord définir ce qu’est la consolidation et comment elle a été mise en place dans le Viewport 2.0. On finira par lister des cas spécifiques d’utilisation avec lesquels cette option est difficilement compatible et où la documentation elle-même propose de le désactiver. :aupoil:</p> <p>Note : Dans ce billet, le mot « rendu » fait référence au rendu temps-réel, sur GPU. On y est pas forcément habitué, alors je préfère le préciser. :reflechi:</p>
<h3>Qu’est-ce que la consolidation ?</h3>
<p>On va s’éviter un cours sur le fonctionnement d’un GPU, mais en très gros, un GPU ne peut « rendre » qu’un seul matériau à la fois. Quand il y a deux matériaux, il rend deux fois avant d’afficher l’image sur le moniteur. Un rendu est appelé un <em>draw call</em>, il peut donc y avoir plusieurs <em>draw call</em> pour une image affichée.</p>
<p>Dans le cadre d’un GPU, un matériau est à comprendre comme « programme qui calcule un pixel donné » ; souvent la surface d’une géométrie en fonction de sa normale, d’une source de lumière, etc. Le terme technique est <em>shader</em>. Les inputs d’un objet (la normale, les textures) ne font pas partie du <em>shader</em>, ils sont définis à l’objet et sont passés au <em>shader</em> juste avant son exécution, au moment du calcul du pixel. Changer de <em>shader</em> est assez couteux, car il nécessite de redéfinir une bonne partie du pipeline du GPU : On change le programme à exécuter (<em>shader</em>), et les blocs d’entrées et de sortie de ce programme (le terme technique est <em>device state</em>). Ce fonctionnement est spécifique au rasterizer GPU, on pourrait presque parler de contrainte matériel. Ce problème n’existe pas en <em>path tracing</em> (disons qu’on a d’autres problèmes :pasClasse: ).</p>
<p>Ce qu’on appelle la « consolidation », c’est le fait de regrouper le rendu des objets utilisant un même <em>shader</em>, afin d’utiliser le minimum de <em>draw call</em> ; on rend une seule fois beaucoup d’objets.</p>
<p>C’est comme ça que fonctionnent les jeux vidéos : Ils ont assez peu de <em>shaders</em> et chaque objet dispose de ses paramètres propres. L’art du rendu temps-réel consiste à faire le moins de <em>draw call</em> possible en jouant avec les paramètres par objet et les buffers de rendu. Je vous invite à lire la présentation <a href="https://advances.realtimerendering.com/s2016/Siggraph2016_idTech6.pdf">The devil is in the details</a> sur le pipeline de rendu de Doom 2016, qui utilise une centaine de <em>shaders</em>, pas plus. :jdicajdirien:</p>
<h3>Maya dans tout ça</h3>
<p>Dans ses premières années, avant le Viewport 2.0, Maya affichait chaque nœud de shape indépendamment. Il y avait (quasiment) autant de <em>draw call</em> que de nœud de shape à afficher. C’est pour ça qu’il était facile d’écrire ses propres locators en OpenGL. C’était surtout inefficace, car plus il y avait de nœuds, plus il y avait de <em>draw call</em>, et c’est là qu’intervient le Viewport 2.0, pour unifier (entre autres) l’ordre dans lequel les choses sont affichées.</p>
<p>VP2 fonctionne comme une base de donnée dédié à l’affichage. Fini la belle époque où l’on pouvait afficher n’importe quoi grâce à des appels OpenGL personnalisés. Avec VP2 on met des choses à afficher (appelées <em>Render Items</em>) dans une boite dédiée et il s’occupe du reste ; c’est lui qui contrôle de pipeline d’affichage.</p>
<h3>Plus de détails sur VP2</h3>
<p>La période de transition entre le « Viewport Legacy » et le « Viewport 2.0 » a été longue, et ce dernier resta longtemps optionnel. Comme vous le devinez, les développeurs de plugins tiers ont dû recoder la partie graphique de leur plugin pour la rendre compatible VP2.</p>
<p>C’est pour les aider qu’Autodesk sortie en 2017 une série de documents « Viewport 2.0 API Porting Guide » :</p>
<ul>
<li><a href="http://images.autodesk.com/adsk/files/VP2_API_Porting_Guide0.pdf">Part 1: Basics</a></li>
<li><a href="https://images.autodesk.com/adsk/files/VP2_API_Porting_Guide_Details0.pdf">Part 2: Porting Details</a></li>
<li><a href="https://images.autodesk.com/adsk/files/VP2_API_Porting_Guide_for_Locators.pdf">Locators</a></li>
</ul>
<p>La première chose qui vient en tête quand on lit la table des matières de ces documents, c’est à quel point ils pourraient vous aider à briller en soirée, entre une citation de Proudhon et Hanouna. Votre serviteur a essayé, ça ne fonctionne pas. Ces documents n’aident en rien, le problème ne vient donc pas de là. :petrus:</p>
<p>Non, ce que je vous propose c’est une traduction partielle d’une section du premier document, qui concerne spécifiquement la consolidation. <em>Section 3.6 : Categorization and Consolidation</em>, sous section <em>Consolidation</em> (page 9) :</p>
<p>Les <em>Render Items</em> sont catégorisés en séries de listes (appelé <em>buckets</em>), suivant qu’ils soient persistants ou transitoires, ainsi que d’autres paramètres d’affichage. […] La catégorisation peut être utilisée pour regrouper les items ayant des propriétés d’affichage similaires, ce qui peut améliorer les performances en évitant des changements de <em>device states</em> inutiles.</p>
<p>La consolidation est une fonctionnalité de VP2 utilisée pour diminuer le « problème des petits paquets » (<em>small batch problem</em>) : C’est-à-dire lorsque le coût des appels systèmes requis par la préparation et l’envoi d’énormément de petits paquets de géométries entraîne des pertes de performances importantes. Pour aider à résoudre ce problème, les <em>Render Items</em> peuvent être consolidés (fusionnés). Notez que, par conséquent, les <em>Render Items</em> originellement créés peuvent ne pas être ceux utilisés au rendu. Par défaut, VP2 utilise un mode hybride, combinant la consolidation statique traditionnelle et la consolidation <em>multi-draw</em>.</p>
<ul>
<li>La consolidation statique traditionnelle améliore la vitesse du rendu des shapes totallement statiques.</li>
<li>La consolidation <em>multi-draw</em> améliore la vitesse du rendu des shapes pour les animations matricielles (c-à-d. les translation/rotation/scale) et statiques, cependant, il nécessite <em>OpenGL Core Profile</em> et des pilotes récents. […]</li>
<li>Le mode hybride permet à certains <em>Render Items</em> de basculer dynamiquement entre les deux modes. Par exemple, lorsqu’une shape statique démarre une animation matricielle, ses <em>Render Items</em> seront sortis de la consolidation statique traditionnelle et reconsidérés, pour une consolidation <em>multi-draw</em>.</li>
</ul>
<blockquote>
<p>Note : Le multi-draw, c’est le fait de dessiner un même objet plusieurs fois, mais avec des transforms différents, en un seul rendu.</p>
</blockquote>
<p>Je m’arrête ici, le document continue, et nécessite une compréhension un peu plus poussée de ce qu’est un <em>Render Item</em>.</p>
<h3>Ce qu’en dit la documentation</h3>
<p>Comparé aux documents dédiés aux développeurs, <a href="http://help.autodesk.com/view/MAYAUL/2023/ENU/?guid=GUID-9BBB6035-2A02-41BB-AF2D-99D9BEE580F1">la documentation</a> n’entre pas énormément dans les détails, mais donne une idée du problème que l’option <em>Consolidate World</em> cherche à résoudre. Voici la traduction :</p>
<p>Cette option essaie de combiner les caches de géométries des shapes utilisant des matériaux identiques. Cela peut drastiquement améliorer les performances de nombreuses scènes, en échange d’une utilisation mémoire plus importante.</p>
<p>Quand il combine les géométries, <em>Consolidate World</em> déplace les vertices de plusieurs objets dans un nouvel espace local (<em>object-space</em>) partagé. Donc si votre shader de plug-in s’appuie sur les coordonnées locales d’un objet, <em>Consolidate World</em> cassera ces coordonnées et votre shader risque de ne pas s’afficher correctement (Ndt : Plus d’informations sur la page <a href="https://help.autodesk.com/view/MAYAUL/2023/ENU/?guid=GUID-95E982CC-F95F-47F9-9CAA-DCBFD9907A31">Troubleshoot working with CgFX shaders</a>, le <a href="https://help.autodesk.com/view/MAYAUL/2022/ENU/?guid=GUID-F81B34C5-30B8-4784-8351-F252006C42D8">tuto GLSL</a>, ainsi que celle sur <a href="https://help.autodesk.com/view/MAYAUL/2023/ENU/?guid=Maya_SDK_Viewport_2_0_API_Part_Two_Of_Semantics_and_annotations_html">la sémantique des shaders</a>).</p>
<p>Pour utiliser cette fonction, les normales des vertices doivent être renormalisées. Par conséquent, les matériaux n’utilisant pas de normales de longueur unitaire ne sont pas compatibles avec <em>Consolidate World</em>.</p>
<p>Quand un objet change/est déformé, il est non-consolidé. Il est ensuite reconsolidé s’il reste inchangé pendant quelques frames. L’option <em>Consolidate World</em> peut donc être à l’origine d’un objet s’affichant différemment dans le viewport peu de temps après avoir été modifié ou désélectionné. Si cela vous gêne, désactiver cette option.</p>
<p>Astuce : Si votre Maya devient instable, essayez de désactiver cette option.</p>
<p>Important : Certaines opérations subissent de légers retards quand cette option est activée.</p>
<p>Voilà pour la seconde traduction. On note une confiance assez relative dans leur feature. :seSentCon:</p>
<p>La section <a href="https://knowledge.autodesk.com/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2022/ENU/Maya-Rendering/files/GUID-B7C04719-3D25-4A02-9F90-96208FB223C2-htm.html">Optimize Viewport 2.0</a> de la documentation ajoute des informations intéressantes :</p>
<p>L’option <em>Consolidate World</em> améliore les performances en combinant les géométries de tous les objets dans le champ de la caméra afin de réduire le nombre d’appels au GPU. Cette option est activée par défaut.</p>
<p>Pour profiter au maximum de <em>Consolidate World</em>, mutualisez les shaders autant que possible, en particulier sur les petits objets statiques. Vous pouvez le faire en sélectionnant F<em>ile > Optimize Scene Size</em> et activer l’option <em>Remove duplicate: Shading networks</em>. Pour ne visualiser qu’un seul matériau dans votre viewport et ainsi bénéficier au maximum de <em>Consolidate World</em>, vous pouvez activer <em>Shading > Use Default Material</em> dans le menu Panel. Si les matériaux ne sont pas nécessaires à votre workflow, cela améliore considérablement les performances dans les scènes lentes.</p>
<p>Remarque : L’activation de <em>Consolidate World</em> augmente l’utilisation mémoire du GPU. Voir <a href="https://knowledge.autodesk.com/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2022/ENU/Maya-Rendering/files/GUID-370E7A71-6570-4277-9C9E-827BEF94FF28-htm.html">Manage GPU memory for Viewport 2.0</a> pour plus d’informations.</p>
<p>C’était la dernière traduction de ce billet ! :redface:</p>
<p>Notez que le frustrum de la caméra semble être pris en compte par la consolidation.</p>
<h3>Résumé</h3>
<p>S’il fallait résumer très brièvement, la consolidation c’est le fait d’assembler toutes les géométries pouvant être rendu ensemble, en une seule fois, afin de générer le minimum de <em>draw call</em>.</p>
<p>Par exemple, en admettant que nous ayons deux objets statiques (des plans), utilisant le même matériau :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2023_02_26_consolidate_world/index_change.png" style="margin: 0px auto; display: table; width: 500px; height: 664px;" />En haut, il y a deux tableaux de points (<em>S0</em> et <em>S1</em>) pour deux objets. Ils sont donc affichés séparément, où, au mieux, en <em>multi-draw</em>. Si on combine ces deux objets en un seul tableau de points (<em>S0)</em> on peut afficher les surfaces en un seul rendu, comme s’il n’y avait bien qu’un seul objet.</p>
<p>Le schéma ci-dessus ne montre que les indices des points, mais cette réévaluation doit se faire également pour le tableau de positions, UVs, normales, etc. ; c’est un nouvel objet qui est créé dans le buffer du GPU.</p>
<p>Ça c’est pour la théorie. :dentcasse:</p>
<h3>Le vrai monde</h3>
<p>On imagine facilement que ce genre de mécanisme fonctionne assez bien en jeu vidéo, qui détermine à l’avance si les objets sont statiques ou non. :gne:</p>
<p>En pratique, la liberté laissée par notre logiciel chéri peut mettre à mal ce mécanisme qui peut se retrouver sur-sollicité, en particulier en animation ; plusieurs personnages animés en blocking + translate, caméra et objets qui bougent, puis s’arrêtent, etc. On comprend assez vite que l’approche consistant à organiser le rendu du GPU suivant qu’un objet est statique, transformé ou déformé peut être mise en défaut avec le travail d’animation, où le contenu de la scène est réévalué à chaque frame.</p>
<p>Le réindexage en blocs de rendu peut être constant si bien que, malgré que <em>Consolidate World</em> devrait en principe toujours fonctionner (il est activé par défaut), il n’est pas rare qu’il se retrouve être à l’origine de nombreux crash. Parfois c’est le pilote qui est responsable, parfois c’est Maya. J’imagine que le multithreading et l’asynchronie rendent cette feature complexe à maintenir dans un logiciel comme Maya.</p>
<p>Comme vous l’avez remarqué plus haut, la situation est telle que la documentation recommande carrément de la désactiver « en cas d’instabilité ». :bete:</p>
<h3>Le doute</h3>
<p>À ce stade, il est difficile d’affirmer quoi que ce soit sur les crashs rencontrés en animation. En effet il n’est pas simple de déterminer la raison exacte d’un crash, en particulier ceux liés à l’affichage. Ces derniers sont assez difficiles à isoler. Quand ils le sont, ils peuvent être corrigés sur une version, puis un autre problème apparait, et il suffit, une fois encore de désactiver <em>Consolidate World</em> pour que le problème disparaisse…</p>
<p>Je reviens sur mon hypothèse celons laquelle cette feature est complexe à maintenir correctement dans un logiciel comme Maya, dont une partie de l’affichage est gérée de façon asynchrone. Je pense que <em>Consolidate World</em> restera une option dont le fonctionnement sera aléatoire en animation, d’où mon conseil de la désactiver en production ; en particulier en animation, où le temps de l’animateur est précieux.</p>
<p>Voici quelques exemples trouvés ici et là qui laissent entendre que <em>Consolidate World</em> est la raison de leur problème. Si on ne peut pas affirmer que la consolidation effectivement est l’origine du problème, l’accumulation des cas fait peser des doutes sur la stabilité de cette option.</p>
<p><a href="https://matthewhamill706.wordpress.com/2018/05/05/technical/">Matthew's Animation Antics - Software Problems</a> :</p>
<blockquote>
<p>As the rigs got more complex throughout the animation process it became more apparent that more had to be done to speed up the scenes. Natasha found that turning off “consolidate world” option in the Viewport 2.0 options prevented any further crashes.</p>
</blockquote>
<p><a href="https://www.reddit.com/r/Maya/comments/2t6uwr/disappearing_texture_problem/">Reddit - disappearing texture problem</a></p>
<blockquote>
<p>Even though you fixed the problem, good chance it has something to do with Viewport 2.0's "Consolidate World" feature. Sometimes I get texture issues in the Viewport and I've narrowed it down in the past to that.</p>
</blockquote>
<p><a href="https://www.filehorse.com/download-autodesk-maya/change-log/">Change-log Maya 2018</a> :</p>
<blockquote>
<p>- Consolidate World breaks geometry override with custom resource handles<br />
[…]<br />
- Crash when selecting node with custom resource handles with consolidate world on</p>
</blockquote>
<div class="hLrx8">
<div class="ThqSJd">
<p class="KPwZRb gKR4Fb" jsname="GNEpNe"><html-blob><a href="https://groups.google.com/g/python_inside_maya/c/5vJQJwMl6Pg">cmds.playblast and VP2.0, processing nag</a> :</html-blob></p>
</div>
</div>
<blockquote>
<p>My bet is one the performance options in the Viewport 2 option dialog, such as Vertex Cache and Consolidate World. I suspect there might be some refreshing going on.</p>
</blockquote>
<p><a href="https://www.animschool.edu/DownloadOfferResponse.aspx?key=kim263-SM62288I">Malcolm 2.0 Public Version</a> :</p>
<blockquote>
<p>Next try: Viewport, Renderer>Viewport 2.0 Options, Turn off Consolidate World.</p>
</blockquote>
<p><a href="https://nitter.fdn.fr/mr_moutch/status/1579077970895212544#m">My scene has crashed 5 times in the last hour, anything i could look to troubleshoot it?</a></p>
<blockquote>
<p>Meanwhile, one of our Rigger found a surprising workaround : In the VP2 performance settings, disable the "Consolidate World" option</p>
</blockquote>
<p><a href="https://learn.ragdolldynamics.com/releases/2021.05.10/">Mimic</a> :</p>
<blockquote>
<p>In Maya 2018, consolidation is disabled to facilitate this shader. It shouldn't affect anything of value, you probably don't even know what it is. But if you'd rather it didn't do that, untick the "Maya 2018 Consolidate World Fix" in the Ragdoll Preferences and reload the plug-in or restart Maya.</p>
</blockquote>
<h3>Conclusion</h3>
<p>Si vous avez des crashs en animation, désactivez <em>Consolidate World</em> et croisez les doigts.</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>Récupérer les valeurs par défaut de kickurn:md5:061970a8b7419d2bdd37cb7e76e51e582022-12-10T23:47:00+01:002023-01-24T13:03:50+01:00NarannScript et code.assarnoldcommandekickpythonrendu<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_12_10_kick_default/kick_default_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 128px; height: 128px;" />Aujourd’hui, un petit bout de code très simple pour récupérer les valeurs par défaut que Arnold (ou la ligne de commande <em>kick</em>) donne à ses nœuds quand on ne les lui fournit pas.</p>
<p>Ce type d’information est utile, entre autres, dans un pipeline qui génère ses <code>.ass</code> lui-même. :siffle:</p>
<p>Bonne lecture ! :sauteJoie:</p> <h3>La ligne de commande <em>kick</em></h3>
<p>La première chose à faire avec une ligne de commande, c’est de regarder d’accéder à sa page d’aide :</p>
<pre>
<code class="language-bash">$ kick --help</code></pre>
<p>Dès lors, on identifie rapidement deux arguments intéressants :</p>
<pre>
<code>-nodes [n|t] List all installed nodes, sorted by Name (default) or Type
-info [n|u] %s Print detailed information for a given node, sorted by Name or Unsorted (default)
</code></pre>
<p>L’argument <code>-nodes</code> liste les nœuds et <code>-info</code> donne des informations sur les paramètres de ces nœuds :</p>
<pre>
<code>$ kick -nodes
built-in nodes sorted by name:
abs shader
add shader
alembic shape (procedural)
ambient_occlusion shader
aov_read_float shader
aov_read_int shader
...</code></pre>
<p>Le nœud <code>options</code> étant le nœud de paramètres de rendu d’Arnold, il y a de fortes chances que la ligne de commande qui vous intéresse en venant ici soit :</p>
<pre>
<code>$ kick -info options
node: options
type: options
output: (null)
parameters: 119
multioutputs: 0
filename: <built-in>
version: 7.1.4.1
Type Name Default
------------ -------------------------------- --------------------------------
INT AA_samples 1
INT AA_seed 1
FLOAT AA_sample_clamp 1e+30
FLOAT indirect_sample_clamp 10
BOOL AA_sample_clamp_affects_aovs false
INT AA_samples_max 20
FLOAT AA_adaptive_threshold 0.015
INT threads 0
ENUM thread_priority low
BOOL abort_on_error true
BOOL abort_on_license_fail false
BOOL skip_license_check false
RGB error_color_bad_texture 1, 0, 0
RGB error_color_bad_pixel 0, 0, 1
RGB error_color_bad_shader 1, 0, 1
STRING[] outputs (empty)
STRING[] light_path_expressions (empty)
NODE[] aov_shaders (empty)
INT xres 320
INT yres 240
INT region_min_x -2147483648
INT region_min_y -2147483648
INT region_max_x -2147483648
INT region_max_y -2147483648
FLOAT pixel_aspect_ratio 1
ENUM fis_filter none
FLOAT fis_filter_width 3
INT bucket_size 64
ENUM bucket_scanning spiral
VECTOR2[] buckets (empty)
BOOL ignore_textures false
BOOL ignore_shaders false
BOOL ignore_atmosphere false
BOOL ignore_lights false
BOOL ignore_shadows false
BOOL ignore_subdivision false
BOOL ignore_displacement false
BOOL ignore_bump false
BOOL ignore_motion false
BOOL ignore_motion_blur false
BOOL ignore_dof false
BOOL ignore_smoothing false
BOOL ignore_sss false
BOOL ignore_operators false
BOOL ignore_imagers false
STRING[] ignore_list (empty)
INT auto_transparency_depth 10
INT texture_max_open_files 0
FLOAT texture_max_memory_MB 4096
BOOL texture_per_file_stats false
STRING texture_searchpath
BOOL texture_automip true
INT texture_autotile 0
BOOL texture_accept_untiled true
BOOL texture_accept_unmipped true
BOOL texture_use_existing_tx true
BOOL texture_auto_generate_tx true
STRING texture_auto_tx_path
INT texture_failure_retries 0
BOOL texture_conservative_lookups true
FLOAT texture_max_sharpen 1.5
NODE camera (null)
NODE subdiv_dicing_camera (null)
BOOL subdiv_frustum_culling false
FLOAT subdiv_frustum_padding 0
NODE background (null)
BYTE background_visibility 255
NODE atmosphere (null)
NODE shader_override (null)
NODE color_manager (null)
NODE operator (null)
FLOAT meters_per_unit 1
STRING scene_units_name
FLOAT indirect_specular_blur 1
FLOAT luminaire_bias 1e-06
FLOAT low_light_threshold 0.001
BOOL skip_background_atmosphere false
BOOL sss_use_autobump false
BYTE max_subdivisions 255
INT curves_rr_start_depth 0
BOOL curves_rr_aggressive true
FLOAT reference_time 0
FLOAT frame 0
FLOAT fps 24
STRING osl_includepath
STRING procedural_searchpath
STRING plugin_searchpath
BOOL procedural_auto_instancing true
BOOL enable_procedural_cache true
BOOL parallel_node_init true
BOOL enable_new_quad_light_sampler true
BOOL enable_new_point_light_sampler true
BOOL enable_progressive_render false
BOOL enable_adaptive_sampling false
BOOL enable_dependency_graph false
BOOL enable_microfacet_multiscatter true
BOOL enable_deprecated_hair_absorp... false
BOOL dielectric_priorities true
BOOL enable_fast_ipr true
BOOL force_non_progressive_sampling false
FLOAT imager_overhead_target_percent 1
INT GI_diffuse_depth 0
INT GI_specular_depth 0
INT GI_transmission_depth 2
INT GI_volume_depth 0
INT GI_total_depth 10
INT GI_diffuse_samples 2
INT GI_specular_samples 2
INT GI_transmission_samples 2
INT GI_sss_samples 2
INT GI_volume_samples 2
ENUM render_device CPU
ENUM render_device_fallback error
STRING gpu_default_names *
INT gpu_default_min_memory_MB 512
INT gpu_max_texture_resolution 0
BOOL gpu_sparse_textures true
INT min_optix_denoiser_sample 0
STRING name </code></pre>
<p>Mais on va essayer d’aller plus loin et de récupérer les valeurs par défaut de tous les nœuds d’Arnold. :reflexionIntense:</p>
<h3>Le code</h3>
<p>Comme d’habitude, je vous donne le script en brut et je l’explique après :</p>
<pre>
<code class="language-python">from __future__ import (absolute_import,
division,
print_function,
unicode_literals)
import re
import subprocess
class ANodeDef(object):
def __init__(self, name, type, rest=None):
self.name = name
self.type = type
self.rest = rest
self.attrs = []
def __repr__(self):
return "{}('{}', '{}')".format(self.__class__.__name__,
self.name,
self.type)
class AAttrDef(object):
def __init__(self, name, type, default):
self.name = name
self.type = type
self.default = default
def __repr__(self):
return "{}('{}', '{}', '{}')".format(self.__class__.__name__,
self.name,
self.type,
self.default)
# Print Arnold version.
process = subprocess.Popen(['kick', '--help'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
first_line = out.split("\n")[0]
print(first_line)
# Get node list.
process = subprocess.Popen(['kick', '-nodes'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
node_line_re = re.compile(r"\s(?P<name>\w+)\s+(?P<type>\w+)(?P<rest>.*)")
node_defs = []
for line in out.split("\n"):
match_grp = node_line_re.match(line)
if not match_grp:
continue
name = match_grp.group('name')
type_ = match_grp.group('type')
rest = match_grp.group('rest')
node = ANodeDef(name, type_, rest)
node_defs.append(node)
# For each node, get its default parameters.
attr_line_re = re.compile(r"(?P<type>\w+)\s+(?P<name>\w+)\s+(?P<default>.*)")
for node_def in node_defs:
process = subprocess.Popen(['kick', '-info', node_def.name],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
param_start = False
for line in out.split("\n"):
if line.startswith("----"):
param_start = True
continue
if not param_start:
continue
match_grp = attr_line_re.match(line)
if not match_grp:
continue
type_ = match_grp.group('type')
name = match_grp.group('name')
default = match_grp.group('default')
attr = AAttrDef(name, type_, default)
node_def.attrs.append(attr)
# Print final output.
for node_def in sorted(node_defs, key=lambda n: n.name):
print("{} ({})".format(node_def.name, node_def.type))
for attr in sorted(node_def.attrs, key=lambda a: a.name):
print(" {} {} {}".format(attr.name, attr.type, attr.default))</code></pre>
<p>Et voilà, du pâté. :enerve:</p>
<p>Vous pouvez copier-coller ça et voir ce que ça donne. Mais nous savons tous que ce qui anime vos vies, c’est le besoin de comprendre les choses, pas vrai ? :baffed:</p>
<h3>Explication</h3>
<p>Alors c’est parti !</p>
<pre>
<code class="language-python">from __future__ import (absolute_import,
division,
print_function,
unicode_literals)</code></pre>
<p>Houdini étant en pleine transition vers Python 3, ce bloc permet de garder un peu de cohérence entre Python 2 et 3.</p>
<p>Ensuite nous avons deux classes :</p>
<pre>
<code class="language-python">
class ANodeDef(object):
def __init__(self, name, type, rest=None):
self.name = name
self.type = type
self.rest = rest
self.attrs = []
def __repr__(self):
return "{}('{}', '{}')".format(self.__class__.__name__,
self.name,
self.type)
class AAttrDef(object):
def __init__(self, name, type, default):
self.name = name
self.type = type
self.default = default
def __repr__(self):
return "{}('{}', '{}', '{}')".format(self.__class__.__name__,
self.name,
self.type,
self.default)</code></pre>
<p>Ces deux classes sont des <em>dataclass</em> (classes de données). L’idée étant de stocker des données dans des objets spécifiques pour éviter d’utiliser des dictionnaires ou des <a>namedtuple</a> (mais vous pouvez utiliser l’un et l’autre si vous êtes pressé).</p>
<p>J’implémente souvent les <code>__repr__</code> sur mes <em>dataclass</em> pour visualiser rapidement les objets dans le debugger ou via des <code>print()</code>.</p>
<ul>
<li><code>ANodeDef</code> stockera les informations (nom et type) liées à chaque nœud.</li>
<li><code>AAttrDef</code> stockera les informations (nom, type et valeur par défaut) liées à chaque paramètre de nœuds.</li>
<li>Les objets <code>AAttrDef</code> seront mis dans la liste <code>attrs</code> de chaque <code>ANodeDef</code>.</li>
</ul>
<p>Ça nous donnera, grosso modo, une hiérarchie sous la forme :</p>
<pre>
<code>ANodeDef
name: alembic
type: shape
rest: (procedural)
attrs: [
AAttrDef
name: invert_normals
type: BOOL
default: false
AAttrDef
name: ray_bias
type: FLOAT
default: 1e-06
...
...</code></pre>
<p>Beaucoup de prise de tête pour rien, me rétorqueriez-vous, « Utilise des dicos ! Ça va plus vite, le code c’est quand même plus beau quand ça fait un max de choses en une seule ligne, gnagnagna… ».</p>
<p><img alt="Dico ou classes de données, nous sommes différents" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_12_10_kick_default/nous_sommes_different.jpg" style="margin: 0px auto; display: table; width: 500px; height: 714px;" /></p>
<p>Ne perdez pas votre temps en débats stériles, vous avez déjà perdu, alors avançons :</p>
<pre>
<code class="language-python"># Print Arnold version.
process = subprocess.Popen(['kick', '--help'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
first_line = out.split("\n")[0]
print(first_line)</code></pre>
<p>Cette partie est la plus surement la plus simple à comprendre : On lance <code>kick --help</code> et on affiche uniquement la première ligne, celle qui affiche la version :</p>
<pre>
<code>Arnold 7.1.4.1 [c989b21f] linux x86_64 clang-10.0.1 oiio-2.4.1 osl-1.12.0 vdb-7.1.1 adlsdk-7.4.2.47 clmhub-3.1.1.43 rlm-14.2.5 optix-6.6.0 2022/11/29 11:24:49</code></pre>
<p>La suite :</p>
<pre>
<code class="language-python"># Get node list.
process = subprocess.Popen(['kick', '-nodes'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
node_line_re = re.compile(r"\s(?P<name>\w+)\s+(?P<type>\w+)(?P<rest>.*)")
node_defs = []
for line in out.split("\n"):
match_grp = node_line_re.match(line)
if not match_grp:
continue
name = match_grp.group('name')
type_ = match_grp.group('type')
rest = match_grp.group('rest')
node = ANodeDef(name, type_, rest)
node_defs.append(node)</code></pre>
<p>Ça commence à devenir intéressant. On lance (via <code>subprocess.Popen()</code>) la commande <code>kick -nodes</code> qui va nous lister tous les nœuds disponibles, et on interprète chaque ligne pour en extraire les informations qu’on stocke dans un objet <code>ANodeDef</code> qu’on ajoute à la liste globale <code>node_defs</code>.</p>
<p>Chaque ligne ressemblera à :</p>
<pre>
<code> node type information_supplémentaires</code></pre>
<p>Par exemple :</p>
<pre>
<code>...
add shader
alembic shape (procedural)
complex_ior shader [deprecated]
...</code></pre>
<p>L’expression régulière <code>node_line_re</code> s’occupe d’attraper le contenu de chaque ligne :</p>
<pre>
<code> \s (?P<name>\w+) \s+ (?P<type>\w+) (?P<rest>.*)
1 espace|un mot appelé name|1 ou plusieurs espaces|un mot appelé type|le reste, optionnel appelé rest</code></pre>
<p>Une fois qu’on a cette liste de nœuds, on va lancer la ligne de commande <code>kick -info</code> pour chaque nœud et attraper le résultat, c’est ce que fait le bloc de code suivant :</p>
<pre>
<code class="language-python"># For each node, get its default parameters.
attr_line_re = re.compile(r"(?P<type>\w+)\s+(?P<name>\w+)\s+(?P<default>.*)")
for node_def in node_defs:
process = subprocess.Popen(['kick', '-info', node_def.name],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = process.communicate()
param_start = False
for line in out.split("\n"):
if line.startswith("----"):
param_start = True
continue
if not param_start:
continue
match_grp = attr_line_re.match(line)
if not match_grp:
continue
type_ = match_grp.group('type')
name = match_grp.group('name')
default = match_grp.group('default')
attr = AAttrDef(name, type_, default)
node_def.attrs.append(attr)</code></pre>
<p>Le code est plus verbeux, mais on va expliquer tout ça. La sortie de la commande <code>kick -info</code> ressemble à ça :</p>
<pre>
<code>Type Name Default
------------ -------------------------------- --------------------------------
BYTE visibility 255
BYTE sidedness 255
BOOL receive_shadows true
BOOL self_shadows true</code></pre>
<p>On a donc besoin d’une expression régulière que je ne détaille pas, car elle est assez similaire à la précédente. :redface:</p>
<p>La seule subtilité c’est qu’on attend (via la variable <code>param_start</code>) d’avoir passé la ligne commençant par <code>----</code> avant de commencer à parser chaque paramètre.</p>
<p>Après tous les <code>if</code> passé, on récupère les valeurs de l’expression régulière, on fabrique un objet <code>AAttrDef</code> et on l’ajoute à la liste des attributs du nœud qu’on inspecte. :reflechi:</p>
<p>Et maintenant qu’on a notre petite hiérarchie, on affiche le tout en triant par nom :</p>
<pre>
<code class="language-python"># Print final output.
for node_def in sorted(node_defs, key=lambda n: n.name):
print("{} ({})".format(node_def.name, node_def.type))
for attr in sorted(node_def.attrs, key=lambda a: a.name):
print(" {} {} {}".format(attr.name, attr.type, attr.default))</code></pre>
<p>Cela nous donne ça :</p>
<pre>
<code>options (options)
AA_adaptive_threshold FLOAT 0.015
AA_sample_clamp FLOAT 1e+30
AA_sample_clamp_affects_aovs BOOL false
etc.</code></pre>
<p>À vous de l’afficher comme vous l’entendez ! :banaeyouhou:</p>
<h3>Conclusion</h3>
<p>Python, c’est bien, il faut faire du Python, faites du Python.</p>
<p style="text-align: center;">:marioCours:</p>Objectif Helios, 5D et « bip » de confirmation de mise au pointurn:md5:a65a3fdc802a5dec885cbdd640f26dee2022-05-21T15:30:00+02:002023-01-09T22:38:43+01:00NarannMes coups de coeurcanonmise au pointmontureobjectifphotovintage<p><img alt="Canon 5D Mark III et objectif Helios-44" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/5D_helios_tn.jpg" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Je fais ce billet pour partager mes galères dans mes recherches sur le moyen de faire fonctionner le « bip » de confirmation de mise au point de mon boitier 5D Mark III avec un objectif Helios 44. On parlera de l’Helios, mais il est possible que les problèmes soulevés et les solutions (et les erreurs aussi…) puisse vous intéresser.</p>
<p>Si vous êtes hermétique aux termes liés au matériel photo, passez votre chemin. Les autres, bienvenues chez vous !</p> <p>TL ; PL :</p>
<ul>
<li>La bague Fotodiox M39 fonctionne sur le Helios 44-2 M39 (les premiers, en métal blanc).</li>
<li>La bague Fotodiox M42 Type 1 fonctionne sur le Pentax SMC (Super Multi Coated) Takumar 50 mm f/1.4 M42.</li>
</ul>
<h3>Résumé</h3>
<p>Mon premier reflex a été un Canon 550D (en 2010), avec un Sigma 17-70 mm f/2.8-4 DC Macro OS HSM, « DC » étant la gamme Sigma dédiée au APS-C.</p>
<p>Ensuite j’ai eu un Sigma 50 mm f/1.4 EX DG HSM, « DG » étant la gamme Sigma dédiée aux <em>full frames</em>, soit un équivalent à un 75 mm (50 × 1.5). Il était loin d’être parfait (plutôt <em>cheap</em>, même), mais comme je me focalise sur les portraits sa profondeur de champ me convenait. Ça été mon objectif principal pendant longtemps.</p>
<p>Depuis j’ai opté pour un 5D Mark III en occasion, par curiosité vis-à-vis du <em>full frame</em>. Combiné à mon Sigma 50 mm f/1.4, et à un 35 mm f/1.8 Canon pour compléter, c’était parfait.</p>
<p>Je m’arrête là, mais ça vous donne une idée du matériel de base. :sourit:</p>
<h3>Pourquoi un objectif <em>vintage</em> ?</h3>
<p>Comparé à pas mal de choses, la qualité optique d’un objectif ne vieillit pas aussi vite (voir, pas du tout). Ce qui change c’est les boîtiers, les montures, le fait d’avoir l’auto focus embarqué, etc. Cela veut dire qu’il est possible d’avoir des objectifs avec une très bonne optique, pour pas trop cher (comparé à certains objectifs modernes qui peuvent atteindre le millier d’euros…).</p>
<p>À cela s’ajoute une vision personnelle donc totalement subjective : Les objectifs actuels sont assez bruts. Ils visent à rendre correctement la couleur, la lumière et à éviter tous les problèmes récurent distorsion, aberration chromatique, etc. Ils renvoient une image forte en détails, avec tout dedans, pour laisser de la marge lors du développement.</p>
<p>Les objectifs plus anciens sont imparfaits, même les très haut de gamme embarquent avec eux les défauts de leur époque. Quand on cherche un objectif vintage, on cherche ça.</p>
<p>Après pas mal de recherche j’hésitais entre le Jupiter 8, le Helios 44 et le Takumar SMC.</p>
<h3>Jupiter 8</h3>
<p>À ma grosse surprise, une personne cherchait à se débarrasser d’un Jupiter à 30 km de chez moi. Pas de frais de port, je le prends !</p>
<p>C’est un Jupiter 8 KMZ 50 mm f/2 monture M39 :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/K63A9476.jpg"><img alt="Objectif Jupiter 8 KMZ" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/.K63A9476_m.jpg" style="margin: 0px auto; display: table; width: 680px; height: 594px;" /></a></p>
<p>Comme la monture de mon 5D est de type Canon EF, J’achète un adaptateur M39 vers EF basique.</p>
<p>Tiens tiens, il est très petit dis donc… Je branche l’ensemble et bizarre… Tout est en macro…</p>
<p>Je vais découvrir qu’une monture ne fait pas tout, et que quand on commence à regarder les objectifs anciens, il faut garder un œil sur pas mal de choses.</p>
<p>Ici, c’est le <em>flange</em> qui posait problème. Le <em>flange</em>, c’est la distance entre le capteur (la pellicule) et la monture de l’objectif (le « <a href="https://fr.wikipedia.org/wiki/Tirage_m%C3%A9canique" hreflang="fr" title="Page Wikipédia Tirage mécanique">tirage mécanique</a> », en français) :</p>
<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/flange.png" style="margin: 0px auto; display: table; width: 323px; height: 400px;" /></p>
<p>Il y a <a href="https://en.wikipedia.org/wiki/Flange_focal_distance" hreflang="en" title="Wikipédia Flange focal distance">énormément</a> suivant les montures, et parfois des variantes pour la même monture…</p>
<p>La monture Canon EF a une distance de 44 mm. Pour le M39, c’est compliqué, mais en gros, pour le Jupiter c’est 28.8 mm. Ça veut dire que pour que mon objectif Jupiter fonctionne sur mon 5D, il faudrait techniquement que j’enfonce l’objectif dans le boîtier. Donc c’est mort pour les montures EF (j’essaierai un jour, si j’ai un <em>mirrorless</em>, qui ont des distances plus courtes).</p>
<p>Bon, j’ai fait quelques tests en macro histoire de, mais autant j’aime les portraits, autant ceux des fourmis de mon jardin en vintage, ce n’était pas le plan… :aupoil:</p>
<h3>Helios 44.</h3>
<p>Ducoup je me rabats sur mon second choix et commande un Helios-44 58мм f/2 M39 Zenit blanc, 8 lames.</p>
<p>M39 aussi, j’ai vérifié le <em>flange</em> : 45.2 mm. Je ne me ferais plus avoir.</p>
<p>Notez que les Helios, il y en a <a href="http://musashichan.com/tests/helios/helios-44-58mm-f-2" hreflang="fr" title="Helios-44 58mm f/2">énormément</a>. Je ne refais pas l’historique : Objectif créé en URSS, peut-être le plus massivement produit de l’histoire. Donc à moins de passer du temps à chercher, il est peu probable que vous tombiez deux fois sur le même.</p>
<p>Il arrive, et c’est la révélation : Il est génial ! Il a quelques défauts : Sa bague est un peu molle et il ne peut pas faire de mise au point à l’infini (je ne suis pas sûr de comprendre pourquoi, je pense que c’est encore un problème de <em>flange</em>). Ça le réserve à un usage de portraits, mais c’est exactement ce que je comptais en faire…</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/K63A9477.jpg"><img alt="Objectif Helios 44" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/.K63A9477_m.jpg" style="margin: 0px auto; display: table; width: 680px; height: 617px;" /></a></p>
<p>En parlant de portrait, le bokeh tourbillonnant (<em>swirly bokeh</em>) de l’objectif donne un cachet immédiat :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/K63A9492_01_tn.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/.K63A9492_01_tn_m.jpg" style="margin: 0px auto; display: table; width: 680px; height: 420px;" /></a></p>
<p>Honnêtement, pour le prix, je ne peux que vous conseiller d’en acheter un (après avoir lu ce billet :hehe: ). On les trouve facilement sur ebay.</p>
<p>Nous disions plus haut que le problème des anciens objectifs est leur manque de matériel embarqué, à savoir, pas d’auto focus. C’est logiquement le cas avec le Helios.</p>
<p>À pleine ouverture, sa profondeur de champ est très faible. Mon Sigma 50 mm m’avait habitué à la sanction sans appel d’une mise au point mal faite à pleine ouverture, mais ce dernier est équipé d’un auto focus qui diminuait énormément les ratés.</p>
<p>Le Helios n’ayant rien du tout, la bonne méthode semble être de faire la meilleure mise au point possible puis de mitrailler en faisant une légère rotation de la bague, ou de passer par la mise au point via l’écran LCD, qui permet de faire un zoom à l’intérieur de l’image et de focaliser sur un élément en particulier, mais ça rendait les prises très longues. :neutral:</p>
<p>Comme je passe toutes mes photos en RAW, ça veut dire que je passe énormément de temps à isoler les quelques bonnes prises.</p>
<h3>Déambulation</h3>
<p>Les mois passent, et il devient mon objectif principal, toujours branché sur mon boîtier. Je suis resté longtemps en mise au point manuelle (alternant entre le viseur et l’écran LCD), mais le nombre de prises ratées devenait de plus en plus frustrant et je commençais à me dire que non, il fallait que je fasse quelques choses pour rendre la prise de vue plus agréable et moins aléatoire.</p>
<p>Et là on tombe un peu dans la zone grise : Il n’y a pas énormément d’information, car chaque combinaison boîtier/objectif est différente.</p>
<p>La première solution qui m’a été proposé a été de mettre un stigmomètre. Il s’agit d’une sorte de lame de verre qui amplifie la profondeur de champs dans le viseur. C’était ce qu’on utilisait « avant » les autofocus. Le seul site qui semble en vendre de façon sérieuse est <a href="https://www.focusingscreen.com/" hreflang="en" title="Focusing Screen website">Focusing Screen</a>. Ils ont <a href="https://www.focusingscreen.com/privacy.php?osCsid=ir0k58pfhl4bsct1hhq31odsf3" hreflang="en" title="Focusing Screen choice">pas mal de choix</a>. Presque séduit, je regarde les <a href="https://www.focusingscreen.com/work/5d3en.htm" hreflang="en" title="Focusing Screen Installation Instruction">instructions d’installation</a>, mais l’idée d’aller jouer avec un tournevis et une pince à épiler à quelques cm du capteur m’intéressait moyennement. Sans compter que cette installation est définitive et risquait de me gêner lors de l’utilisation d’objectifs avec autofocus intégré. L’autre problème de tels stigmomètres étant une diminution non négligeable de la luminosité du viseur. Sans compter le prix (plusieurs centaines d’euros).</p>
<p>Je commençais à perdre espoir, mais lors de mes dernières recherche je comprends qu’il y a bien un mécanisme de vérification du focus dans le boitier de l’appareil photo, mais que la seule et unique raison pour laquelle il ne fonctionne pas avec les objectifs vintage, c’est parce que l’adaptateur ne répond pas… En gros il faut juste que l’adaptateur « réponde » au boitier afin que ce dernier soit capable de proposer un petit « bip » lorsque la mise au point est bonne. Et pour ça, il faut que l’adaptateur soit équipé d’une puce spéciale. Et il n’y a aucun moyen de forcer l’utilisation de l’autofocus sans ça. Putain, mais Canon, quoi !!! :injures:</p>
<p>Au fil de mes recherches, je finis par trouver le Graal : <a href="https://fotodioxpro.com/" hreflang="en" title="Site Fotodiox">Fotodiox</a> propose des adaptateurs avec une puce intégrée, qui se fait passer pour un objectif. :laClasse:</p>
<p>Et là, je tombe dans le piège « montures Helios » qui fait que je vais royalement me planter sur le modèle à prendre (et aussi la raison originale de ce billet). Explication :</p>
<p>Après avoir eut mon Helios, toutes les informations que je trouvais faisaient référence à la monture M42. J’en suis arrivé à intérioriser que j’avais un M42, parce qu’une fois l’adaptateur placé, le fait que j’avais pris une monture exotique (M39 alors que quasiment tous les Helios sont en M42) m’était totalement sortie de la tête.</p>
<p>Je fais cette première erreur en regardant le modèle dédié au M42. Ou plutôt, « les » modèles… En effet, la monture M42 est disponible en 2 versions, suivant votre objectif (si vous cherchez « fotodiox M42 v1 or v2 » vous tomberez sur une petite image).</p>
<p>Sans rentrer dans les détails, l’arrière des objectifs dépends des fabricants et certains ont placé la goupille d’ouverture à différents endroits. Un exemple <a href="https://www.flickr.com/photos/arkku/611901459" hreflang="en" title="M42 Lens Aperture Pin">ici</a> et <a href="https://www.flickr.com/photos/arkku/612516644/in/set-72157600018767343/" hreflang="en" title="Converting an Auto-Aperture M42 Lens to Manual Aperture">là</a>. Bref, je passe énormément de temps à déterminer le type qu’il me fait et fais mon choix, le V1, celui qui est conseillé par le site « pour les Helios » et qu’on voit sur certaines images (je vois un Helios monté dessus, je me dis que c’est ce modèle en oubliant totalement la spécificité du mien).</p>
<p>Ce bordel sans nom entre V1 et V2 est la raison pour laquelle j’ai perdu de vue la question de la monture. Je commande donc <a href="https://fotodioxpro.com/collections/canon-eos-ef-ef-s-mount-adapter/products/m42-ef-v1-fc10" hreflang="en" title="Lens Mount Adapter M42 Type 1 to Canon EOS (EF, EF-S) ">ce modèle</a>. L’adaptateur arrive, j’approche mon objectif et je comprends mon erreur : Helios première génération = M39…</p>
<p>T’es un champion, Dorian… Change rien. :slow_clap:</p>
<p>À titre informatif (et histoire d’ajouter un peu de sel), voici le détail du prix :</p>
<p>L’adaptateur : $24.95<br />
Frais de port : $27.81<br />
Taxes : $10.55<br />
Total : $63.31 (60,29 €)</p>
<p>À cela c’est ajouté 13 € de douane… :baffed:</p>
<p>Petite inspiration triste, je relativise et me dit que la monture M42 c’est la monture d’un Takumar, que j’aurais peut-être un jour… :siffle:</p>
<p>Finalement, je refais une commande : <a href="https://fotodioxpro.com/collections/canon-eos-ef-ef-s-mount-adapter/products/m39-ef-fc10" hreflang="en" title="M39 to Canon EF">M39 vers Canon EF</a>, même tarif… Par contre pas de V1/V2, forcément, c’est plus simple :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/K63A9478.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/.K63A9478_m.jpg" style="margin: 0 auto; display: table;" /></a></p>
<p style="text-align: center;"><em>En bas à gauche, la bague conversion M39 de Fotodiox, en haut en droite la bague de conversion M39 standard.</em></p>
<p>L’adaptateur arrive, je l’installe sur mon boîtier avec mon objectif, j’enclenche à demi-course, commence à tourner la bague de mise au point et joie ! L’appareil émet un petit « bip » quand la mise au point est faite ! J’enclenche, ça fonctionne nickel !</p>
<p>Je commence à mitrailler comme un môme, sous l’air décontenancé de mes proches…</p>
<h3>Adaptateur Fotodiox</h3>
<p>Je n’ai pas grands chose à ajouter si ce n’est que ça fonctionne bien. Certains ont toutefois eut des problèmes, mais c’est dû à comment est configuré l’appareil. Je suis toujours en « Manuel » et toujours sur un seul collimateur d’auto focus (central). Ça se change en appuyant sur la touche des collimateurs, puis la touche « M-Fn ».</p>
<p>Ça et le fait de configurer certaines options internes (AF, page 3) :</p>
<ul>
<li>Map manuelle avec obj. USM ON</li>
<li>Faisceau d’assistance AF ON</li>
<li>Etc.</li>
</ul>
<p>En gros, sachez que si vous n’entendez pas le bip, il est probable que vous n’ayez pas configuré correctement votre boitier.</p>
<h3>Et la bague M42 ? Et le Jupiter ?</h3>
<p>Après avoir hésité pendant plusieurs mois, une offre intéressante tombe et je finis par prendre un objectif (Pentax Super Multi Coated Takumar 50 mm F/1.4 M42). Il arrive, je le branche, je fais la mise au point, et j’entends le bip de confirmation de focus. J’enclenche et ça fonctionne impeccablement :</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/K63A9479.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/.K63A9479_m.jpg" style="margin: 0px auto; display: table; width: 680px; height: 647px;" /></a></p>
<p>L’optique est bonne (jaunie, car il s’agit de <a href="https://fr.wikipedia.org/wiki/Dioxyde_de_thorium#Utilisations" hreflang="fr" title="Page Wikipédia verre de thorium">verre de thorium</a> qui jauni sous sa propre radioactivité, mais j’ai corrigé le problème en la passant sous une lumière UV, quelques heures). L’objectif est magnifique, taillé dans la masse (comme le Helios). En ouverture 1.4, ça ne pardonne pas. En parlant de ça, le 1.4 est vraiment très baveux, mais à 2.0 et 2.8, la qualité est exceptionnelle.</p>
<p>Avec tout ça, je ne suis pas près d’acheter du nouveau matériel.</p>
<h3>Résumé technique</h3>
<ul>
<li>Référence de la bague M39 : Fotodiox Lens Mount Adapter Compatible with M39/L39 (x1mm Pitch) Screw Mount Russian & Leica Thread Mount Lens to Canon EOS (EF, EF-S) Mount SLR Camera Body - with Generation v10 Focus Confirmation Chip</li>
<li>Référence de la bague M42 : Fotodiox Lens Mount Adapter Compatible with M42 Type 1 Screw Mount SLR Lens to Canon EOS (EF, EF-S) Mount SLR Camera Body - with Generation v10 Focus Confirmation Chip</li>
<li>Objectif Helios : Helios-44 58мм f/2 M39 Zenit blanc, 8 lames.</li>
<li>Objectif Takumar : Pentax Super Multi Coated Takumar 50 mm F/1.4 M42.</li>
</ul>
<p>Helios + bague M39 : Fonctionne impec !<br />
Takumar + bague M42 (type 1) : Fonctionne impec !</p>
<p>En espérant que ce billet vous évitera de perdre du temps.</p>
<p><a class="media-link" href="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/5D_helios.jpg"><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2022_21_05_5D_helios/.5D_helios_m.jpg" style="margin: 0px auto; display: table; width: 680px; height: 601px;" /></a></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>mrViewer, MP4 8 bit, OpenColorIO et Rec. 709urn:md5:2142f82ee10b067c7f5d11bf17604a4f2019-07-27T12:17:00+02:002019-07-27T12:17:00+02:00NarannScript et codeconfigurationffmpegmp4mrViewerOpenColorIO<p><img alt="" class="media" src="https://www.fevrierdorian.com/blog/public/billets/2019_07_27_mrViewer_rec709/mrViewer_rec709_tn.png" style="float: left; margin: 0px 1em 1em 0px; width: 150px; height: 150px;" />Si vous utilisez <a href="https://mrviewer.sourceforge.io/" hreflang="en" title="mrViewer - Home">mrViewer</a>, vous avez peut-être déjà eux des problèmes de lectures de fichiers MP4 en <a href="https://fr.wikipedia.org/wiki/Rec._709" hreflang="fr" title="Rec. 709">Rec. 709</a>. En effet, <em>mrViewer</em> lit les MP4 8 bits en <a href="https://fr.wikipedia.org/wiki/SRGB" hreflang="fr" title="sRGB">sRGB</a> par défaut.</p>
<p>Il existe plusieurs façons de corriger le problème. On va essayer de faire le tour dans ce billet. :reflechi:</p> <h3>Générer un fichier MP4</h3>
<p>Le logiciel couramment utilisé dans les pipelines pour générer des fichiers vidéos est <a href="https://ffmpeg.org/" hreflang="en" title="FFmpeg">FFmpeg</a>. Derrière ces quelques lettres peu sexys se cache un monstre du logiciel libre.</p>
<p>Se lancer dans l’écriture d’une ligne de commande <em>FFmpeg</em> s’apparente à calligraphier un Flaubert; le doute vous assaillie rapidement. :petrus:</p>
<p>Voici une ligne de commande qui convertit une séquence EXR en MP4 :</p>
<pre>
<code class="language-bash">ffmpeg -colorspace bt709 -color_primaries bt709 -apply_trc bt709 -i ma_sequence.$04d.exr -f mp4 -pix_fmt yuv420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 ma_sequence.mp4</code></pre>
<p>Les arguments avant la séquence d’entrée (avant « -i ») spécifient les paramètre de la séquence EXR :</p>
<ul>
<li>colorspace bt709</li>
<li>color_primaries bt709</li>
<li>apply_trc bt709</li>
</ul>
<p>Le dernier vient demander à <em>FFmpeg</em> d’appliquer une transformation de couleur vers bt709. Votre EXR, en linéaire, est donc convertis en Rec. 709.</p>
<blockquote>
<p>Notez que <em>FFmpeg</em> utilise le nom des standards. Si cela ne pose pas de problèmes pour <em>BT/Rec/ITU 709</em> (il y a <em>709</em> dedans…), sachez que pour du <em>sRGB</em>, il faut utiliser la valeur « iec61966_2_1 », <em>IEC 61966-2-1</em> étant le nom du standard définissant <em>sRGB</em>. Plus d’informations <a href="https://stackoverflow.com/questions/37489798/ffmpeg-getting-a-dark-output-when-converting-exr-sequence-to-mp4" hreflang="en" title="ffmpeg - getting a dark output when converting .exr sequence to .mp4">ici</a>.</p>
</blockquote>
<p>Dans le cas des paramètres de sortis (après « -i »), c’est presque identique :</p>
<ul>
<li>colorspace bt709</li>
<li>color_primaries bt709</li>
<li>color_trc bt709</li>
</ul>
<p>Le dernier explicitant, ce coup-ci, la transformation de couleur utilisé dans l’image.</p>
<p>Si vous utilisez <em>ffprobe</em>, vous devriez obtenir quelque chose qui ressemble à ça :</p>
<pre>
<code class="language-plain">$ ffprobe ma_sequence.mp4
...
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'ma_sequence.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf57.83.100
Duration: 00:00:03.28, start: 0.000000, bitrate: 3995 kb/s
Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(tv, bt709), 1024x540 [SAR 1:1 DAR 512:270], 3992 kb/s, 24 fps, 24 tbr, 12800 tbn, 48 tbc (default)
...</code></pre>
<p>On voit donc que le flux vidéo est en bt709.</p>
<p>Au passage, durant l’écriture de ce billet, je suis tombé sur le site de <em>John Van Sickle</em> qui <a href="https://www.johnvansickle.com/ffmpeg/" hreflang="en" title="FFmpeg Static Builds">compile des builds</a> de <em>FFmpeg</em> pour <em>Linux</em>, merci à lui. :sourit:</p>
<h3>Comportement de mrViewer</h3>
<p><em>mrViewer</em> s’appuie sur <a href="https://opencolorio.org/" hreflang="en" title="OpenColorIO">OpenColorIO</a> avec le profile <a href="https://opencolorio.org/configurations/nuke_default.html" hreflang="en" title="OpenColorIO nuke-default">nuke-default</a> qui contient les informations de la plupart des espaces colorimétriques de notre industrie.</p>
<p>Pourtant, quand on ouvre notre MP4 8 bits, <em>mrViewer</em> ne semble pas s’intéresser à ce qu’il y a dedans.</p>
<pre>
<code class="language-plain">[img] ma_sequence.mp4 - Got colorspace 'sRGB' from bitdepth 8 bits as default</code></pre>
<p>Mais d’où sort-il son <em>sRGB</em> ? :bete:</p>
<p>Quand on lit les préférences utilisateurs de <em>mrViewer</em> (Dans <em>.filmaura/mrViewer.prefs</em> sous Linux), on trouve ceci :</p>
<pre>
<code class="language-plain">[./ui/view/ocio/ICS]
8bits:sRGB
16bits:
32bits:
float:</code></pre>
<p>Une première méthode consiste donc à remplacer « <em>sRGB</em> » par « <em>rec709</em> » directement dans ce fichier.</p>
<p>Bien que faisable, ce n’est pas forcément optimal : Pouvoir déterminer le profile colorimétrique suivant ce qu’on cherche à afficher (playblast, séquence d’images EXR/JPEG, texture) nécessite de devoir modifier ce paramètre à chaque fois qu’on lance <em>mrViewer</em>. :pasClasse:</p>
<p>La question reste donc en suspend : Où <em>mrViewer</em> définit-il <em>sRGB</em> comme profile colorimétrique des fichiers 8 bits par défaut ? :perplex:</p>
<p>On pourrait envoyer un mail à <a href="https://twitter.com/ggarra1973" hreflang="en" title="Gonzalo Garramuño">Gonzalo Garramuño</a> (qui maintient ce logiciel et le fait évoluer depuis presque dix ans !!!), mais quand on a un égo disproportionné comme le mien, on prend le code, on cherche le message d’erreur dedans (« Got colorspace ») et on le lit. :grenadelauncher:</p>
<p>Après quelques minutes de lecture, je me retrouvais avec pas mal de noms de variables que le code semblait utiliser comme si elles étaient initialisés, sans que je ne retrouve jamais l’endroit ou elles étaient définies. :gne2:</p>
<p>Je ne vais pas vous faire un passage en revue du code, mais le morceau qui va nous intéresser est celui-là :</p>
<pre>
<code class="language-cpp"> fltk::Preferences ics( ocio, "ICS" );
{
#define OCIO_ICS(x, d) \
ok = ics.get( #x, tmpS, d, 2048 ); \
CMedia::ocio_##x##_ics = environmentSetting( "MRV_OCIO_" #x "_ICS" , \
tmpS, ok ); \
uiPrefs->uiOCIO_##x##_ics->text( tmpS );
OCIO_ICS( 8bits, "sRGB" );
OCIO_ICS( 16bits, "" );
OCIO_ICS( 32bits, "" );
OCIO_ICS( float, "" );
}</code></pre>
<p>Fichtre ! Une macro ! :mechantCrash:</p>
<p>Ce bout de code initialise les préférences; pour chaque appel à la macro <em>OCIO_ICS()</em>, il « exécute » (« duplique » serait plus approprié) un bout de code. Par exemple, le premier appel (« <em>OCIO_ICS(8bits, "sRGB")</em> ») donnera :</p>
<pre>
<code class="language-cpp">ok = ics.get("8bits", tmpS, "sRGB", 2048);
CMedia::ocio_8bits_ics = environmentSetting("MRV_OCIO_8bits_ICS", tmpS, ok);
uiPrefs->uiOCIO_8bits_ics->text(tmpS);</code></pre>
<p>Pour expliquer brièvement, <em>ics</em> est une section des préférences, enfant de la variable <em>ocio</em>, portant de nom "ICS" (<em>Input Color Space</em> je suppose). Cette section se retrouve dans les préférences (« <em>[./ui/view/ocio/ICS]</em> », voir plus haut).</p>
<p>La première ligne prend, dans cette section <em>ics</em>, la variable défini à la clef « 8bits » qu’il met dans <em>tmpS</em> (une variable défini plus haut « <em>char tmpS[2048];</em> » pour stocker temporairement une valeur). Si cette clef n’existe pas (dans le cas d’un premier lancement et que les préférences n’existent pas ou sont vides), il prend la valeur « sRGB ». Le 2048 étant juste la limite de le nombre de lettres maximum que la méthode <em>get()</em> peut stocker dans <em>tmpS</em>. Les codeurs C sont habitués à ce genre de choses (et <a href="https://xkcd.com/1354/" hreflang="en" title="xkcd: Heartbleed Explanation">un xkcd pour la route</a> !).</p>
<p>On comprend donc d’où sort la valeur par défaut. :sauteJoie:</p>
<p>La seconde ligne est la plus intéressante, c’est elle qui défini le contenu des variables dont je parlais plus haut. Si la variable d’environnement existe, il renvoie sa valeur. Si elle n’existe pas, il renvoie la valeur de <em>tmpS</em>.</p>
<p>Et ça c’est un peu le cadeau bonus ! À savoir qu’on peut outrepasser la variable par défaut via une variable d’environnement. :smileFou:</p>
<p>Ainsi, votre code de lancement peut définir le contenu de la variable <em>MRV_OCIO_8bits_ICS</em> à la volée au moment du lancement de <em>mrViewer</em>. :laClasse:</p>
<p>Si vous définissez un environnement d’application en Python il suffit de faire ça :</p>
<pre>
<code class="language-python">my_env['MRV_OCIO_8bits_ICS'] = 'rec709'</code></pre>
<p>Et, gratification suprême, la confirmation:</p>
<pre>
<code class="language-plain">[img] ma_sequence.mp4 - Got colorspace 'rec709' from bitdepth 8 bits as default</code></pre>
<h3>Conclusion</h3>
<p>Comme je ne sais pas comment conclure ce billet, je vous propose un poème de haute qualité :</p>
<p>De Rec 709 et sRGB<br />
Dépendent les remarques inspirées<br />
D’un réal toutefois blasé<br />
Face à l’art pixelisé<br />
De graphistes fatigués.</p>
<p>Il insista, faussement serein :<br />
« Que ce bleu soit plus myrtille… »<br />
Des déjections dans leurs pupilles<br />
D’autres raisons, il n’en voyait point<br />
Comment personne de goût pouvait trouver reluisant<br />
L’exposition d’un tel néant.</p>
<p>D’un seul homme, une graphiste objecta<br />
« Tu ne parles plus à des incultes aisés<br />
Retire donc tes lunettes teintées<br />
Du soleil, s’il y en a,<br />
C’est le fruit de notre labeur que tu vois<br />
Car de tes choix peu avisés<br />
En faire de l’or est notre métier ».</p>
<p>Un TD qui passait nonchalamment<br />
Remarqua la querelle chimérique<br />
Alors calme et rassurant<br />
Il prit contrôle des périphériques<br />
Puis tût la discorde en sélectionnant<br />
Le bon profil colorimétrique.</p>
<center>:marioCours:</center>