Suavizado entre trozos

Así que he estado trabajando en un juego en unidad y quiero expandir mi mundo de un mapa de 150x150 a un mundo de procedimientos aparentemente infinito. Mi plan es usar Perlin Noise como base y usar los diferentes valores de 0-1 para determinar el tipo de terreno. El problema con el que me encuentro es cuando saco mis fragmentos y los compenso en consecuencia, mis fragmentos no se alinean correctamente, lo que rompe la ilusión de un mundo infinito.

(visto aquí)

broken chunks


WorldChunk.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;

[System.Serializable]
public class WorldChunk
{
    public int2 Position;
    public int[,] Data;
    public float[,] Sample;

    public WorldChunk(int chunkSize = 16){
        Data = new int[chunkSize, chunkSize];
        Sample = new float[chunkSize, chunkSize];
    }
}

WorldGenerator.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;

public class WorldGenerator : MonoBehaviour
{

    // Base World Data
    public int ChunkSize = 75;
    public string Seed = "";
    [Range(1f, 40f)]
    public float PerlinScale = 10f;
    // Pseudo Random Number Generator
    private System.Random pseudoRandom;

    // Chunk Data Split into Sections (Each Chunk having Coords (x, y))
    public Dictionary<string, WorldChunk> chunks = new Dictionary<string, WorldChunk>();


    //============================================================
    // Set Warm-Up Data
    //============================================================
    private void Awake() {
        // Get/Create Seed
        if (Seed == ""){
            Seed = GenerateRandomSeed();
        }
        // Get Random Number Generator
        pseudoRandom = new System.Random(Seed.GetHashCode());
        // Using to Clear while Making Test Adjustments
        chunks.Clear();
        // Generate Starting Chunk
        for (int x = -1; x <= 1; x++)
        {
            for (int y = -1; y <= 1; y++)
            {
                // Draw Test Chunks
                GenerateChunk(x, y);
            }
        }
    }

    //============================================================
    // Generation Code
    //============================================================

    // ===
    //  Create New Chunks
    // ===
    public void GenerateChunk(int x, int y){
        // Set Key to use
        string key = $"{x},{y}";
        // Check if key exists if not Generate New Chunk
        if (!chunks.ContainsKey(key)){
            // Add Chunk, Set Position in chunk grid (for calling and block data later), Then Generate data
            chunks.Add(key, new WorldChunk(ChunkSize));
            chunks[key].Position = new int2(x, y);
            GenerateChunkData(chunks[key]);
        }
    }

    // ===
    //  Fill Chunks with Perlin Data
    // ===
    private void GenerateChunkData(WorldChunk chunk){
        // Set Offsets
        float xOffset = (float)chunk.Position.x * ChunkSize;
        float yOffset = (float)chunk.Position.y * ChunkSize;
        // Set Data to Chunk
        for (int x = 0; x < ChunkSize; x++)
        {
            for (int y = 0; y < ChunkSize; y++)
            {
                // Get Perlin Map
                float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
                float py = (float)(y) / ChunkSize * PerlinScale + yOffset;

                // Set Temp Sample For Testing (This will change for Map Data (Hills and Water) later)
                chunk.Sample[x,y] = Mathf.PerlinNoise(px, py);
            }
        }
    }

    // ===
    //  Generate Random Seed of Length
    // ===
    private string GenerateRandomSeed(int maxCharAmount = 10, int minCharAmount = 10){
        //Set Characters To Pick from
        const string glyphs= "abcdefghijklmnopqrstuvwxyz0123456789";
        //Set Length from min to max
        int charAmount = UnityEngine.Random.Range(minCharAmount, maxCharAmount);
        // Set output Variable
        string output = "";
        // Do Random Addition
        for(int i=0; i<charAmount; i++)
        {
            output += glyphs[UnityEngine.Random.Range(0, glyphs.Length)];
        }
        // Output New Random String
        return output;
    }

    //============================================================
    // Draw Example
    //============================================================

