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.
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 |
|---|---|
|
Selects the scan direction. |
|
Time from the start of one scan line to the next, expressed as rational seconds in |
|
Exposure duration for each scan line, expressed as rational seconds in |
scanOrder accepts these values:
Token |
Behavior |
|---|---|
|
Disables rolling shutter. |
|
Scans image rows from top to bottom. |
|
Scans image rows from bottom to top. |
|
Scans image columns from left to right. |
|
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 |
|---|---|
|
Return the last completed display image. The final portion returns the newly completed image. |
|
Recomputed for every step from the accumulated scan lines. Intermediate values describe the partially accumulated exposure, not a completed frame. |
|
Recomputed for every step. Per-pixel IDs follow the accumulated scan lines. |
|
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.
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.