【问题标题】:Rendered images flicker although 120 fps渲染图像闪烁,尽管 120 fps
【发布时间】:2014-07-30 13:01:32
【问题描述】:

我的游戏引擎正在 JFrame 内的 Canvas 上绘制平铺地图。我正在使用 Bufferstrategy 来获得更平滑的渲染,并试图在程序内部的几个点上提高性能并最终得到 ca.每秒 120 帧以用黑色填充 1920 * 1080 窗口并在顶部绘制一些等距的 512 * 256 瓷砖。移动地图时,图块和 之间会出现小黑线。我假设它们发生是因为一些图块已经移动到新位置,但是当图像放在屏幕上时还没有完成。但我真的没有解决方案,我也不确定我是否正确。
我可能应该提到我在我的程序中使用了两个线程,一个线程调用更新和渲染方法,另一个线程当前只是获取用户输入以移动相机位置。此外,在某些 lauches 中一切正常,而在另一些中则闪烁非常糟糕。

下面是窗口创建和渲染的一些代码:

private final void createWindow(){
    frame = new JFrame(title);
    frame.setMinimumSize(size);
    frame.setMaximumSize(size);
    frame.setPreferredSize(size);

    display = new Canvas();
    display.setMinimumSize(size);
    display.setMaximumSize(size);
    display.setPreferredSize(size);

    image = ImageLoader.boostPerformance(new BufferedImage((int)size.getWidth(), (int)size.getHeight(), BufferedImage.TYPE_INT_RGB));

    graphics = (Graphics2D) image.getGraphics();

    frame.add(display);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.requestFocusInWindow();
}

public final void renderToScreen(){
    BufferStrategy bs = display.getBufferStrategy();
    if(bs==null){
        display.createBufferStrategy(bufferCount);
        return;
    }

    graphicsToScreen = (Graphics2D) bs.getDrawGraphics();

    graphicsToScreen.drawImage(image, 0, 0, display.getWidth(), display.getHeight(), null);
    graphicsToScreen.dispose();
    bs.show();
}

我使用其他类中的 Graphics2D 图形对象来绘制图块。

public void render(Graphics2D g) {
    g.setColor(Color.black);
    g.fillRect(0, 0, getWidth(), getHeight());

    tilemap.render(g);

    g.setColor(Color.red);
    g.drawString(String.valueOf(Main.actualFps), 30, 30);

}

上面是使用我提到的图形对象的另一个类中的渲染方法。 在这里你可以看到瓦片地图的渲染方法:

private final void renderIsometric(Graphics2D g){

    for(int x = 0; x < 4; x++){
        for(int y = 3; y >= 0; y--){
            if(x > tilesX - 1|| y > tilesY - 1)break;
            if(x < 0 || y < 0)continue;
            if(map[x][y] == null)continue;

            g.drawImage(map[x][y].getImage(), (int)orthoToIso(x, y).x - cameraPosX, (int)orthoToIso(x, y).y - cameraPosY, null);
        }
    }
}

最后这里是确保 fps 计数器正常工作并且 120 fps 的值正确的主循环:

while(running){
        long now = System.nanoTime();
        double ns = 1000000000.0 / preferredUps;
        delta += (now - lastTime) / ns;
        lastTime = now;
        while(delta >= 1){
            //call the update method 
            update();
            updates++;
            delta--;
        }
        //call the render method 
        render();
        frames++;

        //refresh the actualUps and actualFps once every second
        if(System.currentTimeMillis() - timer > 1000){
            timer+=1000;
            actualUps = updates;
            actualFps = frames;
            updates = 0;
            frames = 0;
        }
    }

如何摆脱这些微小的滞后和闪烁问题?此外,我一直想知道通过 LWJGL 而不是 Java2D 使用 OpenGL 是否会产生更好的结果?


感谢您的帮助!

【问题讨论】:

    标签: java performance rendering


    【解决方案1】:

    你看到的东西很可能不会闪烁。它的撕裂编织工件。这是更新屏幕而不同步到屏幕刷新的垂直同步的自然结果。

    窗口系统通常不支持垂直同步(它们有利于性能,因为这些效果在普通 UI 中不易察觉或不重要)。据我所知,没有办法与 (J)Frame 同步(你可能会用 JNI 破解一些东西,要求操作系统告诉你光栅光束何时位于帧的顶部,但这无关紧要) .

    同样以 120fps 渲染实际上可能会使问题更糟,除非显示设备也以 120hz 刷新 - 当您渲染的帧数超过设备实际显示的帧数时强制设备显示多于一帧的图形(例如,屏幕上半部分的第 1 帧,下半部分的第 2 帧)。这总是会导致移动物体出现明显的伪影。

    在纯 Java 中有一种解决方法,那就是 全屏模式(这将等待垂直同步,因此您的渲染速率受限于并由显示设备)。

    你可以试试openGL,但我不知道它在窗口模式下渲染时是否会同步。

    改进的第一步是比显示设备的显示速度更快(例如,对于 60hz 的屏幕,最高 60 fps)。

    【讨论】:

    • 谢谢,全屏模式可以正常工作,它将 fps 限制为 60。但是,由于我有 2560*1080 的屏幕,因此全屏模式会缩放画布。谁能告诉我如何让屏幕两侧出现黑条,以使分辨率保持不变?
    • @leomoe 缩放来自您在 renderToScreen() 方法中的实现:graphicsToScreen.drawImage(image, 0, 0, display.getWidth(), display.getHeight(), null);。如果您使用 drawImage(image, x, y, null) 代替,您应该获得非缩放显示(计算 x/y 以使图像在屏幕上居中或 0,0 以将其放置在左上角)。
    • 哦,谢谢,我应该可以自己解决这个问题……但无论如何,非常感谢!
    • 在多次测试程序之后,我现在意识到这些工件仍然存在于 10 次运行中的 1 次。我想我对此无能为力了?
    • @loemoe 不好说,可能是依赖于底层平台,甚至是显卡硬件和驱动设置。但是,如果它有任何安慰的话,我看到 许多 商业游戏偶尔也无法在 Windows 和 PS3 上正确运行。大概这个问题困扰着所有平台,但我接触最多的两个平台。
    【解决方案2】:

    如果您的应用程序对时间要求不高,您可以使用双缓冲策略解决闪烁问题。

    本质上,您正在合成的图像被复制到辅助屏幕外图像缓冲区,该缓冲区不是在这一帧渲染,而是在下一帧渲染。您可以使用一些技巧让 Swing 组件自动为您提供双重缓冲,另请参阅:

    https://gamedev.stackexchange.com/questions/30921/thread-safe-double-buffering

    Double buffered image example in Jpanel

    双缓冲的缺点是您会引入一帧延迟,这意味着您的输入只会比没有双缓冲的情况下晚一帧导致可见的变化。在 120hz 时,这可能不是什么大问题,但只是为了完整性..

    【讨论】:

    • 我已经在使用 BufferStrategy。这是否意味着我正在使用 DoubleBuffering?
    • 如果 buffercount > 1 那么是的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-04
    • 2019-05-03
    • 2017-05-04
    • 2021-10-29
    • 2013-09-08
    相关资源
    最近更新 更多