Three.js WebGPU
@opentui/three connects Three.js’s WebGPU renderer to OpenTUI buffers. Use ThreeRenderable to place a scene inside the normal renderable tree, or use ThreeCliRenderer directly when the scene should draw into a buffer you manage.
Bun-only runtime
bun add @opentui/three
The package declares Bun >= 1.3.0 and imports bun-webgpu directly. It does not declare Node support, and OpenTUI’s Node examples bundle disables the Three examples. Treat both @opentui/three entrypoints as Bun-only.
The package exports only:
@opentui/three@opentui/three/runtime-modules
It does not provide React or Solid component subpaths. ThreeRenderable is the OpenTUI integration surface.
ThreeRenderable
This example follows the package’s rotating-cube examples while using the THREE namespace re-export:
import { RGBA, createCliRenderer } from "@opentui/core"
import { THREE, ThreeRenderable } from "@opentui/three"
const renderer = await createCliRenderer({ targetFps: 60 })
renderer.start()
const scene = new THREE.Scene()
scene.add(new THREE.AmbientLight(new THREE.Color(0.35, 0.35, 0.35), 1))
const light = new THREE.DirectionalLight(new THREE.Color(1, 0.95, 0.9), 1.2)
light.position.set(2.5, 2, 3)
scene.add(light)
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshPhongMaterial({ color: new THREE.Color(0.25, 0.8, 1) }),
)
scene.add(cube)
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100)
camera.position.set(0, 0, 3)
const view = new ThreeRenderable(renderer, {
width: "100%",
height: "100%",
scene,
camera,
renderer: {
focalLength: 8,
alpha: true,
backgroundColor: RGBA.fromValues(0, 0, 0, 0),
},
})
renderer.root.add(view)
renderer.setFrameCallback(async (deltaMs) => {
cube.rotation.x += 0.6 * (deltaMs / 1000)
cube.rotation.y += 0.4 * (deltaMs / 1000)
})
Options and defaults
interface ThreeRenderableOptions extends RenderableOptions<ThreeRenderable> {
scene?: THREE.Scene | null
camera?: THREE.PerspectiveCamera | THREE.OrthographicCamera
renderer?: Omit<ThreeCliRendererOptions, "width" | "height" | "autoResize">
autoAspect?: boolean
}
| Option | Default | Description |
|---|---|---|
scene | null | Scene drawn by the renderable |
camera | engine default camera | Perspective or orthographic active camera |
renderer | engine defaults | Three renderer settings excluding size and auto-resize |
autoAspect | true | Update a perspective camera’s aspect on layout resize |
inherited live | true | Keep the CLI render loop live unless explicitly disabled |
inherited buffered | forced true | Draw through the renderable’s frame buffer |
The nested renderer background defaults to opaque black. ThreeRenderable forces its engine’s autoResize off because layout resize events call setSize() directly.
Lifecycle and API
ThreeRenderable requires a real CliRenderer context. It registers a CLI frame callback at construction, but does not initialize WebGPU until it has a scene, a frame buffer, and a draw to perform. Initialization failure is logged once and later frames do not retry it. Concurrent draws for the same renderable are skipped.
On resize, positive dimensions update the engine. With autoAspect: true, perspective cameras receive the renderable’s display-aware aspect ratio and updateProjectionMatrix(); orthographic camera bounds are not changed automatically.
Public members:
| Member | Description |
|---|---|
aspectRatio | Current display-aware renderable aspect ratio |
renderer | Underlying ThreeCliRenderer |
getScene() / setScene(scene) | Read or replace the scene |
getActiveCamera() / setActiveCamera(camera) | Read or replace the active camera |
setAutoAspect(enabled) | Enable or disable perspective aspect updates |
Destroying the renderable removes its frame callback, destroys its ThreeCliRenderer, and then performs normal renderable cleanup.
ThreeCliRenderer
Use ThreeCliRenderer directly when you need to choose the destination OptimizedBuffer on every frame:
import { RGBA } from "@opentui/core"
import { THREE, ThreeCliRenderer } from "@opentui/three"
const engine = new ThreeCliRenderer(renderer, {
width: renderer.terminalWidth,
height: renderer.terminalHeight,
focalLength: 8,
backgroundColor: RGBA.fromValues(0, 0, 0, 1),
})
await engine.init()
engine.setActiveCamera(camera)
renderer.setFrameCallback(async (deltaMs) => {
await engine.drawScene(scene, renderer.nextRenderBuffer, deltaMs / 1000)
})
Unlike ThreeRenderable, direct use requires an explicit await engine.init() before canvas-dependent methods such as screenshots, supersampling configuration, or actual scene rendering. The engine registers for CliRenderer destruction and destroys itself with the host renderer; call destroy() yourself when ending its lifetime earlier.
Options and defaults
interface ThreeCliRendererOptions {
width: number
height: number
focalLength?: number
backgroundColor?: RGBA
superSample?: SuperSampleType
alpha?: boolean
autoResize?: boolean
libPath?: string
}
| Option | Default | Description |
|---|---|---|
width, height | required | Output dimensions in terminal cells |
focalLength | omitted | If omitted, the default perspective camera uses a 1-degree FOV; otherwise FOV is derived from output height and focal length |
backgroundColor | opaque black | Three renderer clear color |
superSample | SuperSampleType.GPU | "none", "gpu", or "cpu" |
alpha | false | Enable alpha in the Three WebGPU renderer |
autoResize | true | Follow host CliRenderer resize events |
libPath | - | Passed to bun-webgpu global setup |
When CPU or GPU supersampling is active, internal render dimensions are twice the output width and height. The default supersampling algorithm is SuperSampleAlgorithm.STANDARD; the alternative is PRE_SQUEEZED.
The engine’s default camera is a perspective camera at (0, 0, 3), looking at the origin, with near 0.1 and far 1000. CELL_ASPECT_RATIO, when present, overrides its computed aspect ratio. Otherwise it uses the CLI renderer’s pixel resolution when available, then falls back to terminal width divided by twice terminal height.
After initialization, the Three renderer uses NoToneMapping and LinearSRGBColorSpace.
Methods
| Method | Description |
|---|---|
init() | Create the WebGPU device, CLI canvas, and Three WebGPU renderer |
drawScene(scene, buffer, deltaTime) | Render the active camera into an OptimizedBuffer |
setActiveCamera(camera) / getActiveCamera() | Manage the active camera |
setBackgroundColor(color) | Change the Three clear color |
setSize(width, height, forceUpdate = false) | Resize output, canvas, viewport, and camera projection |
toggleSuperSampling() | Cycle none -> CPU -> GPU -> none |
getSuperSampleAlgorithm() / setSuperSampleAlgorithm(value) | Read or change the supersampling algorithm |
saveToFile(path) | Save the current canvas texture through Jimp |
toggleDebugStats() | Toggle renderer timing text |
renderStats(buffer) | Draw current timing values into a buffer |
destroy() | Remove resize/debug listeners and dispose the canvas and Three renderer |
Concurrent drawScene() calls are not supported; the implementation warns and skips the overlapping draw. The host renderer’s debug-overlay toggle also controls Three timing stats.
Texture and sprite helpers
The root package exports these additional surfaces:
Textures and basic sprites
TextureUtils.loadTextureFromFile(path)and aliasfromFile(path)load a Jimp-decodedDataTexture, vertically flip the source image, and returnnullafter logging a load failure.TextureUtils.createCheckerboard(),createGradient(), andcreateNoise()create procedural textures. Their default size is 256 and they use nearest filtering with clamp-to-edge wrapping.SpriteUtils.fromFile()creates a ThreeSprite; its default material parameters arealphaTest: 0.1anddepthWrite: true.SpriteUtils.sheetFromFile()andSheetSprite.setIndex()address a horizontal sprite sheet.
Resources and instancing
- Types:
ResourceConfig,SheetProperties,InstanceManagerOptions,MeshPoolOptions. - Classes:
MeshPool,InstanceManager,SpriteResource,SpriteResourceManager. SpriteResourceManager.createResource({ imagePath, sheetNumFrames })loads/caches a texture and creates a sheet resource.InstanceManagerallocates slots in one ThreeInstancedMesh; itsrenderOrderdefaults to0andfrustumCulleddefaults tofalse.
Sprite animation
- Types:
AnimationStateConfig,ResolvedAnimationState,AnimationDefinition,SpriteDefinition. - Classes:
SpriteAnimator,TiledSprite. SpriteAnimator.createSprite()creates an instanced tiled sprite andupdate(deltaTime)advances all managed sprites.- Animation defaults are frame duration 100 ms, frame offset 0, loop enabled, initial frame 0, and no horizontal or vertical flip.
- Sprite defaults are generated IDs, scale
1, maximum 1024 instances, render order0, and depth writing enabled. TiledSpriteexposes transform, animation, playback, visibility, frame, and destruction controls.
Particles and explosions
SpriteParticleGeneratorandParticleEffectParametersprovide instanced sprite particles with explicit capacity, lifetime, origins, velocity, angular velocity, and spawn radius. Optional defaults are resolved by its implementation; required effect fields have no package-wide defaults.ExplodingSpriteEffect,ExplosionManager,ExplosionEffectParameters, creation/recreation data, andExplosionHandleimplement GPU sprite explosions.DEFAULT_EXPLOSION_PARAMETERSis exported.PhysicsExplodingSpriteEffect,PhysicsExplosionManager, their parameter/data/handle types, andDEFAULT_PHYSICS_EXPLOSION_PARAMETERSimplement the physics-backed variant.
The regular explosion default is a 5x5 grid lasting 2000 ms with strength 5, gravity 9.8, and fade-out enabled. The physics default is a 5x5 grid lasting 3000 ms with explosion force 25, torque strength 15, and fade-out enabled. Import the exported default objects for the complete current parameter sets instead of duplicating them.
Physics adapters
RapierRigidBodyandRapierPhysicsWorldadapt@dimforge/rapier2d-simd-compatbodies/worlds.PlanckRigidBodyandPlanckPhysicsWorldadaptplanckbodies/worlds.- Both dependencies are optional package dependencies.
The shared PhysicsWorld, PhysicsRigidBody, and descriptor interfaces live in an internal module and are not re-exported from @opentui/three. Do not rely on importing those interface names from the package root.
Low-level canvas and Three namespace
CLICanvas is the bun-webgpu canvas/readback implementation used by ThreeCliRenderer; it is exported along with SuperSampleAlgorithm. The root also exports THREE, a namespace containing the installed three package.
Runtime-loaded plugins
@opentui/three/runtime-modules exports a side-effect-free module map for OpenTUI’s runtime plugin support:
import { ensureRuntimePluginSupport } from "@opentui/core/runtime-plugin-support/configure"
import { runtimeModules as threeRuntimeModules } from "@opentui/three/runtime-modules"
ensureRuntimePluginSupport({
additional: threeRuntimeModules,
})
The map contains the @opentui/three root module. It does not provide separate mappings for three, three/webgpu, or three/tsl.
Public export groups
The root export groups are:
- Rendering:
ThreeRenderable,ThreeRenderableOptions,ThreeCliRenderer,ThreeCliRendererOptions,SuperSampleType. - Canvas:
CLICanvas,SuperSampleAlgorithm. - Textures/sprites:
TextureUtils,SpriteUtils,SheetSprite. - Resource pools:
MeshPool,InstanceManager,SpriteResource,SpriteResourceManager, and their option/config types. - Animation:
SpriteAnimator,TiledSprite, and animation/sprite definition types. - Effects: sprite particle, regular explosion, and physics explosion classes, handles, data, parameters, and default parameter objects.
- Physics adapters: Rapier and Planck world/body wrappers.
- Three.js:
THREEnamespace re-export.