You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

手机四元数数据映射到Vulkan坐标系及旋转行为一致性问题求助

手机四元数数据映射到Vulkan坐标系及旋转行为一致性问题求助

嘿,我完全懂你这种挠头的感觉——姿态映射尤其是跨坐标系的,加上自定义握持方式,简直是玄学现场!我之前做过类似的手持设备到3D模型的姿态同步,踩过不少坑,咱们一步步来解决你的问题:

1. 先搞定初始姿态校准,让旋转有统一的“基准线”

你现在的代码里用了glm::inverse(mockOriginData) * q,但mockOriginData是单位四元数,等于没做任何校准。这就导致手机的旋转是相对于它的出厂初始姿态,而不是你握成剑的姿态,自然会出现“不同方向旋转行为不一致”的问题。

解决方法很简单:

  • 加一个校准逻辑:比如程序启动后,让用户把手机摆成你想要的剑的初始姿态(右手握、屏幕朝左的标准握法),然后按下一个键(或者自动检测稳定姿态),记录此时的手机四元数作为calibrationQuat
  • 之后所有的姿态计算都基于这个校准值:glm::quat relativeRot = glm::inverse(calibrationQuat) * q,这个relativeRot就是手机相对于初始握剑姿态的旋转,这样旋转就有了统一的参考系,不会出现“地面/墙面旋转行为不同”的问题。

2. 修正握持姿态的坐标系映射

因为你握手机的方式不是标准的“屏幕朝前”,所以手机的传感器坐标系和剑的模型坐标系天生不对齐。比如:

  • 手机传感器默认坐标系:X轴向右(屏幕横向)、Y轴向上(屏幕纵向)、Z轴向前(垂直屏幕向外)
  • 你的剑模型坐标系:假设初始时剑刃朝Z轴正方向、握柄朝Y轴负方向、剑脊朝X轴正方向

而你握手机时,屏幕朝左手,此时手机的Z轴指向左手方向,X轴指向你的前方,这就需要一个固定的旋转转换,把手机的坐标系映射到剑的坐标系。

你可以用glm构建一个转换四元数,示例代码如下(你需要根据实际模型姿态微调欧拉角):

// 比如绕Y轴转90度,再绕Z轴转90度,把手机的轴对应到剑的轴
glm::quat phoneToSword = glm::quat(glm::vec3(0.0f, glm::radians(90.0f), glm::radians(90.0f)));
phoneToSword = glm::normalize(phoneToSword);

然后把校准后的相对旋转和这个转换四元数结合:

glm::quat finalRot = phoneToSword * relativeRot;
ubo.model = glm::mat4_cast(finalRot);

3. 确认四元数的分量顺序!

这是很多人踩过的坑:不同的手机传感器SDK输出的四元数分量顺序可能不一样,有的是w,x,y,z,有的是x,y,z,w,甚至有的会调换x/y/z的顺序。你现在的代码里是glm::quat(qw, qy, qx, qz),这个一定要和手机端输出的顺序对应上!如果顺序错了,旋转肯定会乱跳。

4. 调试小技巧

  • 可以在程序里把最终四元数转换成欧拉角(用glm::eulerAngles(finalRot)),然后在屏幕上显示出来,对比手机的实际旋转,看哪个轴的旋转不对,再调整转换四元数的参数。
  • 先固定剑的初始姿态,校准后慢慢做小幅度旋转,比如先绕垂直地面的轴转,再绕平行地面的轴转,逐步验证每个轴的旋转是否和手机一致。

调整后的完整代码示例

// 全局变量,存储校准四元数和校准状态
glm::quat calibrationQuat(1, 0, 0, 0);
bool isCalibrated = false;

// 校准函数,在用户触发时调用(比如按键回调)
void calibrateSwordPose() {
    motionPacket packetData = *getRecentQuaternionData();
    // 这里要确保和update里的四元数分量顺序一致
    calibrationQuat = glm::quat(packetData.orientData.qw, packetData.orientData.qy, packetData.orientData.qx, packetData.orientData.qz);
    calibrationQuat = glm::normalize(calibrationQuat);
    isCalibrated = true;
}

void updateUniformBuffer(uint32_t currentImage) {
    motionPacket packetData = *getRecentQuaternionData();
    glm::quat q(packetData.orientData.qw, packetData.orientData.qy, packetData.orientData.qx, packetData.orientData.qz);
    q = glm::normalize(q);

    glm::quat finalRot = q;
    if (isCalibrated) {
        // 计算相对于校准姿态的旋转
        glm::quat relativeRot = glm::inverse(calibrationQuat) * q;
        // 手机到剑的坐标系转换,根据实际情况调整欧拉角
        glm::quat phoneToSword = glm::quat(glm::vec3(0.0f, glm::radians(90.0f), glm::radians(90.0f)));
        phoneToSword = glm::normalize(phoneToSword);
        // 组合旋转:先应用相对校准的旋转,再应用坐标系转换(注意glm四元数是右乘,顺序要对)
        finalRot = phoneToSword * relativeRot;
    }

    UniformBufferObject ubo{};
    ubo.model = glm::mat4_cast(finalRot);
    // 保持你原来的视图和投影矩阵
    ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
    ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 10.0f);
    ubo.proj[1][1] *= -1;

    memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
}

按照这个思路调整,应该就能解决你说的旋转不一致、行为 erratic 的问题啦!

内容来源于stack exchange

火山引擎 最新活动