使用Perlin噪声(基于瓦片)生成2D地形

6

我一直在研究Perlin噪声,因为我正在开发一个基于“2维侧视”的游戏(模拟应用)。我并不完全理解Perlin噪声是如何工作的,但大致了解。

我找到了这篇文章和代码Perlin Noise,决定试一下。目前我已经设置了世界生成方式,每个“块”有16x16个图块,当Y >= HEIGHT / 2时所有图块都是“实心”,否则是“空气”,这个方案可以正常工作。但地形非常平坦,正如你所想象的那样。这就是我认为Perlin噪声可以派上用场的地方,所以我使用了上述网站上找到的代码,但我并不确定该如何将噪声转换为实心/空气图块。

如你所见,这是非常不真实的地形:enter image description here

我更希望地形像下面这样(看起来像山和滚动山丘),我不关心边缘是否锐利(方形):enter image description here

我不知道如何遍历图块数组来将噪声转换为上述地形。

目前,我正在像这样遍历图块数组(2D),但正如你从第一张图片中看到的那样,它只是随机确定图块是实心还是空气,并没有创建任何滚动山丘或山脉。

for(int Y = 0;Y < HEIGHT;++Y) {
    for(int X = 0;X < WIDTH;++X) {
        Tile Tile;
        if(noise(X,Y) < 0) {
            Tile.Type = BLOCK;
        } else {
            Tile.Type = Air;
        }

        // Then I push the Tile on to a vector

我还应该提到,在这个16x16的“块”的右侧,还有另一个需要与之匹配的块,接下来的下一个块也是如此。块不应与以前的块相同,只需使其看起来不像是单独的块即可,如果这样说有意义的话。
非常感谢您的帮助!谢谢
更新
我已经将我的perlin函数更改为这个,并实现了phresnel建议的内容:
    PerlinNoise *test=new PerlinNoise(0.25, 6);    //0.25 is Persistance 6 is Octaves

        float f,h;
        float seed = 804.343;

        for (int X=0; X!=MAP_WIDTH; ++X) {

            f = test->GetNoise((X * ChunkX) * seed);

            h = f * MAP_HEIGHT + (MAP_HEIGHT / 2);

            for (int y=0; y<h; ++y) {
                Tile tempTile;
                tempTile.Tile = TILE_NONE;
                Tiles[X][y] = tempTile;
            }
            for (int y=h; y<MAP_HEIGHT; ++y) {
                Tile tempTile;
                tempTile.TileID = TILE_BLOCK;
                Tiles[X][y] = tempTile;
            }
        }

做完这些之后,它看起来好多了。对于每个噪声,我使用 X * ChunkX ,否则每个区块都会是相同的,现在看起来像下面这样: enter image description here 如您所见,地形比以前更接近我的要求,但仍不太像真实的地形,因为它不够平滑。是否有办法使其整体外观更加平滑,以创建“起伏的山丘”和“平原”?
我删除了噪声值下方的图块创建,因此更容易看到范围。 enter image description here 如您所见,看起来还可以,只需要将值“平滑”掉,使其不像伪随机值。我还想知道为什么左侧的第一个块始终具有相同的值。
谢谢。
1个回答

4
你的结果很好,因为你有岛屿、大陆、池塘和海洋。Perlin噪声帮助你生成非随机角色的随机地形。
基本上,你将噪声函数的(数学上)无限分辨率减少到仅两个不同图块的离散集合,加上非常有限的块分辨率。鉴于此,你的结果似乎很好。
不同的方法可以改善你的结果。例如,在你的图块图形之间进行lerp:
// Exemplary p-code, fine-tuning required
Color color_at (x, y) {
    Real u = x / width, v = y / height
    Real  f  = min (max(noise(u,v), 0), 1);
    Color c  = water*f + hill*(1-f);
    return c;
}

你可以将lerp函数推广到任何瓷砖数量。
另一种锐利边缘的可能性是受 Voronoi diagrams启发(1)。你使用与上面相同的函数,但不是插值,而是选择最接近噪声值的瓷砖:
Color color_at (x, y) {
    Real u = x / width, v = y / height
    Real  f  = min (max(noise(u,v), 0), 1);
    if (f<0.5) return water;
    else return hill;
}

这与你的方法相同,只不过是按像素进行操作。同样地,这种方法可以泛化应用。
一旦你建立了一个好的设置,就可以使用噪声来实现各种功能:植物/树木分布、敌人分布、动画(这将是一维噪声函数)等。

针对您的评论进行编辑:

连续的一维地形:

如果您需要像经典的2D跳跃游戏中的侧视图,可以考虑使用一维噪声函数。从0迭代到图像宽度,在每个停止点取噪声函数的一个样本f,将f转换为屏幕空间,那么屏幕上方的每个像素都将成为天空的一部分,下方的每个像素都将来自您的瓷砖:

for (int x=0; x!=width; ++x) {

    // Get noise value:
    f = noise1d (x/width);
    h = f * height + (height/2);  // scale and center at screen-center

    // Select tile:
    Tile *tile;
    if (h < waterLimit) {
        h    = waterLimit;
        tile = waterTile;
    } else {
        tile = terrainTile;
    }

    // Draw vertical line:
    for (int y=0; y<h; ++y)
        setPixel (x, y, skyColor(x,y));
    for (int y=h; y<height; ++y)
        setPixel (x, y, tileColor(x,y, tile));
}

再次,示例伪代码。

您当前设置中的完整二维级别:

噪声函数非常灵活。如果您想要更少的蓝色和更多的绿色,只需降低常数项,即将if(h < 0) -> blue改为if(h < -0.25) -> blue


1: 我从未尝试过这种方法,因为我对3D地形渲染(picogen.org)很感兴趣。


抱歉如果我没有解释清楚问题,我需要的是一个“侧视图”,而不是鸟瞰图。 - Elgoog
我想我明白了,你似乎确实很懂。我实际上并不使用setPixel,我的做法是在for循环中一旦设置了瓷砖,比如tile = block,这就定义了后面程序中渲染的瓷砖。在你的回答中,你似乎只迭代x(上面的每个像素都将成为天空的一部分,下面的每个像素都将是[固体])。我初始化地图的方式是[0][0]->[15][15],所以是y然后x。如果按照你的建议去做,似乎会再次变成混乱,我需要改变这个吗? - Elgoog
@Elgoog:是的,我的解决方案假设您可以绘制单个像素。您可以在CPU上快速完成此操作,甚至可能在GPU上完成(但我没有真正的GPGPU经验)。您真正的问题是块状还是有太多“棕色”瓷砖? - Sebastian Mach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接