# How to Implement Motion Blur and Temporal Effects in LuisaRender

> Learn to implement motion blur and temporal effects in LuisaRender. Discover how sampling the shutter interval achieves realistic motion blur for your renders.

- Repository: [LuisaGroup/luisarender](https://github.com/luisagroup/luisarender)
- Tags: how-to-guide
- Published: 2026-03-06

---

**LuisaRender implements motion blur by sampling the camera's shutter interval over multiple time steps, updating the scene pipeline at each sample, and accumulating weighted radiance contributions.**

Motion blur and other temporal effects in LuisaRender rely on a time-sampling architecture that evaluates animated scene elements at specific shutter instances. The renderer supports keyframed transforms, animated lights, and deformable geometry by querying the scene state at arbitrary times within the camera's exposure window. This article explains the camera shutter configuration, the pipeline update mechanism, and the integrator loop patterns required to render motion blur, referencing the actual implementation in the `luisagroup/luisarender` repository.

## Camera Shutter Configuration

The camera node defines the temporal boundaries of the exposure via the `shutter_span` parameter and controls temporal sampling density through `shutter_samples`. These map directly to the `Camera::_shutter_span` member and the `shutter_samples()` method in [`src/base/camera.h`](https://github.com/luisagroup/luisarender/blob/main/src/base/camera.h) (lines 95–106).

To enable motion blur, configure your camera JSON as follows:

```json
{
  "type": "Camera",
  "name": "MotionBlurCam",
  "film": { "width": 1920, "height": 1080 },
  "shutter_span": [0.0, 0.033],
  "shutter_samples": 8,
  "exposure": 1.0
}

```

- `shutter_span`: A two-element array `[start_time, end_time]` in seconds defining the exposure interval.
- `shutter_samples`: The number of discrete time steps sampled within the interval. Higher values reduce noise but increase render time.

## Animating Scene Elements

LuisaRender evaluates time-varying transforms and light parameters through the `AnimatedTransform` and animated value nodes. These are resolved when `pipeline().update(command_buffer, time)` is invoked, triggering evaluation logic in [`src/util/transform.h`](https://github.com/luisagroup/luisarender/blob/main/src/util/transform.h) and the scene graph.

### Animated Transforms

Attach an `AnimatedTransform` to any mesh or instance to enable motion blur for geometry:

```json
{
  "type": "Mesh",
  "name": "FlyingSaucer",
  "file": "saucer.obj",
  "transform": {
    "type": "AnimatedTransform",
    "keyframes": [
      { "time": 0.0,   "translation": [-5, 0, 0] },
      { "time": 0.033, "translation": [5, 0, 0] }
    ]
  }
}

```

The renderer interpolates between keyframes based on the current shutter sample time.

### Animated Lights

Temporal effects also apply to light intensity and color:

```json
{
  "type": "PointLight",
  "name": "Strobe",
  "intensity": {
    "type": "AnimatedFloat",
    "keyframes": [
      { "time": 0.0,   "value": 0.0 },
      { "time": 0.008, "value": 100.0 },
      { "time": 0.016, "value": 0.0 },
      { "time": 0.024, "value": 100.0 },
      { "time": 0.033, "value": 0.0 }
    ]
  }
}

```

During each shutter sample, the pipeline evaluates the light's intensity at the specific time, creating stroboscopic or fading effects automatically.

## Implementing the Temporal Render Loop

Integrators implement motion blur by iterating over `ShutterSample` structures provided by the camera. Each sample contains a `time` and a `weight` (shutter weight). The integrator must call `pipeline().update(command_buffer, time)` to synchronize the scene state before rendering that sample's contribution.

The canonical pattern appears in [`src/integrators/aov.cpp`](https://github.com/luisagroup/luisarender/blob/main/src/integrators/aov.cpp) (lines 60–62 and 71–73):

```cpp
// Retrieve shutter samples from camera configuration
auto shutter_samples = camera->node()->shutter_samples();

for (auto s : shutter_samples) {
    // 1. Update animated scene elements to the current sample time
    pipeline().update(command_buffer, s.point.time);
    
    // 2. Dispatch the rendering kernel for this temporal slice
    command_buffer << render_kernel(sample_id++, s.point.time, s.point.weight)
                   .dispatch(resolution);
}

```

The `s.point.weight` factor represents the **shutter weight** (line 12–15 in the same file). The integrator multiplies the radiance by this weight before accumulation, ensuring correct exposure across the shutter interval.

Path-tracing integrators such as `PathTracing` follow this same loop structure but distribute path samples across both space and time, allowing motion blur without the sample-per-pixel restrictions found in AOV integrators.

## Motion Blur Compatibility and Limitations

Not all integrators support motion blur equally. The `AOVIntegrator` enforces a strict compatibility check: it requires that the auxiliary buffer's samples-per-pixel (spp) match the camera's shutter samples exactly. If they differ, the integrator aborts with the error:

```

"AOVIntegrator is not compatible with motion blur if rendered with different spp from the camera."

```

This restriction exists because AOVs accumulate auxiliary data (depth, normals, etc.) that must correlate temporally with the color buffer. Path-tracing integrators do not impose this limitation because they treat time as an additional dimension sampled stochastically alongside lens and light samples.

To verify compatibility, check your integrator's source file (e.g., [`src/integrators/aov.cpp`](https://github.com/luisagroup/luisarender/blob/main/src/integrators/aov.cpp) lines 60–62) for assertions regarding `shutter_samples` and `spp`.

## Summary

- **Camera Configuration**: Define `shutter_span` and `shutter_samples` in the camera node to control exposure duration and temporal sampling density.
- **Scene Animation**: Use `AnimatedTransform` for geometry motion and animated value nodes for light parameters; the pipeline evaluates these at sample times.
- **Pipeline Update**: Call `pipeline().update(command_buffer, time)` inside the integrator's shutter sample loop to synchronize animated elements.
- **Shutter Weighting**: Multiply radiance by `shutter_weight` (provided in `ShutterSample`) to achieve correct exposure across the interval.
- **Integrator Selection**: Use path-tracing integrators for full motion blur flexibility; avoid `AOVIntegrator` unless sample counts match exactly.

## Frequently Asked Questions

### How does LuisaRender handle sub-frame motion for fast-moving objects?

LuisaRender handles sub-frame motion by sampling multiple time points within the camera's `shutter_span` interval. For each sample, the `pipeline().update(command_buffer, time)` call evaluates animated transforms at that specific timestamp, interpolating between keyframes defined in `AnimatedTransform` nodes. This allows the renderer to capture motion blur even for objects moving rapidly between frames.

### Can I use motion blur with the AOV integrator for rendering depth or normal passes?

The `AOVIntegrator` supports motion blur only when the auxiliary buffer's samples-per-pixel exactly matches the camera's `shutter_samples` count. If the counts differ, the integrator aborts with a compatibility error. For flexible motion blur with arbitrary sample counts, use the `PathTracing` integrator or other path-based integrators that do not enforce this restriction.

### What is the performance cost of increasing shutter samples?

Each additional shutter sample requires a full pipeline update (`pipeline().update`) and a separate render kernel dispatch. Therefore, render time scales linearly with `shutter_samples`. For production scenes, balance quality against performance by using 4–8 samples for moderate motion blur, or 16+ samples for high-velocity objects or long exposures.

### How do I animate light intensity for effects like flickering or strobes?

Animate light intensity using the `AnimatedFloat` type in the light's JSON definition, specifying keyframes with time and value pairs. During rendering, the pipeline automatically evaluates the intensity at each shutter sample time when `pipeline().update` is called. This creates temporal lighting effects such as strobes or fading without requiring manual frame-by-frame rendering.