Files
anomalyco__opentui/packages/web/src/content/docs/reference/three.mdx
T
2026-07-27 23:55:19 +02:00

263 lines
15 KiB
Plaintext

---
title: Three.js WebGPU
description: Render Three.js WebGPU scenes into OpenTUI buffers
order: 9
skill:
entry: true
intents: [three, threejs, webgpu, 3d, sprites, physics]
---
# 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
```bash
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:
```typescript
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
```typescript
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:
```typescript
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
```typescript
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 alias `fromFile(path)` load a Jimp-decoded `DataTexture`, vertically flip the source image, and return `null` after logging a load failure.
- `TextureUtils.createCheckerboard()`, `createGradient()`, and `createNoise()` create procedural textures. Their default size is 256 and they use nearest filtering with clamp-to-edge wrapping.
- `SpriteUtils.fromFile()` creates a Three `Sprite`; its default material parameters are `alphaTest: 0.1` and `depthWrite: true`.
- `SpriteUtils.sheetFromFile()` and `SheetSprite.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.
- `InstanceManager` allocates slots in one Three `InstancedMesh`; its `renderOrder` defaults to `0` and `frustumCulled` defaults to `false`.
### Sprite animation
- Types: `AnimationStateConfig`, `ResolvedAnimationState`, `AnimationDefinition`, `SpriteDefinition`.
- Classes: `SpriteAnimator`, `TiledSprite`.
- `SpriteAnimator.createSprite()` creates an instanced tiled sprite and `update(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 order `0`, and depth writing enabled.
- `TiledSprite` exposes transform, animation, playback, visibility, frame, and destruction controls.
### Particles and explosions
- `SpriteParticleGenerator` and `ParticleEffectParameters` provide 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, and `ExplosionHandle` implement GPU sprite explosions. `DEFAULT_EXPLOSION_PARAMETERS` is exported.
- `PhysicsExplodingSpriteEffect`, `PhysicsExplosionManager`, their parameter/data/handle types, and `DEFAULT_PHYSICS_EXPLOSION_PARAMETERS` implement 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
- `RapierRigidBody` and `RapierPhysicsWorld` adapt `@dimforge/rapier2d-simd-compat` bodies/worlds.
- `PlanckRigidBody` and `PlanckPhysicsWorld` adapt `planck` bodies/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:
```typescript
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: `THREE` namespace re-export.