Render a view sheet
Build a four-view contact sheet from one GLB, learning batch rendering, camera angles, and labels along the way.
Build a labelled four-view contact sheet (front, right, top and iso) from one GLB in a single render call.
Step 1: Render a single view from a chosen angle
Start with one view so the camera controls are visible on their own.
Frame the model explains the two angles, phi and
theta.
import { renderImage } from 'nanoraster';import { readFile, writeFile } from 'node:fs/promises';const glb = Uint8Array.from(await readFile('model.glb'));const front = await renderImage(glb, { format: 'webp', width: 512, height: 512, phi: 90, theta: 0,});await writeFile('front.webp', front.bytes);Rendering…
Verify: front.webp shows the gear face-on, teeth all round the rim.
Step 2: Switch to the batch call
Rendering each view with its own call parses and uploads the GLB every time.
renderImages does that once and renders every view in the list — start
with two, then add the rest in step 3. A sheet wants a tighter fit than a
single hero image, so margin drops to 0.05; drag it and both views refit
together.
import { renderImages } from 'nanoraster';import { readFile, writeFile } from 'node:fs/promises';const glb = Uint8Array.from(await readFile('model.glb'));const sheet = await renderImages(glb, { format: 'webp', width: 512, height: 512, margin: 0.05, views: [ { id: 'front', phi: 90, theta: 0 }, { id: 'right', phi: 90, theta: 90 }, ],});for (const { id, file } of sheet) { await writeFile(file.name, file.bytes); console.log(id, file.name);}Rendering…
Verify: two files appear, and the console prints the IDs in the order you declared them:
front render-front.webp
right render-right.webpThe result filename carries the view id, not a counter.
Step 3: Add the top and iso views
The top view looks down the up axis; the iso view is the default three-quarter
angle. axes draws a camera-aware marker in each image, which is what
orients the top view once it is on its own.
const sheet = await renderImages(glb, { format: 'webp', width: 512, height: 512, margin: 0.05, axes: true, views: [ { id: 'front', phi: 90, theta: 0 }, { id: 'right', phi: 90, theta: 90 }, { id: 'top', phi: 0, theta: 0 }, { id: 'iso', phi: 60, theta: -45 }, ],});Rendering…
Verify: four files. render-top.webp looks down at the model, and render-iso.webp uses
the quick start's angles, because those are the defaults.
Step 4: Label each view
Labels are drawn into the image, screen-upright, so a sheet stays readable
once the images are separated from their filenames. A view carries one when
its entry sets label; there is no flag to turn on, and clearing a label
below removes it. scaleBar adds the bottom-left bar, so the finished sheet
carries its own units too.
const sheet = await renderImages(glb, { format: 'webp', width: 512, height: 512, margin: 0.05, axes: true, scaleBar: true, views: [ { id: 'front', phi: 90, theta: 0, label: 'Front' }, { id: 'right', phi: 90, theta: 90, label: 'Right' }, { id: 'top', phi: 0, theta: 0, label: 'Top' }, { id: 'iso', phi: 60, theta: -45, label: 'Iso' }, ],});Rendering…
Verify: each image carries its name in the top-left corner.
Step 5: Keep it reproducible
Run the script twice and compare the files: the sheet regenerates byte-for-byte on the same host. How it works sets out what is and is not guaranteed across hosts.