高效的基于2D瓷砖的照明系统

Kyr*_*tar 8 java 2d lighting tile

在Java中为基于磁贴的引擎进行照明的最有效方法是什么?
是否会在瓷砖后面放置黑色背景并更改瓷砖的alpha?
或者把黑色前景改成那个?还是其他什么?

这是我想要的那种照明的一个例子:
http://i.stack.imgur.com/F5Lzo.png

bri*_*ore 22

有很多方法可以实现这一目标.花一些时间做出最终决定.我将简要总结一些您可以选择使用的技术并最终提供一些代码.


硬照明

如果你想创建一个硬边照明效果(如你的示例图像),我想到了一些方法:

强硬的例子

快速又脏(如你的建议)

  • 使用黑色背景
  • 根据黑暗值设置切片的alpha 值

问题是,既不能使瓷砖比以前更亮(高光),也不能改变光的颜色.这些都是通常使游戏中的照明看起来很好的方面.

第二套瓷砖

  • 使用第二组(黑色/彩色)瓷砖
  • 将它们放在主要瓷砖上
  • 根据新颜色应该有多强,设置新切片的alpha值.

这种方法与第一种方法具有相同的效果,具有优势,您现在可以使用除黑色之外的另一种颜色为覆盖图块着色,这允许彩色光和高光.

例: 硬灯与黑色瓷砖的例子

即使这很容易,但问题是,这确实是一种非常无效的方式.(每个拼贴两个渲染的拼贴,不断重新着色,许多渲染操作等)


更有效的方法(硬和/或软照明)

在看你的例子时,我想光总是来自特定的源瓦(角色,火炬等)

  • 对于每种类型的灯光(大火炬,小火炬,角色灯光),您可以创建一个图像,该图像表示相对于源图块(光罩)的特定照明行为.火炬可能是这样的(白色是alpha):

中心灯罩

  • 对于作为光源的每个图块,您将此图像作为叠加层在源位置处渲染.
  • 要添加一些浅色,您可以使用例如10%不透明橙色而不是完整alpha.

结果

图像掩盖硬光结果

增加柔和的光线

柔和的光线现在没什么大不了的,只需在光罩中使用更多细节,而不是瓷砖.通过在通常的黑色区域中仅使用15%的alpha,您可以在瓷砖未点亮时添加低视效:

柔光

您甚至可以通过更改遮罩图像轻松实现更复杂的照明形式(视锥细胞等).

多个光源

当组合多个光源时,这种方法会导致一个问题:绘制两个相互交叉的掩模可能会自行取消:

面具取消

我们想要的是他们添加灯而不是减去它们.避免问题:

  • 反转所有光掩模(alpha为暗区,不透明为浅色区)
  • 将所有这些光掩模渲染为与视口具有相同尺寸的临时图像
  • 在整个场景中反转并渲染新图像(就好像它是唯一的光掩模).

这将产生类似于此的东西: 结果图像

掩码反转方法的代码

假设你先渲染所有的瓷砖BufferedImage,我会提供一些类似于最后显示的方法的指导代码(只有灰度支持).

例如火炬和玩家的多个光罩可以像这样组合:

public BufferedImage combineMasks(BufferedImage[] images)
{
    // create the new image, canvas size is the max. of all image sizes
    int w, h;

    for (BufferedImage img : images)
    {
        w = img.getWidth() > w ? img.getWidth() : w;
        h = img.getHeight() > h ? img.getHeight() : h;
    }

    BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

    // paint all images, preserving the alpha channels
    Graphics g = combined.getGraphics();

    for (BufferedImage img : images)
        g.drawImage(img, 0, 0, null);

    return combined;
}
Run Code Online (Sandbox Code Playgroud)

使用此方法创建并应用最终掩码:

public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask)
{
    int width = image.getWidth();
    int height = image.getHeight();

    int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
    int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);

    for (int i = 0; i < imagePixels.length; i++)
    {
        int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha

        // get alpha from color int
        // be careful, an alpha mask works the other way round, so we have to subtract this from 255
        int alpha = (maskPixels[i] >> 24) & 0xff;
        imagePixels[i] = color | alpha;
    }

    image.setRGB(0, 0, width, height, imagePixels, 0, width);
}
Run Code Online (Sandbox Code Playgroud)

如上所述,这是一个原始的例子.实现颜色混合可能需要更多工作.


Mar*_*ank 10

光线跟踪可能是最简单的方法.

  • 你可以存储哪些瓷砖已经被看到(用于自动化,用于'在被蒙蔽时记住你的地图',也许用于迷你地图等)
  • 你只展示你所看到的东西 - 也许是墙上或山上的怪物阻挡了你的视线,然后光线追踪就会停止
  • 即使你自己的光源不能到达很远的地方,也可以看到遥远的"发光物体"或其他光源(火炬熔岩).
  • 光线的长度将用于检查光量(褪色光)
  • 也许你有一个特殊的传感器(ESP,金/食物检测),用于查找不在你视野中的物体?光线跟踪可能也有帮助^^

这怎么办容易?

  • 从你的玩家画一条线到地图边界的每一个点(使用Bresehhams算法http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm 沿着那条线(从你的角色到最后)走,直到你的视图为止阻止;此时停止搜索(或者最后一次迭代,看看你做了什么)
  • 对于线上的每个点设置lighning(距离1可能为100%,距离2可能为70%,依此类推)并标记为已访问的地图图块

也许你不会沿着整个地图走路,也许只要你设置你的光线跟踪20x20视图就足够了?注意:你真的必须沿着视口的边界走,它不需要跟踪每个点.

我正在添加线算法来简化您的工作:

public static ArrayList<Point> getLine(Point start, Point target) {
    ArrayList<Point> ret = new ArrayList<Point>();
    int x0 =  start.x;
    int y0 =  start.y;

    int x1 = target.x;
    int y1 = target.y;

    int sx = 0;
    int sy = 0;

    int dx =  Math.abs(x1-x0);
    sx = x0<x1 ? 1 : -1;
    int dy = -1*Math.abs(y1-y0);
    sy = y0<y1 ? 1 : -1; 
    int err = dx+dy, e2; /* error value e_xy */

    for(;;){  /* loop */
        ret.add( new Point(x0,y0) );
        if (x0==x1 && y0==y1) break;
        e2 = 2*err;
        if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
        if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
    }

    return ret;
}
Run Code Online (Sandbox Code Playgroud)

我前段时间做了这一整个闪电事件,一个*pathfindin随时可以提出进一步的问题

附录:也许我可以简单地为光线追踪添加小算法^^

获取南北边界点只需使用此代码段:

for (int x = 0; x <map.WIDTH; x++){
    Point northBorderPoint = new Point(x,0);
    Point southBorderPoint = new Point(x,map.HEIGHT);

    rayTrace( getLine(player.getPos(), northBorderPoint), player.getLightRadius()) );
    rayTrace( getLine(player.getPos(), southBorderPoint, player.getLightRadius()) );
}
Run Code Online (Sandbox Code Playgroud)

而光线跟踪的工作方式如下:

private static void rayTrace(ArrayList<Point> line, WorldMap map, int radius) {

    //int radius = radius from light source     
    for (Point p: line){

        boolean doContinue = true;
        float d = distance(line.get(0), p);

        //caclulate light linear 100%...0% 
        float amountLight = (radius - d) / radius;  
        if (amountLight < 0 ){
            amountLight = 0;
        }

        map.setLight( p, amountLight );

        if ( ! map.isViewBlocked(p) ){ //can be blockeb dy wall, or monster  
            doContinue = false;
            break;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)