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

#usda 1.0
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"
}
from pxr import Gf, UsdGeom

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().

import ovrtx

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, ovrtx processes the exposure in portions across those steps. isFullFrame becomes true only for the final portion.

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

RenderVars

Intermediate-output behavior

LdrColor, HdrColor

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

NormalSD, DepthSD, DistanceToCameraSD, DistanceToImagePlaneSD, DiffuseAlbedoSD, Camera3dPositionSD

Recomputed for every step from the accumulated scan lines. Intermediate values describe the partially accumulated exposure, not a completed frame.

SemanticSegmentation

Recomputed for every step. Per-pixel IDs follow the accumulated scan lines.

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 portion completes. For setting this policy on cameras through ovstage, see Sensor Scheduling.

For a camera with an authored sensor frame rate, 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 scheduled exposures are retained; a third exceeds the supported overlap limit and can produce unfinished scan lines.#

Note

lineExposure shifts the sample time to the end of each line’s interval. It does not 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 overlapping exposures, 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.

Limits#

A rolling shutter samples each line at the end of its exposure interval; motion within that interval is not integrated into motion blur. At most two exposures are in flight per camera. For an authored frame rate, keep (lineCount - 1) * lineOffset + lineExposure at or below two frame intervals, where lineCount is the output resolution along the scan direction.