r3f-textures
Focuses on React Three Fiber texture handling, including useTexture, texture loading, and environment mapping.
npx skills add enzed/r3f-skills --skill r3f-texturesBefore / After Comparison
1 组When handling textures in React Three Fiber projects, developers often encounter issues such as slow loading, high memory consumption, and low rendering efficiency. This is particularly true when scenes contain a large number of high-resolution textures, severely impacting user experience and making the debugging process exceptionally complex.
After adopting the r3f-textures optimization solution, texture loading becomes significantly more efficient. Through intelligent caching and compression technologies, memory consumption is substantially reduced, and rendering fluidity is improved, ensuring that even complex 3D scenes can deliver an excellent visual experience.
description SKILL.md
name: r3f-textures description: React Three Fiber textures - useTexture, texture loading, environment maps, texture configuration. Use when loading images, working with PBR texture sets, cubemaps, HDR environments, or optimizing texture usage.
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 (
<mesh>
<boxGeometry />
<meshStandardMaterial map={texture} />
</mesh>
)
}
export default function App() {
return (
<Canvas>
<ambientLight />
<TexturedBox />
</Canvas>
)
}
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 (
<mesh>
<planeGeometry args={[5, 5]} />
<meshBasicMaterial map={texture} />
</mesh>
)
}
Multiple Textures (Array)
function MultipleTextures() {
const [colorMap, normalMap, roughnessMap] = useTexture([
'/textures/color.jpg',
'/textures/normal.jpg',
'/textures/roughness.jpg',
])
return (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardMaterial
map={colorMap}
normalMap={normalMap}
roughnessMap={roughnessMap}
/>
</mesh>
)
}
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 (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardMaterial
{...textures}
displacementScale={0.1}
/>
</mesh>
)
}
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 (
<mesh>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial {...textures} />
</mesh>
)
}
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 (
<mesh>
<boxGeometry />
<meshStandardMaterial map={color} normalMap={normal} />
</mesh>
)
}
// 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 (
<mesh>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial map={texture} />
</mesh>
)
}
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 (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardMaterial
metalness={1}
roughness={0}
envMap={envMap}
/>
</mesh>
)
}
// Or use Environment component for scene-wide
function Scene() {
return (
<>
<Environment preset="sunset" background />
<Mesh />
</>
)
}
HDR Environment
import { useEnvironment } from '@react-three/drei'
function HDREnvironment() {
const envMap = useEnvironment({ files: '/hdri/studio.hdr' })
return (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardMaterial
metalness={1}
roughness={0}
envMap={envMap}
envMapIntensity={1}
/>
</mesh>
)
}
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 (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardMaterial envMap={envMap} metalness={1} roughness={0} />
</mesh>
)
}
Video Textures
import { useVideoTexture } from '@react-three/drei'
function VideoPlane() {
const texture = useVideoTexture('/videos/sample.mp4', {
start: true,
loop: true,
muted: true,
})
return (
<mesh>
<planeGeometry args={[16, 9].map(x => x * 0.5)} />
<meshBasicMaterial map={texture} toneMapped={false} />
</mesh>
)
}
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 (
<mesh ref={meshRef}>
<planeGeometry args={[2, 2]} />
<meshBasicMaterial map={textureRef.current} />
</mesh>
)
}
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 (
<mesh>
<planeGeometry args={[2, 2]} />
<meshBasicMaterial map={texture} />
</mesh>
)
}
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 */}
<group ref={otherSceneRef}>
<mesh position={[0, 0, -5]}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="red" />
</mesh>
</group>
{/* 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 (
<mesh>
<planeGeometry args={[1, 1]} />
<meshBasicMaterial map={texture} transparent />
</mesh>
)
}
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 (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial
aoMap={aoTexture}
aoMapIntensity={1}
/>
</mesh>
)
}
Suspense Loading
import { Suspense } from 'react'
import { useTexture } from '@react-three/drei'
function TexturedMesh() {
const texture = useTexture('/textures/large.jpg')
return (
<mesh>
<boxGeometry />
<meshStandardMaterial map={texture} />
</mesh>
)
}
function Fallback() {
return (
<mesh>
<boxGeometry />
<meshBasicMaterial color="gray" wireframe />
</mesh>
)
}
function Scene() {
return (
<Suspense fallback={<Fallback />}>
<TexturedMesh />
</Suspense>
)
}
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 materialsr3f-loaders- Asset loading patternsr3f-shaders- Custom texture sampling
forumUser Reviews (0)
Write a Review
No reviews yet
Statistics
User Rating
Rate this Skill