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

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

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

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


硬照明

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

强硬的例子

快而脏(如你的建议)

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

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

第二套瓷砖

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

这种方法与第一种方法具有相同的效果,现在您可以使用另一种颜色而不是黑色对覆盖图块进行着色,从而允许彩色光和高光。

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

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


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

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

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

中心光罩

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

结果

图像掩盖硬光结果

增加柔和的光线

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

柔光

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

多个光源

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

面具取消

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

  • 反转所有光掩模(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; } 

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

 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); } 

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

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

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

这怎么做容易?

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

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

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

 public static ArrayList getLine(Point start, Point target) { ArrayList ret = new ArrayList(); 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= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */ if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */ } return ret; } 

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

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

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

 for (int x = 0; x  

和光线跟踪的工作原理如下:

 private static void rayTrace(ArrayList 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; } } } 

我现在已经进入独立游戏开发大约三年了。 我这样做的方法首先是使用OpenGL,这样你就可以获得GPU的图形计算能力的所有好处(希望你已经这样做了)。 假设我们从VBO中的所有瓷砖开始,完全点亮。 现在,有几种方法可以达到你想要的效果。 根据您的照明系统的复杂程度,您可以选择不同的方法。

  • 如果您的灯光在播放器周围是圆形的,无论障碍物如何阻挡现实生活中的光线,您都可以选择在顶点着色器中实现的光照算法。 在顶点着色器中,您可以计算顶点到播放器的距离,并应用一些函数来定义计算距离的函数应该是多么明亮。 不要使用alpha,而只是将纹理/ tile的颜色乘以光照值。

  • 如果你想使用自定义光照贴图(更有可能),我建议添加一个额外的顶点属性来指定平铺的亮度。 如果需要,更新VBO。 这里采用相同的方法:将纹理的像素乘以光值。 如果你以玩家位置为起点递归填充光线,那么每次玩家移动时你都会更新VBO。

  • 如果您的光照贴图取决于阳光照射到您的水平的位置,您可以结合使用两种照明技术。 为太阳亮度创建一个顶点属性,为光点发出的光创建另一个顶点属性(如玩家持有的火炬)。 现在,您可以在顶点着色器中组合这两个值。 假设你的太阳升起并像白天和黑夜模式一样下降。 假设太阳亮度是sun ,它是一个介于0和1之间的值。该值可以作为一个统一体传递给顶点着色器。 表示太阳亮度的顶点属性是s ,光点发出的光点是l 。 然后你可以像这样计算该瓷砖的总光线:

     tileBrightness = max(s * sun, l + flicker); 

    flicker (也是顶点着色器均匀)是某种挥动函数,代表光点亮度的微小变化。
    这种方法使场景动态,而无需不断重建VBO。 我在一个概念validation项目中实现了这种方法。 它很棒。 您可以在此处查看其外观: http : //www.youtube.com/watch?v = jTcNitp_IIo 。 请注意video中0:40的手电筒是如何闪烁的。 这是我在这里解释的。