{T}

纹理基础

纹理是增强 3D 场景视觉效果的重要工具,通过贴图可以为物体添加细节、颜色、法线等信息。

概述

纹理(Texture)是应用于 3D 对象表面的图像,可以模拟各种表面细节,如颜色、凹凸、光泽等。Three.js 支持多种纹理类型和加载方式,提供了完整的纹理管理机制。

核心概念

概念说明
UV 坐标2D 纹理坐标,用于将纹理映射到几何体表面
Mipmap多级渐远纹理,用于优化不同距离下的纹理采样
过滤决定纹理放大/缩小时的采样方式
环绕模式决定 UV 坐标超出 [0,1] 范围时的处理方式

纹理加载

TextureLoader

最常用的纹理加载器,支持 JPG、PNG、GIF、WebP 等常见图像格式。

javascript
import * as THREE from 'three';
 
// 创建加载器
const textureLoader = new THREE.TextureLoader();
 
// 加载纹理
const texture = textureLoader.load('texture.jpg');
 
// 加载带回调的纹理
const texture = textureLoader.load(
  'texture.jpg',
  // 加载完成回调
  (texture) => {
    console.log('纹理加载完成');
  },
  // 加载进度回调
  (xhr) => {
    console.log(`${(xhr.loaded / xhr.total * 100)}% 已加载`);
  },
  // 加载错误回调
  (error) => {
    console.error('纹理加载失败:', error);
  }
);
 
// 应用到材质
const material = new THREE.MeshStandardMaterial({
  map: texture
});

批量加载纹理

使用 LoadingManager 管理多个纹理的加载进度。

javascript
import { LoadingManager } from 'three';
 
// 创建加载管理器
const loadingManager = new LoadingManager();
 
loadingManager.onStart = (url, itemsLoaded, itemsTotal) => {
  console.log(`开始加载: ${url}`);
};
 
loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
  console.log(`进度: ${itemsLoaded}/${itemsTotal}`);
};
 
loadingManager.onLoad = () => {
  console.log('所有纹理加载完成');
};
 
loadingManager.onError = (url) => {
  console.error(`加载错误: ${url}`);
};
 
// 使用加载管理器
const textureLoader = new THREE.TextureLoader(loadingManager);
 
const textures = {
  diffuse: textureLoader.load('diffuse.jpg'),
  normal: textureLoader.load('normal.jpg'),
  roughness: textureLoader.load('roughness.jpg')
};

CubeTextureLoader

加载立方体纹理,用于环境贴图和天空盒。

javascript
const cubeTextureLoader = new THREE.CubeTextureLoader();
 
const envMap = cubeTextureLoader.load([
  'px.jpg', 'nx.jpg',  // 正负 X
  'py.jpg', 'ny.jpg',  // 正负 Y
  'pz.jpg', 'nz.jpg'   // 正负 Z
]);
 
scene.environment = envMap;
scene.background = envMap;

RGBELoader

加载 HDR 环境贴图,支持高动态范围图像。

javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
 
const rgbeLoader = new RGBELoader();
 
rgbeLoader.load('environment.hdr', (texture) => {
  texture.mapping = THREE.EquirectangularReflectionMapping;
  scene.environment = texture;
  scene.background = texture;
});

纹理属性

基础属性

javascript
const texture = new THREE.Texture();
 
// 图像数据
texture.image = new Image();
 
// 尺寸
texture.width = 512;
texture.height = 512;
 
// UUID 和名称
texture.uuid;
texture.name = 'myTexture';
 
// 类型
texture.type = THREE.UnsignedByteType;
 
// 格式
texture.format = THREE.RGBAFormat;
 
// 内部格式
texture.internalFormat = 'RGBA8';

UV 变换

通过 UV 变换实现纹理的平移、旋转和缩放效果。

javascript
// 重复
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);  // 在 U 和 V 方向重复 2 次
 
// 偏移
texture.offset.set(0.5, 0.5);  // UV 偏移
 
// 旋转(弧度)
texture.rotation = Math.PI / 4;  // 旋转 45 度
 
// 旋转中心
texture.center.set(0.5, 0.5);  // 以纹理中心旋转

环绕模式

决定纹理坐标超出 [0,1] 范围时的处理方式。

模式常量效果
钳制到边缘ClampToEdgeWrapping边缘像素重复延伸(默认)
重复RepeatWrapping纹理无限重复平铺
镜像重复MirroredRepeatWrapping纹理镜像重复
javascript
// 重复
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.minFilter = THREE.LinearMipmapLinearFilter;  // 默认,三线性过滤
 
// 放大过滤(纹理放大时)
texture.magFilter = THREE.LinearFilter;  // 默认,线性过滤

缩小过滤选项(minFilter)

常量说明质量性能
NearestFilter最近邻采样,像素化效果最佳
LinearFilter双线性插值良好
NearestMipmapNearestFilter最近邻 + 最近 mip良好
NearestMipmapLinearFilter最近邻 + 线性 mip中高中等
LinearMipmapNearestFilter线性 + 最近 mip中高中等
LinearMipmapLinearFilter三线性过滤(默认)一般

放大过滤选项(magFilter)

常量说明效果
NearestFilter最近邻采样像素化、锐利边缘
LinearFilter线性插值(默认)平滑、略微模糊

各向异性过滤

改善斜视角度下纹理的清晰度,特别适用于地面、路面等平面纹理。

javascript
// 获取最大各向异性
const maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
 
// 设置各向异性过滤
texture.anisotropy = maxAnisotropy;
 
// 通常设置为 4-16 即可获得良好效果
texture.anisotropy = 8;

Mipmap

Mipmap 是预生成的多级分辨率纹理,用于优化远距离渲染性能和质量。

javascript
// 自动生成 mipmap(默认 true)
texture.generateMipmaps = true;
 
// 手动设置 mipmap 级别
texture.mipmaps = [
  level0,
  level1,
  level2,
  // ...
];
 
// mipmap 偏移(用于模糊或锐化效果)
texture.mipBias = 0;

纹理类型

漫反射贴图(Diffuse/Albedo Map)

定义物体表面的基础颜色。

javascript
const diffuseMap = textureLoader.load('diffuse.jpg');
 
const material = new THREE.MeshStandardMaterial({
  map: diffuseMap
});

法线贴图(Normal Map)

模拟表面细节,无需增加几何体复杂度,通过修改表面法线实现凹凸效果。

javascript
const normalMap = textureLoader.load('normal.jpg');
 
const material = new THREE.MeshStandardMaterial({
  normalMap: normalMap,
  normalScale: new THREE.Vector2(1, 1)  // 法线强度
});

凹凸贴图(Bump Map)

使用灰度图模拟表面凹凸,比法线贴图简单但效果不如法线贴图。

javascript
const bumpMap = textureLoader.load('bump.jpg');
 
const material = new THREE.MeshStandardMaterial({
  bumpMap: bumpMap,
  bumpScale: 0.05  // 凹凸强度
});

位移贴图(Displacement Map)

实际改变几何体顶点位置,产生真实的凹凸效果。

javascript
const displacementMap = textureLoader.load('displacement.jpg');
 
const material = new THREE.MeshStandardMaterial({
  displacementMap: displacementMap,
  displacementScale: 1,
  displacementBias: 0
});
 
// 需要足够多的顶点才能有效果
const geometry = new THREE.PlaneGeometry(4, 4, 128, 128);

环境光遮蔽贴图(AO Map)

模拟角落和裂缝的阴影,增加场景真实感。

javascript
const aoMap = textureLoader.load('ao.jpg');
 
const material = new THREE.MeshStandardMaterial({
  aoMap: aoMap,
  aoMapIntensity: 1.0
});
 
// 需要设置 UV2(第二套 UV 坐标)
geometry.setAttribute('uv2', geometry.attributes.uv);

自发光贴图(Emissive Map)

实现物体自发光效果,不受光照影响。

javascript
const emissiveMap = textureLoader.load('emissive.jpg');
 
const material = new THREE.MeshStandardMaterial({
  emissive: new THREE.Color(0xffffff),
  emissiveMap: emissiveMap,
  emissiveIntensity: 1.0
});

金属度贴图(Metalness Map)

定义表面的金属程度,白色表示金属,黑色表示非金属。

javascript
const metalnessMap = textureLoader.load('metalness.jpg');
 
const material = new THREE.MeshStandardMaterial({
  metalness: 1.0,
  metalnessMap: metalnessMap
});

粗糙度贴图(Roughness Map)

定义表面的粗糙程度,影响反射的模糊程度。

javascript
const roughnessMap = textureLoader.load('roughness.jpg');
 
const material = new THREE.MeshStandardMaterial({
  roughness: 1.0,
  roughnessMap: roughnessMap
});

透明度贴图(Alpha Map)

定义表面透明度,白色不透明,黑色完全透明。

javascript
const alphaMap = textureLoader.load('alpha.jpg');
 
const material = new THREE.MeshStandardMaterial({
  alphaMap: alphaMap,
  transparent: true
});

创建纹理

从 Canvas 创建

适合动态生成纹理或绘制程序化图案。

javascript
// 创建 Canvas
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
 
const ctx = canvas.getContext('2d');
 
// 绘制内容
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = '#00ff00';
ctx.fillRect(256, 0, 256, 256);
 
// 创建纹理
const texture = new THREE.CanvasTexture(canvas);
 
// 动态更新纹理
function updateTexture() {
  ctx.clearRect(0, 0, 512, 512);
  ctx.fillStyle = `hsl(${Date.now() * 0.01 % 360}, 100%, 50%)`;
  ctx.fillRect(0, 0, 512, 512);
  texture.needsUpdate = true;
}

从数据创建

使用原始像素数据创建纹理,适合程序化生成。

javascript
// 创建数据纹理
const size = 64;
const data = new Uint8Array(size * size * 4);
 
for (let i = 0; i < size * size; i++) {
  const stride = i * 4;
  data[stride] = Math.random() * 255;     // R
  data[stride + 1] = Math.random() * 255; // G
  data[stride + 2] = Math.random() * 255; // B
  data[stride + 3] = 255;                  // A
}
 
const texture = new THREE.DataTexture(data, size, size);
texture.needsUpdate = true;

程序化纹理

javascript
// 创建棋盘格纹理
function createCheckerboardTexture(size, color1, color2) {
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');
  
  const halfSize = size / 2;
  ctx.fillStyle = color1;
  ctx.fillRect(0, 0, halfSize, halfSize);
  ctx.fillRect(halfSize, halfSize, halfSize, halfSize);
  ctx.fillStyle = color2;
  ctx.fillRect(halfSize, 0, halfSize, halfSize);
  ctx.fillRect(0, halfSize, halfSize, halfSize);
  
  return new THREE.CanvasTexture(canvas);
}
 
const checkerTexture = createCheckerboardTexture(256, '#ffffff', '#000000');

纹理优化

尺寸优化

javascript
// 纹理尺寸应为 2 的幂次方以获得最佳性能
const powerOfTwoSizes = [
  1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096
];
 
// 如果不是 2 的幂次方,需要限制过滤方式
texture.minFilter = THREE.LinearFilter;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;

压缩纹理

使用压缩纹理格式可以显著减少显存占用和加载时间。

javascript
// 使用压缩纹理加载器
import { DDSLoader } from 'three/addons/loaders/DDSLoader.js';
 
const ddsLoader = new DDSLoader();
const compressedTexture = ddsLoader.load('texture.dds');
 
// KTX2 格式(推荐)
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
 
const ktx2Loader = new KTX2Loader(manager);
ktx2Loader.setTranscoderPath('/basis/');
ktx2Loader.detectSupport(renderer);
 
const texture = ktx2Loader.load('texture.ktx2');

纹理缓存

javascript
// Three.js 内置缓存
THREE.Cache.enabled = true;
 
// 相同 URL 的纹理只会加载一次
const texture1 = textureLoader.load('texture.jpg');
const texture2 = textureLoader.load('texture.jpg');
// texture1 === texture2

按需加载

javascript
// 根据距离使用不同分辨率的纹理
function loadTextureWithLOD(distance) {
  if (distance < 10) {
    return textureLoader.load('texture_high.jpg');
  } else if (distance < 50) {
    return textureLoader.load('texture_medium.jpg');
  } else {
    return textureLoader.load('texture_low.jpg');
  }
}

纹理动画

UV 动画

通过修改 UV 偏移实现流动效果,常用于水流、熔岩等。

javascript
const texture = textureLoader.load('water.jpg');
 
function animate() {
  requestAnimationFrame(animate);
  
  // UV 偏移动画
  texture.offset.x += 0.01;
  texture.offset.y += 0.005;
  
  renderer.render(scene, camera);
}
animate();

纹理切换动画

实现帧动画效果。

javascript
const textures = [
  textureLoader.load('frame1.jpg'),
  textureLoader.load('frame2.jpg'),
  textureLoader.load('frame3.jpg')
];
 
let currentFrame = 0;
const material = new THREE.MeshBasicMaterial({ map: textures[0] });
 
function animate() {
  requestAnimationFrame(animate);
  
  currentFrame = (currentFrame + 0.1) % textures.length;
  material.map = textures[Math.floor(currentFrame)];
  
  renderer.render(scene, camera);
}
animate();

完整示例

多纹理材质

结合多种贴图创建 PBR 材质。

javascript
import * as THREE from 'three';
 
const textureLoader = new THREE.TextureLoader();
 
// 加载所有贴图
const maps = {
  map: textureLoader.load('diffuse.jpg'),
  normalMap: textureLoader.load('normal.jpg'),
  roughnessMap: textureLoader.load('roughness.jpg'),
  metalnessMap: textureLoader.load('metalness.jpg'),
  aoMap: textureLoader.load('ao.jpg')
};
 
// 设置纹理属性
Object.values(maps).forEach(texture => {
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(1, 1);
  texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
});
 
// 创建材质
const material = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  metalness: 1.0,
  roughness: 1.0,
  ...maps
});
 
// 设置 UV2(AO 贴图需要)
geometry.setAttribute('uv2', geometry.attributes.uv);
 
// 创建网格
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

API 参考

Texture 类主要属性

属性类型默认值说明
imageImagenull纹理图像数据
namestring''纹理名称
mappingnumberUVMapping纹理映射方式
wrapSnumberClampToEdgeWrapping水平环绕模式
wrapTnumberClampToEdgeWrapping垂直环绕模式
magFilternumberLinearFilter放大过滤
minFilternumberLinearMipmapLinearFilter缩小过滤
anisotropynumber1各向异性过滤级别
formatnumberRGBAFormat像素格式
typenumberUnsignedByteType数据类型
offsetVector2(0,0)UV 偏移
repeatVector2(1,1)UV 重复
rotationnumber0UV 旋转(弧度)
centerVector2(0,0)旋转中心
generateMipmapsbooleantrue是否生成 mipmap
needsUpdatebooleanfalse是否需要更新

Texture 类主要方法

方法说明
dispose()释放纹理资源
clone()克隆纹理
toJSON()导出为 JSON

常见问题

Q: 纹理显示模糊怎么办?

A: 检查以下几点:

  1. 确保纹理尺寸是 2 的幂次方
  2. 检查过滤设置,使用 LinearMipmapLinearFilter
  3. 提高各向异性过滤值
  4. 检查纹理分辨率是否足够
javascript
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

Q: 纹理加载失败但不报错?

A: 可能原因:

  1. 跨域问题:设置 TextureLoader.crossOrigin
  2. 文件路径错误
  3. 图片格式不支持
javascript
const loader = new THREE.TextureLoader();
loader.crossOrigin = 'anonymous';
 
// 使用错误回调
loader.load('texture.jpg', 
  (tex) => { console.log('成功'); },
  undefined,
  (err) => { console.error('加载失败:', err); }
);

Q: 纹理闪烁或出现接缝?

A: 可能原因和解决方案:

  1. Mipmap 颜色溢出:使用 repeat 时设置 THREE.RepeatWrapping
  2. UV 坐标问题:检查几何体的 UV 设置
  3. 精度问题:确保纹理边缘处理正确

Q: 如何优化纹理内存占用?

A: 推荐做法:

  1. 使用 2 的幂次方尺寸纹理
  2. 使用压缩纹理格式(KTX2、Basis)
  3. 根据距离使用不同分辨率
  4. 及时调用 dispose() 释放不用的纹理
javascript
// 释放纹理资源
texture.dispose();
 
// 使用压缩格式
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

Q: AO 贴图没有效果?

A: AO 贴图需要第二套 UV 坐标:

javascript
// 必须设置 uv2
geometry.setAttribute('uv2', geometry.attributes.uv);

Q: 法线贴图颜色看起来不对?

A: 检查法线贴图类型:

  • 切线空间法线贴图:偏蓝色(默认)
  • 对象空间法线贴图:彩色
javascript
// 确保使用正确的法线空间
material.normalMapType = THREE.TangentSpaceNormalMap; // 默认

最佳实践

  1. 纹理尺寸:使用 2 的幂次方尺寸(256, 512, 1024, 2048)
  2. 合理分辨率:根据物体大小和观察距离选择
  3. 压缩优化:生产环境使用 KTX2/Basis 压缩格式
  4. 复用纹理:相同纹理共享实例,避免重复加载
  5. 及时释放:不再使用的纹理调用 dispose() 释放显存
  6. 预加载:关键纹理在场景初始化时预加载
  7. 渐进加载:先加载低分辨率版本,再替换为高分辨率

相关链接