学习Three.js--纹理贴图(Texture)
学习Three.js--纹理贴图(Texture)
前置核心说明
纹理贴图是 Three.js 中让3D模型呈现真实外观的核心手段,本质是将2D图片(纹理)「贴」到3D几何体表面,替代单一的纯色材质,实现照片级的视觉效果(如墙面纹理、地面瓷砖、金属质感、木纹等)。
核心规则
-
核心流程:
加载图片 → 创建纹理对象(Texture) → 绑定到材质.map属性 → 几何体通过UV坐标映射纹理; -
颜色空间必设:加载纹理后必须设置
texture.colorSpace = THREE.SRGBColorSpace,否则图片会出现偏色(Three.js r152+ 版本新增,适配真实色彩); - UV坐标是桥梁:UV坐标(2D)关联纹理图片和几何体顶点(3D),是纹理「贴在哪个位置」的核心控制手段;
-
材质适配:所有 Mesh 系列材质(MeshBasicMaterial/MeshStandardMaterial 等)都支持
map纹理属性,仅Line/Points/Sprite材质不支持。
一、纹理核心概念与基础加载
1. 核心术语解析
| 术语 | 核心说明 |
|---|---|
| 纹理对象(Texture) | Three.js 对2D图片的封装,包含图片数据、映射规则、重复模式等属性 |
| UV坐标 | 2D纹理坐标系(U=横向,V=纵向),范围默认0~1,(0,0)=图片左下角,(1,1)=图片右上角 |
| 纹理加载器(TextureLoader) | Three.js 专门用于加载图片并生成纹理对象的工具类 |
| 映射(Mapping) | 纹理通过UV坐标与几何体顶点的绑定关系,决定图片哪部分贴在几何体哪个位置 |
2. 纹理加载(TextureLoader):完整用法与参数
TextureLoader 是加载纹理的核心工具,支持单张加载、批量加载,可处理加载进度/错误/完成回调。
2.1 基础加载
// 1. 创建纹理加载器实例
const texLoader = new THREE.TextureLoader();
// 2. 加载图片并创建纹理对象
// 语法:texLoader.load(图片路径, 加载完成回调, 加载进度回调, 加载错误回调)
const texture = texLoader.load(
'./gravelly_sand_diff_1k.jpg', // 必传:图片路径(本地/CDN)
(texture) => { // 可选:加载完成回调
console.log('纹理加载完成', texture);
},
(xhr) => { // 可选:加载进度回调(xhr=XMLHttpRequest)
console.log(`加载进度:${(xhr.loaded / xhr.total) * 100}%`);
},
(err) => { // 可选:加载错误回调
console.error('纹理加载失败', err);
}
);
// 3. 关键:设置颜色空间(避免图片偏色,r152+必加)
texture.colorSpace = THREE.SRGBColorSpace;
2.2 TextureLoader 核心参数(load方法)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
url |
String | 是 | 图片路径(支持本地相对路径、CDN链接、Base64) |
onLoad |
Function | 否 | 加载完成回调,参数为生成的Texture对象 |
onProgress |
Function | 否 | 加载进度回调,参数为XMLHttpRequest对象 |
onError |
Function | 否 | 加载失败回调,参数为错误对象 |
2.3 批量加载纹理(TextureLoader+Promise)
// 封装批量加载函数
async function loadTextures(urls) {
const loader = new THREE.TextureLoader();
const textures = [];
for (const url of urls) {
const texture = await new Promise((resolve, reject) => {
loader.load(url, resolve, null, reject);
});
texture.colorSpace = THREE.SRGBColorSpace;
textures.push(texture);
}
return textures;
}
// 使用:加载多张纹理
const urls = ['./texture1.jpg', './texture2.jpg'];
loadTextures(urls).then(textures => {
console.log('所有纹理加载完成', textures);
});
2.4 跨域问题解决
加载本地图片或跨域CDN图片时,若出现 THREE.WebGLRenderer: Texture has no image data 错误:
- 本地开发:启动HTTP服务(如VSCode的Live Server),不要直接打开HTML文件;
- CDN/服务器:配置图片服务器的CORS跨域头(
Access-Control-Allow-Origin: *); - 临时方案:将图片转为Base64格式嵌入代码(适合小图片)。
二、UV坐标核心解析(纹理映射的关键)
UV坐标是「2D纹理」和「3D几何体」的桥梁,决定了纹理图片的哪部分会贴在几何体的哪个面上。
1. UV坐标基础规则
| UV坐标 | 对应图片位置 | 说明 |
|---|---|---|
| (0, 0) | 图片左下角 | 纹理原点 |
| (1, 0) | 图片右下角 | U轴(横向)最大值 |
| (0, 1) | 图片左上角 | V轴(纵向)最大值 |
| (1, 1) | 图片右上角 | UV坐标最大值 |
| (0.5, 0.5) | 图片中心 | UV中点 |
2. 自定义UV坐标(BufferGeometry)
预设几何体(BoxGeometry/SphereGeometry)已内置UV坐标,若使用自定义 BufferGeometry,需手动定义UV属性:
2.1 完整映射(纹理全部显示)
// 步骤1:创建自定义几何体(4个顶点的矩形)
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
-0.5, -0.5, 0, // 顶点0
0.5, -0.5, 0, // 顶点1
0.5, 0.5, 0, // 顶点2
-0.5, 0.5, 0 // 顶点3
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// 步骤2:定义UV坐标(完整映射,4个顶点对应图片4个角)
const uvs = new Float32Array([
0, 0, // 顶点0 → 图片左下角
1, 0, // 顶点1 → 图片右下角
1, 1, // 顶点2 → 图片右上角
0, 1 // 顶点3 → 图片左上角
]);
// 绑定UV属性:itemSize=2(每2个值为一组UV坐标)
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
2.2 局部映射(仅显示纹理1/4区域)
// UV坐标范围设为0~0.5,仅映射图片左下角1/4区域
const uvs = new Float32Array([
0, 0, // 顶点0 → 图片(0,0)
0.5, 0, // 顶点1 → 图片(0.5,0)
0.5, 0.5, // 顶点2 → 图片(0.5,0.5)
0, 0.5 // 顶点3 → 图片(0,0.5)
]);
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
2.3 圆形几何体映射(CircleGeometry)
CircleGeometry 内置了适配圆形的UV坐标,无需自定义,直接绑定纹理即可:
// 创建圆形几何体(半径2,分段数100,越高分段越平滑)
const geometry = new THREE.CircleGeometry(2, 100);
// 加载纹理
const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('./gravelly_sand_diff_1k.jpg');
texture.colorSpace = THREE.SRGBColorSpace;
// 创建材质(双面渲染,避免背面不可见)
const material = new THREE.MeshBasicMaterial({
map: texture, // 绑定纹理
side: THREE.DoubleSide
});
// 创建网格对象
const circleMesh = new THREE.Mesh(geometry, material);
scene.add(circleMesh);
3. 预设几何体UV特点
| 几何体 | UV坐标特点 | 适用场景 |
|---|---|---|
| BoxGeometry | 每个面独立UV,纹理会贴到6个面上 | 立方体、盒子 |
| SphereGeometry | UV按经纬度分布,纹理包裹球体 | 星球、球体模型 |
| PlaneGeometry | 单平面UV,完整映射纹理 | 地面、墙面 |
| CircleGeometry | 圆形UV,纹理适配圆形 | 圆形地面、雷达图 |
三、纹理对象核心属性(参数详解+用法)
纹理对象(Texture)的核心属性决定了纹理的显示方式(重复、偏移、旋转等),是实现瓷砖阵列、UV动画的关键,以下是高频使用的属性:
1. 重复模式:wrapS / wrapT
控制纹理在U轴(横向)/V轴(纵向)超出0~1范围时的显示模式,必须配合 repeat 属性使用。
| 属性值 | 说明 | 示例 |
|---|---|---|
| THREE.ClampToEdgeWrapping(默认) | 超出范围时,拉伸纹理最后一行/列像素 | 纹理只显示一次,边缘拉伸 |
| THREE.RepeatWrapping | 超出范围时,重复显示纹理 | 实现瓷砖、地板阵列效果 |
| THREE.MirroredRepeatWrapping | 超出范围时,镜像重复显示纹理 | 无缝拼接的对称纹理 |
用法示例(地面瓷砖阵列)
const geometry = new THREE.PlaneGeometry(10, 10); // 10x10的地面
const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('./cizhuang.jpg');
texture.colorSpace = THREE.SRGBColorSpace;
// 1. 设置重复模式(U/V轴都重复)
texture.wrapS = THREE.RepeatWrapping; // U轴(横向)
texture.wrapT = THREE.RepeatWrapping; // V轴(纵向)
// 2. 设置重复数量(U轴10次,V轴10次)
texture.repeat.set(10, 10); // 格式:repeat.set(U重复数, V重复数)
// 3. 创建材质并绑定纹理
const material = new THREE.MeshLambertMaterial({ map: texture });
const groundMesh = new THREE.Mesh(geometry, material);
groundMesh.rotation.x = -Math.PI / 2; // 旋转为地面
scene.add(groundMesh);
2. 重复数量:repeat
- 类型:
THREE.Vector2(包含x/y属性,对应U/V轴); - 作用:设置纹理在U/V轴的重复次数,值越大,纹理显示越多块;
- 用法:
texture.repeat.x = 10; // U轴重复10次 texture.repeat.y = 10; // V轴重复10次 // 或批量设置 texture.repeat.set(10, 10);
3. 偏移:offset
- 类型:
THREE.Vector2(x=U轴偏移,y=V轴偏移); - 范围:0~1(偏移1=整个纹理宽度/高度);
- 作用:控制纹理在几何体上的偏移位置,常用于UV动画;
- 用法:
texture.offset.x = 0.5; // U轴向右偏移50% texture.offset.y = 0.5; // V轴向上偏移50% // 或批量设置 texture.offset.set(0.5, 0.5);
4. 旋转:rotation
- 类型:Number(弧度);
- 作用:纹理绕中心点旋转,单位为弧度;
- 配合属性:
center(设置旋转中心,默认(0.5,0.5)即纹理中心); - 用法:
texture.rotation = Math.PI / 4; // 旋转45° texture.center.set(0.5, 0.5); // 绕纹理中心旋转(默认值) // 绕图片左下角旋转 texture.center.set(0, 0);
5. 纹理过滤:magFilter / minFilter
控制纹理在「放大/缩小」时的显示质量,解决纹理模糊/锯齿问题:
| 属性 | 作用 | 推荐值 |
|---|---|---|
| magFilter | 纹理放大时的过滤方式 | THREE.LinearFilter(线性过滤,平滑) |
| minFilter | 纹理缩小时的过滤方式 | THREE.LinearMipmapLinearFilter(Mipmap线性过滤,最清晰) |
用法:
// 提升纹理显示质量(解决模糊)
texture.magFilter = THREE.LinearFilter;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.generateMipmaps = true; // 生成Mipmap(minFilter生效必备)
6. 各向异性过滤:anisotropy
- 类型:Number;
- 作用:提升纹理在倾斜视角下的清晰度(如地面纹理斜看时不模糊);
- 用法:
// 获取渲染器支持的最大各向异性值 const maxAnisotropy = renderer.capabilities.getMaxAnisotropy(); texture.anisotropy = maxAnisotropy; // 设置为最大值,效果最佳
四、纹理高级应用场景(完整用法+示例)
1. UV动画(纹理滚动)
通过动态修改 texture.offset 实现纹理滚动(如流水、火焰、移动的地面):
// 加载纹理
const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('./water.jpg');
texture.colorSpace = THREE.SRGBColorSpace;
// 开启重复模式(动画更自然)
texture.wrapS = THREE.RepeatWrapping;
texture.repeat.x = 5; // U轴重复5次
// 动画循环
function animate() {
requestAnimationFrame(animate);
// U轴偏移量递增,实现纹理向右滚动
texture.offset.x += 0.01;
// 可选:V轴偏移,实现斜向滚动
// texture.offset.y += 0.005;
controls.update();
renderer.render(scene, camera);
}
animate();
2. 阵列+UV动画组合(瓷砖地面滚动)
// 加载瓷砖纹理
const texture = texLoader.load('./cizhuang.jpg');
texture.colorSpace = THREE.SRGBColorSpace;
// 1. 设置阵列模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(10, 10); // 10x10瓷砖
// 2. 动画循环(UV滚动)
function animate() {
requestAnimationFrame(animate);
texture.offset.x += 0.005; // 缓慢向右滚动
texture.offset.y += 0.002; // 缓慢向上滚动
controls.update();
renderer.render(scene, camera);
}
animate();
3. 多纹理叠加(基础色+法线+粗糙度)
PBR材质(MeshStandardMaterial)支持多纹理叠加,实现更真实的质感:
// 加载多组纹理
const texLoader = new THREE.TextureLoader();
const colorMap = texLoader.load('./wood_color.jpg'); // 基础色纹理
const normalMap = texLoader.load('./wood_normal.jpg'); // 法线纹理(凹凸感)
const roughnessMap = texLoader.load('./wood_roughness.jpg'); // 粗糙度纹理
// 设置颜色空间(仅基础色纹理需要)
colorMap.colorSpace = THREE.SRGBColorSpace;
// 创建PBR材质,绑定多纹理
const material = new THREE.MeshStandardMaterial({
map: colorMap, // 基础色
normalMap: normalMap, // 法线(凹凸)
roughnessMap: roughnessMap, // 粗糙度
roughness: 1.0, // 全局粗糙度(与纹理叠加)
metalness: 0.1 // 金属度
});
五、完整实战示例(纹理加载+UV自定义+阵列+动画)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Three.js 纹理贴图完整示例</title>
<style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
<script>
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r132/build/three.module.js';
import { OrbitControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/controls/OrbitControls.js";
// 1. 创建三大核心
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(3, 3, 5);
// 2. 加载纹理(示例使用CDN纹理,避免本地路径问题)
const texLoader = new THREE.TextureLoader();
// 瓷砖纹理(CDN示例)
const texture = texLoader.load('https://threejs.org/examples/textures/tiles/tiles_diff.jpg', () => {
renderer.render(scene, camera); // 加载完成后渲染
});
// 关键:设置颜色空间,避免偏色
texture.colorSpace = THREE.SRGBColorSpace;
// 3. 纹理配置(阵列+过滤优化)
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(8, 8); // 8x8瓷砖阵列
// 提升纹理质量
texture.magFilter = THREE.LinearFilter;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.generateMipmaps = true;
// 开启各向异性过滤
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
// 4. 创建地面几何体(PlaneGeometry)
const groundGeo = new THREE.PlaneGeometry(10, 10);
const groundMat = new THREE.MeshStandardMaterial({
map: texture,
side: THREE.DoubleSide
});
const groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI / 2; // 旋转为地面
scene.add(groundMesh);
// 5. 创建立方体(自定义UV示例)
const cubeGeo = new THREE.BoxGeometry(2, 2, 2);
// 自定义立方体UV(仅修改正面,其他面默认)
const uvs = new Float32Array([
0, 0, 1, 0, 1, 1, 0, 1, // 正面UV(完整映射)
0, 0, 0.5, 0, 0.5, 0.5, 0, 0.5, // 右侧面UV(1/4映射)
// 其他面UV省略,使用默认值
]);
cubeGeo.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
const cubeMat = new THREE.MeshStandardMaterial({ map: texture });
const cubeMesh = new THREE.Mesh(cubeGeo, cubeMat);
cubeMesh.position.y = 1; // 立方体放在地面上
scene.add(cubeMesh);
// 6. 添加光源(PBR材质需要光源)
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 8, 5);
scene.add(ambientLight, dirLight);
// 7. 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 8. UV动画循环
function animate() {
requestAnimationFrame(animate);
// 纹理缓慢滚动(U轴+V轴)
texture.offset.x += 0.002;
texture.offset.y += 0.001;
// 立方体旋转
cubeMesh.rotation.x += 0.01;
cubeMesh.rotation.y += 0.01;
controls.update();
renderer.render(scene, camera);
}
animate();
// 9. 窗口适配
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
示例效果
- 场景包含10x10的瓷砖地面,纹理8x8阵列显示,且缓慢滚动;
- 地面上有一个立方体,正面完整映射纹理,右侧面仅显示纹理1/4区域;
- 支持鼠标旋转/缩放视角,立方体自动旋转,纹理滚动动画流畅;
- 纹理显示清晰,无偏色、无模糊问题。
六、纹理优化与避坑指南
1. 性能优化技巧
- 图片尺寸优化:纹理图片尺寸建议为2的幂次方(如256x256、512x512、1024x1024),GPU处理更快;
- 复用纹理对象:多个模型使用同一张纹理时,复用同一个Texture对象,避免重复加载;
-
关闭不必要的功能:静态纹理关闭
generateMipmaps,减少内存占用; - 压缩纹理:使用basis Universal等压缩纹理格式,减小图片体积,提升加载速度。
2. 常见坑点与解决
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 纹理偏色 | 未设置colorSpace | 添加texture.colorSpace = THREE.SRGBColorSpace
|
| 纹理不显示 | 跨域问题/图片路径错误 | 启动HTTP服务/检查路径/配置CORS |
| 纹理模糊 | 过滤方式未优化 | 设置magFilter=LinearFilter+minFilter=LinearMipmapLinearFilter
|
| 阵列不生效 | 未设置wrapS/wrapT | 开启texture.wrapS/wrapT = THREE.RepeatWrapping
|
| UV动画卡顿 | 偏移量递增过快 | 减小offset递增步长(如0.001~0.01) |
核心总结
-
核心流程:
TextureLoader加载图片 → 设置colorSpace → 配置纹理属性(wrap/repeat/offset) → 绑定到材质.map → 几何体UV映射; - UV坐标:0~1范围,是纹理与几何体的桥梁,自定义几何体需手动设置UV属性;
-
关键属性:
-
wrapS/wrapT:控制重复模式,实现瓷砖阵列; -
repeat:设置重复数量; -
offset:实现UV动画; -
colorSpace:避免纹理偏色(r152+必加);
-
- 优化原则:图片尺寸为2的幂次方,开启各向异性过滤,复用纹理对象,关闭不必要的Mipmap。