V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Game Engines
Unreal Engine
MyCryENGINE
mirus
V2EX  ›  游戏开发

求教一个关于游戏中相机绕原点固定旋转的问题

  •  
  •   mirus · 217 天前 · 1620 次点击
    这是一个创建于 217 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我最近想用 Bevy 开发一个小游戏(不过考虑到这个游戏引擎比较小众,下面的讨论还是基于的 Unity 的左手坐标系),里面有一个地球,用户可以简单地通过移动鼠标来旋转地球(不过实际上你是在移动相机,使得地球看起来像是在旋转)。同时,这里还有几个隐含条件:

    • 地球是静止的,且球心是坐标系原点 (0, 0, 0),赤道面即 XZ 平面
    • 相机始终正对地球中心,且距离为已知变量 d
    • 为了确保相机视角不倾斜,相机的 local X 轴始终平行于赤道平面,即 XZ 平面

    一开始,我将地球自转速度和鼠标速度设置为成正比(就像在 Blender 里一样)。 换句话说,如果鼠标向右移动 x 个像素,那么地球就会从西向东旋转 k * x 弧度。 但后来我发现一个问题,如果我放大相机,然后移动地球,地球的自转速度看起来就会太快,因为相机离地球太近了。 所以我意识到这个解决方案不可行。

    然后我想到了另一个想法:使用拖动的方式而非线性旋转。 例如,我单击球面上的 A 点,然后将鼠标移动到视窗坐标( x ,y ),地球将跟随鼠标光标旋转,直到确保 A 点正好移动到视窗位置( x ,y )。 这有点像谷歌地球的做法,不过也存在一些细微差异,比如这里相机的 local X 轴是始终平行于 XZ 平面,这样可以确保答案的唯一性。

    不过后面我就没有具体的实现思路了。假如说我有一个来自空间中的坐标点 P ,我可以用 Unity 的 API Camera.WorldToScreenPoint 将其简单地转换为屏幕上的坐标。 但是目前似乎没有这样的正好符合我要求的 API ,而我自己昨天想了一天也没想到啥思路。所以过来想问下各位大佬有没有什么办法。

    7 条回复    2024-03-30 17:16:53 +08:00
    antonius
        1
    antonius  
       217 天前
    “如果鼠标向右移动 x 个像素,那么地球就会从西向东旋转 k * x 弧度。”
    “如果我放大相机,然后移动地球,地球的自转速度看起来就会太快”

    如果看起来快,那么为何不把相机与观察目标的距离 S 作为参数?让每一帧的旋转弧度和距离 S 成反比?

    观察矩阵的计算和变换,具体相关的实现可以参考: https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
    LaTero
        2
    LaTero  
       217 天前 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 很简单,自己弄一个吧。
    LaTero
        3
    LaTero  
       217 天前 via Android
    大体思路:用 raycast 分别找到上一帧和当前帧光标对应的点 P1, P2 ( world space ),那现在就是要求一个旋转矩阵 T ,使 T*P1 = P2 。新的 View 矩阵就是 View * T ,主要 T 在右边,因为它是在 world space 中旋转 P1 到 P2 得出来的。
    mirus
        4
    mirus  
    OP
       217 天前
    @antonius 不这样做主要是因为后面感觉采取类 Google Earth 的这种拖动式方案更加符合直觉一点,我是希望当放大到一定程度,即相机中的画面接近平面地图的程度时,用户能够采取一种像是移动端拖动图片/2D 地图的体验进行旋转地球。而如果采用的线性旋转方案,虽说在近处能达到类似拖动的效果,但在这种情况下再拉远相机,将地球整个纳入相机,而旋转逻辑不变的话,用户这时就不能使用近处时的操作逻辑进行旋转了(因为这时拖动地球中心和边缘所引起的转动弧度本应是不同的),感觉相对比较割裂。
    mirus
        5
    mirus  
    OP
       217 天前
    @LaTero 首先感谢解答,这个思路应该可行。不过其次我还想再问一下,考虑到在旋转的过程中,我们对相机的朝向、以及其距原点的距离、相机当前帧的光标所对应的 ray 、一开始选定的球面(/世界)坐标都是已知的,这也就意味着,即使我们不获取前帧的数据,理论上也是可以求出唯一解的,这也是我一开始想到的思路,但却没有进一步的推导思路了。在游戏领域中(因为我主业是做前端的,对游戏开发相对不太熟悉),像这种“根据前一帧与当前帧的差值进行计算”和“根据起始状态和当前状态进行计算”这两种方案或者说思维方式,是前者应用更多吗?因为这里看起来前者的思路确实简洁明了,但是不清楚大量帧累计下是否会产生一定误差。
    LaTero
        6
    LaTero  
       217 天前 via Android
    @mirus 是可以,会更准确。把 P1 换成起始点会更好。用前一帧的好处是状态更少,不用保存起始数据,而鼠标坐标的 delta 可以直接从引擎获取。实际应用误差其实是不会很大的,除非是一次拖动会很久很久。“根据前一帧与当前帧的差值进行计算”和“根据起始状态和当前状态进行计算”我不敢说哪个多,但是用差值一般更简单。比如俯视角拖动画面,还有缩放时保持鼠标所指的点在屏幕上不动之类经常是这样做,大部分情况精度是够的。
    LaTero
        7
    LaTero  
       217 天前 via Android
    对了,保存起始点或是用上一帧还有个考虑的点,假如用户把鼠标拖出了地球的范围该怎么办。假如是要 reset 到拖动前的位置就要保存起始点了,但是一般的做法是停在上一帧不动。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2755 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 54ms · UTC 15:24 · PVG 23:24 · LAX 08:24 · JFK 11:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.