Skip to content

@typegpu/kitenvironment

Environment light

Bake an HDR panorama, a cube map or a sky into reflections and diffuse light that light every shaded surface.

kit.environment

module

environment.create(root, { size?, … }?) → Environment

environment.stars(options?)

environment.background(lightFrom, options?) → Compound

environment.clip(image, ceiling?) → Clipped

rootborrows
the root
size?takes
the side of each cube face, which sets memory and bake time
Environmentreturns
an environment you bake and destroy
lightFromtakes
a 'use gpu' function from a direction to its light, such as environment.backdrop
Compoundreturns
a drawing for scene.add
imagetakes
a panorama in equirectangular projection, as environment.add reads it
ceiling?takes
the brightest a channel stays, in the panorama's linear units
Clippedreturns
the clipped copy, and the strongest source's direction, colour and intensity
GPU functions
turnreflectionirradianceoccludedambientbackdropgradientsunColordaylightsunDiscsunAt
schemas
SettingsStar
constants
defaults
types
LightFromPictureSourcePreparedEnvironmentClipped
Optionscreate · 3 fields
size?

number= 256

Sets the side of each cube face in texels, a power of two from 16 to 2048.

samples?

number= 64

Sets the samples per texel for rough reflections, from 1.

settings?

Partial<d.InferInput<typeof Settings>>= defaults, with intensity 1, rotation 0 and blur 0

Sets initial values over defaults.

StarsOptionsstars · 3 fields
cells?

number= 96

Sets the cells along each edge of a cube around the eye; each holds a star or none.

chance?

number= 0.2

Sets the share of cells that hold a star.

size?

number= 0.8

Sets the radius of a star's disc in pixels, at any resolution.

BackgroundOptionsbackground · 1 field
label?

string= 'environment background'

Labels its pipelines in GPU captures.

records
  • bakeencoder
  • build().drawrender

bake(encoder, source) when the source changes, before the passes that shade: one compute pass with a dispatch per mip level of both cubes and one for the harmonics.

owns

2 textures ·2 buffers ·a sampler ·5 pipelines and one per added function ·bind groups

The radiance and reflection cubes, the settings and the harmonics; the kernels that read a panorama, read a cube, halve the radiance, blur the reflections and sum the harmonics; a group per mip level and per added texture. Both cubes hold the mirror level, since a dispatch can't sample a texture it writes.

lifecycle

initAsync()destroy()[Symbol.dispose]

injects
  • definition

    The cube, the harmonics and the settings that environment.reflection, irradiance, occluded, backdrop and ambient read.

binds
  • 2 uniform

    sky.ambient and the environment's functions, the settings and the harmonics, beside the reflection cube and a filtering sampler.

needs
  • passes

    As build(into); fills accessors in its pipelines.

Drag to look round. Each sphere reads the reflections at its roughness, and the matte one the diffuse light. Move the time of day to re-bake the daylight; blur reads a rougher level behind the scene without a bake.

A lighting lights a surface from its sun and a two-colour sky. A real surface takes light from everywhere around it: a mirror shows the sky and the ground, brushed metal a blur of them, and a matte surface the sum of a whole hemisphere. Reading a panorama in every direction for every pixel is too slow for a frame, so an environment bakes it first. A bake turns the panorama into a cube of reflections at six roughnesses and nine numbers of diffuse light. Shading then reads one texel and sums nine terms.

import {
import environment
environment
,
import lighting
lighting
,
import images
images
} from '@typegpu/kit';
const
const file: Response
file
= await
function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
fetch
('/skies/meadow.hdr');
const
const meadow: images.Texture<[number, number]>
meadow
=
import images
images
.
function create(root: TgpuRoot, picture: images.Picture, options?: images.Options): images.Texture

Creates a sampled texture from one image. 8-bit images become rgba8unorm-srgb, or rgba8unorm with color: 'linear'. Half-float images, and 8-bit ones with packed, become format. Throws if the pixel count doesn't match the size, the image is empty or larger than the device allows, packed comes with color, or format with 8-bit colour or data.

@param ― picture pixels from decode, decodeHdr or your own code, or a bitmap

@returns ― a texture you destroy, with every mip level unless mips is false

