Three.js入门全攻略:环境搭建、核心概念与避坑指南

📅 发布时间:2026/10/13 18:50:22
Three.js入门全攻略:环境搭建、核心概念与避坑指南
我刚接触三维开发那会儿和大部分人一样第一眼看到的就是 Three.js 官网上那个旋转的立方体。当时心里想的是这东西看着挺酷但要让我自己从头搭一个出来完全不知道从哪里下手。网上搜THREEJS基础出来一堆教程要么讲得太浅只教你复制粘贴要么一上来就铺开几十个 API看得人直接放弃。后来硬着头皮啃了一段踩了无数坑才算把这条链路彻底捋顺。这篇东西就是写给准备入坑或者刚入坑的朋友。我会从为什么需要 Three.js讲起把环境搭建、核心概念、常见踩坑一条线串到底尽量说人话把那些文档里语焉不详、教程里一笔带过的地方都点透。你不需要有任何三维基础只要会点 JavaScript跟着走一遍就能跑通自己的第一个三维页面。1. 为什么前端要学 Three.js它到底解决什么问题浏览器里做三维渲染底层的技术叫 WebGL。WebGL 本身是一个相当底层的图形接口它只管往 GPU 提交顶点数据、运行着色器、画三角形。你要是直接拿 WebGL 写一个立方体需要自己管理缓冲区、矩阵运算、着色器编译几百行代码起步而且稍不留神就出各种莫名其妙的渲染问题。Three.js 就是在这个底层接口之上封装出来的一套三维引擎它帮你把几何体定义、材质光照、相机投影、渲染循环这些脏活累活全包了让你能用相对高级的 API 去描述我想画一个这样的场景。这个定位很重要Three.js 不是要替代 WebGL而是让前端工程师不必成为图形学专家也能把三维内容搬到网页里。你需要关心的不再是怎么把顶点数据传给 GPU而是我的场景里有什么物体、长什么样、用什么材质、光源放在哪、相机从哪个角度看。这套抽象思维一旦建立起来后面所有东西学起来都会快很多。它实际能做的场景也很广。最常见的是产品展示比如一双鞋、一台家电用户可以在页面上旋转、缩放看细节其次是数据可视化把一堆抽象的数字变成三维的柱状图、点云、关系图谱再就是交互型页面比如地图的 3D 化、展厅导览、游戏原型。我见过不少项目其实业务需求并不复杂就是要一个能转、能看的 3D 场景用 Three.js 恰恰是最合适的选择——比从头写 WebGL 快得多比引入一整个重型游戏引擎又轻得多。需要提醒一句的是这个库的规范名称里是有大写的 T 和点号的写出来是 Three.js不是 threejs 也不是 ThreeJS。很多人自己写 demo 无所谓但进了团队协作代码注释、README、技术文档里拼写统一别人查资料也方便看着也更专业。2. 先把环境搭起来从空文件夹到看到第一个旋转立方体2.1 为什么推荐 Vite 而不是直接引 CDN很多入门教程会让你直接引一个 CDN 链接然后打开 HTML 文件看效果。这种方式确实最快但也有个问题一旦你想用 npm 生态里的其他库或者项目稍微大一点、需要拆模块就很容易卡住。我个人建议直接上 Vite它启动快、配置几乎为零而且是前端主流工具链学一次后面一直能用。搭环境很简单三步搞定npm create vitelatest three-demo -- --template vanilla cd three-demo npm install three装完后打开项目里的main.js把默认的计数器代码删掉换成下面这一段import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.set(2, 2, 5); const renderer new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const geometry new THREE.BoxGeometry(1, 1, 1); const material new THREE.MeshStandardMaterial({ color: 0x00aaff }); const cube new THREE.Mesh(geometry, material); scene.add(cube); const light new THREE.DirectionalLight(0xffffff, 3); light.position.set(3, 5, 4); scene.add(light); const controls new OrbitControls(camera, renderer.domElement); function animate() { requestAnimationFrame(animate); cube.rotation.x 0.01; cube.rotation.y 0.01; controls.update(); renderer.render(scene, camera); } animate();然后npm run dev浏览器打开地址你应该就能看到一个淡蓝色的立方体在转按住鼠标左键拖拽可以旋转视角滚轮可以缩放。这就是 Three.js 的Hello World。2.2 拆解最小可行场景每个对象分别扮演什么角色这一小段代码看着不长但它把 Three.js 的核心五件套全用上了。我在带新手的时候发现如果一开始就把这五个角色搞清楚后面的学习会顺畅非常多。这里直接用大白话拆一下Scene场景一个容器你所有要渲染的东西——模型、灯光、辅助线——都往里放。它本身不干别的就是世界的载体。Camera相机观察者。它定义了从哪个位置、以什么角度看向哪里。PerspectiveCamera 是透视相机会遵循近大远小的规则这也符合人眼看真实世界的直觉。Renderer渲染器负责把场景和相机合在一起算出一张画面再画到画布上。它接收的scene和camera是固定参数所以几乎所有 demo 里你都会看到renderer.render(scene, camera)这一行。Mesh网格由几何体Geometry和材质Material组成。几何体决定物体长什么形状材质决定这个表面怎么被光照影响、呈现什么颜色。记住这两个是分开的同一个方块可以换不同材质同一个材质也可以套在不同几何体上。Light光源没有光的场景是黑的。MeshStandardMaterial 这类 PBR 材质必须配合光源才能看到立体效果否则看起来就是个黑色剪影。这五个角色之间的依赖关系是一条链Scene 装所有东西Camera 观察场景Renderer 把场景相机拍成照片Mesh 是能被看到的实体Light 让实体有明暗和体积感。任何一个环节断了你得到的就是黑屏或者空场景。2.3 相机参数背后的几何直觉新手最容易对相机参数发怵尤其是PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)这四个数字看着跟咒语似的。其实它们就是一个透视关系的四个约束条件。视场角FOV75 代表垂直方向能看到的角度范围。角度越大视野越宽物体看起来越小角度越小像长焦镜头画面会被拉近。可以在 30 到 100 之间多试试找找感觉。宽高比Aspect画布宽度除以高度。不设置或者设错画面里的物体会被拉伸变形。这也是为什么窗口大小变了要重新renderer.setSize并同步更新相机宽高比。近裁面和远裁面相机只渲染这两个平面之间的物体超出范围直接裁剪掉。0.1 和 1000 是近处 0.1 单位、远处 1000 单位单位在 Three.js 里默认算作米但实际意义由你自己定。一个常见坑是忘了改远裁面结果场景里的模型放到很大后部分被切掉看起来像被削了一刀。这三个概念只要记住视场角管广角、宽高比管变形、裁剪面管可见范围后面调相机参数就不会再心虚了。3. 把坐标系、旋转和缩放搞明白你就超越了大多数初级教程3.1 坐标系为什么是右手系一个生活化类比Three.js 里的坐标系是右手坐标系X 轴朝右Y 轴朝上Z 轴指向屏幕外。为什么要强调右手因为三维空间的方向如果不统一你算旋转、写相机位置、判断模型朝向时很容易拿到相反的值。这里有个土办法伸出右手食指指向 X 轴正方向中指指向 Y 轴正方向大拇指指向的就是 Z 轴正方向。这个手势就是整个坐标系的方向约定。位置用Vector3表示camera.position.set(2, 2, 5)的意思就是把相机放在 X2、Y2、Z5 的点上。在调位置时我建议你养成一个习惯先想清楚物体在哪、相机在哪、我要从哪个方向看过去再写数字。比如默认方块在原点 (0,0,0)相机放在 (2,2,5)就是从右上方斜着看过去这样立方体的三个面都能看到立体感最强。你要是把相机放在 (0,0,5)看到的就是一个平平的正方形根本感觉不到这是三维的。3.2 旋转顺序和单位为什么是 0.01 不是 1cube.rotation.x 0.01这句里0.01 的单位是弧度不是角度。一整个圆是 2π 弧度大约 6.28。每帧加 0.01如果帧率 60fps一秒钟大概转 0.6 弧度约 34 度视觉上是个比较舒服的匀速旋转。要是写成 1一秒转 60 弧度将近十圈画面会糊成一片看着就晕。新手想按角度来思考怎么办很简单用 MathUtilsimport * as THREE from three; cube.rotation.y THREE.MathUtils.degToRad(2);degToRad就是把角度转成弧度2 度一帧一秒 120 度左右依然很快但能看清。这个函数在调试旋转角度时非常常用记住它比每次手算要省事得多。另外要注意旋转顺序。Three.js 默认按 X、Y、Z 的欧拉角顺序旋转这意味着你同时对rotation.x和rotation.y赋值时实际效果并不等价于先转 X 再转 Y的直观想象。在大多数简单场景里这不会造成问题但一旦你开始做复杂父子嵌套或者模型朝向控制建议直接了解四元数Quaternion或者给旋转顺序做一个显式设置。初级阶段知道有这回事就行不必深究但至少别再被为什么我转了 X 轴Y 轴也跟着歪这种问题吓到。3.3 缩放和位置操作要区分改变数据和改变层级缩放用cube.scale.set(2, 1, 1)会把方块沿 X 方向拉长一倍。位置用cube.position.x 1或者cube.position.set(1, 0, 0)。这些都很基础但有一个概念容易被忽略变换是作用在对象自身的局部坐标系上的。什么意思你往场景里加了一个组Group组里放了一个方块方块的位置相对组是 (1,0,0)。然后你把组旋转了 90 度方块的绝对位置就不再是 (1,0,0) 了它跟着组的坐标系一起转了。这个父级变换会传递给子级的行为是三维引擎里最常见的层级关系。新手阶段不需要玩得很花哨但至少要明白你操作的是相对谁的变换。如果发现物体位置不对第一反应应该是查一下它是不是被挂在了某个奇怪的父级下面。4. 常见坑花了很长时间渲染出来的却是黑屏、模糊或错乱4.1 黑屏的三种经典原因以及各自的排查手段黑屏是新手遇到的最常见问题而且原因通常不在渲染器本身。我把它总结成一张排查表按概率排序表现最可能的原因处理办法页面空白什么都没有忘了把renderer.domElement加到 body 里检查是否有document.body.appendChild(renderer.domElement)页面有画布但是黑的场景里没有加光源而你的材质依赖光照加一个DirectionalLight或AmbientLight页面黑但有物体轮廓相机离物体太近或者被裁剪面切掉检查camera.position把近裁面改小、远裁面改大这三类问题里最容易让人抓狂的是第二种。很多人看到MeshStandardMaterial是官方推荐的标准材质直接用结果场景里没放灯画面一团黑以为是代码写错了。其实标准材质在后端基于物理光照模型计算没有光源的时候根本无从计算亮度自然就是黑的。这时候要么加灯要么先把材质临时换成MeshBasicMaterial——这个材质不受光照影响永远显示它自己的颜色非常适合排查到底是材质问题还是灯光问题。4.2 画面模糊和锯齿一个 setPixelRatio 就能解决跑通第一个立方体后你可能会发现边缘有明显的锯齿或者整个画面看起来雾蒙蒙的。锯齿是因为渲染器的默认像素比率和你的屏幕不匹配。浏览器里 CSS 像素和设备物理像素经常不是 1:1尤其是在 Retina 屏上一个 CSS 像素可能对应 2 个甚至 3 个物理像素。Three.js 默认按 1:1 渲染于是你的画面被拉伸到物理屏幕上边缘就糊了。解决办法是一行代码renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));这句写在renderer.setSize之前。devicePixelRatio是浏览器提供的屏幕像素比取它和 2 的较小值是为了避免在超高分辨率屏幕上渲染负担过重。手机屏幕普遍 3 倍率全部按 3 渲染会拖垮性能限制到 2 是性能与清晰度的折中方案。这也是为什么很多项目模板里都会带上这一行它不是可有可无。4.3 物体转不动或者操作没反应检查是不是忘了加轨道控制器如果你只是想让物体自动旋转不加 OrbitControls 一点问题没有。但如果你期待的是按住鼠标拖拽旋转视角就必须显式引入控制器。很多新手照着官网代码抄抄到OrbitControls没引入或者引入之后没在动画循环里调用controls.update()结果就是页面显示正常但鼠标拖拽毫无反应。控制器这俩要点缺一不可import { OrbitControls } from three/addons/controls/OrbitControls.js; const controls new OrbitControls(camera, renderer.domElement);另外控制器监听的是renderer.domElement上的鼠标事件如果你在 DOM 上叠加了其他元素盖住了画布鼠标事件就传不进去也会表现为拖不动。我遇到过有同学在画布上盖了一个半透明的信息面板调试半天以为是控制器的问题其实只是事件被挡住了。4.4 颜色偏暗和过曝从默认颜色空间说起近几个版本的 Three.js 已经默认开启了色彩空间管理但你仍有可能遇到画面颜色和设计稿对不上的问题。最常见的是你在代码里写0x00aaff渲染出来却比在 Figma 里看的要暗一点或者闷一点。这是因为 Three.js 默认把颜色当作线性色彩处理而浏览器显示的是 sRGB 色彩空间两者需要转换。简单说如果你的图片纹理是给网页用的建议给纹理设一下texture.colorSpace THREE.SRGBColorSpace;模型或者贴图颜色不对的时候先检查是不是忘了这步。基础阶段没必要深究色彩原理但记住这个纹理要单独标记色彩空间的习惯后面做项目时能省下大量对着色器调色值的时间。4.5 窗口拉伸后画面变形resize 的处理必须同步浏览器窗口一变Three.js 不会自动感知。你需要监听 resize 事件然后更新三样东西相机的宽高比、渲染器的尺寸、渲染器的像素比率。标准的写法是这样的window.addEventListener(resize, () { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); });camera.updateProjectionMatrix()这一行尤其不能漏。相机的投影矩阵是根据aspect计算出来的改了宽高比不更新投影矩阵画面渲染出来就是扁的或者歪的。我见过很多人 resize 事件写得潦草只更新了setSize结果拉伸窗口后物体变形排查很久才发现是投影矩阵没更新。这个顺序问题值得养成肌肉记忆。5. 从跑通 Demo 到写出自己的场景掌握这些能力才算入门5.1 官方示例的代码怎么读先跑起来再拆结构如果你想继续深入我最推荐的方式是去官网的 example 页面挑感兴趣的 demo先把代码下载或复制到本地跑起来再去拆它的结构。读 demo 的代码有一个顺序技巧先找到renderer、camera、scene这三个必建对象再找animate函数最后才看中间那一大坨模型和材质创建代码。为什么是这个顺序因为前三个定义了整个渲染链路的骨架你先把骨架摸清再往里填模型、灯光、动画逻辑时就不会迷失在细节里。还有个小习惯看 demo 时把OrbitControls的代码先注释掉。很多官方示例默认允许拖拽观察但如果你一开始就去理解controls就很容易把控制器逻辑和场景逻辑混在一起。关掉控制器后场景本身是静态的还是动态的会一目了然你也能更清楚地看到物体的变换是写在哪里的。5.2 用改参数观察变化代替背 API 文档Three.js 的 API 很多靠背是背不完的。我的建议是每学一个新概念都写一个独立的小页面只包含一个几何体、一个光源、一个控制器然后不停地改参数看效果。比如学BoxGeometry就分别改它的长宽高观察形状变化学MeshStandardMaterial就改roughness粗糙度和metalness金属感看看不同组合下的表面质感差异学DirectionalLight就把光源位置绕着物体转一圈体会光照方向对阴影和明暗面的影响。我当初学roughness和metalness的时候就是靠这种方式建立起直觉的。把roughness调成 0表面像镜子一样反射调成 1表面像砂纸一样完全漫反射。把metalness调成 1颜色会明显偏向光源色非金属感消失。这些经验你光看文档是体会不到的只有自己动手调过才能真正理解为什么游戏引擎里的金属材质看起来那么滑、油漆材质看起来那么厚。5.3 动画循环的核心requestAnimationFrame 为什么是循环很多初学者会问为什么每次渲染都要包一层requestAnimationFrame能不能直接renderer.render(scene, camera)一次答案是可以但那样你只能得到一帧静态画面。浏览器是按帧刷新屏幕的requestAnimationFrame告诉浏览器下一帧继续调用我这个函数于是每次屏幕刷新都会执行一次动画逻辑加一次渲染这就构成了循环。这三件事必须在animate里做更新物体属性比如旋转、位移、更新控制器状态、渲染画面。顺序可以调换但如果没有requestAnimationFrame(animate)动画就不会持续如果忘了在循环里renderer.render画面就不会更新。这听起来像废话但我在帮人排查时真的遇到过把render放在循环外面的人他们看到的画面只有初始一帧物体动不了。5.4 性能的朴素认识别急着学优化先学会别作恶网上很多进阶内容一上来就讲大场景优化、模型压缩、自定义着色器对刚入门的人来说其实为时过早。基础阶段你只需要掌握几个最简单的性能原则场景里不要堆太多高精度几何体。默认的 BoxGeometry 面数很少但如果你加载一个几十 MB 的 FBX 模型再进行实时旋转帧率很容易掉。每帧都做的事情要尽量简单。比如不是每帧都去创建新的数组、调用new THREE.Vector3()。能拿出来复用的对象就放在外面创建。控制渲染器的像素比率上限前面提到的Math.min(window.devicePixelRatio, 2)就是这个目的。灯光数量尽量控制。实时光源多GPU 计算量会成倍增长尤其是带阴影的光源那更是性能大户。这三条原则属于地基级别的认知。它们不会让你的项目变得多炫酷但能避免你一上来就把页面搞卡。真正的高阶优化等你遇到实际性能瓶颈再学不迟。5.5 模型和纹理从原生几何体迈向真实资源的必经一步学会用BoxGeometry和SphereGeometry之后你很快会想放一个真实的模型进去比如一栋建筑、一个角色、一台设备。Three.js 加载模型常用的是 GLTF 格式对应加载器是import { GLTFLoader } from three/addons/loaders/GLTFLoader.js; const loader new GLTFLoader(); loader.load(/model/scene.gltf, (gltf) { scene.add(gltf.scene); }, undefined, (error) { console.error(error); });这里有几个新手容易忽略的点。第一加载是异步的你得等回调触发后才能往场景里加模型不能在loader.load之后立刻操作gltf.scene。第二模型自带的位置和缩放往往不是你要的加载后通常需要调整一次gltf.scene.scale.set和gltf.scene.position.set甚至需要旋转让模型朝向正确。第三模型的材质可能是独立的如果模型看起来特别暗很可能它的贴图没带进来或者色彩空间标记不对还记得前面说的texture.colorSpace吗这时候就用上了。如果你想自己测试模型加载最简单的办法是在线找一些免费 CC0 协议的 GLTF 模型下载到本地放到public目录下然后用import.meta.env.BASE_URL model/scene.gltf的方式拼接路径。直接用相对路径在 Vite 下有时会因为 base 配置不对而 404这种路径问题也是高频坑。6. 进阶路线打好基础后顺着这三个方向深挖最有效掌握了前面这些你基本已经具备了自己写小型 Three.js 页面的能力。接下来怎么走我给出三个我觉得投入产出比最高的方向你可以按兴趣挑一个先钻进去。方向一图形学补课。Three.js 把如何渲染封装了但你要理解为什么这样渲染还是要回到图形学基础。重点看光照模型、材质、相机投影矩阵。这部分数学比较多但不需要一上来啃大部头遇到什么概念查什么即可。方向二动画与交互。学会直接用requestAnimationFrame控制物体运动后可以进一步了解缓动函数、补间库比如 GSAP怎么和 Three.js 配合。页面的灵动感主要靠这一层实现。方向三资源生产链路。真实项目的模型不是代码写的是建模师在工具里做出来的。你需要搞明白 DCC 工具导出 GLTF 时有哪些选项、贴图怎么压缩、模型怎么减面。这条链路是实战项目中沟通成本最高的部分。这三个方向我建议最少挑一个深入。因为 Three.js 本身只是个工具真正拉开人与人差距的是你对图形学底层、动画设计或者资源管线的理解深度。7. 最后分享一点我个人的实操体会我自己学了这么久的 Three.js最大的体会是入门的关键不是记 API而是建立起场景、相机、渲染器、物体、光源这条思维链路。只要这条链路在脑子里是清晰的遇到任何奇怪的渲染问题都能按图索骥去排查——画面黑了查光源看不到物体查相机拖不动查控制器变形了查宽高比。这套排查逻辑比背下所有构造函数的参数值有用得多。另一个体会是多写一次性实验代码。不要一上来就想做一个完整的案例而是开一个空项目每次只测试一个功能点。想试MeshNormalMaterial就放一个球调参数看效果。想试GridHelper就放一个网格地面挪相机。这种单点试验的积累方式会让你对各种 API 的效果形成真实记忆而不是看了文档第二天就忘。等你积累了几十个这样的实验页再回头做完整项目你会发现写代码的速度和理解深度完全不一样。如果你现在正准备写第一个 Three.js 页面别犹豫直接照我第二部分的代码跑一遍然后把相机位置、光源强度、旋转速度各改一遍感受一下变化。能改出自己理解的效果比看十篇教程都管用。