Unity 中程序生成的无限世界中不同 LOD 块之间的错位问题

问题描述 投票:0回答:1

我正在开展一个个人项目,以提高自己并学习更高级的东西,但我目前陷入困境,无法找到任何解决方案。基本上,我有两个独立的问题,其中一个问题的解决方案会带来另一个问题,它们是:

  • 两组不同的 LOD 块之间的噪声转移(随着玩家远离世界中心,此问题的可观察到的影响逐渐变得更加突出):

“线”的右侧是较高详细的块,左侧是较低详细的块。

  • 较低详细块之间的错位(偏移计算的变化解决了移位问题,但随后导致了较低详细块问题的错位):

这在播放模式中也非常明显,无论较低的详细块有多远(例如网格中块之间的空白空间):

下面的代码是我在每个块生成时添加到它们的脚本,其中包括网格生成和 LOD 系统:

using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshCollider))]
public class Chunk : MonoBehaviour
{
    private ChunkGenerator chunkGenerator;
    private MeshRenderer meshRenderer;
    private MeshFilter meshFilter;
    private MeshCollider meshCollider;

    private bool isActive = false;
    private TerrainSettings terrainSettings;

    private Mesh chunkMesh;
    private bool isMeshGenerated = false;

    private Mesh LODMesh;
    private bool isLODMeshGenerated = false;

    public Vector2Int ChunkCoord { get; private set; }

    // adds all the necessary components
    public void Initialize(Vector2Int chunkCoord, ChunkGenerator generator)
    {
        ChunkCoord = chunkCoord;
        chunkGenerator = generator;

        terrainSettings = generator.terrainSettings;

        if (!TryGetComponent<MeshFilter>(out meshFilter))
        {
            meshFilter = gameObject.AddComponent<MeshFilter>();
        }

        if (!TryGetComponent<MeshRenderer>(out meshRenderer))
        {
            meshRenderer = gameObject.AddComponent<MeshRenderer>();
        }

        if (!TryGetComponent<MeshCollider>(out meshCollider))
        {
            meshCollider = gameObject.AddComponent<MeshCollider>();
        }

        isMeshGenerated = false;
    }

    private void Update()
    {
        UpdateLODMeshes();
    }

    public void Activate()
    {
        if (!isActive)
        {
            gameObject.SetActive(true);

            UpdateLODMeshes();

            isActive = true;
        }
    }

    // very simple LOD logic
    private void UpdateLODMeshes()
    {
        Vector2Int playerChunk = chunkGenerator.GetPlayerChunk();
        int distanceX = Mathf.Abs(ChunkCoord.x - playerChunk.x);
        int distanceZ = Mathf.Abs(ChunkCoord.y - playerChunk.y);
        int lodDistance = chunkGenerator.LODDistance;

        if (distanceX <= lodDistance && distanceZ <= lodDistance)
        {
            if (!isMeshGenerated)
            {
                chunkMesh = GenerateMesh(1);
                isMeshGenerated = true;
                SetMaterial(terrainSettings.terrainMaterial);
            }
            SetMesh(chunkMesh);
            SetChunkScale(1);
        }
        else
        {
            if (!isLODMeshGenerated)
            {
                LODMesh = GenerateMesh(2);
                isLODMeshGenerated = true;
                SetMaterial(terrainSettings.terrainMaterial);
            }
            SetMesh(LODMesh);
            SetChunkScale(1.0225f);
        }
    }

    public void Deactivate()
    {
        if (isActive)
        {
            gameObject.SetActive(false);
            isActive = false;
        }
    }

