如何获得平滑的2D照明效果?


18

我正在XNA中制作基于2D瓦片的游戏。

目前,我的闪电看起来像这样

我怎样才能使它看起来像这样

并非每个块都有自己的色彩,而是具有平滑的覆盖层。

我假设使用某种着色器,并将周围瓷砖的照明值传递给着色器,但是我是着色器的初学者,因此我不确定。

我当前的照明计算光,然后将其传递到a SpriteBatch并使用tint参数进行绘制。每个图块都有一个Color在绘制之前在我的照明算法中计算出的,用于着色。

这是一个有关我当前如何计算光照的示例(我也从左,右和下进行此操作,但是我真的厌倦了逐帧进行此操作...)

在此处输入图片说明

因此,到目前为止,获取和吸引人们的光是没有问题的

我看过无数关于战争迷雾的教程,并使用渐变圆形叠加创建平滑的闪电,但是我已经有了一个不错的方法为每个瓦片分配一个闪电值,只需在它们之间进行平滑即可。

所以要复习

  • 计算照明(完成)
  • 绘制图块(完成,我知道我需要为着色器进行修改)
  • 阴影图块(如何传递值和应用“渐变”)

Answers:


6

这样的事情怎么样?

不要通过着色瓷砖精灵来绘制照明。将未照亮的图块绘制到渲染目标,然后将图块灯光绘制到第二个渲染目标,每个渲染目标均表示为覆盖图块区域的灰度矩形。若要渲染最终场景,请使用着色器组合两个渲染目标,根据第二个目标的值使第一个目标的每个像素变暗。

这将产生您现在所拥有的一切。这对您没有帮助,所以让我们对其进行一些更改。

更改光照贴图渲染目标的尺寸,以便每个图块由单个像素而不是矩形区域表示。合成最终场景时,请使用带有线性滤波的采样器状态。否则,其他所有内容均保持不变。

假设您正确地编写了着色器,则在合成期间应该有效地“放大”光照贴图。通过图形设备的纹理采样器,可以免费获得漂亮的渐变效果。

您也许还可以切出着色器,并使用“变暗”的BlendState更简单地执行此操作,但是我必须先对其进行试验,然后才能提供详细信息。

更新

我今天有一段时间实际上可以模拟这个。上面的答案反映了我使用着色器作为对所有内容的第一个答案的习惯,但是在这种情况下,它们实际上不是必需的,并且不必要地使事情变得复杂。

正如我建议的那样,您可以使用自定义BlendState来实现完全相同的效果。具体来说,此自定义BlendState:

BlendState Multiply = new BlendState()
{
    AlphaSourceBlend = Blend.DestinationAlpha,
    AlphaDestinationBlend = Blend.Zero,
    AlphaBlendFunction = BlendFunction.Add,
    ColorSourceBlend = Blend.DestinationColor,
    ColorDestinationBlend = Blend.Zero,
    ColorBlendFunction = BlendFunction.Add
}; 

混合方程为

result = (source * sourceBlendFactor) blendFunction (dest * destBlendFactor)

因此,使用我们的自定义BlendState,

result = (lightmapColor * destinationColor) + (0)

这意味着纯白色(1,1,1,1)的源颜色将保留目标颜色,纯黑色(0,0,0,1)的源颜色将使目标颜色变暗为纯黑色,并且任何介于两者之间的灰色阴影会使目标颜色变暗中等数量。

为了将其付诸实践,请首先执行创建光照贴图所需的所有操作:

var lightmap = GetLightmapRenderTarget();

然后只需像通常那样将照明的场景直接绘制到后缓冲区即可:

spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
/* draw the world here */
spriteBatch.End();

然后使用自定义BlendState绘制光照贴图:

var offsetX = 0; // you'll need to set these values to whatever offset is necessary
var offsetY = 0; // to align the lightmap with the map tiles currently being drawn
var width = lightmapWidthInTiles * tileWidth;
var height = lightmapHeightInTiles * tileHeight;

spriteBatch.Begin(SpriteSortMode.Immediate, Multiply);
spriteBatch.Draw(lightmap, new Rectangle(offsetX, offsetY, width, height), Color.White);
spriteBatch.End();

这会将目标颜色(未照明的图块)乘以源颜色(光照图),适当地使未照明的图块变暗,并由于将光照图纹理放大到所需的大小而产生渐变效果。


