, , ,

Back in this post, I mentioned a trick to improve the appearance of Blender renders: plunk the “#frame” value into the Cycles random number seed. This sets the seed to the current frame, either allowing you to combine multiple renders of a still scene to stomp out fireflies quicker, or randomizing the noise in animations to fool the eye and/or video compression. Unfortunately, it carries a number of disadvantages:

  1. You have to enable script auto-running to get it to work.
  2. You can’t use it to change the output path, forcing either manual output changes or the use of the command line.
  3. If you’re improving a single frame, you have to pretend your still is an animation. This sucks if you’re hacking the physics engine to get that perfect splash or explosion.
  4. If you’re improving an animation, this doesn’t get rid of the noise at all, it just makes it look nicer.

A comment by Palaes, though, got me researching how this trick works. In a wonderful case of serendipity, a combination of hitting the right pages and persistence led me to create an improved version of this trick, eliminating the third and fourth disadvantages.

To set the stage, I need to explain how the #frame trick works. It takes advantage of what are known as “drivers,” which allow you to combine variables and scripts to control numeric properties within Blender. Specifying a driver is as simple as pre-pending the “#” character to its name, so “#frame” is actually saying “hey Blender, call the driver named ‘frame’ to get the value for this.” In the past, “frame” was pre-defined to reference the variable “bpy.context.scene.frame_current”, but that doesn’t seem to be the case nowadays.

One thing that’s missing from the Blender manual is a good list of what functions are available to drivers. By fluke, Adhi on StackExchange happened to mention a few of them, and one in particular set off lightbulbs.

Enough background, though. Let’s improve on #frame!

  1. Create your scene. Trust me, you want to save these steps for the very end.
  2. Allow Python scripts to auto-run by default. The manual does a good job of explaining how.
  3. In your scene, right-click on the Cycles random number seed and select “Add Driver”. If you’ve done it correctly, the seed value will turn purple.
    Right-click on Cycle's random seed setting, and click "Add Driver". If done correctly, the seed value will turn purple.
  4. Open up the Graph editor, and switch to “Driver” mode. You should see the “Seed()” driver listed.
    Switch to the Driver mode of the Graph Editor. The "Seed()" driver should be listed.
  5. Open up the Properties pane (“n” on the keyboard or “View -> Properties” via the menu). Select the “Seed()” driver if it isn’t already. Click on the Driver Type, and change it to “Scripted Expression”. In the “Expr:” box, type “int(noise.random() * 2147483647)”. Now look at the random seed for Cycles, which should behave very differently now.
    Switch the driver type to "Scripted Expression", with expression "int(noise.random() * 2147483647)". Voila!

Now if you resize the window, hit a key, or so much as sneeze, the random seed will change! Unfortunately, it’ll also change every time Cycles tries to render a frame, which makes live previews worthless:

Unfortunately, this new trick renders all Cycles previews uselessThus why I suggested leaving this trick until last. But don’t worry, if you do need to tweak something there’s an easy fix: trash the Python code in the driver expression and replace it with an ordinary number. The seed will now remain constant, restoring the live preview. If you’re just doing a quick edit, you can even copy the formula to the clipboard then paste it back in once done.

The live preview thing is a pain, I know, but it proves the seed will change with every render, independent of the frame counter. Now you can do an incremental render of animations, just by looping through your frames multiple times and averaging each frame variant together! For stills, just re-render the current frame to different locations and average; your perfectly-captured physics moment will remain intact.