Foreword

Before start, know that it's truly Vray which prompted me to put my nose in mental ray, especially Final Gather map merging. While everything is very simple in Vray, in mental ray, it's a pain (as usual :septic: ).

Basically you will have to inject scripts in the Pre render frame MEL and Post render frame MEL ​​to recreate the behavior of Vray.

Mental ray actually provide a way to merge Final Gather maps, but Maya integration doesn't do it temporally (no Final Gather map with frame number name).

The scene

I took the typical kind of scene that brings Final Gather to its knees (and most GI caching methods in general):

  • No lights (Default Light disabled).
  • A white corridor.
  • From the invisible-to-the-camera side: A plane with a white emissive material.
  • At the crossroads: Animated colorful spheres and cubes.
  • And from the other side, our camera.

fg_diminuer_flicking_001.png

fg_diminuer_flicking_003.png

Keep in mind: This is an extreme case. :papi:

fg_diminuer_flicking_002.png

The Final Gather diffuse bounces number is high to increase visual artectacts. Everything is here to have something awful. :aupoil:

Final Gather without interpolation

There is a render of the scene with Final Gather generated for each frame, without interpolation (Mode Automatic):

fg_anim_001.gif

As you can see, there is a lot of flicking. :ideenoire:

How to solve this? fg_copy is your friend! :dentcasse: (Specially the -f flag).

Well, acutally, there is already a way to give Final Gather maps to Maya to make them merged before render. This way will be the foundation to our "temporal interpolation".

Interpolate frames?

Basically, the idea is simply to "mix" several Final Gather maps together to mitigate the effect of flicking.

For a frame n, we merge Final Gather maps n-2, n-1, n, n+1, and n+2 (two before, two after).

In practice, mental ray will merge Final Gather points with similar normal using Min Radius of the scene. Two Final Gather points having a similar normal (I suppose the Normal Tolerance of the Final Gather Quality section is used to control that) and where the distance doesn't execeed the Min Radius of the scene (if set to zero is 10% of the whole scene bouding box) have their color and position merged.

You can see the scene size mental ray is rendering using the log:

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

This approach is far from perfect and you will see it doesn't solve all problems. The ghosting you can have, better than a by frame flicking though, is still unsightly, particularly on renders with few texture/color variations (diffuse surfaces). That said, this effect may go unnoticed on some sequences (especially camera movements), it really depends on what you've got.

Bake Final Gather

To be able to merge Final Gather maps before render (5 maps in our case) you must, as a first step, generate all of them. And this is what we will do. :enerve:

As we have a range of 101-110 and we will generate two frame before and two frame after, the final Final Gather maps sequence will be 99-112.

Personnally, I use RenderLayers overriding Pre render frame MEL and Post render frame MEL scripts to generate fg map names using the current frame (eg: fgmap.0012.fgmap) just before render start.

fg_diminuer_flicking_004.png

For example, I override the RenderLayer that will be used to generate fg maps with this:

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

Ok, that's not really sexy but as you know, Pre render frame MEL and Post render frame MEL only execute... MEL... So we ask MEL to execute Python! :baffed:

In a "readeable" mode, the two calls are followings:

import preframe
reload(preframe)
preframe.updateFgFiles()

And:

import preframe
reload(preframe)
preframe.cleanFgParams()

Of course, there is a little preframe.py in your: maya\scripts.

The script

Here is the contents of preframe.py, contents that I will explain, function by function.

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

You can copy this in a file in 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")

This function clears the Final Gather file attributes (put an empty string). It's called just before setting the name of the Final Gather maps to save, and just after render, in Post render frame, to be sure to clear text fields.

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

This function is set in the Pre render frame attribute of the RenderLayer and is used to give a correct name to the Final Gather map to save. It gets the current frame and generates a fg map name with the frame number.

The little "hack":

str(frame).zfill(4)

Used to generate the frame number with padding. Converting it to string first then apply a zero fill of 4, to make "10" to "0010" for example.

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

And the last function, in Pre render frame of the RenderLayer, used to fill every text fields with Final Gather maps to merge during final render. This is the same method we used to generate Final Gather map name but we generate two before and two after.

This is where you put Final Gather mode to Freeze to ensure you will merge your files before using them as such.

Render layers

There is a little description of my RenderLayers. :)

fg_diminuer_flicking_005.png

Don't worry about FG_RAW, it's only used to render in Automatic mode.

masterLayer

The main RenderLayer is prepared in order to get Secondary FinalGather Maps.

