Skip to content

v0.16.0

Choose a tag to compare

@github-actions github-actions released this 30 May 17:43
· 95 commits to master since this release

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 GpuPlugin trait, registered through ViewportRuntime::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: bool and Material::matcap_id: Option<MatcapId>, etc. with a single Material::shading_model: ShadingModel field.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::EmptyMesh when vertices or indices is empty.
  • upload_gaussian_splats: ViewportError::InvalidGaussianSplatData when the splat list is empty or when positions, scales, rotations, and opacities differ in length.
  • upload_environment_map: ViewportError::InvalidTextureData when pixels.len() does not equal width * 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 -> ItemSettings and standardised the settings across all first-class scene item types.
  • Standardised pick_id: PickId across all item types. No more id or pickID.
  • Added pick_id and selected to ItemSettings so 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: when false, the item is skipped in the shadow pass. Wired in both the direct shadow loops and the GPU-driven indirect path (via a new cast_shadows slot on the per-instance AABB plus a shadow_pass flag on FrustumUniform).
  • receive_shadows: when false, the lit mesh fragment shader treats the fragment as unshadowed regardless of whether the scene's directional light has a shadow map. Read in mesh.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

  • CameraFrustumItem removed from the public API. Build frustum wireframes directly using PolylineItem with explicit quad strips for near/far planes and lateral edges. SceneFrame::camera_frustums is removed; push PolylineItem values to SceneFrame::polylines instead. Build your own frustum!
  • SurfaceLICItem removed from the public API. SurfaceLIC 'objects' belong to surface meshes and so that is where they have to be defined. Set SceneRenderItem::lic = Some(LicOverlay { vector_attribute, config }).