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