如何将传感器数据应用到Three.js模型实现简易动作捕捉
动作捕捉骨骼旋转映射问题解决方案
你的思路完全正确——Three.js中骨骼的quaternion属性存储的是相对于其父骨骼的局部坐标系旋转,而传感器输出的是世界坐标系下的绝对旋转,直接赋值必然导致运动错位。以下是具体的转换步骤和实现细节:
核心转换逻辑
要将传感器的世界旋转转换为骨骼的局部旋转,需要基于「校准姿态」和「骨骼绑定姿态」的对应关系,计算旋转增量后再转换到局部坐标系:
初始化阶段记录基准数据
- 保存每个传感器在校准完成时的世界四元数(记为
calibSensorQuat),这是传感器的"初始姿态基准"。 - 保存对应骨骼在绑定姿态(T-Pose或校准姿态)下的世界四元数(通过
bone.getWorldQuaternion(new THREE.Quaternion())获取,记为bindBoneWorldQuat),这是骨骼的"初始姿态基准"。
- 保存每个传感器在校准完成时的世界四元数(记为
每一帧的旋转转换
对于任意一帧的传感器世界旋转currentSensorQuat:- 计算传感器相对于校准姿态的相对旋转增量:
这个增量代表传感器从校准姿态到当前姿态的旋转变化。const sensorRelativeQuat = calibSensorQuat.clone().invert().multiply(currentSensorQuat); - 计算骨骼当前应该达到的世界旋转目标:
即骨骼初始姿态叠加传感器的旋转增量,得到世界坐标系下的目标姿态。const targetBoneWorldQuat = bindBoneWorldQuat.clone().multiply(sensorRelativeQuat); - 将世界旋转转换为相对于父骨骼的局部旋转并赋值:
const parentCurrentWorldQuat = new THREE.Quaternion(); bone.parent.getWorldQuaternion(parentCurrentWorldQuat); bone.quaternion.copy(parentCurrentWorldQuat.invert().multiply(targetBoneWorldQuat));
- 计算传感器相对于校准姿态的相对旋转增量:
代码示例(以胸部骨骼为例)
// 初始化:记录基准数据 const calibChestSensorQuat = sensorData.chest.calib.clone(); // 校准完成时的胸部传感器世界四元数 const bindChestBoneWorldQuat = new THREE.Quaternion(); chestBone.getWorldQuaternion(bindChestBoneWorldQuat); // 绑定姿态下胸部骨骼的世界四元数 // 每一帧更新骨骼 function updateChestBone(currentSensorQuat) { // 1. 计算传感器相对校准姿态的旋转增量 const sensorRel = calibChestSensorQuat.clone().invert().multiply(currentSensorQuat); // 2. 计算骨骼的目标世界旋转 const targetWorldQuat = bindChestBoneWorldQuat.clone().multiply(sensorRel); // 3. 转换为局部旋转赋值 const parentWorldQuat = new THREE.Quaternion(); chestBone.parent.getWorldQuaternion(parentWorldQuat); chestBone.quaternion.copy(parentWorldQuat.invert().multiply(targetWorldQuat)); }
关键注意事项
- 四元数乘法顺序:Three.js中
a.multiply(b)表示先应用b的旋转,再应用a的旋转,不要搞反顺序,否则旋转方向会完全错误。 - 坐标系严格对齐:确保Blender导出模型时Y轴向上,且骨骼的初始绑定姿态与传感器校准姿态的轴向完全一致(比如胸部骨骼Y轴向上、X向右、Z向前)。
- 根骨骼特殊处理:如果骨骼是根骨骼(父对象是场景),父骨骼的世界旋转是单位四元数,此时可以简化为直接将
targetBoneWorldQuat赋值给骨骼的quaternion(因为局部旋转等于世界旋转)。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