create
(
const root: TgpuRoot
root
,
import images
images
.
function decodeHdr(bytes: Uint8Array): images.HdrImage

Decodes a Radiance .hdr file into linear half floats for an rgba16float texture. It reads flat and run-length encoded RGBE scanlines in the standard -Y +X order, and clamps values above 65 504, a half float's largest. It runs on any thread, a React Native worker included. Throws for another orientation, an XYZE file, a side above 16 384 or a truncated or corrupt file.

@param ― bytes the whole file

@returns ― pixels for create

decodeHdr
(new
var Uint8Array: Uint8ArrayConstructor
new <ArrayBuffer>(buffer: ArrayBuffer, byteOffset?: number, length?: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(await
const file: Response
file
.
Body.arrayBuffer(): Promise<ArrayBuffer>
arrayBuffer
())));
const
const sky: environment.Environment
sky
=
import environment
environment
.
function create(root: TgpuRoot, options?: environment.Options): environment.Environment

Creates an environment: a cube of radiance, a cube of reflections and nine spherical harmonics of diffuse light, filled by bake from any source add prepared. Lit pipelines read it through sky.ambient as the lighting's ambient hook; sky.background() draws it behind the scene. Throws for a size that is not a power of two from 16 to 2048, or samples below 1.

@injects ― definition the cube, the harmonics and the settings that environment.reflection, irradiance, occluded, backdrop and ambient read

@binds ― 2 uniform: sky.ambient and the environment's functions, the settings and the harmonics, beside the reflection cube and a filtering sampler

@owns ― 2 textures, 2 buffers, a sampler, 5 pipelines and one per added function, bind groups: the radiance and reflection cubes, the settings and the harmonics; the kernels that read a panorama, read a cube, halve the radiance, blur the reflections and sum the harmonics; a group per mip level and per added texture. Both cubes hold the mirror level, since a dispatch can't sample a texture it writes

@records ― bake(encoder, source) when the source changes, before the passes that shade: one compute pass with a dispatch per mip level of both cubes and one for the harmonics

@param ― options.size the side of each cube face, which sets memory and bake time

@returns ― an environment you bake and destroy

create
(
const root: TgpuRoot
root
, {
Options.settings?: Partial<InferInput<WgslStruct<{
intensity: F32;
rotation: F32;
blur: F32;
}>>> | undefined

Sets initial values over defaults. Default defaults, with intensity 1, rotation 0 and blur 0.

settings
: {
blur: number
blur
: 0.1 } });
const
const panorama: environment.Prepared
panorama
=
const sky: environment.Environment
sky
.
Environment.add(source: environment.Source): environment.Prepared

Makes a source ready to bake. A 2D texture is read as a panorama in equirectangular projection and a 6-layer texture as a cube, each through a bind group; a function gets a kernel of its own. Throws for other layer counts.

add
(
const meadow: images.Texture<[number, number]>
meadow
);
await
const sky: environment.Environment
sky
.
Environment.initAsync(): Promise<void>

Compiles the bake kernels and those of every added function.

initAsync
();
const
const light: lighting.Lighting
light
=
import lighting
lighting
.
function create<{
readonly shading: {
readonly ambient: (material: Material, point: Point) => v3f;
};
}>(root: TgpuRoot, options?: ({
readonly shading: {
readonly ambient: (material: Material, point: Point) => v3f;
};
} & lighting.Options & {
readonly shading: {
readonly ambient: (material: Material, point: Point) => v3f;
};
}) | undefined): lighting.Lighting

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
shading: {
readonly ambient: (material: Material, point: Point) => v3f;
} & lighting.Shading & {
readonly ambient: (material: Material, point: Point) => v3f;
}

Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog, filtered and reaching.

shading
: {
ambient: ((material: Material, point: Point) => v3f) & AmbientHook

Computes ambient light. Default shading.hemisphere.

ambient
:
const sky: environment.Environment
sky
.
Environment.ambient: (material: Material, point: Point) => v3f

Fills lighting's ambient hook with diffuse light and reflections from this environment: lighting.create(root, { shading: { ambient: sky.ambient } }).

ambient
} });
const
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
=
import passes
passes
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}, {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>(root: TgpuRoot, screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}>, writes: {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}, options?: passes.TargetOptions<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}> | undefined): passes.Passes<...> (+1 overload)

Describes the targets and outputs for scene passes. With a surface such as the canvas, writes only each renderer's colour. With a target set, writes colour into the target marked color, depth into the target marked depth, and other outputs into their named targets. Multisampled targets resolve according to resolve. Targets with layers draw one pass per layer, and every target the description draws into needs the same layers. add builds a renderer's drawing. begin starts a pass. pipe supplies fills such as a camera.

@param ― screen the surface, or the targets, which give the formats; each one the description names is drawn into

@param ― writes what fills each target, by its key in screen

@returns ― a description you destroy, which holds the pipelines it builds

create
(
const root: TgpuRoot
root
,
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}>
screen
, {
hdr: typeof passes.color
hdr
:
import passes
passes
.
const color: typeof passes.color

Marks the target that receives each renderer's colour.

color
,
depth: typeof passes.depth
depth
:
import passes
passes
.
const depth: typeof passes.depth

Marks the depth target.

depth
})
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

