纹理基础
纹理是增强 3D 场景视觉效果的重要工具,通过贴图可以为物体添加细节、颜色、法线等信息。
概述
纹理(Texture)是应用于 3D 对象表面的图像,可以模拟各种表面细节,如颜色、凹凸、光泽等。Three.js 支持多种纹理类型和加载方式,提供了完整的纹理管理机制。
核心概念
| 概念 | 说明 |
|---|---|
| UV 坐标 | 2D 纹理坐标,用于将纹理映射到几何体表面 |
| Mipmap | 多级渐远纹理,用于优化不同距离下的纹理采样 |
| 过滤 | 决定纹理放大/缩小时的采样方式 |
| 环绕模式 | 决定 UV 坐标超出 [0,1] 范围时的处理方式 |
纹理加载
TextureLoader
最常用的纹理加载器,支持 JPG、PNG、GIF、WebP 等常见图像格式。
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 管理多个纹理的加载进度。
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
加载立方体纹理,用于环境贴图和天空盒。
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 环境贴图,支持高动态范围图像。
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;
});纹理属性
基础属性
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 变换实现纹理的平移、旋转和缩放效果。
// 重复
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 | 纹理镜像重复 |
// 重复
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// 钳制到边缘(默认)
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
// 镜像重复
texture.wrapS = THREE.MirroredRepeatWrapping;
texture.wrapT = THREE.MirroredRepeatWrapping;过滤方式
控制纹理在放大或缩小时的采样质量。
// 缩小过滤(纹理缩小时)
texture.minFilter = THREE.LinearMipmapLinearFilter; // 默认,三线性过滤
// 放大过滤(纹理放大时)
texture.magFilter = THREE.LinearFilter; // 默认,线性过滤缩小过滤选项(minFilter)
| 常量 | 说明 | 质量 | 性能 |
|---|---|---|---|
NearestFilter | 最近邻采样,像素化效果 | 低 | 最佳 |
LinearFilter | 双线性插值 | 中 | 良好 |
NearestMipmapNearestFilter | 最近邻 + 最近 mip | 中 | 良好 |
NearestMipmapLinearFilter | 最近邻 + 线性 mip | 中高 | 中等 |
LinearMipmapNearestFilter | 线性 + 最近 mip | 中高 | 中等 |
LinearMipmapLinearFilter | 三线性过滤(默认) | 高 | 一般 |
放大过滤选项(magFilter)
| 常量 | 说明 | 效果 |
|---|---|---|
NearestFilter | 最近邻采样 | 像素化、锐利边缘 |
LinearFilter | 线性插值(默认) | 平滑、略微模糊 |
各向异性过滤
改善斜视角度下纹理的清晰度,特别适用于地面、路面等平面纹理。
// 获取最大各向异性
const maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
// 设置各向异性过滤
texture.anisotropy = maxAnisotropy;
// 通常设置为 4-16 即可获得良好效果
texture.anisotropy = 8;Mipmap
Mipmap 是预生成的多级分辨率纹理,用于优化远距离渲染性能和质量。
// 自动生成 mipmap(默认 true)
texture.generateMipmaps = true;
// 手动设置 mipmap 级别
texture.mipmaps = [
level0,
level1,
level2,
// ...
];
// mipmap 偏移(用于模糊或锐化效果)
texture.mipBias = 0;纹理类型
漫反射贴图(Diffuse/Albedo Map)
定义物体表面的基础颜色。
const diffuseMap = textureLoader.load('diffuse.jpg');
const material = new THREE.MeshStandardMaterial({
map: diffuseMap
});法线贴图(Normal Map)
模拟表面细节,无需增加几何体复杂度,通过修改表面法线实现凹凸效果。
const normalMap = textureLoader.load('normal.jpg');
const material = new THREE.MeshStandardMaterial({
normalMap: normalMap,
normalScale: new THREE.Vector2(1, 1) // 法线强度
});凹凸贴图(Bump Map)
使用灰度图模拟表面凹凸,比法线贴图简单但效果不如法线贴图。
const bumpMap = textureLoader.load('bump.jpg');
const material = new THREE.MeshStandardMaterial({
bumpMap: bumpMap,
bumpScale: 0.05 // 凹凸强度
});位移贴图(Displacement Map)
实际改变几何体顶点位置,产生真实的凹凸效果。
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)
模拟角落和裂缝的阴影,增加场景真实感。
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)
实现物体自发光效果,不受光照影响。
const emissiveMap = textureLoader.load('emissive.jpg');
const material = new THREE.MeshStandardMaterial({
emissive: new THREE.Color(0xffffff),
emissiveMap: emissiveMap,
emissiveIntensity: 1.0
});金属度贴图(Metalness Map)
定义表面的金属程度,白色表示金属,黑色表示非金属。
const metalnessMap = textureLoader.load('metalness.jpg');
const material = new THREE.MeshStandardMaterial({
metalness: 1.0,
metalnessMap: metalnessMap
});粗糙度贴图(Roughness Map)
定义表面的粗糙程度,影响反射的模糊程度。
const roughnessMap = textureLoader.load('roughness.jpg');
const material = new THREE.MeshStandardMaterial({
roughness: 1.0,
roughnessMap: roughnessMap
});透明度贴图(Alpha Map)
定义表面透明度,白色不透明,黑色完全透明。
const alphaMap = textureLoader.load('alpha.jpg');
const material = new THREE.MeshStandardMaterial({
alphaMap: alphaMap,
transparent: true
});创建纹理
从 Canvas 创建
适合动态生成纹理或绘制程序化图案。
// 创建 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;
}从数据创建
使用原始像素数据创建纹理,适合程序化生成。
// 创建数据纹理
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;程序化纹理
// 创建棋盘格纹理
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');纹理优化
尺寸优化
// 纹理尺寸应为 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;压缩纹理
使用压缩纹理格式可以显著减少显存占用和加载时间。
// 使用压缩纹理加载器
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');纹理缓存
// Three.js 内置缓存
THREE.Cache.enabled = true;
// 相同 URL 的纹理只会加载一次
const texture1 = textureLoader.load('texture.jpg');
const texture2 = textureLoader.load('texture.jpg');
// texture1 === texture2按需加载
// 根据距离使用不同分辨率的纹理
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 偏移实现流动效果,常用于水流、熔岩等。
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();纹理切换动画
实现帧动画效果。
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 材质。
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 类主要属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
image | Image | null | 纹理图像数据 |
name | string | '' | 纹理名称 |
mapping | number | UVMapping | 纹理映射方式 |
wrapS | number | ClampToEdgeWrapping | 水平环绕模式 |
wrapT | number | ClampToEdgeWrapping | 垂直环绕模式 |
magFilter | number | LinearFilter | 放大过滤 |
minFilter | number | LinearMipmapLinearFilter | 缩小过滤 |
anisotropy | number | 1 | 各向异性过滤级别 |
format | number | RGBAFormat | 像素格式 |
type | number | UnsignedByteType | 数据类型 |
offset | Vector2 | (0,0) | UV 偏移 |
repeat | Vector2 | (1,1) | UV 重复 |
rotation | number | 0 | UV 旋转(弧度) |
center | Vector2 | (0,0) | 旋转中心 |
generateMipmaps | boolean | true | 是否生成 mipmap |
needsUpdate | boolean | false | 是否需要更新 |
Texture 类主要方法
| 方法 | 说明 |
|---|---|
dispose() | 释放纹理资源 |
clone() | 克隆纹理 |
toJSON() | 导出为 JSON |
常见问题
Q: 纹理显示模糊怎么办?
A: 检查以下几点:
- 确保纹理尺寸是 2 的幂次方
- 检查过滤设置,使用
LinearMipmapLinearFilter - 提高各向异性过滤值
- 检查纹理分辨率是否足够
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();Q: 纹理加载失败但不报错?
A: 可能原因:
- 跨域问题:设置
TextureLoader.crossOrigin - 文件路径错误
- 图片格式不支持
const loader = new THREE.TextureLoader();
loader.crossOrigin = 'anonymous';
// 使用错误回调
loader.load('texture.jpg',
(tex) => { console.log('成功'); },
undefined,
(err) => { console.error('加载失败:', err); }
);Q: 纹理闪烁或出现接缝?
A: 可能原因和解决方案:
- Mipmap 颜色溢出:使用
repeat时设置THREE.RepeatWrapping - UV 坐标问题:检查几何体的 UV 设置
- 精度问题:确保纹理边缘处理正确
Q: 如何优化纹理内存占用?
A: 推荐做法:
- 使用 2 的幂次方尺寸纹理
- 使用压缩纹理格式(KTX2、Basis)
- 根据距离使用不同分辨率
- 及时调用
dispose()释放不用的纹理
// 释放纹理资源
texture.dispose();
// 使用压缩格式
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';Q: AO 贴图没有效果?
A: AO 贴图需要第二套 UV 坐标:
// 必须设置 uv2
geometry.setAttribute('uv2', geometry.attributes.uv);Q: 法线贴图颜色看起来不对?
A: 检查法线贴图类型:
- 切线空间法线贴图:偏蓝色(默认)
- 对象空间法线贴图:彩色
// 确保使用正确的法线空间
material.normalMapType = THREE.TangentSpaceNormalMap; // 默认最佳实践
- 纹理尺寸:使用 2 的幂次方尺寸(256, 512, 1024, 2048)
- 合理分辨率:根据物体大小和观察距离选择
- 压缩优化:生产环境使用 KTX2/Basis 压缩格式
- 复用纹理:相同纹理共享实例,避免重复加载
- 及时释放:不再使用的纹理调用
dispose()释放显存 - 预加载:关键纹理在场景初始化时预加载
- 渐进加载:先加载低分辨率版本,再替换为高分辨率