Rolling Shutter#

Overview#

A global shutter samples all photosites at the same time. A rolling shutter offsets sample time across the image, one row or column at a time. Moving straight edges can therefore appear curved or skewed.

A moving cube rendered with global and top-to-bottom rolling shutters

The global-shutter image on the left keeps the moving cube square. The top-to-bottom rolling shutter on the right skews it across the scan.#

Creating A Rolling-Shutter Camera#

Apply OmniRtxRollingShutterAPI to a camera and author its analytic timing attributes. Times are rational seconds expressed as a (numerator, denominator) pair.

USD declaration

Description

token omni:rtx:rollingShutter:scanOrder = "none"

Selects the scan direction. none disables rolling shutter.

double2 omni:rtx:rollingShutter:lineOffset = (0, 1)

Time from the start of one scan line to the next, expressed as rational seconds in (numerator, denominator) form. For example, (1, 1000) represents 1 ms. (0, 1) represents zero seconds.

double2 omni:rtx:rollingShutter:lineExposure = (0, 1)

Exposure duration for each scan line, expressed as rational seconds in (numerator, denominator) form. For example, (1, 1000) represents 1 ms. (0, 1) represents zero seconds.

scanOrder accepts these values:

Token

Behavior

none

Disables rolling shutter.

topToBottom

Scans image rows from top to bottom.

bottomToTop

Scans image rows from bottom to top.

leftToRight

Scans image columns from left to right.

rightToLeft

Scans image columns from right to left.

The following example scans from top to bottom. Each line starts 40 microseconds after the previous line, and each line has a (1, 32) second exposure (31.25 ms).

def Camera "Camera" (
    prepend apiSchemas = ["OmniRtxRollingShutterAPI"]
)
{
    double2 omni:rtx:rollingShutter:lineExposure = (1, 32)
    double2 omni:rtx:rollingShutter:lineOffset = (40, 1000000)
    token omni:rtx:rollingShutter:scanOrder = "topToBottom"
}
prim = UsdGeom.Camera.Define(stage, "/Camera").GetPrim()
prim.ApplyAPI("OmniRtxRollingShutterAPI")
prim.GetAttribute("omni:rtx:rollingShutter:scanOrder").Set("topToBottom")
prim.GetAttribute("omni:rtx:rollingShutter:lineOffset").Set(Gf.Vec2d(40, 1_000_000))
prim.GetAttribute("omni:rtx:rollingShutter:lineExposure").Set(Gf.Vec2d(1, 32))

Runtime Use#

Rolling shutter requires the motion BVH. Create the renderer with ovrtx.RendererConfig.motion_bvh set to ovrtx.MotionBvh.AUTO or ovrtx.MotionBvh.ENABLE. AUTO activates the motion BVH when a rolling-shutter camera is rendered. In C, pass OVRTX_MOTION_BVH_AUTO or OVRTX_MOTION_BVH_ENABLE to ovrtx_config_entry_motion_bvh().

config = ovrtx.RendererConfig(motion_bvh=ovrtx.MotionBvh.AUTO)
renderer = ovrtx.Renderer(config)
ovrtx_config_entry_t entries[] = {
    ovrtx_config_entry_motion_bvh(OVRTX_MOTION_BVH_AUTO),
};
ovrtx_config_t config = {entries, 1};

ovrtx_renderer_t* rolling_shutter_renderer = nullptr;
ovrtx_result_t result = ovrtx_create_renderer(&config, &rolling_shutter_renderer);
ASSERT_API_SUCCESS(result.status);

For line index i, ovrtx samples at the end of this interval:

startTime + i * lineOffset to startTime + i * lineOffset + lineExposure.

The total exposure span is (lineCount - 1) * lineOffset + lineExposure. Top-to-bottom and bottom-to-top scans use the RenderProduct height as lineCount. Horizontal scans use its width.

Exposure Across Steps#

If a step() window contains the entire exposure, ovrtx returns one completed frame. If the exposure crosses step boundaries, the scheduler divides it into ordered splits. isFullFrame becomes true only for the final split.

With omni:sensor:Core:partialOutputs = true (the runtime default), each split produces a FrameOutput. The renderer keeps one internal canvas for the exposure, preserves scan lines written by earlier splits, and adds the newly exposed lines on each later split. The RenderVars in the camera output tables behave as follows:

RenderVars

Intermediate-split behavior

LdrColor, HdrColor

Return the last completed display image. The final split returns the newly completed image.

NormalSD, DepthSD, DistanceToCameraSD, DistanceToImagePlaneSD, DiffuseAlbedoSD, Camera3dPositionSD

Recomputed for every split from the current rolling-shutter GBuffer. Intermediate values describe the partially accumulated exposure, not a completed frame.

SemanticSegmentation

Recomputed for every split. Per-pixel IDs follow the current synthetic-data and GBuffer state.

SemanticIdMap

Metadata rather than a scan-line image. It does not accumulate across scan lines and changes only when the scene’s semantic mapping changes.

With omni:sensor:Core:partialOutputs = false, ovrtx suppresses intermediate frame outputs and returns the frame only after the final split completes. For setting this policy on cameras through ovstage, see Multi-rate Sensor Simulation.

For a ticked camera, two exposure windows can overlap. Both are retained. A single step() call can therefore return multiple FrameOutput entries when it completes the older exposure and also advances the newer one. With partial outputs enabled, this can be a full frame for exposure A followed by an intermediate frame for exposure B whose display-color outputs still display A.

Timelines comparing complete, partial, suppressed, two-overlap, and unsupported three-overlap rolling-shutter output states

A normal exposure may complete in one step or accumulate across several steps. Partial-output mode controls whether intermediate frame records are returned. Two simultaneous ticked exposures are retained; a third exceeds the resource windows and can produce unfinished scan lines.#

Note

lineExposure shifts the sample time to the end of each line’s interval. It does not yet integrate motion blur over that interval.

Warning

This limit is separate from one exposure accumulating normally across several step() calls. For a camera with an authored sensor frame rate, keep the total exposure span at or below two sensor frame intervals. If the span is longer, three or more exposures can overlap. ovrtx retains only two exposure resource windows, so accumulated scan lines from the newer exposure can be discarded and appear black. Reduce the resolution, lineOffset, lineExposure, or sensor frame rate.

See Python: Camera Rolling Shutter for a runnable example.

Further Reading#