Skip to main content

Structured volume ray marching

StructuredVolumeRenderer renders regularly sampled scalar and vector volumes. Import it from the focused scene renderer entry point:

import {StructuredVolumeRenderer} from '@luma.gl/scene/raymarch';

The API is experimental and currently requires WebGPU. The scene entry point is a stable facade; the implementation remains in @luma.gl/experimental while the scene renderer taxonomy evolves.

Sources​

All configured channels share construction-time dimensions and local bounds. Scalar channels use float32 storage buffers or r16float/r32float 3D textures. Vector channels use WGSL-aligned float32x4 storage rows or rgba16float/rgba32float 3D textures; xyz contains the vector and w is padding. Buffer offsets must be aligned to four bytes for scalars and 16 bytes for vectors.

const renderer = new StructuredVolumeRenderer(device, {
dimensions: [40, 40, 40],
bounds: {minimum: [-1, -1, -1], maximum: [1, 1, 1]},
scalar: {type: 'buffer', format: 'float32', buffer: scalarBuffer},
vector: {type: 'buffer', format: 'float32x4', buffer: vectorBuffer}
});

Compatible resources can be rebound with setSources(). Dimensions, backing type, and channel format are renderer invariants, so changing any of them requires a new renderer. Buffer and texture variants both use explicit eight-corner trilinear interpolation for matching sampling behavior.

Rendering​

scalar, vector, and hybrid modes combine typed transfer styles with optional solid 3D arrow glyphs. Scalar transfer functions can be sequential or signed negative/neutral/positive maps. Vectors can use direction colors or a constant color. Value, magnitude, density, and opacity scales are independent controls.

The caller owns command encoding, the render pass, and submission:

renderer.prepare(device.commandEncoder, {
mode: 'hybrid',
inverseViewProjectionMatrix,
cameraPosition,
viewport: [0, 0, width, height],
sampleCount: 72,
scalarStyle: {transferFunction: 'signed', valueScale: 0.5, densityScale: 0.2},
vectorStyle: {colorMode: 'direction', magnitudeScale: 1, densityScale: 0.15},
glyphs: {
enabled: true,
gridDimensions: [6, 6, 6],
lengthRange: [0.05, 0.2],
shaftRadius: 0.008,
headRadius: 0.025
}
});

const renderPass = device.beginRenderPass({framebuffer});
renderer.draw(renderPass);
renderPass.end();
device.submit();

prepare() updates uniforms and bindings but never submits. draw() applies the configured viewport and scissor before recording a premultiplied-transparent draw. Multiple renderers can therefore share one encoder and pass for linked views.

Coordinates and compositing​

The inverse view-projection matrix defines world-space camera rays. A model matrix places the local volume in the world; the shader transforms each ray into volume space before intersecting local bounds. Fixed, optionally jittered samples composite front-to-back and stop once opacity is nearly saturated. Bounds rendering is optional.

Sample count, volume resolution, transfer density, and glyph-grid dimensions are the primary performance controls. The renderer does not yet depth-integrate with opaque scene geometry, skip empty regions adaptively, or light the volume. Higher-order reconstruction, arbitrary caller WGSL, ANARI SpatialField/Volume objects, and adaptive ray marching are follow-ups.

Support detection​

Use getStructuredVolumeSupport(device) before construction when an application can run on WebGL 2. It reports WebGPU support without allocating resources.