Java中的2D动态照明

时间:2014-01-06 21:36:06

标签: java 2d lighting

我正在制作一个有篝火对象的游戏。我想做的是照亮每个篝火周围的圆圈中的所有像素。然而,循环遍历每个像素并改变半径内的那些像素并不是那么有效并且使游戏以~7 fps运行。关于如何使这个过程有效或以不同方式模拟光的想法?

我还没有编写火灾代码,但这是检查每个像素/根据数字改变亮度的基本循环:

public static BufferedImage updateLightLevels(BufferedImage img, float light)
{
    BufferedImage brightnessBuffer = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);

    brightnessBuffer.getGraphics().drawImage(img, 0, 0, null);

    for(int i = 0; i < brightnessBuffer.getWidth(); i++)
    {
        for(int a = 0; a < brightnessBuffer.getHeight(); a++)
        {
            //get the color at the pixel
            int rgb = brightnessBuffer.getRGB(i, a);

            //check to see if it is transparent
            int alpha = (rgb >> 24) & 0x000000FF;

            if(alpha != 0)
            {
                //make a new color
                Color rgbColor = new Color(rgb);

                //turn it into an hsb color
                float[] hsbCol = Color.RGBtoHSB(rgbColor.getRed(), rgbColor.getGreen(), rgbColor.getBlue(), null);

                //lower it by the certain amount
                //if the pixel is already darker then push it all the way to black
                if(hsbCol[2] <= light)
                    hsbCol[2] -= (hsbCol[2]) - .01f;
                else
                    hsbCol[2] -= light;

                //turn the hsb color into a rgb color
                int rgbNew = Color.HSBtoRGB(hsbCol[0], hsbCol[1], hsbCol[2]);

                //set the pixel to the new color
                brightnessBuffer.setRGB(i, a, rgbNew);
            }


        }
    }

    return brightnessBuffer;
}

如果我的代码不干净,我道歉,我是自学成才。

2 个答案:

答案 0 :(得分:3)

我可以给你很多方法。

您当前正在渲染CPU,并且您正在检查每个像素。这是顽固的蛮力,蛮力不是CPU最擅长的。它有效,但正如你所见,表现非常糟糕。

我会指出两个方向可以大大提高你的表现:

方法1 - 剔除。每个像素是否真的需要计算其光照?如果您可以改为计算一般的“环境光”,那么您可以在该环境光中绘制大部分像素,然后仅计算最接近光的像素的真实照明;所以灯光会产生“斑点”效应,逐渐消失在环境中。这样,您一次只能对屏幕的几个像素(每个灯光周围的圆圈区域)执行检查。您发布的代码看起来像是描绘了每个像素,我没有看到甚至应用“圆圈”衰减的位置。

编辑:

相反,扫过灯光,然后循环通过灯光位置的局部偏移。

for(Light l : Lights){

for(int x = l.getX() -LIGHT_DISTANCE, x< l.getX() + LIGHT_DISTANCE, y++){

for(int y = l.getY() - LIGHT_DISTANCE, y < l.getY() + LIGHT_DISTANCE, y++){

//calculate light
int rgb = brightnessBuffer.getRGB(x, y);
//do stuff
}

}

您可能想要使用该方法添加一个检查,因此重叠的灯光不会导致一堆重新检查,除非您想要这种行为(理想情况下这些像素的亮度是两倍)

方法2 - 对GPU的随意计算。我们有显卡的原因;它们是专门为能够在你真正需要蛮力的情况下编号而设计的。如果您可以将此过程作为着色器卸载到GPU,那么即使您在每个像素上多次运行它,它也会运行licketysplit。这将要求你学习图形API,但是如果你在java中工作,LibGDX使得使用GPU渲染并将几个着色器传递给GPU非常容易。

答案 1 :(得分:0)

我不确定你计算光值的方式,但我知道使用BufferedImage.getRGB()和BufferedImage.setRGB()方法非常慢。

我建议直接从一个数组(更快的IMO)访问BufferedImage的像素

这样做:

BufferedImage lightImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
Raster r = lightImage.getRaster();
int[] lightPixels = ((DataBufferInt)r.getDataBuffer()).getData();

现在,更改此阵列中的任何像素都会显示在您的图像上。请注意,此数组中使用的值是颜色值,格式为您为图像定义的任何格式。

在这种情况下,它是TYPE_INT_ARGB,这意味着您在设置coloar时必须在数字中包含alpha值(RRGGBB * AA *)

由于此数组是一维数组,因此使用x和y坐标访问像素更加困难。以下方法是更轻松地从lightPixels数组访问像素的实现。

public void setLight(int x, int y,int[] array,int width, int value){
    array[width*y+x] = value;
}

*注意: width 是您的关卡的宽度,或者您的关卡可能存在的2D数组的宽度,如果它是2D数组。

您也可以使用类似方法从lightPixels数组中获取像素,只需排除并返回array[width*y+x]

由你来决定如何使用setLight()和getLight()方法,但在我遇到的情况下,使用此方法比使用getRGB和setRGB要快得多。

希望这有帮助