Skip to content

Instantly share code, notes, and snippets.

@m-biernat
Created March 16, 2024 21:54
Show Gist options
  • Save m-biernat/d23843f1028646fa81175c19f70d2883 to your computer and use it in GitHub Desktop.
Save m-biernat/d23843f1028646fa81175c19f70d2883 to your computer and use it in GitHub Desktop.
A simple height map generator with fractal noise for Unity's terrain
/*
This is a simple implementation of height map generation with fractal noise, and it's compatible with Unity's terrain.
Also, it's a one filer, ready to be copy-pasted into your Unity project and doesn't require any additional libs to run.
Attach NoiseGenerationSample to any GO, add a reference to the Terrain, and pick Generate from the component's context menu.
The code is quite flexible and can be fairly easy modified and adapted to your needs. Check out my GitHub for more :)
Copyright (c) 2024 Michał Biernat @m-biernat, licensed under the MIT License.
*/
using UnityEngine;
namespace NoiseGenerationSample
{
public class NoiseGenerationSample : MonoBehaviour
{
[SerializeField] Terrain _terrain;
[Header("Noise")]
[SerializeField] float _scale = 1.0f;
[Range(1, 16)]
[SerializeField] int _octaves = 8;
[SerializeField] float _gain = .5f;
[Space]
[SerializeField] bool _randomize = false;
[SerializeField] int _seed = 2014;
[ContextMenu("Generate")]
void Generate()
{
var terrainModifier = new TerrainModifier(_terrain);
var noise = new Perlin2D(_scale);
var fractalNoise = new FractalNoise2D(noise.Sample, _octaves, _gain);
if (!_randomize)
Random.InitState(_seed);
var offset = new Vector2 {
x = Random.Range(-10000, 10000),
y = Random.Range(-10000, 10000)
};
var heightMap = new HeightMap(fractalNoise, terrainModifier.Resolution, offset);
heightMap.Generate();
terrainModifier.Apply(heightMap.Data);
}
}
public class TerrainModifier
{
Terrain _terrain;
public Vector2Int Resolution { get; private set; }
public TerrainModifier(Terrain terrain)
{
_terrain = terrain;
int terrainRes = _terrain.terrainData.heightmapResolution;
Resolution = new Vector2Int(terrainRes, terrainRes);
}
public void Apply(float[,] heightMap)
{
_terrain.terrainData.SetHeights(0, 0, heightMap);
}
}
public class HeightMap
{
INoise2D _noise2D;
Vector2Int _resolution;
Vector2 _offset;
public float[,] Data { get; private set; }
public HeightMap(INoise2D noise2D, Vector2Int resolution, Vector2 offset = default(Vector2))
{
_noise2D = noise2D;
_resolution = resolution;
_offset = offset;
Data = new float[resolution.x, resolution.y];
}
public void Generate()
{
var invResolution = new Vector2 {
x = (1.0f / _resolution.x),
y = (1.0f / _resolution.y)
};
for (int x = 0; x < _resolution.x; x++)
{
for (int y = 0; y < _resolution.y; y++)
{
var input = new Vector2 {
x = x * invResolution.x + _offset.x,
y = y * invResolution.y + _offset.y
};
float value = _noise2D.Sample(input);
Data[x, y] = value;
}
}
}
}
public interface INoise2D
{
float Sample(Vector2 input);
}
public class Perlin2D : INoise2D
{
float _scale;
public Perlin2D(float scale = 1.0f)
{
_scale = scale;
}
public float Sample(Vector2 input)
{
return Mathf.PerlinNoise(input.x * _scale, input.y * _scale);
}
}
public class FractalNoise2D : INoise2D
{
System.Func<Vector2, float> _noiseFunc;
int _octaves;
float _gain;
float[] _frequency;
float[] _amplitude;
float _inverseNormDiv;
const float NormMin = .5f;
public FractalNoise2D(System.Func<Vector2, float> noiseFunc, int octaves = 8, float gain = 0.5f)
{
_noiseFunc = noiseFunc;
_octaves = octaves;
_gain = gain;
_amplitude = new float[_octaves];
_frequency = new float[_octaves];
CalculateParams();
}
void CalculateParams()
{
float totalAmplitude = 0;
for (int i = 0; i < _octaves; i++)
{
_frequency[i] = Mathf.Pow(2.0f, i); // Starts with 1.0 and doubles
_amplitude[i] = Mathf.Pow(_gain, i); // Starts with 1.0 and halves (G=0.5)
totalAmplitude += _amplitude[i];
}
_inverseNormDiv = 1 / (totalAmplitude - NormMin); // 1 / (max - min) for Normalize
}
public float Sample(Vector2 input)
{
float value = 0;
for (int i = 0; i < _octaves; i++)
{
value += _amplitude[i] * _noiseFunc.Invoke(input * _frequency[i]);
}
return Normalize(value);
}
// f(min = 0.5f, max = totalAmplitude, value) => (value - min) / (max - min)
float Normalize(float value) => (value - NormMin) * _inverseNormDiv;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment