当 Up 不是真正的 Up 时线性插值 Up 矢量?

Interpolate Up Vector Linearally when Up isn't truly Up?

概览

我已经四处寻找了一段时间,但没有找到答案,所以希望这里的社区可以帮助我。我正在重新设计我的观察相机(写于 2000 年之前)并且无法解决观察和向上矢量对齐导致相机疯狂旋转失控的问题。本来以为是云台锁,现在不太确定了

根据我对万向节锁的理解,当pitch与roll对齐时,pitch就变成了roll;从本质上讲,这就是它看起来的样子,但问题是变化率不应该仅仅因为轴对齐而增加,我应该得到一个平滑的滚动。相反,我得到了一个剧烈的滚动,我无法真正判断滚动的方向。


更新相机的位置

当用户移动鼠标时,我根据鼠标的 XY 坐标移动相机:

Vector2 mousePosition = new Vector2(e.X, e.Y);
Vector2 delta = mousePosition - mouseSave;
mouseSave = mousePosition;

ShiftOrbit(delta / moveSpeed);

ShiftOrbit 方法中,我根据与从上面的鼠标事件传递的 delta 相关的注视向量、右向量和上向量计算新位置:

Vector3 lookAt = Position - Target;
Vector3 right = Vector3.Normalize(Vector3.Cross(lookAt, Up));
Vector3 localUp = Vector3.Normalize(Vector3.Cross(right, lookAt));

Vector3 worldYaw = right * delta.X * lookAt.Length();
Vector3 worldPitch = localUp * delta.Y * lookAt.Length();
Position = Vector3.Normalize(Position + worldYaw + worldPitch) * Position.Length();

这可以正常工作,并以我选择的任何方向围绕其目标移动相机。


视图矩阵

这是我遇到上面概述中提到的问题的地方。由于我的数据在 ECR 坐标中,我的 Up 属性 之前设置为始终 0, 0, 1。但是,这就是当我四处移动相机并更新视图矩阵时导致轴对齐的原因。我使用 SharpDX 方法 Matrix.CreateLookAtRH(Position, Target, Up) 创建我的视图矩阵。

在发现创建视图矩阵时使用的 Up 向量应该更新而不是总是 0, 0, 1 之后,我遇到了另一个问题。当引入偏航和俯仰时,我现在引起了滚动。由于需要,这不应该发生,所以我立即开始寻求修复。

最初我进行了检查以查看是否接近轴对齐,如果是,然后我将用于创建我的视图矩阵的 Up 设置为相机的局部向上,如果我当时不是,我只使用局部向上的 Z 轴来确保向上或向下。

float dot = Math.Abs(Vector3.Dot(Up, Position) / (Up.Length() * Position.Length()));
if (dot > 0.98)
    Up = localUp;
else
    Up = new Vector3(0, 0, localUp.Z);

然而,这有点神经质,似乎仍然不太正确。经过一些尝试和错误,以及在网络上试图找到潜在解决方案的一些广泛研究,我想起了线性插值如何在一段时间内从一个值平滑地过渡到另一个值。然后我改为使用 Vector3.Lerp

float dot = Math.Abs(Vector3.Dot(Up, Position) / (Up.Length() * Position.Length()));
Up = Vector3.Lerp(new Vector3(0, 0, localUp.Z), localUp, dot);

这是非常平滑的,只有当我非常接近轴对齐时才会引起任何滚动,这不足以引起日常用户的注意。


问题

我的相机还可以附加到 0, 0, 0 以外的点,在这种情况下,相机的向上矢量设置为目标的标准化位置。如上所述使用 Vector3.Lerp 时,这会导致概述中的原始问题;因此,在我的相机连接到 0, 0, 0 以外的点的情况下,我改为执行以下操作:

Up = Vector3.Lerp(Vector3.Normalize(Target), localUp, dot);

然而,即使这样也行不通,我不知道如何让它这样做。我已经在这个问题上工作了几天,并且付出了巨大的努力来修复它,到目前为止这是一个很大的改进。


当 up 不等于 0, 0, z 时,如何防止使用 Vector3.Lerp 的剧烈旋转?

想象一个垂直平面绕垂直轴旋转 yaw (ϕ):

相机只允许平面或平面内旋转,它的平面方向由pitch (θ):

ϕθ 应随输入增量存储和递增。使用此设置,相机永远不会倾斜,并且始终可以计算局部向上方向:

du 分别是局部正面和向上方向,并且 总是 垂直(因此对齐不会成为问题)。 target当然可以取position+d

但是等等,有一个陷阱。


假设您将鼠标向右移动; ϕ 增加,您观察到:

  • 如果相机是直立的,视图会向右旋转。
  • 如果相机上下颠倒,视图会向左旋转。

理想情况下,无论垂直方向如何,这都应该是一致的。

解决方法是在相机倒置时将增量符号翻转为ϕ。一种方法是按 cos(θ) 缩放增量,这也会在 θ 接近 90 / 270 度时平滑地降低灵敏度,这样水平旋转方向就不会突然改变。