14

在 Java 中进行 2D 游戏开发时,大多数教程都会创建一个缓冲策略来渲染。这很有意义。然而,人们似乎偏向于将实际图形绘制到缓冲区的方法。

一些教程创建一个缓冲图像,然后创建一个整数数组来表示各个像素颜色。

private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();

Graphics g = bs.getDrawGraphics();
g.setColor(new Color(0x556B2F));
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);

然而,其他一些教程不会创建缓冲图像,将像素绘制到 int 数组,而是使用 BufferStrategy 的 Graphics 组件将其图像直接绘制到缓冲区。

Graphics g = bs.getDrawGraphics();
g.setColor(new Color(0x556B2F));
g.fillRect(0, 0, getWidth(), getHeight());

g.drawImage(testImage.image, x*128, y*128, 128, 128, null);

我只是想知道,为什么要创建整个 int 数组,然后绘制它。这需要在实现矩形、拉伸、透明度等方面做更多的工作。缓冲区策略的图形组件已经有了可以轻松调用的方法。使用 int 数组是否有一些巨大的性能提升?

我已经查了好几个小时了,我看到的所有网站都只是解释了他们在做什么,而不是为什么他们选择这样做。

4

2 回答 2

5

让我们明确一件事:两个代码片段做的事情完全相同——绘制一个图像。然而,这些片段相当不完整 - 第二个片段没有显示“testImage.image”实际上是什么或它是如何创建的。但是它们最终都调用了 Graphics.drawImage() 并且 Graphics 或 Graphics2D 中 drawImage() 的所有变体都绘制了一个简单明了的图像。在第二种情况下,我们根本不知道它是 BufferedImage、VolatileImage 还是 Toolkit Image。

所以这里实际说明的绘图没有区别!

这两个片段之间只有一个区别——第一个片段获得了对最终在内部支持 Image 实例的整数数组的直接引用。这提供了对像素数据的直接访问,而不必通过使用例如相对较慢的 getRGB() 和 setRGB() 方法的 (Buffered)Image API。无法在上下文中具体说明的原因是在这个问题中,数组已获得但从未在片段中使用过。因此,为了给出以下解释存在的任何理由,我们必须假设有人想要直接读取或编辑图像的像素,很可能是出于优化原因,因为 (Buffered)Image API 的“缓慢”操纵数据。

这些优化原因可能是过早的优化,可能会适得其反。


首先,此代码之所以有效,是因为图像的类型是 INT_RGB,它将为图像提供一个 IntDataBuffer。如果它是另一种类型的图像,例如 3BYTE_BGR,则此代码将失败并出现 ClassCastException,因为支持数据缓冲区不是 IntDataBuffer。当您仅手动创建图像并强制执行特定类型时,这可能不是什么大问题,但图像往往是从外部工具创建的文件中加载的。

其次,直接访问像素缓冲区还有另一个更大的缺点:当您这样做时,Java2D 将拒绝该图像的加速,因为它不知道您何时将对其进行超出其控制的更改。只是为了清楚起见:加速是将未更改的图像保留在视频内存中的过程,而不是每次绘制时都从系统内存中复制它。根据您使用的图像数量,这可能会带来巨大的性能提升(如果您破坏它,则会造成损失)。

如何使用 Java2D 创建硬件加速映像?

(正如相关问题向您展示的那样:您应该使用 GraphicsConfiguration.createCompatibleImage() 来构造 BufferedImage 实例)。

所以本质上:尝试对所有内容使用 Java2D API,不要直接访问缓冲区。这个非现场资源很好地说明了 API 必须支持您的哪些功能,而无需进入低级别:

http://www.pushing-pixels.org/2008/06/06/effective-java2d.html

于 2015-06-30T12:58:44.367 回答
5

首先,历史因素很多。早期的 API 非常基础,所以做任何重要的事情的唯一方法就是实现所有必需的原语。

原始数据访问有点过时,我们可以尝试做一些“考古”来找出使用这种方法的原因。我认为主要有两个原因:

1.滤镜效果

让我们不要忘记滤镜效果(各种模糊等)很简单,对任何游戏开发人员都非常重要并且被广泛使用。

在此处输入图像描述

使用 Java 1 实现这种效果的最简单方法是使用int定义为矩阵的数组和过滤器。例如,Herbert Schildt 曾经有很多这样的演示:

public class Blur {

    public void convolve() {
        for (int y = 1; y < height - 1; y++) {
            for (int x = 1; x < width - 1; x++) {
                int rs = 0;
                int gs = 0;
                int bs = 0;
                for (int k = -1; k <= 1; k++) {
                    for (int j = -1; j <= 1; j++) {
                        int rgb = imgpixels[(y + k) * width + x + j];
                        int r = (rgb >> 16) & 0xff;
                        int g = (rgb >> 8) & 0xff;
                        int b = rgb & 0xff;
                        rs += r;
                        gs += g;
                        bs += b;
                    }
                }
                rs /= 9;
                gs /= 9;
                bs /= 9;
                newimgpixels[y * width + x] = (0xff000000
                        | rs << 16 | gs << 8 | bs);
            }
        }
    }
} 

当然,您可以使用 来实现getRGB,但原始数据访问更有效。后来,Graphics2D提供了更好的抽象层:

公共接口 BufferedImageOp

该接口描述了对 BufferedImage 对象执行的单输入/单输出操作。它由 AffineTransformOp、ConvolveOp、ColorConvertOp、RescaleOp 和 LookupOp 实现。这些对象可以传入一个 BufferedImageFilter 以对 ImageProducer-ImageFilter-ImageConsumer 范式中的 BufferedImage 进行操作。

2.双缓冲

另一个问题与闪烁和绘制速度非常慢有关。双缓冲消除了难看的闪烁,突然之间它提供了一种简单的方法来实现过滤效果,因为你已经有了缓冲。

在此处输入图像描述

类似于最终结论的东西:)

我想说你所描述的情况对于任何不断发展的技术来说都很常见。有两种方法可以实现相同的目标:

  • 使用遗留方法,更多代码等
  • 依赖新的抽象层、提供的技术等

还有一些有用的扩展可以进一步简化您的生活,因此无需使用int[]:)

于 2015-07-02T10:13:42.660 回答