Adds fill after existing fills and returns a description of the same passes. Piped fills take precedence over a drawing's own. For example, passes.create(…).pipe(view.inject()) uses the camera from view.

pipe
(
const view: cameras.OrbitCamera
view
.
CameraBase.inject(): Inject

Fills cameras.current for the pipelines created from the configuration it is piped into.

inject
())
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

Adds fill after existing fills and returns a description of the same passes. Piped fills take precedence over a drawing's own. For example, passes.create(…).pipe(view.inject()) uses the camera from view.

pipe
(
const light: lighting.Lighting
light
.
Lighting.inject(): Inject

Fills the shading hooks and resources for pipelines created from the configuration you pipe it into.

inject
());
// After the opaque drawings: it fills only the pixels they leave
const
const behind: passes.Drawn
behind
=
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.add<never, never>(this: passes.Passes<passes.Fed<never, never>>, drawing: passes.Compound<never, never>): passes.Drawn (+1 overload)

Builds a pipeline for drawing that writes every described output and returns its draw methods. If the drawing cannot supply the fields an output needs, type checking fails with that output's name. Use without to leave it out or instead to replace it.

add
(
const sky: environment.Environment
sky
.
Environment.background(options?: environment.BackgroundOptions): passes.Compound

Draws the environment at settings.blur where nothing else drew, as environment.background does. Add it after the opaque drawings.

background
());
let
let baked: boolean
baked
= false;
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
if (!
let baked: boolean
baked
)
const sky: environment.Environment
sky
.
Environment.bake(encoder: TgpuCommandEncoder, source: environment.Prepared, options?: Timed): void

Records the bake of a source added to this environment into one compute pass of encoder. Pipelines that read the environment see it in later passes.

bake
(
frame: frames.Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
,
const panorama: environment.Prepared
panorama
);
let baked: boolean
baked
= true;
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.record(frame: frames.Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}> | undefined): void

Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the list to avoid allocating each frame. Call begin yourself when you need to choose drawings per frame. Records nothing while enabled is false.

record
(
frame: frames.Frame
frame
, [
const behind: passes.Drawn
behind
]); // after the scene's opaque drawings
}),
);
  • sky.ambient is the lighting’s ambient hook. It replaces the sky gradient’s light with the environment’s diffuse light and reflections, at each material’s roughness. The sun still lights and shadows as before.
  • bake records one compute pass. Shading in later passes of the frame reads the new light.

add reads one of three sources and builds the kernel that bakes it. Add every source while loading, then bake the one to show:

  • A 2D texture is a panorama in equirectangular projection, such as a decoded .hdr file from images. It has +Y up, its centre facing +X and its left quarter facing −Z, as Blender writes it.
  • A texture of six layers is a cube map in WebGPU’s face order.
  • A 'use gpu' function from a direction to its light is a sky you write. Pipe its fills in, as tgpu.fn(f).pipe(light.inject()) does for one that reads the lighting.

A panorama needs one bake. Bake a function again when what it reads changes.

environment.gradient is the lighting’s sky gradient as a source: baked, it lights a matte surface as shading.hemisphere does while skyBottom is at least a fifth of skyTop, and adds reflections of the same colours.

environment.daylight is a sky from the lighting’s sun. It scatters sunlight through air and ozone, so the sky is blue by day, bright at the horizon and red at sunset. The lighting’s sky gradient adds to it at every hour and is all that remains once the sun sets. Keep skyTop and skyBottom at a night sky’s dark colours, or they tint the day:

const
const light: lighting.Lighting
light
=
import lighting
lighting
.
function create<{
readonly shading: {
readonly ambient: (material: Material, point: Point) => d.v3f;
};
}>(root: TgpuRoot, options?: ({
readonly shading: {
readonly ambient: (material: Material, point: Point) => d.v3f;
};
} & lighting.Options & {
readonly shading: {
readonly ambient: (material: Material, point: Point) => d.v3f;
};
}) | undefined): lighting.Lighting

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
shading: {
readonly ambient: (material: Material, point: Point) => d.v3f;
} & lighting.Shading & {
readonly ambient: (material: Material, point: Point) => d.v3f;
}

Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog, filtered and reaching.

shading
: {
ambient: ((material: Material, point: Point) => d.v3f) & AmbientHook

Computes ambient light. Default shading.hemisphere.

ambient
:
const sky: environment.Environment
sky
.
Environment.ambient: (material: Material, point: Point) => d.v3f

Fills lighting's ambient hook with diffuse light and reflections from this environment: lighting.create(root, { shading: { ambient: sky.ambient } }).

ambient
} });
const
const daylight: environment.Prepared
daylight
=
const sky: environment.Environment
sky
.
Environment.add(source: environment.Source): environment.Prepared

