Skip to content

Loading Assets

In a THREE.js application, there are various types of assets that you can load and THREE.js provides different types of loaders for these different types of assets.

THREE.js GLTFLoader example
// Instantiate a loader
const loader = new GLTFLoader();
// Optional: Provide a DRACOLoader instance to decode compressed mesh data
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/examples/jsm/libs/draco/');
loader.setDRACOLoader(dracoLoader);
// Load a glTF resource
loader.load(
// resource URL
'models/gltf/duck/duck.gltf',
// called when the resource is loaded
function (gltf) {
scene.add(gltf.scene);
gltf.animations; // Array<THREE.AnimationClip>
gltf.scene; // THREE.Group
gltf.scenes; // Array<THREE.Group>
gltf.cameras; // Array<THREE.Camera>
gltf.asset; // Object
},
);

In Angular Three, the recommended way is to use injectLoader API. injectLoader loads the asset, caches the result, and returns it as a Signal. Caching assets will reduce network requests, bandwidth, and memory usage, which will improve the performance of your application.

Generic assets

injectLoader can accept a THREE.js Loader class as the first argument. For the second argument, injectLoader can accept a single path to the asset, an array of paths, or a dictionary (i.e: Record) of paths to multiple assets.

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { Component } from '@angular/core';
import { injectLoader } from 'angular-three'
@Component({
template: `
@if (gltf(); as gltf) {
<!-- use gltf.scene, gltf.animations etc... -->
}
`
})
export class MyCmp {
gltf = injectLoader(() => GLTFLoader, () => './path/to/my-model.gltf');
}

For asset types that do not have a dedicated section on this page, feel free to check out THREE.js examples to see how the models are loaded using what Loader as well as techniques, then you can build your own abstractions.

GLTF Assets

GLTF assets are usually for premade 3D models and it can come in .gltf or .glb extensions. injectLoader can be used as shown above but a better way is to use injectGLTF instead for GLTF assets.

