PROJET AUTOBLOG


blog.fevrierdorian.com

source: blog.fevrierdorian.com

⇐ retour index

Geler les rigs en production

dimanche 17 avril 2022 à 17:50

dessin rig geleCe billet aurait pu être sous-titré : « Se tirer une balle dans le pied, mais rester debout ». :trollface:

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.

Qu’entend-on par « geler une version » ?

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 :

Mais également :

Les versions numérotées n’ont, en principe, pas vocation à être modifié. La « v3 » dans six mois est supposé être le même fichier que lors de sa publication. :perplex:

Ce n’est pas le cas des versions « nommées » :

Ces versions « nommées » sont, en quelque sorte, des définitions de ce qu’est la version :

latest -> v6
release -> v3
OMAGADSHOT10IZDOWN -> v4

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 toto_rig.v1.mb). 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.

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 toto_rig.latest.mb, 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.

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.

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.

Toutefois, il vient forcément un moment ou ce mécanisme, si économique, joue en votre défaveur.

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 ».

Pourquoi a-t-on besoins de geler les rigs ?

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.

Les parties exposées du rig (ses contrôleurs et attributs) doivent avoir un comportement identique d’une version à l’autre.

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.

En cours de production, cela nous donne :

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 ».

Plusieurs animateurs commencent à animer leurs plans et à les faire valider.

Viens ensuite le moment que tout rigger 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.

À 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.

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:

Plusieurs approches sont possibles suivant la nature le la-dite mise à jour :

La nouvelle version ne change rien au comportement du rig. 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.

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…

C’est le cas le plus simple à gérer, mais aussi le plus rare.

Une nouvelle version de rig peut aussi se retrouver à ajouter des fonctionnalités. 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.

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.

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.

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.

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.

Dernier cas : La nouvelle version de rig casse l’animation.

Ç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.

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.

Éviter le problème ?

Avant d’aborder les stratégies pour minimiser l’impact d’une telle mise à jour, je dois aborder comment l’éviter.

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:

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.

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.

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.

Mais c’est un autre sujet. :cayMal:

Stratégies

Il y a plusieurs façons d’éviter le drame d’une mise à jour de rig qui détruise l’animation. :neutral:

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.

Un gel de version a des implications importantes et c’est le choix de quand 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.

Imaginons la situation suivante :

Notez qu’afin de ne pas complexifier mon exemple, je ne parlerais pas de la version « latest », mais la logique est similaire.

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.

Cette méthode peut fonctionner dans des petits groupes, mais est difficile à mettre en place dans des grosses structures. :papi:

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.

Ceci a les effets suivants :

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 :

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.

Quand je vous disais qu’il n’y a aucune solution miracle… :vomit:

Voici ma solution, imparfaite, qui semble fonctionner « en pratique » :

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.

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 v1, alors qu’on est sur une v4 depuis des mois, d’où l’importance de votre pipeline à stocker et exposer facilement aux graphistes les dépendances de fichiers.

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.

Le gel des versions, un problème plus vaste

Vous l’aurez deviné, la problématique du gel de versions est un problème très vaste qui n’a aucune solution miracle :

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.

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.

:marioCours:

Gérer des caméras sous-samplées dans Nuke

vendredi 31 décembre 2021 à 17:31

Avez-vous déjà eux des problèmes de rendu dans Nuke en utilisant des caméras ayant des sous-samples ? :reflechi:

Non ? Super ! Grace à ce billet, vous allez savoir comment gérer cette situation si un jour ça vous arrive ! :siffle:

On va parler de rendu, de Nuke, de shake cam, d’export Alembic, de sampling temporel, de frame rate, 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:

Lors de l’import d’une caméra Alembic, Nuke échantillonne 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 frame rate :

Interface Nuke d’import de caméra

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.

En pratique on peut travailler comme ça sur la plupart des projets, alors pourquoi s’embêter ? :perplex:

Pourquoi sous-sampler une caméra ?

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.

L’exemple le plus évident concerne les shake cam. Quand un shake cam 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.

Sous-sampler la caméra permet de garder la profondeur du mouvement de shake.

Bien entendu (et c’est trop souvent le cas en production) si la fréquence du shake cam 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 :

Courbes de caméras superposées

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.

Par contre, un export de caméra sous-samplé d’un shake cam avec une fréquence faible entre les images (une demie période max) donnera une profondeur au motion blur.

Bref, on sous-sample pour les shake cam, mais uniquement si on arrive à garder leur fréquence a niveau de la frame… :tuComprendRien:

Un autre cas où le sous-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 shake cam, si vous ne sous-samplez pas, il y a fort à parier que le mouvement central soit une grosse bouilli, illisible.

Attention toutefois : Comme nous allons le voir, sous-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 shake cams, ni mouvements rapides : Laissez tomber. Vous aurez sûrement d’autres problèmes à gérer.

Mais si vous êtes ici, c’est sûrement parce que vous avez déjà rencontré le problème, pas vrai ? :dentcasse:

Comportement de Nuke

Quand vous importez votre caméra Alembic dans Nuke avec le bon frame rate, ce dernier va créer une clef à chaque frame. Le comportement de Nuke est très con :

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:

Le problème

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 matte painting).

Contourner le problème

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 sous-samplé (Merci à Vincent Glaize pour l’astuce). :gne:

Pour cela, il faut jouer sur la valeur de frame rate du nœud Nuke. L’idée est d’augmenter le frame rate de lecture de l’Alembic pour attraper tous les samples, pour ensuite scaler la vitesse de lecture suivant un ratio inverse.

Pas de panique, on va procéder par étapes. :hehe:

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: ).

La caméra est exportée avec cette commande MEL :

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";

Voici la sortie de la commande abcls :

$ 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

Au passage : Peu de gens le savent, mais un fichier Alembic stock ses samples en secondes, pas en frames.

Multipliez ses valeurs par le frame rate correspondant à votre export de caméra (ici 24) pour obtenir qu’équivalent en frame :

>>> 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]

Si on fait fi des problèmes de précision dûes à la sortie de la commande abcls, on obtient : 1.0, 1.125, 1.25, 1.375, 1.5. On a donc bien nos 5 samples aux bonnes positions.

Voici les courbes d’animation du translate Y 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 :

Notez que cette animation va de l’image 1 à 1.5, la position des samples est en rouge.

Si on amène ça dans Nuke à un frame rate de 24, on ne verra rien, car la position du sample sur l’image 1 (et 2) est à 0 :

Donc quelle valeur de frame rate mettre pour faire correctement apparaître ces samples dans Nuke ? :reflexionIntense:

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 frame rate original par 8 pour avoir tous les samples de façon précise : On va donc entrer une valeur de frame rate de 24 × 8 = 192 dans notre nœud Nuke :

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:

Aparté sur l’interpolation

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 frame rate par 5 (192 × 5 = 960), et regardez la courbe :

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.

D’où l’importance de tomber exactement sur les samples exportés. :nevroz:

Bon, on a réussi à avoir l’entièreté des sous-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 :

Ici, Camera_originale est mon nœud de caméra pointant sur l’Alembic et lu à un frame rate de 192.

Camera_mirror est une caméra vide créée manuellement avec des expressions pour relier chaque paramètre :

Par exemple :

Camera_originale.translate.y(t*8)

Ç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.

Une fois cela fait, mettez un nœud de rendu, ajouter quelques samples, calez votre shutter (ici, 0.0 - 0.5) :

En enfin, rendez :

Votre motion blur contient bien l’entièreté du mouvement. :banaeyouhou:

Limitations

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 shutter n’est pas pilotable ; dans la plupart des moteurs on peut alterner entre une distribution uniforme, en triangle, gaussienne, trapèze, etc.

Conclusion

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.

J’espère que ce hack vous permettra de vous dépêtrer de quelques soucis fâcheux. :seSentCon:

À très bientôt !

:marioCours:

Lire les Light Stats dans Guerilla

mardi 14 décembre 2021 à 23:25

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 évidant d’interpréter correctement ces valeurs : Elles ne sont pas forcément simples à comprendre, et encore moins à mettre en relation avec l’image. :reflechi:

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.

Notez que cette version est sortie ce soir et que je n’ai pas pu m’empêcher de faire un billet… :baffed:

Activer le rapport

Pour activer le rapport de statistique des lights, il faut aller dans Preferences, Rendering, Logs & Diagnostics, mettre Verbosity à « Diagnostics » et cocher Render Statistics :

Explication détaillée

À la fin de votre rendu, vous aurez quelque chose qui ressemble à ça :

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

« Pas mal, non ? C’est français. »

Pas de panique, on va commenter tout ça ! :hehe:

Comme vous vous en doutez, nSmp est le nombre de samples envoyés sur la light. Je ne m’éternise pas. :redface:

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:

TL est le total d’intensité lumineuse (Total Luma) agrégé par la lampe sans tenir compte de l’occlusion (c.à.d, sans prendre le test d’ombre).

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 par ici).

Quand un rayon arrive sur une surface, le moteur calcul l’illumination de ce que nous allons appeler le shading point. Pour cela il va utiliser la normale de la surface du shading point et en extraire un hémisphère (la normal étant la direction du pôle).

