Notes concernant Guerilla 2
- Adaptive sampling
- Shadow terminator
- Les caustiques sont activés par défaut
- Supprimez la contribution des rayons secondaires de Velvet
- Diffuse bounces et Light Max
- Faire du lookdev avec de l’adaptatif
- Les poils
- Prune VS Visibility
- Faire du débogage rapidement
- La génération automatique des textures
- Les logs
- Les portails
- Évaluation des procédurales
- Trace sets par défaut
- Configurer ACES dans Guerilla 2.3
- Isoler l’éclairage d’une volumétrique
- Bake2D
- Motion Blur
Adaptive sampling
Avant d’aller plus loin, vous devez prendre connaissance de la documentation :
- Samples est la valeur maximum de samples utilisés quand l’adaptive sampling est activé (l’adaptive sampling est activé quand la valeur d’Adaptive Threshold est plus élevée que 0).
- Adaptive Threshold est à 0 par défaut, ce qui veut dire que tous les samples seront utilisés. 0.03 est une bonne valeur de production.
- Adaptive Min Samples correspond au nombre de samples tracés avant que l’adaptive sampling ne commence. 16 est souvent assez, 8 peut être utilisé en cas de faible variance, pour des rendus simples.
Utilisez toujours une AOV de Heat et Confidence pour vérifier comment votre sampling se comporte.
AOV de Heat
Pour une description de cette AOV, nous allons considérer les valeurs d’adaptive sampling 256/0.03/16.
Rouge
Ce sont les pixels atteignant le nombre maximum de samples (c.à.d. 256). Servez-vous-en pour identifier les zones de votre rendu qui ne peuvent atteindre le seuil voulu (threshold), vous pourriez avoir du grain. À vous de décider si une augmentation du nombre de sample est nécessaire.
Vert
Ce sont les valeurs de sampling minimum et maximum normalisées (de 0.0 à 1.0) :
- La valeur 0.0 (en noir) représente 16 samples.
- La valeur 1.0 (en blanc) représente 256 samples.
Cela permet de visualiser la distribution des samples dans l’image.
Bleu
Ce channel vous permet de déterminer le nombre exact (absolu) de sample projeté sur un pixel donné (169 sur l’image ci-dessus).
AOV de Confidence
Cet AOV de « confiance » affiche la quantité de bruit estimé par le moteur :
- Plus le pixel est sombre, plus le moteur estime que le pixel a « convergé » (c.à.d. qu’il n’y a plus de bruit).
- Plus le pixel est clair, plus le moteur estime qu’il y a du bruit dans le pixel (de la variance), et donc qu’il faut encore y lancer des samples.
Shadow terminator
L’attribut Shadow terminator est dans Attributes, Raytracing et est à 0 par défaut (désactivé).
Une valeur par défaut comprise entre 0.25 et 0.5 élimine la plupart des artéfacts.
Ne montez pas trop cette valeur au risque d’avoir certains sommets qui « attrapent » la lumière (e.g. dans un trou subdivisé).
Les caustiques sont activés par défaut
Les caustiques sont les rayons suivant le chemin : Camera -> Rebond diffus -> Surface glossy ou spéculaire.
Par défaut, Guerilla 2 ne prends pas en compte les caustiques lors du rendu, mais la définition par défaut de ce qui est considéré comme caustique est défini dans Sharp Caustics
:
Sur une RenderPass, dans la section Caustics, Caustics Types est sur Sharp Caustics
par défaut, ce qui veut dire que les rayons suivants le chemin Camera -> Rebond diffus -> Spéculaire ne seront pas rendus.
C’est une bonne chose, car ce chemin est la source de rayons à forte quantité d’énergie qui entraine un « mouchetage » de vos rendus (fireflies).
En revanche, cela implique que les caustiques glossy (c.à.d. les rayons suivants le chemin : Camera -> Rebond diffus -> Glossy) seront tracés, ce qui peut amener un subtil mouchetage dans les rendus aux conditions d’éclairage difficiles.
Il n’est pas rare que la contribution de ces caustiques n’apporte rien d’autre à l’image qu’un subtil grain. Si c’est le cas, mettez Caustics Type à Glossy Caustics
ou All Caustics
pour les supprimer.
Supprimez la contribution des rayons secondaires de Velvet
Le Surface2 est composé d’une couche Velvet permettant de simuler l’illumination des micros-fibres (e.g. pour les vêtements). Par définition, ce calcul d’illumination dépend de la position de la caméra.
Le problème est que la contribution du velvet est utilisé pour les rayons secondaire, même si vous mettez Caustics Type sur All Caustics
. Suivant votre éclairage et vos matériaux, vous risquez de voir apparaitre du bruit dans l’illumination indirecte (notamment le SSS) et les reflets, le velvet propageant les samples de haute intensité.
La plupart du temps, ce que vous souhaitez obtenir est un effet visuel subtile sur vos vêtements, qui n’impacte pas les rayons secondaires.
Mettez VelvetDepth
à 0 de sorte que le velvet ne soit visible que depuis les rayons de caméra.
Avant d’utiliser la couche Velvet, je vous encourage à essayer d’utiliser Diffuse Roughness combiné à une augmentation légère augmentation de la Diffuse Color. Suivant l’effet que vous souhaitez obtenir, ces paramètres peuvent se révéler suffisant, tout en évitant d’augmenter la complexité du shader.
Diffuse bounces et Light Max
Dans la liste suivante, j’utilise la notation Diffuse bounces/Light Max, donc 2/1 veut dire 2 Diffuse bounces et Light Max à 1.
- 1/0 pour les scènes d’extérieur ou d’intérieur sans éclairage extérieur.
- 1/1 pour les intérieurs où vous auriez normalement utilisé des Portals (c.à.d. des fenêtres larges).
- 2/2 pour des scènes d’intérieur fermées avec des petites lumières (e.g. des bougies).
- 3/3 pour les éclairages complexes (e.g. des tunnels).
- 4+ Diffuse bounces est souvent inefficace, les lumières perdant pratiquement toute leur énergie après deux rebonds.
L’utilisation de Light Max combiné à celle des Portals augmente les temps de rendu. Ne combinez pas les deux.
Light Max est couteux, ne l’utilisez pas si les lumières sont suffisamment larges pour que les rayons puissent les trouver facilement. Réciproquement, privilégiez l’utilisation de quelques lumières larges plutôt que beaucoup de petites.
Du point de vue de la variance, il est toujours plus efficace d’utiliser une source large de lumière plutôt que plusieurs petites. Si vos lumières ne sont pas directement visibles depuis la caméra, désactivez-les et créez-en une seule, les englobants et ayant la même couleur et intensité. Vous pouvez ensuite alterner entre le groupe des petites lumières et ce « proxy » qui les représente. Dans le cas d’un décor, proposer un proxy à des groupes de petites lumière permet au graphiste de gagner du temps lors de l’éclairage au plan.
Faire du lookdev avec de l’adaptatif
Je suggère de toujours faire deux types de tournettes de lookdev :
- Une servant à valider le look artistique d’un asset. Il peut utiliser des hautes valeurs de rendu (10000 samples).
- Une seconde ayant une valeur de sample fixe, assez basse, de sorte qu’un shading complexe « apparaîtra » sur l’image sous forme d’un fort bruit, ce qui vous permettra de détecter les assets qui vont potentiellement générer du bruit une fois dans les plans.
Si possible, évitez d’envoyer un lookdev en validation si vous n’avez aucune idée de la complexité de son shader.
Les poils
Préférez beaucoup de poils opaques plutôt que peu de poils transparents. Les poils transparents rendent leurs ombres difficiles à anticiper une fois dans les plans, car vous atteignez rapidement le Max Depth du moteur.
Prune VS Visibility
Quand vous êtes au plan, préférez l’utilisation de l’attribut Pruned pour supprimer les objets du rendu.
Dans une hiérarchie, l’attribut Hidden peut être surchargé au niveau d’un enfant. Pruned stop la traversée de sa hiérarchie immédiatement.
Exemple, dans la hiérarchie suivante :
Root
'-> Enfant
Root
peut avoir Hidden à True
, mais Enfant
peut avoir Hidden à False
surchargé dans un RenderGraph. Si c’est le cas, Enfant
sera visible au rendu.
Cela permet au RenderGraph de réafficher n’importe quel nœud qui a été mis en Hidden par un RenderGraph précédent.
À l’inverse, si Root
à Pruned à True
, la traversée de la hiérarchie s’arrête immédiatement, et Enfant
ne pourra jamais être visible et n’est pas envoyé au moteur.
La documentation est ici.
Sur des grosses hiérarchies, Pruner certains nœuds permet d’optimiser le temps entre le moment ou on clique sur Render et le démarrage du rendu.
Faire du débogage rapidement
- Attributes, Subdivision, Subdivision Level -> 0
- Attributes, Shading, Texture Approximation -> Always
- Attributes, Shading, Texture Max Size -> 0
Ceci désactive toutes les subdivisions et diminue les appels aux textures en prenant la plus petite mipmap possible.
La génération automatique des textures
Par défaut, Guerilla génère des fichiers .tex
dans un dossier .guerilla
pour chaque texture qu’il trouve. Ceci peut devenir un problème quand votre pipeline grossi.
Pour désactiver cela, allez dans Preferences, Texture Manager, et désactivez Auto Build Texture.
La documentation est ici.
Les logs
Allez dans Preferences, Rendering, Logs & Diagnostics puis passez la Verbosity à Diagnostics :
La documentation est ici.
- Render Statistics ne change pas le temps de rendu.
- Render Profile est supposée ajouter une légère augmentation du temps de rendu (suivant les situations), mais je n’ai jamais noté de différence flagrante et je vous encourage fortement à l’activer.
La distribution des samples
Guerilla affiche des informations permettant d’évaluer rapidement l’usage de l’adaptative dans vos rendus :
Silver:
Primary rays : 695M
Average samples/pixel : 753.840464
Median samples/pixel : 916.480000
[0-204] : 12.4% 114K/922K
[204-409] : 8.1% 74.9K/922K
[409-614] : 10.2% 93.6K/922K
[614-819] : 14.1% 131K/922K
[819-1024] : 55.2% 510K/922K
Shot rays/batch : 497.398 2.67G/5.37M
Shadow rays/batch : 918.959 4.93G/5.37M
Ici on voit que 55 % des pixels ont demandé entre 819 et 1024 samples. Avant même de regarder une AOV dédiée, vous avez une idée de la distribution des samples.
On peut illustrer la chose avec un graphique. En prenant la valeur de samples du milieu (102 pour [0-204]
, 306 pour [204-409]
, etc.) et en mettant ces chiffres dans un numbers.plot
:
102 12.4
306 8.1
511 10.2
716 14.1
921 55.2
Puis avec cette commande :
$ gnuplot -e "set terminal png; set style data linespoints; set border linewidth 1.5; set xlabel 'Samples par pixel'; set ylabel 'Pourcentage des pixels';set xrange [0:1024]; set yrange [0:100]; plot 'numbers.plot';" > out.png
On obtient ce graphique approximatif qui illustre cette distribution des samples :
Encore une fois, on remarque qu’ici, les pixels coûteux en samples représentent la moitié des pixels de l’image.
Lister les attributs de shape
La case Diagnostic Shape permet de lister les attributes de chaque shape envoyé au moteur. Pour un fichier Alembic d’animation, on aurait :
02/25/2021 19:50:17 385M [08] SHAP DIA: loaded shape '/chemin/vers/mon/fichier.abc' '/node/nodeShape.RenderGeometry'
02/25/2021 19:50:17 385M [08] SHAP DIA: P float3[286] min=(-50.629753,3.517493,4.852751) max=(-50.393837,3.630370,5.088417)
02/25/2021 19:50:17 385M [08] SHAP DIA: P2 float3[286] min=(-50.424358,3.517493,4.852391) max=(-50.188423,3.630370,5.088114)
02/25/2021 19:50:17 385M [08] SHAP DIA: N float3[286] min=(-0.971818,-0.996817,-0.964958) max=(0.928156,0.999970,0.969747)
02/25/2021 19:50:17 385M [08] SHAP DIA: st float2[286] min=(0.034514,-1.971024) max=(0.973398,-1.034953)
02/25/2021 19:50:17 385M [08] SHAP DIA: dP avg=(0.205598,0.000000,-0.000384) min=(0.204475,0.000000,-0.001563) max=(0.206905,0.000000,0.000866)
P
etP2
étant les tableaux de position des points dans l’intervalle de temps rendu (t
+shutter open,t
+shutter close).N
étant la normale sur chaque point.st
étant les UV (st
étant l’appellation RenderMan pour les UV).dP
étant ce que je crois être la bounding box englobant les tableaux de positionP
etP2
.
Notez que cela fonctionne pour les particules et les channels des fichiers OpenVDB.
Dans le cas d’un système de particule exporté depuis Maya on obtient :
02/25/2021 19:51:04 204M [03] SHAP DIA: loaded shape '/chemin/vers/mon/fichier.abc' '/nParticle1/nParticleShape1.RenderGeometry'
02/25/2021 19:51:04 204M [03] SHAP DIA: P float3[33] min=(-0.592114,-0.050974,-0.386162) max=(0.619097,0.635682,0.457532)
02/25/2021 19:51:04 204M [03] SHAP DIA: P2 float3[33] min=(-0.592114,-0.050974,-0.386162) max=(0.619097,0.635682,0.457532)
02/25/2021 19:51:04 204M [03] SHAP DIA: float12 float1[33] min=(0.000000) max=(32.000000)
02/25/2021 19:51:04 204M [03] SHAP DIA: color2 float3[33] min=(-2.304702,-1.120984,-2.014977) max=(2.083903,2.404513,1.995705)
02/25/2021 19:51:04 204M [03] SHAP DIA: dP avg=(0.000000,0.000000,0.000000) min=(0.000000,0.000000,0.000000) max=(0.000000,0.000000,0.000000)
Il est intéressant de noter que la taille du tableau est 33
, ce qui veut dire qu’il y a 33 particules. Si on le compare aux valeurs de min
et max
de float12
, on peut en déduire que float12
correspond aux valeurs d’id
des particules
Il peut arriver que ces lignes affichent no deform motion blur
. Dans ce cas, P2
et dP
ne sont pas présents. Cela veut dire qu’il n’y a pas de déformation de shape dans l’intervalle échantillonné, mais ça ne veut pas dire qu’il n’y en a pas ailleurs dans le fichier.
Ces messages ne sont affichés que lors de la mise de la shape en cache. Cela veut dire qu’il faut vider le cache pour les afficher de nouveau :
Les portails
Les portails restent une option intéressante, mais leur coût en temps de rendu augmente avec leur nombre.
Voici une scène très simple composée d’une boite fermée avec une ouverture sur le côté et un SkyLight :
Paramètres :
Samples | 10 000 |
Adaptive Threshold | 0.1 |
Adaptive Min Samples | 16 |
Sampling Mode | Progressive |
Résolution | 1280 × 720 |
Cette scène très simple a été rendue dans différente configurations. D’abords sans portails, puis un portail :
Puis 9 :
Puis 18 :
Je cherchais à déterminer si l’utilisation de plusieurs portails avait un impact sur le temps de rendu.
Temps de rendu | Configuration |
---|---|
4m57s | Sans portails |
1m40s | 1 portail |
1m57s | 9 portails |
2m16s | 18 portails |
En mettant ces chiffres dans un numbers.plot
:
0 297
1 100
9 117
18 136
Puis avec cette commande :
$ gnuplot -e "set terminal png; set style data linespoints; set xlabel 'Nombre de portails'; set ylabel 'Temps (sec)';set xrange [-2:20]; set yrange [90:305]; plot 'numbers.plot';" > out.png
On obtient ce magnifique graphique :
On a donc un gain important au premier portail (l’éclairage étant prédisposé à leur utilisation) qui diminue de façon linéaire. Il est donc important de ne pas en abuser.
Évaluation des procédurales
Les procédurales mises dans les RenderGraphs ont un comportement particulier qu’il est nécessaire de comprendre si on souhaite s’éviter de nombreuses confusions, notamment sur ce qui a trait à « l’Evaluation Order ». Pour rappel l’évaluation d’un nœud dans les RenderGraphs se fait grossièrement de la sorte :
- On prend un nœud de type
Primitive
de la Node list. - On passe ce nœud dans le premier RenderGraph (suivant l’Evaluation Order).
- Les attributs du nœud sont modifiés par les nœuds du RenderGraph.
- On passe ce nœud au RenderGraph suivant (retour à la seconde étape).
- Une fois le nœud passé dans tous les RenderGraphs, on l’envoie avec ses attributs définitifs au moteur.
- On passe à la primitive suivante (retour à la première étape).
Ce rappel étant fait, prenons un exemple et imaginons la hiérarchie suivante :
Root (Transform)
'- sol (Transform)
'- solShape (Mesh)
Si vous souhaitez que solShape
soit le scalp de votre procédurale, votre RenderGraph sera composé d’un nœud de type Path
pointant vers solShape
connecté à l’entrée scalp
d’un nœud de type Procedural
(nommé myProc
sur le schéma ci-dessous) dont la sortie est connectée au reste du RenderGraph :
.------------.
| Procedural |
| myProc |
.----------. | |
| Path | O Parent |
| solShape | O hairs |
| | O instances |
| Output O-----O scalp |
'----------' | Output O--.
'------------' \
Ce qu’il faut comprendre ici c’est qu’à l’inverse du fonctionnement habituel d’un RenderGraph, les nœuds vers lesquelles pointe le nœud de type Path
(ici, solShape
) ne sont pas directement passés à la procédurale. Au lieu de ça, ces nœuds sont mis dans un set avec le nom/asset_id de la procédurale. Ici nous aurions un set scalp:myProc
dans lequel sera mis solShape
.
Ce mécanisme de set est utilisé pour tout ce qui est connecté aux entrées de la procédurale :
- Parent
- hairs
- instances
- scalp
Plusieurs primitives du scene graph peuvent donc être dans le set scalp:myProc
(e.g. si le nœud de Path
est une expression régulière).
Ce set est visible en haut à gauche quand vous sélectionnez un nœud passant dans le graph (ici, solShape
) :
Voilà pour la première partie de l’évaluation : Les nœuds en entrées de la procédurale sont assignés un set de sorte à pouvoir être récupéré rapidement plus tard.
La seconde partie de l’évaluation est plus simple : Guerilla va chercher tous les nœuds de procédurale connectés, de quelque façon que ce soit, à la sortie (l’Output) d’un RenderGraph. Chaque procédurale ainsi trouvée est ajoutée au scene graph (c.à.d, mis dans la scène de rendu) soit :
- À la racine de la scène, quand l’attribut
Parent
n’est connecté à rien. - Sous chaque nœud présent dans le set
parent:myProc
, set automatiquement généré au rendu et comprenant tous les nœuds connectés àParent
.
À ce stade, le moteur contient des nœuds de procédurale avec les informations pour générer leur contenu, mais rien n’est encore généré. C’est la raison pour laquelle vous ne pouvez pas instancier le contenu d’une procédurale (e.g. un buisson procédural instancié plusieurs fois). Vous pouvez instancier un nœud de procédurale (e.g. en utilisant une expression régulière dans le Path
connecté à son attribut Parent), mais pas son contenu. Ce n’est qu’une fois que le rendu commence que le contenu de la procédurale est généré.
La boite englobante utilisée par défaut est celle du (ou des) scalp(s), ce qui n’est pas toujours optimal (vous pouvez désactiver l’attribut Load On Demand de la procédurale pour y remédier).
Isolation des procédurales depuis un RenderGraph de lookdev préfixé
Vous utilisez peut-être l’attribut User Prefix de vos RenderGraph pour assigner vos lookdev. Imaginons une scène comme celle-ci :
toto_001:Root (Transform)
'- toto_001:Mesh (Transform)
'- toto_001:Scalp (Primitive)
toto_002:Root (Transform)
'- toto_002:Mesh (Transform)
'- toto_002:Scalp (Primitive)
Un RenderGraph avec un User Prefix à toto_
appliquera son lookdev au contenu de toto_001:Root
et toto_002:Root
.
Cela peut poser des problèmes avec l’assignation des procédurales. Imaginons le graph d’assignation suivant :
Description :
- Assignation d’une procédurale
toto_000:Fur
sous chaqueRoot
. - Utilisation des objets
Scalp
comme scalps. - Assignation d’un shader de
Curves
sur chaque nœud de procédurale (nommétoto_000:Fur
). - Assignation d’un shader de
Surface2
sur tout ce qui est sous le groupeMesh
.
Le résultat de l’évaluation est visible dans le Live SceneGraph :
Toutefois, au rendu moment du rendu, vous constaterez que chaque procédurale génère ses poils sur deux scalps (toto_001:Scalp
et toto_002:Scalp
) :
04/16/2022 10:43:46 313M [03] PROC LOG: toto_001:Root|toto_000:Fur: computing hair implantation on scalp toto_001:Root|toto_001:Mesh|toto_001:Scalp..
04/16/2022 10:43:46 313M [03] PROC LOG: toto_001:Root|toto_000:Fur: 400 hairs implanted
04/16/2022 10:43:46 313M [03] PROC LOG: toto_001:Root|toto_000:Fur: computing hair implantation on scalp toto_002:Root|toto_002:Mesh|toto_002:Scalp..
04/16/2022 10:43:46 313M [03] PROC LOG: toto_001:Root|toto_000:Fur: 400 hairs implanted
04/16/2022 10:43:46 313M [03] PROC LOG: toto_001:Root|toto_000:Fur: generating hairs for toto_001:Root|toto_001:Mesh|toto_001:Scalp..
04/16/2022 10:43:46 313M [03] PROC LOG: toto_001:Root|toto_000:Fur: generating hairs for toto_002:Root|toto_002:Mesh|toto_002:Scalp..
...
04/16/2022 10:43:46 315M [06] PROC LOG: toto_002:Root|toto_000:Fur: computing hair implantation on scalp toto_001:Root|toto_001:Mesh|toto_001:Scalp..
04/16/2022 10:43:46 315M [06] PROC LOG: toto_002:Root|toto_000:Fur: 400 hairs implanted
04/16/2022 10:43:46 315M [06] PROC LOG: toto_002:Root|toto_000:Fur: computing hair implantation on scalp toto_002:Root|toto_002:Mesh|toto_002:Scalp..
04/16/2022 10:43:46 315M [06] PROC LOG: toto_002:Root|toto_000:Fur: 400 hairs implanted
04/16/2022 10:43:46 315M [06] PROC LOG: toto_002:Root|toto_000:Fur: generating hairs for toto_001:Root|toto_001:Mesh|toto_001:Scalp..
04/16/2022 10:43:46 315M [06] PROC LOG: toto_002:Root|toto_000:Fur: generating hairs for toto_002:Root|toto_002:Mesh|toto_002:Scalp..
Vous avez donc 4 systèmes de poils, générés les un sur les autres. Vous pouvez facilement le constater en ajoutant de la transparence aux poils et en visualisant leur alpha, chaque poil est en fait doublé : Avec une opacité à 0.25, votre poil aura une opacité à 0.4375, car il y a en fait deux poils.
Et si vous ajoutez une troisième hiérarchie toto_003:Root
, vous aurez 9 systèmes de poils (3×3), et cela continue de façon exponentielle.
Description du comportement de l’envoi des procédurales au moteur
Pour comprendre pourquoi nous avons ce comportement, il faut comprendre comment les procédurales sont envoyées au moteur.
Au moment du rendu, Guerilla passe la procédurale au moteur, non pas avec des liens directs vers les surfaces de scalps à utiliser, mais avec des noms de sets. Ces sets sont supposés contenir les shapes de scalps.
MaProcédurale[scalp="set_de_scalps", (autres paramètres)]
"set_de_scalps"=Set[toto_001:Root|toto_001:Mesh|toto_001:Scalp,
toto_002:Root|toto_002:Mesh|toto_002:Scalp]
Une façon de s’en rendre compte consiste à sélectionner une des procédurales dans la vue Live SceneGraph et de faire Shift+D
. Ce raccourci liste les attributs envoyés au moteur, ainsi que leur valeur respective. Dans notre cas, on peut y trouver :
...
proceduraluser.hairsset: "hairs:10_lookdev_test|test_000:Fur_$(identifier.assetid)"
proceduraluser.instances: {}
proceduraluser.instancesset: "instances:10_lookdev_test|test_000:Fur_$(identifier.assetid)"
proceduraluser.interpolationmethod: "Shepard"
proceduraluser.scalp: {}
proceduraluser.scalpset: "scalp:10_lookdev_test|test_000:Fur_$(identifier.assetid)"
...
On voit clairement que les scalps utilisés sont en fait issus d’un set, généré au moment du rendu : scalp:10_lookdev_test|test_000:Fur_$(identifier.assetid)
. Notez la présence de l’expression $(identifier.assetid)
dont la valeur est simplement celle de l’attribut Asset Id.
Dans notre cas, comme ni toto_001:Root
, ni toto_002:Root
n’ont de valeur d’Asset Id, le nom du set de scalps se retrouve être le même pour les deux : scalp:10_lookdev_test|test_000:Fur_
.
Encore une fois, ce set est généré dynamiquement, au moment du rendu, en fonction de ce qui est présent au moment de l’assignation du RenderGraph. Dit autrement :
- La primitive
toto_001:Scalp
est mis dans le setscalp:10_lookdev_test|test_000:Fur_
. - La primitive
toto_002:Scalp
est mis dans le setscalp:10_lookdev_test|test_000:Fur_
(le même set que précédemment). - Au moment du rendu, la procédurale
toto_001:Root|toto_000:Fur
utilise le contenu du setscalp:10_lookdev_test|test_000:Fur_
, qui contient les deux scalps. - Ensuite, la seconde procédurale
toto_002:Root|toto_000:Fur
utilise le contenu du même set,scalp:10_lookdev_test|test_000:Fur_
, en tant que scalp.
D’où le fait que chaque procédurale génère des poils sur son scalp, ainsi que celui de l’autre.
Solution
Comme vous vous en doutez, il faut agir sur le set généré au moment, à savoir l’Asset Id. En effet, si on met les valeurs suivantes :
- Asset Id
toto_001
surtoto_001:Root
. - Asset Id
toto_002
surtoto_002:Root
.
Le problème disparaît. Le nom de chaque set est généré avec ses propres scalps depuis l’expression scalp:10_lookdev_test|test_000:Fur_$(identifier.assetid)
:
toto_001:Root|toto_000:Fur
aura le setscalp:10_lookdev_test|test_000:Fur_toto_001
contenanttoto_001:Scalp
.toto_002:Root|toto_000:Fur
aura le setscalp:10_lookdev_test|test_000:Fur_toto_002
contenanttoto_002:Scalp
.
Les procédurales sont donc générées sur leur scalp respectif. Pour reprendre la démonstration précédente :
- La primitive
toto_001:Scalp
est mis dans le setscalp:10_lookdev_test|test_000:Fur_toto_001
. - La primitive
toto_002:Scalp
est mis dans le setscalp:10_lookdev_test|test_000:Fur_toto_002
(différent du set précédemment). - Au moment du rendu, la procédurale
toto_001:Root|toto_000:Fur
utilise le contenu du setscalp:10_lookdev_test|test_000:Fur_toto_001
, qui contient le scalptoto_001:Scalp
. - Ensuite, la seconde procédurale
toto_002:Root|toto_000:Fur
utilise le contenu de l’autre set,scalp:10_lookdev_test|test_000:Fur_002
, en tant que scalp.
Le log confirme que seul deux systèmes de poils sont générés.
04/16/2022 14:36:55 370M [02] PROC LOG: toto_001:Root|toto_000:Fur: computing hair implantation on scalp toto_001:Root|toto_001:Mesh|toto_001:Scalp..
04/16/2022 14:36:55 370M [02] PROC LOG: toto_001:Root|toto_000:Fur: 400 hairs implanted
04/16/2022 14:36:55 370M [02] PROC LOG: toto_001:Root|toto_000:Fur: generating hairs for toto_001:Root|toto_001:Mesh|toto_001:Scalp..
...
04/16/2022 14:36:55 370M [05] PROC LOG: toto_002:Root|toto_000:Fur: computing hair implantation on scalp toto_002:Root|toto_002:Mesh|toto_002:Scalp..
04/16/2022 14:36:55 370M [05] PROC LOG: toto_002:Root|toto_000:Fur: 400 hairs implanted
04/16/2022 14:36:55 370M [05] PROC LOG: toto_002:Root|toto_000:Fur: generating hairs for toto_002:Root|toto_002:Mesh|toto_002:Scalp..
Vous pouvez vérifier la transparence : Avec une opacité à 0.25, votre poil aura une opacité à 0.25, car il y a en fait un seul poil.
Trace sets par défaut
Voici la liste des trace sets par défaut de Guerilla (2.1).
Lights
Les lumières semblent avoir deux traces sets par défaut :
LightSet
: Valeur par défautLights
.ShadowSet
: Valeur par défautShadows
.
Surface2
DiffuseTraceSet
: Valeur par défautDiffuse
(Section Diffuse).VelvetTraceSet
: Valeur par défautReflection
(Section Velvet).Spec1TraceSet
: Valeur par défautReflection
(Section Velvet).MetalTraceSet
: Valeur par défautReflection
(Section Metal).Spec2TraceSet
: Valeur par défautReflection
(Section Spec 2).DirtTraceSet
: Valeur par défautDiffuse
(Section Dirt).SSSScatterSet
: Vide par défaut (Section SSS).SSSTraceSet
: Valeur par défautDiffuse
(Section SSS).GlassTraceSet
: Valeur par défautRefraction
(Section Glass).
Hair
MultipleScatterSet
: Vide par défaut (Section Advanced/Multiple Scatter).TraceSet
: Valeur par défautDiffuse
(Section Advanced/Diffuse).
Volume
TraceSet
: Valeur par défautDiffuse
(Section Advanced).
Volume2
VolumeTraceSet
: Valeur par défautDiffuse
(Section Advanced).
Eye2
DiffuseTraceSet
: Valeur par défautDiffuse
(Section Diffuse).SSSScatterSet
: Valeur par défautDiffuse
(Section SSS).SSSTraceSet
: Valeur par défautDiffuse
(Section SSS).Spec1TraceSet
: Valeur par défautReflection
(Section Iris Spec).Spec2TraceSet
: Valeur par défautReflection
(Section Cornea Spec).
Configurer ACES dans Guerilla 2.3
Faire pointer la variable d’environnement OCIO
sur le fichier config.ocio
voulu.
Désactiver Use Project Gamma dans Preferences/Project Settings/OCIO :
Passer sa Render View à ACES Views/sRGB :
Passez l’attribut Gamma de vos textures à Input - Generic - sRGB - Texture
:
La logique de déduction du rôle utilisé quand l’attribut Gamma laissé vide est disponible dans la section Textures de la documentation officielle.
Passer le Gamma de vos HDR d’Environment Map sur Utility - Linear - sRGB
(Guerilla semble utiliser le role texture_paint
par défaut) :
Optionnel : Dans ses RenderPass, passer la valeur de Clamp Pre Filter de 10
à 16
pour profiter de la plage dynamique de la LUT d’affichage de ACES :
Grossièrement, cela donne :
Color picker
Dans OpenColorIO, le role color_picking
sert à définir l’espace dans lequel sont affiché les couleurs (voir documentation). Il ne sert donc qu’à l’affichage. La documentation de Guerilla semble aller dans ce sens.
La question de l’interprétation des valeurs pické semble être une zone grise de OpenColorIO, chaque logiciel semblant interpréter les valeurs différemment.
Correspondances couleurs sRGB/Rec.709 vers ACEScg
Voici un tableau de correspondances de quelques couleurs sRGB/Rec.709 vers ACEScg :
Couleur | sRGB/Rec.709 | ACEScg | ||||
---|---|---|---|---|---|---|
Rouge | Vert | Bleu | Rouge | Vert | Bleu | |
Rouge pur | 1.0 | 0.0 | 0.0 | 0.613116 | 0.070197 | 0.020619 |
Vert pur | 0.0 | 1.0 | 0.0 | 0.33951 | 0.916355 | 0.10958 |
Bleu pur | 0.0 | 0.0 | 1.0 | 0.047375 | 0.013449 | 0.869801 |
Jaune | 1.0 | 1.0 | 0.0 | 0.952626 | 0.986552 | 0.130199 |
Cyan | 0.0 | 1.0 | 1.0 | 0.386885 | 0.929804 | 0.979381 |
Gris neutre | 0.5 | 0.5 | 0.5 | 0.184475 | 0.184475 | 0.184475 |
Isoler l’éclairage d’une volumétrique
Un défi qui revient souvent en rendu est de calculer une volumétrique. Si la volumétrique est mise tel-quel dans la scène. Elle récupère les trace sets par défaut. Elle se retrouve donc dans l’illumination indirecte de la scène, mais utilise également toutes les lights de la scène pour se calculer.
L’objectif est donc double :
- Sortir la volumétrique des trace sets par défaut de sorte qu’elle ne reçoive ni ne participe à aucune contribution extérieure.
- Mettre la volumétrique dans son propre trace set pour pouvoir contrôler son lighting.
Du fait de cette isolation, la volumétrique sera forcément moins intégrée à l’éclairage courant, mais son coût de rendu sera grandement diminué. À vous de voir.
Supprimer la volumétrique des trace sets par défaut
Par défaut, Guerilla assigne certains trace sets à tous les objets de la scène :
Passer la volumétrique dans ces trace set, pour les en supprimer :
-All,-Diffuse,-Reflection,-Refraction,-Lights,-Shadows
Si vous sélectionnez votre volumétrique et que vous allez dans Lighting/Set, le champ doit être vide :
Si vous rendez, la volumétrique ne doit ni s’afficher, ni apparaître dans une contribution indirecte.
À ce stade, la volumétrique est isolée : Elle ne reçoit ni ne participe à aucune contribution.
Assigner les volumétriques dans leur propre trace sets
Nous allons maintenant mettre les volumétriques dans des trace set dédiées. Dans notre exemple, nous allons simplement reprendre les noms des trace sets par défaut, en les suffixant par _VOL
:
All_VOL,Diffuse_VOL,Reflection_VOL,Refraction_VOL,Lights_VOL,Shadows_VOL
Cela ne doit pas impacter le rendu. Si c’est le cas, ça veut dire que ces trace sets existaient déjà avant. Une fois encore, vérifiez en sélectionnant la volumétrique et allez dans Lighting/Set, le champ doit être vide :
Éclairer les trace sets de la volumétrique
Maintenant, créez une light (une env light avec une HDR, par exemple).
Modifier les trace sets utilisés pour l’éclairage et les ombres de cette light :
- Assignez
Lights_VOL
à Light Set. - Assignez
Shadows_VOL
à Shadow Set.
En faisant ça, votre light n’éclairera que ce qu’il y a dans le trace set Lights_VOL
(en l’occurrence, votre volumétrique), et n’utilisera que ce qu’il y a dans le trace set Shadows_VOL
pour tracer les ombres (une fois encore, uniquement votre volumétrique).
Définir le trace set à utiliser pour la diffuse de la volumétrique
La dernière étape consiste à définir, dans le shader de volumétrique, le nom du trace set utilisé pour tracer les rayons indirects de votre volumétrique. Pour cela, dans votre shader de volumétrique, allez dans Advanced et modifiez Trace Set pour lui donner la valeur Diffuse_VOL
:
À titre d’information, la définition de la documentation pour ce paramètre :
Trace Set est le trace set d’objets utilisé pour tracer les rayons d’indirects du volume. Il n’a pas d’effet si Ray Depth est nulle.
Notez que par défaut, les volumes ne créent pas d’indirect, car l’attribut Volume Bounces d’une RenderPass est 0
.
À cette étape, nous avons isolé la volumétrique dans un lighting dédié.
Description des effets
Quand un rayon part de la caméra, il pénètre dans la volumétrique.
Au bout d’une certaine distance (dépendante de Step Size), il fait un sampling du shader de volumétrique à cette position :
- Pour l’illumination, il prend les lights présentes dans le trace set dans lequel il se trouve ; ici,
Lights_VOL
, qui ne contient qu’une seule light, l’env light. - Pour déterminer si (et surtout, de combien) l’env light est coupée (occludée), elle trace
Shadows_VOL
qui ne contient qu’un seul objet, notre volumétrique. En l’état, notre volumétrique n’a donc d’ombre que d’elle-même. - Optionnellement, pour l’illumination indirecte, il utilise le trace set mis dans l’attribut Trace Set du shader (
Volume
), à savoir,Diffuse_VOL
, qui ne contient qu’un objet, notre volumétrique.
Avec ça, on a notre sample de volumétrique. On continue d’avancer le rayon suivant Step Size et on recommence.
Autre cas : Le rayon tombe sur une surface non volumétrique de la scène ; le sol, le mur, etc. Il calcule l’illumination comme n’importe quelle surface puis va envoyer des rayons aux alentours pour tracer les contributions secondaires ; lights, shadows, diffuse, reflection, etc.
Avec notre mise en place, il ne tracera jamais la volumétrique, car ce dernier a été retiré de tous les trace sets par défaut ; Diffuse, Shadows, etc.
Limitations
Si cela permet de diminuer les temps de rendu, isoler totalement la volumétrique d’un lighting produit rarement un effet convaincant. Il est souvent nécessaire de réintégrer la volumétrique dans l’éclairage de la scène. Chaque modification augmentera le temps de rendu.
Par exemple, avec notre mise en place, la volumétrique ne créera pas d’ombres au sol, sur les murs, etc. Pour cela, il faut la remettre dans le trace set Shadows
.
Inversement, les objets ne créeront pas d’ombres portées sur dans la volumétrique, ce qui est parfois l’effet voulu.
On peut ajouter le trace set par défaut Shadows
au Shadow Set de l’env light, de sorte que tous les objets soient aussi pris en compte dans les ombres, mais ça n’est pas parfait.
Dans le cas des personnages et de certains props (panneaux), c’est ce qu’on veut, mais dans d’autres cela contrecarre l’éclairage HDR lui-même ; si on ajoute les sols où les murs, par exemple, l’HDR ne sera jamais atteinte. Et votre volumétrique sera énormément occludée.
Le but d’une HDR est de simuler l’éclairage global. Si votre volumétrique est éclairée de la sorte, et que vous remettez tous les objets dans les ombres, vous couperez une partie de l’éclairage de l’HDR.
Vous pouvez donc, à l’inverse, ajouter quelques objets et les personnages principaux (sans leurs poils, par exemple) à Shadows_VOL
, de sorte qu’ils fassent partie des ombres de la volumétrique.
En faisant cela, vous réintégrerez les ombres des objets et personnages aux alentours à la volumétrique. L’avantage est que le temps de tracing d’une occlusion est relativement rapide et l’apport visuel est important, car il « ancre » les ombres des objets dans la volumétrique, un peu comme une simple ombre portée « ancre » un objet dans une scène.
Bake2D
Extension des bords (edge extend)
Guerilla ne permet pas de faire du edge extend. Vous devez passer par un autre logiciel pour le faire (par exemple, Nuke via le nœud EdgeExtend).
Privilégiez des valeurs de Samples et Adaptive Min Samples élevées pour éviter les problèmes d’aliasing lors de l’unpremult de vos textures bakées. Par exemple, dans le cas d’un bake d’occlusion, il vaut mieux avoir un Samples/Adaptive Min Samples à 256
et une valeur de Samples dans l’occlusion de 4
à 16
plutôt que l’inverse.
Influence du Pixel Filter
Par défaut, la render pass Bake2D
arrive avec un Pixel Filter en Mitchel
, ce qui ne donne pas de bons résultats dans les bakes d’occlusion. Passez le Pixel Filter en Box
avec un X Width et un Y Width à 1
. Ceci permet d’éviter des problèmes de filtrage, en particulier sur les ambiante occlusions :
À gauche le Pixel Filter à Mitchel
(par défaut), à droite en Box
.
L’occlusion et la position de la caméra
Quand on fait un bake d’une ambiante occlusion dans Guerilla, il peut apparaître des artefacts, comme si le cone angle était à une valeur faible. Ce problème semble augmenter avec le nombre de samples d’occlusion :
Il est fort probable que votre caméra soit trop loin du modèle que vous souhaitez baker. Si vous rapprochez votre caméra (ou que vous la placez carrément au centre du monde), le problème disparaît :
Motion Blur
Le Motion Blur Density à Parametric à 0.5
, 0.5
, 0.0
est identique à Trapeze 0.5
.
Dernière mise à jour : sam. 16 avril 2022