Makes a source ready to bake. A 2D texture is read as a panorama in equirectangular projection and a 6-layer texture as a cube, each through a bind group; a function gets a kernel of its own. Throws for other layer counts.

add
(
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
fn: <(direction: d.v3f) => d.v3f>(inner: (direction: d.v3f) => d.v3f) => TgpuGenericFn<(direction: d.v3f) => d.v3f> (+2 overloads)
fn
(
import environment
environment
.
const daylight: (direction: d.v3f) => d.v3f

Returns the light of a clear sky from Rayleigh and Mie scattering with ozone, lit by the lighting's sun: sunDirection, and sunIntensity above the atmosphere. Below the horizon it shows dark ground through haze. The lighting's sky gradient adds beneath, so it alone remains at night. It draws no sun disc. Pipe light.inject() into it.

daylight
).
TgpuGenericFn<(direction: d.v3f) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(direction: d.v3f) => d.v3f>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const light: lighting.Lighting
light
.
Lighting.inject(): Inject

Fills the shading hooks and resources for pipelines created from the configuration you pipe it into.

inject
()));
let
let moved: boolean
moved
= true;
const
const place: (hour: number) => void
place
= (
hour: number
hour
: number) => {
const
const sun: d.v3f
sun
=
import environment
environment
.
const sunAt: (hour: number, latitude: number, declination: number) => d.v3f

Returns a unit direction towards the sun at hour, from 0 to 24 in local solar time, at latitude and with the sun's declination, both in radians: about 0.41 at midsummer in the north, 0 at the equinoxes and −0.41 at midwinter. +X points east, +Y up and +Z south, so the sun rises in +X and, north of the tropics, stands in +Z at noon. Turn the compass with environment.turn. Below the horizon the direction points down. It runs on the CPU and in shaders.

sunAt
(
hour: number
hour
, 0.7, 0.3); // 40° north in early summer
const
const color: d.v3f
color
=
import environment
environment
.
const sunColor: (direction: d.v3f) => d.v3f

Returns the colour of sunlight at the ground with the sun in direction, for a sun of intensity 1 above the atmosphere: white overhead, orange low and black below the horizon. Set the lighting's sunColor to it with daylight. It runs on the CPU and in shaders.

sunColor
(
const sun: d.v3f
sun
);
const light: lighting.Lighting
light
.
Lighting.settings: TgpuUniform<d.WgslStruct<{
sunDirection: d.Vec3f;
sunIntensity: d.F32;
sunColor: d.Vec3f;
skyIntensity: d.F32;
skyTop: d.Vec3f;
fogDensity: d.F32;
skyBottom: d.Vec3f;
fogFalloff: d.F32;
fogColor: d.Vec3f;
skyReflection: d.F32;
fogStart: d.F32;
}>>

Holds the sun, sky and fog values from defaults and the settings option. Use patch to upload only the fields you supply. The shadow's next record follows a sunDirection written here.

settings
.
TgpuBufferBindingBase<WgslStruct<{ sunDirection: Vec3f; sunIntensity: F32; sunColor: Vec3f; skyIntensity: F32; skyTop: Vec3f; fogDensity: F32; skyBottom: Vec3f; fogFalloff: F32; fogColor: Vec3f; skyReflection: F32; fogStart: F32; }>>.patch(data: {
sunDirection?: d.InferInput<d.Vec3f> | undefined;
sunIntensity?: number | undefined;
sunColor?: d.InferInput<d.Vec3f> | undefined;
skyIntensity?: number | undefined;
skyTop?: d.InferInput<d.Vec3f> | undefined;
fogDensity?: number | undefined;
skyBottom?: d.InferInput<d.Vec3f> | undefined;
fogFalloff?: number | undefined;
fogColor?: d.InferInput<d.Vec3f> | undefined;
skyReflection?: number | undefined;
fogStart?: number | undefined;
} | undefined): void
patch
({
sunDirection?: d.InferInput<d.Vec3f> | undefined

Points towards the sun. It can have any length; shading normalizes it.

sunDirection
:
const sun: d.v3f
sun
,
sunColor?: d.InferInput<d.Vec3f> | undefined

Sets the sun colour in linear colour space.

sunColor
:
const color: d.v3f
color
});
let moved: boolean
moved
= true;
};
const
const rebake: clock.Cadence
rebake
=
import clock
clock
.
function cadence(options: clock.CadenceOptions): clock.Cadence

