纹理是什么
纹理本质上是贴到几何体表面的图片,但它不只是“给模型上颜色”。同一组纹理可以控制颜色、透明度、凹凸、光照细节、金属感、粗糙度等多个视觉属性。课程里先用门的贴图举例,说明纹理可以从不同角度影响模型外观。
常见纹理类型
| 纹理类型 | 作用 | 记忆点 |
|---|---|---|
| Color / Albedo | 基础颜色贴图 | 把图片像素直接映射到模型表面 |
| Alpha | 透明度贴图 | 灰度图:白色可见,黑色不可见 |
| Height | 高度贴图 | 通过移动顶点制造起伏;需要足够细分才明显 |
| Normal | 法线贴图 | 不移动顶点,但改变光照计算,让表面看起来有细节 |
| Ambient Occlusion | 环境遮蔽贴图 | 伪造缝隙、凹陷处的阴影,增强对比 |
| Metalness | 金属度贴图 | 白色偏金属,黑色偏非金属 |
| Roughness | 粗糙度贴图 | 白色粗糙、反光散;黑色光滑、反光强 |
| PBR | 基于物理的渲染 | 常由金属度、粗糙度、法线等贴图共同实现更真实的材质 |
其中 Normal Map 很重要:它不会增加模型面数,却能模拟细节,性能比真的增加几何体细分更好。
加载纹理的几种方式
方式一:原生 JavaScript 加载图片
流程是:创建 Image → 设置 src → 图片加载完成后创建或更新 THREE.Texture。
const image = new Image()
const texture = new THREE.Texture(image)
image.addEventListener('load', () => {
texture.needsUpdate = true
})
image.src = '/textures/door/color.jpg'
这里的关键是:图片加载是异步的,所以图片加载完成后要设置 texture.needsUpdate = true,通知 Three.js 把纹理上传/更新到 GPU。课程中也强调,WebGL 需要把图片转换成 GPU 可用的纹理格式。
方式二:使用 TextureLoader
这是实际开发中更常用的写法:
const textureLoader = new THREE.TextureLoader()
const colorTexture = textureLoader.load('/textures/door/color.jpg')
const material = new THREE.MeshBasicMaterial({
map: colorTexture
})
TextureLoader.load() 会返回一个 Texture,并在图片加载完成后自动更新。Three.js 官方文档也说明,TextureLoader 用于加载纹理,内部会通过 ImageLoader 加载图片。
方式三:使用 LoadingManager
当你有多张贴图需要加载,比如 color、alpha、normal、roughness、metalness 等,可以用 LoadingManager 统一监听加载状态:
const loadingManager = new THREE.LoadingManager()
loadingManager.onStart = () => {
console.log('loading started')
}
loadingManager.onLoad = () => {
console.log('loading finished')
}
loadingManager.onProgress = () => {
console.log('loading progressing')
}
loadingManager.onError = () => {
console.log('loading error')
}
const textureLoader = new THREE.TextureLoader(loadingManager)
const colorTexture = textureLoader.load('/textures/door/color.jpg')
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const normalTexture = textureLoader.load('/textures/door/normal.jpg')
const roughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
适合做“加载进度条”“所有资源加载完再进入场景”等功能。课程页也明确提到,LoadingManager 适合在多个图片加载时统一处理开始、完成、进度和错误事件。
颜色空间:SRGBColorSpace
如果颜色贴图看起来发灰,通常是颜色空间没设置对。课程更新说明里提到,用作 map 或 matcap 的纹理通常是 sRGB 编码,需要设置:
colorTexture.colorSpace = THREE.SRGBColorSpace
Three.js 官方颜色管理文档也说明,像 .map、.emissiveMap 这种包含颜色信息的 PNG/JPEG 纹理,应标注为 SRGBColorSpace;而 normal、roughness 这类非颜色数据纹理通常保持 NoColorSpace。
记法:
// 颜色贴图:需要
colorTexture.colorSpace = THREE.SRGBColorSpace
// 非颜色数据贴图:通常不需要
normalTexture.colorSpace // 默认 NoColorSpace 即可
roughnessTexture.colorSpace // 默认 NoColorSpace 即可
metalnessTexture.colorSpace // 默认 NoColorSpace 即可
纹理映射到材质
最基本的用法是把纹理放到材质的 map 属性里:
const material = new THREE.MeshBasicMaterial({
map: colorTexture
})
map 是颜色贴图入口。后面学更真实的材质时,会用到:
const material = new THREE.MeshStandardMaterial({
map: colorTexture,
alphaMap: alphaTexture,
aoMap: ambientOcclusionTexture
})
注意:MeshBasicMaterial 不受光照影响,适合先看贴图效果;如果要看 normal、metalness、roughness 这些跟光照相关的效果,一般要用 MeshStandardMaterial 或类似 PBR 材质。
纹理重复、偏移、旋转
Three.js 的纹理对象有这些常用属性:
texture.repeat.set(2, 3)
texture.offset.set(0.5, 0.25)
texture.rotation = Math.PI * 0.25
texture.center.set(0.5, 0.5)
含义:
| 属性 | 作用 |
|---|---|
repeat |
控制纹理重复次数 |
offset |
控制纹理偏移 |
rotation |
控制纹理旋转,单位是弧度 |
center |
控制旋转中心,(0.5, 0.5)是纹理中心 |
Three.js 文档说明,repeat 大于 1 时,需要配合 wrapS / wrapT 设置成 RepeatWrapping 或 MirroredRepeatWrapping,否则不会真正平铺。
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 2)
常见包裹模式:
| 模式 | 效果 |
|---|---|
ClampToEdgeWrapping |
默认模式,边缘像素被拉伸 |
RepeatWrapping |
正常重复 |
MirroredRepeatWrapping |
镜像重复 |
纹理过滤:放大与缩小
纹理显示时会遇到两个问题:
一是贴图被放大,比如低分辨率图片贴到很大的面上;二是贴图被缩小,比如高分辨率贴图在远处只占几个像素。
对应两个属性:
texture.magFilter = THREE.NearestFilter
texture.minFilter = THREE.LinearMipmapLinearFilter
| 属性 | 什么时候用 | 常见值 |
|---|---|---|
magFilter |
纹理被放大时 | NearestFilter/LinearFilter |
minFilter |
纹理被缩小时 | 可使用 mipmap 相关过滤 |
NearestFilter 会产生像素风、颗粒感;LinearFilter 会更平滑。Three.js 文档中也说明,magFilter 控制纹理放大采样,minFilter 控制纹理缩小采样,默认 magFilter 是 LinearFilter,默认 minFilter 是 LinearMipmapLinearFilter。
Mipmaps:远处纹理优化
Mipmaps 可以理解为:Three.js / GPU 为同一张纹理准备一系列逐级缩小的版本。物体远离摄像机时,不再硬采样原始大图,而是使用更合适的小图版本。
优点:
- 减少远处纹理闪烁。
- 提高远处显示质量。
- 在很多情况下提升采样效率。
代价:
- 会额外占用显存。
- 不一定适合像素风纹理。
Three.js 文档中提到,纹理默认会生成 mipmaps;如果你手动创建 mipmaps,可以把 generateMipmaps 设为 false。
纹理内存与性能
纹理往往是 Three.js 项目中最吃显存的资源之一。Three.js manual 给出的经验公式是:
纹理显存 ≈ width × height × 4 × 1.33 bytes
这里的重点是:JPG / PNG 文件在硬盘上很小,不代表进 GPU 后也小。进入 GPU 后通常会按像素展开,占用大小主要取决于图片分辨率。
优化建议:
- 不要盲目使用超大尺寸贴图。
- 能用 1024 就不要用 4096。
- JPG 适合无透明通道的颜色贴图。
- PNG 适合需要透明通道或清晰边缘的贴图。
- 多张贴图时考虑纹理图集 Texture Atlas。
- 生产项目可考虑压缩纹理格式。
实战记忆版流程
做一个带门贴图的材质,可以这样记:
const loadingManager = new THREE.LoadingManager()
const textureLoader = new THREE.TextureLoader(loadingManager)
const colorTexture = textureLoader.load('/textures/door/color.jpg')
colorTexture.colorSpace = THREE.SRGBColorSpace
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const normalTexture = textureLoader.load('/textures/door/normal.jpg')
const roughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
const metalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const material = new THREE.MeshStandardMaterial({
map: colorTexture,
alphaMap: alphaTexture,
aoMap: ambientOcclusionTexture
transparent: true
})
推荐阅读文章
PBR的介绍
最后总结
这节纹理课的核心不是“怎么贴一张图”,而是理解:纹理是一组控制材质外观的数据。颜色贴图控制表面颜色,Alpha 控制透明,Height / Normal 控制凹凸细节,AO 控制缝隙阴影,Metalness / Roughness 共同服务于 PBR 真实感渲染。加载时优先用 TextureLoader,多资源用 LoadingManager,颜色贴图要设置 THREE.SRGBColorSpace,性能上要关注尺寸、mipmap、过滤方式和显存占用。
评论区