# Headless 3D rendering: Blender, Three.js, Unreal and vork, with the commands

How to render 3D with no window: tested commands and timings for Blender and Three.js and vork 0.1.0, Unreal from its docs, and the headless modes that produce no pixels at all.

Author: Ravi Parikh

Canonical: https://voyager.so/blog/headless-3d-rendering

Published: 2026-11-06T14:26:33-08:00
Updated: 2026-11-06

Headless 3D rendering means producing finished frames from a command, with no window and nobody at a screen. Blender does it with `blender -b`, Three.js does it inside headless Chromium, Unreal does it through Movie Render Queue on the command line, and vork, a newer engine our team at Voyager makes, has no window to begin with. Most of these still want a GPU. The word "headless" hides that, and two well-known headless modes produce no pixels at all.

The short version: use Blender if you have `.blend` files or need Cycles-quality light, fog and simulation. Use Three.js if the same scene also ships to a browser. Use Unreal if the project already lives there. Try vork if an agent is writing the scene from scratch and you have a recent NVIDIA GPU or Apple Silicon, and accept that it is a research preview with real gaps.

We ran the Blender, Three.js and vork commands below on one Mac on 6 November 2026, and read the Unreal, Godot, Babylon.js and Bevy details from their own documentation the same day. Versions are named beside each command, because these move. vork is made by Voyager, so read its section with that in mind. Its limits are listed from its own README.

**Key takeaways**

* On a trivial 640x480 scene, every renderer finished in about four seconds or less once warm, so speed did not separate them; scene features, hardware and how the scene gets authored do.
* Godot's `--headless` and Babylon.js's NullEngine are for servers and tests, not images. For pixels you need a real GPU context or software rendering.
* vork takes one JSON file and one command with no editor or browser behind it, but it does not support AMD, Intel or CPU rendering and ignores fog and volumes in 0.1.0.

![A moving camera passes a carrot cake on a carved plate, with a kettle out of focus behind it.](https://cdn.moda.app/voyager/blog/vork/cake-poster-2dcd4ddc4d0f3ad6.webp)

[Video: A moving camera passes a carrot cake on a carved plate, with a kettle out of focus behind it.](https://cdn.moda.app/voyager/blog/vork/cake-720-c02ceb877b0f9f52.mp4)

Rendered without a window: this is rendered 3D in vork, not a photographed take. Poly Haven CC0 assets; carrot cake photography by Greg Zaal, processing by James Ray Cock; scene and render in vork.

## Three things "headless" can mean

The confusion in this topic comes from one word covering three requirements. Sort out which one you have before picking a tool.

| What you are missing | What it means                              | What still works                                                                                                                                   |
| -------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| A window             | Nobody needs to see the render happen      | All of the tools here                                                                                                                              |
| A display server     | A Linux box with no X11 or Wayland session | Tools that render without one. Blender added a headless Linux path for EEVEE in [3.4](https://developer.blender.org/docs/release_notes/3.4/eevee/) |
| A GPU                | A cheap CPU-only server or CI runner       | Cycles on CPU, or browser software rendering. vork stops here                                                                                      |

Then there are the modes named headless that do not draw anything:

* **Godot `--headless`.** The [dedicated server guide](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_dedicated_servers.html) describes it for a machine with no GPU or display server. It is meant for running game logic. When [we ran Godot 4.7.2 for our Godot post](/blog/godot-mcp), `--write-movie` needed a screen and saved nothing useful with `--headless`.
* **Babylon.js NullEngine.** Babylon's [server guide](https://doc.babylonjs.com/setup/support/serverSide) says plainly that it "does not produce any rendering". It is for tests, server logic and loaders.

## The same scene through each renderer

We built the same blocking in each tool: a sphere on a plane, one key light, one camera, 640x480, using each renderer's defaults, except that the Three.js version adds a fill light and AgX tone mapping. The looks were not matched and this is not a benchmark. It answers a narrower question: what does the shortest honest path to a PNG look like, and what does it cost in time on one machine?

| Route                                        | Version | Setup before first render                           | First render | Warm renders   | What came out                                                                                                                                      |
| -------------------------------------------- | ------- | --------------------------------------------------- | ------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| vork `std/final` (8 samples, denoised)       | 0.1.0   | Installer, including 86 s of shader setup           | 5.2 s        | 0.42 to 0.44 s | Soft shadow, smooth, with a grey default background                                                                                                |
| Blender EEVEE (64 samples)                   | 5.2.1   | Already installed                                   | 2.0 s        | 0.81 to 0.85 s | Correct form, visible grain at the shadow edge, black where no world was set                                                                       |
| Blender Cycles on Metal (adaptive, denoised) | 5.2.1   | Already installed                                   | 3.9 s        | 2.2 to 4.0 s   | The cleanest soft shadow of the four                                                                                                               |
| Three.js in headless Chromium                | r187    | `npm i three playwright`, plus the browser download | 1.9 s        | 0.34 to 0.6 s  | Hard-edged shadow map from a directional light, because `RectAreaLight` has [no shadow support](https://threejs.org/docs/pages/RectAreaLight.html) |

Times are the whole command, including startup and writing the file. Apple M5 Max, 6 November 2026.

![The same sphere, ground plane and key light rendered four ways, in a two by two grid: vork top left with a grey background, Blender EEVEE top right with grain at the shadow edge, Blender Cycles bottom left with the cleanest shadow, Three.js bottom right with a hard-edged shadow.](https://cdn.moda.app/voyager/blog/headless-3d-rendering/same-scene-4-up-f1608415c0030068.webp)

The same blocking through each route at 640x480, using defaults. Looks are not matched; this shows the shortest path to a PNG, not the best each can do.

Two things to take from this. At this size, nobody should choose on speed: all four are fast enough for an agent loop. And Cycles took several times as long as the other three and gave the best image, the usual trade for a path tracer. vork's own README records its path tracer running [2 to 5.8 times slower than Cycles](https://github.com/voyager-so/vork#path-tracing-carried-forward-measurements) in its two named cases, so a pathtraced comparison would not flatter it.

## Blender: `blender -b`

Blender is the default answer, and a good one. `-b` runs without the interface and `-f` or `-a` renders. From the [Blender 5.2 manual](https://docs.blender.org/manual/en/latest/advanced/command_line/render.html):

```sh
blender -b file.blend -f 10
blender -b file.blend -E CYCLES -s 10 -e 500 -a
```

[Arguments run in the order given](https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html), so the file comes before the render flag, and case matters: `-F` and `-f` are different flags. Most automated jobs pass a Python script instead of a prepared file. This is the script we ran, Blender 5.2.1, with no saved `.blend`:

```python
# scene.py
import bpy, sys
from mathutils import Vector

engine, out = sys.argv[sys.argv.index("--") + 1:]
bpy.ops.wm.read_factory_settings(use_empty=True)
s = bpy.context.scene

def mat(rgb, rough):
    m = bpy.data.materials.new("m")
    m.use_nodes = True
    b = m.node_tree.nodes["Principled BSDF"]
    b.inputs["Base Color"].default_value = (*rgb, 1)
    b.inputs["Roughness"].default_value = rough
    return m

def add(data, loc):
    o = bpy.data.objects.new(data.name, data)
    s.collection.objects.link(o)
    o.location = loc
    o.rotation_euler = (Vector((0, 0, 0)) - o.location).to_track_quat("-Z", "Y").to_euler()
    return o

bpy.ops.mesh.primitive_uv_sphere_add(radius=1, segments=32, ring_count=16)
bpy.ops.object.shade_smooth()
bpy.context.object.data.materials.append(mat((0.12, 0.35, 0.65), 0.3))
bpy.ops.mesh.primitive_plane_add(size=10, location=(0, 0, -1))
bpy.context.object.data.materials.append(mat((0.35, 0.35, 0.35), 0.5))

light = bpy.data.lights.new("key", "AREA")
light.size, light.energy, light.color = 4, 700, (1, 0.92, 0.8)
add(light, (-3, -4, 5))
s.camera = add(bpy.data.cameras.new("cam"), (3, -5, 2))

s.render.engine = engine
s.render.resolution_x, s.render.resolution_y = 640, 480
s.render.filepath = out
if engine == "CYCLES":
    s.cycles.device = "GPU"
    prefs = bpy.context.preferences.addons["cycles"].preferences
    prefs.compute_device_type = "METAL"  # OPTIX or CUDA on NVIDIA
    prefs.get_devices()
    for d in prefs.devices:
        d.use = d.type != "CPU"
bpy.ops.render.render(write_still=True)
```

```sh
blender --background --factory-startup --python scene.py -- BLENDER_EEVEE $PWD/eevee.png
blender --background --factory-startup --python scene.py -- CYCLES $PWD/cycles.png
```

Two habits save trouble on a server. Use the data API where you can: many `bpy.ops` operators want a UI context and fail in background mode. And check how your Linux setup handles EEVEE, which still needs a working GPU context with the headless path added in 3.4; [this report on Blender's tracker](https://projects.blender.org/blender/blender/issues/128418) is worth reading before you commit to a Docker image. Cycles can also render on the CPU.

Choose Blender if the scene already exists as a `.blend`, or if you need fog, volumes, simulation, modelling tools or Cycles. Its licence is the GPL with [Cycles itself under Apache 2.0](https://www.blender.org/about/license/), and it runs on far more hardware than vork.

## Three.js: Chromium with a real GL context

Three.js has no native command-line renderer. The practical route is a headless browser. This is the whole program we ran, with `three` r187 and Playwright 1.65:

```js
// render.mjs: npm i three playwright && npx playwright install chromium
import { chromium } from 'playwright';
import { readFileSync, writeFileSync } from 'node:fs';

const browser = await chromium.launch({ args: ['--use-angle=metal', '--enable-gpu', '--ignore-gpu-blocklist'] });
const page = await browser.newPage();
await page.route('**/*', (r) => {
  const name = new URL(r.request().url()).pathname.slice(1);
  if (name.endsWith('.js')) return r.fulfill({ contentType: 'text/javascript', body: readFileSync(`node_modules/three/build/${name}`, 'utf8') });
  return r.fulfill({ contentType: 'text/html', body: '<!doctype html>' });
});
await page.goto('http://local.test/');
const png = await page.evaluate(async () => {
  const THREE = await import('/three.module.js');
  const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
  renderer.setSize(640, 480);
  renderer.shadowMap.enabled = true;
  renderer.toneMapping = THREE.AgXToneMapping;
  const scene = new THREE.Scene();
  const ball = new THREE.Mesh(new THREE.SphereGeometry(1, 64, 32),
    new THREE.MeshStandardMaterial({ color: new THREE.Color(0.12, 0.35, 0.65), roughness: 0.3 }));
  ball.castShadow = true;
  const ground = new THREE.Mesh(new THREE.PlaneGeometry(10, 10),
    new THREE.MeshStandardMaterial({ color: new THREE.Color(0.35, 0.35, 0.35) }));
  ground.rotation.x = -Math.PI / 2; ground.position.y = -1; ground.receiveShadow = true;
  const key = new THREE.DirectionalLight(new THREE.Color(1, 0.92, 0.8), 4);
  key.position.set(-3, 5, 4); key.castShadow = true;
  scene.add(ball, ground, key, new THREE.HemisphereLight(0xffffff, 0x222222, 0.5));
  const camera = new THREE.PerspectiveCamera(30.3, 640 / 480, 0.1, 100);
  camera.position.set(3, 2, 5); camera.lookAt(0, 0, 0);
  renderer.render(scene, camera);
  return renderer.domElement.toDataURL('image/png');
});
writeFileSync('three.png', Buffer.from(png.split(',')[1], 'base64'));
await browser.close();
```

The GL flags are the part that goes wrong. Our flags are for a Mac; Linux needs a different backend. Remotion, which renders Three.js scenes in headless Chromium, publishes a clear [guide to Chromium's GL options](https://www.remotion.dev/docs/gl-options): `angle` on a desktop, `angle-egl` on a cloud instance with a GPU, `swangle` with no GPU, where it warns that rendering might be slow. It also warns of memory leaks with `angle` and suggests splitting long renders. For a video, your code has to own time: step the scene to each frame, capture it, and encode it yourself, since nothing here writes an MP4.

Choose Three.js if the scene also ships to the web, if your team already knows it, or if a CPU-only server with software rendering is good enough. Its MIT licence is the simplest here. Expect real-time looks, not offline light transport.

## Unreal: Movie Render Queue

Unreal's route is documented for [command line rendering with Movie Render Queue](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-command-line-rendering-with-move-render-queue-in-unreal-engine) (the page we read is for 5.8):

```text
UnrealEditor-Cmd.exe <path to .uproject> <map> -game -MoviePipelineConfig=<path to queue asset> -windowed -resx=<width> -resy=<height> -log -notexturestreaming
```

We have not run this, so we report it from the documentation only. The page uses `-windowed` and does not say whether the command can run with no display or no GPU. Unreal's assets are binary, so the queue and level have to be authored in the editor or through its Python API first. Unreal 5.8 also has an experimental first-party MCP server, described in [Epic's docs](https://dev.epicgames.com/documentation/unreal-engine/unreal-mcp-in-unreal-editor).

Choose Unreal when the project is already Unreal, or when you need its real-time world building at high quality. It is the heaviest setup here, and [non-game companies above $1 million in annual revenue need a paid seat](https://voyager.so/resources/apps/unreal-engine) (read 23 October 2026).

## vork: nothing but a command

vork is a headless engine by design, written in Rust and Slang, rendering through Vulkan and Metal. There is no editor. A scene is a JSON file, which a Python or TypeScript SDK can also generate:

```json
{"version":2,"nodes":[
  {"type":"mesh","name":"ball","geometry":{"std":"sphere","radius":1},
   "materials":[{"inputs":{"base_color":[0.12,0.35,0.65],"specular_roughness":0.3}}]},
  {"type":"mesh","translation":[0,-1,0],"geometry":{"std":"grid","size":[10,10]},
   "materials":[{"inputs":{"base_color":[0.35,0.35,0.35]}}]},
  {"type":"light","shape":"rectangle","translation":[-3,5,4],"target":[0,0,0],
   "energy":700,"size":4,"color":[1,0.92,0.8]},
  {"type":"camera","translation":[3,2,5],"target":[0,0,0]}
],
"animation":[{"tracks":[{"path":["nodes","ball","translation"],
  "keys":[[0,[0,0,0]],[2,[2,0,0]]],"interpolation":"LINEAR"}]}],
"render":{"resolution":[640,480]}}
```

```sh
# vork 0.1.0. The API is changing quickly, so check `vork help` for yours.
vork render shot.json --profile std/previs --out preview.png
vork render shot.json --profile std/final --out final.png
vork render shot.json --profile std/final --frame-start 0 --frame-end 47 --out clip.mp4
```

The MP4 is written directly through VideoToolbox: 48 frames took 7.2 seconds the first time on our Mac and 1.8 seconds warm. On Linux, MP4 output still needs ffmpeg to mux the stream.

The catches are substantial, and they come from [vork's own README](https://github.com/voyager-so/vork#research-preview-limits):

* **Hardware.** A recent NVIDIA GPU with hardware ray tracing on Linux or Windows, or Apple Silicon. AMD, Intel and CPU rendering are unsupported, so most cheap servers and CI runners are out.
* **Compile time.** `vork setup` takes a few minutes, and the first render of a scene with new programs can compile for minutes with no output, so don't kill it or give it a short timeout. We saw 86 seconds of setup on this Mac; the project's NVIDIA runs took about 2.5 to 4.5 minutes.
* **Missing features.** Fog and participating volumes are ignored in 0.1.0.
* **Failures.** Large scenes and dense transparency can fail, including on Mac, and lowering the resolution does not necessarily help.
* **Licence.** It is source-available under the vork Preview License, free during the research preview including commercial work. It is not open source, and later versions may come under different terms.
* **First results.** An agent starting from scratch can get [something basic](/blog/render-3d-with-claude-code). The showcase shots took much more direction.

![Wipes across real footage of a woman asleep at a laptop: tracked geometry, a glowing seed, then the room relit to evening and night.](https://cdn.moda.app/voyager/blog/vork/seed-wipes-poster-dc3ed4e0f4cdbc44.webp)

[Video: Wipes across real footage of a woman asleep at a laptop: tracked geometry, a glowing seed, then the room relit to evening and night.](https://cdn.moda.app/voyager/blog/vork/seed-wipes-720-74f7064027a28e00.mp4)

Rendered without a window: real footage plus rendered 3D from vork, with no generative video. Footage: Ron Lach / Pexels, modified under the Pexels Licence. Original performer; no endorsement implied.

Where it does well: the README's 1080p [default final stills](https://github.com/voyager-so/vork#default-final-stills) and the [full results](https://github.com/voyager-so/vork/blob/main/benchmarks/RESULTS.md) ran 1.15 to 1.90 times faster than retained EEVEE baselines on an NVIDIA L4, using eight denoised samples against EEVEE's 64, and it says plainly these are not equal-quality speedups. Scenes can carry Slang compute programs, which is how its [million-bird shot](/blog/what-is-vork) was made. Choose vork when an agent writes the scene and reads the frames back, and your shot avoids its gaps.

## Other engines worth knowing

* **Bevy** has an official [headless renderer example](https://github.com/bevyengine/bevy/blob/main/examples/app/headless_renderer.rs) that renders to an image with no window. Good if you are already writing Rust and willing to work from a code example; its README warns that [important features are missing](https://github.com/bevyengine/bevy) and releases break APIs about every three months.
* **Cycles standalone** builds [without Blender](https://github.com/blender/cycles) and renders XML scenes. Fine for a pipeline that wants only the path tracer.
* **Mitsuba 3** is a [Python-first research renderer](https://github.com/mitsuba-renderer/mitsuba3) with CPU and NVIDIA backends, and it needs no window. Pick it for inverse rendering and light transport research, not for deliverables on a deadline.

## Which one to use

| If your job is mostly                                                  | Choose                                      | Because                                                        |
| ---------------------------------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------- |
| Rendering existing `.blend` files on a server                          | Blender with Cycles                         | Stable flags, CPU fallback, best quality here                  |
| The same scene in a website and a server render                        | Three.js                                    | One codebase, MIT, software rendering fallback                 |
| Cinematics from an existing Unreal project                             | Unreal Movie Render Queue                   | It is already authored there                                   |
| An agent writing shots from scratch, with custom shaders or simulation | vork, if your hardware and shot fit         | No project to set up; scene is text; compute programs included |
| Shots with fog, smoke or volumes                                       | Blender                                     | vork 0.1.0 ignores them                                        |
| Running on CPU only                                                    | Blender Cycles or Three.js with software GL | vork does not run there                                        |
| Research on light transport                                            | Mitsuba 3                                   | Built for it                                                   |

## Frequently asked questions

### What is headless 3D rendering?

Rendering 3D scenes to images or video from a command, with no window or person at a screen. It's used on servers, in batch pipelines and by AI agents. It does not necessarily mean no GPU: most real-time engines still need one.

### Can Blender render without a GPU or display?

Yes, with limits. `blender -b` needs no window, and [Cycles](https://docs.blender.org/manual/en/latest/advanced/command_line/render.html) can render on the CPU, slowly. EEVEE needs a working GPU context; a headless Linux path arrived in [Blender 3.4](https://developer.blender.org/docs/release_notes/3.4/eevee/), so test it in your own container first.

### Can Three.js run headless?

In a headless browser, yes, as above. There is no first-party command-line renderer, and Three.js's WebGL renderer needs a GL context, from a GPU or from software rendering such as SwiftShader. Remotion documents the [options](https://www.remotion.dev/docs/gl-options) in detail.

### Does Godot's `--headless` flag render images?

Not usefully. It is [described for dedicated servers](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_dedicated_servers.html), and in our own [Godot 4.7.2 run](/blog/godot-mcp) `--write-movie` needed a screen.

### Is vork open source?

No. vork is source-available under the [vork Preview License](https://github.com/voyager-so/vork/blob/main/LICENSE), and free during the research preview, including for commercial work. You may modify it for your own use, but may not redistribute changes or run it as a hosted rendering service without permission.

### Can I render on a server with no GPU using vork?

No. vork 0.1.0 needs a recent NVIDIA GPU with hardware ray tracing or Apple Silicon. Software Vulkan adapters are unsupported, per the [install notes](https://github.com/voyager-so/vork#install).


## Voyager is a desktop AI agent for creative work

Describe what you want to make; Voyager plans it, uses your apps and tools, and keeps everything editable.

[Sign up](/sign-up)