Schedules work every every frames or hz times per second on average. Reports the time since its last run. With hz, it runs in the frame closest to each scheduled tick. At 25 Hz and 60 fps, gaps alternate between two and three frames. Throws unless exactly one of every and hz is set. every must be an integer above 0, and hz must be finite and above 0.

@returns ― a cadence whose due says whether the work runs this frame

cadence
({
hz: number

Runs work hz times per second on average. Default none: set this or every.

hz
: 15 });
const
const frame: (frame: Frame) => void
frame
= (
frame: Frame
frame
:
(alias) interface Frame
import Frame

Represents one frame being recorded. Each run creates a separate object, so modules can compare frames by identity.

Frame
) => {
if (
let moved: boolean
moved
&&
const rebake: clock.Cadence
rebake
.
Cadence.due(dt: number): boolean

Adds dt seconds and returns whether work is due this frame. Returns true on the first call.

due
(
frame: Frame
frame
.
Frame.dt: number

Reports seconds since the previous submitted frame. The first frame has a dt of zero.

dt
)) {
const sky: environment.Environment
sky
.
Environment.bake(encoder: TgpuCommandEncoder, source: environment.Prepared, options?: Timed): void

Records the bake of a source added to this environment into one compute pass of encoder. Pipelines that read the environment see it in later passes.

bake
(
frame: Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
,
const daylight: environment.Prepared
daylight
);
let moved: boolean
moved
= false;
}
};
  • environment.sunAt(hour, latitude, declination) points to the sun at an hour of local solar time, with the latitude and the sun’s declination in radians: about 0.41 at midsummer in the north, 0 at the equinoxes. +X is east, +Y up and +Z south. Turn the compass with environment.turn.
  • sunIntensity is the sun above the air. environment.sunColor(direction) runs on the CPU and returns how much of it reaches the ground, reddened near the horizon and 0 below it. Set the lighting’s sunColor to it, so the sun lights surfaces as the sky shows it.
  • The sky’s brightness follows sunIntensity. The air dims a low sun far more than the sky around it, so expose for sunColor times sunIntensity.
  • Re-bake while the sun moves, at a capped rate as above.
  • environment.sunDisc(direction, brightness) returns the sun’s disc. Add it to a background of your own, below.

sky.settings holds intensity, rotation and blur. Shading reads them at every lookup, so changing one needs no bake.

  • intensity scales the diffuse light, the reflections and the background.
  • rotation turns the environment about the vertical, in radians, carrying +X towards +Z. Turn the lighting’s sun with it: environment.turn(direction, rotation) turns a direction the same way, on the CPU or the GPU.
  • blur is the roughness the background draws at, from 0 for the sharp panorama to 1.

Your own shaders read the environment through its functions. Fill them with sky.inject(). sky.ambient carries its own fills, so a lighting that takes it needs nothing more:

  • environment.reflection(direction, roughness) returns the reflection along direction at roughness.
  • environment.irradiance(normal) returns the diffuse light on a surface facing normal. A white matte surface under a uniform environment of radiance 1 gets 1.
  • environment.occluded(material, point, visibility) is the ambient light sky.ambient gives, darkened by a visibility from 0 to 1 on top of the material’s occlusion. Rough reflections dim as diffuse light does. Sharp ones dim less where the surface faces the eye and more at grazing angles. Give it ambient occlusion, recorded before the passes that shade:
const
const shaded: (material: shading.Material, point: shading.Point) => v3f
shaded
= (
material: {
color: v3f;
roughness: number;
emission: v3f;
metallic: number;
occlusion: number;
}
material
:
import shading
shading
.
type Material = {
color: v3f;
roughness: number;
emission: v3f;
metallic: number;
occlusion: number;
}

This schema describes a surface with the glTF metallic-roughness model.

Material
,
point: {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}
point
:
import shading
shading
.
type Point = {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}

This schema describes a point being shaded in world space.

Point
) => {
'use gpu';
return
import environment
environment
.
const occluded: (material: shading.Material, point: shading.Point, visibility: number) => v3f

Computes diffuse light and reflections from the environment, with visibility from 0 to 1, such as ao.at(point.position), darkening both on top of the material's occlusion. Reflections look along shading.reflectionOf, scaled by the lighting's skyReflection, and both shares go through shading.surrounded, as shading.hemisphere's do.

occluded
(
material: {
color: v3f;
roughness: number;
emission: v3f;
metallic: number;
occlusion: number;
}
material
,
point: {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}
point
,
import ao
ao
.
const at: (position: v3f) => number

Returns the visibility of sky light at a world position, from 0 in a closed crease to 1 in the open. It projects the position through cameras.current and blends the 4 nearest texels whose eye distance matches. Where none matches, such as on a translucent surface in front of the opaque one, it returns 1. Call it from shading in a pipeline piped with ao.inject(), such as an ambient hook.

at
(
point: {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}
point
.
position: v3f
position
));
};
const
const light: lighting.Lighting
light
=
import lighting
lighting
.
function create<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>;
};
}>(root: TgpuRoot, options?: ({
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>;
};
} & lighting.Options & {
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>;
};
}) | undefined): lighting.Lighting

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>;
} & lighting.Shading & {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>;
}

Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog, filtered and reaching.

