【three.js教程】Three.js 矩阵变换:position/quaternion/scale 和 matrix 到底怎么配合

Three.js 矩阵变换:position/quaternion/scale 和 matrix 到底怎么配合

原文出处:Three.js Manual -- Matrix transformations

本文基于官方手册「Matrix transformations」章节整理,用通俗方式带你看懂:Three.js 怎么用一个 4×4 矩阵存下位置/旋转/缩放、两种改变换的方式分别怎么用、对象矩阵和世界矩阵差在哪,以及为什么旋转永远用四元数存。

写在前面

你在 Three.js 里让一个物体移动、旋转、缩放,本质上都是在操作它的矩阵(matrix) 。每个 Object3D 身上都有一块矩阵,把「位置、旋转、缩放」这三件事编码成一个 4×4 数学对象。这篇就讲清楚:这矩阵从哪来、怎么改、对象矩阵和世界矩阵有什么区别、以及旋转为什么老跟 quaternion 绑在一起。

顺带呼应一下前面「运行时更新」那篇:matrixAutoUpdate 这套开关,本篇会讲得更底层。

一、矩阵是什么:一个 4×4 存下三大变换

Three.js 用矩阵来编码三维变换------平移(position)、旋转、缩放。每个 Object3D 实例都有个 matrix,存的就是这个对象的位置、旋转和缩放。

更新一个对象的变换,有两条路可走。

二、方式一:改便捷属性,让引擎重算矩阵(最常用)

绝大多数情况你都用这套:直接改 position、quaternion、scale 这几个「便捷属性」,让 three.js 自己从它们重算出矩阵。

js 复制代码
object.position.copy( start_position );
object.quaternion.copy( quaternion );

默认情况下,matrixAutoUpdate 是 true,矩阵会被自动重新计算。

但如果你确定对象是静态的,或者想自己掌控「什么时候重算」,把它关掉能获得更好的性能:

js 复制代码
object.matrixAutoUpdate = false;

关掉之后,改完属性要手动刷新矩阵:

js 复制代码
object.updateMatrix();

这正是「运行时更新」那篇提过的套路:静态对象关掉自动更新,只在需要时 updateMatrix() 一次,省掉每帧的矩阵运算。

三、方式二:直接改 matrix(进阶)

如果你更习惯直接操作矩阵本身,Matrix4 类提供了一堆方法。比如从四元数造旋转、再设位置:

js 复制代码
object.matrix.makeRotationFromQuaternion( quaternion );
object.matrix.setPosition( start_position );
object.matrixAutoUpdate = false;
  1. matrixAutoUpdate 必须 设为 false------否则引擎每帧又从 position/scale 重算,把你手改的矩阵覆盖掉;
  2. 千万别调 updateMatrix() 。一旦调用,它会从 position、scale 等属性重新算矩阵,把你手动做的修改全冲掉。

换句话说:方式一和方式二互斥------要么让引擎从属性算矩阵,要么你自己改矩阵并关掉自动重算,二选一。

四、对象矩阵 vs 世界矩阵

这是新手最容易混的概念:

  • 对象的 matrix :存的是相对于父级 的变换。如果一个物体挂在 scene 下,它的父级是场景,那 matrix 基本就是「世界里的样子」;但如果它挂在另一个会动的物体(比如一个 Group)下,它的 matrix 只是「相对那个父物体」的偏移。
  • 世界矩阵(world matrix) :要拿到对象在世界坐标下的真正变换,得访问它的世界矩阵。

当父级或子级的变换发生变化时,可以调用下面这个方法,让子对象的世界矩阵刷新:

js 复制代码
object.updateMatrixWorld();

另外,可以通过 applyMatrix4() 直接用一个 Matrix4 去变换对象。但要注意一个坑:这个方法底层依赖 Matrix4.decompose()(把矩阵分解回 position/quaternion/scale),并不是所有矩阵都能这么分解 。例如:当对象有一个非均匀缩放(non-uniform scale)的父级 时,它的世界矩阵可能无法分解,applyMatrix4() 就不适用了。

划重点:非均匀缩放(scale 三个分量不相等,比如 x=2、y=1、z=1)会"污染"子节点的世界矩阵分解,做矩阵运算时要特别小心。

五、Rotation 与 Quaternion:为什么旋转永远用四元数

Three.js 表示三维旋转有两套:

  • 欧拉角(Euler angles) :直观,但有个著名毛病叫万向节死锁(gimbal lock)------某些角度配置下会丢失一个旋转自由度,导致物体绕某一轴转不动。
  • 四元数(Quaternion):没有万向节死锁问题。

所以 Three.js 的规则是:对象的旋转永远存在它的 quaternion 里,欧拉角只是个方便你输入的界面。

早版本里有个 useQuaternion 属性:设成 false 就让矩阵从欧拉角算。这个做法已经被废弃。现在想从欧拉角设旋转,用这个方法,它会同步更新四元数:

js 复制代码
object.setRotationFromEuler( euler );

记忆法:你可以用欧拉角「下达指令」,但引擎内部始终以四元数为准存储和执行。

总结

矩阵变换速查:

主题 要点 代码/开关
存储 每个 Object3D 用 4×4 matrix 存 position/rotation/scale ---
方式一 改 position/quaternion/scale,引擎重算矩阵 matrixAutoUpdate=true(默认),静态可设 false 后手动 updateMatrix()
方式二 直接改 matrix matrixAutoUpdate=false 且绝不调 updateMatrix()
对象 vs 世界 matrix 相对父级;世界变换要刷世界矩阵 updateMatrixWorld()
旋转 永远存于 quaternion,欧拉角只是输入界面 用 setRotationFromEuler() 替代废弃的 useQuaternion

延伸阅读

相关推荐
外收内放21 分钟前
Python基础语法练习题(69)
开发语言·python
lojox25 分钟前
深入理解 C++ STL:vector 底层实现、迭代器失效与 list 特性
开发语言·c++
iotxiaohu27 分钟前
用 cflow 查看 C 函数调用关系
c语言·开发语言·cflow
zhang-ge28 分钟前
python包和项目管理器uv在ubuntu下的安装与使用
开发语言·python·uv
微笑星河q1 小时前
C++类和对象(下)初始化列表、类型转换、static、友元、内部类、匿名对象
开发语言·c++
凤年徐1 小时前
【Python】Python 语法速通指南
开发语言·python
quantdash_cc1 小时前
量化数据清洗应该放在数据层还是策略层?从系统架构看清职责边界
开发语言·python·数据分析·系统架构·量化交易·股票数据·quantdash
|_⊙2 小时前
C++ 类和对象零散知识点
开发语言·c++
xiangyun612 小时前
C++ list 模拟实现(二):const迭代器与反向迭代器,模板的黑魔法
开发语言·数据库·c++·算法·stl·list
DLite2 小时前
NLang 0.9.0发布:成为可嵌入语言
开发语言·编程语言·编译技术·nlang