如何通过鼠标输入实现相机的平滑移动



好吧,所以我已经试着弄清楚这一点好几年了(不开玩笑,我会一直做下去,直到我再也做不到为止,然后开始另一项任务,告诉自己我稍后会再做,这是一个永无止境的循环),但我想我终于收集到了足够多的关于正在发生的事情的信息,我可以详细地解释,有人可能会为我指明正确的方向。

问题是,在我一直在用c++/glfw/gley/opengl开发(业余项目)的这个三维框架/引擎中,我似乎无法实现基于鼠标输入的平滑相机旋转。我已经检查并确保禁用了windows内的鼠标加速,禁用了glfw内的光标,并启用了glfw_RAW_mouse_MOTION。没有任何东西(据我所知)应该扭曲正在获得的鼠标输入值。

以下是我生成的调试输出,显示:frame_time|current_mouse_x_position|mouse_delta_for_tick

0.010011: 5803.000000: 9.000000
0.010001: 5810.000000: 7.000000
0.010011: 5819.000000: 9.000000
0.010002: 5836.000000: 17.000000
0.010011: 5845.000000: 9.000000
0.010001: 5854.000000: 9.000000
0.010001: 5861.000000: 7.000000
0.010015: 5876.000000: 15.000000
0.010002: 5884.000000: 8.000000
0.010002: 5894.000000: 10.000000
0.010264: 5902.000000: 8.000000
0.010009: 5919.000000: 17.000000
0.010010: 5928.000000: 9.000000
0.010011: 5935.000000: 7.000000
0.010006: 5943.000000: 8.000000
0.010010: 5958.000000: 15.000000
0.010090: 5965.000000: 7.000000
0.010005: 5971.000000: 6.000000
0.010013: 5979.000000: 8.000000
0.010042: 5994.000000: 15.000000
0.010012: 6001.000000: 7.000000
0.010004: 6009.000000: 8.000000
0.010009: 6016.000000: 7.000000
0.010007: 6033.000000: 17.000000

这些数据是在我的手允许的情况下沿着x轴移动鼠标时收集的,你可以在mouse_delta_for_tick列中看到,每隔一段时间,这些值就会是应该值的两倍

我的第一个想法是,这可能是由于采样位置之间的时间量,但您可以在第一列中看到frame_time保持相对一致。所以我不确定是什么原因导致这些数值如此偏离

您可以想象,当使用这些值来计算相机旋转时,它会变得断断续续。以下是我每次更新的方式:

void Client::updateMouseDelta()
{
// this->input updated elsewhere as fast as engine can update it, so in this function it is
// the current mouse position for this tick
this->currentMousePosition.x = this->input.mouseXPos;
this->currentMousePosition.y = this->input.mouseYPos;
if (this->firstMouseInput)
{
this->lastMousePosition = this->currentMousePosition;
this->firstMouseInput = false;
}
//TODO mousedelta calculation and framerate or something causing noisy deltas??????idk????
this->mouseDelta.x = this->currentMousePosition.x - this->lastMousePosition.x;
this->mouseDelta.y = this->lastMousePosition.y - this->currentMousePosition.y;
this->lastMousePosition = this->currentMousePosition;
}
void Client::updatePitchYaw()
{
this->yaw += this->mouseDelta.x * this->mouseSensitivity;
this->pitch += this->mouseDelta.y * this->mouseSensitivity;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (this->pitch > 89.0f)
this->pitch = 89.0f;
if (this->pitch < -89.0f)
this->pitch = -89.0f;
}
void Client::updateRotation(float frameTime, float renderLerpInterval)
{
this->updateMouseDelta();
this->updatePitchYaw();
this->lookDirection.x = cos(glm::radians(this->yaw)) * cos(glm::radians(this->pitch));
this->lookDirection.y = sin(glm::radians(this->pitch));
this->lookDirection.z = sin(glm::radians(this->yaw)) * cos(glm::radians(this->pitch));
this->lookDirection = glm::normalize(this->lookDirection);
auto cameraPosition = this->ccc->getCapsuleActor()->getInterpolatedTranslation(renderLerpInterval);
this->worldCamera->setPosition(cameraPosition);
this->worldCamera->setLookAt(cameraPosition + this->lookDirection);
}

这些年来,我做了各种各样的事情,试图通过平滑输入来掩盖这种口吃,但它从来都不是完美的,而且总是在鼠标移动中引入明显的延迟(移动平均滤波器,转换为四元数,并在之前和当前旋转之间使用slerp,一个低通滤波器(尽管现在我想起来这可能是一个不同的无关问题))。

同样,为了再次确认这与鼠标输入无关,我使用箭头键进行了一个简单的旋转测试(非常平滑):

void Client::updatePitchYaw()
{
if (this->input.keyUp)
this->pitch += 0.75f;
if (this->input.keyDown)
this->pitch -= 0.75f;
if (this->input.keyRight)
this->yaw += 0.75f;
if (this->input.keyLeft)
this->yaw -= 0.75f;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (this->pitch > 89.0f)
this->pitch = 89.0f;
if (this->pitch < -89.0f)
this->pitch = -89.0f;
}

