Avant propos

Avant de commencez sachez que c'est vraiment Vray qui m'a poussé à remettre mon nez dans mental ray, en particulier le merge des maps de Final Gather. Alors que tout est très simple dans Vray, dans mental ray, c'est la douleur (comme d'hab :septic: ).

En gros on va devoir injecter du script dans les Pre render frame MEL et Post render frame MEL pour recréer le comportement de Vray.

Mental ray permet déjà de "merger" des maps de Final Gather, mais l’intégration dans Maya ne permet pas de faire ça de manière temporelle simplement.

La scène

J'ai pris le genre typique de scène qui met à genou le Final Gather (et la plupart des méthodes de chaching de GI de manière générale):

  • Aucune light (Default Light désactivées).
  • Un couloir blanc.
  • Du coté non visible de la caméra: Une plaque avec un material émissive blanc.
  • A l'intersection: Des sphère et des cubes colorés animés.
  • Et de l'autre coté, notre camera.

fg_diminuer_flicking_001.png

fg_diminuer_flicking_003.png

C'est un cas extrême, ne l’oublions pas. :papi:

fg_diminuer_flicking_002.png

Le nombre de rebonds diffus du Final Gather est volontairement élevés pour gonfler les artefacts. Tout est présent pour avoir quelque chose de bien dégueulasse. :aupoil:

Final Gather sans interpolation

Voici un rendu de la scène avec un Final Gather calculé à chaque frame, sans interpolation (Mode Automatic):

fg_anim_001.gif

Comme vous pouvez le voir, ça flick pas mal. :ideenoire:

