【问题标题】:Perlin terrain pages mismatched: elusive bugPerlin 地形页面不匹配:难以捉摸的错误
【发布时间】:2012-07-12 07:40:34
【问题描述】:

我一直在 Unity 游戏引擎中开发一款游戏,我希望能够将分页地形用于无限世界(当今的常见主题)。我的地形生成器专门使用柏林噪声。但是此时,开发已经被一个错误严重拖慢:页面边缘不匹配,甚至不匹配。问题出在我生成地形的代码中,或者可能在每次我完成生成页面时运行的另一段代码中。但是问题肯定不在unity函数调用上,所以即使你不熟悉unity也可以帮助我。

可能的原因... 1) 为每个页面的错误位置采样 Perlin 噪声,数学失败。 2) 随机数生成器在页面生成之间重新播种。 3) 在页面生成之间重新植入 Perlin 噪声。 4) ?我不知道。

注意:我几乎排除了数字 2 和 3,查看所有其他代码。我每次只为生成器播种一次,Unity 不会碰巧在生成器运行之间重新播种 Random。

实际上,如果您能描述一种可以使这更容易的调试方法,请提供建议。从长远来看,这对我的帮助会更大。

这是我为每个页面运行一次的代码,带有完整的注释,因此您不必了解 Unity3D...

 /* x and y are the indices of the tile being loaded. The game maintains a
    square of pages loaded, where the number of pages per side is equal to
    loadSquares (see below). x and y are the indices of the page to generate
    within that square. */
    public void generate(int x, int y)
    {
         /* pagePos represents the world x and y coordinates of the bottom left
            corner of the page being generated. This is given by...
              new Vector2((-(float)loadSquares / 2.0f + x + xCoord) * tileSize, 
                          (-(float)loadSquares / 2.0f + y + zCoord) * tileSize);
            This is because the origin tile's center is at 0, 0. xCoord represents
            the tile that the target object is on, which is what the loaded square
            is centered around. tileSize is the length of each side of each tile.
         */
        Vector2 pagePos = getPagePos(x, y);
        //Here I get the number of samples x and y in the heightmap.
        int xlim = td[x,y].heightmapWidth;
        int ylim = td[x,y].heightmapHeight;
            //The actual data
        float[,] array = new float[xlim, ylim];
            //These will represent the minimum and maximum values in this tile.
            //I will need them to convert the data to something unity can use.
        float min = 0.0f;
        float max = 0.0f;
        for(int cx = 0; cx < xlim; cx++)
        {
            for(int cy = 0; cy < ylim; cy++)
            {
                //Here I actually sample the perlin function.
                //Right now it doesn't look like terrain (intentionally, testing).
                array[cx,cy] = sample(
                    new Vector3((float)cx / (float)(xlim - 1) * tileSize + pagePos.x, 
                                (float)cy / (float)(ylim - 1) * tileSize + pagePos.y, 
                    122.79f));
                //On the first iteration, set min and max
                if(cx == 0 && cy == 0)
                {
                    min = array[cx,cy];
                    max = min;
                }
                else
                {
                    //update min and max
                    min = Mathf.Min(min, array[cx,cy]);
                    max = Mathf.Max(max, array[cx,cy]);
                }
            }
        }
        //Set up the Terrain object to receive the data
        float diff = max != min ? max - min : 10.0f;
        tr[x,y].position = new Vector3(pagePos.x, min, pagePos.y);
        td[x,y].size = new Vector3(tileSize, diff, tileSize);
        //Convert the data to fit in the Terrain object
     /* Unity's terrain only accepts values between 0.0f and 1.0f. Therefore,
        I shift the terrain vertically in the code above, and I
        squish the data to fit below.
     */
        for(int cx = 0; cx < xlim; cx++)
        {
            for(int cy = 0; cy < ylim; cy++)
            {
                array[cx,cy] -= min;
                array[cx,cy] /= diff;
            }
        }
        //Set the data in the Terrain object
        td[x,y].SetHeights(0, 0, array);
    }
}

有趣且可能很重要的细节:瓷砖与 x/z 方向上相邻角落的瓷砖正确连接。只要瓷砖没有相同的 x-to-z 增量,就会发生采样偏移。在下图中,右侧图块是另一个图块的 +x 和 +z。具有这种关系的所有图块都已正确连接。

这是以 zip 格式上传的项目文件。告诉我它是否不起作用或什么... http://www.filefactory.com/file/4fc75xtd3yzl/n/FPS_zip

要查看地形,如果没有从那里开始,请在切换到 testScene 后按 Play。 GameObject 生成数据和地形对象(它具有来自 scripts/General/ 附加的 RandomTerrain 脚本)。您可以从那里将参数修改为柏林噪声。请注意,现在只有第一个 perlin 八度音程 o_elevator 在地形生成中处于活动状态。为了解决这个故障,所有其他公共 perlin octave 变量都没有影响。

【问题讨论】:

  • sample 是做什么的?阅读它似乎对理解代码至关重要。我的理解是,为了对齐页面,您需要使sample“合作”。
  • sample 返回 perlin 函数在给定坐标处的值。这些坐标是在世界空间中提供的,因此每个地形块仅对世界坐标中的结果进行采样,这应该会产生无缝地形。但有些不对劲。 sample() 的参数可能有问题,但 sample() 经测试正常运行。本次测试中采样的perlin函数幅度为5,波长相同。
  • 函数看起来正确。还有什么可能影响结果的吗? sample 正确吗?修改后的结果吗? getPagePos 正确吗?
  • 样本是正确的。 getPagePos 是正确的,否则瓷砖会水平错位(它们不是)。这里发生了一件非常奇怪的事情。也许它与 TerrainData.heightmapscale 有关?
  • 你能把统一项目上传到某个地方以便查看吗?

标签: c# debugging unity3d terrain perlin-noise


【解决方案1】:

TL;DR:您需要删除 getTargetCoords 并使用 tr[x,y].position = new Vector3(pagePos.y, 0.0f, pagePos.x)

如何达到上述目标:一次只在 1 个维度上工作。这意味着转动它(从您附加的统一代码):

            array[cx,cy] = sample(
                new Vector3((float)cx * tileSize / (float)(xlim - 1) + pagePos.x, 0.0f,
                            (float)cy * tileSize / (float)(ylim - 1) + pagePos.y));

进入这个:

            array[cx,cy] = sample(
                new Vector3(
                    (float)cx * tileSize / (float)(xlim - 1) + pagePos.x,
                    0.0f,
                    0.0f
                ) 
            ); 

我很快意识到你的getPagePos 有问题,因为它添加了来自getTargetCoords 的值,并且该函数使用了由getPagePos 函数设置的位置!圈内圈子,伙计们。

protected void getTargetCoords()
{
    xCoord = Mathf.RoundToInt(target.position.x / tileSize);
    zCoord = Mathf.RoundToInt(target.position.z / tileSize);
}

所以让我们失去那个函数和对它的引用。事实上,让我们暂时简化getPagePos

protected Vector2 getPagePos(int x, int y)
{
    return new Vector2( 0.0f,0.0f);
}

天哪,TAWNOS 疯了吗?!嗯,是的,但那是无关紧要的。假设您的示例函数是正确的(我根据您的断言授予您的假设),那么我们应该从获取简单值(零!)开始,并通过添加的每个附加假设进行验证。

简化了getPagePos,让我们再看看(float)cx * tileSize / (float)(xlim - 1) + pagePos.x。它想做什么?好吧,它似乎试图通过从图块的原点偏移来找到当前样本的世界坐标点。让我们验证这是一个真实的假设:有xLim 个样本分布在tileSize 个单位上,因此世界坐标中的每个步长为tileSize / xLim。将它乘以当前值将得到我们的偏移量,所以这段代码看起来是正确的。

