v0.16.0
GPU compute plugin hook
Plugins can now run their own GPU work each frame and feed the result straight into the standard mesh pipeline. Before, plugins could only update the scene CPU-side; anything that wanted to run a compute shader or hand the renderer a GPU buffer had to fork the lib.
- A new
GpuPlugintrait, registered throughViewportRuntime::with_gpu_plugin. Plugins run in priority order each frame, between the scene step and the renderer's own work. - Position and normal override buffers: a plugin can hand the renderer a GPU buffer of per-vertex positions or normals, and the standard mesh pipeline reads from it instead of the vertex buffer. No CPU round-trip and no re-upload each frame.
- Override and skinning compose: a skinned mesh can be driven from a GPU simulation at the bind-pose stage, with skinning still applied on top.
- A post-paint hook is available for screen-space effects that need to sample the rendered color, depth, or pick-id targets.
Volumetric effects. ScatterVolume (fog, smoke, clouds, fire)
A new scene item, the scatter volume, renders ray-marched participating media: atmospheric fog, smoke columns, cloud layers, fire, magic effects. Each volume is a box or a sphere placed in the scene with a density, a colour, and a handful of look knobs; the renderer composites visible volumes onto the scene every frame with no upload step. Up to 16 volumes can overlap a single pixel.
Scene-graph lights
Lights are now scene-graph nodes rather than per-frame configuration data.
Materials and shading
ShadingModel enum
- Replaced
Material::use_pbr: boolandMaterial::matcap_id: Option<MatcapId>, etc. with a singleMaterial::shading_model: ShadingModelfield.Matcap(id);`
Toon shading
A new ShadingModel::Toon variant that quantises the lighting into hard bands, with optional banded specular highlights and parameter knobs for band count, ramp smoothness, and specular sharpness. Runs through the standard opaque and OIT mesh pipelines, so skinned meshes and transparent surfaces both pick it up without extra wiring. The variant carries silhouette outline parameters (thickness and colour) but the silhouette pass that consumes them is a separate follow-up — at the moment a toon material renders the cel-shaded interior without the dark outline around the silhouette.
Flat shading
A new ShadingModel::Flat variant for surface and volume meshes. Runs the normal lighting block but replaces the per-vertex normal with a per-fragment geometric normal, so the polygon facets of the underlying triangulation are visible. Fills the gap between fully lit and fully unlit, where unlit alone tends to flatten geometry into a featureless blob.
Decals
Screen-space decal projection: place a texture onto any opaque surface without modifying the receiver mesh. The renderer reads the scene depth buffer each frame, reconstructs world position per pixel, and projects the decal onto whatever geometry lies inside the projection volume. No per-receiver setup is needed.
- Normal map support: can appear as craters or embossed marks rather than flat stickers.
- Three blend modes: replace (alpha), multiply (darkens the receiver, good for grime and weathering), and additive (brightens the receiver, good for fire, sparks, and glows). Stacking multiple additive decals accumulates correctly.
- Roughness and metallic overrides.
- Draw order: a sort key controls which decals composite on top of others within a frame.
- Animated decals: UV offset and scale can be driven by a scroll animation without per-frame updates from the application. Decals can also be given a lifetime and a fade-out duration; the scene removes them automatically when they expire.
- Receiver masking: individual scene nodes can opt out of receiving decals entirely.
- Emissive channel: scalar multiplier.
- Soft edges: edge fade parameter.
- Tri-planar projection: samples the texture from all three local axes and blends by surface normal, avoiding UV stretching on corners and non-planar surfaces.
- Cylindrical projection: wraps a decal around a cylindrical surface using angle and axial position as UV coordinates. Works on both the outside of a column and the inside of a tube.
Improvements
Improved item data upload responses
upload_mesh, upload_gaussian_splats, and upload_environment_map now return ViewportResult
Error variants returned:
upload_mesh:ViewportError::EmptyMeshwhenverticesorindicesis empty.upload_gaussian_splats:ViewportError::InvalidGaussianSplatDatawhen the splat list is empty or whenpositions,scales,rotations, andopacitiesdiffer in length.upload_environment_map:ViewportError::InvalidTextureDatawhenpixels.len()does not equalwidth * height * 4.
prepare_scene / prepare_viewport call ordering is now enforced at compile time
OwnedPath::prepare_scene and PassPath::prepare_scene now return a ScenePreparedToken. prepare_viewport on both path types now requires &ScenePreparedToken as a parameter (between queue and id), making it a compile error to call prepare_viewport without a prior prepare_scene in the same frame. Migration: capture the token from prepare_scene and pass &token to each prepare_viewport call.
// Before
renderer.pass().prepare_scene(device, queue, frame, &scene_fx);
renderer.pass().prepare_viewport(device, queue, vp_id, frame);
// After
let token = renderer.pass().prepare_scene(device, queue, frame, &scene_fx);
renderer.pass().prepare_viewport(device, queue, &token, vp_id, frame);ItemSettings extends and replaces AppearanceSettings on all scene item types
All renderable item types now express pick identity and selection state through a single settings: ItemSettings field. The former id: u64, pick_id: u64, selected: bool, and appearance: AppearanceSettings top-level fields are removed.
- Renamed
AppearanceSettings->ItemSettingsand standardised the settings across all first-class scene item types. - Standardised
pick_id: PickIdacross all item types. No moreidorpickID. - Added
pick_idandselectedtoItemSettingsso that both are standard and universal across all vp-lib item types.
ItemSettings flags now behave consistently on every item type
Per-item hidden, unlit, and opacity previously honoured by some types and silently ignored by others. All three now follow the same contract everywhere: if the type has the underlying mechanism (lighting, alpha output, draw enumeration) the flag drives it; if it doesn't, the flag is a documented no-op. ItemSettings flags now behave consistently on every item type.
Shadow opt-out on ItemSettings
ItemSettings gains two new fields, both defaulting to true:
cast_shadows: whenfalse, the item is skipped in the shadow pass. Wired in both the direct shadow loops and the GPU-driven indirect path (via a newcast_shadowsslot on the per-instance AABB plus ashadow_passflag onFrustumUniform).receive_shadows: whenfalse, the lit mesh fragment shader treats the fragment as unshadowed regardless of whether the scene's directional light has a shadow map. Read inmesh.wgsl,mesh_instanced.wgsl, and the skinned variants.
Fixes
Light direction convention unified across non-mesh shaders
mesh.wgsl treats LightSource.pos_or_dir as the surface-to-light direction (matching the doc on LightSource::default()), but some of the item newer outline shaders -- glyph, streamtube, ribbon, GPU implicit, and GPU marching cubes -- were negating it and treating it as a light-travel direction. The two conventions are opposite. With a directional light pointing upward (light source above the scene), meshes lit from above as intended, but glyphs, streamtubes, ribbons, implicit surfaces, and marching-cubes surfaces lit from below. All five shaders now match the mesh convention, so a single LightSource.direction produces a coherent response across every lit type in the same scene.
Fixed: GPU position and normal overrides were silently ignored on most scenes set_position_override_buffer and set_normal_override_buffer would accept the binding but the renderer would draw the mesh at its rest position anyway, because the override-bound item was being routed through a code path that did not know about the override. Items with an override now go through the per-object pipeline and the override actually takes effect. A regression test renders an override that pushes every vertex off-screen and fails if the mesh stays visible.
Breaking changes
CameraFrustumItemremoved from the public API. Build frustum wireframes directly usingPolylineItemwith explicit quad strips for near/far planes and lateral edges.SceneFrame::camera_frustumsis removed; pushPolylineItemvalues toSceneFrame::polylinesinstead. Build your own frustum!SurfaceLICItemremoved from the public API. SurfaceLIC 'objects' belong to surface meshes and so that is where they have to be defined. SetSceneRenderItem::lic = Some(LicOverlay { vector_attribute, config }).