Comment résoudre ça? fg_copy est ton amis! :dentcasse: (Plus spécifiquement, l'option -f).

Bon, en fait il est déjà possible de donner des maps de Final Gather à Maya pour qu'il les merges avant rendu. C'est d’ailleurs cette approche qui va servir de base à notre "interpolation temporelle".

Interpoler les frames?

En gros, le principe consiste simplement à "mélanger" plusieurs maps de Final Gather entre elles pour atténuer l'effet de flicking.

Pour une frame n, on merge les maps de Final Gather n-2, n-1, n, n+1, et n+2 (deux avants, deux après).

Dans la pratique, mental ray va mélanger les points de Final Gather ayant des normales similaires en utilisant le Min Radius de la scène. Deux points de Final Gather ayant une normale similaire (Je suppose que c'est le paramètre Normal Tolerance de la section Final Gather Quality qui est utilisé) et dont la distance n’excède pas le Min Radius de la scène (qui, si il est à zéro, correspond à 10% du diamètre de la scène) voient leur couleur et leur position mélangées.

Vous pouvez voir la taille de la scène que mental ray utilise dans le log:

RC   0.2     41 MB info : scene extent: (-12.34,-0.45,-17.07) : (15.83,10.96,12.07)

Cette approche est loin d'être parfaite et vous verrez qu'elle ne résout pas tous les problèmes. En effet, l'effet de ghosting qu'elle génère, bien que plus acceptable qu'un flicking par frame, reste particulièrement disgracieux, en particulier sur les rendus avec peu de textures/variations de couleurs (surfaces "douces"). Cela dit, cet effet peut passer totalement inaperçu sur certaines séquences (notamment les mouvements de caméra), c'est donc au cas par cas.

Baker le Final Gather

Pour pouvoir merger les maps de Final Gather avant rendu (5 maps dans notre cas) il faut, dans un premier temps, toutes les générer. Et c'est ce qu'on va faire. :enerve:

Comme il va s'agir de deux fg maps avant et deux fg maps après, pour un frame range de 101 à 110, on a donc les maps de Final Gather de l'image 99 à l'image 112.

Personnellement, je procède par RenderLayers en overridant les scripts Pre render frame MEL et Post render frame MEL pour entrer le nom de la fg maps à la valeur de la frame courante (fgmap.0012.fgmap par exemple) juste avant que le rendu ne se lance.

fg_diminuer_flicking_004.png

Par exemple, pour le RenderLayer qui va générer les fg maps, j'override avec ça:

python("import preframe\nreload(preframe)\npreframe.updateFgFiles()")
python("import preframe\nreload(preframe)\npreframe.cleanFgParams()")

Bon, c'est pas super sexy mais comme vous le savez, les Pre render frame MEL et Post render frame MEL ne supporte que... Le MEL... On demande donc à du MEL d’exécuter du Python! :baffed:

En mode "lisible", les deux appels sont les suivants:

import preframe
reload(preframe)
preframe.updateFgFiles()

Et:

import preframe
reload(preframe)
preframe.cleanFgParams()

Et bien entendu, il y a un petit fichier preframe.py à placer dans votre: maya\scripts.

Le script

Voici le contenu du preframe.py, contenu que je vais détailler, fonction après fonction.

import maya.cmds as cmds
import os
 
def cleanFgParams():
 
	cmds.setAttr("miDefaultOptions.finalGatherFilename", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[0]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[1]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[2]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[3]", "", type="string")
 
def updateFgFiles():
 
	cleanFgParams()
 
	frame = int(cmds.currentTime( query=True ))
	fgmapFile = "fgmap.%s.fgmap" % str(frame).zfill(4)	# "fgmap.0012.fgmap"
 
	print "Set fgmap file for frame %s -> %s" % (frame, fgmapFile)
	cmds.setAttr("miDefaultOptions.finalGatherRebuild", 1)	# Rebuild On
	cmds.setAttr("miDefaultOptions.finalGatherFilename", fgmapFile, type="string")
 
 
def prepareFgFiles():
 
	cleanFgParams()
 
	frame = int(cmds.currentTime( query=True ))
	cmds.setAttr("miDefaultOptions.finalGatherRebuild", 2)	# Freeze
	cmds.setAttr("miDefaultOptions.finalGatherFilename", "fgmap.%s.fgmap" % str(frame-2).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[0]", "fgmap.%s.fgmap" % str(frame-1).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[1]", "fgmap.%s.fgmap" % str(frame).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[2]", "fgmap.%s.fgmap" % str(frame+1).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[3]", "fgmap.%s.fgmap" % str(frame+2).zfill(4), type="string")

Vous pouvez vous copier ça dans un maya/scripts/preframe.py.

cleanFgParams()
def cleanFgParams():
 
	cmds.setAttr("miDefaultOptions.finalGatherFilename", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[0]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[1]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[2]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[3]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[4]", "", type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[5]", "", type="string")

Cette fonction vide les entrées des attributs de file du Final Gather (met une chaine de caractère vide). Elle est utilisée juste avant de setter les noms des maps de Final Gather à sauver, et juste après (en Post render frame), pour être sur de vider les champs de texte.

updateFgFiles()
def updateFgFiles():
 
	cleanFgParams()
 
	frame = int(cmds.currentTime( query=True ))
	fgmapFile = "fgmap.%s.fgmap" % str(frame).zfill(4)	# "fgmap.0012.fgmap"
 
	print "Set fgmap file for frame %s -> %s" % (frame, fgmapFile)
	cmds.setAttr("miDefaultOptions.finalGatherRebuild", 1)	# Rebuild On
	cmds.setAttr("miDefaultOptions.finalGatherFilename", fgmapFile, type="string")

Cette fonction est utilisé en Pre render frame du RenderLayer et sert à donner un nom correct de map de Final Gather à sauver. Elle récupère le numéro de la frame courante et génère un nom de fg map à la frame.

Le petit "bricolage":

str(frame).zfill(4)

Sert à transformer le numéro de la frame en chaine de caractère (string) puis de lui appliquer un zero fill de 4, pour transformer "10" en "0010" par exemple.

prepareFgFiles()
def prepareFgFiles():
 
	cleanFgParams()
 
	frame = int(cmds.currentTime( query=True ))
	cmds.setAttr("miDefaultOptions.finalGatherRebuild", 2)	# Freeze
	cmds.setAttr("miDefaultOptions.finalGatherFilename", "fgmap.%s.fgmap" % str(frame-2).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[0]", "fgmap.%s.fgmap" % str(frame-1).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[1]", "fgmap.%s.fgmap" % str(frame).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[2]", "fgmap.%s.fgmap" % str(frame+1).zfill(4), type="string")
	cmds.setAttr("miDefaultOptions.finalGatherMergeFiles[3]", "fgmap.%s.fgmap" % str(frame+2).zfill(4), type="string")

Et dernière fonction, en Pre render frame du RenderLayer, qui sert à remplir tous les champs de maps à merger lors du rendu finale. C'est le même mécanisme de génération des numéros de frame mais on en génère deux avant et deux après.

A ce moment là, vous avez un Final Gather en mode Freeze, qui va merger vos fichiers avant de les utiliser tel quel.

Les render layers

Voici une petite description de mes RenderLayers. :)

fg_diminuer_flicking_005.png

Oubliez FG_RAW qui ne servait qu'à rendre en mode Automatic.

masterLayer

Le RenderLayer de base est préparé de manière à recevoir les Secondary FinalGather Maps.

fg_diminuer_flicking_009.png

Notez que, comme tous les multiattributs de Maya, si ces valeurs sont vides, les blocs sont supprimés à l'ouverture de la scène. C'est la raison pour laquelle je met le mot temp dedans. Dans tous les cas, je supprime ces valeurs juste avant le rendu de la frame (via la fonction cleanFgParams() si vous avez suivi). C'est uniquement pour empêcher Maya de supprimer les entrées (Workaroundman! :nousfesonslemal: ).

Note: Chaque entrée à un temp avec un numéro différent juste pour que je puisse rendre le RenderLayer FG_RAW sans que mental ray tente de merger une fg map qu'il est en train de générer.

Mais en principe, jamais une map de Final Gather nommé "temp" ne devrait être créé durant notre process. Si c'est le cas c'est que quelque chose ne marche pas. :zinzin:

FGMAPONLY

Ce RenderLayer sera le premier lancé. C'est celui qui générera les maps de Final Gather (avec deux frames avant et deux frames après).

Les Pre render frame MEL et Post render frame MEL à overrider (via Create Layer Override) sont les suivants:

python("import preframe\nreload(preframe)\npreframe.updateFgFiles()")
python("import preframe\nreload(preframe)\npreframe.cleanFgParams()")

Il ne faut pas oublier de passer ce RenderLayer en Multiframe (Optimize for Animation dans Maya). Mais l'attribut n'est pas overrideable depuis l'interface. :trollface:

fg_diminuer_flicking_006.png

Il faut passer par le node miDefaultOptions que vous pouvez sélectionnez en tapant la commande MEL suivante:

select miDefaultOptions

Et en allant chercher le Final Gather Mode dans les Extra Attributes:

fg_diminuer_flicking_007.png

Simple comme bonjours... :mayaProf:

Comme l'image rendue à ce moment là ne nous sert pas, vous pouvez overrider le Render Mode pour ne rendre que l'étape de Final Gather:

fg_diminuer_flicking_005a.png

RENDERONLY

C'est dans ce RenderLayer que sera fait le "vrai" rendu. L'idée étant de setter les différentes fg maps (n-2, n-1, n, n+1 et n+2) pour que mental ray les merges en Freeze (pas de rebuild), et nous calcule le résultat final.

python("import preframe\nreload(preframe)\npreframe.prepareFgFiles()")
python("import preframe\nreload(preframe)\npreframe.cleanFgParams()")

Je vous invite à relire la fonction prepareFgFiles() pour bien comprendre ce qu'on fait.

La ligne de commande

Et vient le moment de lancer le rendu! :popcorn:

Je vous donne les deux lignes de batch pour que vous puissiez vous en inspirer:

"C:\Program Files\Autodesk\Maya2013\bin\render.exe" -mr:v 4 -mr:rt 4 -cam camera1 -rl FGMAPONLY -s 99 -e 126 -preFrame "python(\"import preframe\nreload(preframe)\npreframe.updateFgFiles()\")" -postFrame "python(\"import preframe\nreload(preframe)\npreframe.cleanFgParams()\")" -proj "D:\3D\VracProject" "D:\3D\VracProject\scenes\tuto_flick_mr_maponly.ma"
"C:\Program Files\Autodesk\Maya2013\bin\render.exe" -mr:v 4 -mr:rt 4 -cam camera1 -rl RENDERONLY -preFrame "python(\"import preframe\nreload(preframe)\npreframe.prepareFgFilesNoAnim()\")" -postFrame "python(\"import preframe\nreload(preframe)\npreframe.cleanFgParams()\")" -proj "D:\3D\VracProject" "D:\3D\VracProject\scenes\tuto_flick_mr_maponly.ma"

Note: J'ai personnellement rencontré quelques soucis avec les overrides des Pre render frame MEL et Post render frame MEL. C'est la raison pour laquelle je les écris "en dur" dans la ligne de commande.

La génération des maps de Final Gather (première ligne)

A la fin de l’exécution de la première ligne, vous deviez avoir quelque chose dans le dossier renderData/mentalray/finalgMap qui ressemble à ça:

fg_diminuer_flicking_008.png

Seconde ligne, le rendu!

Ne tergiversons pas.

Avant: fg_anim_001.gif

Après: fg_anim_002.gif

FG Merge. Parce que je le vaux bien. :smileFou:

Bon, j’admets que le gif n'est pas ce qu'il y a de mieux pour juger de la qualité d'une image mais vous remarquerez que c'est beaucoup plus stable. Vous remarquerez aussi sur le mur droit, l'effet de ghosting dont je parlais précédemment.

Encore une fois, on est dans un cas extrême:

  • Une surface émissive, aucune source de lumière direct.
  • Un couloir.
  • Des paramètres de rebonds élevés.
  • Des paramètres de qualité de Final Gather bas.

Voici la scène pour que vous puissiez voir un peu les paramètres.

Important: Suivez bien vos logs car (et je ne trouve pas ça super :pasClasse: ) le mode Freeze n'est pas aussi bête qu'il en a l'air (voir doc). Si une map de Final Gather n'existe pas pas, il la créé quand même. Vous aurez donc l'impression qu'à la fin de ces deux lignes tout c'est bien passé (les map de Final Gather sont là ainsi que les images) mais tout flickera...

Quelques infos à vérifier (exemple pour le calcule de la frame 123):

RC   0.2     26 MB info : option:   rebuild         freeze
RC   0.2     26 MB info : option:   files           D:/3D/VracProject/renderData/mentalray/finalgMap/fgmap.0121.fgmap
RC   0.2     26 MB info : option:                   D:/3D/VracProject/renderData/mentalray/finalgMap/fgmap.0122.fgmap
RC   0.2     26 MB info : option:                   D:/3D/VracProject/renderData/mentalray/finalgMap/fgmap.0123.fgmap
RC   0.2     26 MB info : option:                   D:/3D/VracProject/renderData/mentalray/finalgMap/fgmap.0124.fgmap
RC   0.2     26 MB info : option:                   D:/3D/VracProject/renderData/mentalray/finalgMap/fgmap.0125.fgmap
RCFG 0.2     27 MB info : finalgather map is frozen, using loaded from file(s)

Je vous invite donc, dans un premier temps, à lancer ces deux lignes séparéments et à vous assurer du résultat entre chacune.

J'insiste sur le fait que vous n'y arriverez pas forcément du premier coup. Il y a pas mal de choses à faire mine de rien. Donc persévérez et sachez que c'est possible. :hehe:

Variante

Comme vous pouvez le constater, le flicking des objets animés (sphères et cube) ne change pas des masses. On peut donc envisager plusieurs variantes pour résoudre le soucis.

La première approche consiste à générer deux séquence de fg map distinctes:

  • L'une uniquement pour les objets animé (on passe les objets statiques en primary rays off) avec des paramètres de Final Gather plus élevés. Nommé fg_anim.####.fgmap.
  • L'autre uniquement pour les objets statiques (on passe les objets animés en primary rays off de manière à conserver leurs rebonds diffus sur la fg map des objets statiques). Nommé fg_static.####.fgmap.
  • Et on merge l'ensemble après.

On peut aussi aller un cran au dessus dans la complexité (au point ou on en est :pasClasse: ) et utiliser la commande fg_copy pour merger les fg maps d'anim avec un radius plus élevé pour aller chercher les points de Final Gather plus loins dans l'espace. Cela dit, je ne suis pas sur que cette méthode offre de meilleurs résultats mais c'est à tester.

Et enfin, vous pouvez combiner ça avec les importons pour avoir de meilleur points de Final Gather. :)

Conclusion

Je pensais que c'était impossible mais finalement, on peut bien "faker" l'option Interp. samples de Vray dans mental ray. :sourit:

Bien sur, faire ça de cette manière n'est pas forcément super évident, et il est une fois de plus regrettable de devoir se battre à ce point pour profiter d'un feature accessible via un simple slider dans Vray... :redface:

Toutefois, j'espère que ce billet vous aura intéressé et que vous y avez appris des choses. Je pense que si on arrive à mettre ce genre de système en place, on diminue pas mal l'un des principaux soucis du Final Gather (ou techniques similaires) dans les animations.

N'hésitez pas à me faire des retours dans les commentaires si il manque des éléments. :dentcasse:

A bientôt!

Dorian

:marioCours: