| name | 3d-image-renderer |
| description | Generate PNG images by building a real Three.js 3D scene and capturing one frame headlessly — no image model involved. Takes a text prompt and an aspect ratio (1:1, 16:9, or 9:16) and produces a rendered PNG. Use this skill whenever the user asks to generate, create, or render an image, picture, scene, wallpaper, thumbnail background, or product shot with this system — especially with phrases like "generate an image of", "render a scene", "create a PNG", "no image model", "3D render of", or a prompt paired with an aspect ratio. Also use it when the user wants an image in a named visual style (dark studio, Apple light, nature, sunset, night, underwater). Not for editing existing images, 2D vector art, or animated video (VidTSX handles video). |
3D Image Renderer
Renders a text prompt into a PNG by writing a Three.js scene and capturing
one frame with headless-gl under Xvfb. Think of every image as frame 0 of a
TSX animation composition: deterministic, seeded, time = 2.0s.
Inputs
- prompt — what to render
- aspect ratio — one of
1:1 (1080×1080), 16:9 (1920×1080), 9:16
(1080×1920). Default 16:9 if unspecified. No custom sizes.
Workflow (follow in order)
1. Setup
bash <skill_dir>/scripts/setup.sh
Idempotent; instant when cached. Installs npm deps into
/home/claude/scene-render/ and drops the bundled prebuilt gl binary in
place (compiles from source only if the binary fails its smoke test). It also
copies pipeline.mjs and validate.mjs into the workdir. If it prints
SETUP FAILED, report the error to the user — do not hand-roll a fallback.
2. Plan the scene
- Pick ONE style preset → read
references/style-presets.md now.
- List the hero elements: every concrete noun in the prompt
("headphones", "tree", "flowers") is a hero element.
- Read
references/geometry-recipes.md for any element it covers before
inventing geometry.
3. Write the scene
Create /home/claude/scene-render/scene.mjs. Import ONLY from the local
pipeline — never re-implement renderer setup, pixel readback, or PNG writing:
import {
THREE, createRenderer, captureFrame, addStudioEnvironment,
seededRandom, visibleHeightAt,
} from './pipeline.mjs';
const time = 2.0;
const { renderer, glContext, W, H, OUT_W, OUT_H } =
createRenderer('16:9', { exposure: 1.15 });
const scene = new THREE.Scene();
await captureFrame({
renderer, glContext, scene, camera, W, H, OUT_W, OUT_H,
outPath: '/home/claude/scene-render/out.png',
});
console.log('DONE');
Scene-authoring rules:
- All randomness via
seededRandom(seed) — never Math.random().
InstancedMesh for anything repeated 50+ times; per-instance color via
setColorAt + instanceColor.needsUpdate = true.
- Bake rotations into geometry (
geometry.rotateZ(...)) — do not stack Euler
rotations on meshes for construction.
- Validate every horizon/background element with
visibleHeightAt (the FOV
rule in geometry-recipes.md) BEFORE rendering.
- No post-processing. Glow = emissive + additive blending + fog.
- WebGL1 only: no
transmission, no WebGL2-only features, three stays at
0.152.2 (pinned by setup — do not upgrade).
4. Render
cd /home/claude/scene-render && xvfb-run -a node scene.mjs
5. Validate (mandatory — never skip)
Write a hero spec covering each prompt-named element with its dominant
material color, then:
node validate.mjs out.png heroes.json
[
{ "name": "flowers", "hex": "f5a8c8", "tolerance": 45, "minPct": 0.3 },
{ "name": "tree canopy", "hex": "5a9440", "tolerance": 60, "minPct": 2.0 }
]
Also open the PNG with the view tool if image viewing works in this session.
If it returns placeholders, rely on the validator output (histogram, ASCII
map, presence checks) — it was designed for exactly that.
Fix and re-render when:
- any hero element FAILs presence (typical fixes: more instances, bigger
scale, camera reframe, contrasting material)
- WASHED OUT / TOO DARK / LOW CONTRAST warnings (typical fixes: exposure and
ambient per the style preset table; in light styles swap pure-white
materials for metallic/mid-gray)
- the ASCII map shows a composition failure (subject cropped, background
walling off the sky)
Maximum 2 fix iterations, then deliver best result and tell the user what
still needs work.
6. Deliver
cp /home/claude/scene-render/out.png "/mnt/user-data/outputs/<descriptive_name>.png"
Present the PNG with present_files. Keep the summary short: style preset
used, hero elements, and any validation fixes applied. Only include
scene.mjs in the delivery if the user asks for the code.
Hard rules (violating any of these produced real failures)
- three@0.152.2 pinned — r163+ drops WebGL1 and headless-gl is WebGL1-only.
- Pixel readback needs vertical flip + alpha forced to 255 (pipeline does it).
- No MSAA headless — SSAA 2× via the pipeline is the anti-aliasing.
- Light styles: exposure ≤ 1.08, ambient ≤ 0.35, and the hero MUST include a
metallic or mid-gray material. Pure white on white is invisible.
- Background elements sized by intuition WILL wall off the sky — run the FOV
check.
- Prompt-named elements must pass the presence validator before delivery.
- Everything deterministic:
seededRandom, fixed time = 2.0.