nanoraster
0.4.1

Render multiple views

Declare an ordered plan of identified camera views — with per-view size and format overrides — and render it in one call from one GLB.

Open Markdown

Render several outputs of one model in a single call — the plan call — keeping the IDs and order you declared, as the tutorial does for its contact sheet. One call parses and uploads the GLB once, shares the GPU device across every view, and schedules the whole set together, which a loop of single renders never can.

1. Declare the views as a tuple

Each view needs a unique id and its own angles. axes marks the orientation of each one, which a set of angles alone does not show.

import { renderImages } from 'nanoraster';import { readFile } from 'node:fs/promises';const glb = Uint8Array.from(await readFile('model.glb'));const images = await renderImages(glb, {  format: 'webp',  width: 640,  height: 480,  axes: true,  views: [    { id: 'iso', phi: 60, theta: -45 },    { id: 'front', phi: 90, theta: 0 },  ],});

Rendering…

2. Read the results positionally or by ID

The result is a tuple, not a map: position and length match the input.

const [iso, front] = images;

console.log(iso.id); // 'iso'
console.log(front.file.name); // 'render-front.webp'

TypeScript types iso.id as 'iso' rather than string, but only while the options object is inferred as const.

3. Ladder sizes and formats with per-view overrides

A view is more than a camera: width, height, format and quality can be overridden per entry, defaulting to the shared values. One subject becomes a card, a social image and a print asset in one call.

const [card, og, print] = await renderImages(glb, {
  format: 'webp',
  width: 768,
  height: 576,
  views: [
    { id: 'card', phi: 60, theta: -45 },
    { id: 'og', phi: 60, theta: -45, width: 1536, height: 804 },
    { id: 'print', phi: 60, theta: -45, width: 1536, height: 804, format: 'png' },
  ],
});

console.log(og.file.name); // 'render-og.webp'
console.log(print.file.mimeType); // 'image/png'

Each entry's filename and MIME type follow its own resolved format, and TypeScript narrows print.file.mimeType to 'image/png' while card and og stay 'image/webp'. Overrides pass the same validation as the shared values — a per-view quality: 1 on WebP still means lossless, and annotated views still need 192 pixels each way.

4. Preserve literal types across a variable

Assigning options to a variable widens the literals and loses the IDs. as const satisfies RenderImagesOptions prevents that: as const keeps every literal, and satisfies still rejects misspelled or misplaced keys — all at compile time, with nothing added to the bundle.

import { renderImages, type RenderImagesOptions } from 'nanoraster';

const options = {
  format: 'webp',
  views: [
    { id: 'iso', phi: 60, theta: -45 },
    { id: 'top', phi: 0, theta: 0 },
  ],
} as const satisfies RenderImagesOptions;

const images = await renderImages(glb, options);

Variations

One image only. Use renderImage. The batch call requires a non-empty tuple and returns a tuple even for one view, under the same exactness rules.

Labels per view. Give an entry a label and that view is labelled; leave it off and it is not. See Format and annotate.

Shared-only settings. axes, scaleBar, background, lighting, margin, up and projection apply to the whole request; the camera pair, label and the four output fields vary per view.

Renders spread over time. The plan call shares the device across views within one call; createRenderer shares it across calls over time. They compose — a warm renderer accepts the same plan calls. Either way, when you can write the full list of images down, send it as one call rather than a loop: only the renderer can schedule work it can see.

Stage timings. timings: true attaches parse, setup, and per-view render, overlay and encode milliseconds to the result as results.timings; see RenderTimings.

On this page