学习Three.js--纹理贴图(Texture)

0 阅读11分钟

学习Three.js--纹理贴图(Texture)

前置核心说明

纹理贴图是 Three.js 中让3D模型呈现真实外观的核心手段,本质是将2D图片(纹理)「贴」到3D几何体表面,替代单一的纯色材质,实现照片级的视觉效果(如墙面纹理、地面瓷砖、金属质感、木纹等)。

核心规则

  1. 核心流程加载图片 → 创建纹理对象(Texture) → 绑定到材质.map属性 → 几何体通过UV坐标映射纹理
  2. 颜色空间必设:加载纹理后必须设置 texture.colorSpace = THREE.SRGBColorSpace,否则图片会出现偏色(Three.js r152+ 版本新增,适配真实色彩);
  3. UV坐标是桥梁:UV坐标(2D)关联纹理图片和几何体顶点(3D),是纹理「贴在哪个位置」的核心控制手段;
  4. 材质适配:所有 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方法)
参数名类型必填说明
urlString图片路径(支持本地相对路径、CDN链接、Base64)
onLoadFunction加载完成回调,参数为生成的Texture对象
onProgressFunction加载进度回调,参数为XMLHttpRequest对象
onErrorFunction加载失败回调,参数为错误对象
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 错误:

  1. 本地开发:启动HTTP服务(如VSCode的Live Server),不要直接打开HTML文件;
  2. CDN/服务器:配置图片服务器的CORS跨域头(Access-Control-Allow-Origin: *);
  3. 临时方案:将图片转为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个面上立方体、盒子
SphereGeometryUV按经纬度分布,纹理包裹球体星球、球体模型
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>

示例效果

e628d623-98ee-42f3-85e8-aa2eb050cd5d.png

  1. 场景包含10x10的瓷砖地面,纹理8x8阵列显示,且缓慢滚动;
  2. 地面上有一个立方体,正面完整映射纹理,右侧面仅显示纹理1/4区域;
  3. 支持鼠标旋转/缩放视角,立方体自动旋转,纹理滚动动画流畅;
  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)

核心总结

  1. 核心流程TextureLoader加载图片 → 设置colorSpace → 配置纹理属性(wrap/repeat/offset) → 绑定到材质.map → 几何体UV映射
  2. UV坐标:0~1范围,是纹理与几何体的桥梁,自定义几何体需手动设置UV属性;
  3. 关键属性
    • wrapS/wrapT:控制重复模式,实现瓷砖阵列;
    • repeat:设置重复数量;
    • offset:实现UV动画;
    • colorSpace:避免纹理偏色(r152+必加);
  4. 优化原则:图片尺寸为2的幂次方,开启各向异性过滤,复用纹理对象,关闭不必要的Mipmap。