React Three Fiber Textures
Quick Start
tsx1import { Canvas } from '@react-three/fiber' 2import { useTexture } from '@react-three/drei' 3 4function TexturedBox() { 5 const texture = useTexture('/textures/wood.jpg') 6 7 return ( 8 <mesh> 9 <boxGeometry /> 10 <meshStandardMaterial map={texture} /> 11 </mesh> 12 ) 13} 14 15export default function App() { 16 return ( 17 <Canvas> 18 <ambientLight /> 19 <TexturedBox /> 20 </Canvas> 21 ) 22}
useTexture Hook (Drei)
The recommended way to load textures in R3F.
Single Texture
tsx1import { useTexture } from '@react-three/drei' 2 3function SingleTexture() { 4 const texture = useTexture('/textures/color.jpg') 5 6 return ( 7 <mesh> 8 <planeGeometry args={[5, 5]} /> 9 <meshBasicMaterial map={texture} /> 10 </mesh> 11 ) 12}
Multiple Textures (Array)
tsx1function MultipleTextures() { 2 const [colorMap, normalMap, roughnessMap] = useTexture([ 3 '/textures/color.jpg', 4 '/textures/normal.jpg', 5 '/textures/roughness.jpg', 6 ]) 7 8 return ( 9 <mesh> 10 <sphereGeometry args={[1, 64, 64]} /> 11 <meshStandardMaterial 12 map={colorMap} 13 normalMap={normalMap} 14 roughnessMap={roughnessMap} 15 /> 16 </mesh> 17 ) 18}
Named Object (Recommended for PBR)
tsx1function PBRTextures() { 2 // Named object automatically spreads to material 3 const textures = useTexture({ 4 map: '/textures/color.jpg', 5 normalMap: '/textures/normal.jpg', 6 roughnessMap: '/textures/roughness.jpg', 7 metalnessMap: '/textures/metalness.jpg', 8 aoMap: '/textures/ao.jpg', 9 displacementMap: '/textures/displacement.jpg', 10 }) 11 12 return ( 13 <mesh> 14 <sphereGeometry args={[1, 64, 64]} /> 15 <meshStandardMaterial 16 {...textures} 17 displacementScale={0.1} 18 /> 19 </mesh> 20 ) 21}
With Texture Configuration
tsx1import { useTexture } from '@react-three/drei' 2import * as THREE from 'three' 3 4function ConfiguredTextures() { 5 const textures = useTexture({ 6 map: '/textures/color.jpg', 7 normalMap: '/textures/normal.jpg', 8 }, (textures) => { 9 // Configure textures after loading 10 Object.values(textures).forEach(texture => { 11 texture.wrapS = texture.wrapT = THREE.RepeatWrapping 12 texture.repeat.set(4, 4) 13 }) 14 }) 15 16 return ( 17 <mesh> 18 <planeGeometry args={[10, 10]} /> 19 <meshStandardMaterial {...textures} /> 20 </mesh> 21 ) 22}
Preloading
tsx1import { useTexture } from '@react-three/drei' 2 3// Preload at module level 4useTexture.preload('/textures/hero.jpg') 5useTexture.preload(['/tex1.jpg', '/tex2.jpg']) 6 7function Component() { 8 // Will be instant if preloaded 9 const texture = useTexture('/textures/hero.jpg') 10}
useLoader (Core R3F)
For more control over loading.
tsx1import { useLoader } from '@react-three/fiber' 2import { TextureLoader } from 'three' 3 4function WithUseLoader() { 5 const texture = useLoader(TextureLoader, '/textures/color.jpg') 6 7 // Multiple textures 8 const [color, normal] = useLoader(TextureLoader, [ 9 '/textures/color.jpg', 10 '/textures/normal.jpg', 11 ]) 12 13 return ( 14 <mesh> 15 <boxGeometry /> 16 <meshStandardMaterial map={color} normalMap={normal} /> 17 </mesh> 18 ) 19} 20 21// Preload 22useLoader.preload(TextureLoader, '/textures/color.jpg')
Texture Configuration
Wrapping Modes
tsx1import * as THREE from 'three' 2 3function ConfigureWrapping() { 4 const texture = useTexture('/textures/tile.jpg', (tex) => { 5 // Wrapping 6 tex.wrapS = THREE.RepeatWrapping // Horizontal: ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping 7 tex.wrapT = THREE.RepeatWrapping // Vertical 8 9 // Repeat 10 tex.repeat.set(4, 4) // Tile 4x4 11 12 // Offset 13 tex.offset.set(0.5, 0.5) // Shift UV 14 15 // Rotation 16 tex.rotation = Math.PI / 4 // Rotate 45 degrees 17 tex.center.set(0.5, 0.5) // Rotation pivot 18 }) 19 20 return ( 21 <mesh> 22 <planeGeometry args={[10, 10]} /> 23 <meshStandardMaterial map={texture} /> 24 </mesh> 25 ) 26}
Filtering
tsx1function ConfigureFiltering() { 2 const texture = useTexture('/textures/color.jpg', (tex) => { 3 // Minification (texture larger than screen pixels) 4 tex.minFilter = THREE.LinearMipmapLinearFilter // Smooth with mipmaps (default) 5 tex.minFilter = THREE.NearestFilter // Pixelated 6 tex.minFilter = THREE.LinearFilter // Smooth, no mipmaps 7 8 // Magnification (texture smaller than screen pixels) 9 tex.magFilter = THREE.LinearFilter // Smooth (default) 10 tex.magFilter = THREE.NearestFilter // Pixelated (retro style) 11 12 // Anisotropic filtering (sharper at angles) 13 tex.anisotropy = 16 // Usually renderer.capabilities.getMaxAnisotropy() 14 15 // Generate mipmaps 16 tex.generateMipmaps = true // Default 17 }) 18}
Color Space
Important for accurate colors.
tsx1function ConfigureColorSpace() { 2 const [colorMap, normalMap, roughnessMap] = useTexture([ 3 '/textures/color.jpg', 4 '/textures/normal.jpg', 5 '/textures/roughness.jpg', 6 ], (textures) => { 7 // Color/albedo textures should use sRGB 8 textures[0].colorSpace = THREE.SRGBColorSpace 9 10 // Data textures (normal, roughness, metalness, ao) use Linear 11 // This is the default, so usually no action needed 12 // textures[1].colorSpace = THREE.LinearSRGBColorSpace 13 // textures[2].colorSpace = THREE.LinearSRGBColorSpace 14 }) 15}
Environment Maps
useEnvironment Hook
tsx1import { useEnvironment, Environment } from '@react-three/drei' 2 3// Use as texture 4function EnvMappedSphere() { 5 const envMap = useEnvironment({ preset: 'sunset' }) 6 7 return ( 8 <mesh> 9 <sphereGeometry args={[1, 64, 64]} /> 10 <meshStandardMaterial 11 metalness={1} 12 roughness={0} 13 envMap={envMap} 14 /> 15 </mesh> 16 ) 17} 18 19// Or use Environment component for scene-wide 20function Scene() { 21 return ( 22 <> 23 <Environment preset="sunset" background /> 24 <Mesh /> 25 </> 26 ) 27}
HDR Environment
tsx1import { useEnvironment } from '@react-three/drei' 2 3function HDREnvironment() { 4 const envMap = useEnvironment({ files: '/hdri/studio.hdr' }) 5 6 return ( 7 <mesh> 8 <sphereGeometry args={[1, 64, 64]} /> 9 <meshStandardMaterial 10 metalness={1} 11 roughness={0} 12 envMap={envMap} 13 envMapIntensity={1} 14 /> 15 </mesh> 16 ) 17}
Cube Map
tsx1import { useCubeTexture } from '@react-three/drei' 2 3function CubeMapTexture() { 4 const envMap = useCubeTexture( 5 ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'], 6 { path: '/textures/cube/' } 7 ) 8 9 return ( 10 <mesh> 11 <sphereGeometry args={[1, 64, 64]} /> 12 <meshStandardMaterial envMap={envMap} metalness={1} roughness={0} /> 13 </mesh> 14 ) 15}
Video Textures
tsx1import { useVideoTexture } from '@react-three/drei' 2 3function VideoPlane() { 4 const texture = useVideoTexture('/videos/sample.mp4', { 5 start: true, 6 loop: true, 7 muted: true, 8 }) 9 10 return ( 11 <mesh> 12 <planeGeometry args={[16, 9].map(x => x * 0.5)} /> 13 <meshBasicMaterial map={texture} toneMapped={false} /> 14 </mesh> 15 ) 16}
Canvas Textures
tsx1import { useRef, useEffect } from 'react' 2import { useFrame } from '@react-three/fiber' 3import * as THREE from 'three' 4 5function CanvasTexture() { 6 const meshRef = useRef() 7 const textureRef = useRef() 8 9 useEffect(() => { 10 const canvas = document.createElement('canvas') 11 canvas.width = 256 12 canvas.height = 256 13 const ctx = canvas.getContext('2d') 14 15 // Draw on canvas 16 ctx.fillStyle = 'red' 17 ctx.fillRect(0, 0, 256, 256) 18 ctx.fillStyle = 'white' 19 ctx.font = '48px Arial' 20 ctx.fillText('Hello', 50, 150) 21 22 textureRef.current = new THREE.CanvasTexture(canvas) 23 }, []) 24 25 // Update texture dynamically 26 useFrame(({ clock }) => { 27 if (textureRef.current) { 28 const canvas = textureRef.current.image 29 const ctx = canvas.getContext('2d') 30 ctx.fillStyle = `hsl(${clock.elapsedTime * 50}, 100%, 50%)` 31 ctx.fillRect(0, 0, 256, 256) 32 textureRef.current.needsUpdate = true 33 } 34 }) 35 36 return ( 37 <mesh ref={meshRef}> 38 <planeGeometry args={[2, 2]} /> 39 <meshBasicMaterial map={textureRef.current} /> 40 </mesh> 41 ) 42}
Data Textures
tsx1import { useMemo } from 'react' 2import * as THREE from 'three' 3 4function NoiseTexture() { 5 const texture = useMemo(() => { 6 const size = 256 7 const data = new Uint8Array(size * size * 4) 8 9 for (let i = 0; i < size * size; i++) { 10 const value = Math.random() * 255 11 data[i * 4] = value 12 data[i * 4 + 1] = value 13 data[i * 4 + 2] = value 14 data[i * 4 + 3] = 255 15 } 16 17 const texture = new THREE.DataTexture(data, size, size) 18 texture.needsUpdate = true 19 return texture 20 }, []) 21 22 return ( 23 <mesh> 24 <planeGeometry args={[2, 2]} /> 25 <meshBasicMaterial map={texture} /> 26 </mesh> 27 ) 28}
Render Targets
Render to texture.
tsx1import { useFBO } from '@react-three/drei' 2import { useFrame } from '@react-three/fiber' 3import { useRef } from 'react' 4 5function RenderToTexture() { 6 const fbo = useFBO(512, 512) 7 const meshRef = useRef() 8 const otherSceneRef = useRef() 9 10 useFrame(({ gl, camera }) => { 11 // Render other scene to FBO 12 gl.setRenderTarget(fbo) 13 gl.render(otherSceneRef.current, camera) 14 gl.setRenderTarget(null) 15 }) 16 17 return ( 18 <> 19 {/* Scene to render to texture */} 20 <group ref={otherSceneRef}> 21 <mesh position={[0, 0, -5]}> 22 <sphereGeometry args={[1, 32, 32]} /> 23 <meshStandardMaterial color="red" /> 24 </mesh> 25 </group> 26 27 {/* Display the texture */} 28 <mesh ref={meshRef}> 29 <planeGeometry args={[4, 4]} /> 30 <meshBasicMaterial map={fbo.texture} /> 31 </mesh> 32 </> 33 ) 34}
Texture Atlas / Sprite Sheet
tsx1import { useTexture } from '@react-three/drei' 2import { useState } from 'react' 3import { useFrame } from '@react-three/fiber' 4import * as THREE from 'three' 5 6function SpriteAnimation() { 7 const texture = useTexture('/textures/spritesheet.png') 8 const [frame, setFrame] = useState(0) 9 10 // Configure texture 11 texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping 12 texture.repeat.set(1/4, 1/4) // 4x4 sprite sheet 13 14 useFrame(({ clock }) => { 15 const newFrame = Math.floor(clock.elapsedTime * 10) % 16 16 if (newFrame !== frame) { 17 setFrame(newFrame) 18 const col = newFrame % 4 19 const row = Math.floor(newFrame / 4) 20 texture.offset.set(col / 4, 1 - (row + 1) / 4) 21 } 22 }) 23 24 return ( 25 <mesh> 26 <planeGeometry args={[1, 1]} /> 27 <meshBasicMaterial map={texture} transparent /> 28 </mesh> 29 ) 30}
Material Texture Maps Reference
tsx1<meshStandardMaterial 2 // Base color (sRGB) 3 map={colorTexture} 4 5 // Surface detail (Linear) 6 normalMap={normalTexture} 7 normalScale={[1, 1]} 8 9 // Roughness (Linear, grayscale) 10 roughnessMap={roughnessTexture} 11 roughness={1} // Multiplier 12 13 // Metalness (Linear, grayscale) 14 metalnessMap={metalnessTexture} 15 metalness={1} // Multiplier 16 17 // Ambient Occlusion (Linear, requires uv2) 18 aoMap={aoTexture} 19 aoMapIntensity={1} 20 21 // Self-illumination (sRGB) 22 emissiveMap={emissiveTexture} 23 emissive="#ffffff" 24 emissiveIntensity={1} 25 26 // Vertex displacement (Linear) 27 displacementMap={displacementTexture} 28 displacementScale={0.1} 29 displacementBias={0} 30 31 // Alpha (Linear) 32 alphaMap={alphaTexture} 33 transparent={true} 34 35 // Environment reflection 36 envMap={envTexture} 37 envMapIntensity={1} 38 39 // Lightmap (requires uv2) 40 lightMap={lightmapTexture} 41 lightMapIntensity={1} 42/>
Second UV Channel (for AO/Lightmaps)
tsx1import { useEffect, useRef } from 'react' 2 3function MeshWithUV2() { 4 const meshRef = useRef() 5 6 useEffect(() => { 7 // Copy uv to uv2 for aoMap/lightMap 8 const geometry = meshRef.current.geometry 9 geometry.setAttribute('uv2', geometry.attributes.uv) 10 }, []) 11 12 return ( 13 <mesh ref={meshRef}> 14 <boxGeometry /> 15 <meshStandardMaterial 16 aoMap={aoTexture} 17 aoMapIntensity={1} 18 /> 19 </mesh> 20 ) 21}
Suspense Loading
tsx1import { Suspense } from 'react' 2import { useTexture } from '@react-three/drei' 3 4function TexturedMesh() { 5 const texture = useTexture('/textures/large.jpg') 6 return ( 7 <mesh> 8 <boxGeometry /> 9 <meshStandardMaterial map={texture} /> 10 </mesh> 11 ) 12} 13 14function Fallback() { 15 return ( 16 <mesh> 17 <boxGeometry /> 18 <meshBasicMaterial color="gray" wireframe /> 19 </mesh> 20 ) 21} 22 23function Scene() { 24 return ( 25 <Suspense fallback={<Fallback />}> 26 <TexturedMesh /> 27 </Suspense> 28 ) 29}
Performance Tips
- Use power-of-2 dimensions: 256, 512, 1024, 2048
- Compress textures: Use KTX2/Basis for web
- Enable mipmaps: For distant objects
- Limit texture size: 2048 usually sufficient
- Reuse textures: Same texture = better batching
- Preload important textures: Avoid pop-in
tsx1// Preload critical textures 2useTexture.preload('/textures/hero.jpg') 3 4// Check texture memory 5useFrame(({ gl }) => { 6 console.log('Textures:', gl.info.memory.textures) 7}) 8 9// Dispose unused textures (R3F usually handles this) 10texture.dispose()
See Also
r3f-materials- Applying textures to materialsr3f-loaders- Asset loading patternsr3f-shaders- Custom texture sampling