Horizontal and vertical sections

The two section actors were the original bridge from traditional 2-D forecast products into the 3-D context: they reproduce the look of the corresponding 2-D map and cross-section products, but are embedded in the 3-D scene and can be moved interactively in real time. Both are built from the same three steps — place a triangle grid, sample the data field once per grid point, then render contours from the cached result.

Source code: src/actors/nwphorizontalsectionactor.cpp, src/actors/nwpverticalsectionactor.cpp, src/glsl/hsec_*.fx.glsl, src/glsl/vsec_*.fx.glsl.

Horizontal sections

Geometry. A horizontal section on a pressure surface p is a grid of triangles whose vertices sit at the horizontal positions of the data grid points and at the vertical position corresponding to p.

Sampling. The expensive part — finding the value of the field at p — is done once per grid point and cached, not once per fragment. A compute shader (hsec_verticalinterpolation.fx.glsl) is dispatched over the grid points and writes its result into a 2-D image (crossSectionGrid, format r32f). For each grid column it

  1. performs a binary search for the two levels k and k+1 enclosing p in that column, and

  2. interpolates the scalar value linearly in ln(p) between them.

The binary search is necessary because on hybrid sigma-pressure grids the levels enclosing a given pressure differ from column to column; see panel (a) of the figure in Sampling NWP grids on the GPU. Interpolating in ln(p) rather than in p follows the ECMWF FULLPOS interpolation routines.

The cached cross-section only has to be recomputed when the section pressure, the data field, or the region changes; not when the camera moves.

Filled contours are rendered by drawing the triangle grid and assigning a colour per fragment (hsec_filledcontours.fx.glsl). The scalar value comes from the cached cross-section texture with hardware interpolation, and the colour from the 1-D texture that the attached transfer function actor provides as a lookup table.

Line contours are generated by a marching squares implementation in a geometry shader (hsec_marching_squares.fx.glsl). Every cell of the cached cross-section texture is examined in parallel, and where the isoline crosses the cell, a line segment is emitted.

Wind barbs and arrows are generated in a geometry shader as well (hsec_vectorglyphs.fx.glsl, programs WindBarbs and Arrows): one input point per glyph position, with the geometry of the barb (shaft, pennants, full and half barbs) emitted on the GPU given the u and v components. Size and density can be scaled automatically with the zoom level, so that the glyph density stays legible while the user navigates.

Streambarbs are integrated on the CPU, inside the data pipeline. MHorizontalStreamBarbsDataSource produces an MStreamBarbsData item. Because this is an ordinary asynchronous pipeline request, the UI thread stays responsive while the streamlines are computed, and the result is cached and memory-managed like any other data item. Only the drawing happens on the GPU, from the resulting vertex buffers (streambarbs_draw.fx.glsl).

Vertical sections

A vertical section is drawn along a path through an arbitrary number of waypoints. The geometry is again a triangle grid: one column of vertices for each of a number of interpolation points along the path (their spacing is a user setting), and one row for each vertical level of the data grid.

Here the sampling happens in the vertex shader (vsec_interpolation_filledcontours.fx.glsl):

  • The vertical position of each vertex is computed for the level it belongs to. For pressure level data this is a lookup; for hybrid sigma-pressure data the surface pressure is interpolated to the vertex position and the level pressure is computed from the \(a_k\) / \(b_k\) coefficients. This is why the bottom of a vertical section follows the orography — panel (d) of the figure in Sampling NWP grids on the GPU.

  • The scalar value is interpolated horizontally on that level.

The result is cached in a 2-D texture as well, and refreshed when a waypoint is moved. Filled and line contours are then generated exactly as for the horizontal section.