r3f-textures

React Three Fiber textures - useTexture, texture loading, environment maps, texture configuration. Use when loading images, working with PBR texture sets,…

INSTALLATION
npx skills add https://github.com/enzed/r3f-skills --skill r3f-textures
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

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 (

<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()

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card