Useful add-ons for react-three-fiber providing 100+ components for 3D web applications
—
Asset loading utilities for textures, models, and media with automatic caching, disposal, and format support. These components provide efficient loading strategies for various 3D assets.
GLTF model loading hook with automatic disposal, Draco compression, and type safety.
/**
* GLTF model loading hook with automatic disposal
* @param path - Path to GLTF/GLB file
* @param useDraco - Enable Draco compression support, true
* @param useMeshOpt - Enable MeshOpt compression support, true
* @returns GLTF object with nodes, materials, and animations
*/
function useGLTF(path: string, useDraco?: boolean, useMeshOpt?: boolean): GLTF & ObjectMap;
interface GLTF {
/** Animation clips from the model */
animations: AnimationClip[];
/** Root scene group */
scene: Group;
/** All scenes in the file */
scenes: Group[];
/** Cameras from the model */
cameras: Camera[];
/** Asset metadata */
asset: object;
/** Parser used */
parser: GLTFParser;
/** User data */
userData: any;
}
interface ObjectMap {
/** Named nodes from the model */
nodes: { [name: string]: Object3D };
/** Named materials from the model */
materials: { [name: string]: Material };
}
// Static methods
function useGLTF.preload(path: string): void;
function useGLTF.clear(input: string | string[]): void;Usage Examples:
import { useGLTF } from '@react-three/drei';
// Basic GLTF loading
function Model({ path }) {
const { nodes, materials, scene } = useGLTF(path);
return <primitive object={scene} />;
}
// Accessing specific nodes and materials
function DetailedModel() {
const { nodes, materials } = useGLTF('/models/character.glb');
return (
<group>
<mesh
geometry={nodes.Head.geometry}
material={materials.SkinMaterial}
/>
<mesh
geometry={nodes.Body.geometry}
material={materials.ClothMaterial}
/>
<skinnedMesh
geometry={nodes.Character.geometry}
material={materials.CharacterMaterial}
skeleton={nodes.Character.skeleton}
/>
</group>
);
}
// With animations
function AnimatedModel() {
const group = useRef();
const { nodes, materials, animations } = useGLTF('/models/animated.glb');
const { actions } = useAnimations(animations, group);
useEffect(() => {
actions['Walk']?.play();
}, [actions]);
return (
<group ref={group}>
<primitive object={nodes.Scene} />
</group>
);
}
// Preload models for better performance
useGLTF.preload('/models/important.glb');
// Clear cache when needed
useGLTF.clear('/models/old.glb');Texture loading hook with multiple format support, automatic disposal, and batch loading.
/**
* Texture loading hook with format support
* @param input - Texture path or array of paths
* @param onLoad - Load callback function
* @returns Texture or array of textures
*/
function useTexture(
input: string | string[],
onLoad?: (texture: Texture | Texture[]) => void
): Texture | Texture[];
// Static methods
function useTexture.preload(input: string | string[]): void;
function useTexture.clear(input: string | string[]): void;Usage Examples:
import { useTexture } from '@react-three/drei';
// Single texture
function TexturedMesh() {
const texture = useTexture('/textures/diffuse.jpg');
return (
<mesh>
<planeGeometry />
<meshStandardMaterial map={texture} />
</mesh>
);
}
// Multiple textures
function PBRMaterial() {
const [colorMap, normalMap, roughnessMap, metalnessMap] = useTexture([
'/textures/color.jpg',
'/textures/normal.jpg',
'/textures/roughness.jpg',
'/textures/metalness.jpg'
]);
return (
<meshStandardMaterial
map={colorMap}
normalMap={normalMap}
roughnessMap={roughnessMap}
metalnessMap={metalnessMap}
/>
);
}
// Object syntax for named textures
function NamedTextures() {
const textures = useTexture({
diffuse: '/textures/diffuse.jpg',
normal: '/textures/normal.jpg',
roughness: '/textures/roughness.jpg'
});
return (
<meshStandardMaterial
map={textures.diffuse}
normalMap={textures.normal}
roughnessMap={textures.roughness}
/>
);
}
// Texture configuration callback
function ConfiguredTexture() {
const texture = useTexture('/textures/tile.jpg', (texture) => {
texture.wrapS = texture.wrapT = RepeatWrapping;
texture.repeat.set(4, 4);
texture.minFilter = NearestFilter;
texture.magFilter = NearestFilter;
});
return (
<mesh>
<planeGeometry args={[10, 10]} />
<meshBasicMaterial map={texture} />
</mesh>
);
}
// Preload critical textures
useTexture.preload([
'/textures/hero-diffuse.jpg',
'/textures/hero-normal.jpg'
]);Font loading hook for text rendering with caching and format support.
/**
* Font loading hook for text rendering
* @param path - Path to font JSON file
* @returns Font data object with glyphs
*/
function useFont(path: string): FontData;
interface FontData {
/** Font data object */
data: any;
/** Character glyph definitions */
glyphs: { [key: string]: Glyph };
}
interface Glyph {
/** Glyph X position in font atlas */
x: number;
/** Glyph Y position in font atlas */
y: number;
/** Glyph width */
width: number;
/** Glyph height */
height: number;
/** X advance for character spacing */
xAdvance?: number;
/** X rendering offset */
xOffset?: number;
/** Y rendering offset */
yOffset?: number;
}
// Static methods
function useFont.preload(path: string): void;
function useFont.clear(path: string): void;Usage Examples:
import { useFont, Text3D } from '@react-three/drei';
// Basic font usage
function StyledText() {
const font = useFont('/fonts/helvetiker_regular.json');
return (
<Text3D font={font} size={1} height={0.2}>
Hello World
<meshNormalMaterial />
</Text3D>
);
}
// Multiple fonts
function MultiFontText() {
const regularFont = useFont('/fonts/helvetiker_regular.json');
const boldFont = useFont('/fonts/helvetiker_bold.json');
return (
<group>
<Text3D font={regularFont} position={[0, 1, 0]}>
Regular Text
<meshStandardMaterial color="blue" />
</Text3D>
<Text3D font={boldFont} position={[0, -1, 0]}>
Bold Text
<meshStandardMaterial color="red" />
</Text3D>
</group>
);
}
// Preload fonts
useFont.preload('/fonts/main.json');Environment texture loading hook with presets, HDRI support, and configuration options.
/**
* Environment texture loading hook with presets
* @param props - Environment loader configuration
* @returns Environment cube texture
*/
function useEnvironment(props?: EnvironmentLoaderProps): Texture;
interface EnvironmentLoaderProps {
/** Environment preset name */
preset?: PresetsType;
/** Custom environment files (single HDR or 6 cube faces) */
files?: string | string[];
/** Base path for environment files */
path?: string;
/** Texture encoding, sRGBEncoding */
encoding?: TextureEncoding;
}
type PresetsType =
| 'apartment' | 'city' | 'dawn' | 'forest' | 'lobby'
| 'night' | 'park' | 'studio' | 'sunset' | 'warehouse';Usage Examples:
import { useEnvironment } from '@react-three/drei';
// Environment preset
function ReflectiveSphere() {
const envMap = useEnvironment({ preset: 'sunset' });
return (
<mesh>
<sphereGeometry />
<meshStandardMaterial
envMap={envMap}
metalness={1}
roughness={0}
/>
</mesh>
);
}
// Custom HDRI environment
function CustomEnvironment() {
const envMap = useEnvironment({
files: '/hdri/studio_small_03_1k.hdr',
encoding: RGBEEncoding
});
return (
<mesh>
<torusGeometry />
<meshStandardMaterial envMap={envMap} />
</mesh>
);
}
// Cube map environment (6 faces)
function CubeEnvironment() {
const envMap = useEnvironment({
files: [
'/cubemap/px.jpg', '/cubemap/nx.jpg',
'/cubemap/py.jpg', '/cubemap/ny.jpg',
'/cubemap/pz.jpg', '/cubemap/nz.jpg'
],
path: '/environments/'
});
return (
<mesh>
<boxGeometry />
<meshStandardMaterial envMap={envMap} />
</mesh>
);
}FBX model loading hook with animation support and material handling.
/**
* FBX model loading hook
* @param path - Path to FBX file
* @returns FBX group with animations
*/
function useFBX(path: string): Group & { animations: AnimationClip[] };
// Static methods
function useFBX.preload(path: string): void;
function useFBX.clear(path: string): void;Usage Examples:
import { useFBX } from '@react-three/drei';
function FBXModel() {
const fbx = useFBX('/models/character.fbx');
return <primitive object={fbx} />;
}
// With animations
function AnimatedFBX() {
const group = useRef();
const fbx = useFBX('/models/animated.fbx');
const { actions } = useAnimations(fbx.animations, group);
useEffect(() => {
actions['Take 001']?.play();
}, [actions]);
return (
<group ref={group}>
<primitive object={fbx} />
</group>
);
}KTX2 texture loading hook for GPU-compressed textures.
/**
* KTX2 texture loading hook for GPU compression
* @param path - Path to KTX2 file
* @returns KTX2 texture
*/
function useKTX2(path: string): CompressedTexture;
// Static methods
function useKTX2.preload(path: string): void;
function useKTX2.clear(path: string): void;Video texture loading hook with playback control and format support.
/**
* Video texture loading hook with playback control
* @param src - Video source URL or HTMLVideoElement
* @param props - Video texture configuration
* @returns Video texture
*/
function useVideoTexture(
src: string | HTMLVideoElement,
props?: Partial<VideoTextureProps>
): VideoTexture;
interface VideoTextureProps extends Omit<ThreeElements['videoTexture'], 'ref' | 'args'> {
/** Auto-play video, true */
autoplay?: boolean;
/** Loop video, true */
loop?: boolean;
/** Video crossOrigin, 'anonymous' */
crossOrigin?: string;
/** Muted playback, true */
muted?: boolean;
/** Video playback rate, 1 */
playbackRate?: number;
/** Video volume, 1 */
volume?: number;
/** Load start callback */
onLoadStart?: () => void;
/** Load callback */
onLoad?: (video: HTMLVideoElement) => void;
/** Progress callback */
onProgress?: (event: ProgressEvent) => void;
/** Error callback */
onError?: (error: ErrorEvent) => void;
}Usage Examples:
import { useVideoTexture } from '@react-three/drei';
// Basic video texture
function VideoScreen() {
const texture = useVideoTexture('/videos/demo.mp4');
return (
<mesh>
<planeGeometry args={[16, 9]} />
<meshBasicMaterial map={texture} />
</mesh>
);
}
// Video with controls
function InteractiveVideo() {
const texture = useVideoTexture('/videos/interactive.mp4', {
autoplay: false,
loop: false,
muted: false,
onLoad: (video) => {
console.log('Video loaded:', video.duration);
}
});
const playPause = () => {
const video = texture.image;
video.paused ? video.play() : video.pause();
};
return (
<group>
<mesh onClick={playPause}>
<planeGeometry args={[4, 3]} />
<meshBasicMaterial map={texture} />
</mesh>
</group>
);
}Cube texture loading hook for skyboxes and environment mapping.
/**
* Cube texture loading hook for skyboxes
* @param files - Array of 6 image paths [px, nx, py, ny, pz, nz]
* @param options - Cube texture options
* @returns Cube texture
*/
function useCubeTexture(
files: [string, string, string, string, string, string],
options?: CubeTextureOptions
): CubeTexture;
interface CubeTextureOptions {
/** Base path for files */
path?: string;
/** Texture encoding */
encoding?: TextureEncoding;
}
// Static methods
function useCubeTexture.preload(files: string[], path?: string): void;Usage Examples:
import { useCubeTexture } from '@react-three/drei';
function Skybox() {
const texture = useCubeTexture([
'px.jpg', 'nx.jpg',
'py.jpg', 'ny.jpg',
'pz.jpg', 'nz.jpg'
], { path: '/skybox/' });
const { scene } = useThree();
scene.background = texture;
return null;
}function ProgressiveAssets() {
const [loadHigh, setLoadHigh] = useState(false);
// Load low-res first
const lowTexture = useTexture('/textures/low-res.jpg');
// Load high-res after delay
const highTexture = useTexture(
loadHigh ? '/textures/high-res.jpg' : null
);
useEffect(() => {
const timer = setTimeout(() => setLoadHigh(true), 1000);
return () => clearTimeout(timer);
}, []);
return (
<mesh>
<planeGeometry />
<meshStandardMaterial map={highTexture || lowTexture} />
</mesh>
);
}// Preload critical assets during app initialization
const preloadCriticalAssets = () => {
// Models
useGLTF.preload('/models/hero.glb');
useGLTF.preload('/models/environment.glb');
// Textures
useTexture.preload([
'/textures/hero-diffuse.jpg',
'/textures/hero-normal.jpg',
'/textures/environment.jpg'
]);
// Fonts
useFont.preload('/fonts/main.json');
// Environment
useEnvironment.preload({ preset: 'studio' });
};
// Call during app startup
useEffect(preloadCriticalAssets, []);function MemoryEfficientLoader() {
const [currentModel, setCurrentModel] = useState('model1');
// Clear previous model from cache when switching
useEffect(() => {
return () => {
// Clean up unused assets
if (currentModel !== 'model1') {
useGLTF.clear('/models/model1.glb');
}
if (currentModel !== 'model2') {
useGLTF.clear('/models/model2.glb');
}
};
}, [currentModel]);
const model = useGLTF(`/models/${currentModel}.glb`);
return <primitive object={model.scene} />;
}function RobustAssetLoader() {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
try {
const model = useGLTF('/models/complex.glb');
const texture = useTexture('/textures/diffuse.jpg', () => {
setLoading(false);
});
if (error) return <ErrorFallback error={error} />;
if (loading) return <LoadingSpinner />;
return (
<mesh>
<primitive object={model.scene} />
<meshStandardMaterial map={texture} />
</mesh>
);
} catch (err) {
setError(err);
return <ErrorFallback error={err} />;
}
}
function ErrorFallback({ error }) {
return (
<mesh>
<boxGeometry />
<meshBasicMaterial color="red" />
<Html center>
<div>Failed to load: {error.message}</div>
</Html>
</mesh>
);
}function ConditionalAssets() {
const { viewport } = useThree();
const isMobile = viewport.width < 768;
const [quality, setQuality] = useState('medium');
// Load appropriate quality assets
const modelPath = `/models/character-${isMobile ? 'low' : quality}.glb`;
const texturePath = `/textures/diffuse-${isMobile ? '512' : '2048'}.jpg`;
const model = useGLTF(modelPath);
const texture = useTexture(texturePath);
return (
<group>
<primitive object={model.scene} />
<meshStandardMaterial map={texture} />
</group>
);
}function BatchLoader({ onProgress }) {
const [loaded, setLoaded] = useState(0);
const totalAssets = 10;
const handleLoad = useCallback(() => {
setLoaded(prev => {
const newLoaded = prev + 1;
onProgress?.(newLoaded / totalAssets);
return newLoaded;
});
}, [onProgress, totalAssets]);
// Load multiple assets with progress tracking
const textures = useTexture([
'/tex1.jpg', '/tex2.jpg', '/tex3.jpg',
'/tex4.jpg', '/tex5.jpg'
], handleLoad);
const models = [
useGLTF('/model1.glb'),
useGLTF('/model2.glb'),
useGLTF('/model3.glb'),
useGLTF('/model4.glb'),
useGLTF('/model5.glb')
];
return (
<group>
{models.map((model, i) => (
<primitive key={i} object={model.scene} />
))}
</group>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-three--drei