shading
: {
ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f> & shading.AmbientHook

Computes ambient light. Default shading.hemisphere.

ambient
:
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
fn: <(material: shading.Material, point: shading.Point) => v3f>(inner: (material: shading.Material, point: shading.Point) => v3f) => TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f> (+2 overloads)
fn
(
const shaded: (material: shading.Material, point: shading.Point) => v3f
shaded
).
TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const sky: environment.Environment
sky
.
Environment.inject(): Inject

Fills environment.reflection, irradiance, occluded, backdrop and ambient for pipelines created from the configuration it is piped into.

inject
()).
TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const occlusion: ao.Ao
occlusion
.
Ao.inject(): Inject

Fills ao.at for pipelines created from the configuration it is piped into.

inject
()) },
});

Reflections follow the lighting’s skyReflection, as shading.hemisphere’s do.

sky.background() is a drawing that fills every pixel left uncovered with the environment at blur, through the scene’s camera. It lies on the far plane and tests depth without writing it, so add it after the opaque drawings and before translucent ones. Covered pixels skip its shader.

  • It writes only colour. Every other output keeps its clear value, as where nothing drew, so TAA finds the sky’s motion from depth and the camera.
  • Depth must clear to 1, as it does by default.
  • { label } names its pipelines in GPU captures.

For a background of your own, pass a 'use gpu' function from a direction to its light to environment.background, with its fills piped in. This one adds the sun’s disc to the daylight:

const
const withSun: (direction: d.v3f) => d.v3f
withSun
= (
direction: d.v3f
direction
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
) => {
'use gpu';
return
import environment
environment
.
const backdrop: (direction: d.v3f) => d.v3f

Returns the environment in direction at settings.blur, as sky.background() draws it.

backdrop
(
direction: d.v3f
direction
) +
import environment
environment
.
const sunDisc: (direction: d.v3f, brightness: number) => d.v3f

Returns the sun's disc in direction, 0.5° across with a soft edge, at the lighting's sun colour and intensity times brightness. Add it to a background's function. Pipe light.inject() into it.

sunDisc
(
direction: d.v3f
direction
, 40);
};
const
const behind: Compound<never, never>
behind
=
import environment
environment
.
function background(lightFrom: environment.LightFrom, options?: environment.BackgroundOptions): Compound

Makes a drawing that fills every pixel left uncovered with the light from its direction through the scene's camera. It lies on the far plane and tests depth without writing it, so add it after the opaque drawings, where early depth testing skips covered pixels, and before translucent ones. It writes only colour: every other output, such as TAA's motion, keeps its clear value, as where nothing drew. Depth must clear to 1, as it does by default. Pipe the function's fills in, such as tgpu.fn(f).pipe(sky.inject()).

@param ― lightFrom a 'use gpu' function from a direction to its light, such as environment.backdrop

@returns ― a drawing for scene.add

background
(
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
fn: <(direction: d.v3f) => d.v3f>(inner: (direction: d.v3f) => d.v3f) => TgpuGenericFn<(direction: d.v3f) => d.v3f> (+2 overloads)
fn
(
const withSun: (direction: d.v3f) => d.v3f
withSun
).
TgpuGenericFn<(direction: d.v3f) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(direction: d.v3f) => d.v3f>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const sky: environment.Environment
sky
.
Environment.inject(): Inject

Fills environment.reflection, irradiance, occluded, backdrop and ambient for pipelines created from the configuration it is piped into.

inject
()).
TgpuGenericFn<(direction: d.v3f) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(direction: d.v3f) => d.v3f>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const light: lighting.Lighting
light
.
Lighting.inject(): Inject

Fills the shading hooks and resources for pipelines created from the configuration you pipe it into.

inject
()), {
BackgroundOptions.label?: string | undefined

Labels its pipelines in GPU captures. Default 'environment background'.

label
: 'sky',
});

environment.stars({ cells, chance, size }) returns a function that finds the star in a direction: at most one per cell of a cube around the eye, as a soft disc of radius size in pixels. It gives the disc’s coverage and three random values fixed per star, to shape its brightness, tint or twinkle. Call it only in a fragment shader, such as a background’s, since the disc reads derivatives:

const
const starAt: (direction: d.v3f) => d.Infer<typeof environment.Star>
starAt
=
import environment
environment
.
function stars(options?: environment.StarsOptions): (direction: d.v3f) => d.Infer<typeof environment.Star>

Returns a 'use gpu' function that finds the star in a direction: at most one per cell of a cube around the eye, with a Gaussian disc of radius size pixels wherever the direction is drawn. Shape the light from its seed, as in tint * pow(star.seed.x, 6) * star.disc. Call it only in a fragment shader, since the disc's size reads derivatives.

@returns ― a 'use gpu' function from a unit direction to the Star there

stars
({
StarsOptions.cells?: number | undefined

Sets the cells along each edge of a cube around the eye; each holds a star or none. Default 96.

cells
: 96,
StarsOptions.chance?: number | undefined

Sets the share of cells that hold a star. Default 0.2.

chance
: 0.2 });
const
const night: Compound<never, never>
night
=
import environment
environment
.
function background(lightFrom: environment.LightFrom, options?: environment.BackgroundOptions): Compound

Makes a drawing that fills every pixel left uncovered with the light from its direction through the scene's camera. It lies on the far plane and tests depth without writing it, so add it after the opaque drawings, where early depth testing skips covered pixels, and before translucent ones. It writes only colour: every other output, such as TAA's motion, keeps its clear value, as where nothing drew. Depth must clear to 1, as it does by default. Pipe the function's fills in, such as tgpu.fn(f).pipe(sky.inject()).

@param ― lightFrom a 'use gpu' function from a direction to its light, such as environment.backdrop

@returns ― a drawing for scene.add

background
((
direction: d.v3f
direction
) => {
'use gpu';
const
const star: {
disc: number;
seed: d.v3f;
}
star
=
const starAt: (direction: d.v3f) => d.Infer<typeof environment.Star>
starAt
(
direction: d.v3f
direction
);
const
const tint: d.v3f
tint
=
import std
std
.
mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
export mix
mix
(
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(1, 0.85, 0.7),
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0.7, 0.8, 1),
const star: {
disc: number;
seed: d.v3f;
}
star
.
seed: d.v3f

Holds three random values from 0 to 1, fixed per star, for its brightness, tint and phase.

seed
.
v3f.y: number
y
);
return
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0.002, 0.003, 0.01) +
const tint: d.v3f
tint
* (
import std
std
.
function pow(base: number, exponent: number): number (+1 overload)
export pow
pow
(
const star: {
disc: number;
seed: d.v3f;
}
star
.
seed: d.v3f

Holds three random values from 0 to 1, fixed per star, for its brightness, tint and phase.

seed
.
v3f.x: number
x
, 6) *
const star: {
disc: number;
seed: d.v3f;
}
star
.
disc: number

Rises from 0 to 1 at the centre of a star's disc, 0 where no star is.

disc
);
});

A photographed sun is far brighter than its sky. Baked, it makes a mirror’s glint but no shadow, and its diffuse light rings: nine numbers can’t hold so small a source, so the side facing away from it darkens. environment.clip lowers the panorama’s brightest texels and returns the light it took from the strongest source, in the lighting’s units. Give that back as the lighting’s sun, with its shadow:

const {
const image: images.HdrImage

Holds a copy of the panorama with no channel above the ceiling.

image
,
const direction: [number, number, number]

Points towards the source in the panorama's frame, before rotation.

direction
,
const color: [number, number, number]

Holds the source's linear colour, with its largest channel 1.

color
,
const intensity: number

Holds the source's light as the lighting's sunIntensity, 0 when nothing was clipped.

intensity
} =
import environment
environment
.
function clip(image: images.HdrImage, ceiling?: number): environment.Clipped

Lowers every channel of a panorama above ceiling to it, and returns the light it took from the strongest source, such as a photographed sun, in the lighting's units. Give that back as the lighting's sun: it then shadows, and the baked diffuse light doesn't ring. Clipped texels within 20° of a brighter one count as one source. It runs on the CPU, in time proportional to the pixels. Throws for a ceiling outside 2^-14 to 65 504.

@param ― image a panorama in equirectangular projection, as environment.add reads it

@param ― ceiling the brightest a channel stays, in the panorama's linear units

@returns ― the clipped copy, and the strongest source's direction, colour and intensity

clip
(
const meadow: images.HdrImage
meadow
);
const
const panorama: environment.Prepared
panorama
=
const sky: environment.Environment
sky
.
Environment.add(source: environment.Source): environment.Prepared