fg_diminuer_flicking_009.png

Note that, as all Maya's multi-attributes, if this values are empty, blocks are removed while reopening the scene. That's why I put the temp words in it. In any case, I remove this values just before frame render start (using the cleanFgParams() function). That's only to prevent Maya to remove entries (Workaround time!).

Important: Each entry has a temp with a different number just so I can render the FG_RAW RenderLayer avoiding mental ray to try to merge a fg map it is actually generating.

But there shouldn't ever be any Final Gather map named "temp" in your fg map folder. If there is, something has gone wrong during our process. :zinzin:

FGMAPONLY

This RenderLayer will be the first started. It's the one that will generate Final Gather maps (with two frames before and two frames after).

Pre render frame MEL and Post render frame MEL to override (using Create Layer Override) are followings:

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

Don't forget to put this RenderLayer in Multiframe (Optimize for Animation in Maya). But the attribute can't be override from the Render Settings. :trollface:

fg_diminuer_flicking_006.png

You must go through the miDefaultOptions node you can select typing this MEL command:

select miDefaultOptions

And looking for Final Gather Mode in Extra Attributes:

fg_diminuer_flicking_007.png

Easy as 1-2-3... :mayaProf:

As there is no interest to keep the image rendered during this process, you can override Render Mode to make mental ray compute only Final Gather:

fg_diminuer_flicking_005a.png

RENDERONLY

It's in this RenderLayer the "real" render will be done. The idea is to set the differents fg maps (n-2, n-1, n, n+1 et n+2) to make mental ray merging them in Freeze (no rebuild), and compute the final render.

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

I encourage you to read the prepareFgFiles() function to fully understand what we do.

Command line

And it's time to start rendering! :popcorn:

I give you my two batch command line to help you:

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

Notice: I've personally encounter few problems with Pre render frame MEL and Post render frame MEL overrides. That's the reason why I "hard" write them in the commande line.

Generating Final Gather maps (first line)

In the end of the first line execution, you should have the folder renderData/mentalray/finalgMap with something like:

fg_diminuer_flicking_008.png

Second line, render!

Aaaaaaaand:

Before: fg_anim_001.gif

After: fg_anim_002.gif

FG Merge. Because I'm worth it. :smileFou:

Ok, I admit gif is not the best thing to assess the image quality but you will notice it's a lot more stable. You will notice too, on the right wall, the ghosting effect I was talking about.

Once again, it's an extreme case:

  • No direct lighting.
  • An emissive surface.
  • A corridor.
  • High number of bouces.
  • Low Final Gather quality parameters.

There is the scene so you can see parameters and try all of this yourself.

Important: Keep an eye on you logs because (and I don't think it's "good" :pasClasse: ) Freeze mode is not as stupid it seems to be (see the doc). If a Final Gather map don't exists, it create it anyway. You will so have the impression, when the two batch lines will ends, that everything passed well (Final Gather maps are there and images so) but everything will flick...

Few informations you should keep an eye on (example for the 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)

I encourage you, as a first step, to start these two lines separately and check everything passed well between them.

I insist on the fact that you will not necessary succeed the first time. There is actually a lot of things to do. So persevere and keep in mind "this is possible". :hehe:

Variant

As you can see, flicking of animated objects (spheres and cube) don't change too much. We can consider many variant to solve this problem.

The first approach is to generate two distinct Final Gather maps sequence:

  • One only with animated objects (putting static objects in primary rays off) with higher Final Gather quality. Named fg_anim.####.fgmap.
  • The other only with static objects (putting animated objects in primary rays off in order to keep their diffuse bounces on the fg map). Named fg_static.####.fgmap.
  • And we merge everything just before render.

We can also go a step up in complexity (while we're there... :pasClasse: ) and use the fg_copy command to merge animated object Final Gather maps with a higher radius to to get farther Final Gather points. That said, I'm not sure this approach produces better results but it has to be tested.

And last but not least, you can combine this with importons to have better Final Gather points. :)

Conclusion

I was thinking it was impossible but we can actually "immitate" the Interp. samples Vray option in mental ray. :sourit:

Of course, doing it this way is not necessarily easy, and it's again unfortunate having to fight to benefit a feature available using a simple slider in Vray... :redface:

However, I hope this post have interest you and that you've learned things. I think if you can put this kind of system in place, you solve the main concerns about Final Gather (or similar techniques) in animations.

Don't hesitate to feedbacks in comments if I missed some points/explaination you would like to have. :dentcasse:

See you guys!

Dorian

:marioCours: