16 · Vector3:从矩阵、欧拉角和颜色中提取数据

返回系列目录 · 上一章:变换与投影

提取数据是读取另一个对象已经存储的分量;变换是让输入向量经过一个映射。这两者经常混淆,例如 setFromEuler 得到三个角度,并不会得到物体朝向。本章所有接口覆盖接收者并返回 this,不修改参数对象。

setFromMatrixPosition(m)

参数是 Matrix4,读取 elements[12]、[13]、[14],即仿射矩阵最后一列的前三个数。平移矩阵 makeTranslation(3,4,5) 提取得 (3,4,5)。

从 object.matrix 读取通常得到相对父节点的位置;从 object.matrixWorld 读取得到对象局部原点的世界位置。它不更新矩阵、不遍历父节点,也不求几何体视觉中心。带复杂投影含义的任意矩阵不应把这三个元素直接解释成位置。

object.updateWorldMatrix(true, false);
const worldOrigin = new THREE.Vector3().setFromMatrixPosition(object.matrixWorld);
// 普通业务也可直接 object.getWorldPosition(worldOrigin)。

如果模型顶点整体偏离原点,worldOrigin 可能落在模型外;需要视觉中心时应计算世界包围盒中心。

setFromMatrixScale(m)

参数是 Matrix4,计算其左上 3×3 的三个列向量长度,得到 (sx,sy,sz)。纯 TRS 且无负缩放时,旋转不改变列长度,因此能得到缩放幅度。

const m = new THREE.Matrix4().makeScale(-2, 3, 4);
const scale = new THREE.Vector3().setFromMatrixScale(m); // (2,3,4)

长度不会保留符号,所以无法据此恢复负缩放或镜像轴。含剪切矩阵时,列长度是变换后基向量长度,不是唯一的独立缩放分解;旋转子节点叠加非均匀缩放父节点就可能出现这种情况。若只是要模型世界包围盒尺寸,使用 Box3.getSize,不要把提取的 scale 当作实际尺寸。

setFromMatrixColumn(m, index)

从 Matrix4 的第 index 列读取前三个数,索引从 0 开始:0、1、2 常表示变换后的 X、Y、Z 基向量,3 对仿射矩阵表示平移。它读取的向量不自动归一化,也不一定彼此垂直。

const m = new THREE.Matrix4().makeScale(2, 3, 4);
const yBasis = new THREE.Vector3().setFromMatrixColumn(m, 1); // (0,3,0)

实现从 index*4 处读数组,没有像 getComponent 那样检查索引,因此应传 0~3 的整数。若取相机的世界前方,应注意相机朝局部 -Z:取第 2 列归一化后需要取反;普通模型的“前面”由建模约定决定。

setFromMatrix3Column(m, index)

从 Matrix3 第 index 列读取三个数,偏移是 index*3。合法列为 0~2,实现不额外验证索引。对于矩阵:

M=[123456789] M=\begin{bmatrix}1&2&3\\4&5&6\\7&8&9\end{bmatrix}

读取第 1 列得到 (2,5,8)。Matrix3.set() 参数按行书写,但内部 elements 按列存储,不能据参数排列猜数组偏移。对二维齐次变换,第三列通常包含二维平移和齐次分量,此时不应把它解释为三维空间的 Z 方向。

setFromEuler(e)

读取 Euler 的 x、y、z 三个角度(r185 内部读取其 _x/_y/_z),忽略旋转顺序,得到普通 Vector3。new THREE.Euler(0,Math.PI/2,0,'YXZ') 提取得 (0,π/2,0)。

这个结果的分量单位是弧度,不是方向余弦,更不是前向轴。若想求旋转后的朝向,应从模型默认前向,例如 (0,0,1) 出发调用 applyEuler 或 applyQuaternion。存档旋转时只保存这个向量会丢失 order,应保存 Euler 的完整表示或使用 Quaternion。

setFromColor©

从 THREE.Color 的 r、g、b 拷贝到 x、y、z。例如 new THREE.Color().setRGB(0.2,0.4,0.6) 得向量 (0.2,0.4,0.6)。它只是复制已存储值,不执行色彩空间转换,不提供 alpha,也不自动把数值乘 255。

Three.js 默认色彩管理下,Color 一般以线性工作色彩空间保存数据;从 CSS 或十六进制颜色创建 Color 时可能已发生输入转换。因此 new THREE.Color('#808080') 的内部通道通常不是直接的 128/255。向 shader 传颜色时优先使用 Color uniform 表达语义,确需通用 vec3 数据才转换,避免把线性值再次当 sRGB 解码。

实践:显示设备的世界朝向与真实尺寸

import * as THREE from 'three';

const origin = new THREE.Vector3();
const forward = new THREE.Vector3();
const size = new THREE.Vector3();
const bounds = new THREE.Box3();

function inspectDevice(object) {
  object.updateWorldMatrix(true, true);
  origin.setFromMatrixPosition(object.matrixWorld);
  // 这里约定设备局部 +Z 为前方;变换后的轴包含线性变换影响。
  forward.setFromMatrixColumn(object.matrixWorld, 2);
  if (forward.lengthSq() > 1e-12) forward.normalize();
  bounds.setFromObject(object).getSize(size);
  return {
    origin: origin.toArray(),
    forward: forward.toArray(),
    worldAabbSize: size.toArray()
  };
}

返回数组是为了避免调用方持有临时向量后被下一次调用覆盖。包围盒是世界轴对齐盒,设备旋转后尺寸可能变化;这不表示物体被缩放,而是包围盒对世界轴取了更大的覆盖范围。零缩放会让轴方向退化,不能仅 normalize 后假定方向始终有效。

七章接口阅读检查

到此 Vector3 的构造、三个分量、isVector3、全部公开方法与迭代器均已分别讲解。选择接口时可按四个问题检查:它返回数还是向量?是否改原对象?输入属于哪个空间?输入是否要求单位长度?这些条件比记住链式调用顺序更重要。

练习(暂不附答案)

  1. 创建带平移、旋转和缩放的父子层级,分别从 matrix 与 matrixWorld 提取位置并解释差别。
  2. 给模型施加负缩放,比较 object.scale 和 setFromMatrixScale 的结果,说明为什么不能据此恢复原 TRS。
  3. 用一个 Euler 分别执行 setFromEuler 和 applyEuler,说明为什么只有后者能用于获得旋转后的前向量。
  4. 比较 CSS 灰色创建的 Color 与 setRGB 创建的同数值颜色,读取分量并查清涉及的输入色彩空间。