Cameras
The render is taken from your Three.js camera, exactly where it is when you
call render(). Perspective and orthographic cameras both work.
Supported cameras
| Three.js | In the render |
|---|---|
PerspectiveCamera | The same view. fov, aspect, near, far and zoom are all used. |
OrthographicCamera | The same view, from left, right, top, bottom and zoom. |
A camera that sits inside a moving rig, such as an orbit pivot or a turntable, is rendered from where you actually see it.
Which camera gets used
In order:
- The
camerayou pass torender(), for that one call. - The camera you passed to
attach(). - In React Three Fiber, the live camera.
useBakery3()picks it up for you, also after drei's controls swap it.
// Render another viewpoint without moving what the user is looking at
await bakery3.render({ camera: detailCamera });Several cameras at once
Pass cameras to render the same scene from each of them in one call. The
scene is uploaded once, however many cameras you list.
const shots = await bakery3.render({
cameras: ['hero', 'front', 'detail'],
quality: 'studio',
maxCost: 2, // the ceiling for the whole set
});
const [hero, front, detail] = await shots.result();
gallery.hero.src = hero.url;You can name a camera in three ways, and mix them in one list:
| Given as | Means |
|---|---|
'hero' | The camera in your scene whose name is hero. In R3F, <PerspectiveCamera name="hero" />. |
a Camera | That camera, where it is now. Its name is its id. Without a name it is camera-1, camera-2, by its place in the list. |
{ id, position, target, fov } | A shot you have no camera object for. It looks from position at target, with +Y up. |
You get a set of pictures back instead of one job. shots.result() waits for
all of them and returns them in the order you listed the cameras. Each has the
camera it was taken from, its url, its size and its cost. maxCost is the
limit for the whole set, and one idempotencyKey covers the whole set too.
If a picture is refused when it is sent, result() rejects with
RenderSetRefused instead of returning a shorter list. The pictures that were
accepted still render.
import { RenderSetRefused } from '@oeave/bakery3';
try {
const [hero, front, detail] = await shots.result();
} catch (error) {
if (!(error instanceof RenderSetRefused)) throw error;
error.refusals; // [{ camera: 'detail', error: { code: 'RenderBudgetExceeded', … } }]
for await (const image of shots.images()) show(image.camera, image.url);
}To render every variant of a product from every camera, give the same list to
renderVariants(). See Render every variant.
Match the image size to your viewport
The image has your camera's shape unless you say otherwise, so a 16:9
viewport renders 16:9, framed as you see it. Leave out width and height
for the quality's long edge, or give one side and the other follows.
await bakery3.render(); // 2048 × 1152 from a 16:9 camera
await bakery3.render({ height: 1080 }); // 1920 × 1080Give both sides in another shape and the picture keeps the camera's height of
view, so it shows more or less at the sides. With cameras, every picture in
the set has the first camera's shape.
Depth of field
Mark the camera with an f-number and the point to keep sharp. The render blurs what is nearer and farther than that point.
camera.userData.bakery3 = {
focus: { fStop: 2, target: product.position },
};
await bakery3.render();A smaller fStop blurs more: 4 is a light blur, 2 a clear one, 1.2 a
strong one. It behaves like a full-frame camera at your camera's fov, so a
narrower fov blurs more at the same number.
target is a point in world space, as an array or a Vector3. In a video the
point stays sharp while the camera moves. To focus at a fixed distance
instead, give distance in meters in place of target.
Your Three.js viewport stays sharp, because Three.js has no depth of field of
its own. Remove the focus mark to render everything sharp again.
Animate the camera
You can animate the camera's position, lookAt and fov, and orbit it around
your product. See Render video.
Frame a product so it fits
Place the camera from the product's bounding box instead of a hand-tuned position. The framing then still works when the model is swapped for a bigger one.
import { Box3, Vector3 } from 'three';
const box = new Box3().setFromObject(product);
const center = box.getCenter(new Vector3());
const radius = box.getSize(new Vector3()).length() / 2;
const distance = radius / Math.sin((camera.fov * Math.PI) / 360);
const direction = new Vector3(0.6, 0.4, 1).normalize();
camera.position.copy(center).addScaledVector(direction, distance);
camera.lookAt(center);
camera.updateMatrixWorld();
await bakery3.render();