【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,存的就是这个对象的位置、旋转和缩放。

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

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

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

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

默认情况下,matrixAutoUpdatetrue,矩阵会被自动重新计算

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

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() 。一旦调用,它会从 positionscale 等属性重新算矩阵,把你手动做的修改全冲掉。

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

四、对象矩阵 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

延伸阅读

相关推荐
纪念 22917 分钟前
数据结构排序(四)
开发语言·数据结构
IvanCodes20 分钟前
Python 基础语法(四):循环结构与流程控制
开发语言·python
OPEN-F20 分钟前
C++工程化:编译链接、调试与性能优化
开发语言·c++
坐吃山猪34 分钟前
【多线程】什么时候必须/推荐使用Thread类
java·开发语言
一只小阿乐43 分钟前
java 快速上手开发 2
java·开发语言·前端
张宇Joaquin1 小时前
灵渠测试工具-urma_perftest
java·开发语言
nicole bai1 小时前
dialog封装
前端·javascript·vue.js
高彬1 小时前
SAP ECC6.0 对接 C# web Services服务
开发语言·前端·c#·sap
张小姐的猫1 小时前
【C++开发脚手架】 —— Jsoncpp上手
java·linux·开发语言·网络·c++·tcp/ip·udp