This renderer accepts normal Three.js scene objects, but Three.js loaders still need Node-aware file and image loading. Browser-oriented examples often rely on DOM image elements, object URLs, or WebGL upload behavior that is not available in a plain Node process.
Use loadGltfFromFile() for local .gltf or .glb files. Its filePath
argument accepts relative paths, absolute paths, and file:// URLs. It reads
bytes from disk, installs the local file:// fetch bridge used by Three.js
FileLoader, and registers encoded PNG/JPEG/WebP image handlers so external
files, data URI images, and embedded GLB bufferView images expose
renderer-supported buffers.
import fs from 'node:fs/promises'
import * as THREE from 'three'
import {
loadGltfFromFile,
render,
} from '@headless-three/renderer'
const gltf = await loadGltfFromFile('./model.gltf')
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 })
await fs.writeFile('render.png', imageBuffer)If you need custom loader plugins, register them through the configuration hook before the file is parsed:
import { loadGltfFromFile } from '@headless-three/renderer'
import { VRMLoaderPlugin } from '@pixiv/three-vrm'
const gltf = await loadGltfFromFile('./avatar.vrm', {
configureLoader(loader) {
loader.register((parser) => new VRMLoaderPlugin(parser))
},
})For VRM avatars, loadVrmFromFile() installs @pixiv/three-vrm's
VRMLoaderPlugin for you. Pass the plugin constructor explicitly when you
already imported the Pixiv package and want to avoid an extra dynamic import:
import { VRMLoaderPlugin } from '@pixiv/three-vrm'
import { loadVrmFromFile } from '@headless-three/renderer'
const gltf = await loadVrmFromFile('./avatar.vrm', { VRMLoaderPlugin })
const vrm = gltf.userData.vrmFor VRMA clips, use loadVrmAnimationFromFile() with
@pixiv/three-vrm-animation installed. Use applyVrmAnimation() to create a
VRMA clip from either direct VRM/VRMA objects or the glTF wrappers returned by
the local VRM helpers, seek an animation mixer to a still-frame time with
setTime() or the update() fallback, and update the avatar before rendering.
Wrapper inputs use userData.vrmAnimations[0] by default and accept
animationIndex for multi-animation VRMA files. Pass updateDelta to control
the VRM update step, or updateVrm: false when the caller will update the
avatar manually.
For fully custom loading flows, createNodeGltfLoader(rootDir) returns the
configured { loader, manager, encodedImages } bundle so callers can add more
handlers or reuse the loader directly. Loader rootDir values accept relative
paths, absolute paths, and file:// directory URLs, and the returned
GLTFLoader has its path set to that root so loader.load('model.gltf')
resolves local relative assets without additional caller setup. The helper also
installs the narrow WebP Image support probe that Three.js GLTFLoader uses
for EXT_texture_webp capability detection; image bytes are still loaded
through the encoded-buffer handlers rather than a DOM image decoder.
Local helper paths are normalized through resolveLocalAssetPath(url, rootDir).
Relative paths resolve under relative, absolute, or file:// rootDir
values, POSIX absolute paths and Windows drive-letter paths are preserved as
local files, and file:// asset URLs are decoded with Node's fileURLToPath().
URI-encoded local filenames such as
Texture%20BaseColor.png and external buffers with literal spaces resolve
through the same local file paths. Data URI images are handled by the encoded
image loader, Blob URLs are handled by the Blob URL image path, and remote
HTTP(S) asset or root URLs are rejected by the local helpers.
The repository includes the same pattern as a runnable script:
pnpm --filter @headless-three/renderer build:ts
node examples/render-gltf.mjs ./model.gltf render.pngFor VRM avatars and optional VRMA animation clips, install the Pixiv loader
packages in your project and use the VRM example. The example first uses
packages visible to the script, then falls back to resolving the optional Pixiv
packages from the current working project's node_modules before reporting a
missing dependency. Set TIME and ANIMATION_INDEX to choose the still-frame
time and VRMA wrapper animation entry:
pnpm add @pixiv/three-vrm @pixiv/three-vrm-animation
pnpm --filter @headless-three/renderer build:ts
node examples/render-vrm.mjs ./avatar.vrm ./dance.vrma render.pngcreateEncodedImageTextureLoader() returns the lower-level
EncodedImageTextureLoader Three.js loader handler used by loadGltfFromFile().
It supports both callback-style load() and promise-style loadAsync(). Its
textures expose encoded PNG/JPEG/WebP bytes through texture.image and
texture.source.data, and it reports optional LoadingManager
itemStart/itemEnd/itemError hooks and resolveURL() URL modifiers when a
manager is supplied. setPath() accepts relative, absolute, and file://
directory prefixes for later relative image loads before URL modifiers run,
without rewriting data URI, Blob URL, absolute, or fully-qualified asset URLs.
The renderer decodes those bytes natively, so no DOM Image, canvas, or WebGL
context is needed for external image files or PNG/JPEG/WebP data URI or Blob URL
image references.
Three.js FileLoader uses fetch(). Modern Node includes fetch, but its
built-in implementation does not consistently read file:// URLs. The
installLocalFileFetch() adds the ProgressEvent global expected by Three.js,
handles local external .bin files, and delegates HTTP(S) URLs to the native
fetch implementation.
Use a file:// base URL from pathToFileURL() when the glTF references local
external .bin buffers. GLTFLoader resolves those URLs before calling
FileLoader.
For images embedded directly as PNG/JPEG/WebP data URIs in .gltf JSON, register
the encoded-buffer loader for data URI image schemes:
const encodedImages = createEncodedImageTextureLoader(root)
manager.addHandler(/^data:image\/(?:png|jpe?g|webp)/i, encodedImages)
manager.addHandler(/\.(png|jpe?g|webp)$/i, encodedImages)For images embedded in GLB files as PNG/JPEG/WebP bufferViews, the exported
helpers normalize those embedded image references to data URIs before
GLTFLoader parses the asset. This avoids the browser-only default image loader
path that Three.js otherwise uses for bufferView images in Node.
For custom flows that still produce Blob URLs, register the same encoded-buffer loader for Blob URLs:
manager.addHandler(/^blob:/i, encodedImages)If your loader stack bypasses the exported helpers, preprocess embedded images as external PNG/JPEG/WebP files or data URIs so the encoded-buffer loader can handle them.
After loading, texture slots should expose one of the renderer-supported image forms:
texture.image = Buffer | Uint8Arrayfor encoded PNG/JPEG/WebP bytes.texture.source.data = Buffer | Uint8Arrayfor encoded PNG/JPEG/WebP bytes.texture.image = { data, width, height }for raw one-channel, two-channel, RGB, or RGBA data.
Compressed KTX2/Basis textures are not decoded by the renderer yet. External or embedded compressed image payloads fail during helper loading with pre-decode guidance; decode them to RGB/RGBA data or convert them to PNG/JPEG/WebP before rendering.
Only install polyfills that your loader path actually uses:
fetch: needed by Three.jsFileLoaderon older Node versions.Bloband object URLs: needed for embedded glTF images.createImageBitmap: needed if you wantGLTFLoaderto useImageBitmapLoaderfor embedded images.- DOM
Image/document.createElementNS: needed only when falling back to Three.jsTextureLoader/ImageLoader; the exported helpers already provide the minimal WebP support probe needed byEXT_texture_webpdetection.
If a polyfilled image loader returns browser image objects instead of encoded
bytes or raw one-channel, two-channel, RGB, or RGBA data, normalize those
textures before calling render().
The renderer fails clearly for browser Image/ImageBitmap/canvas-like
texture objects because their pixels are not readable in headless Node.