地图生成器的渐变圆圈

Gradient Circles for Map Generator

所以我正在制作一个可以生成随机岛屿的地图生成器。它在生成器的核心使用 Perlin 噪声,然后使用带有渐变的圆圈来制作岛屿。

circle 方法在地图的中心创建了一些圆圈,其颜色从 64 开始渐变到 0。问题是这种方法在地图的某些部分创建了一个不自然的外观有圆形边缘。当为一个像素生成 perlin 噪声时,它将在渐变图上获取该像素,然后将其乘以蓝色值。

因此,如果柏林噪声在像素 1、5 上给出 1,并且梯度图上的蓝色值为 54,它将输出噪声值 54。如果像素 130、560 上的柏林噪声为 0.5,并且渐变色值为64则噪声值为32.

这是我得到的:

代码有两个关键点,perlin位:

noise = NoiseGenerator.Noise(x, y);
double gradColour = getGradColour(x, y).B;
double addedNoise = noise * gradColour;
double gradNoise = addedNoise;// - gradColour;

然后是渐变贴图生成器:

    public static void DrawGrad(float X, float Y, float R, Color C1, Color C2)
    {
        Graphics g = Graphics.FromImage(imgGrad);
        GraphicsPath path = new GraphicsPath();
        path.AddEllipse(X, Y, R, R);

        PathGradientBrush pathGrBrush = new PathGradientBrush(path);
        pathGrBrush.CenterColor = C1;

        Color[] colours = { C2 };
        pathGrBrush.SurroundColors = colours;
        g.FillEllipse(pathGrBrush, X, Y, R, R);
        //g.FillEllipse(Brushes.Red, X, Y, R, R);
        g.Flush();
    }

        int amount = rnd.Next(25, 30);
        for (int i = 0; i < amount / 4; i++)
        {
            float X = rnd.Next(-800, 1748);
            float Y = rnd.Next(-800, 1748);
            float R = rnd.Next(1000, 1200);
            DrawGrad(X, Y, R, Color.FromArgb(255, 0, 0, rnd.Next(15, 20)), Color.FromArgb(0, 0, 0, 0));
        }
        for (int i = 0; i < amount; i++)
        {
            double positionDiv = 1.98;
            double X1 = rnd.Next(0, 450) / positionDiv;
            double Y1 = rnd.Next(0, 450) / positionDiv;
            double R1 = rnd.Next(300, 650) / 4;
            float R = (float)R1;
            float X = (float)X1;
            float Y = (float)Y1;

            while (X + R > 1004)
            {
                X = 924 - R;
            }
            while (Y + R > 1004)
            {
                Y = 924 - R;
            }
            if(X < 30)
            {
                X = 30;
            }
            if(Y < 30)
            {
                Y = 30;
            }
            DrawGrad(X, Y, R, Color.FromArgb(255, 0, 0, rnd.Next(40, 64)), Color.FromArgb(0, 0, 0, rnd.Next(13, 17)));
        }

我只是想知道是否还有其他人知道 C# 中可以使用 perlin 噪声创建孤岛的任何其他方法?任何建议将不胜感激。

正如我在评论中提到的那样,菱形和正方形更容易获得足够好的结果。所以算法:

  1. 配置生成属性

    这里你需要有一组参数,比如最小、最大海拔、海平面、植被的海拔范围,sand/rock/dirt,等等,坡度参数等等。

  2. 创建地形高度图我称之为zed[][]

    为此,您需要稍微修改 Diamond&Square 算法。问题是这个算法产生 "inland" 像 terrain.

    要对其进行调整以使其产生类似岛屿的地形,您需要在拐角处以尽可能低的高度对其进行初始化。此外,您还需要忽略第一个菱形步骤并使用一些随机值(而不是角的平均值)初始化中点。最后在每次方形迭代后将边界点更正为最小(水下)高程(或附近的某个随机值)。

    为了获得良好的输出,我在生成时使用了大约范围 <-2^15 , 2^16>。在此之后,我在生成的地形中找到最小和最大海拔,并重新缩放到配置的海拔范围。

    别忘了菱形和正方形需要分辨率贴图(2^n)+1!!!

  3. 创建表面贴图我称之为typ[][]

    完成地形图后,您可以按升序添加基于高程的要素,例如:

    • 水,沙,植被类型,山石,雪

    然后根据地形坡度添加参数

    • 岩石

    然后你可以添加额外的东西,比如(基于一些规则):

    • 河流、溪流、瀑布、建筑物、道路...

我用 C++ 是这样的:

