Fast, Interactive Surface Rendering in .NET
ILNumerics is famous for its ability to generate beautiful 3D surface visualizations based on technical data. Today our surfaces support thousands of data driven applications in all industries. For all situations where the main surface class hits certain limits we have created FastSurface.
What's new in FastSurface?
In general, FastSurface creates very similar results to Surface. But, as the name suggests, it does so in a different way. While Surface is optimzed to create nice looking plots it can be slow for large data or when the data size changes frequently.
FastSurface brings the following features:
- Optimized for fast rendering - different internal data structures, different rendering model. You will recognize a significant speedup for rendering a single frame and a much higher responsiveness for user interaction compared to Surface.
- Optimized for low memory consumption - handling the rendering data in a much more compact way allows for significantly higher limits for the memory required for FastSurface. This allows to plot larger data out of the box before having to switch over to interpolation techniques and down-sampling for your data.
- Optimized for quick updates - interactivity and dynamic updates are possible for all drawing objects in ILNumerics Visualization Engine. FastSurface, however, was designed in a way which makes such updates at runtime especially efficient. Once created, changes to any property or to any combination of properties can be made with the least computational effort possible - again speeding up the surface.
It does also bring certain limitations:
- No wireframe - no, really. If you want a wireframe take Surface.
- Only one coloring mode - ony colormapped rendering is supported. If the need arises, one can realize all other coloring options nevertheless. See below for details.
- Lighting is currently not supported. This will be provided in a future version.
Besides the standard tag parameter there is no other data needed for the constructor: Fast surfaces are created empty. Afterwards, initial data are provided in the very same way as for updates to the existing plot object: via the FastSurface.Update() method.
Example: A simple fast surface from terrain data.
Note how the surface was created empty. A reference to it is fetched afterwards and the Update() function is used to configure the data and the colormap. Note also, that we need to explicitely call PlotCube.Reset() in order to configure the axes limits to show the whole scene. This is because we create the surface as empty and provide the data later. So we need to indicate the plot cube to adapt to the new data after updating the surface.
All parameters to Update() are optional. The surface retains the state of any ommited parameter and updates only those data which have really changed. This also effects updates to GPU buffers in case the OpenGL driver is used for rendering.
The Update() function provides two classes of parameters: positional parameters and coloring parameters. Positional parameters are responsible for defining the shape of the rendering grid and the positions of the grid vertex data in all three dimensions: X, Y, and Z. Coloring parameters control the color of the grid vertices – directly or indirectly: colormap, C, minmaxColorRange. Don't mix-up the latter one with the minLimit or maxLimit parameters! They relate to a certain performance optimization and are described below. Visit the APIDOC for Update() in order to learn all configuration options.
The optional parameters make the API of Update() extremely flexible. It is possible to configure the surface completely within a single call to Update(). Or arbitrary aspects of the surface can be modified sequentially.
One example of efficiently re-configuring a single parameter of FastSurface is found in the examples section. It modifies the minimum and maximum data range of the colormap of the surface. All other surface data stay the same. The example creates a sophisticated interactive visualization of colormapped terrain data with very few lines of code:
Besides the absence of a wireframe for the grid, FastSurface offers all options for data visualization and configuration as known from Surface - and more:
- Colormapped rendering.
- Option to accept scalar values for any positional parameter and for the color data C of all grid elements. The scalar value will replace the former values of all elements at once.
- X and Y can be set as grid vectors. On large, regular surfaces providing a single vector of grid elements instead of a large matrix with many redundant data can save significant memory.
- The color for every grid element can be configured individually by selecting it from the colormap. The data value is determined by the Z coordinate or directly provided as C matrix. The mapping from the grid element color value to the corresponding color in the colormap is controlled by the minmaxColorRange tuple.
- FastSurface supports colorbars. Just put the colorbar as a child of FastSurface.
- The option to define individual X, Y and Z elements allows the creation of arbitrary surfaces, including parametric surfaces.
Next to the special configuration options, FastSurface allows access to the all objects of the underlying scene graph. The Fill shape is a triangle strip shape object and serves as the main visible shape of FastSurface. This opens up all configuration options for general shapes of ILNumerics Visualization Engine, including access to its internal vertex buffers (Positions, Colors, Indices, Normals), the Color property to set the surface into solid color mode, all mouse events and the option to enable lighting for the surface. Furthermore, FastSurface itself derives from Group, hence offers all configuration options of common group objects.
Example: Create a simple fast surface, fill with transparent, colormapped colors.
As mentioned above FastSurface() is better suited for visualizing large data than Surface(). But even FastSurface() could not cope with gigantic data up until now.
Too large data might prohibit you from quickly rotating, enlarging or minimizing your surface plot. To see if it is recommended to downscale your data, take a look at the property downscalingRecommended. If downscalingRecommended is true and you set the input parameter performDownscaling to true, a suitable number of grid points will be computed.
If you start rotating, minimizing or enlarging your surface plot now, the number of grid points will change according to the current view. As you enlarge the surface plot, the number of grid points will increase, since it is very likely that you are interested in looking at details. While a lower number of grid points is sufficient for a surface plot viewed from further away. All of this happens automatically in the background, allowing you to focus on other things.
However, you can always take a look at both the downscaled number of grid points and data. But also the original data and its size are available.
To use this feature, you must assign the input parameter Z. The underlying data must be a (m x n) matrix. Each dimension must be equal to or larger than 2.
There is not much to say here. FastSurface brings excellent performance out of the box. Its implementation profits from 10 years of experience in creating efficient algorithms in .NET. As a user of FastSurface one does not need to keep many rules in mind. However, here come a few hints which may help to prevent from redundant operations:
- Don't provide any parameters without the need to do so! Every parameter which was provided will cause the surface object to update its corresponding data / buffers. These operations will be done, regardless if the data provided have really changed or not.
- After updating any positional parameters (X, Y, or Z) or changing the shape / size of the surface grid, the surface needs to recompute the bounding box for the new data. This operation requires to iterate the full FastSurface.Fill.Positions buffer. If the data of the new bounding box after updating is known it is possible to save this iteration in the call to Update(). The minLimit and maxLimit parameters can be used for that.