Skip to content

normalTextureResource

normalTextureResource and NgtsNormalTexture are ports of Drei’s useNormalTexture which load normal map textures from a repository of pre-made surface details.

Normal maps add surface detail and depth to materials without additional geometry, creating the illusion of bumps, scratches, and other surface imperfections.

Note: normalTextureResource is not meant for production environments as it relies on third-party CDN.

Usage

Using normalTextureResource

import { normalTextureResource } from 'angular-three-soba/staging';
@Component({
template: `
@if (normalMap.resource.hasValue()) {
<ngt-mesh>
<ngt-sphere-geometry *args="[1, 64, 64]" />
<ngt-mesh-standard-material [normalMap]="normalMap.resource.value()" />
</ngt-mesh>
}
`,
})
export class NormalMapDemo {
normalMap = normalTextureResource(() => 42);
}

With Texture Settings

normalMap = normalTextureResource(() => 15, {
settings: () => ({
repeat: [4, 4],
anisotropy: 16,
offset: [0, 0],
}),
});

Dynamic Normal Map Selection

@Component({
template: `
@if (normalMap.resource.hasValue()) {
<ngt-mesh>
<ngt-plane-geometry *args="[5, 5, 32, 32]" />
<ngt-mesh-standard-material [normalMap]="normalMap.resource.value()" [normalScale]="[1, 1]" />
</ngt-mesh>
}
<p>Available normal maps: {{ normalMap.numTot() }}</p>
`,
})
export class DynamicNormalDemo {
selectedId = signal(0);
normalMap = normalTextureResource(this.selectedId);
nextNormalMap() {
this.selectedId.update((id) => (id + 1) % this.normalMap.numTot());
}
}

Using NgtsNormalTexture Directive

The structural directive provides a declarative approach:

<ng-template [normalTexture]="{ id: 42, repeat: [2, 2] }" let-resource>
@if (resource.hasValue()) {
<ngt-mesh>
<ngt-box-geometry />
<ngt-mesh-standard-material
[normalMap]="resource.value()"
[normalScale]="[0.5, 0.5]"
/>
</ngt-mesh>
}
</ng-template>

With onLoad Callback

normalMap = normalTextureResource(() => 100, {
settings: () => ({ repeat: [2, 2] }),
onLoad: (texture) => {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
console.log('Normal map loaded:', texture);
},
});

Combining with Other Material Properties

@if (normalMap.resource.hasValue()) {
<ngt-mesh>
<ngt-sphere-geometry *args="[1, 128, 128]" />
<ngt-mesh-standard-material
color="#888888"
[metalness]="0.9"
[roughness]="0.2"
[normalMap]="normalMap.resource.value()"
[normalScale]="[0.8, 0.8]"
/>
</ngt-mesh>
}

Tiled Normal Maps for Floors/Walls

floorNormal = normalTextureResource(() => 'concrete', {
settings: () => ({
repeat: [10, 10],
anisotropy: 16,
}),
onLoad: (texture) => {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
},
});

Settings Options

SettingTypeDescription
repeat[x, y]How many times to tile the texture
anisotropynumberAnisotropic filtering level for quality
offset[x, y]UV offset for texture positioning

Notes

  • Normal maps work best with MeshStandardMaterial or MeshPhysicalMaterial
  • Use normalScale on the material to control the intensity of the effect
  • Higher repeat values create finer detail patterns
  • Anisotropic filtering improves quality at oblique angles
  • The repository contains various surface types (fabric, metal, stone, etc.)

Arguments

name type description required
id () => string | number Signal containing the normal map ID (index or name from the repository). Default: () => 0 no
options.settings () => { repeat?: number[]; anisotropy?: number; offset?: number[] } Signal containing texture settings for repeat, anisotropy, and offset. no
options.onLoad (texture: THREE.Texture) => void Callback when texture loads successfully. no
options.injector Injector Optional injector for dependency injection context. no

Returns

typedescription
objectAn object containing texture loading state and utilities.

Properties

nametypedescription
urlSignal<string>Signal containing the resolved normal texture URL.
resourceResourceRef<THREE.Texture>Resource reference for the loaded texture.
numTotSignal<number>Signal containing the total number of available normal maps.