世界空间到局部空间 World to Local Space

整理从节点世界位移和世界旋转缩放矩阵还原局部变换的思路与伪代码。

以下是基于已知每个节点的世界位移 worldPosVec3)和世界旋转+缩放矩阵 worldRSMat3),递归或遍历求出每个节点局部变换(平移+旋转/缩放)的思路与伪代码。


原理概述

  1. 旋转+缩放 局部旋转+缩放矩阵 localRS 等于父节点世界旋转+缩放矩阵的逆与自身世界旋转+缩放矩阵相乘:

    localRS = inverse(parent.worldRS) * node.worldRS

    此处 inverse(parent.worldRS) 是父节点的旋转+缩放矩阵求逆 (stackoverflow.com)

  2. 平移 先计算相对位移向量 delta = node.worldPos - parent.worldPos,再将其从世界空间“变回”父节点局部空间:

    localPos = inverse(parent.worldRS) * delta

    这等价于引擎中的“Inverse Transform Location”操作 (forums.unrealengine.com)

  3. 根节点处理 若某节点无父节点,则其局部变换即等同于世界变换:

    localRS = worldRS;
    localPos = worldPos;

伪代码

struct Node {
    Vec3   worldPos;   // 世界位移
    Mat3   worldRS;    // 世界旋转+缩放矩阵
    Mat3   localRS;    // 输出:局部旋转+缩放矩阵
    Vec3   localPos;   // 输出:局部平移
    Node*  parent;     // 指向父节点(nullptr 表示根)
    // … 其他成员 …
};
 
// 递归或遍历调用此函数
void computeLocalTransforms(Node* node) {
    if (node->parent) {
        // 1. 计算父的世界RS逆矩阵
        Mat3 invParentRS = inverse(node->parent->worldRS);
        
        // 2. 计算局部旋转+缩放
        node->localRS = invParentRS * node->worldRS;  
        //    local RS = M_parent^{-1} * M_world       
 
        // 3. 计算局部平移
        Vec3 delta = node->worldPos - node->parent->worldPos;
        node->localPos = invParentRS * delta;       
        //    local Pos = M_parent^{-1} * (P_world - P_parent) 
    }
    else {
        // 根节点:局部即世界
        node->localRS  = node->worldRS;
        node->localPos = node->worldPos;
    }
 
    // 4. 递归处理子节点(假设有 children 列表)
    for (Node* child : node->children) {
        computeLocalTransforms(child);
    }
}

复杂度与数值稳定性

  • 矩阵求逆:3×3 矩阵求逆开销较小,可直接用通用算法或库函数(如 Eigen 的 .inverse())。
  • 数值稳定性:若 worldRS 中包含非均匀缩放或反射,逆矩阵仍然有效;要注意极端缩放可能导致条件数变大,从而放大舍入误差。

以上方法在绝大多数引擎和数学库中均是标准做法,明确地将子节点的世界空间表示“变换”回其父节点的局部空间。