
Three.js 纹理给模型贴上图片的完整指南原文出处Three.js Manual – Textures本文基于官方手册 Textures 章节整理从贴一张图讲到内存占用、压缩格式、过滤mip和重复/偏移/旋转一篇讲透。写在前面前面我们给场景打了光模型有明暗了。但真实的 3D 世界哪有纯色方块砖墙、木地板、花花绿绿的贴纸靠的都是纹理Texture——简单说就是贴图。纹理是 three.js 里一个很大的话题。这篇我们从最基础的贴一张图开始一路讲到新手最容易忽略的内存黑洞、不同格式怎么选、以及让纹理更清晰的mip 过滤和重复/旋转技巧。一、Hello Texture贴一张图上去纹理通常就是一张图片Photoshop、GIMP 里画的都行。用法极简用TextureLoader把图片读进来赋给材质的map属性代替原来的color就行。constloadernewTHREE.TextureLoader();consttextureloader.load(resources/images/wall.jpg);texture.colorSpaceTHREE.SRGBColorSpace;// 颜色空间别忘了设constmaterialnewTHREE.MeshBasicMaterial({map:texture,// 之前是 color: 0xFF8844});注意这里用的是MeshBasicMaterial不受光影响所以不用灯光也能看见。另外colorSpace SRGBColorSpace这行别漏否则颜色会偏暗偏灰。二、一个立方体六面贴不同图想给立方体每个面贴不同的图简单——传一个材质数组六个面各对应一个。functionloadColorTexture(path){consttextureloader.load(path);texture.colorSpaceTHREE.SRGBColorSpace;returntexture;}constmaterials[newTHREE.MeshBasicMaterial({map:loadColorTexture(resources/images/flower-1.jpg)}),newTHREE.MeshBasicMaterial({map:loadColorTexture(resources/images/flower-2.jpg)}),// ... 3~6 同理];constcubenewTHREE.Mesh(geometry,materials);细节坑不是所有几何体都支持多材质。BoxGeometry正好 6 个面用 6 个材质ConeGeometry是 2 个底锥CylinderGeometry是 3 个底/顶/侧。想在一块几何体上贴多个图正规做法是用纹理图集Texture Atlas——把多张图拼进一张大图靠顶点纹理坐标去取各块。三、加载纹理的几种方式1. 最简单直接 loadconsttextureloader.load(resources/images/flower-1.jpg);重点这样加载的纹理一开始是透明的等图片异步下载完 three.js 才自动更新上去。好处是页面秒开、不用等。2. 等单张加载完再做事load接受一个回调图片加载完成后才触发。适合加载完才能正确初始化的场景loader.load(resources/images/wall.jpg,(texture){texture.colorSpaceTHREE.SRGBColorSpace;constmaterialnewTHREE.MeshBasicMaterial({map:texture});constcubenewTHREE.Mesh(geometry,material);scene.add(cube);});3. 等所有图都加载完LoadingManager多张图想全部就绪再显示用LoadingManager包住 loader设onLoad回调constloadManagernewTHREE.LoadingManager();constloadernewTHREE.TextureLoader(loadManager);constmaterials[/* ... 6 张图 ... */];loadManager.onLoad(){constcubenewTHREE.Mesh(geometry,materials);scene.add(cube);};还能用onProgress做一个进度条loadManager.onProgress(url,itemsLoaded,itemsTotal){constprogressitemsLoaded/itemsTotal;progressBarElem.style.transformscaleX(${progress});};4. 跨域加载CORS想用别的服务器上的图片那台服务器必须返回正确的 CORS 头否则 three.js 直接报错用不了。imgur、flickr、github 都允许大多数普通网站不允许。四、内存黑洞纹理吃内存比你想的大得多这是全文最该记住的一点。纹理往往是 three.js 应用里最吃内存的部分而且它是按像素原始尺寸算的跟文件大小无关显存占用 ≈ 宽 × 高 × 4 × 1.33 字节举个手册里的例子一张压缩得很好的木纹图文件只有 157KB但原图是 3024×3761 像素。一算3024 × 3761 × 4 × 1.33 ≈ 60,505,764 字节 ≈ 60 MB几张这种图就能把你内存吃爆。原因是GPU 一般要求纹理数据是未压缩的才能用。所以结论很直白——纹理要尺寸小不只是文件小。文件小 下载快尺寸小 占内存少。在满足画质的前提下越小越好。五、JPG 还是 PNG跟普通网页一样JPG 有损压缩、体积小、下载快但不支持透明PNG 无损、支持透明、适合法线贴图这类非图片数据。关键提醒在 WebGL 里 JPG 并不会比 PNG 省内存——回到第四条的公式内存只跟像素尺寸有关。六、过滤与 Mip让小图不闪烁把一张 16×16 的小图贴到一个被缩得很小的立方体上GPU 怎么知道那 1~2 个像素该显示什么颜色这就是过滤filtering。GPU 的解法是预生成mipmap把原图逐级缩小一半、混色一直缩到 1×1。物体越小就用越小的 mip 来算颜色避免远处闪烁。放大时magFilterNearestFilter取最近单个像素像 Minecraft 的像素风或LinearFilter取 4 个像素混色平滑。缩小时minFilter有 6 种最常用的是LinearMipmapLinearFilter在两个 mip 各取 4 像素共 8 个混色质量最高。texture.magFilterTHREE.NearestFilter;// 像素风texture.minFilterTHREE.LinearMipmapLinearFilter;// 高质量质量越高越慢——读 8 个像素混色肯定比读 1 个慢。材质后面可能一次叠加 4~5 张纹理移动端尤其要权衡。七、重复、偏移、旋转、包裹纹理还能玩贴瓷砖效果靠这几个属性someTexture.wrapSTHREE.RepeatWrapping;// 水平方向重复someTexture.wrapTTHREE.RepeatWrapping;// 垂直方向重复someTexture.repeat.set(4,2);// 横贴 4 次、竖贴 2 次someTexture.offset.set(0.5,0.25);// 偏移单位是整张图1整张someTexture.center.set(0.5,0.5);// 旋转中心默认左上角(0,0)someTexture.rotationTHREE.MathUtils.degToRad(45);// 旋转 45°wrapS/wrapT可设ClampToEdgeWrapping边缘像素拉伸、RepeatWrapping平铺、MirroredRepeatWrapping镜像平铺。大坑如果你改了wrapS/wrapT必须手动设texture.needsUpdate truethree.js 才会应用。其它属性repeat/offset/rotation是自动生效的。总结话题关键点贴图TextureLoader.load→ 赋给material.map多面材质数组注意几何体支持几个面加载简单 load / 回调单等 / LoadingManager 多等 / 跨域要 CORS内存尺寸×4×1.33别只看文件大小格式JPG 快无透明PNG 有透明内存一样过滤magFilter 放大、minFilter 缩小含 mip变换wrap/repeat/offset/rotation改 wrap 要 needsUpdate延伸阅读Texture – three.js docsTextureLoaderLoadingManager接上一篇 Lights灯光