Lighting in Apricot has three layers. Pick what the scene needs:
- A look (
<Scene look>): ready-made lights and a background. Most scenes need nothing else. - An environment (
<Scene environment>or<Environment>): what metals and glossy materials reflect, plus soft ambient light. - Your own lights: light components, or
<Daylight>for a sun that follows the time of day.
Looks#
look |
Background | Lights | Good for |
|---|---|---|---|
"soft" (default) |
warm cream #f6efe6 |
warm sky light, warm key from the top right, cool fill | Toys, emoji-style objects, friendly UI |
"studio" |
cool grey #eceff3 |
neutral sky light, key and rim light | Product viewers |
"flat" |
white #ffffff |
even ambient light, no shading or tone mapping | Cartoon or UI-like flat colour |
"none" |
none | none | Lighting it yourself: an environment, <Daylight> or light components |
import { Box, Scene } from "@apricot/react";
export const Soft = () => (
<Scene style={{ flex: 1 }}>
<Box radius={0.15} color="#ff9f59" />
</Scene>
);
export const Studio = () => (
<Scene style={{ flex: 1 }} look="studio">
<Box radius={0.15} material="paint" />
</Scene>
);look is read once, when the scene mounts.
Environments: reflections#
An environment is a procedural photo studio evaluated in the shader: a ceiling-to-floor gradient plus soft boxes of light. Metals and glossy paint reflect it, and it lights everything softly. There is no image to load or generate, so it costs nothing at startup.
environment |
Mood |
|---|---|
"none" (default) |
No reflections |
"studio" |
Dark studio with bright soft boxes: metals and cars |
"soft" |
Light and even: pale products, white phones, soft paint |
"sunset" |
A low warm sun, an orange horizon, a cool sky |
"night" |
Dark, with blue and pink neon strips and a thin light above |
For shiny objects, let the environment be the only light:
import { StudioFloor } from "@apricot/assets";
import { Scene, Sphere } from "@apricot/react";
export function Chrome() {
return (
<Scene style={{ flex: 1 }} look="none" environment="studio" background="#0e0f12">
<StudioFloor radius={6} colors={["#2a2d33", "#0e0f12"]} />
<Sphere radius={0.8} position={[0, 0.8, 0]} material="chrome" />
</Scene>
);
}Changing the environment at runtime#
<Scene environment> is fixed at mount. To switch, fade, dim or turn the studio, use <Environment> inside the scene. Everything about it is live and free per frame: switching names fades between them, and rotation and intensity take shared values.
import { Environment, Scene, Sphere } from "@apricot/react";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { useSharedValue } from "react-native-reanimated";
export function TurnTheLights({ night }: { night: boolean }) {
const turn = useSharedValue(0);
const start = useSharedValue(0);
const drag = Gesture.Pan()
.onBegin(() => {
start.value = turn.value;
})
.onUpdate((e) => {
turn.value = start.value + e.translationX / 120; // radians
});
return (
<GestureDetector gesture={drag}>
<Scene style={{ flex: 1 }} look="none" background="#0e0f12">
<Environment name={night ? "night" : "studio"} rotation={turn} transition={0.8} />
<Sphere radius={0.8} material="car-paint" />
</Scene>
</GestureDetector>
);
}Your own studio#
Describe one with sky (ceiling, horizon and floor colours) and boxes. Each box is { direction, size, intensity, color }: direction points from the object towards the light, size is the half width and half height in radians, and intensity above 1 makes highlights.
import { Environment, Scene, Sphere } from "@apricot/react";
export function CustomStudio() {
return (
<Scene style={{ flex: 1 }} look="none">
<Environment
sky={["#3b2f5c", "#1c1630", "#07060b"]}
boxes={[
{ direction: [0.4, 0.6, 0.7], size: [0.25, 0.12], intensity: 6, color: "#ffd9b0" },
{ direction: [-0.8, 0.1, 0.5], size: [0.06, 0.6], intensity: 4, color: "#9ab8ff" },
]}
/>
<Sphere radius={0.8} material="chrome" />
</Scene>
);
}To reuse a studio by name, register it once with defineEnvironment("brand", { sky, boxes, intensity }), then use environment="brand" or <Environment name="brand" />.
Light components#
Add lights on top of a look, or with look="none" as the only lights. intensity, position and target accept shared values, so lights can move and dim every frame for free.
| Component | Light | Key props (defaults) |
|---|---|---|
<AmbientLight> |
Even light from everywhere | color (white), intensity (1) |
<HemisphereLight> |
Sky colour from above, ground colour from below | sky (white), ground (warm grey), intensity |
<DirectionalLight> |
Sunlight: parallel rays | position ([3, 5, 4]), target ([0, 0, 0]), intensity, color, castShadow |
<PointLight> |
A bulb | position ([0, 2, 0]), distance (0: no limit), intensity, color |
<SpotLight> |
A cone of light | position, target, angle (0.5 rad half-angle), penumbra (0.3), distance |
import { DirectionalLight, HemisphereLight, Scene, Sphere } from "@apricot/react";
export function Lamp() {
return (
<Scene style={{ flex: 1 }} look="none" background="#ffffff">
<HemisphereLight sky="#ffe6cc" ground="#c83020" intensity={2} />
<DirectionalLight position={[-2, 4, 5]} intensity={2.1} color="#ffe8cc" />
<DirectionalLight position={[4, 1.5, 5]} intensity={0.9} color="#ffdcc0" />
<Sphere radius={0.8} color="#ff9f59" />
</Scene>
);
}Daylight: a sun that follows the clock#
<Daylight time> sets the sun, sky light and background for an hour of the day (0–24): night, dawn, noon, dusk. Use it with look="none". Plain values animate there over transition (1.5 s); shared values follow exactly, so a slider can scrub the day. With an environment, reflections dim at night too.
import { Ground, Trees } from "@apricot/assets";
import { Daylight, Scene } from "@apricot/react";
export function DayNight({ hour }: { hour: number }) {
return (
<Scene style={{ flex: 1 }} look="none" environment="soft" fog shadows camera={{ position: [0, 12, 30] }}>
<Daylight time={hour} />
<Ground />
<Trees count={80} />
</Scene>
);
}Options: sunrise (6) and sunset (19) hours, north (radians; where the sun rises), background (true: the sky colour becomes the background and fog colour) and keys (replace the day's colours, see DAY).
Shadows#
Turn them on with one prop. The main directional light casts them: the look's key light, <Daylight>'s sun, or the light marked <DirectionalLight castShadow>.
import { Box, Plane, Scene } from "@apricot/react";
export function Shadows() {
return (
<Scene style={{ flex: 1 }} shadows camera={{ position: [3, 3, 5] }}>
<Plane size={10} rotation={[-Math.PI / 2, 0, 0]} material="matte" color="#e8e1d7" />
<Box size={1} radius={0.1} position={[0, 0.5, 0]} />
</Scene>
);
}- Who casts and receives: everything casts and receives by default, except flat things (floors, planes, decals), which only receive, and see-through things, which do neither. Override per object with
castShadow/receiveShadowon shapes,Mesh,ModelandInstances. - Options:
shadows={{ size, softness, darkness, radius }}.size: map resolution, 1024 by default. 2048 is sharper and uses four times the memory.softness: edge softness in texels, 2 by default.darkness: 0 to 1, 0.85 by default.radius: for big scenes, the area in metres around what the camera looks at, for example{ radius: 120 }for a city.
- Cost: the shadow map is redrawn only when something that casts moves. Turning the camera around a still scene costs nothing extra. Each casting mesh is drawn again during a redraw, so instance repeated casters.
Fog and background#
fog:truefades objects between 30 and 90 units into the background colour.{ near, far, color }sets the range. It hides the edge of a large ground.background: any colour. Shown exactly as given, so the scene blends into your UI.transparent: no background at all (overlays).
Related#
- Lights reference: every prop,
Daylight,Environment, shadows. - Shapes and materials.
- Paths and labels: the Metro line scene uses daylight, fog and shadows together.