Re-rendering with updated cameras in a sequence with Python

This tutorial shows how to use Python to update only the camera in a static scene and re-render without re-cooking geometry.

In some scenes, there are large geometry datasets or procedurals that take significant time to load but that do not change from frame to frame. In these cases, it is nice to take advantage of this and not be forced to reload or rebuild the geometry. Arnold allows us to update or add nodes, and the raytracing acceleration data structures are not view-dependent, which is why IPR re-renders are so fast. We can take advantage of this.

If you have a static scene with an animating camera, you will need to update two nodes every frame: the camera and the output driver. Obviously, if you want to render from a new location, you need to have the new camera. The output driver's filename needs to be updated because, if you used the same exact driver, you would overwrite the frame you had previously rendered.

In the script below, the Python function AiASSLoad(assfile, AI_NODE_ALL) reads in everything from an ass file. We call this for the first frame we render, but we don't want subsequent frames to re-read in our static geometry. We do this by reading in only the camera, driver, filter, and option nodes using AiASSLoad(assdir+ass, AI_NODE_CAMERA | AI_NODE_DRIVER | AI_NODE_FILTER | AI_NODE_OPTIONS).

Arnold is happy to have as many cameras drivers and filters as a user chooses to put into a scene, but we want to replace these nodes with the new ones. To do this, we just loop through and delete all cameras, drivers, and filters. That leaves our scene ready for the new ones we will read into the scene. Then, after we read them in, we need to make a list of them, so we can easily delete them later for the next frame.

Finally, we simply render the frame and loop back. This method was used to render the Mandelbulb sequence for the Managing RAM and threading in procedurals tutorial. A high-resolution Mandelbulb takes about 45 minutes to compute, but only 4 minutes to render. This was a perfect test for this Python script, and there is a ten frame sequence of .ass files attached for you to test this script with.

camera_only.py

#!/usr/bin/env python

from arnold import *
import os
import sys

if len(sys.argv)!=2:
    print "\nusage: camera_only.py path/to/ass/files/directory/"
    print "the directory should contain only the sequence of assfiles you want rendered....\n"
    sys.exit()
# this should be a directory full of .ass files
assdir = sys.argv[1]
# begin, load first frame, and render it.
AiBegin()
# use sorted() to get them in sequence
assfiles = sorted(os.listdir(assdir))
# first time, load ALL nodes
AiASSLoad (os.path.join(assdir, assfiles[0]), AI_NODE_ALL)
# and render your first frame.
AiRender()

# list to hold cameras, filters, and drivers. 
deletion_list = []

# this will let you iterate over all camera, driver, and filter nodes in the Arnold universe
nodeIter = AiUniverseGetNodeIterator(AI_NODE_CAMERA | AI_NODE_DRIVER | AI_NODE_FILTER)
while not AiNodeIteratorFinished(nodeIter):
    node = AiNodeIteratorGetNext(nodeIter)
    deletion_list.append(node)
AiNodeIteratorDestroy(nodeIter)
# you have already rendered the first frame
del assfiles[0]
# now cycle through all frames in the given directory
for ass in assfiles:
    # echo out useful information
    print "loading "+ass
    for node in deletion_list:
        AiNodeDestroy(node)
    # this loads only cameras, drivers, filters, and an option node.
    AiASSLoad (os.path.join(assdir, ass), AI_NODE_CAMERA | AI_NODE_DRIVER | AI_NODE_FILTER | AI_NODE_OPTIONS)
    # clear our old list
    deletion_list = []
    #loop through nodes making a fresh list of cameras, filters, and drivers
    nodeIter = AiUniverseGetNodeIterator(AI_NODE_CAMERA | AI_NODE_DRIVER | AI_NODE_FILTER)
    while not AiNodeIteratorFinished(nodeIter):
        node = AiNodeIteratorGetNext(nodeIter)
        deletion_list.append(node)
    AiNodeIteratorDestroy(nodeIter)
    # finally, render this frame before looping again
    AiRender()
# we are done, this is our final call
AiEnd()

 

This type of script was used to render this sequence: Mandelbulb. The quarter billion spheres took significant time to generate, but the re-renders were quite fast.

Re-rendering after updating the vertices of a quad area light

This second example needs the multilight_area.ass file attached. It will first render the scene, then update the vertices of the area light called "light_0003", and then re-render.

from arnold import *

AiBegin()
AiMsgSetConsoleFlags(AI_LOG_ALL);
AiASSLoad("multilight_area.ass")

print("***** 1st pass, render.tif *****")

AiRender()

print("***** 2nd pass, render2.tif *****")

# Change output filename
driver = AiNodeLookUpByName('testrender')
if driver is not None and AiNodeIs(driver, 'driver_tiff') == True:
    AiNodeSetStr(driver, 'filename', 'render2.tif')

# Muck with the blue quad light
quadlight = AiNodeLookUpByName('light_0003')
if quadlight is not None and AiNodeIs(quadlight, 'quad_light') == True:
    verts = AiNodeGetArray(quadlight, 'vertices')
    AiArraySetPnt(verts, 0, AiArrayGetPnt(verts, 0) + AtPoint(0, 0, -2))
    AiArraySetPnt(verts, 1, AiArrayGetPnt(verts, 1) + AtPoint(0, 0, -2))
    AiArraySetPnt(verts, 2, AiArrayGetPnt(verts, 2) + AtPoint(0, 0, -2))
    AiArraySetPnt(verts, 3, AiArrayGetPnt(verts, 3) + AtPoint(0, 0, -2))
    #v0 = AiArrayGetPnt(verts, 0)
    #print("0: %f %f %f" % (v0.x, v0.y, v0.z))
    #v1 = AiArrayGetPnt(verts, 1)
    #print("1: %f %f %f" % (v1.x, v1.y, v1.z))
    #v2 = AiArrayGetPnt(verts, 2)
    #print("2: %f %f %f" % (v2.x, v2.y, v2.z))
    #v3 = AiArrayGetPnt(verts, 3)
    #print("3: %f %f %f" % (v3.x, v3.y, v3.z))

AiRender()

AiEnd()
  • No labels