You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

场景图DFS遍历中支持忽略父节点旋转的相对变换矩阵计算问题

解决方案

核心思路

问题根源在于递归遍历未将父节点的变换拆分为旋转部分和非旋转(平移+缩放)部分,导致无法精准控制旋转继承逻辑。我们需要在DFS遍历中传递父节点的两类累积变换,让子节点根据自身开关选择是否继承旋转,同时保持纯递归、无全局缓存、支持任意节点启动的特性。

步骤1:拆分节点变换属性

将每个节点的局部变换拆分为独立的旋转矩阵和非旋转(平移+缩放)矩阵,同时添加旋转继承开关:

struct Node {
    Node* parent;
    std::vector<Node*> children;
    // 局部纯旋转矩阵
    Mat4 local_rot;
    // 局部平移+缩放矩阵(无旋转)
    Mat4 local_trans_scale;
    // 是否继承父节点旋转
    bool inherit_parent_rotation;
    // 其他渲染属性(如网格、材质)
};

如果原有代码使用完整的局部变换矩阵,可通过矩阵分解提取这两部分:

  • 平移向量:取矩阵的第四列(列主序)或第四行(行主序)
  • 缩放因子:提取前三列的长度
  • 旋转矩阵:将前三列归一化后组成正交矩阵(纯旋转)

步骤2:递归遍历函数实现

2.1 获取任意节点的基础累积变换

从目标节点向上递归父节点,计算其相对于根节点的旋转与非旋转累积变换:

void get_base_transforms(Node* node, Mat4& out_non_rot, Mat4& out_rot) {
    if (!node->parent) {
        // 根节点基础变换为单位矩阵
        out_non_rot = Mat4::identity();
        out_rot = Mat4::identity();
        return;
    }

    Mat4 parent_non_rot, parent_rot;
    get_base_transforms(node->parent, parent_non_rot, parent_rot);

    // 根据当前节点的继承开关计算自身累积变换
    if (node->inherit_parent_rotation) {
        out_rot = parent_rot * node->local_rot;
    } else {
        out_rot = node->local_rot;
    }
    // 平移缩放始终继承父节点
    out_non_rot = parent_non_rot * node->local_trans_scale;
}

2.2 从任意节点启动DFS遍历(转换到相机局部空间)

基于节点的基础累积变换,向下遍历子节点并计算每个节点的相机空间变换:

// 处理单个节点的相机空间变换
void process_node(Node* node, const Mat4& camera_space_transform) {
    // 这里添加渲染逻辑,如提交相机空间变换到渲染管线
}

// 递归遍历子节点
void traverse_children(Node* node, Mat4 parent_non_rot, Mat4 parent_rot, const Mat4& camera_inverse) {
    Mat4 current_rot, current_non_rot;
    // 计算当前节点的累积变换
    if (node->inherit_parent_rotation) {
        current_rot = parent_rot * node->local_rot;
    } else {
        current_rot = node->local_rot;
    }
    current_non_rot = parent_non_rot * node->local_trans_scale;

    // 转换到相机局部空间:相机逆变换 × 节点世界变换
    Mat4 world_transform = current_non_rot * current_rot;
    Mat4 camera_space = camera_inverse * world_transform;

    process_node(node, camera_space);

    // 递归处理子节点
    for (Node* child : node->children) {
        traverse_children(child, current_non_rot, current_rot, camera_inverse);
    }
}

// 启动遍历的入口函数
void start_traversal_from_node(Node* start_node, const Mat4& camera_inverse) {
    Mat4 start_non_rot, start_rot;
    get_base_transforms(start_node, start_non_rot, start_rot);

    // 处理启动节点自身
    Mat4 start_world = start_non_rot * start_rot;
    process_node(start_node, camera_inverse * start_world);

    // 遍历子节点
    for (Node* child : start_node->children) {
        traverse_children(child, start_non_rot, start_rot, camera_inverse);
    }
}

关键注意事项

  1. 矩阵乘法顺序:确保与你的图形API约定一致(行主序/列主序),如果是列主序,变换乘法顺序需调整为local_transform * parent_transform。
  2. 旋转矩阵正交性:分解局部变换时,要保证旋转矩阵是正交矩阵(行列式为1),避免缩放或剪切干扰旋转继承逻辑。
  3. 无全局缓存:所有变换计算均在递归过程中实时完成,无需存储全局空间变换,符合需求。

内容的提问来源于stack exchange,提问作者reneb86

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:44:54