使用等离子体样式分形生成高度图时的人工制品

时间:2013-02-11 00:27:28

标签: c# terrain fractals procedural-generation heightmap

我今天花了几个小时来研究如何进行随机地形生成,在阅读了等离子体分形(中点位移和钻石平方算法)后,我决定尝试实施一个。我的结果实际上并不可怕,但我有这些可怕的方形/线条/网格类型的文物,我似乎无法摆脱它!

当渲染为灰度图像时,我的高度图看起来像: height map http://sphotos-d.ak.fbcdn.net/hphotos-ak-ash3/535816_10151739010123327_225111175_n.jpg

显然,涉及到相当多的代码,但我会尝试发布仅相关的代码。我没有发布将它变成纹理的代码,但不要担心我已经尝试用平滑渐变填充我的高度数组并且纹理很好:)

首先,我将地图的四个角设置为0到1之间的随机值,然后启动递归置换算法:

    public void GenerateTerrainLayer()
    {  
        //set the four corners of the map to have random values
        TerrainData[0, 0] = (float)RandomGenerator.NextDouble();
        TerrainData[GenSize, 0] = (float)RandomGenerator.NextDouble();
        TerrainData[0, GenSize] = (float)RandomGenerator.NextDouble();
        TerrainData[GenSize, GenSize] = (float)RandomGenerator.NextDouble();

        //begin midpoint displacement algorithm...
        MidPointDisplace(new Vector2_I(0, 0), new Vector2_I(GenSize, 0), new Vector2_I(0, GenSize), new Vector2_I(GenSize, GenSize));
    }

TerrainData只是一个浮点数*的2D数组。 Vector2_I只是我自己的整数向量类。最后四个函数是MidPointDisplace,它是递归函数,CalculateTerrainPointData平均2个数据值并添加一些噪声,CalculateTerrainPointData2平均4个数据值并添加一些噪声并具有稍高的比例值(它仅用于中心点),最后我的噪音函数,atm只是一些随机噪音而不是像perlin等真正的噪音。它们看起来像这样:

   private void MidPointDisplace(Vector2_I topleft, Vector2_I topright, Vector2_I bottomleft, Vector2_I bottomright)
    {
        //check size of square working on.. if its shorter than a certain amount stop the algo, we've done enough
        if (topright.X - topleft.X < DisplacementMaxLOD)
        {
            return;
        }


        //calculate the positions of all the middle points for the square that has been passed to the function
        Vector2_I MidLeft, MidRight, MidTop, MidBottom, Center;

        MidLeft.X = topleft.X;
        MidLeft.Y = topleft.Y + ((bottomleft.Y - topleft.Y) / 2);

        MidRight.X = topright.X;
        MidRight.Y = topright.Y + ((bottomright.Y - topright.Y) / 2);

        MidTop.X = topleft.X + ((topright.X - topleft.X) / 2);
        MidTop.Y = topleft.Y;

        MidBottom.X = bottomleft.X + ((bottomright.X - bottomleft.X) / 2);
        MidBottom.Y = bottomleft.Y;

        Center.X = MidTop.X;
        Center.Y = MidLeft.Y;

        //collect the existing data from the corners of the area passed to algo
        float TopLeftDat, TopRightDat, BottomLeftDat, BottomRightDat;

        TopLeftDat = GetTerrainData(topleft.X, topleft.Y);          
        TopRightDat = GetTerrainData(topright.X, topright.Y);          
        BottomLeftDat = GetTerrainData(bottomleft.X, bottomleft.Y);          
        BottomRightDat = GetTerrainData(bottomright.X, bottomright.Y);

        //and the center


        //adverage data and insert for midpoints..
        SetTerrainData(MidLeft.X, MidLeft.Y, CalculateTerrainPointData(TopLeftDat, BottomLeftDat, MidLeft.X, MidLeft.Y));
        SetTerrainData(MidRight.X, MidRight.Y, CalculateTerrainPointData(TopRightDat, BottomRightDat, MidRight.X, MidRight.Y));
        SetTerrainData(MidTop.X, MidTop.Y, CalculateTerrainPointData(TopLeftDat, TopRightDat, MidTop.X, MidTop.Y));
        SetTerrainData(MidBottom.X, MidBottom.Y, CalculateTerrainPointData(BottomLeftDat, BottomRightDat, MidBottom.X, MidBottom.Y));
        SetTerrainData(Center.X, Center.Y, CalculateTerrainPointData2(TopLeftDat, TopRightDat, BottomLeftDat, BottomRightDat, Center.X, Center.Y));


        debug_displacement_iterations++;

        //and recursively fire off new calls to the function to do the smaller squares
        Rectangle NewTopLeft = new Rectangle(topleft.X, topleft.Y, Center.X - topleft.X, Center.Y - topleft.Y);
        Rectangle NewTopRight = new Rectangle(Center.X, topright.Y, topright.X - Center.X, Center.Y - topright.Y);
        Rectangle NewBottomLeft = new Rectangle(bottomleft.X, Center.Y, Center.X - bottomleft.X, bottomleft.Y - Center.Y);
        Rectangle NewBottomRight = new Rectangle(Center.X , Center.Y, bottomright.X - Center.X, bottomright.Y - Center.Y);

        MidPointDisplace(new Vector2_I(NewTopLeft.Left, NewTopLeft.Top), new Vector2_I(NewTopLeft.Right, NewTopLeft.Top), new Vector2_I(NewTopLeft.Left, NewTopLeft.Bottom), new Vector2_I(NewTopLeft.Right, NewTopLeft.Bottom));
        MidPointDisplace(new Vector2_I(NewTopRight.Left, NewTopRight.Top), new Vector2_I(NewTopRight.Right, NewTopRight.Top), new Vector2_I(NewTopRight.Left, NewTopRight.Bottom), new Vector2_I(NewTopRight.Right, NewTopRight.Bottom));
        MidPointDisplace(new Vector2_I(NewBottomLeft.Left, NewBottomLeft.Top), new Vector2_I(NewBottomLeft.Right, NewBottomLeft.Top), new Vector2_I(NewBottomLeft.Left, NewBottomLeft.Bottom), new Vector2_I(NewBottomLeft.Right, NewBottomLeft.Bottom));
        MidPointDisplace(new Vector2_I(NewBottomRight.Left, NewBottomRight.Top), new Vector2_I(NewBottomRight.Right, NewBottomRight.Top), new Vector2_I(NewBottomRight.Left, NewBottomRight.Bottom), new Vector2_I(NewBottomRight.Right, NewBottomRight.Bottom));

    }

    //helper function to return a data value adveraged from two inputs, noise value added for randomness and result clamped to ensure a good value
    private float CalculateTerrainPointData(float DataA, float DataB, int NoiseX, int NoiseY)
    {
         return MathHelper.Clamp(((DataA + DataB) / 2.0f) + NoiseFunction(NoiseX, NoiseY), 0.0f, 1.0f) * 1.0f;
    }

    //helper function to return a data value adveraged from four inputs, noise value added for randomness and result clamped to ensure a good value
    private float CalculateTerrainPointData2(float DataA, float DataB, float DataC, float DataD, int NoiseX, int NoiseY)
    {
        return MathHelper.Clamp(((DataA + DataB + DataC + DataD) / 4.0f) + NoiseFunction(NoiseX, NoiseY), 0.0f, 1.0f) * 1.5f;
    }

    private float NoiseFunction(int x, int y)
    {
        return (float)(RandomGenerator.NextDouble() - 0.5) * 0.5f;
    }

