AMR Contour and Interface support in VTK and ParaView

October 20, 2026
A close-up visualization of an adaptive triangular mesh surface rendered in copper-orange with blue wireframe edges, highlighting local mesh refinement across complex terrain in VTK/ParaView.

Introduction

Adaptive Mesh Refinement (AMR) is a popular method of numerical simulation used to adapt the accuracy of the simulation, which produces volumes of data of different levels of detail.

While this can be achieved using an unstructured grid, using a dedicated data model is preferred as it improves computation speed and memory usage.

Support for such data was added in VTK data model a long time ago with success although with a small adoption rate by the community and was recently deeply reworked as outlined in this blog.

In essence, an AMR dataset is just a collection of regular grids of different sizes and refinements, organized by levels, each more refined than the previous.

It comes in two flavors, OverlappingAMR and NonOverlappingAMR.

A close-up scientific visualization of a 2D structured triangular mesh displaying a scalar field gradient from yellow to dark orange, with a vertical color bar legend on the right representing $\text{Log10(Density) [N/cm}^3\text{]}$.
OverlappingAMR result of a jet simulation visualized in ParaView, data provided by Jean Favre, CSCS
A computational fluid dynamics (CFD) simulation slice in ParaView/VTK showing velocity magnitude ("U Magnitude") around an aerodynamic silhouette, mapped across a triangular mesh with a top color legend ranging from 0.0 to 92.0.
OverlappingAMR result of the flow of air over a car body, data provided by Håkon Strandenes, KMTurbulenz

OverlappingAMR, as its name indicates, contains grids that overlap each other, with each level covering the whole domain. NonOverlapping, on the contrary, contains grids without any kind of overlap which is made for a more intricate grid organization at the higher levels.

OverlappingAMR is usually more efficient at the cost of some data duplication in the coarser levels, which we will focus on in this blog.

The contouring issue

Having different levels of refinement means that there are interfaces where, in essence, we have T cells.

A T cells, usually looks like that:

A line-art diagram of an adaptive mesh refinement boundary showing T-junction hanging nodes highlighted with red lines and red arrows at the interface between fine and coarse grid levels
A 2D illustration of T cells in red at the interface of two grids of different refinements

For many algorithms, a T cell acts as a discontinuity where the interpolation breaks from one cell to another. Indeed, both “central” points are only present in cells on the left side and not on the right.

In the specific case of the contour algorithm, where we generate isosurfaces (isolines in 2D) based on values interpolation, the result looks like this:

A line-art schematic illustrating scalar interpolation discontinuity at an AMR fine-to-coarse grid boundary, featuring green isolines ($14.5$) and a red bracket marking a numerical jump between node values ($10$ versus $14$ and $15$).
A 2D illustration of discontinuous contour computation because of T cells

The solution is obviously to generate a proper interface in order to make this T cells disappear, generating new points and cells on the fly before running the contour:

A 2D illustration of an interface to address the T cell issue
A line-art schematic showing a stitched, conformal AMR grid interface where coarse cells are triangulated to eliminate T-junction hanging nodes, resolving scalar isoline discontinuities across fine-to-coarse boundaries.
A 2D illustration of the resulting contour with the interface

While this problem is generic of any kind of T cell, the particularity of AMR is that we know where those T cells are and where we should generate an interface, and all the cells we are dealing with are voxels, so this is a pretty fixable situation

Generating the interface

The illustrations above are in 2D and the resulting continuous interface is quite easy to visualize, but in 3D, the problem becomes a bit more complex. Here is how we do it.

The general idea of the algorithm is as follows:

The input is an overlapping AMR (aka a data object tree of cartesian grids).
The output is a partitioned dataset containing cartesian grids and unstructured grid.

For each block of the AMR:

  • Identify each interface cells, hide them all in the block and add the block to the output
  • Using all interface cells, construct on edges maps containing the points for the rough grid and points from the refined grid within the edge
  • On each interface cells, construct a proper continuous interface using the edge map while interpolating data on the created points
  • Add the interface cells as unstructured grid to the output

Considering that we work block by block, there will be an interface for each block, which will be generated where it “touches” a block of higher refinement. It also means that if there are edges that are part of more than two blocks of different refinement levels, the resulting interface may not be fully continuous. When tested on real cases, this case happened rarely and the resulting interface was always continuous anyway.

