I am working on an infinite procedural terrain generator and at the moment I am trying to generate a proper heightmap from a biomes map.
I'm not sure what is the best way in creating a proper biomes map, but I decided to use a cellular noise.
I have setup a simple biomes by providing a threshold and a noise generator for each of the biome.
When I generate a heightmap, I sample a biomes map and I get a biomes index by using a sampled value. Once I retrieve an index, I use a biome sample function to get a noise value at the position.
Currently this is the result I am getting
Which is expected, however I am currently struggling in blending these biomes seamlessly together. Also at the moment the biomes map defines these sharp regions, but instead I would like to make it more organic. I tried to blend the sampled cellular noise with a perlin noise, however it resulted in the same map.
I use this library in my code to generate noise maps: https://github.com/Auburn/FastNoiseLite/tree/master/CSharp
This is the whole code:
using System;
using UnityEngine;
using UnityEngine.UI;
public class BiomesMap : MonoBehaviour
{
[SerializeField] RawImage output;
const int size = 512;
[SerializeField] int seed = 5;
[SerializeField] Biome[] biomes;
void Start()
{
for (int i = 0; i < biomes.Length; i++)
{
biomes[i].InitNoise(seed);
}
GenerateMap();
}
float[,] GenerateBiomesMap()
{
var noise = new FastNoiseLite(seed);
noise.SetFractalType(FastNoiseLite.FractalType.None);
noise.SetDomainWarpType(FastNoiseLite.DomainWarpType.BasicGrid);
noise.SetFrequency(0.005f);
noise.SetCellularJitter(1f);
noise.SetNoiseType(FastNoiseLite.NoiseType.Cellular);
noise.SetCellularDistanceFunction(FastNoiseLite.CellularDistanceFunction.EuclideanSq);
noise.SetCellularReturnType(FastNoiseLite.CellularReturnType.CellValue);
float[,] map = new float[size, size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
map[x, y] = (noise.GetNoise(x, y) + 1) * .5f;
}
}
return map;
}
void GenerateMap()
{
float[,] biomesMap = GenerateBiomesMap();
float[,] heightmap = new float[size, size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
var value = biomesMap[x, y];
int biomesIndex = GetBiomeIndex(value);
if (biomesIndex == -1) { Debug.LogErrorFormat("Biome not found. {0}", value); return; }
heightmap[x, y] = biomes[biomesIndex].Sample(x, y);
}
}
Output(heightmap);
}
int GetBiomeIndex(float value)
{
for (int i = 0; i < biomes.Length; i++)
{
if (value <= biomes[i].threshold)
{
return i;
}
}
return -1;
}
void Output(float[,] heightmap)
{
Color[] pixels = new Color[size * size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float value = heightmap[x, y];
pixels[x + (y * size)] = new Color(value, value, value, 1);
}
}
Texture2D result = new Texture2D(size, size);
result.SetPixels(pixels);
result.Apply();
output.texture = result;
}
}
[Serializable()]
public class Biome
{
public string name;
[Range(0, 1)]
public float threshold;
public NoiseGenerator noiseParameters = new NoiseGenerator();
FastNoiseLite noise;
public void InitNoise(int seed)
{
noise = new FastNoiseLite();
noise.SetSeed(seed);
noise.SetNoiseType(noiseParameters.noiseType);
noise.SetFrequency(noiseParameters.frequency);
noise.SetFractalType(noiseParameters.fractalType);
if (noiseParameters.fractalType != FastNoiseLite.FractalType.None)
{
noise.SetFractalOctaves(noiseParameters.octaves);
noise.SetFractalLacunarity(noiseParameters.lacunarity);
noise.SetFractalGain(noiseParameters.gain);
noise.SetFractalWeightedStrength(noiseParameters.weightedStrength);
noise.SetFractalPingPongStrength(noiseParameters.pingPongStrength);
}
if (noiseParameters.noiseType == FastNoiseLite.NoiseType.Cellular)
{
noise.SetCellularDistanceFunction(noiseParameters.distanceFunction);
noise.SetCellularReturnType(noiseParameters.cellularReturn);
noise.SetCellularJitter(noiseParameters.cellularJitter);
}
noise.SetDomainWarpType(noiseParameters.domainWarpType);
noise.SetDomainWarpAmp(noiseParameters.amplitude);
}
public float Sample(float x, float y)
{
return (noise.GetNoise(x, y) + 1) * .5f;
}
}
[Serializable()]
public class NoiseGenerator
{
[Header("General")]
public FastNoiseLite.NoiseType noiseType;
public float frequency;
[Header("Fractal Noise")]
public FastNoiseLite.FractalType fractalType;
[Range(1, 10)]
public int octaves;
public float lacunarity;
public float gain;
public float weightedStrength;
public float pingPongStrength;
[Header("Cellular Noise")]
public FastNoiseLite.CellularDistanceFunction distanceFunction;
public FastNoiseLite.CellularReturnType cellularReturn;
public float cellularJitter;
[Header("Domain Wrap")]
public FastNoiseLite.DomainWarpType domainWarpType;
public float amplitude;
}