使用噪声和中位数割创建像素星云

我在游戏《最后的边界》中想要一个星云。 它们看起来令人惊叹,没有它们的空间就不是空间,而是散布在背景周围的白色像素。 但是由于我是按照“像素艺术”风格制作游戏的,因此我需要以某种方式使我的噪波库生成像素化图像。

以下是一些示例:



更多例子





在单色示例中,使用8种颜色,在其他示例中,使用16种颜色。 在本文中,我将讨论如何为《最后的边界》创建像素化星云。

当我们使用噪声库(例如LibNoise)时无论 使用(或编写自己的) 哪种 引擎 ,其值通常分布在-11的范围内。 从理论上讲,二维噪声的可能范围是-0.70.7 ,但是某些实现会缩放结果,将其转换为-11的间隔。 要使用2D纹理,通常将其转换为01的间隔,然后RGB(255,255,255) RGB(0,0,0)RGB(255,255,255)的范围内。


从每个像素的x,y坐标生成的Perlin噪声缩放为0.3f

然后,您可以使用分数布朗运动使图像具有云层的绚丽感。


Perlin噪声以8个八度音阶,频率0.01 ,规则度0.5和腔隙度2.0进行分数布朗运动。

我注意到Internet上有很多Perlin噪声,单纯形噪声和分数布朗运动(fBm)的错误实现。 关于什么是什么似乎有很多困惑。 确保使用正确的实现,因为如果您要创建上述链,那么在实现不正确的情况下,您可能无法获得所需的结果。

假设我们要创建烟雾效果,也就是说,这样的解决方案将适合我们。 但是,如果从RGB(0,0,0)RGB(255,255,255)出现一堆新的颜色,我们的像素艺术游戏看起来会很奇怪。 突然,游戏中会出现255个新等级的灰色。

我们需要将它们转换为有限数量的颜色。 那就是我们以后要做的。 同时...

产生随机星云


我重复了有关生成随机星云的现成教程,但是添加了一些步骤并应用了自己的噪声库。 我几年前写这本书是因为我想对Perlin的噪音以及如何将其与其他概念结合使用来创建纹理等有一个很好的了解。

也许您可以一步一步地重复我的操作,否则您将不得不添加一些会影响噪声的代码。 我将解释除初始噪声和fBm之外的所有内容,以便您可以自己编写代码。 我认为可以假定您已经具有生成噪声和fBm的能力。

首先,我将显示生成星云的结果:


成品结果

重要的是要注意它还没有像素化。 它具有全方位的色彩和像素化的星空。 我们稍后将像素化的星云。

首先要做的是生成五种不同的纹理:红色,绿色,蓝色,Alpha和蒙版。 相应的最终颜色通道需要红色,绿色和蓝色纹理。 实际上,我只生成一个或两个颜色通道,因为事实证明,同时使用这三个通道会产生令人难以置信的彩色星云,看上去很难看。 任何一种颜色或两种颜色的组合都可以。

Alpha通道很重要,因为它取决于下层恒星是否会穿过星云发光。 我将通过显示上面示例的alpha通道来说明这一点。


从我们的示例准备好alpha通道

区域越白,该值越接近1.0 ,这为我们提供了255的alpha值。 区域越黑,它越透明。 如果看一个示例,您会看到黑色区域对应于可见星空的区域。


星空的例子

这些星星与示例中的星星不同,因为它们是在每个屏幕截图中随机生成的。 我希望这不会阻止您了解星云是如何产生的。

我的噪声库由模块组成,遵循Lib Noise的示例。 该库中的所有内容都是可以链接在一起的“模块”。 一些模块生成新值(Perlin模块,常量值),其他模块将它们连接(乘,加),而某些模块仅对该值执行操作(Lerp,Clamp)。

色彩通道


我们使用一种,两种或三种颜色都没关系-红色,绿色和蓝色通道是以相同的方式生成的; 我只是为它们使用不同的种子值。 我的种子值取决于当前系统时间。

在下面,它们全部以灰度表示,但是从理论上讲,它们只是三个通道之一的值。 灰度只是为了说明结果。

1.佩林的噪音


如上所述,Perlin的噪音将是起点。 如果需要,可以使用单纯噪声,它的2D实现似乎不属于Ken Perlin,但我可能是错的。 从数学的角度来看,单纯形噪声使用较少的指令,因此类似星云的生成将更快。 由于它使用单纯形而不是网格,因此会产生更漂亮的噪点,但是我们不会对其进行过多处理,因此这并不是特别重要。