这似乎很简单,我以后看我能做什么。我之前尝试过类似的东西(我在其他所有物体上渲染了光照贴图,并使用了模糊着色器,但是我无法使renderTarget变成“透明”的,它具有紫色的覆盖层,但是我希望它可以透视到实际的图块层)
Cyral

在设备上设置渲染目标后,调用device.Clear(Color.Transparent);。
科尔·坎贝尔

尽管在这种情况下,值得指出的是,您的光照贴图可能应清除为白色或黑色,分别是全亮和全暗。
科尔·坎贝尔

我认为那是我以前尝试过的,但是它仍然是紫色的,明天当我有时间进行此工作时,我会仔细研究一下。
赛勒尔

几乎可以解决所有问题,但是它从黑色逐渐消失为白色,而不是从黑色逐渐变为透明。着色器部分让我感到困惑,如何编写一个以将原始未照明的场景调暗为新场景。
Cyral

10

好的,这里是一种非常简单的方法,可以创建简单平滑的2D闪电,分三步进行渲染:

  • 通行证1:游戏世界没有闪电。
  • 通行证2:没有世界的闪电
  • 屏幕上显示的1.和2. pass的组合。

在通道1中,您将绘制所有精灵和地形。

在步骤2中,您将绘制第二组精灵,它们是光源。它们看起来应该与此类似:
在此处输入图片说明
用黑色初始化渲染目标,并使用“最大”或“添加”混合将这些精灵绘制到其上。

在通道3中,您将两个先前的通道合并在一起。有多种不同的方式可以将它们组合在一起。但是,最简单,最不时髦的方法是通过Multiply将它们融合在一起。看起来像这样:
在此处输入图片说明


问题是,我已经有一个照明系统,点光源在这里不起作用,因为有些物体需要光通过,而另一些则不能。
Cyral 2012年

2
嗯,这比较棘手。您需要进行射线投射以获得阴影。特里亚(Teraria)在其地形上使用大砖块,因此在那没问题。您可以使用不精确的光线投射,但是看起来比terraria更好,并且如果您没有遮挡图形,则可能更不合适。要获得一种先进且美观的技术,请参见
API-Beast

2

您发布的第二个链接看起来像是战争迷雾,对此还有其他一些问题

在我看来,这就像一种质地,当播放器向前移动时,您可以“擦除”黑色叠加的位(通过将这些像素的alpha设置为0)。

我想说这个人一定是在玩家探索过的地方使用了笔刷类型的“擦除”。


1
这不是战争迷雾,而是计算每一帧的闪电。我指出的游戏类似于Terraria。
Cyral 2012年

我的意思是它可能会实施类似雾战争
bobobobo

2

浅顶点(在图块之间的角)而不是图块。根据每个图块的四个顶点混合照明。

对每个顶点进行视线测试以确定其光照程度(您可以让一个块阻止所有光线或减少光线,例如,计算与每个顶点有多少个相交点并结合距离以计算光线值,而不是使用纯二进制可见/不可见测试)。

渲染图块时,将每个顶点的光照值发送到GPU。您可以轻松地使用片段着色器来获取图块精灵中每个片段处的插值光值,以平滑地照亮每个像素。自从接触GLSL以来已经足够长的时间了,我不愿意提供真实的代码示例,但是在伪代码中,它很简单:

texture t_Sprite
vec2 in_UV
vec4 in_Light // coudl be one float; assuming you might want colored lights though

fragment shader() {
  output = sample2d(t_Sprite, in_UV) * in_Light;
}

顶点着色器只需要将输入值传递到片段着色器,就算远程复杂也没有。

您将获得具有更多顶点的更平滑的照明(例如,如果将每个图块渲染为四个子图块),但是根据要获得的质量,这样做可能不值得。首先尝试更简单的方法。


1
line-of sight tests to each vertex?那将是昂贵的。
ashes999

您可以通过一些技巧进行优化。对于2D游戏,您可以非常快速地对严格的网格进行大量的射线测试。代替直接的LOS测试,您可以“流动”(在我的生命中,我现在还不能想到一个合适的词;啤酒之夜),而不是进行射线测试,而是在顶点上进行光照。
肖恩·米德迪奇

使用视线测试会使情况更加复杂,我已经弄清楚了照明设备。现在,当我将4个顶点发送到着色器时,该怎么做?我知道您不愿意提供真实的代码示例,但是如果我能获得更多信息,那将很好。我还用更多信息编辑了问题。
赛勒尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.