scene-graph.ts
import {
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA,
} from "@angular/core";
import { NgtArgs } from "angular-three";
import { NgtsCameraControls } from "angular-three-soba/controls";
import { injectGLTF } from "angular-three-soba/loaders";
import { NgtsCenter, NgtsEnvironment } from "angular-three-soba/staging";
import littlestTokyo from "./LittlestTokyo-transformed.glb" with { loader: "file" };
@Component({
selector: "app-scene-graph",
template: `
<ngts-center>
<ngt-primitive *args="[gltf.scene()]" [parameters]="{ scale: 0.0075 }" />
</ngts-center>
<ngts-environment [options]="{ preset: 'city' }" />
<ngts-camera-controls />
`,
imports: [NgtArgs, NgtsCameraControls, NgtsEnvironment, NgtsCenter],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SceneGraph {
protected gltf = injectGLTF(() => littlestTokyo);
}

Credits: THREE.js Animation Keyframes; Model Littlest Tokyo by Glen Fox

Animations

GLTF models can come with built-in animations. When GLTFLoader loads the GLTF asset, these animations become available as AnimationClip and AnimationMixer can be used to interact with them.

Angular Three provides an injectAnimations to simplify the process of using GLTF animations.

scene-graph-animation.ts
import {
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA,
effect,
} from "@angular/core";
import { injectGLTF } from "angular-three-soba/loaders";
import { NgtArgs } from "angular-three";
import { NgtsCameraControls } from "angular-three-soba/controls";
import { NgtsCenter, NgtsEnvironment } from "angular-three-soba/staging";
import {
injectAnimations,
type NgtsAnimationClips,
} from "angular-three-soba/misc";
import type { GLTF } from "three-stdlib";
import littlestTokyo from "../gltf-demo/LittlestTokyo-transformed.glb" with { loader: "file" };
interface LittlestTokyoGLTF extends GLTF {
animations: NgtsAnimationClips<"Take 001">[];
}
@Component({
selector: "app-scene-graph",
template: `
<ngts-center>
<ngt-primitive *args="[gltf.scene()]" [parameters]="{ scale: 0.0075 }" />
</ngts-center>
<ngts-environment [options]="{ preset: 'city' }" />
<ngts-camera-controls />
`,
imports: [NgtArgs, NgtsCameraControls, NgtsEnvironment, NgtsCenter],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SceneGraph {
protected gltf = injectGLTF<LittlestTokyoGLTF>(() => littlestTokyo);
private animations = injectAnimations(this.gltf, this.gltf.scene);
constructor() {
effect((onCleanup) => {
if (!this.animations.isReady) return;
const { actions } = this.animations;
actions["Take 001"].reset().fadeIn(0.5).play();
onCleanup(() => actions["Take 001"].fadeOut(0.5).stop());
});
}
}

Credits: THREE.js Animation Keyframes; Model Littlest Tokyo by Glen Fox

Reusing GLTF

When working with 3D models, you might run into some challenges:

  • Models are premade and are demo’ed with ngt-primitive component which makes it tricky to modify parts of the model.
  • Models, used with injectLoader, are cached and loaded once. This is a performance boost. However since it is loaded once, the uuid (of the underlying Object3D) never changes and THREE.js will not render the same Object3D more than once.

To address both of these challenges, Angular Three provides a generator/schematic to generate an Angular component from your GLTF model. Learn more about angular-three-plugin:gltf

Textures

Another common type of assets used in THREE.js applications is Texture via TextureLoader.

import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { injectLoader } from "angular-three";
import { TextureLoader } from "three/src/loaders/TextureLoader.js";
@Component({
template: `
<ngt-mesh-standard-material [map]="texture()" />
`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class MyCmp {
protected texture = injectLoader(() => TextureLoader, () => './path/to/my-texture.png');
}

Similarly to GLTF assets, you can use a dedicated injectTexture instead of injectLoader

scene-graph.ts
import {
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA,
} from "@angular/core";
import { NgtArgs } from "angular-three";
import { NgtsOrbitControls } from "angular-three-soba/controls";
import { injectTexture } from "angular-three-soba/loaders";
import { NgtsEnvironment } from "angular-three-soba/staging";
/**
*
Image Attributions
The images used in this project are sourced from NASA and ESA and may require attribution. Please refer to the image sources for specific attribution requirements.
- Earth Albedo map: https://visibleearth.nasa.gov/images/57730/the-blue-marble-land-surface-ocean-color-and-sea-ice/82679l
- Earth Bump map: https://visibleearth.nasa.gov/images/73934/topography/84331l
*/
@Component({
selector: "app-scene-graph",
template: `
<ngt-mesh>
<ngt-sphere-geometry *args="[10, 64, 64]" />
@let _textures = textures();
@let map = _textures?.map;
@let bumpMap = _textures?.bumpMap;
<ngt-mesh-standard-material [map]="map" [bumpMap]="bumpMap" />
</ngt-mesh>
<ngts-environment [options]="{ preset: 'sunset' }" />
<ngts-orbit-controls [options]="{ autoRotate: true, autoRotateSpeed: 0.25 }" />
`,
imports: [NgtArgs, NgtsOrbitControls, NgtsEnvironment],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SceneGraph {
protected textures = injectTexture(() => ({
map: "https://raw.githubusercontent.com/nartc/threejs-earth/refs/heads/main/src/assets/Albedo.jpg",
bumpMap: "https://raw.githubusercontent.com/nartc/threejs-earth/refs/heads/main/src/assets/Bump.jpg",
}));
}

Credits: Make your own Earth in Three.js by Franky Hung

The main difference between injectLoader and injectTexture is the ease of loading multiple textures; both with Array and Record types.

onLoad

One of the more common use-cases when working with Texture is to modify the Texture properties like colorSpace, wrapS, wrapT, etc. In order to do this with injectTexture, you can pass in an onLoad callback.

injectTexture(() => "input", {
onLoad: (textures) => {
// textures is an Array of Texture
textures[0].wrapS = THREE.RepeatWrapping;
textures[0].wrapT = THREE.RepeatWrapping;
textures[0].colorSpace = THREE.SRGBColorSpace;
},
});