    private void OnDrawGizmos() {
        // Do this because I'm lazy and don't want to draw pixels to generated Sprites
        Awake();
        // For Each WorldChunk in the chunk Data
        foreach (WorldChunk c in chunks.Values)
        {
            // Check if it exists (Foreach is stupid sometimes... When live editing)
            if (c != null){
                // Get World Positions for Chunk (Should probably Set to a Variable in the Chunk Data)
                Vector3 ChunkPosition = new Vector3(c.Position.x * ChunkSize, c.Position.y * ChunkSize);

                // For Each X & For Each Y in the chunk
                for (int x = 0; x < ChunkSize; x++)
                {
                    for (int y = 0; y < ChunkSize; y++)
                    {
                        // Get Cell position
                        Vector3 cellPos = new Vector3((ChunkPosition.x - ChunkSize/2f) + x, (ChunkPosition.y - ChunkSize/2f) + y);
                        // Get Temp Sample and set to color
                        float samp = c.Sample[x,y];
                        Gizmos.color = new Color(samp, samp, samp);
                        // Draw Tile as Sample black or white.
                        Gizmos.DrawCube(cellPos, Vector3.one);
                    }
                }

                // Size for Cubes
                Vector3 size = new Vector3(ChunkSize, ChunkSize, 1f);
                // Set Color Opaque Green
                Gizmos.color = new Color(0f, 1f, 0f, 0.25f);
                // Draw Chunk Borders (Disable to show issue)
                // Gizmos.DrawWireCube(ChunkPosition, size);
                
            }
        }
        
    }
}

Me gustaría señalar cuando uso:

// Get Perlin Map
float px = (float)(x + xOffset) / ChunkSize * PerlinScale;
float py = (float)(y + yOffset) / ChunkSize * PerlinScale;

En lugar de

// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;

Todo se alinea correctamente, pero el ruido perlin se repite.

¿Cuál sería la mejor manera de suavizar entre los trozos para que todo coincida? ¿Existe una mejor manera de escribir esto?

EDITAR:


¡Gracias por la ayuda Draykoon D! aquí está la información actualizada y los enlaces a los scripts actualizados en pastebin si alguien los necesita.

example

Aquí está el código de actualización para cualquiera que lo desee: ** WorldGenerator.cs **


https://pastebin.com/3BjLy5Hk

** WorldGenerator.cs **


https://pastebin.com/v3JJte3N

¡Espero que eso ayude!

1
SaveZeQueen 14 ago. 2020 a las 08:44

2 respuestas

La mejor respuesta

La palabra clave que busca es enlosable.

Pero tengo una gran noticia para usted, las funciones de ruido como el perlin son de naturaleza periódica. Entonces, en lugar de llamar a ChunckSize * ChunkSize una función de ruido, solo debe llamarla una vez y luego dividir los resultados.

Te aconsejo que leas este excelente tutorial:

https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-1D-noise

3
Draykoon D 14 ago. 2020 a las 07:01
  1. No utilices el ruido de Perlin. Tiene un fuerte sesgo hacia las direcciones de 45 y 90 grados. Tus colinas están todas alineadas con estas y no están orientadas en una variedad de direcciones más interesante. Puede usar Unity.mathematics.noise.snoise(float2) pero su período de repetición es bastante pequeño y puede que no sea muy rápido si no está usando trabajos de Unity Burst. esto es lo que creé / uso / recomiendo, pero es ciertamente no es la única opción que existe! Tenga en cuenta que todos estos ruidos tienen un rango de -1 a 1 en lugar de 0 a 1, así que si eso es importante, haga value=value*0.5+0.5; para cambiar la escala.

  2. Ahora que eso está fuera del camino, para resolver su problema, debe separar la idea de fragmentos y generación. Esta es una buena idea en general, y siempre creo en ocultar los detalles de implementación del backend (por ejemplo, fragmentos) del juego tanto como sea posible (por ejemplo, evitar los límites visibles). Cada vez que genere un fragmento, debe encontrar su coordenada de inicio en el mundo, de modo que las coordenadas continúen sin problemas con el resto. Por ejemplo, si los fragmentos son 128x128, entonces el fragmento que comienza en (0, 0) debe tener la coordenada inicial (0, 0), entonces el fragmento que comienza en (0, 1) debe tener la coordenada inicial (0, 128). Solo entonces, convierta una coordenada mundial en una coordenada de ruido multiplicándola por la frecuencia deseada.

1
KdotJPG 15 ago. 2020 a las 19:02