简体   繁体   中英

How to smooth between multiple perlin noise chunks?

Smoothing Between Chunks

So I've been working on a game in unity and want to expand my world from a 150x150 map into a seemingly infinite procedural world. My plan is to use Perlin Noise as the base and use the different values from 0-1 to determine the terrain type. The issue I'm running into is when I draw out my chunks and offset accordingly my chunks do not line up correctly, which kind of break the illusion of an infinite world.

(seen here)

碎块


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);
                
            }
        }
        
    }
}

I would like to point out when I use:

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

instead of

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

Everything aligns up correctly but the perlin noise just repeats.

What would be the best way for me to smooth between the chunks so that everything matches up? Is there a better way to write this maybe?

EDIT:


Thanks for the help Draykoon D! here is the updated info and links to the updated scripts on pastebin if anyone needs them!

例子

Here is the update code for anyone who wants it: ** WorldGenerator.cs**


https://pastebin.com/3BjLy5Hk

** WorldGenerator.cs**


https://pastebin.com/v3JJte3N

Hope that helps!

The key word you are looking for is tileable.

But I have a great news for you, noise function such as perlin are periodic in nature. So instead of calling ChunckSize * ChunkSize a noise function you should only call it once and then divide the results.

I will advice you to read this excellent tutorial:

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

  1. Don't use Perlin noise. It has heavy bias towards the 45 and 90 degree directions. Your hills are all aligned to these, and aren't oriented along a more interesting variety of directions. You could use Unity.mathematics.noise.snoise(float2) but its repeat period is rather small, and it might not be very fast if you aren't using Unity Burst jobs. this is what I created/use/recommend , but it's certainly not the only option out there, Note that all these noises are range -1 to 1 rather than 0 to 1, so if that's important than do value=value*0.5+0.5; to rescale it.

  2. Now that that's out of the way, to solve your issue you need to separate the idea of chunks and generation. This is a good idea in general, and I always believe in hiding backend implementation details (eg chunks) from gameplay as much as possible (eg avoid visible boundaries). Each time you generate a chunk, you should find its start coordinate in the world, so that coordinates continue seamlessly with the rest. For example, if the chunks are 128x128, then the chunk starting at (0, 0) should have starting coordinate (0, 0), then the chunk starting at (0, 1) should have starting coordinate (0, 128). Only then, convert a world coordinate into a noise coordinate by multiplying by your desired frequency.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM