Skip to content

@typegpu/kitgltf

glTF models

Read or load glTF into CPU arrays for mesh pools and skins.

kit.gltf

cpu

gltf.read(source, { parts?, rotation?, placements, … }) → PlacedModelData

gltf.read(source, { parts?, rotation?, … }?) → ModelData

gltf.load(url, { fetch?, placements, … }) → Promise<PlacedModelData>

gltf.load(url, { fetch?, … }?) → Promise<ModelData>

gltf.draco(module) → Draco

sourcetakes
the file's bytes, from anywhere
parts?takes
names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin
rotation?takes
a turn of the whole model, for one that doesn't face +z
PlacedModelDatareturns
plain arrays and images, for meshPool.add
ModelDatareturns
plain arrays and images, for meshPool.add
urltakes
the model's address; files it names are fetched from beside it
fetch?takes
a fetch of your own, for headers or app assets
Promise<PlacedModelData>returns
the model read returns, for meshPool.add
Promise<ModelData>returns
the model read returns, for meshPool.add
moduletakes
a Draco decoder module, made once and shared by every read
Dracoreturns
a decoder for the draco option
types
DracoDracoArrayDracoAttributeDracoMeshDracoModuleExtraModelDataPartDataPlacementDataPlacedModelDataFetch
ReadOptionsread · 6 fields
parts?

readonly Name[]= none

Selects named nodes and their descendants as separate parts instead of entries in meshes.

rotation?

readonly [x: number, y: number, z: number, w: number]= none

Rotates the whole model around its origin with a unit quaternion [x, y, z, w].

placements?

P= false, which copies placements and merges them by material

Reads each glTF mesh once in mesh space when true, with one entry per material.

maxBytes?

number= 256 MiB

Limits total vertex and index bytes calculated before reading mesh data.

draco?

Draco= none, which throws for a model that needs Draco

Decodes primitives compressed with KHR_draco_mesh_compression.

uvs1?

boolean= false, which keeps it only for materials whose textures read it

Keeps every primitive's TEXCOORD_1 as uvs1, such as the unwrap of a lightmap the program samples itself.

LoadOptionsload · 7 fields
fetch?

Fetch= the global fetch

Fetches the model and the files beside it, such as a fetch that adds headers or reads app assets.

parts?

readonly Name[]= none

Selects named nodes and their descendants as separate parts instead of entries in meshes.

rotation?

readonly [x: number, y: number, z: number, w: number]= none

Rotates the whole model around its origin with a unit quaternion [x, y, z, w].

placements?

P= false, which copies placements and merges them by material

Reads each glTF mesh once in mesh space when true, with one entry per material.

maxBytes?

number= 256 MiB

Limits total vertex and index bytes calculated before reading mesh data.

draco?

Draco= none, which throws for a model that needs Draco

Decodes primitives compressed with KHR_draco_mesh_compression.

uvs1?

boolean= false, which keeps it only for materials whose textures read it

Keeps every primitive's TEXCOORD_1 as uvs1, such as the unwrap of a lightmap the program samples itself.

Click a named node to read it as a separate part. Open or drop your own .glb to inspect it in this browser without uploading it.

gltf.read converts a .glb to CPU arrays and materials. It flattens nodes and unpacks accessors. The mesh pool uploads the result with one write per array. The reader needs neither WebAssembly nor TextDecoder and runs in browsers, Node and Hermes. Only Draco meshes need a decoder you add.

import {
import gltf
gltf
} from '@typegpu/kit';
const
const response: Response
response
= await
function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
fetch
('/models/windmill.glb');
const
const model: gltf.ModelData<never>
model
=
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<never, false> | undefined): gltf.ModelData<never> (+1 overload)

Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies default-scene node transforms and groups meshes by material. Morph targets are omitted.

Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin as meshes do; other parts have no skin bindings.

It checks every index, count and range before reading. Invalid glTF, unsupported file versions and unsupported required features throw with details. Use load for a model whose buffers or images are separate files. For extensions that need conversion, errors name the gltf-transform command.

@param ― source the file's bytes, from anywhere

@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin

@param ― options.rotation a turn of the whole model, for one that doesn't face +z

@returns ― plain arrays and images, for meshPool.add

read
(await
const response: Response
response
.
Body.arrayBuffer(): Promise<ArrayBuffer>
arrayBuffer
());
const
const windmill: meshPool.Model
windmill
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
const model: gltf.ModelData<never>
model
);
await
const windmill: meshPool.Model
windmill
.
Model.ready: Promise<void>

Resolves when all textures have decoded and uploaded, at once for a model whose materials name none. Until then, meshes draw without textures. Rejects if image decoding fails or the pool is destroyed before upload. A destroyed pool uploads nothing.

ready
;
const things: instances.Instances<Plain>
things
.
Instances<Plain>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
} | ((mesh: meshPool.Mesh, index: number) => {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}) | undefined): IdOf<...>

Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row, and all rows move and change together under one id. values may be a function of each mesh, to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the group is empty, a posed kind receives a mesh without joints or the set is full.

add
(
const windmill: meshPool.Model
windmill
, {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [2, 0, 0] });

Load the bytes with fetch on the web, readFileSync in Node or expo-file-system on React Native. Pass an ArrayBuffer or any view of one to read, including a Node Buffer.

A finished look for model scenes lights a scene of many models with shadows, ambient occlusion, TAA and bloom.

gltf.load fetches a .glb or .gltf with the buffers and images it keeps in separate files, such as a shared Textures/colormap.png, and reads it as read does:

const
const crate: gltf.ModelData<never>
crate
= await
import gltf
gltf
.
function load<never>(url: string, options?: gltf.LoadOptions<never, false> | undefined): Promise<gltf.ModelData<never>> (+1 overload)

Fetches a .glb or .gltf with the buffers and images it keeps in separate files, then reads it as read does. It resolves each file's URI against url and fetches them all at once. Base64 data: URIs are decoded without fetching.

@param ― url the model's address; files it names are fetched from beside it

@param ― options.fetch a fetch of your own, for headers or app assets

@returns ― the model read returns, for meshPool.add

load
('/models/space-kit/crate.glb');
const
const crates: meshPool.Model
crates
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
const crate: gltf.ModelData<never>
crate
);
  • load resolves each file’s URI against the model’s URL and fetches the files at once. It decodes base64 data: URIs without fetching.
  • It takes the options of read, and fetch for a fetch of your own, such as one that adds headers.
  • A file that fails to load throws an error naming its URL.

Exporters often shrink meshes with Draco compression, KHR_draco_mesh_compression, which needs a decoder. The kit bundles none. Create a decoder module with the draco3d package, wrap it with gltf.draco and pass it to read or load:

import {
function createDecoderModule(object?: Record<string, unknown>): Promise<DecoderModule>
createDecoderModule
} from 'draco3d';
const
const wasm: Response
wasm
= await
function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
fetch
('/draco/draco_decoder.wasm');
const
const decoder: DecoderModule
decoder
= await
function createDecoderModule(object?: Record<string, unknown>): Promise<DecoderModule>
createDecoderModule
({
wasmBinary: ArrayBuffer
wasmBinary
: await
const wasm: Response
wasm
.
Body.arrayBuffer(): Promise<ArrayBuffer>
arrayBuffer
() });
const
const draco: gltf.Draco
draco
=
import gltf
gltf
.
function draco(module: gltf.DracoModule): gltf.Draco
export draco

Makes a Draco decoder for the draco option of read and load from a decoder module you create, such as await createDecoderModule({ wasmBinary }) from the draco3d package. The kit bundles no decoder, so a program without Draco models pays nothing for one.

@param ― module a Draco decoder module, made once and shared by every read

@returns ― a decoder for the draco option

draco
(
const decoder: DecoderModule
decoder
);
const
const response: Response
response
= await
function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
fetch
('/models/windmill-draco.glb');
const
const model: gltf.ModelData<never>
model
=
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<never, false> | undefined): gltf.ModelData<never> (+1 overload)

Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies default-scene node transforms and groups meshes by material. Morph targets are omitted.

Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin as meshes do; other parts have no skin bindings.

It checks every index, count and range before reading. Invalid glTF, unsupported file versions and unsupported required features throw with details. Use load for a model whose buffers or images are separate files. For extensions that need conversion, errors name the gltf-transform command.

@param ― source the file's bytes, from anywhere

@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin

@param ― options.rotation a turn of the whole model, for one that doesn't face +z

@returns ― plain arrays and images, for meshPool.add

read
(await
const response: Response
response
.
Body.arrayBuffer(): Promise<ArrayBuffer>
arrayBuffer
(), {
ReadOptions<Name extends string = never, P extends boolean = boolean>.draco?: gltf.Draco | undefined

Decodes primitives compressed with KHR_draco_mesh_compression. Make one with draco from a Draco decoder module. Default none, which throws for a model that needs Draco.

draco
});
  • Serve draco_decoder.wasm from the draco3d package beside your files, or import its URL through your bundler. In Node, leave out wasmBinary and the module finds the file itself.
  • The draco3d entry also loads Draco’s encoder. To bundle only the decoder, import createDecoderModule as the default export of draco3d/draco_decoder_nodejs.js.
  • Create the decoder once and pass it to every read. Reading stays synchronous. The night market loads its market this way, on the web with draco3d and on React Native with the JavaScript build below.
  • read decodes each compressed primitive once, also with placements, and checks the decoded arrays against their accessors’ counts and types.
  • Hermes has no WebAssembly. On React Native, bundle Google’s JavaScript build of the decoder, draco_decoder_gltf.js, and pass gltf.draco the module its DracoDecoderModule() resolves to. It decodes slower than WebAssembly. A gltf.Draco of your own, a function that decodes one primitive’s bytes into indices and attributes, works too.

read returns a gltf.ModelData that you can pass to pool.add:

Field
meshes One meshPool.MeshData per material, with node transforms applied.
images Encoded images referenced by index from material textures.
parts Requested parts, keyed by node name. Empty if none were requested.
clips Animations as poses.ClipData. Empty if the file has none.
skin Joint data as poses.SkinData, present only for skinned meshes.
placements Mesh transforms from nodes, present only with placements.

Each mesh holds:

Field
positions Three floats a vertex.
normals Three floats a vertex, unit length.
uvs Two floats a vertex, from TEXCOORD_0, or zero.
uvs1 Two floats a vertex, from TEXCOORD_1, when a texture of its material reads it or with uvs1: true.
indices u32, three a triangle, counter-clockwise seen from its front.
material glTF’s metallic-roughness material, below.
joints Four a vertex, indices in the model’s skin.joints, with a skin.
weights Four floats a vertex beside joints, adding up to 1.
extra color, four floats a vertex, linear RGBA, from COLOR_0, when present.
  • For the default scene, read transforms positions by node matrices and normals by their inverse transposes. Mirrored nodes reverse triangle winding so fronts remain counter-clockwise.
  • Strips and fans become triangle lists.
  • A mesh without normals gets flat normals. Each triangle needs separate vertices, increasing the vertex count by up to three times.
  • If any primitive has COLOR_0, its mesh gets extra.color. Vertices from other primitives are white. Create the mesh pool with extra: { color: d.vec4f } to keep these colours. Otherwise the pool drops them and warns once.

Each meshPool.MaterialData holds every field, with glTF defaults for omitted ones:

Field Default
name ''
color [1, 1, 1, 1] Base colour factor, linear, with alpha.
roughness 1
metallic 1
emission [0, 0, 0] Linear, times KHR_materials_emissive_strength.
textures none color, metallicRoughness, normal, emission and occlusion. Each has an image index into images, a sampler with WebGPU address modes and filters, and a uvSet and transform when it reads other coordinates.
normalScale 1 How far the normal texture bends the normal.
occlusionStrength 1 How far the occlusion texture darkens, from 0 for none to 1 for all of it.
alpha, alphaCutoff 'opaque', 0.5 'opaque', 'mask' or 'blend', and where a mask cuts. A renderer draws 'blend' as opaque.
doubleSided false Whether its back faces show too.

Without an explicit metallic factor or texture, a material renders as rough white metal. The rogue’s Glow material in the figure has this default. Set its instance to metallic: 0, or set the factor in the file.

Meshes merge by material, so a turret barrel and base with the same material become one mesh. Request independently moving nodes by name to keep them separate, each with its own pivot:

const
const turret: gltf.ModelData<"barrel">
turret
=
import gltf
gltf
.
function read<"barrel">(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<"barrel", false> | undefined): gltf.ModelData<"barrel"> (+1 overload)

Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies default-scene node transforms and groups meshes by material. Morph targets are omitted.

Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin as meshes do; other parts have no skin bindings.

It checks every index, count and range before reading. Invalid glTF, unsupported file versions and unsupported required features throw with details. Use load for a model whose buffers or images are separate files. For extensions that need conversion, errors name the gltf-transform command.

@param ― source the file's bytes, from anywhere

@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin

@param ― options.rotation a turn of the whole model, for one that doesn't face +z

@returns ― plain arrays and images, for meshPool.add

read
(
const bytes: ArrayBuffer
bytes
, {
ReadOptions<"barrel", false>.parts?: readonly "barrel"[] | undefined

Selects named nodes and their descendants as separate parts instead of entries in meshes. A selected node nested in another selected node becomes a separate part too. A part holding a skinned mesh stays bound to the skin. Default none.

parts
: ['barrel'] });
const
const base: meshPool.Model
base
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
const turret: gltf.ModelData<"barrel">
turret
); // everything but the barrel
const
const barrel: meshPool.Model
barrel
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
const turret: gltf.ModelData<"barrel">
turret
.
ModelData<"barrel">.parts: {
readonly barrel: gltf.PartData;
}

Contains the requested named nodes and their descendants.

parts
.
barrel: gltf.PartData
barrel
); // the barrel uses its node pivot
const [
const x: number
x
,
const y: number
y
,
const z: number
z
] =
const turret: gltf.ModelData<"barrel">
turret
.
ModelData<"barrel">.parts: {
readonly barrel: gltf.PartData;
}

Contains the requested named nodes and their descendants.

parts
.
barrel: gltf.PartData
barrel
.
PartData.position: Vec3

Gives the node origin in model space.

position
;
const things: instances.Instances<Plain>
things
.
Instances<Plain>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
} | ((mesh: meshPool.Mesh, index: number) => {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}) | undefined): IdOf<...>

Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row, and all rows move and change together under one id. values may be a function of each mesh, to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the group is empty, a posed kind receives a mesh without joints or the set is full.

add
(
const base: meshPool.Model
base
, {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [4, 0, 0] });
const things: instances.Instances<Plain>
things
.
Instances<Plain>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
} | ((mesh: meshPool.Mesh, index: number) => {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}) | undefined): IdOf<...>

Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row, and all rows move and change together under one id. values may be a function of each mesh, to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the group is empty, a posed kind receives a mesh without joints or the set is full.

add
(
const barrel: meshPool.Model
barrel
, {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [4 +
const x: number
x
,
const y: number
y
,
const z: number
z
],
rotation?: ArrayLike<number> | InferInput<Vec4f> | undefined

Sets rotation as a unit quaternion [x, y, z, w].

rotation
: [...
const turret: gltf.ModelData<"barrel">
turret
.
ModelData<"barrel">.parts: {
readonly barrel: gltf.PartData;
}

Contains the requested named nodes and their descendants.

parts
.
barrel: gltf.PartData
barrel
.
PartData.rotation: Vec4

Gives node rotation in model space as a unit quaternion [x, y, z, w].

rotation
] });
  • A name selects one node in the default scene and all its descendants. Missing names throw with a list of the first 20 available names. Duplicate names also throw.
  • A part uses its node’s local space, so rotations use the exported pivot, unless it holds a skinned mesh (skinned parts). Node scale and mirroring stay in the vertices. Use position and rotation to place the part within the model.
  • A named descendant of another named node becomes a separate part, excluded from its parent.
  • Pass a part to pool.add as a model. It shares the parent model’s images, which the pool decodes once.

By default, a house mesh placed a thousand times becomes a thousand copies merged by material. Set placements: true to read each glTF mesh once in local space and return its node transforms as placements. The pool then stores the house once, and instances draw each placement:

const
const town: gltf.PlacedModelData<never>
town
=
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options: gltf.ReadOptions<never, true> & {
readonly placements: true;
}): gltf.PlacedModelData<never> (+1 overload)

Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies default-scene node transforms and groups meshes by material. Morph targets are omitted.

Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin as meshes do; other parts have no skin bindings.

It checks every index, count and range before reading. Invalid glTF, unsupported file versions and unsupported required features throw with details. Use load for a model whose buffers or images are separate files. For extensions that need conversion, errors name the gltf-transform command.

@param ― source the file's bytes, from anywhere

@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin

@param ― options.rotation a turn of the whole model, for one that doesn't face +z

@returns ― plain arrays and images, for meshPool.add

read
(
const bytes: ArrayBuffer
bytes
, {
placements: true

Reads each glTF mesh once in mesh space when true, with one entry per material. Node transforms are returned in placements for instancing. Throws for a skinned model. Default false, which copies placements and merges them by material.

placements
: true });
const
const houses: meshPool.Model
houses
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
const town: gltf.PlacedModelData<never>
town
); // stores each glTF mesh once, split by material
for (const {
const mesh: number

Selects an entry in meshes.

mesh
,
const position: [x: number, y: number, z: number]
position
,
const rotation: [x: number, y: number, z: number, w: number]

Gives rotation as a unit quaternion [x, y, z, w].

rotation
,
const scale: [x: number, y: number, z: number]

Gives scale per axis. A negative x scale indicates a mirrored mesh.

scale
} of
const town: gltf.PlacedModelData<never>
town
.
PlacedModelData<never>.placements: readonly gltf.PlacementData[]

Lists mesh placements for nodes outside the selected parts, in scene node order.

placements
) {
const things: instances.Instances<Plain>
things
.
Instances<Plain>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
} | ((mesh: meshPool.Mesh, index: number) => {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}) | undefined): IdOf<...>

Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row, and all rows move and change together under one id. values may be a function of each mesh, to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the group is empty, a posed kind receives a mesh without joints or the set is full.

add
(
const houses: meshPool.Model
houses
.
Model.meshes: readonly meshPool.Mesh[]
meshes
[
const mesh: number

Selects an entry in meshes.

mesh
]!, {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
,
rotation?: ArrayLike<number> | InferInput<Vec4f> | undefined

Sets rotation as a unit quaternion [x, y, z, w].

rotation
,
scale?: number | ArrayLike<number> | InferInput<Vec3f> | undefined

Sets one scale factor for all axes or a separate factor per axis.

scale
});
}
  • A placement decomposes a node transform into position, rotation and scale. This loses shear. A mirrored node produces a negative x scale.
  • A glTF mesh with two materials becomes two meshes. Each node that places it therefore produces two placements.
  • Parts behave the same with or without placements. A model with a skin throws when read with placements.

For poses, read returns skinned meshes in their bind pose with skin data. It returns animations as clips whether or not the file has meshes:

Field
skin.joints Joints and their ancestor nodes, with parents before children. Each has a name, parent index, rest transform and inverse bind matrix.
clips Animation names, durations and channels. Each channel names a node, its position, rotation or scale, and its keyframes.
  • An unskinned mesh attached to a joint, such as a helmet, binds to that joint alone. An unattached mesh throws; read it as a part instead.
  • A part holding a skinned mesh stays bound to the skin, so one figure can split into pieces you highlight or hide apart. Its meshes keep joints and weights in model space, and its position and rotation are the model’s origin. Add each piece to the pool and draw it with the same pose as the rest. A rigid mesh in such a part follows its joint, as in meshes. Other parts do not bind to joints.
  • A file containing only clips is valid. Its clips play on skins with matching joint names.
  • Unnamed nodes and animations get names such as node 12 and animation 3.
  • All meshes in a model must use one skin. Multiple skins or vertices with more than four joints through JOINTS_1 throw.

glTF and instances.facing use +z as forward. Rotate differently oriented models in the exporter or when reading them. Kernels and deform then use that orientation:

const
const fish: gltf.ModelData<never>
fish
=
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<never, false> | undefined): gltf.ModelData<never> (+1 overload)

Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies default-scene node transforms and groups meshes by material. Morph targets are omitted.

Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin as meshes do; other parts have no skin bindings.

It checks every index, count and range before reading. Invalid glTF, unsupported file versions and unsupported required features throw with details. Use load for a model whose buffers or images are separate files. For extensions that need conversion, errors name the gltf-transform command.

@param ― source the file's bytes, from anywhere

@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin or, holding a skinned mesh, by the skin

@param ― options.rotation a turn of the whole model, for one that doesn't face +z

@returns ― plain arrays and images, for meshPool.add

read
(
const bytes: ArrayBuffer
bytes
, {
ReadOptions<Name extends string = never, P extends boolean = boolean>.rotation?: readonly [x: number, y: number, z: number, w: number] | undefined

Rotates the whole model around its origin with a unit quaternion [x, y, z, w]. Use this for models that do not face +z as instances.facing expects. For example, [0, -Math.SQRT1_2, 0, Math.SQRT1_2] turns +x to +z. Parts, skin and clips rotate together; read separate clip files with the same rotation. Default none.

rotation
: [0, -
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.SQRT1_2: number

The square root of 0.5, or, equivalently, one divided by the square root of 2.

SQRT1_2
, 0,
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.SQRT1_2: number

The square root of 0.5, or, equivalently, one divided by the square root of 2.

SQRT1_2
] }); // nose from +x to +z

Rotation uses the model origin and applies to parts, the skin and clips. Apply the same rotation when reading a separate clip file for that model.

Images contain encoded bytes and a MIME type, defaulting to image/png if omitted. read copies image bytes so the pool’s texture cache does not retain the whole file. pool.add decodes images, and meshes draw without textures until ready settles. Runtimes without createImageBitmap, including worklets and Node, need another decoder.

Convert these before you ship them. The error names the gltf-transform command. A model that needs Draco without a decoder throws too, naming the same copy command:

The file Run
compresses its meshes with meshopt gltf-transform copy in.glb out.glb
keeps its textures in KTX2 only gltf-transform ktxdecompress in.glb out.glb
  • read takes one .glb and refuses buffers and images in separate files. load fetches them, or gltf-transform copy in.gltf out.glb packs them into one file.

  • The reader supports these required extensions: KHR_mesh_quantization, KHR_materials_emissive_strength, KHR_texture_transform and EXT_texture_webp, and KHR_draco_mesh_compression with a decoder. Other required extensions throw.

  • The reader skips morph targets, cameras and lights. It skips TEXCOORD_1 where no texture reads it, unless read with { uvs1: true }, which keeps the unwrap of a lightmap your own shader samples. It skips later sets.

  • A texture that reads TEXCOORD_1 needs a pool made with uvs1: true, which meshPool.roomFor sets for such a model. Textures using TEXCOORD_2 or later throw. A texture’s KHR_texture_transform becomes its transform. The cloister reads both from Blender’s export.

  • Lines and points throw an error such as gltf.read reads triangles, got a primitive of mode 1.

Before following an index, buffer view or accessor range, read checks that it is valid. This matters when loading player uploads:

Accessor 0 exceeds its buffer view. It needs 12000000 bytes, but the view has 36.
  • Non-binary-glTF bytes, including a server’s 404 page, throw The bytes aren't a binary glTF. A file shorter than the length declared in its header throws before its contents are read.
  • A node that is its own ancestor, or has two parents, throws.
  • By default, each node placement copies a mesh. A small file could therefore request millions of copies. Before reading mesh data, read totals the placed vertices and indices and throws above maxBytes, which defaults to 256 MiB. For uploads, lower it with { maxBytes: 32 * 2 ** 20 }. With placements, each mesh counts once. The size error suggests that option. The limit covers the counts accessors declare, and a Draco primitive is decoded before its counts are checked against them, so read Draco files only from sources you trust.
  • An accessor without a buffer view throws. glTF permits it to request count zero values, which lets a small JSON field request gigabytes. Exporters use these accessors for morph targets, which read skips.
  • An animation whose key times don’t increase throws.
  • Meshopt and KTX2 need WebAssembly decoders that the kit does not include. Convert those files.
  • A mirrored placement turns its mesh’s triangles inside out, so back-face culling hides its fronts.
  • Part names must match exported node names. A missing name throws when the model loads.
  • A nested part uses model space rather than its parent part’s space. For a barrel on a rotating turret, compose the turret rotation into the barrel rotation.