OpenGL 中的恒定游戏速度与 GLUT 中的可变 FPS 无关?
我一直在阅读 Koen Witters 有关不同游戏循环解决方案的详细文章,但我在实现时遇到了一些问题最后一个是 GLUT,这是推荐的。
在阅读了其他人关于如何实现恒定游戏速度的几篇文章、教程和代码后,我认为我目前已经实现的(我将在下面发布代码)就是 Koen Witters 所说的游戏速度依赖关于可变 FPS,这是他文章的第二篇文章。
首先,根据我的搜索经验,有一些人可能有知识可以帮助解决这个问题,但不知道什么是 GLUT,我将尝试解释(请随时纠正我)相关功能我的这个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)
函数返回自调用glutInit
(或首次调用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 来说这是根本不可能的?如果没有,我有什么选择? 解决这个问题(恒定游戏速度)的最佳方法是什么?
[编辑]另一种方法:
我一直在尝试,这就是我现在能够实现的目标。我现在不是在定时函数上计算经过的时间(这限制了我的游戏的帧速率),而是在 renderScene()
中进行计算。每当场景发生变化时,我都会调用 glutPostRedisplay() (即:相机移动、一些对象动画等),这将调用 renderScene()
。例如,我可以使用此函数中的经过时间来移动我的相机。
我的代码现在变成了这样:
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() ,并继续调用它,直到没有任何新内容可以重绘。我认为,为了更好的目的而在代码中添加一点复杂性。
你怎么认为?
I've been reading Koen Witters detailed article about different game loop solutions but I'm having some problems implementing the last one with GLUT, which is the recommended one.
After reading a couple of articles, tutorials and code from other people on how to achieve a constant game speed, I think that what I currently have implemented (I'll post the code below) is what Koen Witters called Game Speed dependent on Variable FPS, the second on his article.
First, through my searching experience, there's a couple of people that probably have the knowledge to help out on this but don't know what GLUT is and I'm going to try and explain (feel free to correct me) the relevant functions for my problem of this OpenGL toolkit. Skip this section if you know what GLUT is and how to play with it.
GLUT Toolkit:
- GLUT is an OpenGL toolkit and helps with common tasks in OpenGL.
- The
glutDisplayFunc(renderScene)
takes a pointer to arenderScene()
function callback, which will be responsible for rendering everything. TherenderScene()
function will only be called once after the callback registration. - The
glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0)
takes the number of milliseconds to pass before calling the callbackprocessAnimationTimer()
. The last argument is just a value to pass to the timer callback. TheprocessAnimationTimer()
will not be called eachTIMER_MILLISECONDS
but just once. - The
glutPostRedisplay()
function requests GLUT to render a new frame so we need call this every time we change something in the scene. - The
glutIdleFunc(renderScene)
could be used to register a callback torenderScene()
(this does not makeglutDisplayFunc()
irrelevant) but this function should be avoided because the idle callback is continuously called when events are not being received, increasing the CPU load. - The
glutGet(GLUT_ELAPSED_TIME)
function returns the number of milliseconds sinceglutInit
was called (or first call toglutGet(GLUT_ELAPSED_TIME)
). That's the timer we have with GLUT. I know there are better alternatives for high resolution timers, but let's keep with this one for now.
I think this is enough information on how GLUT renders frames so people that didn't know about it could also pitch in this question to try and help if they fell like it.
Current Implementation:
Now, I'm not sure I have correctly implemented the second solution proposed by Koen, Game Speed dependent on Variable FPS. The relevant code for that goes like this:
#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();
}
This implementation doesn't fell right. It works in the sense that helps the game speed to be constant dependent on the FPS. So that moving from point A to point B takes the same time no matter the high/low framerate. However, I believe I'm limiting the game framerate with this approach. [EDIT: Each frame will only be rendered when the time callback is called, that means the framerate will be roughly around TICKS_PER_SECOND
frames per second. This doesn't feel right, you shouldn't limit your powerful hardware, it's wrong. It's my understanding though, that I still need to calculate the elapsedTime
. Just because I'm telling GLUT to call the timer callback every TIMER_MILLISECONDS
, it doesn't mean it will always do that on time.]
I'm not sure how can I fix this and to be completely honest, I have no idea what is the game loop in GLUT, you know, the while( game_is_running )
loop in Koen's article. [EDIT: It's my understanding that GLUT is event-driven and that game loop starts when I call glutMainLoop()
(which never returns), yes?]
I thought I could register an idle callback with glutIdleFunc()
and use that as replacement of glutTimerFunc()
, only rendering when necessary (instead of all the time as usual) but when I tested this with an empty callback (like void gameLoop() {}
) and it was basically doing nothing, only a black screen, the CPU spiked to 25% and remained there until I killed the game and it went back to normal. So I don't think that's the path to follow.
Using glutTimerFunc()
is definitely not a good approach to perform all movements/animations based on that, as I'm limiting my game to a constant FPS, not cool. Or maybe I'm using it wrong and my implementation is not right?
How exactly can I have a constant game speed with variable FPS? More exactly, how do I correctly implement Koen's Constant Game Speed with Maximum FPS solution (the fourth one on his article) with GLUT? Maybe this is not possible at all with GLUT? If not, what are my alternatives? What is the best approach to this problem (constant game speed) with GLUT?
[EDIT] Another Approach:
I've been experimenting and here's what I was able to achieve now. Instead of calculating the elapsed time on a timed function (which limits my game's framerate) I'm now doing it in renderScene()
. Whenever changes to the scene happen I call glutPostRedisplay()
(ie: camera moving, some object animation, etc...) which will make a call to renderScene()
. I can use the elapsed time in this function to move my camera for instance.
My code has now turned into this:
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();
}
Conclusion, it's working, or so it seems. If I don't move the camera, the CPU usage is low, nothing is being rendered (for testing purposes I only have a grid extending for 4000.0f, while zFar is set to 1000.0f). When I start moving the camera the scene starts redrawing itself. If I keep pressing the move keys, the CPU usage will increase; this is normal behavior. It drops back when I stop moving.
Unless I'm missing something, it seems like a good approach for now. I did find this interesting article on iDevGames and this implementation is probably affected by the problem described on that article. What's your thoughts on that?
Please note that I'm just doing this for fun, I have no intentions of creating some game to distribute or something like that, not in the near future at least. If I did, I would probably go with something else besides GLUT. But since I'm using GLUT, and other than the problem described on iDevGames, do you think this latest implementation is sufficient for GLUT? The only real issue I can think of right now is that I'll need to keep calling glutPostRedisplay()
every time the scene changes something and keep calling it until there's nothing new to redraw. A little complexity added to the code for a better cause, I think.
What do you think?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
glut 被设计成游戏循环。当您调用 glutMainLoop() 时,它会执行一个“for 循环”,除了 exit() 信号外,没有终止条件。您可以像现在一样实现您的程序,但您需要一些细微的更改。首先,如果您想知道 FPS 是多少,您应该将该跟踪放入 renderScene() 函数中,而不是放在更新函数中。当然,您的更新函数会按照计时器指定的速度被调用,并且您将 elapsedTime 视为帧之间时间的度量。一般来说,这是正确的,因为您调用 glutPostRedisplay 的速度相当慢,并且 glut 在不需要时不会尝试更新屏幕(如果场景没有更改,则无需重绘)。然而,也有其他时候会调用 renderScene。例如,如果您将某些内容拖过窗口。如果您这样做,您会看到更高的 FPS(如果您在渲染函数中正确跟踪 FPS)。
glut is designed to be the game loop. When you call glutMainLoop(), it executes a 'for loop' with no termination condition except the exit() signal. You can implement your program kind of like you're doing now, but you need some minor changes. First, if you want to know what the FPS is, you should put that tracking into the renderScene() function, not in your update function. Naturally, your update function is being called as fast as specified by the timer and you're treating elapsedTime as a measure of time between frames. In general, that will be true because you're calling glutPostRedisplay rather slowly and glut won't try to update the screen if it doesn't need to (there's no need to redraw if the scene hasn't changed). However, there are other times that renderScene will be called. For example, if you drag something across the window. If you did that, you'd see a higher FPS (if you were properly tracking the FPS in the render function).
您可以使用 glutIdleFunc,它会尽可能连续调用,类似于 while(game_is_running) 循环。也就是说,无论您要放入
while
循环中的任何逻辑,都可以放入glutIdleFunc
的回调中。您可以通过自行跟踪刻度来避免使用 glutTimerFunc,如您链接的文章中所示(使用 glutGet(GLUT_ELAPSED_TIME) )。You could use
glutIdleFunc
, which is called continuously whenever possible--similar to thewhile(game_is_running)
loop. That is, whatever logic you would otherwise put into thatwhile
loop, you could put into the callback forglutIdleFunc
. You can avoid usingglutTimerFunc
by keeping track of the ticks on your own, as in the article you linked (usingglutGet(GLUT_ELAPSED_TIME)
).例如,有一个鼠标驱动的旋转矩阵,该矩阵以固定帧速率更新,与渲染帧速率无关。在我的程序中,空格键切换基准测试模式,并确定布尔 fxFPS。
拖动时松开鼠标按钮,您可以“抛出”由该矩阵变换的对象。
如果 fxFPS 为 true,则渲染帧速率将限制为动画帧速率;否则,即使没有足够的毫秒数来触发任何动画,也会重复绘制相同的帧以进行基准测试。
如果您正在考虑减慢和加快帧速度,则必须仔细考虑每种情况下是指渲染帧还是动画帧。在此示例中,简单动画的渲染限制与动画加速相结合,适用于在可能较慢的动画中可能丢帧的任何情况。
为了加速动画,循环重复执行旋转。与使用自适应旋转角度进行触发的选项相比,这样的循环并不算太慢;只是要小心你在任何循环中放入的内容,实际上执行时间越长,FPS 越低。对于它所考虑的每个帧丢失,此循环所需的时间远远少于额外的帧,因此它相当安全。
然后……
享受绕轴扔东西;我发现大多数人都是这样。请注意,fps 对界面或渲染没有任何影响。我已经最大限度地减少了除法的使用,因此比较应该是良好且准确的,并且时钟中的任何不准确都不会不必要地累积。
我认为,多人游戏的同步是另外 18 个对话。
Have, as an example, a mouse-driven rotation matrix that updates at a fixed frame-rate, independently of the rendering frame-rate. In my program, space-bar toggles benchmarking mode, and determines the Boolean fxFPS.
Let go of the mouse button while dragging, and you can 'throw' an object transformed by this matrix.
If fxFPS is true then the rendering frame-rate is throttled to the animation frame-rate; otherwise identical frames are drawn repeatedly for benchmarking, even though not enough milliseconds will have passed to trigger any animation.
If you're thinking about slowing down AND speeding up frames, you have to think carefully about whether you mean rendering or animation frames in each case. In this example, render throttling for simple animations is combined with animation acceleration, for any cases when frames might be dropped in a potentially slow animation.
To accelerate the animation, rotations are performed repeatedly in a loop. Such a loop is not too slow compared with the option of doing trig with an adaptive rotation angle; just be careful what you put inside any loop that actually takes longer to execute, the lower the FPS. This loop takes far less than an extra frame to complete, for each frame-drop that it accounts for, so it's reasonably safe.
And then later...
Enjoy throwing things around an axis; I find that most people do. Notice that the fps affects nothing whatsoever, in the interface or the rendering. I've minimised the use of divisions, so comparisons should be nice and accurate, and any inaccuracy in the clock does not accumulate unnecessarily.
Syncing of multiplayer games is another 18 conversations, I would judge.