nanoraster
0.7.1

Light the subject

Replace the studio preset with a rig of your own, render from the environment alone, or fix a light to the model instead of the camera.

Open Markdown

The studio preset lights every render unless the request says otherwise. lighting takes the preset name or an explicit rig. A rig replaces the studio lights and inherits every value it leaves out.

1. Replace the studio lights

import { directionFromOrbit, renderImage } from 'nanoraster';const image = await renderImage(glb, {  format: 'webp',  lighting: {    lights: [      { direction: directionFromOrbit({ azimuth: -40, elevation: 38 }), color: [3, 2.9, 2.7] },      { direction: directionFromOrbit({ azimuth: 56, elevation: -16 }), color: [0.7, 0.8, 1] },    ],    ambient: 0.02,    exposure: 1,  },});

Rendering…

 

advanced · light 1 direction XYZ
advanced · light 2 direction XYZ

lights replaces the studio light outright. It never adds to them. direction points from the surface toward the light, in view space by default (+x right, +y up, +z toward the viewer). The rig travels with the camera and lights every view the same way. The renderer normalises any non-zero vector.

color is linear RGB radiance, unitless. The rig takes up to eight lights. ambient lifts the shadow side. exposure scales the rig before the PBR Neutral tone map.

The studio preset uses a view-locked neutral room, ambient: 0.1 / Math.PI, and one direct light:

Lightdirectioncolor
Headlamp[1, 1, 1][1.5, 1.5, 1.5]

2. Render from the environment alone

const image = await renderImage(glb, {  format: 'webp',  lighting: {    lights: [],    ambient: 0,    environment: 'studio',    exposure: 1.4,  },});

Rendering…

 

An empty lights array with ambient: 0 leaves the prefiltered room environment as the only source. A rig inherits the studio ambient unless it says otherwise. Metals have no diffuse lobe, so the environment alone renders them well. Dielectrics render soft and flat. environment: 'none' removes the specular reflection and the diffuse irradiance together, leaving only the lights you supplied. With none, the subject is black.

3. Fix a light to the model

const world = { up: '+z', forward: '-y', unit: 'meter' } as const;const images = await renderImages(glb, {  format: 'webp',  world,  lighting: {    lights: [{ direction: directionFromOrbit({ azimuth: 112, elevation: 0 }, world), color: [3, 2.9, 2.7] }],    space: 'world',  },  views: [    {      id: 'front',      camera: { framing: 'fit', direction: directionFromOrbit({ azimuth: 90, elevation: 0 }, world) },    },    {      id: 'back',      camera: { framing: 'fit', direction: directionFromOrbit({ azimuth: -90, elevation: 0 }, world) },    },  ],});

Rendering…

advanced · light 1 direction XYZ
advanced · direction XYZ

space: 'world' reads directions in the declared caller world. The light stays fixed to the Z-up subject while the camera orbits. nanoraster converts the rig once; do not supply GLB-space values. Under the default 'view', the rig travels with each camera, which is usually better for contact sheets.

4. Keep the preset pinned

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

Omitting lighting, passing 'studio' and spelling out the studio values produce the same bytes. Pin the preset when the request, not the default, should say how the image was lit. Determinism covers either.

Variations

Lighting is shared across a batch. renderImages takes one rig for the whole call. A model-fixed light uses space: 'world', as above.

Annotations ignore the rig. The renderer composites the axis indicator, label and scale bar after shading. It draws authored edge lines unlit.

Metals depend on the environment. A fully metallic subject has no diffuse term, so under environment: 'none' it renders nearly black. See Material model.

Cost scales with light count. Eight lights cost roughly eight times the direct-lighting work of one. That is invisible at thumbnail sizes, but not at 4096².

Every bound is exported. The validator applies renderImageMaxLights, renderImageLightColorRange, renderImageAmbientRange and renderImageExposureRange.

On this page