void map_random(int _xs,int _ys)
    {
    // config
    int h0=-1000,h1=3000;       // [m] terrain elevation range
    int h_water= 0;             // [m] sea level
    int h_sand=15;              // [m] sand level
    int h_evergreen=1500;       // [m] evergreen level
    int h_snow=2000;            // [m] snow level
    int h_rock=1800;            // [m] mountine rock level
    float a_rock=60.0;          // [deg] mountine rock slope
    float d_pixel=15.0;         // [m] pixel size
    bool _island=true;

    // types
    enum _cover_enum
        {
        _cover_none=0,
        _cover_water,
        _cover_snow,
        _covers,
        _cover_shift=0,
        _cover_mask=15,
        };
    DWORD _cover[_covers]=
        {
        //  RRGGBB
        0x00000000,     // none
        0x00004080,     // water
        0x008F8F8F,     // snow
        };
    enum _terrain_enum
        {
        _terrain_enum_beg=-1,
        _terrain_dirt,
        _terrain_sand,
        _terrain_rock,
        _terrains,
        _terrain_shift=4,
        _terrain_mask=15,
        };
    DWORD _terrain[_terrains]=
        {
        //  RRGGBB
        0x00301510,     // dirt
        0x00EEC49A,     // sand
        0x00777777,     // rock
        };
    enum _flora_enum
        {
        _flora_enum_beg=-1,
        _flora_none,
        _flora_grass,
        _flora_hardwood,
        _flora_evergreen,
        _flora_deadwood,
        _floras,
        _flora_shift=8,
        _flora_mask=15,
        };
    DWORD _flora[_floras]=
        {
        //  RRGGBB
        0x00000000,     // none
        0x007F7F3F,     // grass
        0x001FFF1F,     // hardwood
        0x00007F00,     // evergreen
        0x007F3F1F,     // deadwood
        };

    // variables
    float a,b; int c,t,f;
    int x,y,z,xx,yy,mxs,mys,dx,dy,dx2,dy2,r,r2;
    int **ter=NULL,**typ=NULL;
    Randomize();
    // align resolution to power of 2
    for (mxs=1;mxs+1<_xs;mxs<<=1); if (mxs<3) mxs=3;
    for (mys=1;mys+1<_ys;mys<<=1); if (mys<3) mys=3;
    ter=new int*[mys+1]; for (y=0;y<=mys;y++) ter[y]=new int[mxs+1];
    typ=new int*[mys+1]; for (y=0;y<=mys;y++) typ[y]=new int[mxs+1];

    // [Terrain]

    // diamond & square random height map -> ter[][]
    dx=mxs; dx2=dx>>1; r=1<<16;                     // init step,half step and randomness
    dy=mys; dy2=dy>>1; r2=r>>1;
    // set corners values
    if (_island)
        {
        t=-r2;
        ter[  0][  0]=t;
        ter[  0][mxs]=t;
        ter[mys][  0]=t;
        ter[mys][mxs]=t;
        ter[dy2][dx2]=r2;
        }
    else{
        ter[  0][  0]=Random(r);
        ter[  0][mxs]=Random(r);
        ter[mys][  0]=Random(r);
        ter[mys][mxs]=Random(r);
        }
    for (;dx2|dy2;dx=dx2,dx2>>=1,dy=dy2,dy2>>=1)    // subdivide step until full image is filled
        {
        if (!dx) dx=1;
        if (!dy) dy=1;
        // diamond (skip first one for islands)
        if ((!_island)||(dx!=mxs))
         for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
          for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
           ter[y][x]=((ter[y-dy2][x-dx2]+ter[y-dy2][x+dx2]+ter[y+dy2][x-dx2]+ter[y+dy2][x+dx2])>>2)+Random(r)-r2;
        // square
        for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
         for (x=dx ,xx=mxs-dx ;x<=xx;x+=dx)
          ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])>>2)+Random(r)-r2;
        for (y=dy ,yy=mys-dy ;y<=yy;y+=dy)
         for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
          ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])>>2)+Random(r)-r2;
        for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
            {
            y=  0; ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y+dy2][x])/3)+Random(r)-r2;
            y=mys; ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x])/3)+Random(r)-r2;
            }
        for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
            {
            x=  0; ter[y][x]=((ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])/3)+Random(r)-r2;
            x=mxs; ter[y][x]=((ter[y][x-dx2]+ter[y-dy2][x]+ter[y+dy2][x])/3)+Random(r)-r2;
            }

        // adjust border
        if (_island)
            {
            for (y=0;y<=mys;y+=dy2) { ter[y][0]=t; ter[y][mxs]=t; }
            for (x=0;x<=mxs;x+=dx2) { ter[0][x]=t; ter[mys][x]=t; }
            }

        // adjust randomness
        // r=(r*100)>>8; if (r<2) r=2; r2=r>>1;
        r>>=1; if (r<2) r=2; r2=r>>1;
        }
    // rescale to <h0,h1>
    xx=ter[0][0]; yy=xx;
    for (y=0;y<mys;y++)
     for (x=0;x<mxs;x++)
        {
        z=ter[y][x];
        if (xx>z) xx=z;
        if (yy<z) yy=z;
        }
    for (y=0;y<mys;y++)
     for (x=0;x<mxs;x++)
      ter[y][x]=h0+(((ter[y][x]-xx)*(h1-h0))/(yy-xx));

    // [Surface]

    for (y=0;y<mys;y++)
     for (x=0;x<mxs;x++)
        {
        z=ter[y][x];
        // max slope [deg]
        a=atan2(ter[y][x+1]-z,d_pixel);
        b=atan2(ter[y+1][x]-z,d_pixel);
        if (a<b) a=b; a*=180.0/M_PI;

        c=_cover_none;
        if (z<=h_water) c=_cover_water;
        if (z>=h_snow ) c=_cover_snow;

        t=_terrain_dirt;
        if (z<=h_sand)  t=_terrain_sand;
        if (z>=h_rock)  t=_terrain_rock;
        if (a>=a_rock)  t=_terrain_rock;

        f=_flora_none;
        if (t==_terrain_dirt)
            {
            r=Random(100);
            if (r>10) f=_flora_grass;
            if (r>50)
                {
                if (z>h_evergreen) f=_flora_evergreen;
                else{
                    r=Random(h_evergreen);
                    if (r<=z) f=_flora_evergreen;
                    else      f=_flora_hardwood;
                    }
                }
            if (r<5) f=_flora_deadwood;
            }
        typ[y][x]=(c<<_cover_shift)|(t<<_terrain_shift)|(f<<_flora_shift);
        }

    // [copy data] rewrite this part to suite your needs it just compute color based on type of terrain and height
    // ter[][] is elevation in meters
    // typ[][] is surface type
