viewport-lib
viewport-lib is a gpu-accelerated 3D viewport library for rust. The library gives you a renderer, camera, picking-tools, light sources, gizmos and scene primitives.
Whichever gui library you choose to use (winit, eframe, Iced, Slint, etc.), the integration model stays the same in each case:
viewport-libowns rendering and viewport-side maths;- your application owns the window, event loop, and tool state;
WARNING: viewport-lib has only recently been extracted as a stand-alone library from a separate project and the API is still somewhat unstable.
Core features
- mesh, point cloud, polyline, and volume rendering
- directional lighting, shadow mapping, and post-processing
- material shading, normal maps, transparency, outlines, and x-ray views
- clip planes, section views, scalar coloring, and colormaps
- arcball camera, view presets, framing, and smooth camera animation
- CPU/GPU picking, rectangle selection, transform gizmos, and snapping
- annotations, axes indicators
Examples
The examples/ directory contains working integrations for several GUI frameworks.
- winit-viewport: the most basic setup: raw
winit+wgpuwith no GUI framework. Start here if you want to understand the minimal integration. - eframe-viewport: a straightforward example of embedding the viewport inside an
egui/eframeapplication usingegui_wgpucallback resources. - winit-showcase: several more advanced rendering options across multiple showcases.
- winit-primitives: demonstrates the built-in geometry primitives.
Other examples: iced-viewport, slint-viewport, gtk4-viewport, qt-viewport
Run examples with:
cargo run --release --example winit-viewport
Quick start
In a typical app you will need to use both the renderer (to build and submit a FrameData) and the input handler to define and handle keys and events.
Rendering
use glam::{Mat4, vec3};
use viewport_lib::{
Camera,
CameraFrame,
FrameData,
SceneFrame,
SceneRenderItem,
primitives,
};
// Upload the cube primitive mesh once at startup
let mesh_index = renderer.resources_mut().upload_mesh_data(&device, &primitives::cube(1.0))?;
// Build a frame each render tick
let camera = Camera::default();
let model = glam::Mat4::from_translation(glam::vec3(1.0, 2.0, 0.0));
let item = SceneRenderItem {
mesh_index,
model: model.to_cols_array_2d(),
..SceneRenderItem::default()
};
let fd = FrameData::new(
CameraFrame::from_camera(&camera, [width, height]),
SceneFrame::from_surface_items(vec![item]),
);
renderer.prepare(&device, &queue, &fd);
// then call the renderer -- this will depend on what GUI you are using
// renderer.paint_to(&mut render_pass, &fd);
Input handling
OrbitCameraController is one of the available built-in controllers. You can also build your own controller directly on top of ViewportInput and ViewportBinding if you need different navigation behaviour - but OrbinCameraController is a good starting point. Push events each frame, then call apply_to_camera to orbit/pan/zoom and get back an ActionFrame for the rest of your input logic.
use ;
// --- app state ---
let mut orbit = new;
let mut manip = new;
// prime the controller before the first frame
orbit.begin_frame;
// --- each frame ---
// 1. drive camera navigation; get the action frame for this frame
let frame = if manip.is_active else ;
// 2. drive the manipulation controller
let ctx = ManipulationContext ;
match manip.update
// 3. reset for next frame
orbit.begin_frame;