Search Unity

Question Problems with texture generation using perlin noise

Discussion in 'Getting Started' started by Fox671, Mar 5, 2022.

  1. Fox671

    Fox671

    Joined:
    Jan 1, 2022
    Posts:
    2
    Hello, I'm using perlin noise to generate a texture, and then use it to generate an infinite world. I think by attaching the code, everything will be clearer.
    I am using three scripts:
    Noise
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public static class Noise {
    5.  
    6.     public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight, int seed, float scale, int octaves, float persistance, float lacunarity, Vector2 offset) {
    7.         float[,] noiseMap = new float[mapWidth, mapHeight];
    8.  
    9.         System.Random prng = new System.Random(seed);
    10.         Vector2[] octaveOffsets = new Vector2[octaves];
    11.         for (int i = 0; i < octaves; i++) {
    12.             float offsetX = prng.Next(-100000, 100000) + offset.x;
    13.             float offsetY = prng.Next(-100000, 100000) - offset.y;
    14.             octaveOffsets[i] = new Vector2 (offsetX, offsetY);
    15.         }
    16.  
    17.         if (scale <= 0) {
    18.             scale = 0.0001f;
    19.         }
    20.  
    21.         float maxNoiseHeight = float.MinValue;
    22.         float minNoiseHeight = float.MaxValue;
    23.  
    24.         float halfWidth = mapWidth / 2f;
    25.         float halfHeight = mapHeight / 2f;
    26.  
    27.  
    28.         for (int y = 0; y < mapHeight; y++) {
    29.             for (int x = 0; x < mapWidth; x++) {
    30.      
    31.                 float amplitude = 1;
    32.                 float frequency = 1;
    33.                 float noiseHeight = 0;
    34.  
    35.                 for (int i = 0; i < octaves; i++) {
    36.                     float sampleX = (x - halfWidth + octaveOffsets[i].x) / scale * frequency;
    37.                     float sampleY = (y - halfHeight + octaveOffsets[i].y) / scale * frequency ;
    38.  
    39.                     float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
    40.                     noiseHeight += perlinValue * amplitude;
    41.  
    42.                     amplitude *= persistance;
    43.                     frequency *= lacunarity;
    44.                 }
    45.  
    46.                 if (noiseHeight > maxNoiseHeight) {
    47.                     maxNoiseHeight = noiseHeight;
    48.                 } else if (noiseHeight < minNoiseHeight) {
    49.                     minNoiseHeight = noiseHeight;
    50.                 }
    51.                 noiseMap [x, y] = noiseHeight;
    52.             }
    53.         }
    54.  
    55.         for (int y = 0; y < mapHeight; y++) {
    56.             for (int x = 0; x < mapWidth; x++) {
    57.                 noiseMap[x, y] = Mathf.InverseLerp (minNoiseHeight, maxNoiseHeight, noiseMap[x, y]);
    58.             }
    59.         }
    60.  
    61.         return noiseMap;
    62.     }
    63. }
    MapGenerator
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MapGenerator : MonoBehaviour {
    6.  
    7.     public const int mapChunkSize = 32;
    8.     public const int tileSize = 16;
    9.     [Header("Noise Parameters")]
    10.     public float noiseScale;
    11.     public int octaves;
    12.     [Range(0,1)]
    13.     public float persistance;
    14.     public float lacunarity;
    15.     public int seed;
    16.     public Vector2 offset;
    17.  
    18.     public TerrainType[] regions;
    19.     public Texture2D mainTexture;
    20.  
    21.     public MapData GenerateMapData(Vector2 centre) {
    22.         float[,] noiseMap = Noise.GenerateNoiseMap(mapChunkSize, mapChunkSize, seed, noiseScale, octaves, persistance, lacunarity, centre + offset);
    23.  
    24.         Texture2D texture = new Texture2D(mapChunkSize * tileSize, mapChunkSize * tileSize);
    25.         texture.filterMode = FilterMode.Point;
    26.         texture.wrapMode = TextureWrapMode.Clamp;
    27.         for (int x = 0; x < mapChunkSize; x++) {
    28.             for (int y = 0; y < mapChunkSize; y++) {
    29.                 float currentHeight = noiseMap[x, y];
    30.                 for (int i = 0; i < regions.Length; i++) {
    31.                     if (currentHeight <= regions[i].height) {
    32.                         texture.SetPixels(x * tileSize, y * tileSize, tileSize, tileSize, mainTexture.GetPixels(regions[i].coord.x, regions[i].coord.y, tileSize, tileSize));
    33.                         break;
    34.                     }
    35.                 }
    36.             }
    37.         }
    38.         texture.Apply();
    39.  
    40.         return new MapData(noiseMap, texture);
    41.     }
    42. }
    43.  
    44. [System.Serializable]
    45. public struct TerrainType {
    46.     public string name;
    47.     public float height;
    48.     public Vector2Int coord;
    49. }
    50.  
    51. public struct MapData {
    52.     public readonly float[,] heightMap;
    53.     public readonly Texture2D texture;
    54.  
    55.     public MapData (float[,] heightMap, Texture2D texture) {
    56.         this.heightMap = heightMap;
    57.         this.texture = texture;
    58.     }
    59. }
    EndlessTerrain

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class EndlessTerrain : MonoBehaviour {
    6.  
    7.     public const float maxViewDst = 64;
    8.     int mapChunkSize;
    9.     public Transform viewer;
    10.  
    11.     public static Vector2 viewerPosition;
    12.     int chunksVisibleInViewDst;
    13.  
    14.     static MapGenerator mapGenerator;
    15.  
    16.     Dictionary<Vector2, TerrainChunk> terrainChunkDictionary = new Dictionary<Vector2, TerrainChunk>();
    17.     List<TerrainChunk> terrainChunksVisibleLastUpdate = new List<TerrainChunk>();
    18.  
    19.     void Start() {
    20.         mapGenerator = FindObjectOfType<MapGenerator>();
    21.  
    22.         mapChunkSize = MapGenerator.mapChunkSize;
    23.         chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / mapChunkSize);
    24.     }
    25.  
    26.     void Update() {
    27.         viewerPosition = new Vector2 (viewer.position.x, viewer.position.y);
    28.         UpdateVisibleChunks ();
    29.     }
    30.  
    31.     void UpdateVisibleChunks() {
    32.  
    33.         for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++) {
    34.             terrainChunksVisibleLastUpdate.SetVisible(false);
    35.         }
    36.         terrainChunksVisibleLastUpdate.Clear();
    37.        
    38.         int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x);
    39.         int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y);
    40.  
    41.         for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++) {
    42.             for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++) {
    43.                 Vector2 viewedChunkCoord = new Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
    44.  
    45.                 if (terrainChunkDictionary.ContainsKey(viewedChunkCoord)) {
    46.                     terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk();
    47.                     if (terrainChunkDictionary[viewedChunkCoord].IsVisible()) {
    48.                         terrainChunksVisibleLastUpdate.Add(terrainChunkDictionary[viewedChunkCoord]);
    49.                     }
    50.                 } else {
    51.                     terrainChunkDictionary.Add(viewedChunkCoord, new TerrainChunk(mapChunkSize, viewedChunkCoord, transform));
    52.                 }
    53.             }
    54.         }
    55.     }
    56.  
    57.     public class TerrainChunk {
    58.  
    59.         public GameObject chunkObject;
    60.         Vector2 position;
    61.         Bounds bounds;
    62.  
    63.         SpriteRenderer spriteRenderer;
    64.         MapData mapData;
    65.  
    66.         public TerrainChunk(int size, Vector2 coord, Transform parent) {
    67.             position = coord * size;
    68.             bounds = new Bounds(coord, Vector2.one);
    69.             Vector3 positionV3 = new Vector3(coord.x, coord.y, 0);
    70.             mapData = mapGenerator.GenerateMapData(position);
    71.  
    72.             chunkObject = new GameObject("Terrain Chunk");
    73.             chunkObject.transform.position = positionV3;
    74.             spriteRenderer = chunkObject.AddComponent<SpriteRenderer>();
    75.             spriteRenderer.sprite = Sprite.Create(mapData.texture, new Rect(0, 0, mapData.texture.width, mapData.texture.height), new Vector2(0.5f, 0.5f), 16);
    76.  
    77.             chunkObject.transform.localScale = Vector3.one / size;
    78.             chunkObject.transform.parent = parent;
    79.             SetVisible(false);
    80.         }
    81.  
    82.         public void UpdateTerrainChunk() {
    83.             float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
    84.             bool visible = viewerDstFromNearestEdge <= maxViewDst;
    85.             SetVisible(visible);
    86.         }
    87.  
    88.         public void SetVisible(bool visible) {
    89.             chunkObject.SetActive(visible);
    90.         }
    91.  
    92.         public bool IsVisible() {
    93.             return chunkObject.activeSelf;
    94.         }
    95.     }
    96. }

    So what's the problem? The problem is that for each chunk I generate my own texture by changing the Vector2 offset value. And for some reason I don't understand, the offset value works strangely in Noise, because instead of a smooth transition between chunks, I get completely unrelated chunk textures. If you have any questions, ask, I will answer all.
     
    Last edited: Mar 6, 2022