/*
    for (y=0;y<_ys;y++)
     for (x=0;x<_xs;x++)
       pic.p[y][x].dd=(((ter[y][x]-h0)*255)/(h1-h0))*0x00010101;
    for (y=0;y<_ys;y++)
     for (x=0;x<_xs;x++)
        {
        r=typ[y][x];
        c=(r>>  _cover_shift)&  _cover_mask;
        t=(r>>_terrain_shift)&_terrain_mask;
        f=(r>>  _flora_shift)&  _flora_mask;
               r=_terrain[t];
        if (c) r=  _cover[c];
        if (c==_cover_water)
            {
            xx=256-((ter[y][x]<<7)/h0);
            yy=int(r>>16)&255; yy=(yy*xx)>>8; r=(r&0x0000FFFF)|(yy<<16);
            yy=int(r>> 8)&255; yy=(yy*xx)>>8; r=(r&0x00FF00FF)|(yy<< 8);
            yy=int(r    )&255; yy=(yy*xx)>>8; r=(r&0x00FFFF00)|(yy    );
            }
        if (f){ if (c) r|=_flora[f]; else r=_flora[f]; };
        pic.p[y][x+_xs].dd=r;
        }
*/    
    // free ter[][],typ[][]
    for (y=0;y<=mys;y++) delete[] ter[y]; delete[] ter; ter=NULL;
    for (y=0;y<=mys;y++) delete[] typ[y]; delete[] typ; typ=NULL;
    }

当前设置的输出是这样的:

[备注]

这种方法通常只会在岛上产生一个大山丘。 (内陆生成OK)如果你想要更多,你可以创建更多的地形图并将它们平均在一起。

我改为执行以下操作:我将中间点设置为最大高度并忽略第一个菱形通道。在第一个方形通道之后,我将中间点设置回某个随机值。这增加了更多中央山丘的可能性,然后只有一个。使用这种方法并添加光照(环境光 + 法线阴影)来预览和轻微调整像素大小 (35m) 我得到了这个结果:

在极少数情况下,这会生成像地图一样的内陆(如果中心区域太小。要处理它,您可以扫描角落寻找水。如果有土地再次生成或在第一遍中为中心点随机性添加一些偏差.

你可以玩代码,例如添加河流:

  1. 找到最高的山
  2. 获取随机位置close/around它
  3. 设置为河流类型
  4. 找到未设置为河流类型的最小高度相邻像素
  5. 如果它在地图边缘或设置为 sea/water 类型停止否则循环 #3

    如果你想要不止一条河流,那么不要忘记对已经完成的河流使用一些临时类型,这样算法才能正常工作。您还可以随着距起点的距离增加河流体积...结果如下:

    在此之后你还应该平衡形成的湖泊水位。