9

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

在阅读了其他人关于如何实现恒定游戏速度的几篇文章、教程和代码之后,我认为我目前已经实现的(我将在下面发布代码)是 Koen Witters 所说的基于可变 FPS 的游戏速度,他的文章的第二个。

首先,通过我的搜索经验,有几个人可能有知识可以帮助解决这个问题,但不知道 GLUT 是什么,我将尝试解释(请随时纠正我)相关功能我对这个 OpenGL 工具包的问题。如果您知道 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)以来的毫秒数。这就是我们使用 GLUT 的计时器。我知道高分辨率计时器有更好的选择,但现在让我们继续使用这个。glutInitglutGet(GLUT_ELAPSED_TIME)

我认为这是关于 GLUT 如何渲染帧的足够信息,因此不了解它的人也可以提出这个问题,以便在他们喜欢它时尝试提供帮助。

当前实施:

现在,我不确定我是否正确实施了 Koen 提出的第二个解决方案,即Game Speed 取决于 Variable 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 调用计时器回调 every TIMER_MILLISECONDS,并不意味着它总是会准时这样做。]

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

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

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

我如何才能在可变 FPS 的情况下获得恒定的游戏速度?更准确地说,我如何使用 GLUT 正确实现 Koen 的恒定游戏速度和最大 FPS解决方案(他的文章中的第四个)?也许这对 GLUT 来说根本不可能?如果没有,我的替代方案是什么?使用 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()每次场景发生变化时我都需要继续调用它,并一直调用它,直到没有新的东西可以重绘。我认为,为了更好的原因,为代码添加了一点复杂性。

你怎么看?

4

3 回答 3

1

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

于 2011-03-18T20:51:03.060 回答
0

例如,有一个鼠标驱动的旋转矩阵,它以固定的帧速率更新,与渲染帧速率无关。在我的程序中,空格键切换基准测试模式,并确定布尔 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 个对话。

于 2014-11-14T17:41:41.897 回答
0

您可以使用glutIdleFunc,尽可能连续调用它 - 类似于while(game_is_running)循环。也就是说,无论您将在该while循环中放入什么逻辑,您都可以放入 for 的回调中glutIdleFuncglutTimerFunc您可以通过自己跟踪刻度来避免使用,如您链接的文章中(使用glutGet(GLUT_ELAPSED_TIME))。

于 2011-03-18T20:06:07.523 回答