nanoraster
0.4.1

Frame the model

Place the camera with two angles, match the model's up axis, choose a projection, and control how tightly the subject fits.

Open Markdown

Most renderers ask for a camera position, a target and an up vector. nanoraster asks for two angles: the camera sits on a sphere centred on the model's bounds, and the radius is solved so the subject fills the frame.

1. Place the camera with two angles

You choose the direction the model is seen from, not the distance.

import { renderImage } from 'nanoraster';const image = await renderImage(glb, {  format: 'webp',  phi: 60,  theta: -45,});

Rendering…

phi is the polar angle from the up axis (default 60); theta is the right-handed azimuth around it (default -45). Because phi is measured from the up axis it behaves like colatitude, not latitude: 0 looks straight down the axis (top), 60 is the default three-quarter view, 90 is level with the model (elevation) and 180 looks straight up (bottom). At phi: 0 only theta spins the image.

Exact ranges for every field named here are in RenderImageOptions.

2. Match the model's up axis

up does not rotate the model. It selects the world axis the angles are measured against, so changing it reinterprets phi and theta rather than transforming geometry.

const image = await renderImage(glb, {  format: 'webp',  up: 'x',  phi: 60,  theta: -45,});

Rendering…

Y-up is the glTF convention and the default. CAD exports are often Z-up; render one without saying so and the subject tips onto its side. The demo gear's axis runs along X, so up: 'x' turns the same two angles into a view looking down onto its face.

3. Choose a projection

const image = await renderImage(glb, {  format: 'webp',  projection: 'orthographic',});

Rendering…

perspective is the default; nearer features look larger. Use orthographic for canonical views where two parts of the model must be comparable at any depth. It also makes the scale annotation depth-invariant.

4. Tighten or loosen the fit

margin is the fraction of the fitted extent left empty around the subject; the camera radius is solved from the bounds and margin together.

const image = await renderImage(glb, {  format: 'webp',  margin: 0.02,});

Rendering…

marginResult
0Subject touches the frame edge
0.1Default; a comfortable border
0.5Maximum; subject occupies the middle half

Variations

Consistent framing across a set. In a batch call, phi and theta are per view while up, projection and margin are shared, so every tile of a contact sheet is framed the same way.

Very wide or very tall outputs. Fitting accounts for the aspect ratio you request, so a 1920×480 banner frames differently from a 512×512 square; set width and height before tuning margin.

Notes

  • You cannot dolly the camera. Removing distance from the API removes a class of bad output: no camera inside the model, no subject reduced to a speck. Adjust margin if the subject looks too small.
  • Angles are not portable across up axes. A view tuned at Y-up needs different numbers at Z-up.
  • The fit is deterministic. With identical bounds, angles and fitting options — margin, width and height all feed the fit — the renderer produces an identical camera, a precondition for reproducible output.
  • Degenerate bounds still render. A flat or single-point model has degenerate bounds; the fit falls back rather than dividing by zero, though the result is rarely useful.

On this page