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.
Every render is lit by the studio preset 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 { renderImage } from 'nanoraster';const image = await renderImage(glb, { format: 'webp', lighting: { lights: [ { direction: [-0.5, 0.6, 0.6], color: [3, 2.9, 2.7] }, { direction: [0.6, -0.2, 0.4], color: [0.7, 0.8, 1] }, ], ambient: 0.02, exposure: 1, },});Rendering…
lights replaces the three studio lights 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), so the rig travels with the camera
and every view is lit the same way; any non-zero vector is normalised. color is
linear RGB radiance, unitless, up to eight lights. ambient lifts the shadow
side; exposure scales the rig before the ACES tone map.
The studio preset, for reference (ambient: 0.02, analytic environment):
| Light | direction | color |
|---|---|---|
| Key | [-0.45, 0.61, 0.63] | [2.09, 2.09, 2.09] |
| Fill | [0.45, -0.61, -0.63] | [1.45, 1.42, 1.38] |
| Headlamp | [0.03, 0.74, 0.67] | [0.68, 0.66, 0.62] |
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 analytic environment as
the only source (a rig inherits the studio ambient unless it says otherwise).
Metals, which have no diffuse lobe, read well this way; dielectrics go soft and
flat. environment: 'none' removes the specular reflection and the diffuse
irradiance together and leaves only the lights you supplied — with none, the
subject is black.
3. Fix a light to the model
const images = await renderImages(glb, { format: 'webp', lighting: { lights: [{ direction: [1, 0.4, 0], color: [3, 2.9, 2.7] }], space: 'world', }, views: [ { id: 'front', phi: 90, theta: 0 }, { id: 'back', phi: 90, theta: 180 }, ],});Rendering…
space: 'world' authors the directions in glTF world coordinates, so the rig
stays attached to the model while the camera orbits it. That deliberately breaks
what makes a view sheet comparable: under the default 'view', front and
back differ only in geometry; under 'world' the back is in shadow because the
light never moved. Choose it when the light belongs to the subject, not 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 it 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 is space: 'world', as above.
Annotations ignore the rig. The axis indicator, label and scale bar are composited after shading; authored edge lines are drawn 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 — invisible at thumbnail sizes, not at 4096².
Every bound is exported. renderImageMaxLights, renderImageLightColorRange,
renderImageAmbientRange and renderImageExposureRange are what the
validator applies.