@headless-three/renderer
v0.4.5
Published
Headless wgpu renderer for Three.js scenes in Node.js. Produces PNG or raw RGBA from a THREE.Scene + THREE.Camera without a browser or WebGL.
Maintainers
Readme
@headless-three/renderer
Headless wgpu renderer for Three.js scenes in Node.js.
This package exists for Node.js environments where WebGL is not available. You build or load a normal Three.js scene graph, pass the THREE.Scene or THREE.Object3D root and THREE.Camera to this package, and the native addon renders it with wgpu.
npm install @headless-three/renderer threeimport fs from 'node:fs'
import * as THREE from 'three'
import { render } from '@headless-three/renderer'
const scene = new THREE.Scene()
scene.background = new THREE.Color(0.04, 0.045, 0.05)
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xe84d3d })
scene.add(new THREE.Mesh(geometry, material))
const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100)
camera.position.set(2.5, 1.8, 3.2)
camera.lookAt(0, 0, 0)
const imageBuffer = render(scene, camera, {
width: 512,
height: 512,
})
fs.writeFileSync('render.png', imageBuffer)With local glTF/GLB assets, render the loaded root directly:
import fs from 'node:fs'
import * as THREE from 'three'
import { loadGltfFromFile, render } from '@headless-three/renderer'
const gltf = await loadGltfFromFile('./model.glb')
const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100)
camera.position.set(2, 1.5, 4)
camera.lookAt(0, 0, 0)
const imageBuffer = render(gltf.scene, camera, {
width: 1024,
height: 1024,
})
fs.writeFileSync('render.png', imageBuffer)For local Node.js glTF/GLB loading with external buffers or texture files, see the Node loader setup guide. The repository also includes runnable glTF and VRM/VRMA examples.
API
The module exports a convenience render(scene, camera, options) function and a reusable Renderer class:
import { Renderer } from '@headless-three/renderer'
const renderer = new Renderer()
const imageBuffer = renderer.render(scene, camera, { width: 512, height: 512 })Renderer.renderAsync(scene, camera, options) is a Promise-returning compatibility wrapper around the same scene-output contract.
Renderer.renderGpuFrame(scene, camera, options) returns a same-device GPU texture lease without RGBA readback when Renderer.getGpuOutputCapabilities().texture.supported is true. Native handles are borrowed bigint values and must be released deterministically. See the GPU-native output contract.
For real-time media, Renderer.createGpuFramePool() provides asynchronous,
bounded, reusable rgba8unorm, nv12-planes, and capability-gated
p010-planes output. Accepted work and blocking GPU completion run on a
renderer-owned thread rather than libuv's shared pool. The YUV forms are real
GPU-converted separate plane textures, not directly importable encoder-native
multi-planar surfaces. Consult capabilities and the linked contract before
using borrowed plane handles.
const pool = renderer.createGpuFramePool({
width: 1920,
height: 1080,
capacity: 3,
format: 'nv12-planes',
overflow: 'drop-newest',
})
const frame = await pool.render(scene, camera)
if (frame) {
const yTexture = frame.planeHandle(0)
const uvTexture = frame.planeHandle(1)
await consumeOnRendererDevice({ yTexture, uvTexture, planes: frame.planes })
// The consumer must first restore every plane to requiredStateOnRelease.
frame.completeExternalUse()
frame.release()
}These two handles are separate textures, not an encoder-native NV12 surface.
If a handle was requested, release() throws until external work is complete
and completeExternalUse() has acknowledged the required state restoration.
For CPU-only WebRTC sources, i420-planes plus renderI420() converts and
packs BT.601 limited-range I420 on the GPU and reads back only 1.5 bytes/pixel:
const target = Buffer.allocUnsafeSlow(width * height * 1.5)
const pool = renderer.createGpuFramePool({
width,
height,
capacity: 3,
format: 'i420-planes',
overflow: 'drop-newest',
})
const frame = await pool.renderI420(scene, camera, {}, target)
if (frame) rtcVideoSource.onFrame(frame)The optional target is reused without renderer allocation and must have byte
offset zero, an exact backing store, and the exact packed I420 length. This is
a production CPU fallback for APIs such as @roamhq/wrtc; that consumer still
copies into its internal WebRTC frame. Encoder-native DMA-BUF remains
capability-gated unsupported.
Renderer.sortObjects, Renderer.opaque, Renderer.transparent, Renderer.setOpaqueSort(fn), Renderer.setTransparentSort(fn), and the matching render() options (sortObjects, opaque, transparent, opaqueSort, transparentSort) control native draw-list sorting and bucket inclusion.
It also exports Node loader helpers:
loadGltfFromFile(filePath, options): loads local.gltfor.glbfiles from relative paths, absolute paths, orfile://URLs with encoded texture handlers and localfile://buffer support already installed.loadVrmFromFile(filePath, options): loads local VRM files with@pixiv/three-vrm'sVRMLoaderPluginregistered. The Pixiv package remains an optional dependency in your project.loadVrmAnimationFromFile(filePath, options): loads local VRMA files with@pixiv/three-vrm-animation'sVRMAnimationLoaderPluginregistered. The animation package remains optional.applyVrmAnimation(vrm, vrmAnimation, options): creates and applies a VRMA animation clip for still-frame rendering.createNodeGltfLoader(rootDir, options): creates a configuredGLTFLoaderbundle for advanced flows, including plugin registration throughoptions.configureLoader.createEncodedImageTextureLoader(rootDir, manager)/EncodedImageTextureLoader: aLoadingManagerimage handler for local PNG/JPEG/WebP files, PNG/JPEG/WebP data URIs, and PNG/JPEG/WebP Blob URLs.installLocalFileFetch(): a smallfile://fetch bridge for Three.jsFileLoaderwhen loading local external glTF buffers.resolveLocalAssetPath(url, rootDir): shared path resolution for local loader helpers.
Supported Three.js Surface
See the versioned compatibility matrix for the public support contract, known gaps, and platform package status. Scale-test budgets and platform notes are documented in docs/scale-budgets.md.
The current release supports:
- Scene roots as
THREE.SceneorTHREE.Object3D, plus perspective, orthographic, array, and cube cameras. - PNG output by default, raw RGBA output with
format: 'rgba', viewport and scissor rectangles, render targets, cube targets, readback helpers, selected color attachments, and depth texture readback. - Scene backgrounds from colors, CSS color strings, 2D textures, equirectangular textures, cube textures, and supported PMREM/CubeUV inputs.
- Output color-space controls, tone mapping controls, clear-color state, clipping planes, post-processing effects, and auxiliary render modes for mask, object-id, normal, and depth output.
THREE.Mesh,SkinnedMesh,InstancedMesh,InstancedBufferGeometry,BatchedMesh, sprites, points, lines, dashed lines, LOD, morph targets, geometry groups, vertex colors, frustum culling, layers, render ordering, and custom sort callbacks.- Common built-in materials including
MeshBasicMaterial,MeshLambertMaterial,MeshPhongMaterial,MeshStandardMaterial,MeshPhysicalMaterial,MeshNormalMaterial,MeshMatcapMaterial,MeshToonMaterial,MeshDepthMaterial,MeshDistanceMaterial,ShadowMaterial, sprite, point, line, and dashed-line materials. - PBR metallic/roughness lighting, physical material extensions, alpha testing, alpha hash, alpha-to-coverage, wireframe output, fog, blending, stencil state, local and global clipping, and material render-state validation.
- Raw texture data and encoded PNG/JPEG/WebP texture buffers, including common channel formats, color-space handling, UV transforms, selected UV channels, wrap modes, filters, generated mipmaps, explicit raw mipmaps on supported slots, anisotropy, canvas-like texture readback, and WebP helper probing.
- Ambient, directional, point, spot, hemisphere, rect-area, and light-probe lighting in Three.js r155+ physical light units, plus directional, spot, point, and cascaded directional shadows within the documented native shadow-layer budget.
- Image-based lighting from scene environments, reflection probes, and supported material
envMapinputs, including equirectangular, six-face cube, CubeUV, and packed PMREM/CubeUV sources, through a CPU port of the Three.js r180PMREMGeneratorfor standard and physical materials. - Lighting, sRGB output, and sRGB texture filtering follow Three.js r180
WebGLRenderer; a parity suite compares 42 scenes with Chrome WebGL output within 2-3 levels. - VRM and VRMA still-frame flows through the optional Pixiv loaders, CPU skinning, skeletal animation, and morph targets. Pixiv MToon surfaces retain authored shade colors, rim/matcap lighting, and extruded outlines; see the compatibility matrix for remaining limitations.
- WebGLRenderer/CommonRenderer compatibility state for many high-level renderer properties and no-op lifecycle methods that existing Three.js integrations probe.
- Custom WGSL fragment bodies through
material.userData.headlessThreeRenderer.fragmentWgsl.
ShaderMaterial, RawShaderMaterial, NodeMaterial, browser WebGL context access, real WebXR session binding, compressed texture decoding, live video texture readback, WebGPU compute, and direct low-level WebGL buffer or texture binding are outside the scene-oriented API unless the compatibility matrix documents a covered adapter path.
Invalid option containers, malformed Three.js-like objects, unsupported constants, incompatible texture formats, and unsupported renderer escape hatches fail with explicit errors instead of falling through to a browser-only path.
Native Packages
The main package depends on optional native packages for the current platform:
@headless-three/renderer-darwin-arm64@headless-three/renderer-darwin-x64@headless-three/renderer-linux-arm64-gnu@headless-three/renderer-linux-x64-gnu@headless-three/renderer-win32-x64-msvc
Install the main package, not the platform package directly.
