
Three.js 这库我前前后后刷了不知道多少遍每次以为会了过俩月再看文档又能发现一堆之前没注意的新东西。你给的标题后面挂一串 1我姑且理解成“一遍一遍学、一坑一坑踩”的意思——这玩意儿确实值得用这种笨办法啃。这次我就围绕着 threejs 185 版本把新手最常卡的几个点一次性聊透怎么搭第一个场景、新版锯齿问题到底怎么治、中文文档和教程怎么用效率最高以及我踩过的那些坑。这篇东西适合刚接触 Three.js 的人也适合那些已经能跑 demo 但总觉得代码写得不得劲的朋友。目标是让你读完直接照着敲不用再花一周去翻零散的资料。1. 为什么我建议直接学 Three.js而不是先碰游戏引擎很多人会纠结既然要搞 3D是不是直接学 Unity、Unreal 更划算我的看法是如果你做的是网页应用、数据可视化、产品展示这类偏“交互页面”的东西Three.js 就是最合适的。它本质上是封装了 WebGL 的 JavaScript 库不依赖任何插件浏览器原生就能跑移动端兼容性也到了可以放心用的程度。你不需要学 C#不需要装几个 G 的编辑器一个浏览器、一个编辑器就能开始。1.1 上手门槛低但天花板很高Three.js 的核心逻辑只有几个概念场景Scene、相机Camera、渲染器Renderer、几何体Geometry、材质Material、灯光Light。这六个概念全搞明白你基本就能拼出 80% 的静态场景。而它真正的难度在后面的进阶部分——后处理、自定义着色器、性能优化、物理引擎集成、骨骼动画这些每一个都可以单独写一本书。它的学习曲线是典型的“缓坡起步、陡坡在后”。前一个星期你会觉得“就这也不难嘛”一个月后你会被各种细节按在地上摩擦。这种特性和很多工业级技术栈很像也恰恰说明它值得投入。1.2 学习路线怎么排才不劝退我见过太多人上来就搜“Three.js 游戏开发教程”结果跟着做到一半项目跑不起来心态直接炸。正确姿势是这样先把官网文档的“入门手册”通读一遍重点是创建一个场景这几个页面。然后照着 examples 页面里那些基础 demo 动手改从立方体换成球体从旋转改成平移把每行代码的作用搞清楚。之后再去看某个细分方向比如模型加载、动画、或者着色器。千万不要一上来就啃源码。Three.js 源码结构很清晰但那是给已经会用的人看的不是给新手的第一本教材。2. 第一个 3D 场景我建议你这样写网上随便搜都能找到一堆“10 分钟入门 Three.js”的代码但大部分代码都是直接抄来抄去里面的坑一点没提。我下面给的版本是 r185 下能稳定跑通的每一步我都会解释为什么这么写。2.1 场景、相机、渲染器三件套先跑通先上一个最小的完整示例import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 100 ); camera.position.set(4, 4, 8); camera.lookAt(0, 0, 0); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const controls new OrbitControls(camera, renderer.domElement); controls.enableDamping true; const geometry new THREE.BoxGeometry(1, 1, 1); const material new THREE.MeshStandardMaterial({ color: 0x3f7cff, roughness: 0.4, metalness: 0.2 }); const cube new THREE.Mesh(geometry, material); scene.add(cube); scene.add(new THREE.AmbientLight(0xffffff, 0.6)); const dirLight new THREE.DirectionalLight(0xffffff, 1.2); dirLight.position.set(3, 5, 4); scene.add(dirLight); function animate() { requestAnimationFrame(animate); cube.rotation.x 0.005; cube.rotation.y 0.008; controls.update(); renderer.render(scene, camera); } animate();注意几个细节。camera.lookAt这行很多教程会漏掉PerspectiveCamera 默认看向原点附近一旦你手动改了 position 而不调 lookAt场景可能就在画面之外。controls.enableDamping是让鼠标操作有惯性你如果觉得手感黏糊可以先不开但开了一开始就习惯它比较好因为很多交互 demo 都依赖它。MeshStandardMaterial是 PBR 材质表现力比最基础的MeshBasicMaterial强很多但必须配合灯光才能看到效果。新手最容易出现的问题就是“物体是黑的”八成是因为用了 StandardMaterial 却没加灯。2.2 动画循环里到底发生了什么requestAnimationFrame(animate)是浏览器提供的定时回调执行间隔跟着显示器刷新率走通常是 60 次每秒一些高刷屏能到 120。把旋转增量写在回调里物体就会持续转动。需要注意增量数值是“每帧”的不是“每秒”的同样的 0.00560Hz 下每秒转 0.3 弧度120Hz 下就是 0.6 弧度。想做得严谨应该用THREE.Clock把增量换算成“每秒速度乘以 deltaTime”否则高刷屏上物体转得飞快这种问题排查起来很容易懵。2.3 常用灯光的坑与经验灯光选型上AmbientLight负责兜底保证物体不被照得死黑。DirectionalLight负责给物体的受光面建立明暗关系。如果你只想快速看个效果HemisphereLight加DirectionalLight的组合比 AmbientLight 更自然因为天空和地面的光色不同物体边缘会有微妙的冷暖变化。我个人的习惯是先加 DirectionalLight亮度调到 1.0 以上不够再补 AmbientLight尽量不要一开始就堆五六盏灯性能开销大而且阴影一开就会出现各种接缝问题。3. threejs 185 版本锯齿问题亲测可用的几种方案这是最近被问烂的问题。r185 以后的版本尤其在高分屏上很多朋友发现物体边缘有狗啃一样的锯齿开antialias: true也不管用。先说结论这不是你的代码写错了是 WebGL2 普及之后浏览器的默认行为变了。3.1 先确认你看到的究竟是哪种“锯齿”我们要区分两种东西一种是几何边缘的锯齿就是斜线、弧线上一格一格的像素点这叫走样Aliasing。另一种是纹理上的闪烁远处地面或者细小网格在移动时出现波纹状闪动这叫摩尔纹或临时性走样。两者原因不同手段也不同。几何边缘的锯齿本质是屏幕像素是离散的一条斜线不可能被像素完美还原。抗锯齿算法的核心就是把这几个阶梯像素的颜色做混合让视觉上感觉平滑。开antialias: true是最简单的方式但在 WebGL2 下不一定有效因为很多浏览器在高 DPI 下并不会真的给默认画布开启多重采样MSAA。3.2 antialias 参数为什么时灵时不灵这里面有个很坑的点。renderer new THREE.WebGLRenderer({ antialias: true })只是请求一个带 MSAA 的 WebGL 上下文但浏览器是否响应这个请求取决于诸多因素显卡驱动、浏览器实现、以及你是否手动设置了renderer.setPixelRatio。如果你没特别注意设备像素比代码长这样renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight);在 DPR 为 2 的 MacBook 上实际渲染尺寸就是 CSS 尺寸的两倍。此时每个逻辑像素由四个物理像素组成锯齿感会弱很多但也不会完全消失尤其是在低 DPR 的 Windows 屏幕上效果就很差。所以如果你只是想快速改善先把这两行加上看看是否比之前平滑。如果还是不行再尝试下面的方案。3.3 用后处理抗锯齿彻底解决后处理抗锯齿是目前最稳的方案。核心思路是先用一个较大的分辨率把场景渲染到离屏缓冲区再用后处理通道把边缘柔化一遍。Three.js 官方示例里提供了 SMAAPass我个人实测下来效果比 FXAA 好边缘更干净代价是性能开销稍高。代码如下import { EffectComposer } from three/addons/postprocessing/EffectComposer.js; import { RenderPass } from three/addons/postprocessing/RenderPass.js; import { SMAAPass } from three/addons/postprocessing/SMAAPass.js; const composer new EffectComposer(renderer); composer.addPass(new RenderPass(scene, camera)); const smaaPass new SMAAPass( window.innerWidth * renderer.getPixelRatio(), window.innerHeight * renderer.getPixelRatio() ); composer.addPass(smaaPass); // 动画循环里改成 composer.render();注意SMAAPass 的宽高参数要传物理像素尺寸不是 CSS 尺寸。如果不乘getPixelRatio()画面会模糊到怀疑人生。提示加了 EffectComposer 后如果需要 resize 窗口记得同步更新 composer 的 size否则画面会变形。3.4 轮廓线技巧是性能敏感场景的折中方案后处理最稳但有些场景比如移动端低端机跑 SMAA 有点吃力。这时还有一个视觉欺骗技巧给模型加描边。用EdgesGeometry提取几何体的棱边再用LineSegments把边缘画成深色半透明线。人眼对“模型边缘整齐”的感知会显著降低对锯齿的敏感度。这个方法特别适合机械感强、棱角分明的模型比如工业设备、建筑模型。圆润的模型就算了反而会像低模三渲二。4. 中文教程和中文文档到底该怎么用Three.js 的中文资料并不缺但质量参差不齐。很多教程还在用 r130 甚至更老的 API照抄下来在 r185 上直接跑不起来。所以学会筛选资料比学会写代码更优先。4.1 中文文档的正确阅读顺序访问官网文档时地址栏后面加zh或者直接看中文版入口比如threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene这是官方翻译维护的中文文档虽然翻译偶有延迟但 API 说明是跟着版本走的不会出现旧版 API 误导你的情况。一个建议不要从开头的“安装”和“浏览器支持”逐字逐句读太催眠。直接跳到“创建一个场景”对照代码自己敲一遍。然后再跳去“使用纹理”、“光照”、“动画”这几篇。遇到不认识的类用右上角搜索框搜直接看对应的 API 页面。4.2 教程不要只“看”要“改”收藏一百篇文章不如亲手改爆十个 demo。Examples 页面里每个 demo 都带源码点开之后复制到本地然后开始乱改把球体半径改大、颜色改成渐变、相机位置调到正上方、去掉灯光看看效果……改坏了大不了重新加载。这个过程才是真正的学习因为你能亲眼看到每个参数对画面的影响比你死记硬背 API 有效得多。4.3 遇到问题去哪里查资料国内直接搜“Three.js 中文教程”会出来很多个人博客质量不稳定但胜在能快速给你一个方向。稍微偏门一点的问题比如某个 Shader 报错、某个第三方格式解析失败建议直接去 GitHub 仓库的 issues 搜索。Three.js 的维护者很活跃很多你踩的坑老外早就踩过一轮了。搜索技巧用英文搜关键词带上版本号比如three.js r185 antialias issue。中文搜索经常会把新版本和旧版本的解决方案混在一起反而浪费时间。5. 常见问题与排查技巧实录运行 Three.js 项目时新手遇到的大部分问题其实都很集中。我把踩过频率最高的几类整理成了一张速查表按概率排序。5.1 一张速查表解决 80% 的新手问题现象可能原因解决办法页面黑屏无报错相机位置在物体内部或看向错误方向检查 camera.position 和 camera.lookAt物体是纯黑色用了 StandardMaterial 但没加灯或灯光强度太低添加 AmbientLight、DirectionalLight模型加载出来是白的材质贴图没正确关联 UV或加载是异步的确认 loader.load 回调里再检查材质纹理模糊贴图分辨率低或纹理的 mipmap 设置不对使用高分辨率贴图调整 texture.minFilter抗锯齿无效WebGL2 默认 MSAA 行为不稳定尝试后处理 SMAA 或调高 pixelRatio窗口变大后画面拉伸渲染器尺寸没同步更新监听 resize调用 camera.aspect 更新与 renderer.setSize页面卡顿draw calls 过高或阴影渲染次数太多合并几何体减少灯光数量调低 shadow map 尺寸这些问题是所有 Three.js 开发者都绕不过去的坎你能快速定位就能省下大量排查时间。5.2 三个排查工具我每天都在用第一个是浏览器的 WebGL 扩展查看器在chrome://gpu页面能看到当前机器的 WebGL 信息确认硬件加速是否正常。第二个是帧率监控我一般直接放一个简单的 stats.js页面角落里实时显示 FPS跑起来心里有底。第三个是把renderer.info打印出来里面有 draw call 数量、三角形数量性能优化时全靠它判断瓶颈。注意很多人一卡就觉得是模型面数太多其实大部分场景的瓶颈都在 draw calls。一两个百万面的模型比一百个一万面的模型反而更容易优化因为前者可以只用一两次 draw call 画完后者却要一百次。6. 从入门到能接需求方向比勤奋更重要当你能把在线场景网站上的示例改成自己想要的样子基本算入门了。但距离“能接需求”还有一段路这段路最重要的不是写更多复制粘贴的 demo而是补三个方向交互、性能、生态。6.1 交互设计让用户和模型产生关系静态场景只是展示真正让项目加分的是交互。比如用Raycaster实现鼠标点选物体、用TWEEN或 GSAP 做相机平滑移动、用TransformControls让用户拖拽旋转物体。这些交互能力不需要你深入底层但要花时间把官方示例里的交互插件挨个玩一遍。我的建议是先做一个小项目练手一个可旋转、可点选、可查看详情信息的 3D 产品展示页。把模型加载、点击高亮、弹出信息面板、相机自动聚焦全部串起来。这个项目做完你会对 Three.js 的整体工作流有完整认识。6.2 性能调优从“能跑”到“跑得爽”性能优化的第一原则是不要凭感觉用数据说话。打开renderer.info看 draw calls 和 triangles。draw calls 超过 200 就要小心超过 500 基本上移动端就要掉帧了。优化手段优先级是合并静态几何体、使用实例化网格、压缩贴图尺寸、减少动态光源数量、禁用不必要的阴影。6.3 值得收藏的第三方库清单配合 Three.js 使用的生态已经很成熟了我日常最常用的有这些cannon-es或dimforge/rapier3d物理引擎做碰撞、刚体运动dat.GUI或lil-gui调试面板所见即所得调参GLTFTransform等工具处理模型导出格式gsap动画缓动做相机运动和交互动画特别方便three-mesh-bvh优化射线检测性能复杂场景必备学到这里你基本已经超过大部分自称“会用 Three.js”的人。最后说一个我自己的习惯每次跑新项目我都会特意打开性能面板观察 draw calls 和内存占用。不是给自己添堵而是很多问题在项目早期发现改造成本极低等做完了再回头修那才是真正的噩梦。这次关于 threejs 185 版本的踩坑记录就先到这。锯齿问题如果你按我给的步骤试完大部分机器上都能解决要是还不行看看是不是驱动太久没更新了。我最近一次遇到类似问题最后卡在集成显卡驱动上和代码一点关系都没有。