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.