求教一个关于游戏中相机绕原点固定旋转的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Game Engines
Unreal Engine
MyCryENGINE
mirus
V2EX    游戏开发

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

  •  
  •   mirus 2024-03-3 13:26:55 +08:00 2289 次点击
    这是一个创建于 638 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我最近想用 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  
       2024-03-30 15:46:15 +08:00
    “如果鼠标向右移动 x 个像素,那么地球就会从西向东旋转 k * x 弧度。”
    “如果我放大相机,然后移动地球,地球的自转速度看起来就会太快”

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

    观察矩阵的计算和变换,具体相关的实现可以参考: https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
    LaTero
        2
    LaTero  
       2024-03-30 16:04:30 +08:00 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  
       2024-03-30 16:22:47 +08:00 via Android
    大体思路:用 raycast 分别找到上一帧和当前帧光标对应的点 P1, P2 ( world space ),那现在就是要求一个旋转矩阵 T ,使 T*P1 = P2 。新的 View 矩阵就是 View * T ,主要 T 在右边,因为它是在 world space 中旋转 P1 到 P2 得出来的。
    mirus
        4
    mirus  
    OP
       2024-03-30 16:39:50 +08:00
    @antonius 不这样做主要是因为后面感觉采取类 Google Earth 的这种拖动式方案更加符合直觉一点,我是希望当放大到一定程度,即相机中的画面接近平面地图的程度时,用户能够采取一种像是移动端拖动图片/2D 地图的体验进行旋转地球。而如果采用的线性旋转方案,虽说在近处能达到类似拖动的效果,但在这种情况下再拉远相机,将地球整个纳入相机,而旋转逻辑不变的话,用户这时就不能使用近处时的操作逻辑进行旋转了(因为这时拖动地球中心和边缘所引起的转动弧度本应是不同的),感觉相对比较割裂。
    mirus
        5
    mirus  
    OP
       2024-03-30 17:00:56 +08:00
    @LaTero 首先感谢解答,这个思路应该可行。不过其次我还想再问一下,考虑到在旋转的过程中,我们对相机的朝向、以及其距原点的距离、相机当前帧的光标所对应的 ray 、一开始选定的球面(/世界)坐标都是已知的,这也就意味着,即使我们不获取前帧的数据,理论上也是可以求出唯一解的,这也是我一开始想到的思路,但却没有进一步的推导思路了。在游戏领域中(因为我主业是做前端的,对游戏开发相对不太熟悉),像这种“根据前一帧与当前帧的差值进行计算”和“根据起始状态和当前状态进行计算”这两种方案或者说思维方式,是前者应用更多吗?因为这里看起来前者的思路确实简洁明了,但是不清楚大量帧累计下是否会产生一定误差。
    LaTero
        6
    LaTero  
       2024-03-30 17:13:14 +08:00 via Android
    @mirus 是可以,会更准确。把 P1 换成起始点会更好。用前一帧的好处是状态更少,不用保存起始数据,而鼠标坐标的 delta 可以直接从引擎获取。实际应用误差其实是不会很大的,除非是一次拖动会很久很久。“根据前一帧与当前帧的差值进行计算”和“根据起始状态和当前状态进行计算”我不敢说哪个多,但是用差值一般更简单。比如俯视角拖动画面,还有缩放时保持鼠标所指的点在屏幕上不动之类经常是这样做,大部分情况精度是够的。
    LaTero
        7
    LaTero  
       2024-03-30 17:16:53 +08:00 via Android
    对了,保存起始点或是用上一帧还有个考虑的点,假如用户把鼠标拖出了地球的范围该怎么办。假如是要 reset 到拖动前的位置就要保存起始点了,但是一般的做法是停在上一帧不动。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5811 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 02:07 PVG 10:07 LAX 18:07 JFK 21:07
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86