就像在谷歌地球中一样,我的游戏渲染一个球体,相机围绕它移动。球体的半径为 1,中心位于坐标原点。相机始终瞄准球体中心,其上矢量是 z 方向。或者用代码来说,我的视图矩阵是
lookAt(cam_position, vec3(0), vec3(0,0,1))
并且透视矩阵是普通矩阵。
现在我想要的是,当玩家使用鼠标滚轮时,相机应该 1)放大/缩小(即与球体的距离减少)和 2)光标下的球体上的点仍应位于相同的屏幕空间位置运动之后(最好是运动期间)。
我天真地尝试通过将相机沿着一条线移动到光标指向的点来实现这一点。但我很快发现,相机的“任何”线性移动似乎都无法满足该限制。我想强调的是,移动相机时,它始终瞄准球体中心,这就是为什么朝光标点直线移动是不够的。 这个确切的行为是在谷歌地球中实现的,所以这显然是可能的。但我不知道怎么办。我知道用数学术语来说,这可以表述为该点的屏幕空间解析坐标在运动变换下保持不变。但我怀疑写下这个方程有助于我解决这个问题。我什至不确定最终的相机路径会是什么样子:圆弧或完全不同的东西。理想情况下,这只是来自某个特殊起源的线性变换,但我不知道。
出于说明目的,这里的设置显示相机在一条直线上接近“轨道”,红点标记了一个示例点,我希望在屏幕上保持不变。再次注意,这条路径是不正确的,这只是我的尝试。
这是相机的结果视图。您可以在开始和结束时的同一屏幕空间位置看到红点,但在两者之间
不是。
作为一个正确的例子,邀请每个人在earth.google.com上使用鼠标滚轮。
或者,也可以从相机的角度来思考问题。我们可以将其放在原点,“向上”(在 2D 中以使事情变得更简单)查看圆。缩放它会使圆圈下降(更近)。为了保持一个点固定,它仍然需要位于同一条“线上”,需要旋转球体来纠正。也许这会有所帮助,但不确定。插图中较近的圆需要旋转约 45° 才能仍然匹配,但相机具有点不变性:
编辑:为了使事情更加具体,理想情况下我更喜欢解决方案采用矩阵变换的形式。我怀疑它可以这样表述,而不是明确地写下正确路径的方程式。但我不知道如何/是否可以解决这个问题,所以欢迎任何指点
phi
和
psi
以及距离 d
,您的相机位置 C
是(当然有多种选择): / cos phi * sin psi \
C = d * | cos phi * cos psi |
\ sin phi /
同样,相机的右、上、前矢量为:
/ -cos psi \
r = | sin psi |
\ 0 /
/ -cos phi * sin psi \
f = | -cos phi * cos psi |
\ -sin phi /
/ -sin phi * sin psi \
u = | -sin phi * cos psi |
\ cos phi /
到目前为止,一切都很好。我们现在可以借助这些向量对投影进行建模(假设您有一个标准的中心投影)。我们实际上并没有进行完整的投影,而是只进行了我们需要的部分。世界空间点
(x, y)
的视图空间坐标
p
是(跳过一些中间步骤,∘ 是点积): p ∘ r
x = ---------
p ∘ f + d
p ∘ u
y = ---------
p ∘ f + d
解决问题的基本思路是:当相机开始移动时,计算
x
和
y
。您希望相机从已知距离 d_start
过渡到已知距离 d_end
。其他坐标 phi
和 psi
仅在起始位置已知。理想情况下,您可以求解这些坐标的上述方程。然而,方程组似乎没有一个紧凑的封闭式解(至少我找不到)。因此,另一种方法是使用数值方法。由于无论如何你都是小步前进,所以像梯度下降这样的简单方法应该可以很好地工作。那么你具体可以做什么:Initialize by calculating x and y and store them in x_obj, y_obj
Store the current phi and psi coordinates
Animate d from d_start to d_end:
do
calculate the objective o = { x_obj - x, y_obj - y }^2
calculate the gradient ∇o w.r.t. phi and psi
if o < some threshold then exit loop
(phi, psi) -= step * ∇o
loop
Position the camera at (phi, psi, d)
Next animation step
要计算梯度,您可能需要考虑使用auto diff