Makes a source ready to bake. A 2D texture is read as a panorama in equirectangular projection and a 6-layer texture as a cube, each through a bind group; a function gets a kernel of its own. Throws for other layer counts.

add
(
import images
images
.
function create(root: TgpuRoot, picture: images.Picture, options?: images.Options): images.Texture

Creates a sampled texture from one image. 8-bit images become rgba8unorm-srgb, or rgba8unorm with color: 'linear'. Half-float images, and 8-bit ones with packed, become format. Throws if the pixel count doesn't match the size, the image is empty or larger than the device allows, packed comes with color, or format with 8-bit colour or data.

@param ― picture pixels from decode, decodeHdr or your own code, or a bitmap

@returns ― a texture you destroy, with every mip level unless mips is false

create
(
const root: TgpuRoot
root
,
const image: images.HdrImage

Holds a copy of the panorama with no channel above the ceiling.

image
));
const
const light: lighting.Lighting
light
=
import lighting
lighting
.
function create<{
readonly shading: {
readonly ambient: (material: Material, point: Point) => v3f;
};
readonly settings: {
readonly sunDirection: [number, number, number];
readonly sunColor: [number, number, number];
readonly sunIntensity: number;
};
}>(root: TgpuRoot, options?: ({
readonly shading: {
readonly ambient: (material: Material, point: Point) => v3f;
};
readonly settings: {
readonly sunDirection: [number, number, number];
readonly sunColor: [number, number, number];
readonly sunIntensity: number;
};
} & lighting.Options & {
...;
}) | undefined): lighting.Lighting

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
shading: {
readonly ambient: (material: Material, point: Point) => v3f;
} & lighting.Shading & {
readonly ambient: (material: Material, point: Point) => v3f;
}

Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog, filtered and reaching.

shading
: {
ambient: ((material: Material, point: Point) => v3f) & AmbientHook

Computes ambient light. Default shading.hemisphere.

ambient
:
const sky: environment.Environment
sky
.
Environment.ambient: (material: Material, point: Point) => v3f

Fills lighting's ambient hook with diffuse light and reflections from this environment: lighting.create(root, { shading: { ambient: sky.ambient } }).

ambient
},
settings: {
readonly sunDirection: [number, number, number];
readonly sunColor: [number, number, number];
readonly sunIntensity: number;
} & ((Partial<InferInput<WgslStruct<{
sunDirection: Vec3f;
sunIntensity: F32;
sunColor: Vec3f;
skyIntensity: F32;
skyTop: Vec3f;
fogDensity: F32;
skyBottom: Vec3f;
fogFalloff: F32;
fogColor: Vec3f;
skyReflection: F32;
fogStart: F32;
}>>> | undefined) & {
readonly sunDirection: [number, number, number];
readonly sunColor: [number, number, number];
readonly sunIntensity: number;
})

Sets initial sun, sky and fog values over defaults. Default defaults, which has no fog.

settings
: {
sunDirection: [number, number, number]
sunDirection
:
const direction: [number, number, number]

Points towards the source in the panorama's frame, before rotation.

direction
,
sunColor: [number, number, number]
sunColor
:
const color: [number, number, number]

Holds the source's linear colour, with its largest channel 1.

color
,
sunIntensity: number
sunIntensity
:
const intensity: number

Holds the source's light as the lighting's sunIntensity, 0 when nothing was clipped.

intensity
},
});
  • clip(image, ceiling) lowers every channel above ceiling, 30 by default, and takes what was above it. Clipped texels within 20° of a brighter one count as one source.
  • direction is in the panorama’s frame. Turn it with environment.turn(d.vec3f(...direction), rotation).
  • A panorama with two light sources of about equal light, such as two softboxes, gives back one of them. The other’s clipped light is lost.

sky.destroy() frees the settings, both cubes and the harmonics buffer. The textures you added stay yours. Functions that read the environment then throw The environment was destroyed. when a pipeline compiles.

  • A bake runs in one compute pass and can’t be split across frames. Re-bake a moving sky at a capped rate.
  • Reflections come from infinitely far away. They don’t move as a surface moves, and they don’t show the scene itself.
  • Nine numbers of diffuse light hold only broad changes. A small, bright source rings; clip it and light with the sun.
  • Each pipeline reads one environment. Two environments in one frame need two sets of pipelines.
  • Each environment holds two cubes of 8 bytes a texel with their mips: about 8 MB at size 256, four times that at each doubling, and 537 MB at 2048. A bake’s time grows with the texels too.
  • The reflection cube has six roughness levels from size 256. Below that it has fewer, so the roughest keeps 8 texels a side.