Read a Previous Frame#
Goal. Read an AOV as it was N frames ago.
Before you start. Publish, Overwrite and Read Back AOVs.
The Shape#
Suffix the RenderVar’s sourceName with :-N. Everything else is an ordinary RenderVar.
Highlighted: the same AOV named twice, once live and once a frame back.
motion_scene.usda, from the runnable previous-frame example#def Scope "Render"
{
def RenderProduct "MotionDemo"
{
uniform int2 resolution = (960, 540)
rel camera = </World/Camera>
rel orderedVars = [ <LdrColor>, <PreviousColor>, <Motion> ]
def RenderVar "LdrColor"
{
uniform string sourceName = "LdrColor"
opaque omni:rtx:aov
}
# The same AOV, one frame back. The ":-1" suffix is the whole
# difference; everything else is an ordinary input RenderVar.
def RenderVar "PreviousColor"
{
uniform string sourceName = "LdrColor:-1"
opaque omni:rtx:aov
}
def RenderVar "Motion"
{
uniform string sourceName = "Motion"
opaque omni:rtx:aov.connect = <../MotionShader.outputs:Difference>
}
def Shader "MotionShader" (
references = @MotionKernel.usda@
)
{
opaque inputs:Current.connect = <../LdrColor.omni:rtx:aov>
opaque inputs:Previous.connect = <../PreviousColor.omni:rtx:aov>
}
}
}
How It Works#
N counts frames back, so :-1 is last frame. It runs from 1 to 8. Each retained frame is a
separately allocated, full-size GPU resource, which is what bounds the depth.
The source may come from the renderer or from a node. When it is a node’s output, SPG orders the producer ahead of the consumer.
Nothing on the node says anything about time. It declares ordinary opaque ports, and which
of them is a past frame follows from the RenderVar the scene connects it to. The same node runs
unchanged on two live AOVs.
An unrecognised suffix is not an error. :-0, :- on its own, a non-numeric suffix, and
any N above 8 all leave the name unchanged, so the RenderVar reads the live AOV under that literal
name.
The last separator is the one that counts, so a:-b:-2 reads the source named a:-b, two
frames back.
This is not the same as a stateful output. A stateful output is a node’s own resource, handed
back to it next frame. :-N reads an AOV, through a RenderVar connected like any other. Refer to Keep State Across Frames.
Verify It Worked#
Compare a moving scene against a still one, with the same graph. A node that subtracts the previous frame from the current one lights up while something moves and goes dark when nothing does; a node whose suffix was ignored is reading the live AOV twice and stays dark in both cases. A still scene on its own cannot tell the two apart.
The previous-frame example runs exactly that pair, and the moving count comes out more than an order of magnitude above the still one.
Do not expect the still count to be zero, or either count to repeat between runs. A path-traced render keeps refining, so consecutive frames of a static scene differ a little and the exact numbers move. The ratio is what carries the result.
When It Goes Wrong#
The result matches the current frame: nothing in the scene changed between the two frames, or the suffix was not recognised and the RenderVar is reading the live AOV.
Nothing appears at all: check the base name, which is everything before the last
:-.