Interface cell identification

Identifying the interface cells is pretty easy, it is merely a matter of iterating over each point of each cell in the block and checking if this point is present in the overall AMR in a block of higher refinement. This is the most expensive part of the algorithm. If it is, the cell is part of the interface. The information about the point is stored for the next step as well.

A line-art schematic of an AMR grid boundary with ghost cells shaded in gray, highlighting shared corner nodes marked with red asterisks and T-junction hanging nodes marked with blue asterisks along the fine-to-coarse interface.
A 2D illustration of the interface identification with refined points in red and intermediary points in blue.

Edges iteration acceleration map

In order to reconstruct the interface, we need to know exactly which edges are refined and the location of the refined points on the edge. Since this will be used a lot by many different cells, finding the points on the refined edge only once is critical for performance.

In order to find them, it is needed to iterate over all points of all edges of all cells in the interface.
In our implementation this is done during interface cell identification.

An interface edge is an edge where both points are refined, and the center point of the edge is also in a refined block.

Once an edge has been identified as refined, the intermediary points must be recovered from the AMR. In order to do that, first the refined block is recovered, then the “WalkAxis” is computed, in order to know in which direction the edge is, X, Y or Z.

Using the direction, the edge can be walked on the refined block moving from one refined edge to the next. It is critical that the order of the edge points are kept.

It can happen that the edge is refined over multiple refined grids, in the case “contact” points are identified in order to know when to switch from one grid to another.

A line-art schematic of a multi-block AMR grid interface showing color-coded fine grid blocks (pink and green) meeting a coarse block (yellow), with red corner nodes, blue hanging nodes, and a light blue shared block boundary node along the interface.
A diagram legend defining color codes for grid blocks (pink, green, yellow) and point types (red, blue, light blue) used in adaptive mesh refinement (AMR) schematics.

The result is a map of pairs of non-refined points to refined points that can later be used to reconstruct the interface.

Interface construction

To construct the interface, first the point data and cell data are initialized to contain the same structure as the grid, and a merging locator is also initialized as it will be used to create the interface unstructured grid, cell structures are also initialized.

Then we iterate on each voxel of the grid that has been flagged as part of the interface.

First we add the center of the voxel to the interface, as it will always be needed.
Then we iterate on each face of the voxel, recover each edge and center and identify one of three cases, either the face is a refined face, a non-refined face, or an interface face.

To identify the case, we simply count the number of refined points on the face, 4 (and center) is refined, 0 is non-refined and anything else is interface.

The non-refined case is trivial, we can just create a simple pyramid with the face as the base and the center as the top.

A 3D geometric render of a semi-transparent pink coarse hexahedral mesh cell subdivided into internal tetrahedral pyramid elements to stitch a non-conformal AMR interface.
A non-refined face generating a single pyramid interface

The refined case is a bit more complex, we must create many pyramids where the base is a square of refined points and the top is the center. Those refined points have to be recovered in the refined grids as they are not on the edges. Contact points are also critical to move from one grid to another while moving along the edges.

First the face must be cut into subfaces, where a subface is fully contained in a single refined grid, to do that we use the contact points on edges and are able to find the “opposite” point which is not on any edge. On most case each face contains a single subface which is itself, but it can be more complex than that.

Then we generate pyramids on the subface, one pyramid by refined voxels.
In order to do that we just iterate on both edges and create pyramids as we go using the subface points.

A line-art schematic of a 2D stitched AMR interface where a yellow coarse cell is subdivided fan-wise from a central internal node to connect directly to fine-grid boundary nodes (pink and green blocks).
A 2D illustration of “pyramids” being created on different grids thanks to contact points
In black, the voxel center.
A 3D geometric render of a semi-transparent pink hexahedral cell with a radial pyramid-fan subdivision on its right face, demonstrating 3D conformal mesh stitching for AMR boundaries in VTK/ParaView.
A simple refined face generating multiple pyramids in its interface

Finally the interface case requires us to fill up the spaces between refined faces and non-refined faces, which we do using tetrahedrons.

We check each edge of the face to see if it is refined or not.