    // mesh generation
    public Mesh GenerateMesh(int resolution)
    {
        Mesh mesh = new();
        int chunkSize = chunkGenerator.chunkSize;

        // offset calculation
        Vector2Int offset = new(ChunkCoord.x * (chunkSize - resolution), ChunkCoord.y * (chunkSize - resolution));

        // noise map generation
        float[,] noiseMap = NoiseGenerator.GenerateNoiseMap(
            terrainSettings.noiseScale, terrainSettings.frequency, terrainSettings.octaves, terrainSettings.persistence, terrainSettings.lacunarity,
            terrainSettings.groundLevel, terrainSettings.groundFlatness, terrainSettings.mountainLevel, terrainSettings.mountainFlatness,
            terrainSettings.mountainHeightMultiplier, terrainSettings.generateIslands, terrainSettings.islandFrequency,
            terrainSettings.islandRadiusRandomizer, terrainSettings.islandRadius, terrainSettings.islandHeightMultiplier, chunkSize, chunkSize, offset
        );

        // generating mesh data
        GenerateMeshData(chunkSize, resolution, out Vector3[] vertices, out Vector2[] uvs, out int[] triangles, noiseMap);

        mesh.vertices = vertices;
        mesh.uv = uvs;
        mesh.triangles = triangles;

        mesh.RecalculateBounds();
        mesh.RecalculateNormals();
        meshCollider.sharedMesh = mesh;

        return mesh;
    }

    // mesh data generation
    public void GenerateMeshData(int chunkSize, int resolution, out Vector3[] vertices, out Vector2[] uvs, out int[] triangles, float[,] noiseMap)
    {
        int reducedSize = chunkSize / resolution;
        int numVertices = reducedSize * reducedSize;
        int numTriangles = (reducedSize - 1) * (reducedSize - 1) * 6;

        vertices = new Vector3[numVertices];
        uvs = new Vector2[numVertices];
        triangles = new int[numTriangles];

        int vertexIndex = 0;
        int triangleIndex = 0;

        // vertex generation
        for (int y = 0; y < reducedSize; y++)
        {
            for (int x = 0; x < reducedSize; x++)
            {
                int xPos = x * resolution;
                int zPos = y * resolution;

                float yPos = noiseMap[xPos, zPos] * terrainSettings.heightScale;

                vertices[vertexIndex] = new Vector3(xPos, yPos, zPos);
                uvs[vertexIndex] = new Vector2(x / (float)(reducedSize - 1), y / (float)(reducedSize - 1));

                // triangle generation
                if (x < reducedSize - 1 && y < reducedSize - 1)
                {
                    // Define the indices of the vertices for the triangles
                    int topLeft = vertexIndex;
                    int topRight = vertexIndex + 1;
                    int bottomLeft = vertexIndex + reducedSize;
                    int bottomRight = vertexIndex + reducedSize + 1;

                    triangles[triangleIndex] = topLeft;
                    triangles[triangleIndex + 1] = bottomLeft;
                    triangles[triangleIndex + 2] = topRight;
                    triangles[triangleIndex + 3] = topRight;
                    triangles[triangleIndex + 4] = bottomLeft;
                    triangles[triangleIndex + 5] = bottomRight;

                    triangleIndex += 6;
                }

                vertexIndex++;
            }
        }
    }

    private void SetMesh(Mesh mesh)
    {
        meshFilter.mesh = mesh;
    }

    private void SetMaterial(Material material)
    {
        if (meshRenderer != null)
        {
            meshRenderer.material = material;
        }
    }

    private void SetChunkScale(float scale)
    {
        transform.localScale = new(scale, 1, scale);
    }
}

当我进行偏移计算时,就会出现问题。

  • 此计算会导致移位问题:
Vector2Int offset = new(ChunkCoord.x * (chunkSize - resolution), ChunkCoord.y * (chunkSize - resolution));
  • 此计算会导致错位问题:
Vector2Int offset = new(ChunkCoord.x * (chunkSize - 1), ChunkCoord.y * (chunkSize - 1));

另外,下面的代码是噪声生成脚本:

using UnityEngine;

public static class NoiseGenerator
{
    private const float Half = 0.5f;
    private const float Zero = 0f;
    private const float One = 1f;