现在,让我们获得一个简单的平铺偏移,然后看看所有内容都匹配。我不会尝试将下降置于所有图块的中间,而是进行简单的网格偏移(稍后可以调整以恢复居中)。

    return new Vector2( 
        tileSize * (float)x,
        0.0f
    );

当您运行此程序时,您可能会很快发现问题所在。底边映射它旁边的瓦片的顶边,而不是右边匹配左边。如果不深入研究,这可能是由您的采样方法的工作方式引起的。我不知道。以下是更新后的函数:

protected Vector2 getPagePos(int x, int y)
{
    float halfTile = loadSquares / 2.0f;
    return new Vector2( 
        (-halfTile * tileSize) + (x * tileSize),
        (-halfTile * tileSize) + (y * tileSize)
    );
}

public void generate(int x, int y)
{
    if(x >= loadSquares || x < 0 || y >= loadSquares || y < 0) return;
    t[x,y] = Terrain.CreateTerrainGameObject(new TerrainData()).GetComponent<Terrain>();
    t[x,y].name = "Terrain [" + x + "," + y + "]";
    td[x,y] = t[x,y].terrainData;
    td[x,y].heightmapResolution = resolution;

    tr[x,y] = t[x,y].transform;
    Vector2 pagePos = getPagePos(x, y);
    //Actual data generation happens here.
    int xLim = td[x,y].heightmapWidth;
    int yLim = td[x,y].heightmapHeight;

    float[,] array = new float[xLim, yLim];
    float min = int.MaxValue;
    float max = int.MinValue;
    for(int cx = 0; cx < xLim; cx++)
    {
        for(int cy = 0; cy < yLim; cy++)
        {
            array[cx,cy] = sample(
                new Vector3(
                    (float)cx * (tileSize / (float)(xLim - 1)) + pagePos.x, 
                    0.0f,
                    (float)cy * (tileSize / (float)(yLim - 1)) + pagePos.y
                ) 
            ); 

            if(min > array[cx,cy]) 
                min = array[cx,cy];
            if(max < array[cx,cy]) 
                max = array[cx,cy];

        }
    }

    //Set up the Terrain object to receive the data
    float diff = max != min ? max - min : 10.0f;
    tr[x,y].position = new Vector3(pagePos.y, min, pagePos.x);
    td[x,y].size = new Vector3(tileSize, diff, tileSize);

    //Convert the data to fit in the Terrain object
    for(int cx = 0; cx < xLim; cx++)
    {
        for(int cy = 0; cy < yLim; cy++)
        {
            array[cx,cy] -= min;
            array[cx,cy] /= diff;
        }
    }
    //Set the data in the Terrain object
    td[x,y].SetHeights(0, 0, array);
}

【讨论】:

  • 然后我已经扭转了一些事情。我将尝试反转我传递给样本的值。会不会是统一的地形 z 坐标系统是颠倒的,就像在计算机图形学中一样?
  • 实际上,当它们以这种方式定位时,它们似乎都合并为一个。我看不出我的代码和你的代码有什么大的区别……
  • 好吧,我不知道你是怎么解决的,但我会在查看确切差异时弄清楚。顺便说一句,getTargetCoords 确定了目标正在走过的图块,这意味着游戏可以确定要加载哪些图块。无论如何,非常感谢!
  • 把 getTargetCoords 放回去,重写到 getPagePos 函数中。我试过了,它有效。你设法解决了一些非常非常奇怪的问题,我看不出有什么不同。
  • 我不得不花点时间用笔和纸或调试器来检查你是如何产生噪音的,但我怀疑向量处理中有问题,那里。我主要关心的是解决提出的问题。感谢您的接受:)
猜你喜欢
  • 2011-10-07
  • 1970-01-01
  • 2011-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多