If the edge is not refined, then we just create a tetrahedron using the edge, the face center and the voxel center. If the edge is refined, then we create tetrahedrons from each of the refined edge sections, the face center and the voxel center. This create a perfect paving between the single pyramid of non-refined faces and the multiple pyramids of the refined faces.

A 3D geometric render showing an internal pyramid subdivision on the right face of a semi-transparent pink hexahedral cell, illustrating 3D conformal mesh stitching across AMR resolution boundaries in VTK/ParaView.
An interface face generating tetrahedrons to pave the space between refined and non-refined faces.

All the voxels are replaced by an interface the same way, resulting in coherency over the whole overlapping AMR.

A 3D ParaView visualization of a stitched multi-block AMR volume displaying a scalar gradient mapped across hexahedral and triangulated transition elements, with a vertical color bar legend on the right for "Centroid X" ranging from -1.9e+00 to 3.8e-01.
The interface between two refinement level of a simple Overlapping AMR data

Data Recovery and Interpolation

While creating the interface as described above, we are constructing an unstructured grid containing many points and cells, from the input overlapping AMR.

In order to be able to compute anything on the resulting output, we need to keep the point data and cell data around, however figure out the right values is not as trivial as it looks like.

For cell data, it is pretty simple, we just recover the cell data from the Overlapping AMR grid containing the unstructured cells and copy it.

For point data, it is more complex, there are two possibilities, either the point is present in the input grid, or it is a new point we created.

For points present in the grid, we must recover the data from the most refined grid available, in most cases we know which it is but in some cases it is required to copy the data multiple times until the most refined grid is reached.

For new points, interpolating is required. It concerns face center and voxel center.

For face center, we use all the existing points in the face and compute a simple shepherd interpolation.

For voxel center, we recover all points that were ever added for the paving, including the face centers and compute a simple shepherd interpolation.

The results can sometimes look a bit jaded when contouring from a very rough grid to a refined grid but it is enough for the purpose of generating a continuous interface.

A 3D scientific visualization in ParaView showing scalar field rendering within a translucent wireframe bounding box, featuring color-coded scalar isosurfaces and two vertical legends for Centroid Magnitude and Gaussian Pulse.
Resulting contour where the interpolation at voxels centers is visible

Output construction

Reconstructing the output is quite trivial once we have the interface.

We just create a partitioned dataset containing the refined grids with the interface blanked out, and each of the unstructured grid interfaces for each refinement level.

A 3D ParaView visualization of a composite multi-block AMR cube dataset color-coded by vtkBlockColors, showing distinct coarse blocks (gray, red, green) joined seamlessly by dark blue triangulated interface stitching blocks.
The output of the interface filter

Contour

With the interface computed, computing the contour is pretty straightforward, we just run the standard contour filter on the whole partitioned dataset output while taking care of keeping the interface filter around to keep the benefit of the interface being already computed if only the contour value is changed when making multiple contour computation.

The has been integrated in ParaView and is available in ParaView nightly and will be available in ParaView v6.3.0

A close-up scientific visualization in VTK/ParaView showing adaptive triangular mesh refinement across hilly surface terrain in copper-orange with blue wireframe outlines.
Incorrect contour between grids of different refinement using ParaView 6.2.0
A close-up scientific visualization in VTK/ParaView showing adaptive triangular mesh refinement across hilly surface terrain in copper-orange with blue wireframe outlines.
Perfect contour between two grids of different refinement using ParaView nightly!

Perspectives

Distributed computation:

As of writing this, VTK does not support distributed AMR very well, so distributed AMR was not considered in the testing of this filter, however we are confident that it should work with minimal changes, and this is definitely something to address once distributed AMR is fully functional.

Other interpolation types:

While shepherd interpolation is enough in our case, it may be possible to use other kinds of interpolations to get better results.

Performance:

Most of the time is spent looking for which grid contains which points (vtkOverlappingAMR::FindGrid), which is already a call we try to reduce as much as possible thanks to the acceleration structure. So investigating how to make this call faster may be a possible performance improvement.

Acknowledgments

This work was funded by CEA, DAM, DIF, F-91297 Arpajon, France

Thanks for Jean Favre, CSCS and Håkon Strandenes, KMTurbulenz for providing test data.

Leave a Reply