    public static float[,] GenerateNoiseMap(TerrainSettings settings, int mapWidth, int mapHeight, Vector2 offset)
    {
        float[,] noiseMap = new float[mapWidth, mapHeight];
        float invNoiseScale = One / settings.noiseScale;

        float halfWidth = mapWidth * Half;
        float halfHeight = mapHeight * Half;

        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                float sampleX = (x - halfWidth + offset.x) * invNoiseScale;
                float sampleY = (y - halfHeight + offset.y) * invNoiseScale;

                float amplitude = One;
                float frequency = One;
                float noiseHeight = Zero;

                for (int i = 0; i < settings.octaves; i++)
                {
                    float perlinValue = Mathf.PerlinNoise(sampleX * frequency, sampleY * frequency);
                    noiseHeight += perlinValue * amplitude;

                    amplitude *= settings.persistence;
                    frequency *= settings.lacunarity;
                }

                float groundFlatness = Mathf.Clamp01((settings.groundLevel - noiseHeight) * settings.groundFlatness);
                float groundSmoothTransition = SmoothStep(Zero, One, groundFlatness);

                noiseHeight = Mathf.Lerp(noiseHeight, settings.groundLevel, groundSmoothTransition);

                float mountainFlatness = Mathf.Clamp01((noiseHeight - settings.mountainLevel) * settings.mountainFlatness);
                float mountainSmoothTransition = SmoothStep(Zero, One, mountainFlatness);

                noiseHeight = Mathf.Lerp(noiseHeight, settings.mountainLevel, mountainSmoothTransition);

                if (noiseHeight > settings.mountainLevel)
                {
                    float mountainMultiplier = settings.mountainHeightMultiplier * (noiseHeight - settings.mountainLevel);
                    noiseHeight += mountainMultiplier;
                }

                if (settings.generateIslands)
                {
                    float islandNoise = Mathf.PerlinNoise(sampleX * settings.islandFrequency, sampleY * settings.islandFrequency);
                    float islandMultiplier = Mathf.Clamp01(islandNoise - settings.islandRadiusRandomizer) * settings.islandRadius;

                    noiseHeight += islandMultiplier * settings.islandHeightMultiplier;
                }

                noiseMap[x, y] = noiseHeight;
            }
        }

        return noiseMap;
    }

    private static float SmoothStep(float edge0, float edge1, float x)
    {
        x = Mathf.Clamp01((x - edge0) / (edge1 - edge0));
        return x * x * (3 - 2 * x);
    }
}

我知道我的代码有点混乱而且不是很优化,但我真的被困在这里,在解决这个问题之前我无法继续处理这个问题。我知道“解决方案”并不是真正的解决方案,因为它们带来了另一个问题。我尝试了偏移计算并对方程式尝试了太多东西,但我知道这不能通过猜测并希望找到正确的方程式来解决。我知道存在一个严重错误,但我找不到它,我需要帮助。预先感谢您的所有帮助。

c# unity-game-engine unityscript terrain procedural-generation
1个回答
0
投票

在我发布问题后不久,我找到了解决方案。我确信问题要么出在偏移计算中,要么出在噪声产生中。值得庆幸的是,在我遇到这是另一篇相关文章之后,我变得足够聪明,可以寻找其他东西。

在我实现 LOD 系统之前,我的块生成器中存在“块之间的空白空间”问题,因此我通过以下块位置计算(通过从块大小中减去 1)过早地解决了它:

float posX = x * (chunkSize - 1);
float posZ = z * (chunkSize - 1);

由于我只处理一级细节,因此以这种方式“解决”它是有意义的。解决方案是从计算中删除“- 1”并替换“Chunk”脚本中的这一行:

...
Mesh mesh = new();
int chunkSize = chunkGenerator.chunkSize;

Vector2Int offset = new(ChunkCoord.x * (chunkSize - 1), ChunkCoord.y * (chunkSize - 1));
...

有了这个:

...
Mesh mesh = new();
int chunkSize = chunkGenerator.chunkSize + resolution;

Vector2Int offset = new(ChunkCoord.x * (chunkSize - resolution), ChunkCoord.y * (chunkSize - resolution));
...

在偏移计算中用“chunkSize”减去“分辨率”是正确的方法,但我们还向 chunkSize 本身添加“分辨率”,以防止问题中列出的所有问题。

© www.soinside.com 2019 - 2024. All rights reserved.