Stagecraft

Stagecraft is a three.js-like scene graph and WebGPU renderer for Ruby. It combines wgpu, larb, rgltf, and texel behind a compact Scene / Mesh / Material / Camera / Renderer API.

The renderer uses WebGPU zero-to-one depth, PBR metallic-roughness materials, HDR rendering with ACES tone mapping, frustum culling, transparent and opaque queues, MSAA, directional shadow maps, skinning, and glTF animation.

Requirements

  • Ruby 3.2 or newer
  • A WebGPU backend supported by the wgpu gem
  • SDL3 for the default window adapter

Installation

Add Stagecraft to your bundle:

bundle add stagecraft

Or install it directly:

gem install stagecraft

First scene

require "stagecraft"

app = Stagecraft::App.new(title: "Hello Stagecraft", width: 1280, height: 720)

scene = Stagecraft::Scene.new
scene.background = Stagecraft::Color.new(0.05, 0.06, 0.08)
scene.add(Stagecraft::Lights::Ambient.new(intensity: 0.25))

sun = Stagecraft::Lights::Directional.new(intensity: 3.0, cast_shadow: true)
sun.position.set(4, 8, 4)
sun.look_at(Larb::Vec3.new)
scene.add(sun)

cube = Stagecraft::Mesh.new(
  Stagecraft::Geometries.box(1, 1, 1),
  Stagecraft::Materials::PBR.new(
    base_color: "#e91e63",
    roughness: 0.4,
    metallic: 0.1
  )
)
cube.cast_shadow = true
scene.add(cube)

camera = Stagecraft::Cameras::Perspective.new(
  fov: 60,
  aspect: app.aspect,
  near: 0.1,
  far: 100
)
camera.position.set(0, 2, 5)
controls = Stagecraft::Controls::Orbit.new(camera, app.window)

app.run do |dt|
  controls.update(dt)
  cube.rotation.rotate_y!(dt)
  app.renderer.render(scene, camera)
end

App#run clamps delta time to 100 ms, polls the window at vsync cadence, and disposes its renderer and window when the loop ends.

glTF and animation

Loaders::GLTF accepts a .gltf / .glb path or an existing Rgltf::Document. It converts scenes, cameras, punctual lights, materials, textures, skins, and animation channels:

model = Stagecraft::Loaders::GLTF.load("assets/robot.glb")
scene.add(model.scene)

mixer = Stagecraft::Animation::Mixer.new(model.scene)
mixer.play(model.animations.first, loop: :repeat, fade_in: 0.25)

app.run do |dt|
  mixer.update(dt)
  app.renderer.render(scene, camera)
end

glTF LINEAR, STEP, and CUBICSPLINE tracks are supported. Multiple actions blend by weight; rotations use normalized quaternion blending.

Materials and textures

Stagecraft includes PBR, Unlit, and custom WGSL materials. Base color and emissive textures are uploaded as sRGB; metallic-roughness, normal, and occlusion textures stay linear. KHR texture transforms are carried into the material uniforms.

texture = Stagecraft::Texture.load(
  "assets/albedo.png",
  channels: 4,
  color_space: :srgb
)
material = Stagecraft::Materials::PBR.new(
  base_color_map: texture,
  metallic: 0.0,
  roughness: 0.7
)

Materials::Shader accepts a WGSL string or any object responding to #to_wgsl. Stagecraft injects frame and object bindings and packs the uniform hash according to WGSL alignment:

material = Stagecraft::Materials::Shader.new(
  wgsl: File.read("effect.wgsl"),
  uniforms: {
    tint: Stagecraft::Color.new("#7c4dff"),
    speed: 2.0
  }
)

Custom shaders own bind group 1. Frame data is group 0 and object / skin data is group 2. See the tutorial for the complete contract.

Headless rendering

An offscreen renderer returns a readable frame backed by a Texel::Image:

renderer = Stagecraft::Renderer.offscreen(width: 512, height: 512, msaa: 4)
frame = renderer.render(scene, camera)
Texel.save(frame.read_pixels, "frame.png")
renderer.dispose

Integration hooks

The renderer exposes device, queue, and surface_format. UI renderers such as imgui-ruby can append commands after the main pass:

renderer.on_after_render do |encoder, hdr_target_view|
  overlay.render(encoder, hdr_target_view, stats: renderer.stats)
end

PhysicsBinding connects any body exposing position and quaternion rotation. Materials::Shader accepts rlsl-like objects through #to_wgsl.

Resource lifetime and diagnostics

GPU resources are versioned and cached. Call dispose on mutable geometry and textures when they are no longer needed, and always dispose standalone renderers. App handles renderer shutdown automatically.

Set STAGECRAFT_DEBUG=1 to report live render statistics once per second and warn about cached resources remaining at renderer shutdown:

STAGECRAFT_DEBUG=1 bundle exec ruby examples/01_spinning_cube.rb

Window adapters

SDL3 is the portable default. window: :glfw is available when the application supplies the platform-specific WebGPU surface_factory, because glfw-ruby does not expose a portable WebGPU surface handle.

An existing object implementing the window adapter protocol can be passed as window:. It must provide poll_events, should_close?, drawable_size, create_surface(instance), and close.

Learn and explore

Use STAGECRAFT_SMOKE=1 to close a window example after one frame.

Development

bin/setup
bundle exec rake spec
bundle exec rake build

GPU-free unit tests cover scene traversal, bounds, culling, sorting, shader composition, uniform layout, glTF conversion, and animation. Offscreen WebGPU rendering can be used for platform integration tests.

License

Stagecraft is released under the MIT License.