实际代码未在下面显示,因为在实际源中, x,y值在步骤3中被fBm更改。这只是图像的x,y坐标乘以静态比例因子。


从每个像素的x,y坐标生成的Perlin噪声缩放为0.3f PixelValue = PerlinNoise(x * 0.3f, y * 0.3f)

Perlin噪声创建的值大约在-11的范围内,因此要创建上面显示的通常的灰度图像,我们将它们转换为01的间隔。 我测试了这些值的范围,以便转换产生最大的对比度(最低的值对应于0 ,最大的对应于1 )。

2.乘法


下一个使用的模块将生成的噪声乘以5 。 这可以被认为是对比度的调整。 负值较暗,正值较亮。

我在这里没有显示任何内容,因为在将值从-55转换为01结果不会改变。

3.分数布朗运动(fBM)


这个阶段将噪声变成许多人认为是真正的“噪声效应”。 在这里,我们从噪声函数(在本例中为perlin(x,y) )执行越来越小的样本的八度音阶,以增加蓬松度。


上面显示的Perlin噪声的分数布朗运动。 8个八度音阶,频率.01f ,规律性.5f2.5f

您已经可以看到有趣的事物的起源。 上面显示的图像不是通过缩放像素的x,y坐标生成的,fBM会这样做。 同样,将这些值反向转换为01的间隔到可能的-55间隔。

4.限制(钳位)


现在,我将值限制为-11 。 超出此时间间隔的所有内容将被完全丢弃。


相同的fBm,限制为-1 1

此操作的任务是将这些值转换为较短的时间间隔,同时创建更陡峭的渐变并增加全白或全黑区域。 这些死或空的区域对于星云效应很重要,我们稍后将解决。 如果一开始我们没有乘以5 ,那么钳位将不会有任何改变。

5.加1


现在我们从钳位中获取值并将它们加1.,从而将值传输到02的区间。 转换后,结果将与以前相同。

6.除以2


您可能知道我将结果除以2 (乘以.5 )会发生什么。 在图像中,没有任何变化。

步骤5和6将值转换为01的范围。

7.创建变形纹理


下一步是创建变形纹理。 我将使用Perlin噪声(具有新的种子值)>乘以4>执行fBm来执行此操作。 在这种情况下,fBm使用5个八度音阶,频率为0.025 ,规则度为0.5并且腔隙度为1.5


变形纹理

为了创建比星云现有纹理更多的细节,需要这种纹理。 星云是一个相当大的波浪状云,这种质地会使它发生很小的变化。 通过它,Perlin噪声的网格性质将开始显现。

8.使用偏移纹理偏移颜色纹理


接下来,我将采用这两个纹理,并使用一个纹理将另一个纹理的坐标偏移一个因子。 在我们的例子中,组合看起来像这样:


偏差结果

失真纹理用于更改我们在源噪声数据中寻找的x,y坐标。

请记住,上面显示的图像仅用于说明目的。 在每个阶段,我们实际上只有一个噪声函数。 我们给它传递值x,y ,它返回一个数字。 在某些阶段,此数字的间隔可能会有所不同,但在上面我们将其转换回灰度以创建图像。 通过使用噪声函数传输的图像的每个x,y坐标作为x,y来创建图像。

也就是说,当我们说:

给我X = 0和Y = 0的左上角像素的值

函数返回一个数字。 如果我们向Perlin询问,我们知道它将在-11之间,如果如上所述,如果应用钳位,加法和乘法,我们将得到一个介于01之间的值。

了解了这一点后,我们了解到失真噪声函数会产生从-11 。 因此,当我们说:

给我像素X = 0和Y = 0的左上角像素的值

偏移模块首先向偏移函数询问x,y坐标。 其结果在-11之间(与上面一样)。 然后乘以40 (这是我选择的系数 )。 结果将是-4040之间的值。

然后,我们使用该值并将其添加到要查找的x,y的坐标中,并使用此结果搜索颜色纹理。 由于将噪声函数(至少在我的噪声库中)无法找到负的x,y坐标,因此将钳位设为0可以消除负值。

也就是说,通常看起来像这样:

 ColourFunction(x,y) =     0  1 DisplaceFunction(x,y) =     -1  1 DoDisplace(x,y) = { v = DisplaceFunction(x,y) * factor clamp(v,0,40) x = x + v; y = y + v; if x < 0 then x = 0 if y < 0 then y = 0 return ColourFunction(x,y) } 

希望您能理解。 实际上,我们并不是在看x,y ,而是在偏移处。 并且由于幅度也是一个平滑的梯度,因此它平滑地移动。

