diff --git a/offline_ray_tracer/source/main.cpp b/offline_ray_tracer/source/main.cpp
index 6769d691a016b9e6c541fb86ea2b4423fa5571c3..752b79e753a88dd576b3e468a15133bedb9e2433 100644
--- a/offline_ray_tracer/source/main.cpp
+++ b/offline_ray_tracer/source/main.cpp
@@ -1,9 +1,10 @@
-#include <algorithm>
 #include <cstdint>
 #include <iostream>
 
 #include <vtkActor.h>
 #include <vtkAVIWriter.h>
+#include <vtkCamera.h>
+#include <vtkCameraInterpolator.h>
 #include <vtkOSPRayPass.h>
 #include <vtkPolyDataMapper.h>
 #include <vtkRenderer.h>
@@ -33,7 +34,21 @@ std::int32_t main(std::int32_t argc, char** argv)
   window  ->SetMultiSamples      (settings.samples);
   renderer->SetPass              (ospray_pass);
 
-  std::cout << "Setting up mappers, actors and volumes.\n";
+  std::cout << "Setting up camera interpolator.\n";
+  auto interpolator = vtkSmartPointer<vtkCameraInterpolator>::New();
+  interpolator->SetInterpolationType (vtkCameraInterpolator::INTERPOLATION_TYPE_SPLINE);
+  for (auto& key_frame : settings.key_frames)
+  {
+    auto camera = renderer->GetActiveCamera();
+    camera      ->SetPosition  (key_frame.position[0] , key_frame.position[1] , key_frame.position[2]);
+    camera      ->SetFocalPoint(key_frame.position[0] + key_frame.forward [0], 
+                                key_frame.position[1] + key_frame.forward [1], 
+                                key_frame.position[2] + key_frame.forward [2]);
+    camera      ->SetViewUp    (key_frame.up      [0] , key_frame.up      [1] , key_frame.up      [2]);
+    interpolator->AddCamera    (key_frame.time, camera);
+  }
+
+  std::cout << "Setting up actors and volumes.\n";
   auto pd_mapper = vtkSmartPointer<vtkPolyDataMapper>                     ::New();
   auto ug_mapper = vtkSmartPointer<vtkUnstructuredGridVolumeRayCastMapper>::New();
   auto pd_actor  = vtkSmartPointer<vtkActor>                              ::New();
@@ -43,14 +58,14 @@ std::int32_t main(std::int32_t argc, char** argv)
   renderer ->AddActor (pd_actor );
   //renderer ->AddVolume(ug_volume);
 
-  std::cout << "Setting up image and video writers.\n";
+  std::cout << "Setting up video writer.\n";
   auto window_to_image = vtkSmartPointer<vtkWindowToImageFilter>::New();
   auto video_writer    = vtkSmartPointer<vtkAVIWriter>          ::New();
   window_to_image->SetInput          (window);
   video_writer   ->SetInputConnection(window_to_image->GetOutputPort());
   video_writer   ->SetFileName       ("video.avi");
   video_writer   ->SetQuality        (2);
-  video_writer   ->SetRate           (settings.update_rate); // 24
+  video_writer   ->SetRate           (1000.0 / settings.update_rate);
   video_writer   ->Start             ();
 
   std::cout << "Starting rendering.\n";
@@ -58,28 +73,22 @@ std::int32_t main(std::int32_t argc, char** argv)
   auto last_slice   = -1;
   while (current_time < settings.key_frames.back().time)
   {
-    std::cout << "Rendering frame " << current_time << ".\n";
-
-    auto  iterator        = std::lower_bound(settings.key_frames.begin(), settings.key_frames.end(), current_time, [ ] (const rt::settings::key_frame& key_frame, const float value)
-    {
-      return key_frame.time < value;
-    });
-    auto& key_frame_1     = *(iterator    );
-    auto& key_frame_2     = *(iterator + 1);
-    auto  parametric_time = (current_time - key_frame_1.time) / (key_frame_2.time - key_frame_1.time);
-    // TODO: Update camera.
-
     std::size_t slice = std::floor(current_time / settings.time_scale);
     slice = settings.loop ? slice % settings.data_filepaths.size() : std::min(slice, settings.data_filepaths.size() - 1);
     if (last_slice != slice)
     {
+      std::cout << "Loading slice " << slice << ".\n";
       last_slice = slice;
       pd_mapper->SetInputData(rt::poly_data_io        ::read(settings.data_filepaths[slice].geometry));
       //ug_mapper->SetInputData(rt::unstructured_grid_io::read(settings.data_filepaths[slice].volume  ));
     }
 
-    window      ->Render();
-    video_writer->Write ();
+    std::cout << "Rendering frame " << current_time << ".\n";
+    interpolator   ->InterpolateCamera       (current_time, renderer->GetActiveCamera());
+    renderer       ->ResetCameraClippingRange();
+    window         ->Render                  ();
+    window_to_image->Modified                ();
+    video_writer   ->Write                   ();
 
     current_time += settings.update_rate;
   }
diff --git a/test_data/example.json b/test_data/example.json
index b98c69754ed96209dd176439587cc4399c0a2f7d..4289a056443b077c550926070fdd243928d49008 100644
--- a/test_data/example.json
+++ b/test_data/example.json
@@ -8,15 +8,15 @@
   "time_scale" : 1000.0,
   "loop"       : true,
 
-  "image_size" : [4096, 3072],
+  "image_size" : [1920, 1080],
   "samples"    : 32,
   
-  "update_rate": 16,
+  "update_rate": 16.0,
   "key_frames" :
   [
-    { "time": 0.0   , "position": [  0.0,   0.0, -50.0], "forward": [0.0, 0.0, 1.0], "up": [0.0, 1.0, 0.0] },
-    { "time": 1000.0, "position": [  0.0, -50.0,   0.0], "forward": [0.0, 1.0, 0.0], "up": [0.0, 0.0, 1.0] },
-    { "time": 2000.0, "position": [-50.0,   0.0,   0.0], "forward": [1.0, 0.0, 0.0], "up": [0.0, 1.0, 0.0] },
-    { "time": 3000.0, "position": [  0.0,   0.0, -50.0], "forward": [0.0, 0.0, 1.0], "up": [0.0, 1.0, 0.0] }
+    { "time": 0.0   , "position": [   0.0,    0.0, -100.0], "forward": [0.0, 0.0, 1.0], "up": [0.0, 1.0, 0.0] },
+    { "time": 1000.0, "position": [   0.0, -100.0,    0.0], "forward": [0.0, 1.0, 0.0], "up": [0.0, 0.0, 1.0] },
+    { "time": 2000.0, "position": [-100.0,    0.0,    0.0], "forward": [1.0, 0.0, 0.0], "up": [0.0, 1.0, 0.0] },
+    { "time": 3000.0, "position": [   0.0,    0.0, -100.0], "forward": [0.0, 0.0, 1.0], "up": [0.0, 1.0, 0.0] }
   ]
 }