我最近想用 Bevy 开发一个小游戏(不过考虑到这个游戏引擎比较小众,下面的讨论还是基于的 Unity 的左手坐标系),里面有一个地球,用户可以简单地通过移动鼠标来旋转地球(不过实际上你是在移动相机,使得地球看起来像是在旋转)。同时,这里还有几个隐含条件:
d
一开始,我将地球自转速度和鼠标速度设置为成正比(就像在 Blender 里一样)。 换句话说,如果鼠标向右移动 x 个像素,那么地球就会从西向东旋转 k * x 弧度。 但后来我发现一个问题,如果我放大相机,然后移动地球,地球的自转速度看起来就会太快,因为相机离地球太近了。 所以我意识到这个解决方案不可行。
然后我想到了另一个想法:使用拖动的方式而非线性旋转。 例如,我单击球面上的 A 点,然后将鼠标移动到视窗坐标( x ,y ),地球将跟随鼠标光标旋转,直到确保 A 点正好移动到视窗位置( x ,y )。 这有点像谷歌地球的做法,不过也存在一些细微差异,比如这里相机的 local X 轴是始终平行于 XZ 平面,这样可以确保答案的唯一性。
不过后面我就没有具体的实现思路了。假如说我有一个来自空间中的坐标点 P ,我可以用 Unity 的 API Camera.WorldToScreenPoint 将其简单地转换为屏幕上的坐标。 但是目前似乎没有这样的正好符合我要求的 API ,而我自己昨天想了一天也没想到啥思路。所以过来想问下各位大佬有没有什么办法。
1
antonius 243 天前
“如果鼠标向右移动 x 个像素,那么地球就会从西向东旋转 k * x 弧度。”
“如果我放大相机,然后移动地球,地球的自转速度看起来就会太快” 如果看起来快,那么为何不把相机与观察目标的距离 S 作为参数?让每一帧的旋转弧度和距离 S 成反比? 观察矩阵的计算和变换,具体相关的实现可以参考: https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/ |
2
LaTero 243 天前 via Android
上一次用 bevy 它还在 0.6 ,就帮你顺便翻了下文档
https://docs.rs/bevy/latest/bevy/prelude/struct.Camera.html#method.world_to_viewport 这是 world to screen (假设 render target 是全窗口) screen to world 要用 raycast ,球面的 raycast 很简单,自己弄一个吧。 |
3
LaTero 243 天前 via Android
大体思路:用 raycast 分别找到上一帧和当前帧光标对应的点 P1, P2 ( world space ),那现在就是要求一个旋转矩阵 T ,使 T*P1 = P2 。新的 View 矩阵就是 View * T ,主要 T 在右边,因为它是在 world space 中旋转 P1 到 P2 得出来的。
|
4
mirus OP @antonius 不这样做主要是因为后面感觉采取类 Google Earth 的这种拖动式方案更加符合直觉一点,我是希望当放大到一定程度,即相机中的画面接近平面地图的程度时,用户能够采取一种像是移动端拖动图片/2D 地图的体验进行旋转地球。而如果采用的线性旋转方案,虽说在近处能达到类似拖动的效果,但在这种情况下再拉远相机,将地球整个纳入相机,而旋转逻辑不变的话,用户这时就不能使用近处时的操作逻辑进行旋转了(因为这时拖动地球中心和边缘所引起的转动弧度本应是不同的),感觉相对比较割裂。
|
5
mirus OP @LaTero 首先感谢解答,这个思路应该可行。不过其次我还想再问一下,考虑到在旋转的过程中,我们对相机的朝向、以及其距原点的距离、相机当前帧的光标所对应的 ray 、一开始选定的球面(/世界)坐标都是已知的,这也就意味着,即使我们不获取前帧的数据,理论上也是可以求出唯一解的,这也是我一开始想到的思路,但却没有进一步的推导思路了。在游戏领域中(因为我主业是做前端的,对游戏开发相对不太熟悉),像这种“根据前一帧与当前帧的差值进行计算”和“根据起始状态和当前状态进行计算”这两种方案或者说思维方式,是前者应用更多吗?因为这里看起来前者的思路确实简洁明了,但是不清楚大量帧累计下是否会产生一定误差。
|
6
LaTero 243 天前 via Android
@mirus 是可以,会更准确。把 P1 换成起始点会更好。用前一帧的好处是状态更少,不用保存起始数据,而鼠标坐标的 delta 可以直接从引擎获取。实际应用误差其实是不会很大的,除非是一次拖动会很久很久。“根据前一帧与当前帧的差值进行计算”和“根据起始状态和当前状态进行计算”我不敢说哪个多,但是用差值一般更简单。比如俯视角拖动画面,还有缩放时保持鼠标所指的点在屏幕上不动之类经常是这样做,大部分情况精度是够的。
|
7
LaTero 243 天前 via Android
对了,保存起始点或是用上一帧还有个考虑的点,假如用户把鼠标拖出了地球的范围该怎么办。假如是要 reset 到拖动前的位置就要保存起始点了,但是一般的做法是停在上一帧不动。
|