r3f-textures
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Three Fiber Textures
React Three Fiber 纹理处理
Quick Start
快速开始
tsx
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>
)
}tsx
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)
useTexture Hook(Drei)
The recommended way to load textures in R3F.
这是在R3F中加载纹理的推荐方式。
Single Texture
单张纹理
tsx
import { useTexture } from '@react-three/drei'
function SingleTexture() {
const texture = useTexture('/textures/color.jpg')
return (
<mesh>
<planeGeometry args={[5, 5]} />
<meshBasicMaterial map={texture} />
</mesh>
)
}tsx
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)
多张纹理(数组形式)
tsx
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>
)
}tsx
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)
命名对象(PBR推荐用法)
tsx
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>
)
}tsx
function PBRTextures() {
// 命名对象会自动展开到材质属性中
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
纹理配置
tsx
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>
)
}tsx
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) => {
// 加载完成后配置纹理
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
预加载
tsx
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')
}tsx
import { useTexture } from '@react-three/drei'
// 在模块级别预加载
useTexture.preload('/textures/hero.jpg')
useTexture.preload(['/tex1.jpg', '/tex2.jpg'])
function Component() {
// 如果已预加载,会立即获取
const texture = useTexture('/textures/hero.jpg')
}useLoader (Core R3F)
useLoader(R3F核心API)
For more control over loading.
tsx
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')用于对加载过程进行更多控制。
tsx
import { useLoader } from '@react-three/fiber'
import { TextureLoader } from 'three'
function WithUseLoader() {
const texture = useLoader(TextureLoader, '/textures/color.jpg')
// 加载多张纹理
const [color, normal] = useLoader(TextureLoader, [
'/textures/color.jpg',
'/textures/normal.jpg',
])
return (
<mesh>
<boxGeometry />
<meshStandardMaterial map={color} normalMap={normal} />
</mesh>
)
}
// 预加载
useLoader.preload(TextureLoader, '/textures/color.jpg')Texture Configuration
纹理配置
Wrapping Modes
环绕模式
tsx
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>
)
}tsx
import * as THREE from 'three'
function ConfigureWrapping() {
const texture = useTexture('/textures/tile.jpg', (tex) => {
// 环绕方式
tex.wrapS = THREE.RepeatWrapping // 水平方向:ClampToEdgeWrapping、RepeatWrapping、MirroredRepeatWrapping
tex.wrapT = THREE.RepeatWrapping // 垂直方向
// 重复次数
tex.repeat.set(4, 4) // 4x4平铺
// 偏移
tex.offset.set(0.5, 0.5) // 偏移UV坐标
// 旋转
tex.rotation = Math.PI / 4 // 旋转45度
tex.center.set(0.5, 0.5) // 旋转中心点
})
return (
<mesh>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial map={texture} />
</mesh>
)
}Filtering
过滤方式
tsx
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
})
}tsx
function ConfigureFiltering() {
const texture = useTexture('/textures/color.jpg', (tex) => {
// 缩小过滤(纹理尺寸大于屏幕像素)
tex.minFilter = THREE.LinearMipmapLinearFilter // 使用mipmap平滑过渡(默认)
tex.minFilter = THREE.NearestFilter // 像素化效果
tex.minFilter = THREE.LinearFilter // 平滑无mipmap
// 放大过滤(纹理尺寸小于屏幕像素)
tex.magFilter = THREE.LinearFilter // 平滑过渡(默认)
tex.magFilter = THREE.NearestFilter // 像素化效果(复古风格)
// 各向异性过滤(角度视图下更清晰)
tex.anisotropy = 16 // 通常使用renderer.capabilities.getMaxAnisotropy()
// 生成mipmap
tex.generateMipmaps = true // 默认开启
})
}Color Space
色彩空间
Important for accurate colors.
tsx
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
})
}对色彩准确性至关重要。
tsx
function ConfigureColorSpace() {
const [colorMap, normalMap, roughnessMap] = useTexture([
'/textures/color.jpg',
'/textures/normal.jpg',
'/textures/roughness.jpg',
], (textures) => {
// 颜色/基础色纹理应使用sRGB
textures[0].colorSpace = THREE.SRGBColorSpace
// 数据纹理(法线、粗糙度、金属度、环境光遮蔽)使用线性色彩空间
// 这是默认设置,通常无需手动配置
// textures[1].colorSpace = THREE.LinearSRGBColorSpace
// textures[2].colorSpace = THREE.LinearSRGBColorSpace
})
}Environment Maps
环境贴图
useEnvironment Hook
useEnvironment Hook
tsx
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 />
</>
)
}tsx
import { useEnvironment, Environment } from '@react-three/drei'
// 作为纹理使用
function EnvMappedSphere() {
const envMap = useEnvironment({ preset: 'sunset' })
return (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardMaterial
metalness={1}
roughness={0}
envMap={envMap}
/>
</mesh>
)
}
// 或者使用Environment组件实现场景全局环境
function Scene() {
return (
<>
<Environment preset="sunset" background />
<Mesh />
</>
)
}HDR Environment
HDR环境
tsx
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>
)
}tsx
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
立方体贴图
tsx
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>
)
}tsx
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
视频纹理
tsx
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>
)
}tsx
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
Canvas纹理
tsx
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>
)
}tsx
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')
// 在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)
}, [])
// 动态更新纹理
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
数据纹理
tsx
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>
)
}tsx
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.
tsx
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>
</>
)
}渲染到纹理。
tsx
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 }) => {
// 将其他场景渲染到FBO
gl.setRenderTarget(fbo)
gl.render(otherSceneRef.current, camera)
gl.setRenderTarget(null)
})
return (
<>
{/* 要渲染到纹理的场景 */}
<group ref={otherSceneRef}>
<mesh position={[0, 0, -5]}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="red" />
</mesh>
</group>
{/* 显示纹理 */}
<mesh ref={meshRef}>
<planeGeometry args={[4, 4]} />
<meshBasicMaterial map={fbo.texture} />
</mesh>
</>
)
}Texture Atlas / Sprite Sheet
纹理图集/精灵表
tsx
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>
)
}tsx
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)
// 配置纹理
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping
texture.repeat.set(1/4, 1/4) // 4x4精灵表
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
材质纹理贴图参考
tsx
<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}
/>tsx
<meshStandardMaterial
// 基础颜色(sRGB)
map={colorTexture}
// 表面细节(线性色彩空间)
normalMap={normalTexture}
normalScale={[1, 1]}
// 粗糙度(线性色彩空间,灰度图)
roughnessMap={roughnessTexture}
roughness={1} // 乘数
// 金属度(线性色彩空间,灰度图)
metalnessMap={metalnessTexture}
metalness={1} // 乘数
// 环境光遮蔽(线性色彩空间,需要uv2)
aoMap={aoTexture}
aoMapIntensity={1}
// 自发光(sRGB)
emissiveMap={emissiveTexture}
emissive="#ffffff"
emissiveIntensity={1}
// 顶点位移(线性色彩空间)
displacementMap={displacementTexture}
displacementScale={0.1}
displacementBias={0}
// 透明度(线性色彩空间)
alphaMap={alphaTexture}
transparent={true}
// 环境反射
envMap={envTexture}
envMapIntensity={1}
// 光照贴图(需要uv2)
lightMap={lightmapTexture}
lightMapIntensity={1}
/>Second UV Channel (for AO/Lightmaps)
第二UV通道(用于环境光遮蔽/光照贴图)
tsx
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>
)
}tsx
import { useEffect, useRef } from 'react'
function MeshWithUV2() {
const meshRef = useRef()
useEffect(() => {
// 复制uv到uv2,用于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
Suspense加载
tsx
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>
)
}tsx
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
tsx
// 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()- 使用2的幂次尺寸:256、512、1024、2048
- 压缩纹理:Web端推荐使用KTX2/Basis格式
- 开启mipmap:适用于远景物体
- 限制纹理尺寸:2048通常已足够
- 复用纹理:相同纹理可提升批处理效率
- 预加载重要纹理:避免加载时闪烁
tsx
// 预加载关键纹理
useTexture.preload('/textures/hero.jpg')
// 检查纹理内存占用
useFrame(({ gl }) => {
console.log('纹理数量:', gl.info.memory.textures)
})
// 释放未使用的纹理(R3F通常会自动处理)
texture.dispose()See Also
相关链接
- - Applying textures to materials
r3f-materials - - Asset loading patterns
r3f-loaders - - Custom texture sampling
r3f-shaders
- - 材质纹理应用
r3f-materials - - 资源加载模式
r3f-loaders - - 自定义纹理采样
r3f-shaders