如果你已经做到了这一点,谢谢,我想我的主要问题是:我如何通过鼠标输入实现我玩过的其他游戏似乎都能实现的那种黄油般平滑的相机旋转(反击、震动3、半衰期等)?一定有什么东西我遗漏了。

编辑

添加主要的游戏循环代码和鼠标输入更新代码(循环的精简版本,但所有相关部分都应该存在),这应该会清楚我是如何获得鼠标位置值的:

/*
App::execute() invoked via main(), once invoked, this method controls the
application until it is terminated.
*/
void App::execute()
{
this->fixedLogicTime = 1 / this->config.LOGIC_TICK; // LOGIC_TICK = 120.0
this->currentTime = this->time();
while (true)
{
if (this->shouldClose || this->window->shouldClose())
break;
this->newTime = this->time();
this->frameTime = this->newTime - this->currentTime;

// UPDATE MOUSE INPUT HERE. THIS METHOD CALLS THE Window::setMouse() method
// which is included below this one
this->window->updateInputState();

if (this->frameTime >= (1 / this->config.MAX_RENDER_FPS)) // cap max fps
{
this->currentTime = this->newTime;              
if (this->frameTime > 0.25)
this->frameTime = 0.25;
this->accumulator += this->frameTime;
while (this->accumulator >= this->fixedLogicTime)
{
this->activeScene->applyTransformations();
this->activeScene->processSensors();
this->activeScene->innerLoop((float)this->fixedLogicTime); // fixed logic updates
this->activeScene->updateAnimations(this->fixedLogicTime);
this->activeScene->stepPhysics((float)this->fixedLogicTime);
this->activeScene->postPhysics((float)this->fixedLogicTime);
this->accumulator -= this->fixedLogicTime;
}

float renderLerpInterval = (float)(this->accumulator / this->fixedLogicTime);

// THIS METHOD WOULD BE CALLING THE METHODS WHERE THE MOUSE INPUT VALUES ARE USED
// IE, MY PREVIOUSLY POSTED Client::updateRotation() METHOD
this->activeScene->outerLoop((float)this->frameTime, renderLerpInterval); // immediate logic updates

this->gpu->clearBuffers(0.0f, 0.0f, 0.0f, 1.0f);
this->activeScene->draw(renderLerpInterval);
}
}
}
void Window::setMouse() 
{
double mXPos;
double mYPos;
glfwGetCursorPos(this->glfwWindow, &mXPos, &mYPos);
this->inputState.mouseXPos = (float)mXPos;
this->inputState.mouseYPos = (float)mYPos;
}

您可以通过按键获得平滑的旋转,因为您可以从上一个/实际值中添加(减去)固定值,并将旋转增加(减少)固定量。每一帧都显示一个固定角度的旋转(平滑)。

如果您想获得与按键相同的结果,请使用相同的方法。

将屏幕划分为多个单元格(单元格大小由缩放因子决定,例如180度需要多少次按键,这等于屏幕宽度或鼠标从左到右的移动)。根据鼠标位置或移动的距离,计算指针所在的单元格或移动的单元格数量,这将为您提供所需的旋转角度(按固定的量,就像按键一样)。

因此,鼠标指针的微小变化不会产生任何影响,只有当移动的距离大于单元格大小时,才会发生旋转(移动的单元格乘以缩放因子)。

好吧。我终于想明白了,它是如此简单,以至于我几乎不好意思发布这个答案,但也许它会帮助一些人。。。

是老鼠:(

我有多台工作机器。最近我一直在用我的笔记本电脑和这个鼠标,这就是我再次注意到口吃的地方。然而,在我得出与代码相关的结论之前,我在我的立式桌面设置上测试了完全相同的构建,它也使用了一个与上面类似但不完全相同的hp鼠标,问题仍然存在。这是我的确认,有些事情是不对的,因为它发生在两个系统上。

今天晚上,我以为这与性能有关,就跳上了我的主桌面(5900x、rtx3070等),它使用了一个razer枪头鼠标。。。旋转非常平稳。然后我抓了两个hp鼠标,在这个系统上试了一下,你不知道吗。。。这一团糟。

我愿意接受这个问题,但我在上一次主要桌面构建中使用了hp鼠标多年,玩了无数游戏,从未注意到任何这种口吃,我的意思是,这是一款1000dpi的鼠标,应该足够灵敏。我想知道windows 10 usb鼠标驱动程序是否有什么变化?这就是我所能想到的,因为razer鼠标有自己的专有驱动程序。

这一切都让我想知道有多少人在使用这些廉价的老鼠并经历过这种情况,但却不知道这是一个问题。

您的鼠标输入不正确。也许你想用这样的另一个程序来仔细检查一下你是否得到了更严重的价值

<html>
<body>
<form name="Form1">
<textarea  type="text" name="posx" />
</textarea>
</form>
<script type="text/javascript">
var start = Date.now();
document.onmousemove = function (event)
{       
if (Date.now() - start<10) return;
start = Date.now();    
document.Form1.posx.value += start + "t"+ event.clientX +"n" ;    
}
</script>
</body>
</html>

最新更新