Avec cet hémisphère, le moteur peu déjà éliminer les lights étant « derrière l’hémisphère » (c.à.d, derrière la normale du shading point) ainsi que celles « dos au shading point » (c.à.d, qui montrent leurs fesses au shading point) car elles ne contribueront pas à l’illumination du shading point.

Reste les autres lights, celles qui sont face au shading point : Comment déterminer la light ayant la meilleure contribution ? :petrus:

On pourrait lancer des rayons au hasard sur toutes les lights, mais en pratique, seules quelques-unes contribueraient vraiment à l’illumination du shading point. 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 shading point.

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 shading point, 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 shading point si elle est visible par ce dernier.

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 shading point.

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 shading point, 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 shading point.

Mais l’effet de bord est évident : Si vous avez une light derrière un mur, mais en direction de votre shading point, le moteur privilégiera cette lampe.

C’est ça le 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).

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:

Une fois que vous avez compris ça, vous êtes en mesure d’expliquer la relation entre TL% et OL%.

Première chose : TL% 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. Total de TL% de toutes les lights est 100 %.

Et enfin : OL% 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.

Vous l’aurez deviné : TL% - OL% = Pourcentage de perte de cette light par rapport à tous les autres lights.

Et le ratio OL% / TL% = ratio d’efficacité de cette light dans la scène. Exemple dans notre cas sur la light la moins efficace :

>>> 4.0/5.0
0.8  # 80 %

Et cela correspond bien à notre valeur de Eff% (moins les imprécisions). :youplaBoum:

En pratique, la valeur TL n’est pas d’une grande aide, ce sont les valeurs TL%, OL% et Eff% qui sont importantes.

Le test du mur

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:

Voici la scène :

Un plan, une sphère, deux lights positionnées à 180 l’une de l’autre et un mur sur celle de droite :

Le rendu :

Clairement la light derrière le mur ne contribue pas au rendu, et les statistiques le confirme :

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

On remarque immédiatement l’Eff% de la light derrière le mur à 0. Cette light est inefficace et peut être retirée du rendu sans risque. :bravo:

La raison pour laquelle le TL% 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 shading points 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.

Regardons les temps de rendu. Ma RenderPass 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.

Allons encore plus loin et regardons les light stats de ce « lighting parfait » :

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

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 shading point 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.

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é.

Notez aussi le nombre total de samples lancé avant de converger : Un tiers de moins. Une information confirmée par l’AOV de heat.

Rendu avec les deux lights :

Rendu avec une seule light, à droite :

On remarque que Guerilla n’arrive pas à sampler une partie de l’image. Et pour cause, aucune light n’illumine ses zones. :cayMal:

Conclusion

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:

À très bientôt !

:marioCours:

Faire un AOV d’occlusion avec la DiffuseColor dans Guerilla

samedi 9 octobre 2021 à 23:29

Il y a plein de façons de faire de l’ambiant occlusion. :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’ambiant occlusion 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.

Quand le lookdev des assets est (enfin) disponible, il peut être intéressant d’avoir une ambiant occlusion 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:

Méthode de base

Avant de faire des choses compliquées, on peut s’appuyer sur ce que Guerilla propose, à savoir les AOVs Occlusion et Albedo. Voici une scène éclairée avec amour :petrus:  :

Le lookdev des objets, c’est juste une texture colorée différente mise dans l’attribut « DiffuseColor » de leur « Surface2 » respectif. :siffle:

On lui ajoute les deux AOVs, Occlusion et Albedo :

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:

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).

Logique de récupération des shaders dans Guerilla

Petit rappel au cas où vous ne seriez pas au courant. :mayaProf:

Quand vous assignez un material dans un RenderGraph, Guerilla n’assigne en fait qu’un nom (ici, « Surface2 ») à l’attribut shader.surface. 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 :

...
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
...

Au moment de l’envoi au moteur, Guerilla va chercher le material auquel correspond ce nom en suivant une logique assez particulière :

Pour faire court, si un material nommé « Surface2 » est présent à la racine de votre scène, il sera utilisé à la place du Surface2 de la librairie :

Guerilla surface2 scene vs library

Ici, c’est le Surface2 à gauche qui sera envoyé au moteur, prenant le pas sur celui de la librairie, à droite. :tuComprendRien:

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:

Donc si on modifie notre Surface2 importé localement afin que sa seule sortie soit une occlusion multipliée par son attribut DiffuseColor, le calcul d’illumination de tous les objets sera l’ambiant occlusion utilisant ses propres textures.

Faisons donc ça ! :youplaBoum:

Méthode avancée

Créez un shader Surface2 à la racine (« Ctrl+Espace », « surface2 », « Entrée ») :

Le shader s’importe à la racine de votre scène :

Entrez à l’intérieur et supprimez tout ce que vous y trouvez !

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.

Ajouter un nœud AOV (« Ctrl+Espace », « aov », « Entrée ») :

Renommer « Value » par un nom perso « ColoredAO » :

Mettre un nœud « Occlusion » et le connecter à « ColoredAO » :

Dans la RenderPass, créez un AOV. Vous pouvez le nommer comme vous voulez :

Dans cet AOV, mettez Accepted expression à [Technical] Primary et Shader Color à ColoredAO :

Si vous rendez à ce stade, vous aurez une occlusion tout simple :

Mais ce qu’on veut, c’est de la couleur ! :perplex:

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 » :

Puis cochez « Exposed » :

Notez que vous pouvez le faire en cliquant sur l’attribut du nœud, directement dans le graph :

Et sous vos yeux ébahis :

Que pour les « Surface2 » ?

Si vous n’overridez que le Surface2 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 Hair, Curve et Eye.

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 Hair ou de Curve, vous utiliserez sûrement la « Color » ou la « RootColor », suivant votre workflow. :pasClasse:

Notez que vous pouvez aussi multiplier les couleurs vous-même si le cœur vous en dit (je n’ai jamais testé) :

Le cas du shader de Eye

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:

Pour que ça fonctionne, il faut l’évaluer totalement. Pour cela, connectez directement l’Albedo à occlusion, comme ici :

Ceci évalue tout le shader des yeux, mais comme ils ne sont pas particulièrement coûteux, on peut se le permettre.

Voilà ! En espérant que ça serve à quelqu’un !

À très bientôt !

:marioCours:

Les variations de lookdev par tags dans Guerilla

samedi 2 octobre 2021 à 23:32

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:

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:

La conclusion restera ouverte…

La variation, un concept flou

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 tout le monde comprend que personne ne voyait la même chose. :tuComprendRien:

Mais pourquoi ? D’où vient le problème ? :reflexionIntense:

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:

Intégrer un concept de variation nécessite de se poser pas-mal de questions. En vrac :

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 ?

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…

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:

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:

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 :

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:

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.

Fini le pâté de texte, maintenant on rentre dans le vif du sujet ! :enerve:

Présentation de la méthode par tag

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:

À l’intérieur du RenderGraph

Vous pouvez embrancher votre lookdev directement dans le RenderGraph de lookdev, via l’utilisation d’un tag :

Variation par tag dans un RenderGraph Guerilla

Ici, « var1 » et « var2 » applique un override sur le shader « Surface2 ».

Via un RenderGraph dédié

L’idée est d’avoir un RenderGraph s’appliquant après celui du lookdev (attribut order plus élevé) et dédié à l’override d’attributs propres à cette variation :

Variation par RenderGraph dans Guerilla

Remarquez comme on a juste découpé le RenderGraph précédent.

Ensuite, on configure les deux RenderGraph (de droite) pour qu’ils s’appliquent sur leur tag respectif (et un order suivant celui du RenderGraph de base) :

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.

S’arrêter à ce stade pose pas mal de problèmes :

Ici, j’assigne uniquement le tag de variation (« var1 ») 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 « var1 » 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:

Au lieu d’assigner un seul tag « var1 » à ce RenderGraph, on peut également lui coller un second tag, lié à l’asset. Dans le cas de l’asset chaise, on aurait : « asset_chaise, var1 ». 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 « prefix » où « reference », par exemple).

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.

Un nœud SetTags, pour les chambouler tous !

J’en profite pour dire qu’il existe un nœud SetTags qui permet d’assigner dynamiquement des tags à des objets (via des regex dans des Path, par exemple). La seule contrainte étant que ce nœud doit être dans un RenderGraph précédent le RenderGraph utilisant le tag :

Si un RenderGraph « A » utilise le nœud SetTags pour appliquer le tag « toto » sur des objets, ce tag ne pourra être utilisé que dans RendreGraph « B », dont l’attribut order est plus grand que celui de « A ». On aurait donc une chaîne de RenderGraph ressemblant à ça :

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:

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.

La limitation de la méthode par tag

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:

Ces mécanismes de gestion des tags ont en commun de s’appuyer sur le SceneGraph (la Node List), 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.

Cela pourra faire l’objet d’un billet dédié… :jdicajdirien:

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 que 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 !).

En espérant vous avoir donné des éléments de réflexion sur le sujet. :youplaBoum:

À bientôt !

:marioCours: