简介:three.js-r137.zip 是为前端开发者准备的 three.js r137 版本资料集,聚焦 WebGL 3D 渲染技术,帮助读者快速掌握在浏览器中构建三维场景的方法。压缩包共 2000 个文件,约 306.64MB,以 JS 源码和 HTML 示例为主,同时收录大量纹理贴图(jpg/png)、模型文件(gltf/glb/obj)、样式与文档,便于对照学习和二次开发,目前已有 190 人学习/下载。内容系统梳理了场景、相机、灯光、材质、纹理等核心概念,并涵盖 r137 版本在渲染性能、阴影处理、几何体扩展和动画控制上的改进,适合从零基础到进阶的前端开发者用于 3D 产品展示、游戏开发、数据可视化等方向。通过编译运行内置示例,可以直观理解 API 调用与调试要点,快速上手并缩短开发调试周期。
1. three.js-r137.zip 不只是一个离线包,而是一个被锁定的渲染环境
很多工程师拿到three.js-r137.zip后的第一反应,是解压、找index.html、双击、希望看到 3D 场景。实际维护老系统或做离线交付时,这个 zip 锁住的并不是文件,而是一整套 WebGL 渲染边界。r137 发布时正好处在 ES Module 普及和传统<script>标签并存的过渡期,所以同一份压缩包里既有three.min.js这种全局式构建,也有three.module.js这种模块化入口。对正在接手旧版数字孪生项目、在内网环境做three.js 下载后离线开发,或者需要照着旧教程复现效果的工程师来说,这个包比npm install three更可控:版本不会因为依赖解析悄悄改变,连带examples/jsm下的扩展模块路径也固定在同一天。先花 10 分钟搞清 r137 的边界,比急着升级或抄新代码更值得。
2. 解开 three.js-r137.zip:先跑通 ES Module 本地服务
拿到包之后,我一般先不看 3D 示例,而是先把目录结构和加载方式确认清楚。GitHub 下载的 zip 包不需要安装,解压后把该目录当作静态站点根目录就行。r137 的 release 包中通常能看到这样的结构:
three.js-r137/ build/ three.min.js three.module.js examples/ jsm/ controls/ loaders/ src/ package.jsonbuild/three.module.js是模块化开发的入口,build/three.min.js是老式全局引入用的。package.json里的 version 可以辅助核对,但最准的是运行时读取THREE.REVISION。examples/jsm是 r137 的扩展模块目录,里面是OrbitControls、GLTFLoader等,后面章节会用到。
2.1 先起本地静态服务,不要双击 index.html
如果你在 3D 页面里用了任何<script type="module">或new Worker,浏览器在file://协议下都会拦截跨域请求,直接双击打开的页面大概率只有一片空白,控制台里出现 CORS 报错。所以本地开发的第一步是起一个 HTTP 服务:
cd three.js-r137 python3 -m http.server 8080如果你没有 Python,只有 Node.js,也可以执行npx serve -l 8080。两种方式都是把当前目录暴露到本机 8080 端口,页面里的相对路径从根目录开始计算。之后访问http://localhost:8080。
2.2 用 importmap 把 three 映射到本地文件
r137 的模块化页面可以直接用script type="importmap",这是不需要打包器就能在浏览器里解析three模块的方法。在静态目录下建一个index.html:
<!doctype html> <html lang="zh-CN"> <head> <meta charset="utf-8" /> <title>r137 启动页</title> <style> html, body { margin: 0; height: 100%; background: #111; } </style> </head> <body> <script type="importmap"> { "imports": { "three": "./build/three.module.js" } } </script> <script type="module"> import * as THREE from 'three'; document.title = 'three.js r' + THREE.REVISION; </script> </body> </html>这段代码做了三件事:用importmap声明 bare import 名称three对应本地文件;用type="module"启用 ES Module 解析;把THREE.REVISION写到页面标题上。当标题显示three.js r137时,说明模块链路已经通了。如果标题不变,优先检查浏览器网络面板里.module.js请求是否 404,或者是否有 CORS 错误。
这里有个关键点:importmap里build/three.module.js前面的./不能省,否则浏览器会拿它当作绝对路径请求。如果你的项目后续要部署到子路径,还必须把这里改成基于import.meta.url的动态相对路径,否则子目录部署就会失效。
2.3 全局式 three.min.js 和模块化的取舍
老项目的three.js 教程常见写法是<script src="./build/three.min.js"></script>后用全局THREE。这种用法在 r137 里依然可用,区别主要是工程化和按需加载。把两种方式放在一起看:
| 引入方式 | 适用场景 | 是否依赖 HTTP 服务 | 主要局限 |
|---|---|---|---|
<script src="three.min.js"> | 遗留页面、快速 Demo | 普通 script 可以 file 打开,但纹理/模型加载仍受限 | 全局命名空间,扩展模块要额外 script |
importmap +three.module.js | 新页面、离线包集成 | 必须 HTTP 服务 | 需要写 importmap,浏览器支持有版本要求 |
| npm 包 | webpack/vite 工程 | 不依赖 | package-lock 锁版本,内网需要镜像缓存 |
实际维护中,如果只是画一个不会损坏的浮动物体,全局式更快;如果后面要接OrbitControls、GLTFLoader这些examples/jsm里的模块,推荐直接用 importmap,因为examples/jsm内部大量使用import语法,普通<script>无法直接消化。
3. r137 最小可运行场景:renderer / scene / camera 的初始化顺序
本地服务跑通之后,下一步不是急着写大场景,而是用一个旋转立方体验证渲染管线。很多报错不是 three.js 本身的问题,而是初始化顺序错了。下面这段代码是我在 r137 上常用的最小模板。
3.1 直接可贴的旋转立方体代码
import * as THREE from 'three'; import { OrbitControls } from './examples/jsm/controls/OrbitControls.js'; const container = document.querySelector('#app'); const width = container.clientWidth; const height = container.clientHeight; // 1. renderer 先创建,确保 canvas 就绪 const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(width, height); container.appendChild(renderer.domElement); // 2. scene 与 camera,注意 camera 是透视相机 const scene = new THREE.Scene(); scene.background = new THREE.Color(0x111122); const camera = new THREE.PerspectiveCamera( 50, width / height, 0.1, 1000 ); camera.position.set(3, 2, 5); camera.lookAt(0, 0, 0); // 3. 添加几何体、材质、灯光 const box = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshStandardMaterial({ color: 0x33aaff, roughness: 0.4, metalness: 0.1 }) ); scene.add(box); const ambient = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambient); const dirLight = new THREE.DirectionalLight(0xffffff, 1.2); dirLight.position.set(3, 4, 2); scene.add(dirLight); // 4. OrbitControls 接管相机交互 const controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.enableDamping = true; // 5. 动画循环 const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const elapsed = clock.getElapsedTime(); box.rotation.x = elapsed * 0.5; box.rotation.y = elapsed * 0.8; controls.update(); renderer.render(scene, camera); } animate(); // 6. 窗口自适应 window.addEventListener('resize', () => { const w = container.clientWidth; const h = container.clientHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); });代码中的顺序是刻意安排的:WebGLRenderer必须先创建并拿到domElement,后续OrbitControls才能把它作为交互挂载点。PerspectiveCamera的四个参数分别是fov、aspect、near、far,fov 越小越接近长焦,项目里常用 45 到 60;near 和 far 一般设到 0.1 和 1000 就够用,太小的 near 会引发深度精度问题。
灯光部分:AmbientLight提供基础亮度,DirectionalLight提供方向阴影用的硬光。MeshStandardMaterial对粗糙度和金属度敏感,r137 中roughness越高反光越弱,metalness越高越接近导体材质。如果场景只有环境光,立方体会显得很平,所以通常要补一个方向光。
controls.enableDamping = true会让相机在停止拖动后还有一段惯性滑动,但必须配合动画循环里的controls.update()。忘记这一步是惯性控制不生效的最常见原因。
3.2 r137 材质发灰时,先检查 outputEncoding
上面的代码跑起来后,很多同事的第一个反馈是蓝色材质比设计稿暗一截。这不是显示设备的问题,而是 r137 默认没有做 sRGB 输出转换。修复方法是在 renderer 创建后加两行:
renderer.outputEncoding = THREE.sRGBEncoding;如果用了外部的 albedo 贴图,还需要对贴图单独标记:
const texture = new THREE.TextureLoader().load('texture.png'); texture.encoding = THREE.sRGBEncoding;这个 API 在 r137 里是有效的,但到了 r150 以后,outputEncoding被outputColorSpace替代,枚举也从THREE.sRGBEncoding变成THREE.SRGBColorSpace。如果你在搜three.js新版代码时直接复制outputColorSpace到 r137 里,会得到 undefined。反之,把旧代码sRGBEncoding粘到新版工程里也会失效。这是 r137 和现代版本之间最常见的兼容地雷,放到后面第 5 章的检查清单里专门验证。
4. r137 离线包接入数字孪生工程:examples/jsm 的加载路径与依赖方式
three.js-r137.zip最有价值的地方在于它完整保留了examples/jsm目录,在离线环境里可以直接引用OrbitControls、GLTFLoader等官方扩展。这在工业数字孪生项目中非常常见:系统部署在隔离内网,不能在线拉取 CDN,也没有 npm 私有仓库,一个 zip 包就是唯一的依赖来源。
4.1 importmap 与 examples/jsm 的依赖关系
examples/jsm下的模块分散在controls/、loaders/、objects/等子目录里,模块之间常用相对路径互相引用,也会引用three这个 bare import。比如 r137 的OrbitControls.js顶部可能是import { ... } from 'three';。如果你在自己的工程里直接 import 这个文件,但页面没有定义 importmap,浏览器无法把'three'解析成一个 URL,会报 “Failed to resolve module specifier”。
所以在使用 zip 包内的任何 jsm 扩展前,必须先在入口 HTML 里定义统一的 importmap:
{ "imports": { "three": "./build/three.module.js" } }这样examples/jsm/controls/OrbitControls.js内部的import ... from 'three'才能命中同一个three.module.js。注意这里的three映射一定要指向模块版本,而不是three.min.js,因为 min 版本不是 ES Module,无法被 import 语法复用。
4.2 不放心时,先查模块内部 import 了谁
拿到陌生 zip 包时,我习惯先查一下它的真实依赖,而不是猜。进入解压目录后执行:
cd three.js-r137 grep -Rho "from .*" examples/jsm | sed "s/from //" | sort -u这段命令把examples/jsm下所有 ES Module 引用的来源粗略列出来。输出里如果出现from 'three',说明依赖顶层 importmap;如果出现from './xxx.js'或from '../xxx.js',说明它依赖 zip 包内的相对文件。看到路径后,用ls对照一下对应文件是否存在,就能在编码前排除缺失依赖的问题。
如果拿到的是一个从 GitHub 页面直接下载的源码 zip,有时examples/jsm里的依赖会指向../../examples/fonts这类资源目录。只要整个 zip 都在,相对路径不会断;真正容易断的是你把jsm某个文件单独复制到项目里,却没有带上它引用的兄弟文件。
4.3 在 r137 里加载一个 GLB 模型
下面是接入模型加载的最小片段。这里我假设你已经把模型放在站点根目录的models/下。
import * as THREE from 'three'; import { GLTFLoader } from './examples/jsm/loaders/GLTFLoader.js'; import { OrbitControls } from './examples/jsm/controls/OrbitControls.js'; const container = document.querySelector('#app'); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(container.clientWidth, container.clientHeight); container.appendChild(renderer.domElement); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 0.1, 2000 ); camera.position.set(10, 8, 10); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; scene.add(new THREE.AmbientLight(0xffffff, 0.8)); const dirLight = new THREE.DirectionalLight(0xffffff, 1.5); dirLight.position.set(5, 10, 7); scene.add(dirLight); const loader = new GLTFLoader(); loader.load( './models/room.glb', (gltf) => { scene.add(gltf.scene); const box = new THREE.Box3().setFromObject(gltf.scene); const center = box.getCenter(new THREE.Vector3()); controls.target.copy(center); }, undefined, (err) => console.error('GLTF 加载失败', err) ); function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate();代码里renderer增加了alpha: true,这样页面背景能透明,方便和cesium等底图合成的工业数字孪生页面做叠加。GLTFLoader的load回调里用Box3计算模型包围盒,再把相机控制目标移动到模型中心,避免模型偏在原点外看不全。GLTF 加载失败分支里最常见的错误是本地模型路径 404,或者纹理文件路径对不上,先看 Network 面板,别急着改代码。
4.4 三种获取 three.js 的方式怎么选
把前面的选型汇总成一张表,对带离线限制的项目尤其有用:
| 来源 | 版本锁定 | 离线可用 | 工程化便利度 | 注意点 |
|---|---|---|---|---|
| CDN 引入 | URL 中版本号 | 否 | 低 | 内网不可用,版本不可控 |
| npm 安装 | package-lock | 需要镜像缓存 | 高 | 升级时副作用大 |
| zip + importmap | 物理文件 | 是 | 中 | 需要自行管理静态服务路径 |
在 r137 这个版本下,alpha: true的透明背景页面往往是最先被用到的集成点。要注意WebGLRenderer的alpha打开后,如果还用scene.background设置不透明颜色,透明效果会被覆盖。常见做法是让页面背景由外层 DOM/Cesium 决定,three.js 场景里不设置 background。
5. 拿到 three.js-r137.zip 后先做三次检查:版本、依赖与颜色空间
最后用一个 10 分钟能做完的检查流程收住这个主题。每次拿到陌生 zip 包,或在 r137 工程里排查问题时,我按顺序做下面三件事。
5.1 验证 REVISION 而不是目录名
package.json和文件夹名都可以被改,但运行时版本号不会骗人。在页面控制台执行:
import * as THREE from 'three'; console.log(THREE.REVISION);输出137就说明页面加载的就是 r137。如果输出了别的数字,说明你虽然拿着 r137 的 zip,但实际项目引用了另一个构建文件。反过来,如果项目已经混入了新版本,THREE.REVISION会立刻暴露问题。
5.2 用 grep 列出扩展模块的依赖路径
从 GitHub 下载的 zip 包大多数是完整的,但手动拷贝examples/jsm下的单个文件时很容易漏掉兄弟文件。在解压后的根目录执行:
cd three.js-r137 grep -Rho "from .*" examples/jsm | sed "s/from //" | sort -u如果输出里只有three和../、./开头的路径,你可以按路径逐个确认。所有相对路径的目标都要存在于 zip 包内,否则浏览器会在运行时 404。这个命令不关注from 'three',因为那是 importmap 的职责范围。
5.3 颜色空间回归:新老 API 的判断
r137 代码里用renderer.outputEncoding = THREE.sRGBEncoding;是合法写法。但在代码里做一次防御性判断,可以避免未来迁移时炸掉:
if (THREE.ColorManagement !== undefined) { renderer.outputColorSpace = THREE.SRGBColorSpace; } else { renderer.outputEncoding = THREE.sRGBEncoding; }这个判断依据是THREE.ColorManagement是否存在:r150+ 有它,r137 没有。这样同一份页面在 r137 和现代版本下都能得到正确的 sRGB 输出。把这段逻辑放到渲染器初始化末尾,再配合THREE.REVISION的检查,r137 包是否真的生效、扩展模块是否完整、颜色是否走对,三件事就都验证完了。
本文还有配套的精品资源,点击获取