好的,谢谢花时间去看 - 希望有人知道这个网格状模式出现在哪里:)

*编辑 - 意外写入整数,更正为花车

3 个答案:

答案 0 :(得分:2)

我在您的代码中发现了3个问题。 (其中2个是相关的)

你不要缩减每一步的随机性。每一步都必须减少随机性。否则你会得到白色( - )噪音。你选择一个因子(0.5-0.7对我来说工作得很好)并在每次递归中将减少乘以alpha,然后按照该因子缩放生成的随机数。

你交换了钻石和方形步骤。首先是钻石,然后是正方形。反过来是不可能的(见下)。

您的方形步骤仅使用一个方向上的点。这个可能导致你正在谈论的矩形结构。方块必须将值平均到四个边。这意味着平方步长取决于钻石步骤产生的点。而且不仅是您当前正在查看的矩形的菱形步骤,还有它旁边的矩形。对于地图之外的值,您可以换行,使用固定值或仅平均3个值。

答案 1 :(得分:1)

我在CalculateTerrainPointData实施中发现了一个问题:您没有在每次迭代时缩小NoiseFunction的结果。

请参阅Midpoint Displacement算法的this description

  
      
  • 从单个水平线段开始。   
        
    • 重复足够多次:   
          
      • 重复场景中的每个线段:   
            
        • 找到线段的中点。
        •   
        • 以Y为单位移动Y中点。
        •   
        • 缩小随机数的范围。
        •   
      •   
    •   
  •   

在代码中执行此操作而不会进行太多更改的快速方法是向scale添加一些MidPointDisplace参数(默认设置为1.0f)和CalculateTerrainPointData;在CalculateTerrainPointData中使用它来乘以NoiseFunction的结果;并通过每次递归调用MidPointDisplace(..., 0.5f * scale)来减少它。

不确定这是否是图片看起来错误或存在其他问题的唯一原因。

答案 2 :(得分:0)

根据维基百科的summary of midpoint displacement,只有中心点的平均值会增加噪音 - 尝试只通过CalculateTerrainPointData2&amp;添加噪音。消除CalculateTerrainPointData中的噪音。