还有其他方法可以执行偏移。 我的噪音库有一个产生螺旋位移的模块。 它可以用来绘制纹理,逐渐减少到许多点。 这是一个例子

仅此而已。 我们对每个颜色通道使用新的种子值,将上述操作重复三遍。 您可以创建一个或两个通道。 我认为创建第三个不值得。

阿尔法通道


创建Alpha通道的方式与使用颜色通道的方式几乎相同:

  1. 我们从Perlin的噪音开始
  2. 乘以5
  3. 具有8个八度音阶的fBM,频率0.005 ,规则度0.5和腔隙度2.5
  4. 我们使用Clamp将结果限制为-11的间隔,加1 ,除以2 (即我们将间隔从-11移到从01的间隔。
  5. 我们将结果向负方向少量移动。 我抵消了0.4 。 因此,一切都变得更暗了。
  6. 我们将结果限制为01的间隔。 由于我们移动了所有内容,使其变暗了一些,因此实际上,我们创建了更多具有0区域,并且某些区域变为负值。

结果是alpha通道纹理。


阿尔法纹理

正如我所说,黑色区域将是透明的,而白色区域将是不透明的。

通道遮罩


这是用于创建覆盖在其他所有物体上的阴影的最后一个纹理。 就像所有其他纹理一样开始:

  1. 噪音珀林
  2. 乘以5
  3. 我们执行fBm, 5个八度音阶,频率0.01 ,规律性0.1 ,微弱度0.1 。 规律性小,因此云密度较小
  4. 执行从-11的间隔转换到从01的间隔转换

但是我们创建了两个这样的纹理:


遮罩


面膜B

我们将这两个纹理暴露给我称为“ 选择”模块的地方。 实际上,我们使用来自模块A或模块B的值。选择取决于模块C的值。它还需要另外两个值-Select PointFalloff

如果模块C的点x,y值大于或等于SelectPoint ,则我们使用模块B的点x,y的值。如果值小于或等于SelectPoint - Falloff ,则我们使用模块A的x,y的值。

如果它在SelectPoint - FalloffSelectPoint ,则我们在模块A和模块B的x,y值之间执行线性插值。

 float select(x, y, moduleA, moduleB, moduleC, selectPoint, falloff) { float s = moduleC(x,y); if(s >= selectPoint) return moduleB(x,y); else if(s <= selectPoint - falloff) return moduleA(x,y); else { float a = moduleA(x,y); float b = moduleB(x,y); return lerp(a, b, (1.0 / ((selectPoint - (selectPoint-falloff)) / (selectPoint - s))); } } 

在我们的例子中,模块A是一个常量模块,值为0 。 模块B是遮罩A的第一个纹理, 选择器 (模块C)是B的第二个遮罩SelectPoint将为0.4 ,而Falloff将为0.1 。 结果,我们得到:


终极面膜

通过增加或减少SelectPoint ,我们减少或增加了蒙版中的黑色量。 通过增加或减少falloff ,我们可以增加或减少蒙版的软边缘。 除了使用其中一个掩码之外,我还可以使用Constant模块,其值为1 ,但是我想向“未掩码”区域添加一些随机性。

混合颜色通道和遮罩


现在,我们需要为每个颜色通道应用一个蒙版。 这是使用Blending模块完成的。 它合并了两个模块中值的百分比,因此值的总和为100%。

也就是说,我们可以取模块A的x,y值的50% x,y模块B的x,y的值的50%。或者取75%和25%,依此类推。 我们从每个模块获取的百分比取决于另一个模块-模块C。如果模块C的x,y值为0 ,则我们将从模块A中获取100%,从模块B中获取0%。如果为1 ,则我们获取逆值。

结合使用每种颜色纹理。

  • 模块A-常数0
  • 模块B是我们已经看到的颜色通道
  • 模块C-遮罩结果

这意味着仅当蒙版的值大于0 (接近白色)时,才会显示色彩通道的噪声,并且可见性的大小取决于蒙版的值。

这是我们示例的结果:


最终结果

在与面膜混合之前,将其与原始图像进行比较。


与面膜混合之前

也许这个例子不是很明显,但是由于偶然的原因,很难具体选择一个好的例子。 遮罩的作用是创建较暗的区域。 当然,您可以自定义蒙版,使它们更明显。

在此重要的是,将相同的蒙版应用于整个颜色通道,即,阴影中出现相同的区域。

我们将所有东西结合在一起


我们最初完成的示例:


准备好例子

它使用红色,绿色和Alpha通道:


红色通道


绿色通道


阿尔法通道

然后我们将它们放在星空之上。

现在一切看起来都不错,但并不非常适合像素艺术游戏。 我们需要减少颜色的数量...

中位数


本文的这一部分可以应用于任何内容。 假设您要生成大理石纹理,并希望减少颜色数量。 这是中位数剪切算法派上用场的地方。 我们将使用它来减少上面显示的星云中的颜色数量。

这是在将其叠加在星空之前发生的。 颜色的数量是完全任意的。

维基百科中描述的中位数剪切算法:

假设我们有一个具有任意数量像素的图像,并且想要生成16种颜色的调色板 。 将图像中的所有像素(即RGB值 )放入垃圾桶 。 找出篮子中所有像素中哪个颜色通道(红色,绿色或蓝色)具有最大的值范围,然后根据该通道的值对像素进行排序。 例如,如果蓝色通道的值范围最大,则RGB值(32、8、16)的像素小于RGB值(1、2、24)的像素,因为16 <24。放入一个新的篮子。 (此步骤为中位数剪切算法起了名字;篮子被像素列表的中位数除以一半。)对两个篮子重复该过程,这将给我们4个篮子,然后对所有4个篮子重复,得到8个篮子,然后对8个篮子重复,得到16篮子。 我们平均每个购物篮中的像素,并获得16种颜色的调色板。 由于购物篮的数量在每次迭代时都会翻倍,因此该算法只能生成这种调色板,其中颜色的数量是2 的幂 。 例如,要生成12色调色板,您需要首先生成16色调色板,然后以某种方式组合一些颜色。

资料来源: https : //en.wikipedia.org/wiki/Median_cut

在我看来,这种解释相当糟糕,并不是特别有用。 在实施该算法时,以这种方式可以获得相当难看的图像。 我通过一些更改实现了它:

  1. 我们将boxes容器与表示间隔的值一起存储(有关更多信息,请参见下文)。 该box仅存储原始图像中的一些动态像素。
  2. 将原始图像中的所有像素添加为第一个并使用间隔0
  3. 虽然总数少于所需的颜色数量,但我们继续以下步骤。
  4. 如果interval值为0 ,则对于每个当前框,我们确定该框的主要颜色通道,然后按该颜色对此box的像素进行排序。 — Red, Green, Blue Alpha, . , redRange = Max(Red) - Min(Red) . , .
  5. box boxes . , box .
  6. , 4 5 box , boxes . , , , . , , .
  7. box ( == ) boxes . 0 ( ). , , , — . .

当我们达到等于所需颜色数量的盒子数量时,我们只需对每个盒子中的所有像素进行平均,即可确定最适合这些颜色的调色板元素。我只是使用了欧几里得距离,但是有一些感知解决方案可以更好地做到这一点。

这是一张可以更清楚地说明所有内容的图片。为了演示,我仅使用RGB,因为alpha很难显示。


让我们将此方法应用于示例图像。


原来的


中位数最多可以切割16种颜色,

我发现当使用两个颜色通道时,使用16种颜色可以获得很好的效果。但请记住,这里我们使用alpha通道,该通道还涉及计算颜色之间的距离。因此,如果您不关心透明度,则可以使用更少的颜色。由于与Wikipedia示例不同,我的中位数剪切可以使用任意数量的颜色(而不仅仅是两个度),因此您可以自定义其颜色以满足您的需求。


从16到2种颜色,

我们从每种颜色中选择一种颜色box,只需对所有值进行平均即可。但是,这不是唯一的方法。您可能已经注意到,与原始结果相比,我们的结果不是那么好。如果需要,则可以在较高的时间间隔中设置优先级,为时间间隔的定义增加权重。或者,您可以轻松地选择图像中最亮的颜色的1、2或3种并将它们添加到调色板中。因此,如果需要16种颜色,请生成13种颜色的调色板,然后手动添加亮色。


具有三种最亮颜色的调色板

现在,一切看起来都不错,但是图片太脏了。它具有大面积的相同颜色。现在我们需要解决它们。

抖动


我不需要告诉您什么是抖动,因为您已经在使用像素艺术。因此,为了获得更平滑的图像,我们将使用其中一种抖动算法,该算法有很多。

我实现了一个简单的Floyd-Steinberg抖动算法没有任何不愉快的惊喜。但是,效果非常强。这又是我们的示例:


原来

我们再切到的16种颜色的调色板:


这些值将映射到16色调色板,

然后进行抖动处理,然后转换为调色板:


抖动完成的结果

Source: https://habr.com/ru/post/zh-CN464203/


All Articles