Skip to content

Three.js - 纹理

纹理是将图像映射到3D模型表面,以增加颜色、细节和真实感。在现代3D图形中,特别是基于物理渲染(PBR)技术中,纹理不仅仅是添加颜色——它们还定义了粗糙度、金属度和表面高度等属性。本章将介绍如何加载和应用这些强大的贴图。

纹理的基础是加载图像文件。Three.js为此提供了 TextureLoader(纹理加载器)。加载的纹理可以应用于材质的 map 属性,该属性对应于基础颜色。

import * as THREE from 'three';
// 1. 创建一个 TextureLoader 实例
const textureLoader = new THREE.TextureLoader();
// 2. 加载图像文件
const colorTexture = textureLoader.load('/textures/door/color.jpg');
// 3. 将其应用于材质
const material = new THREE.MeshStandardMaterial({ map: colorTexture });

加载是异步的。纹理可能不会立即显示。Three.js 在后台处理此事,但对于更复杂的场景,你可以使用加载器的回调函数来了解何时加载完成。

你可以控制纹理的重复方式(平铺)以及在放大或缩小时的外观。

  • 纹理包裹(平铺):使用 texture.wrapS(水平方向)和 texture.wrapT(垂直方向)配合 THREE.RepeatWrapping,使纹理在表面上平铺。然后你可以通过 texture.repeat.set(x, y) 设置重复次数。
  • 过滤:当纹理被拉伸到大面积(magFilter)或缩小到小面积(minFilter)时,过滤决定了像素的渲染方式。THREE.LinearFilter(线性过滤)提供平滑的外观,而 THREE.NearestFilter(最近点过滤)则呈现锐利、像素化的外观。为了在 minFilter 上获得最佳质量,请使用 Mipmap 选项之一,例如 THREE.LinearMipmapLinearFilter。
// 使纹理在水平方向重复4次,垂直方向重复2次
colorTexture.wrapS = THREE.RepeatWrapping;
colorTexture.wrapT = THREE.RepeatWrapping;
colorTexture.repeat.set(4, 2);
// 用于像素艺术或锐利纹理
colorTexture.magFilter = THREE.NearestFilter;
// 为 PBR 材质设置颜色空间
colorTexture.colorSpace = THREE.SRGBColorSpace;

重要提示:对于 MeshStandardMaterial 等 PBR 材质,包含颜色信息(如 map)的纹理,务必设置正确的 colorSpace(颜色空间)。THREE.SRGBColorSpace 是大多数颜色纹理的正确选择。

MeshStandardMaterial 通过使用多种类型的纹理贴图来模拟逼真的光照。这些通常是灰度图或专用图像,用于控制表面的特定物理属性。

  • 法线贴图(normalMap):在不增加多边形数量的情况下,添加凹凸、划痕和纹理等精细表面细节。它通过操纵光的反射方式来模拟细节。这比凹凸贴图更高级。
  • 环境光遮蔽(AO)贴图(aoMap):一种灰度贴图,用于在光线被阻挡的缝隙和角落处添加伪造阴影。它使物体看起来更具立体感,不那么扁平。这需要第二组UV坐标(uv2)。
  • 粗糙度贴图(roughnessMap):一种灰度贴图,白色表示非常粗糙的表面(漫反射,如粉笔),黑色表示非常光滑的表面(锐利反射,如镜子)。
  • 金属度贴图(metalnessMap):一种灰度贴图,白色表示表面100%是金属,黑色表示非金属(电介质)。
  • 置换贴图(displacementMap):与仅模拟细节的法线贴图不同,置换贴图实际移动了几何体的顶点。这可以创建真实的几何细节,但计算成本高昂,并且需要高度细分的网格。

此示例演示了如何将一套完整的PBR纹理应用于一个球体。它假设你的公共目录中有一个 /textures/ 文件夹,其中包含了一组纹理(颜色、法线、粗糙度等)。你可以在 AmbientCG 等网站上找到免费的PBR纹理集。

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// 基础设置(场景、相机、渲染器、控制器,与前面的示例相同)
// ...
/**
* 纹理
*/
const textureLoader = new THREE.TextureLoader();
// 加载所有必需的 PBR 贴图
const colorTexture = textureLoader.load('/textures/metal_plate/color.jpg');
const aoTexture = textureLoader.load('/textures/metal_plate/ambientOcclusion.jpg');
const heightTexture = textureLoader.load('/textures/metal_plate/height.png');
const normalTexture = textureLoader.load('/textures/metal_plate/normal.jpg');
const roughnessTexture = textureLoader.load('/textures/metal_plate/roughness.jpg');
const metalnessTexture = textureLoader.load('/textures/metal_plate/metalness.jpg');
// 为颜色纹理设置正确的颜色空间
colorTexture.colorSpace = THREE.SRGBColorSpace;
/**
* 对象
*/
// 使用高细节几何体来查看置换效果
const geometry = new THREE.SphereGeometry(1, 64, 64);
// 添加 aoMap 的 UV 属性。它与颜色贴图共享相同的 UV。
geometry.setAttribute('uv2', new THREE.BufferAttribute(geometry.attributes.uv.array, 2));
const material = new THREE.MeshStandardMaterial({
map: colorTexture,
aoMap: aoTexture,
aoMapIntensity: 1,
normalMap: normalTexture,
roughnessMap: roughnessTexture,
metalnessMap: metalnessTexture,
displacementMap: heightTexture,
displacementScale: 0.05 // 小心调整此值
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
/**
* 光源
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 20);
pointLight.position.set(2, 3, 4);
scene.add(pointLight);
// ... (动画循环和响应式处理,与前面的示例相同)

纹理通常是3D应用程序文件大小中最大的一部分。优化纹理对于实现快速加载时间至关重要。

  • 文件格式:对于颜色贴图和其他无需透明度的贴图,使用 .jpg 格式。仅当你需要 Alpha 通道时才使用 .png 格式。
  • 分辨率:使用在视觉上仍保持良好效果的最小分辨率。为了广泛兼容性和Mipmap效率,请保持尺寸为二的幂次(例如,512x512,1024x1024)。
  • 纹理压缩:对于生产环境,使用像 Basis Universal 这样的工具来创建超级压缩的 .basis 或 .ktx2 文件,这些文件可以被GPU高效解码,从而显著减少内存使用和加载时间。