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 loaderconst loader = new GLTFLoader();
// Optional: Provide a DRACOLoader instance to decode compressed mesh dataconst dracoLoader = new DRACOLoader();dracoLoader.setDecoderPath('/examples/jsm/libs/draco/');loader.setDRACOLoader(dracoLoader);
// Load a glTF resourceloader.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 loaderResource API. loaderResource loads the asset, caches the result,
and returns it as a ResourceRef. Caching assets will reduce network requests, bandwidth, and memory usage, which will improve the performance of your application.
Generic assets
loaderResource can accept a THREE.js Loader class as the first argument. For the second argument, loaderResource 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 { loaderResource } from 'angular-three'
@Component({ template: ` @if (gltf.value(); as gltf) { <!-- use gltf.scene, gltf.animations etc... --> } `})export class MyCmp { gltf = loaderResource(() => 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. loaderResource can be used as shown above but a better way is to use gltfResource instead
for GLTF assets.
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA,} from "@angular/core";import { NgtArgs } from "angular-three";import { NgtsCameraControls } from "angular-three-soba/controls";import { gltfResource } 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 = gltfResource(() => 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 animations to simplify the process of using GLTF animations.
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, effect,} from "@angular/core";import { gltfResource } 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 { animations, 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 = gltfResource<LittlestTokyoGLTF>(() => littlestTokyo); private animations = animations(this.gltf.value, 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-primitivecomponent which makes it tricky to modify parts of the model. - Models, used with
loaderResource, are cached and loaded once. This is a performance boost. However since it is loaded once, theuuid(of the underlyingObject3D) never changes and THREE.js will not render the sameObject3Dmore 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 { loaderResource } from "angular-three";import { TextureLoader } from "three/src/loaders/TextureLoader.js";
@Component({ template: ` <ngt-mesh-standard-material [map]="texture.value()" /> `, schemas: [CUSTOM_ELEMENTS_SCHEMA],})export class MyCmp { protected texture = loaderResource(() => TextureLoader, () => './path/to/my-texture.png');}Similarly to GLTF assets, you can use a dedicated textureResource instead of loaderResource
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA,} from "@angular/core";import { NgtArgs } from "angular-three";import { NgtsOrbitControls } from "angular-three-soba/controls";import { textureResource } 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.value(); @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 = textureResource(() => ({ 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 loaderResource and textureResource 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 textureResource,
you can pass in an onLoad callback.
textureResource(() => "input", { onLoad: (texture) => { texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.colorSpace = THREE.SRGBColorSpace; },});onLoad callback argument depends on the input type.
- If input is a single path, the callback argument is a
Textureinstance. - If input is an array of paths, the callback argument is an array of
Textureinstances. - If input is a
Recordof paths, the callback argument is aRecordofTextureinstances.