Three.js 日常开发中的向量与矩阵
这是一套面向日常开发问题的中文教程,共 41 章、164 道不附答案的练习。重点是 Vector3 与 Matrix3 的逐项接口讲解,同时覆盖坐标转换、Euler、Quaternion、Object3D、相机、交互、光照和 Shader 数据流。
目标是让你能解释“公式为什么这样写、输入在哪个空间、结果为什么对应这个视觉效果”,并把几何工具组合成可以维护的业务代码。数学入门读者可顺序阅读,有具体需求时直接使用下面的问题导航。
版本与约定
- 接口以本仓库安装的 Three.js 0.185.1 / r185 源码为准,Vector3 与 Matrix3 的公共方法及属性逐项覆盖,包括弃用接口的迁移提示。
- 渲染示例面向 WebGLRenderer / WebGL2 默认坐标约定;NDC 深度使用
[-1,1],不能无条件套到 WebGPU。 - 默认右手坐标系、列向量、角度单位为弧度、时间单位为秒;世界长度单位由项目定义,案例常以 1 单位 = 1 米理解。
- 相机使用常规单位缩放层级。相机缩放、非均匀父缩放、剪切和不可逆变换有额外限制,相关章节会明确说明。
- 数值输出允许浮点舍入。多数 Vector3/Matrix3 运算修改接收对象,代码中的 clone、copy 和输出参数都有明确目的。
- 每章末尾四道练习,暂不提供答案;正文中的演示结果用于解释知识点,不是练习答案。
怎么读
从基础开始: 01–09 建立坐标与几何直觉 → 10–21 系统学习重点接口 → 22–25 掌握旋转和层级 → 选择 26–37 案例 → 38–41 补齐 Shader 与工程实践。
按问题学习: 先读对应案例,遇到不熟悉的公式或接口,再查基础章与接口索引。不必为了实现拖拽先通读所有随机采样接口。
| 日常问题 | 建议阅读 |
|---|---|
| 物体在父节点下,怎样获取或设置世界位置? | 03 局部与世界空间、25 Object3D |
| camera space、world space、NDC 有什么区别? | 04 相机空间、05 裁剪与 NDC、06 完整转换 |
| 点积、叉积到底解决什么问题? | 07 点积、08 二维叉积、09 三维叉积 |
| 给模型加 HTML 标注,缩放窗口后位置不对 | 26 HTML 标签 |
| 鼠标拖拽物体,按下时突然跳动 | 27 拾取与拖拽 |
| 不同大小模型如何自动完整显示? | 28 自动取景 |
| 角色越靠近目标越慢,或者高刷新率跑得更快 | 14 插值、29 角色运动 |
| 目标在左边还是右边、是否位于视野范围? | 30 导航与视野 |
| 如何围绕目标旋转、让相机平滑跟随? | 24 旋转组合、31 跟随相机 |
| 如何沿斜坡或墙面移动? | 32 投影运动 |
| 反射以后速度不对,或穿过了地面 | 33 弹球、40 数值稳定性 |
| 怎样测量点到边的距离、把零件吸附到插槽? | 34 测量与吸附 |
| 模型拉伸后光照异常 | 20 法线矩阵、35 光照修复 |
| UV 旋转方向为什么和图案看起来相反? | 21 UV 矩阵、36 纹理编辑 |
| 如何向任意方向发射均匀分布的粒子? | 37 粒子发射器 |
| JavaScript 的 Vector3 如何变成 GLSL 的 vec3? | 38 Shader 数据流 |
| 应该把计算放在 CPU 还是 GPU? | 39 计算分工、41 排错与性能 |
完整目录与练习导航
每个链接对应一篇独立 Markdown 文档,练习位于各章末尾。
基础与坐标空间
- 点、向量、方向与单位
- Vector2 与 Vector4 快速入门
- Local Space 与 World Space
- Camera Space:把世界放到相机面前
- Clip Space、NDC 与屏幕坐标
- 坐标转换完整链路与逆向转换
点积与叉积
Vector3:七章完整接口详解
Matrix3:五章完整接口详解
旋转与 Three.js 变换体系
真实场景实践
- 给三维设备添加 HTML 信息标签
- 鼠标拾取与拖动家具
- 商品模型自动居中与完整取景
- 角色移动、追踪与平滑转向
- 小地图导航与目标视野检测
- 第三人称相机跟随与环绕
- 车辆沿斜坡移动、物体贴墙滑行
- 弹球反射与表面反弹
- 模型测量、最近点与装配吸附
- 修复模型缩放后的光照异常
- 纹理编辑器:平移、缩放与旋转
- 可定向的喷泉与粒子发射器
Shader 协作与工程实践
如何运行代码
数值例子: 在 Vite 的 JavaScript 模块中使用 import * as THREE from 'three',再运行相关片段。许多短代码块承接同一章的变量,不能把整个系列所有代码块拼成一个文件;重复的变量名和不同场景本来就应该隔离。
实战例子: 使用 examples/runtime.js 公共外壳。把它复制到你的练习入口脚本旁边,再将某章“完整核心代码”保存为 main.js。章节中的 import { createDemo } from './runtime.js' 就能解析。使用已有 Vite 练习页时,应替换原场景初始化与渲染循环,避免同时创建两个 renderer。
运行页面需有一个已确定尺寸的容器。默认是 body,练习入口样式可以设置 html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; };也可以调用 createDemo({ parent: 容器元素 }),由页面为容器指定尺寸。不要把未设高度的空 body 当成有效视口。
外壳 API:
| 返回值 | 契约 |
|---|---|
THREE |
Three.js 模块命名空间 |
scene |
空场景,无预设灯光或地面 |
camera |
PerspectiveCamera,60°,near=0.1、far=1000,初始位置 (6,5,8),看向原点 |
renderer |
WebGLRenderer,DPR 上限为 2,自动响应容器与窗口尺寸 |
onFrame(callback) |
注册渲染前回调 (dt, elapsed),返回退订函数;时间单位为秒 |
onDispose(callback) |
注册 DOM、事件等自定义释放逻辑,返回注销函数 |
dispose() |
停止循环,移除监听与画布,释放独立场景中的常见几何体、材质与纹理 |
dt 上限为 0.05 秒,elapsed 是截断后累计的模拟时间。这能减轻后台恢复时的大步跳跃,但不保证与真实时钟同步,也不是完整物理引擎。几何体或材质跨场景共享时,不应直接套用这个独立示例的资源所有权策略。
实战章节可把 dispose 加入解构,并在入口末尾加 if (import.meta.hot) import.meta.hot.dispose(dispose);,以便 Vite 热更新时释放旧场景。离开自定义路由或切换示例时同样应主动调用 dispose。
从项目根目录执行 yarn dev,打开你的练习页面;不要通过 file:// 直接加载模块。新建正式项目示例页时,还要遵守仓库的设计规范和入口登记规则。本次教程提供 Markdown 与公共外壳,不新增项目展示页面。
系统默认 Node 12 可以运行当前项目的 Vite 构建,但直接在该 Node 中导入 r185 的现代模块可能遇到语法错误。终端数学实验应使用能够解析该依赖的现代 Node;不要把浏览器模块的运行问题与 Vite 构建是否成功混为一谈。
公式与选择速查
| 目的 | 公式或选择 | 条件 |
|---|---|---|
| 从 A 朝 B | B - A |
两点处于同一空间 |
| 匀速位移 | position += unitDirection * speed * dt |
非零方向,单位一致 |
| 前后与夹角 | `a·b = | a |
| 二维左右 | ax*by - ay*bx |
先声明轴向与参考顺序 |
| 三角形法线 | (B-A)×(C-A) |
非共线;方向依赖绕序 |
| 投影到单位方向 u | (v·u)u |
u 为单位向量 |
| 投影到平面 | v - (v·n)n |
n 为单位法线 |
| 镜面反射 | v - 2(v·n)n |
n 为单位法线 |
| 法线变换 | (A⁻¹)ᵀn 后归一化 |
A 为可逆线性部分 |
| 局部到裁剪 | P * V * M * vec4(p,1) |
右侧先作用 |
| 裁剪到 NDC | clip.xyz / clip.w |
w 非零,注意可见性 |
| 页面点到世界 | NDC → 射线 → 与几何体求交 | 仅 x/y 不足以确定深度 |
| 稳定朝向插值 | Quaternion 的 slerp/rotateTowards | 有效单位四元数 |
接口索引
下方索引按类与章节列出全部 Vector3、Matrix3 公共接口。点击章节可找到对应独立小节。
| 类型 | 章节 | 接口与属性 |
|---|---|---|
| Vector3 | 10 章 | constructor()、set()、setScalar()、setX()、setY()、setZ()、setComponent()、getComponent()、clone()、copy()、equals()、fromArray()、toArray()、fromBufferAttribute()、x/y/z、isVector3、[Symbol.iterator]() |
| Vector3 | 11 章 | add()、addScalar()、addVectors()、addScaledVector()、sub()、subScalar()、subVectors()、multiply()、multiplyScalar()、multiplyVectors()、divide()、divideScalar()、negate() |
| Vector3 | 12 章 | lengthSq()、length()、manhattanLength()、normalize()、setLength()、distanceTo()、distanceToSquared()、manhattanDistanceTo()、min()、max()、clamp()、clampScalar()、clampLength()、floor()、ceil()、round()、roundToZero() |
| Vector3 | 13 章 | dot()、cross()、crossVectors()、angleTo()、projectOnVector()、projectOnPlane()、reflect() |
| Vector3 | 14 章 | lerp()、lerpVectors()、random()、randomDirection()、setFromSpherical()、setFromSphericalCoords()、setFromCylindrical()、setFromCylindricalCoords() |
| Vector3 | 15 章 | applyEuler()、applyAxisAngle()、applyQuaternion()、applyMatrix3()、applyNormalMatrix()、applyMatrix4()、transformDirection()、project()、unproject() |
| Vector3 | 16 章 | setFromMatrixPosition()、setFromMatrixScale()、setFromMatrixColumn()、setFromMatrix3Column()、setFromEuler()、setFromColor() |
| Matrix3 | 17 章 | constructor()、set()、identity()、copy()、clone()、equals()、fromArray()、toArray()、elements、isMatrix3 |
| Matrix3 | 18 章 | multiply()、premultiply()、multiplyMatrices()、multiplyScalar() |
| Matrix3 | 19 章 | determinant()、invert()、transpose()、transposeIntoArray() |
| Matrix3 | 20 章 | extractBasis()、setFromMatrix4()、getNormalMatrix() |
| Matrix3 | 21 章 | makeTranslation()、makeRotation()、makeScale()、translate()、rotate()、scale()、setUvTransform() |
本次验证范围
编写完成后已检查 41 个章节文件、164 道练习、110 个 JavaScript 代码块语法和全部本地文档链接;Vector3、Matrix3 方法标题与 r185 源码逐项对照无遗漏。关键数学与实战逻辑通过数值断言验证。
第 26–38 章及第 41 章的完整场景代码,使用公共外壳在本机 Edge 无头浏览器中分别以 1280×720、390×844 运行,共 28 次:未出现 JavaScript 或 Shader 编译错误,画布尺寸及销毁检查通过,并查看了光照、纹理与数据流示例截图。这里验证的是示例基本运行,不代表全部练习已实现,也不代表所有交互、设备和性能条件已覆盖。
项目 yarn build 通过;现有构建仍提示空入口 chunk 和大于 500 KiB 的 chunk,这些不由本次文档新增引起。文档中的代码块不会自动成为 Vite 构建入口,因此构建结果与示例运行检查分别记录。
资料与范围
接口核对来源:Vector3 源码、Matrix3 源码;安装依赖后本地源码链接才可用。
官方参考:Vector3、Matrix3、Euler、Quaternion。在线文档可能跟随新版本变化,本系列遇到差异以所标注的本地版本为准。
正文中的光照、碰撞和粒子模型用于讲清数学及常规实现;相关章节会说明与完整物理引擎、复杂渲染管线之间的边界。练习优先要求预测、验证、排错与组合应用,避免只记住接口名称。