Three.js 纹理 #

纹理(Texture)是 3D 图形中用于增加物体表面细节的图像。通过纹理贴图,可以让简单的几何体呈现出丰富的外观效果。

纹理概述 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Three.js 纹理类型                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │   基础纹理    │  │   功能纹理   │  │   高级纹理   │         │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤         │
│  │ 漫反射贴图    │  │ 法线贴图     │  │ 环境贴图     │         │
│  │ 透明贴图     │  │ 凹凸贴图     │  │ 光照贴图     │         │
│  │ 自发光贴图    │  │ 粗糙度贴图   │  │ 位移贴图     │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                              │
└─────────────────────────────────────────────────────────────┘

加载纹理 #

TextureLoader #

javascript
const textureLoader = new THREE.TextureLoader();

const texture = textureLoader.load('texture.jpg');

const texture = textureLoader.load(
  'texture.jpg',
  (texture) => {
    console.log('纹理加载完成');
  },
  (progress) => {
    console.log('加载进度:', progress);
  },
  (error) => {
    console.error('加载失败:', error);
  }
);

加载管理器 #

javascript
const loadingManager = new THREE.LoadingManager();

loadingManager.onStart = (url, loaded, total) => {
  console.log(`开始加载: ${url}`);
};

loadingManager.onProgress = (url, loaded, total) => {
  console.log(`进度: ${loaded}/${total}`);
};

loadingManager.onLoad = () => {
  console.log('所有资源加载完成');
};

loadingManager.onError = (url) => {
  console.error(`加载错误: ${url}`);
};

const textureLoader = new THREE.TextureLoader(loadingManager);
const texture = textureLoader.load('texture.jpg');

基础纹理属性 #

纹理包裹 #

javascript
const texture = textureLoader.load('texture.jpg');

texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;

texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;

texture.wrapS = THREE.MirroredRepeatWrapping;
texture.wrapT = THREE.MirroredRepeatWrapping;

纹理重复 #

javascript
texture.repeat.set(2, 2);

texture.repeat.x = 4;
texture.repeat.y = 4;

纹理偏移 #

javascript
texture.offset.set(0.5, 0.5);

texture.offset.x = 0.5;
texture.offset.y = 0.5;

纹理旋转 #

javascript
texture.rotation = Math.PI / 4;

texture.center.set(0.5, 0.5);
texture.rotation = Math.PI / 4;

纹理过滤 #

javascript
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;

texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;

texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;

各向异性过滤 #

javascript
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

纹理类型 #

漫反射贴图(Map) #

javascript
const texture = textureLoader.load('diffuse.jpg');

const material = new THREE.MeshStandardMaterial({
  map: texture
});

法线贴图(Normal Map) #

javascript
const normalMap = textureLoader.load('normal.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  normalMap: normalMap,
  normalScale: new THREE.Vector2(1, 1)
});

凹凸贴图(Bump Map) #

javascript
const bumpMap = textureLoader.load('bump.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  bumpMap: bumpMap,
  bumpScale: 0.05
});

粗糙度贴图(Roughness Map) #

javascript
const roughnessMap = textureLoader.load('roughness.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  roughness: 1.0,
  roughnessMap: roughnessMap
});

金属度贴图(Metalness Map) #

javascript
const metalnessMap = textureLoader.load('metalness.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  metalness: 1.0,
  metalnessMap: metalnessMap
});

环境光遮蔽贴图(AO Map) #

javascript
const aoMap = textureLoader.load('ao.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  aoMap: aoMap,
  aoMapIntensity: 1.0
});

geometry.setAttribute('uv2', geometry.getAttribute('uv'));

自发光贴图(Emissive Map) #

javascript
const emissiveMap = textureLoader.load('emissive.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  emissive: 0xffffff,
  emissiveMap: emissiveMap,
  emissiveIntensity: 1.0
});

透明贴图(Alpha Map) #

javascript
const alphaMap = textureLoader.load('alpha.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  alphaMap: alphaMap,
  transparent: true
});

位移贴图(Displacement Map) #

javascript
const displacementMap = textureLoader.load('displacement.jpg');

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  displacementMap: displacementMap,
  displacementScale: 0.5,
  displacementBias: 0
});

环境贴图 #

CubeTextureLoader #

javascript
const cubeTextureLoader = new THREE.CubeTextureLoader();

const envMap = cubeTextureLoader.load([
  'px.jpg',
  'nx.jpg',
  'py.jpg',
  'ny.jpg',
  'pz.jpg',
  'nz.jpg'
]);

scene.background = envMap;

const material = new THREE.MeshStandardMaterial({
  color: 0xff6b6b,
  metalness: 1.0,
  roughness: 0.1,
  envMap: envMap,
  envMapIntensity: 1.0
});

HDR 环境贴图 #

javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

const rgbeLoader = new RGBELoader();

rgbeLoader.load('environment.hdr', (texture) => {
  texture.mapping = THREE.EquirectangularReflectionMapping;
  
  scene.background = texture;
  scene.environment = texture;
});

PMREMGenerator #

javascript
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

const pmremGenerator = new THREE.PMREMGenerator(renderer);
const envMap = pmremGenerator.fromScene(new RoomEnvironment()).texture;

scene.environment = envMap;

程序化纹理 #

Canvas 纹理 #

javascript
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');

ctx.fillStyle = '#ff6b6b';
ctx.fillRect(0, 0, 256, 256);

ctx.fillStyle = '#ffffff';
ctx.font = '48px Arial';
ctx.fillText('Hello', 50, 128);

const texture = new THREE.CanvasTexture(canvas);

const material = new THREE.MeshBasicMaterial({ map: texture });

DataTexture #

javascript
const width = 256;
const height = 256;
const data = new Uint8Array(width * height * 4);

for (let i = 0; i < width * height; i++) {
  const stride = i * 4;
  data[stride] = Math.random() * 255;
  data[stride + 1] = Math.random() * 255;
  data[stride + 2] = Math.random() * 255;
  data[stride + 3] = 255;
}

const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
texture.needsUpdate = true;

const material = new THREE.MeshBasicMaterial({ map: texture });

视频纹理 #

javascript
const video = document.createElement('video');
video.src = 'video.mp4';
video.loop = true;
video.muted = true;
video.play();

const texture = new THREE.VideoTexture(video);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;

const material = new THREE.MeshBasicMaterial({ map: texture });

UV 映射 #

UV 坐标 #

text
UV 坐标系统:
(0,1) ─────────── (1,1)
  │                 │
  │                 │
  │                 │
  │                 │
(0,0) ─────────── (1,0)

U: 水平方向 (0-1)
V: 垂直方向 (0-1)

自定义 UV #

javascript
const geometry = new THREE.PlaneGeometry(2, 2);

const uvAttribute = geometry.getAttribute('uv');

uvAttribute.setXY(0, 0, 0);
uvAttribute.setXY(1, 1, 0);
uvAttribute.setXY(2, 0, 1);
uvAttribute.setXY(3, 1, 1);

uvAttribute.needsUpdate = true;

UV 变换 #

javascript
texture.matrixAutoUpdate = false;
texture.matrix.setUvTransform(
  0.5,
  0.5,
  2,
  2,
  Math.PI / 4,
  0,
  0
);

纹理动画 #

纹理滚动 #

javascript
const texture = textureLoader.load('texture.jpg');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;

function animate() {
  requestAnimationFrame(animate);
  
  texture.offset.x += 0.01;
  texture.offset.y += 0.005;
  
  renderer.render(scene, camera);
}
animate();

精灵动画 #

javascript
const texture = textureLoader.load('spritesheet.png');
texture.repeat.set(1 / 4, 1 / 4);

let currentFrame = 0;
const totalFrames = 16;
const framesPerRow = 4;

function animate() {
  requestAnimationFrame(animate);
  
  currentFrame = (currentFrame + 1) % totalFrames;
  const col = currentFrame % framesPerRow;
  const row = Math.floor(currentFrame / framesPerRow);
  
  texture.offset.x = col / framesPerRow;
  texture.offset.y = 1 - (row + 1) / (totalFrames / framesPerRow);
  
  renderer.render(scene, camera);
}
animate();

纹理优化 #

纹理尺寸 #

javascript
// 推荐尺寸:2 的幂次方
const recommendedSizes = [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];

// 非 2 的幂次方纹理会被自动调整
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;

纹理压缩 #

javascript
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

const ktx2Loader = new KTX2Loader(manager);
ktx2Loader.setTranscoderPath('jsm/libs/basis/');
ktx2Loader.detectSupport(renderer);

ktx2Loader.load('texture.ktx2', (texture) => {
  material.map = texture;
});

纹理释放 #

javascript
texture.dispose();

纹理类型对比表 #

纹理类型 用途 影响属性
map 基础颜色 color
normalMap 表面细节 法线方向
bumpMap 凹凸效果 表面高度
roughnessMap 粗糙度 roughness
metalnessMap 金属度 metalness
aoMap 遮蔽 环境光
emissiveMap 自发光 emissive
alphaMap 透明度 opacity
displacementMap 位移 顶点位置
envMap 环境 反射

完整示例 #

javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(3, 3, 3);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);

const textureLoader = new THREE.TextureLoader();

const diffuseMap = textureLoader.load('https://threejs.org/examples/textures/brick_diffuse.jpg');
const normalMap = textureLoader.load('https://threejs.org/examples/textures/brick_normal.jpg');
const roughnessMap = textureLoader.load('https://threejs.org/examples/textures/brick_roughness.jpg');

diffuseMap.wrapS = THREE.RepeatWrapping;
diffuseMap.wrapT = THREE.RepeatWrapping;
diffuseMap.repeat.set(2, 2);

normalMap.wrapS = THREE.RepeatWrapping;
normalMap.wrapT = THREE.RepeatWrapping;
normalMap.repeat.set(2, 2);

roughnessMap.wrapS = THREE.RepeatWrapping;
roughnessMap.wrapT = THREE.RepeatWrapping;
roughnessMap.repeat.set(2, 2);

const material = new THREE.MeshStandardMaterial({
  map: diffuseMap,
  normalMap: normalMap,
  roughnessMap: roughnessMap,
  roughness: 0.8,
  metalness: 0.1
});

const geometry = new THREE.BoxGeometry(2, 2, 2);
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshStandardMaterial({
  color: 0x333333,
  roughness: 0.9,
  metalness: 0.1
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.position.y = -1;
scene.add(plane);

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);
  
  const elapsedTime = clock.getElapsedTime();
  
  cube.rotation.y = elapsedTime * 0.5;
  
  controls.update();
  renderer.render(scene, camera);
}
animate();

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

下一步 #

现在你已经掌握了纹理系统,接下来学习 着色器,创建自定义渲染效果!

最后更新:2026-03-28