diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89e5e4f11..5e06fe983 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,10 @@ Notable changes to Ascent are documented in this file. This changelog started on
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project aspires to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## Unreleased 
+## Unreleased
 ### Preferred dependency versions for ascent@develop
 - conduit@0.9.2
-- vtk-m@2.1.0 (requires [patch 1](https://github.com/Alpine-DAV/ascent/blob/0aef6cffd522be7419651e6adf586f9a553297d0/scripts/build_ascent/2024_05_03_vtkm-mr3215-ext-geom-fix.patch) 
+- vtk-m@2.1.0 (requires [patch 1](https://github.com/Alpine-DAV/ascent/blob/0aef6cffd522be7419651e6adf586f9a553297d0/scripts/build_ascent/2024_05_03_vtkm-mr3215-ext-geom-fix.patch)
                         [patch 2](https://github.com/Alpine-DAV/ascent/blob/develop/scripts/build_ascent/2024_07_02_vtkm-mr3246-raysubset_bugfix.patch) )
 - raja@2024.02.1
 - umpire@2024.02.1
@@ -31,11 +31,12 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s
 - Added `fields` option to the project 2d to support scalar rendering of specific fields.
 - Added `dataset_bounds` option to the project 2d, which can be used instead of a full 3D camera specification
 - Added support for triggers to execute actions from multiple files via an `actions_files` option that takes a list of actions files.
-- Added an `external_surfaces` transform filter, that can be used to reduce memory requirements in pipelines where you plan to only process the external faces of a data set. 
+- Added an `external_surfaces` transform filter, that can be used to reduce memory requirements in pipelines where you plan to only process the external faces of a data set.
 - Added a `declare_fields` action, that allows users to explicitly list the fields to return for field filtering. This option avoids complex field parsing logic.
+- Added a 2d camera mode (`camera/2d: [left, right, bottom, top]`) to scene render cameras and the `project_2d` (scalar rendering) filter cameras.
 
 ### Changed
-- Changed the replay utility's binary names such that `replay_ser` is now `ascent_replay` and `raplay_mpi` is now `ascent_replay_mpi`. This will help prevent potential name collisions with other tools that also have replay utilities. 
+- Changed the replay utility's binary names such that `replay_ser` is now `ascent_replay` and `raplay_mpi` is now `ascent_replay_mpi`. This will help prevent potential name collisions with other tools that also have replay utilities.
 
 ### Fixed
 - Resolved a few cases where MPI_COMM_WORLD was used instead instead of the selected MPI communicator.
@@ -75,7 +76,7 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s
 ### Changed
 - Changed the Data Binning filter to accept a `reduction_field` parameter (instead of `var`), and similarly the axis parameters to take `field` (instead of `var`).  The `var` style parameters are still accepted, but deprecated and will be removed in a future release.
 - Changed the Streamline and WarpXStreamline filters to apply the VTK-m Tube filter to their outputs, allowing for the results to be rendered.
-- Updated CMake Python build infrastructure to use 
+- Updated CMake Python build infrastructure to use
 
 ### Fixed
 - Various small bug fixes
@@ -128,12 +129,12 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s
 - Added extract `flatten` from Conduit Blueprint
 - Added Log base 10 filter. Filter type is `log10`
 - Added Log base 2 filter. Filter type is `log2`
-- Added Feature Map in the docs. Detailing Devil Ray and VTKh features 
+- Added Feature Map in the docs. Detailing Devil Ray and VTKh features
 - Added `scripts/build_ascent/build_ascent.sh` a script that demonstrates how to manually build Ascent and its main dependencies
 - Added ability to override dimensions for the rendered bounding box around a dataset
 - Added CMake option `ENABLE_HIDDEN_VISIBILITY` (default=ON), which controls if hidden visibility is used for private symbols
 - Added documentation for how to use ROCm's rocprof profiler for GPUs with Ascent
-- Added support for Caliper performance annotations 
+- Added support for Caliper performance annotations
 - Added automatic slice filter that evaluates a number of slices and outputs the one with the highest entropy
 
 ### Changed
@@ -157,15 +158,15 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s
 - Added OCCA Derived Field Generation support
 - Added more math expressions
 - Added a time expression
-- Added Cinema rendering support for Devil Ray 
-- Added `streamline` and `particle_advection` transforms 
+- Added Cinema rendering support for Devil Ray
+- Added `streamline` and `particle_advection` transforms
 - Added history gradient expressions
 - Added the ability save named sessions
 - Added new options to specify Cinema rendering parameters
 - Added the ability save subsets of expression results to session files
 - Added the ability to add comments to PNG files that Ascent creates
 - Added timings out control option to Ascent (and Flow)
-- Added support to render Polygonal nd Polyhedral Meshes 
+- Added support to render Polygonal nd Polyhedral Meshes
 - Added option to turn of world annotations
 - Added FIDES Support
 
diff --git a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_conduit_to_vtkm_parsing.cpp b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_conduit_to_vtkm_parsing.cpp
index fe9bc7797..f6104abdb 100644
--- a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_conduit_to_vtkm_parsing.cpp
+++ b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_conduit_to_vtkm_parsing.cpp
@@ -87,14 +87,33 @@ parse_image_dims(const conduit::Node &node, int &width, int &height)
 
 }
 
+
 //-----------------------------------------------------------------------------
 void
 parse_camera(const conduit::Node camera_node, vtkm::rendering::Camera &camera)
 {
   typedef vtkm::Vec<vtkm::Float32,3> vtkmVec3f;
+
   //
   // Get the optional camera parameters
   //
+
+  // check for 2d mode first
+  if(camera_node.has_child("2d"))
+  {
+    // camera:
+    //  2d: [l,r,b,t]
+    camera.SetModeTo2D();
+    conduit::Node n;
+    camera_node["2d"].to_float64_array(n);
+    const float64 *view_vals = n.as_float64_ptr();
+
+    camera.SetViewRange2D(view_vals[0],
+                          view_vals[1],
+                          view_vals[2],
+                          view_vals[3]);
+  }
+
   if(camera_node.has_child("look_at"))
   {
       conduit::Node n;
@@ -248,8 +267,8 @@ parse_color_table(const conduit::Node &color_table_node)
     }
     else if (control_points_node.dtype().is_object())
     {
-        if (control_points_node.has_child("r") && 
-            control_points_node.has_child("g") && 
+        if (control_points_node.has_child("r") &&
+            control_points_node.has_child("g") &&
             control_points_node.has_child("b"))
         {
             clear = true;
diff --git a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_rendering_filters.cpp b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_rendering_filters.cpp
index 8fbc6d5fc..8fe459da0 100644
--- a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_rendering_filters.cpp
+++ b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_rendering_filters.cpp
@@ -147,6 +147,7 @@ check_renders_surprises(const conduit::Node &renders_node)
   const int num_renders = renders_node.number_of_children();
   // render paths
   std::vector<std::string> r_valid_paths;
+  r_valid_paths.push_back("camera/2d");
   r_valid_paths.push_back("image_name");
   r_valid_paths.push_back("image_prefix");
   r_valid_paths.push_back("image_width");
@@ -214,7 +215,7 @@ check_renders_surprises(const conduit::Node &renders_node)
         const conduit::Node &position = phi_theta_positions.child(i);
         std::stringstream ss;
         ss << "[" << i << "]";
-        if (position.name() != ss.str()) 
+        if (position.name() != ss.str())
         {
           surprises += "Surprise parameter '";
           surprises += position.name();
@@ -522,8 +523,8 @@ vtkh::Render parse_render(const conduit::Node &render_node,
       conduit::Node n;
       render_node["color_bar_position"].to_float32_array(n);
       const float32 *cb_pos = n.as_float32_ptr();
-      vtkm::Bounds pos(vtkm::Range(cb_pos[0+4*i],cb_pos[1+4*i]), 
-			vtkm::Range(cb_pos[2+4*i],cb_pos[3+4*i]), 
+      vtkm::Bounds pos(vtkm::Range(cb_pos[0+4*i],cb_pos[1+4*i]),
+			vtkm::Range(cb_pos[2+4*i],cb_pos[3+4*i]),
 			vtkm::Range(0.0,0.0));
       cb_position.push_back(pos);
     }
@@ -911,7 +912,7 @@ class CinemaManager
 
   void create_cinema_angles()
   {
-    for(int p = 0; p < m_phi_values.size(); ++p) 
+    for(int p = 0; p < m_phi_values.size(); ++p)
     {
       for(int t = 0; t < m_theta_values.size(); ++t)
       {
@@ -1267,13 +1268,13 @@ DefaultRender::execute()
 	  {
 	    float64_accessor d_bounds = render_node["dataset_bounds"].value();
 	    int num_bounds = d_bounds.number_of_elements();
-	    
+
 	    if(num_bounds != 6)
             {
               std::string render_name = renders_node.child_names()[i];
               std::string fpath = filter_to_path(this->name());
               ASCENT_ERROR("Render ("<<fpath<<"/"<<render_name<<")"<<
-                           " only provided " << num_bounds << 
+                           " only provided " << num_bounds <<
 	                   " dataset_bounds when 6 are required:" <<
 			   " [xMin,xMax,yMin,yMax,zMin,zMax]");
 	    }
@@ -1292,12 +1293,12 @@ DefaultRender::execute()
 	  }
 
 	  if(is_auto_camera)
-	  { 
+	  {
             DataObject *source
               = graph().workspace().registry().fetch<DataObject>("source_object");
-            
+
             std::shared_ptr<VTKHCollection> collection = source->as_vtkh_collection();
-      
+
 	    if(!render_node.has_path("auto_camera/field"))
               ASCENT_ERROR("Auto Camera must specify a 'field'");
 	    if(!render_node.has_path("auto_camera/metric"))
@@ -1308,41 +1309,41 @@ DefaultRender::execute()
             std::string field_name = render_node["auto_camera/field"].as_string();
             std::string metric     = render_node["auto_camera/metric"].as_string();
             int samples            = render_node["auto_camera/samples"].as_int32();
-      
+
             if(!collection->has_field(field_name))
             {
               ASCENT_ERROR("Unknown field '"<<field_name<<"' in Auto Camera");
             }
-      
+
             std::string topo_name = collection->field_topology(field_name);
             vtkh::DataSet &dataset = collection->dataset_by_topology(topo_name);
-      
+
             vtkh::AutoCamera auto_cam;
-      
+
 	    int height = 1024;
 	    int width  = 1024;
             if(render_node.has_path("auto_camera/bins"))
             {
               int bins = render_node["auto_camera/bins"].as_int32();
-              auto_cam.SetNumBins(bins); 
+              auto_cam.SetNumBins(bins);
             }
             if(render_node.has_path("auto_camera/height"))
             {
               height = render_node["auto_camera/height"].as_int32();
-              auto_cam.SetHeight(height); 
+              auto_cam.SetHeight(height);
             }
             if(render_node.has_path("auto_camera/width"))
             {
               width = render_node["auto_camera/width"].as_int32();
-              auto_cam.SetWidth(width); 
+              auto_cam.SetWidth(width);
             }
-      
+
             auto_cam.SetInput(&dataset);
             auto_cam.SetField(field_name);
             auto_cam.SetMetric(metric);
             auto_cam.SetNumSamples(samples);
             auto_cam.Update();
-            
+
             vtkm::rendering::Camera *camera = new vtkm::rendering::Camera;
             *camera = auto_cam.GetCamera();
 	    vtkh::Render render = vtkh::MakeRender(width,
@@ -1388,12 +1389,12 @@ DefaultRender::execute()
           scene_bounds = original_bounds;
         }
       }
-      
+
       vtkh::Render render = vtkh::MakeRender(1024,
                             1024,
                             scene_bounds,
                             image_name);
-      
+
       Node meta = Metadata::n_metadata;
       if(meta.has_path("comments"))
       {
@@ -1444,7 +1445,7 @@ VTKHBounds::execute()
     {
         ASCENT_ERROR("VTKHBounds input must be a data object");
     }
-    
+
     DataObject *data_object = input<DataObject>(0);
 
     // data could be the result of a failed pipeline
@@ -1453,7 +1454,7 @@ VTKHBounds::execute()
       std::shared_ptr<VTKHCollection> collection = data_object->as_vtkh_collection();
       bounds->Include(collection->global_bounds());
     }
-    
+
     set_output<vtkm::Bounds>(bounds);
 }
 
@@ -1801,7 +1802,7 @@ CreatePlot::execute()
     }
 
 
-    
+
 
     if(type == "mesh")
     {
@@ -1900,25 +1901,33 @@ ExecScene::declare_interface(conduit::Node &i)
 }
 
 //-----------------------------------------------------------------------------
-void generate_camera_meshes(conduit::Node &image_data){
+void generate_camera_meshes(conduit::Node &image_data)
+{
   conduit::Node &camera = image_data["camera"];
+
+  if(camera.has_child("2d"))
+  {
+    // skip cam mesh for 2d cam mode
+    return;
+  }
+
   conduit::Node &cam_frust = camera["camera_frustum_mesh"];
   // std::string image_name = image_data["image_name"].as_string();
-  
+
   // Scene Bounds Mesh
   float64_accessor scene_bounds = image_data["scene_bounds"].value();
-  double x_scene_bounds[] = {scene_bounds[0], scene_bounds[0], scene_bounds[0], scene_bounds[0], 
+  double x_scene_bounds[] = {scene_bounds[0], scene_bounds[0], scene_bounds[0], scene_bounds[0],
                              scene_bounds[3], scene_bounds[3], scene_bounds[3], scene_bounds[3]};
-  double y_scene_bounds[] = {scene_bounds[1], scene_bounds[1], scene_bounds[4], scene_bounds[4], 
+  double y_scene_bounds[] = {scene_bounds[1], scene_bounds[1], scene_bounds[4], scene_bounds[4],
                              scene_bounds[1], scene_bounds[1], scene_bounds[4], scene_bounds[4]};
-  double z_scene_bounds[] = {scene_bounds[2], scene_bounds[5], scene_bounds[5], scene_bounds[2], 
+  double z_scene_bounds[] = {scene_bounds[2], scene_bounds[5], scene_bounds[5], scene_bounds[2],
                              scene_bounds[2], scene_bounds[5], scene_bounds[5], scene_bounds[2]};
 
   cam_frust["coordsets/scene_bounds_coords/type"] = "explicit";
   cam_frust["coordsets/scene_bounds_coords/values/x"].set(x_scene_bounds, 8);
   cam_frust["coordsets/scene_bounds_coords/values/y"].set(y_scene_bounds, 8);
   cam_frust["coordsets/scene_bounds_coords/values/z"].set(z_scene_bounds, 8);
-  
+
   cam_frust["topologies/scene_bounds_topo/type"] = "unstructured";
   cam_frust["topologies/scene_bounds_topo/coordset"] = "scene_bounds_coords";
   cam_frust["topologies/scene_bounds_topo/elements/shape"]  = "line";
@@ -1937,14 +1946,14 @@ void generate_camera_meshes(conduit::Node &image_data){
   // Initializing and normalizing up vector
   float64_accessor up = camera["up"].value();
   vtkm::Vec<vtkm::Float64,3> vtkm_up(up[0], up[1], up[2]);
-  
+
   vtkm::Vec<vtkm::Float64,3> forward(0,0,-1);
   double angle_between = vtkm::ACos(vtkm::Dot(forward, vtkm_look)) / vtkm::Pi() * 180;
 
   // If the look vector has been rotated by a certain angle, adjust the camera up vector to match
   if (vtkm::Abs(angle_between) >= 0.001) {
     vtkm::Vec<vtkm::Float64,3> axisOfRotation = vtkm::Cross(vtkm_look, forward);
-    vtkm_up = 
+    vtkm_up =
       vtkm::Transform3DVector(vtkm::Transform3DRotate(-angle_between, axisOfRotation), vtkm_up);
   }
   vtkm::Normalize(vtkm_up);
@@ -1965,24 +1974,24 @@ void generate_camera_meshes(conduit::Node &image_data){
   // Near frustum
   double frust_near_height = near_dist * vtkm::Tan(fov * 0.5 * vtkm::Pi() / 180.0) / zoom;
   double frust_near_width  = frust_near_height;
-  vtkm::Vec<vtkm::Float64,3> near_frust_ll = look_near_pt + (-1 * vtkm_up * frust_near_height) 
+  vtkm::Vec<vtkm::Float64,3> near_frust_ll = look_near_pt + (-1 * vtkm_up * frust_near_height)
                                              + (vtkm_side * frust_near_width * image_aspect );
-  vtkm::Vec<vtkm::Float64,3> near_frust_lr = look_near_pt + (-1 * vtkm_up * frust_near_height) 
+  vtkm::Vec<vtkm::Float64,3> near_frust_lr = look_near_pt + (-1 * vtkm_up * frust_near_height)
                                              + (-1 * vtkm_side * frust_near_width * image_aspect);
-  vtkm::Vec<vtkm::Float64,3> near_frust_ur = look_near_pt + (vtkm_up * frust_near_height) 
+  vtkm::Vec<vtkm::Float64,3> near_frust_ur = look_near_pt + (vtkm_up * frust_near_height)
                                              + (-1 * vtkm_side * frust_near_width * image_aspect);
-  vtkm::Vec<vtkm::Float64,3> near_frust_ul = look_near_pt + (vtkm_up * frust_near_height) 
+  vtkm::Vec<vtkm::Float64,3> near_frust_ul = look_near_pt + (vtkm_up * frust_near_height)
                                              + (vtkm_side * frust_near_width * image_aspect);
   // Far frustum
   double frust_far_height = far_dist * vtkm::Tan(fov * 0.5 * vtkm::Pi() / 180.0) / zoom;
   double frust_far_width  = frust_far_height;
-  vtkm::Vec<vtkm::Float64,3> far_frust_ll = look_far_pt + (-1 * vtkm_up * frust_far_height) 
+  vtkm::Vec<vtkm::Float64,3> far_frust_ll = look_far_pt + (-1 * vtkm_up * frust_far_height)
                                             + (vtkm_side * frust_far_width * image_aspect);
-  vtkm::Vec<vtkm::Float64,3> far_frust_lr = look_far_pt + (-1 * vtkm_up * frust_far_height) 
+  vtkm::Vec<vtkm::Float64,3> far_frust_lr = look_far_pt + (-1 * vtkm_up * frust_far_height)
                                             + (-1 * vtkm_side * frust_far_width * image_aspect);
-  vtkm::Vec<vtkm::Float64,3> far_frust_ur = look_far_pt + (vtkm_up * frust_far_height) 
+  vtkm::Vec<vtkm::Float64,3> far_frust_ur = look_far_pt + (vtkm_up * frust_far_height)
                                             + (-1 * vtkm_side * frust_far_width * image_aspect);
-  vtkm::Vec<vtkm::Float64,3> far_frust_ul = look_far_pt + (vtkm_up * frust_far_height) 
+  vtkm::Vec<vtkm::Float64,3> far_frust_ul = look_far_pt + (vtkm_up * frust_far_height)
                                             + (vtkm_side * frust_far_width * image_aspect);
 
   // Assembling frustum mesh
@@ -1996,7 +2005,7 @@ void generate_camera_meshes(conduit::Node &image_data){
   double z_val_frust[] = {near_frust_ll[2],near_frust_lr[2],near_frust_ur[2],near_frust_ul[2],
                           far_frust_ll[2], far_frust_lr[2], far_frust_ur[2], far_frust_ul[2],
                           look_near_pt[2],  look_far_pt[2], up_vector_pt[2]};
-  
+
   cam_frust["coordsets/camera_frustum_coords/type"] = "explicit";
   cam_frust["coordsets/camera_frustum_coords/values/x"].set(x_val_frust, 11);
   cam_frust["coordsets/camera_frustum_coords/values/y"].set(y_val_frust, 11);
@@ -2057,13 +2066,30 @@ ExecScene::execute()
       image_data["image_width"] = renders->at(i).GetWidth();
       image_data["image_height"] = renders->at(i).GetHeight();
 
-      image_data["camera/position"].set(&renders->at(i).GetCamera().GetPosition()[0],3);
-      image_data["camera/look_at"].set(&renders->at(i).GetCamera().GetLookAt()[0],3);
-      image_data["camera/up"].set(&renders->at(i).GetCamera().GetViewUp()[0],3);
-      image_data["camera/zoom"] = renders->at(i).GetCamera().GetZoom();
-      image_data["camera/fov"] = renders->at(i).GetCamera().GetFieldOfView();
-      image_data["camera/near_plane"] = renders->at(i).GetCamera().GetClippingRange().Min;
-      image_data["camera/far_plane"] = renders->at(i).GetCamera().GetClippingRange().Max;
+      // check for 2d vs 3d camera
+      if(renders->at(i).GetCamera().GetMode() ==  vtkm::rendering::Camera::Mode::TwoD)
+      {
+        vtkm::Bounds bounds =  renders->at(i).GetCamera().GetViewRange2D();
+        double view_2d[4] = {bounds.X.Min,
+                             bounds.Y.Min,
+                             bounds.X.Max,
+                             bounds.Y.Max};
+
+        image_data["camera/2d"].set(view_2d,4);
+      }
+      else
+      {
+        image_data["camera/position"].set(&renders->at(i).GetCamera().GetPosition()[0],3);
+        image_data["camera/look_at"].set(&renders->at(i).GetCamera().GetLookAt()[0],3);
+        image_data["camera/up"].set(&renders->at(i).GetCamera().GetViewUp()[0],3);
+        image_data["camera/zoom"] = renders->at(i).GetCamera().GetZoom();
+        image_data["camera/fov"] = renders->at(i).GetCamera().GetFieldOfView();
+        image_data["camera/near_plane"] = renders->at(i).GetCamera().GetClippingRange().Min;
+        image_data["camera/far_plane"] = renders->at(i).GetCamera().GetClippingRange().Max;
+      }
+      auto pan_vals = renders->at(i).GetCamera().GetPan();
+      image_data["camera/xpan"] = pan_vals[0];
+      image_data["camera/ypan"] = pan_vals[1];
       vtkm::Bounds bounds=  renders->at(i).GetSceneBounds();
       double coord_bounds [6] = {bounds.X.Min,
                                  bounds.Y.Min,
@@ -2071,7 +2097,7 @@ ExecScene::execute()
                                  bounds.X.Max,
                                  bounds.Y.Max,
                                  bounds.Z.Max};
-      
+
       image_data["scene_bounds"].set(coord_bounds, 6);
 
       generate_camera_meshes(image_data);
diff --git a/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_1_100.png b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_1_100.png
new file mode 100644
index 000000000..1f47cc254
Binary files /dev/null and b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_1_100.png differ
diff --git a/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_2_100.png b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_2_100.png
new file mode 100644
index 000000000..e830a7113
Binary files /dev/null and b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_2_100.png differ
diff --git a/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_3_100.png b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_3_100.png
new file mode 100644
index 000000000..203c86337
Binary files /dev/null and b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_3_100.png differ
diff --git a/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_4_100.png b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_4_100.png
new file mode 100644
index 000000000..aa96f1f80
Binary files /dev/null and b/src/tests/_baseline_images/tout_render_2d_uniform_2d_cam_view_4_100.png differ
diff --git a/src/tests/ascent/t_ascent_render_2d.cpp b/src/tests/ascent/t_ascent_render_2d.cpp
index ee3d19e10..e5de102b4 100644
--- a/src/tests/ascent/t_ascent_render_2d.cpp
+++ b/src/tests/ascent/t_ascent_render_2d.cpp
@@ -406,6 +406,91 @@ TEST(ascent_render_2d, test_render_2d_uniform_render_serial_backend)
     EXPECT_TRUE(check_test_image(output_file));
 }
 
+//-----------------------------------------------------------------------------
+TEST(ascent_render_2d, test_render_2d_cam)
+{
+
+    // the vtkm runtime is currently our only rendering runtime
+    Node n;
+    ascent::about(n);
+    // only run this test if ascent was built with vtkm support
+    if(n["runtimes/ascent/vtkm/status"].as_string() == "disabled")
+    {
+        ASCENT_INFO("Ascent vtkm support disabled, skipping test");
+        return;
+    }
+
+    ASCENT_INFO("Testing 2D Ascent Runtime 2d camera controls");
+
+    //
+    // Create an example mesh.
+    //
+    Node data, verify_info;
+    conduit::blueprint::mesh::examples::braid("uniform",
+                                               EXAMPLE_MESH_SIDE_DIM,
+                                               EXAMPLE_MESH_SIDE_DIM,
+                                               0,
+                                               data);
+
+    EXPECT_TRUE(conduit::blueprint::mesh::verify(data,verify_info));
+
+    string output_path = prepare_output_dir();
+    string output_file_base = conduit::utils::join_file_path(output_path, "tout_render_2d_uniform_2d_cam");
+    string output_file_v1 = output_file_base  + "_view_1_";
+    string output_file_v2 = output_file_base  + "_view_2_";
+    string output_file_v3 = output_file_base  + "_view_3_";
+    string output_file_v4 = output_file_base  + "_view_4_";
+    string output_info = output_file_base  + "_info";
+    // remove old images before rendering
+    remove_test_image(output_file_v1);
+    remove_test_image(output_file_v2);
+    remove_test_image(output_file_v3);
+    remove_test_image(output_file_v4);
+
+    //
+    // Create the actions.
+    //
+    Node actions;
+    conduit::Node &add_scenes = actions.append();
+    add_scenes["action"] = "add_scenes";
+    conduit::Node &scenes = add_scenes["scenes"];
+    scenes["scene1/plots/plt1/type"] = "pseudocolor";
+    scenes["scene1/plots/plt1/field"] = "braid";
+
+    scenes["scene1/renders/r1/image_prefix"] =  output_file_v1;
+    scenes["scene1/renders/r1/camera/2d"] = {-10.0,10.0,-10.0,10.0};
+
+    scenes["scene1/renders/r2/image_prefix"] =  output_file_v2;
+    scenes["scene1/renders/r2/camera/2d"] = {-20.0,20.0,-20.0,20.0};
+
+    scenes["scene1/renders/r3/image_prefix"] =  output_file_v3;
+    scenes["scene1/renders/r3/camera/2d"] = {-7.0,3.0,0.0,4.0};
+
+    scenes["scene1/renders/r4/image_prefix"] = output_file_v4;
+    scenes["scene1/renders/r4/camera/2d"] = {-10.0,0.0,-10.0,10.0};
+    scenes["scene1/renders/r4/image_width"]  = 512;
+    scenes["scene1/renders/r4/image_height"] = 1024;
+
+
+    conduit::Node info;
+
+    Ascent ascent;
+    ascent.open();
+    ascent.publish(data);
+    ascent.execute(actions);
+    ascent.info(info);
+    ascent.close();
+
+    std::cout << info.to_yaml() << std::endl;
+
+    // check output
+    EXPECT_TRUE(check_test_image(output_file_v1));
+    EXPECT_TRUE(check_test_image(output_file_v2));
+    EXPECT_TRUE(check_test_image(output_file_v3));
+    EXPECT_TRUE(check_test_image(output_file_v4));
+}
+
+
 //-----------------------------------------------------------------------------
 TEST(ascent_render_2d, test_render_2d_bentgrid_example)
 {
diff --git a/src/tests/ascent/t_ascent_scalar_renderer.cpp b/src/tests/ascent/t_ascent_scalar_renderer.cpp
index 257dce26f..57ce66f4d 100644
--- a/src/tests/ascent/t_ascent_scalar_renderer.cpp
+++ b/src/tests/ascent/t_ascent_scalar_renderer.cpp
@@ -226,7 +226,7 @@ TEST(ascent_scalar_rendering, test_scalar_rendering_data_bounds_specified)
 
     conduit::relay::io::blueprint::save_mesh(data,conduit::utils::join_file_path(output_path,
                                                 "tout_scalar_rendering_bounds_specified_input"),"hdf5");
-    
+
     //
     // Create the actions.
     //
@@ -275,6 +275,77 @@ TEST(ascent_scalar_rendering, test_scalar_rendering_data_bounds_specified)
     ASCENT_ACTIONS_DUMP(actions,output_file,msg);
 }
 
+//-----------------------------------------------------------------------------
+TEST(ascent_scalar_rendering, test_scalar_rendering_2d_camera)
+{
+    // the vtkm runtime is currently our only rendering runtime
+    Node n;
+    ascent::about(n);
+    // only run this test if ascent was built with vtkm support
+    if(n["runtimes/ascent/vtkm/status"].as_string() == "disabled")
+    {
+        ASCENT_INFO("Ascent support disabled, skipping test");
+        return;
+    }
+
+
+    //
+    // Create an example mesh.
+    //
+    Node data, verify_info;
+    conduit::blueprint::mesh::examples::braid("quads",
+                                              EXAMPLE_MESH_SIDE_DIM,
+                                              EXAMPLE_MESH_SIDE_DIM,
+                                              0,
+                                              data);
+
+    EXPECT_TRUE(conduit::blueprint::mesh::verify(data,verify_info));
+
+    ASCENT_INFO("Testing Scalar Rendering with a 2d camera");
+
+
+    string output_path = prepare_output_dir();
+    string output_file = conduit::utils::join_file_path(output_path,"tout_scalar_rendering_2d_camera");
+
+    conduit::relay::io::blueprint::save_mesh(data,conduit::utils::join_file_path(output_path,
+                                                  "tout_scalar_rendering_2d_camera_input"),"hdf5");
+
+    //
+    // Create the actions.
+    //
+
+    conduit::Node actions;
+    // add the pipeline
+    conduit::Node &add_pipelines= actions.append();
+    add_pipelines["action"] = "add_pipelines";
+    conduit::Node &pipelines = add_pipelines["pipelines"];
+    pipelines["pl1/f1/type"] = "project_2d";
+    conduit::Node &params = pipelines["pl1/f1/params"];
+    params["image_width"]  = 512;
+    params["image_height"] = 512;
+    params["camera/2d"] = { -7.0, 3.0, 0.0,4.0 };
+
+    // add the extracts
+    conduit::Node &add_extracts = actions.append();
+    add_extracts["action"] = "add_extracts";
+    conduit::Node &extracts=add_extracts["extracts"];;
+    extracts["e1/type"]  = "relay";
+    extracts["e1/pipeline"] = "pl1";
+    extracts["e1/params/path"] = output_file;
+    extracts["e1/params/protocol"] = "blueprint/mesh/hdf5";
+
+
+    Ascent ascent;
+    ascent.open();
+    ascent.publish(data);
+    ascent.execute(actions);
+    ascent.close();
+
+    // check that we created an image
+    std::string msg = "An example of scalar rendering with 2d camera mode";
+    ASCENT_ACTIONS_DUMP(actions,output_file,msg);
+}
+
 //-----------------------------------------------------------------------------
 int main(int argc, char* argv[])
 {