恒定的游戏速度独立于OpenGL与GLUT中的可变FPS?

我一直在阅读Koen Witters关于不同游戏循环解决方案的详细文章 ,但是我在使用GLUT实现最后一个时遇到了一些问题,这是推荐的。

在阅读了几篇关于如何实现恒定游戏速度的文章,教程和代码之后,我认为我目前已实现的内容(我将在下面发布代码)是Koen Witters所谓的Game Speed依赖于可变FPS ,第二篇关于他的文章。

首先,通过我的搜索经验,有几个人可能有帮助,但不知道什么是GLUT,我将尝试解释(随意纠正我)相关的function我的这个OpenGL工具包的问题。 如果您知道GLUT是什么以及如何使用它,请跳过此部分。

GLUT工具包:

  • GLUT是一个OpenGL工具包,可以帮助完成OpenGL中的常见任务。
  • glutDisplayFunc(renderScene)接受一个指向renderScene()函数回调的指针,该回调将负责渲染所有内容。 renderScene()函数仅在回调注册后调用一次。
  • glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0)在调用回调processAnimationTimer()之前需要经过的毫秒数。 最后一个参数只是一个传递给计时器回调的值。 processAnimationTimer()不会在每个TIMER_MILLISECONDS而只需调用一次。
  • glutPostRedisplay()函数请求GLUT渲染一个新帧,所以每当我们改变场景中的某些东西时我们都需要调用它。
  • glutIdleFunc(renderScene)可用于向renderScene()注册回调(这不会使glutDisplayFunc()无关)但应避免使用此函数,因为在未接收到事件时会持续调用空闲回调,从而增加CPU负载。
  • glutGet(GLUT_ELAPSED_TIME)函数返回自调用glutGet(GLUT_ELAPSED_TIME)或首次调用glutGet(GLUT_ELAPSED_TIME) )以来的毫秒数。 那是GLUT的计时器。 我知道高分辨率计时器还有更好的选择,但是现在让我们继续使用这个计时器。

我认为这是关于GLUT如何渲染帧的足够信息,所以那些不了解它的人也可以在这个问题中投入试图帮助它们如果它们像它一样堕落。

目前的实施:

现在,我不确定我是否正确实施了Koen提出的第二种解决方案, 游戏速度依赖于可变FPS 。 相关代码如下:

 #define TICKS_PER_SECOND 30 #define MOVEMENT_SPEED 2.0f const int TIMER_MILLISECONDS = 1000 / TICKS_PER_SECOND; int previousTime; int currentTime; int elapsedTime; void renderScene(void) { (...) // Setup the camera position and looking point SceneCamera.LookAt(); // Do all drawing below... (...) } void processAnimationTimer(int value) { // setups the timer to be called again glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0); // Get the time when the previous frame was rendered previousTime = currentTime; // Get the current time (in milliseconds) and calculate the elapsed time currentTime = glutGet(GLUT_ELAPSED_TIME); elapsedTime = currentTime - previousTime; /* Multiply the camera direction vector by constant speed then by the elapsed time (in seconds) and then move the camera */ SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f)); // Requests to render a new frame (this will call my renderScene() once) glutPostRedisplay(); } void main(int argc, char **argv) { glutInit(&argc, argv); (...) glutDisplayFunc(renderScene); (...) // Setup the timer to be called one first time glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0); // Read the current time since glutInit was called currentTime = glutGet(GLUT_ELAPSED_TIME); glutMainLoop(); } 

这种实施方式并不合适。 它的工作原理是帮助游戏速度恒定依赖于FPS。 因此,无论高/低帧速率,从A点移动到B点都需要相同的时间。 但是,我相信我用这种方法限制游戏帧率。 [ 编辑:每个帧只会在调用时间回调时呈现,这意味着帧速率大致约为TICKS_PER_SECOND帧。 这感觉不对,你不应该限制强大的硬件,这是错误的。 但是我的理解是,我仍然需要计算elapsedTime 。 仅仅因为我告诉GLUT每个TIMER_MILLISECONDS调用定时器回调,这并不意味着它总会按时完成。

我不知道如何解决这个问题并且完全诚实,我不知道GLUT中的游戏循环是什么,你知道,Koen的文章中的while( game_is_running )循环。 [ 编辑:我的理解是GLUT是事件驱动的 ,当我调用glutMainLoop() (它永远不会返回)时,游戏循环开始,是吗?]

我以为我可以使用glutIdleFunc()注册一个空闲回调并使用它作为glutTimerFunc()替换,只在必要时呈现(而不是像往常一样)但是当我用空回调测试它时(如void gameLoop() {} )它基本上什么都不做,只有一个黑屏,CPU飙升到25%并保持在那里,直到我杀了游戏,它恢复正常。 所以我不认为这是要遵循的道路。

使用glutTimerFunc()绝对不是一个很好的方法来执行基于此的所有动作/动画,因为我将我的游戏限制为恒定的FPS,而不是很酷。 或者也许我使用它错了,我的实现不对?

如何通过可变FPS获得恒定的游戏速度? 更准确地说,我如何使用GLUT正确实施Koen的恒定游戏速度和最大FPS解决方案(他的文章中的第四个)? 也许这对GLUT来说根本不可能? 如果没有,我的替代方案是什么? 使用GLUT解决这个问题(恒定游戏速度)的最佳方法是什么?

[编辑]另一种方法:

我一直在尝试,这就是我现在能够实现的目标。 我没有计算定时函数的经过时间(限制了我游戏的帧速率),而是在renderScene() 。 每当发生场景变化时,我都会调用glutPostRedisplay() (即:相机移动,某些对象动画等等),这将调用renderScene() 。 我可以使用此function中的经过时间来移动我的相机。

我的代码现在变成了这样:

 int previousTime; int currentTime; int elapsedTime; void renderScene(void) { (...) // Setup the camera position and looking point SceneCamera.LookAt(); // Do all drawing below... (...) } void renderScene(void) { (...) // Get the time when the previous frame was rendered previousTime = currentTime; // Get the current time (in milliseconds) and calculate the elapsed time currentTime = glutGet(GLUT_ELAPSED_TIME); elapsedTime = currentTime - previousTime; /* Multiply the camera direction vector by constant speed then by the elapsed time (in seconds) and then move the camera */ SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f)); // Setup the camera position and looking point SceneCamera.LookAt(); // All drawing code goes inside this function drawCompleteScene(); glutSwapBuffers(); /* Redraw the frame ONLY if the user is moving the camera (similar code will be needed to redraw the frame for other events) */ if(!IsTupleEmpty(cameraDirection)) { glutPostRedisplay(); } } void main(int argc, char **argv) { glutInit(&argc, argv); (...) glutDisplayFunc(renderScene); (...) currentTime = glutGet(GLUT_ELAPSED_TIME); glutMainLoop(); } 

结论,它似乎正在发挥作用。 如果我不移动相机,CPU使用率很低,没有渲染任何内容(出于测试目的,我只有一个网格扩展为4000.0f,而zFar设置为1000.0f)。 当我开始移动相机时,场景开始重绘。 如果我一直按下移动键,CPU使用率会增加; 这是正常的行为。 当我停止移动时它会回落。

除非我遗漏了什么,否则现在似乎是一个好方法。 我确实在iDevGames上发现了这篇有趣的文章 ,这个实现可能会受到该文章中描述的问题的影响。 你对此有何看法?

请注意,我只是为了好玩,我无意创建一些游戏来分发或类似的东西,至少在不久的将来。 如果我这样做,除了GLUT之外,我可能会选择其他的东西。 但是因为我正在使用GLUT,而不是iDevGames上描述的问题,你认为这个最新的实现对于GLUT来说已经足够吗? 我现在能想到的唯一真正的问题是,每当场景发生变化时我都需要继续调用glutPostRedisplay()并继续调用它,直到没有什么新的重绘方式。 我认为,为了更好的原因,为代码增加了一点复杂性。

你怎么看?

过剩是设计成游戏循环。 当你调用glutMainLoop()时,它会执行’for循环’,除了exit()信号之外没有终止条件。 您可以像现在一样实现您的程序,但是您需要进行一些小的更改。 首先,如果您想知道FPS是什么,您应该将该跟踪放入renderScene()函数中,而不是放在更新函数中。 当然,您的更新函数的调用速度与计时器指定的一样快,您可以将elapsedTime视为帧之间时间的度量。 一般来说,这是真的,因为你正在调用glutPostRedisplay而且如果它不需要则不会尝试更新屏幕(如果场景没有改变则不需要重绘)。 但是,还有其他时候会调用renderScene。 例如,如果您在窗口中拖动某些内容。 如果你这样做,你会看到更高的FPS(如果你在渲染function中正确跟踪FPS)。

你可以使用glutIdleFunc ,它尽可能连续调用 – 类似于while(game_is_running)循环。 也就是说,无论你while循环中输入什么逻辑,你都可以放入glutIdleFunc的回调。 您可以通过跟踪自己的滴答来避免使用glutTimerFunc ,如您链接的文章(使用glutGet(GLUT_ELAPSED_TIME) )。

作为示例,具有鼠标驱动的旋转矩阵,其以固定的帧速率更新,与渲染帧速率无关。 在我的程序中,空格键切换基准测试模式,并确定布尔fxFPS。

拖动时松开鼠标按钮,您可以“抛出”由此矩阵转换的对象。

如果fxFPS为真,则渲染帧速率被限制为动画帧速率; 否则,重复绘制相同的帧以进行基准测试,即使没有足够的毫秒数来触发任何动画。

如果您正在考虑放慢速度并加快帧速度,那么您必须仔细考虑在每种情况下是否表示渲染或动画帧。 在此示例中,对于简单动画的渲染限制与动画加速相结合,适用于可能在可能较慢的动画中丢弃帧的任何情况。

为了加速动画,在循环中重复执行旋转。 与使用自适应旋转角度进行触发的选项相比,这样的循环不会太慢; 只要小心你放入任何实际需要更长时间执行的循环,FPS越低。 对于它所考虑的每个帧丢失,这个循环所需的额外帧要少得多,因此它是相当安全的。

 int xSt, ySt, xCr, yCr, msM = 0, msOld = 0; bool dragging = false, spin = false, moving = false; glm::mat4 mouseRot(1.0f), continRot(1.0f); float twoOvHght; // Set in reshape() glm::mat4 mouseRotate(bool slow) { glm::vec3 axis(twoOvHght * (yCr - ySt), twoOvHght * (xCr - xSt), 0); // Perpendicular to mouse motion float len = glm::length(axis); if (slow) { // Slow rotation; divide angle by mouse-delay in milliseconds; it is multiplied by frame delay to speed it up later int msP = msM - msOld; len /= (msP != 0 ? msP : 1); } if (len != 0) axis = glm::normalize(axis); else axis = glm::vec3(0.0f, 0.0f, 1.0f); return rotate(axis, cosf(len), sinf(len)); } void mouseMotion(int x, int y) { moving = (xCr != x) | (yCr != y); if (dragging & moving) { xSt = xCr; xCr = x; ySt = yCr; yCr = y; msOld = msM; msM = glutGet(GLUT_ELAPSED_TIME); mouseRot = mouseRotate(false) * mouseRot; } } void mouseButton(int button, int state, int x, int y) { if (button == 0) { if (state == 0) { dragging = true; moving = false; spin = false; xCr = x; yCr = y; msM = glutGet(GLUT_ELAPSED_TIME); glutPostRedisplay(); } else { dragging = false; spin = moving; if (spin) continRot = mouseRotate(true); } } } 

然后……

 bool fxFPS = false; int T = 0, ms = 0; const int fDel = 20; void display() { ms = glutGet(GLUT_ELAPSED_TIME); if (T <= ms) { T = ms + fDel; for (int lp = 0; lp < fDel; lp++) { orient = rotY * orient; orientCu = rotX * rotY * orientCu; // Auto-rotate two orientation quaternions if (spin) mouseRot = continRot * mouseRot; // Track rotation from thowing action by mouse } orient1 = glm::mat4_cast(orient); orient2 = glm::mat4_cast(orientCu); } // Top secret animation code that will make me rich goes here glutSwapBuffers(); if (spin | dragging) { if (fxFPS) while (glutGet(GLUT_ELAPSED_TIME) < T); glutPostRedisplay(); } // Fast, repeated updates of the screen } 

喜欢在轴上扔东西; 我发现大多数人都这样做。 请注意,fps在界面或渲染中不会影响任何内容。 我已经最大限度地减少了分割的使用,因此比较应该是美观和准确的,并且时钟中的任何不准确性都不会不必要地累积。

我认为,多人游戏的同步是另外18次对话。