Search code examples
c#unity-game-engineterrainheightmap

Why do my terrains sampled from heightmap image files look "blocky"?


I generate terrains from heightmaps but results look "blocky".

It works if I sample the PerlinNoise method for the grayscale value but not when I read it from a texture. Debugging shows a smooth output (0.0 - 1.0) with no shears. I have tried PNG and JPEG formats as well as normalized and remapped grayscale heightmaps.

public class TerrainTut : MonoBehaviour
{
    [SerializeField] private Texture2D _heightmap;

    private void Start()
    {
        Terrain t = GetComponent<Terrain>();
        t.terrainData = GenerateTerrainData(t.terrainData);
    }

    private TerrainData GenerateTerrainData(TerrainData terrainData)
    {
        terrainData.SetHeights(0,0, GenerateHeights(terrainData.heightmapResolution));

        return terrainData;
    }

    private float[,] GenerateHeights(int heightmapRes)
    {
        float[,] heights = new float[heightmapRes, heightmapRes];

        for (int x = 0; x < heightmapRes; x++)
        {
            for (int y = 0; y < heightmapRes; y++)
            {
                //heights[x, y] = CalculateNoiseHeight(x, y, heightmapRes);//smooth?
                heights[x, y] = CalculateTextureHeight(x, y, heightmapRes);//not smooth?
            }
        }

        return heights;
    }

    private float CalculateNoiseHeight(int x, int y, int heightmapRes, float noiseScale = 20f)
    {
        float noiseX = ((float)x / heightmapRes) * noiseScale;
        float noiseY = ((float)y / heightmapRes) * noiseScale;

        return Mathf.PerlinNoise(noiseX, noiseY);
    }

    private float CalculateTextureHeight(int x, int y, int heightmapRes)
    {
        float noiseX = ((float)x / heightmapRes) * _heightmap.width;
        float noiseY = ((float)y / heightmapRes) * _heightmap.height;

        return _heightmap.GetPixel(x, y).grayscale;//tried using neighbour pixels but its the same
    }
}

Terrain and heightmap:

enter image description here enter image description here


Solution

  • You're picking exact colors from your height-map without blending them. GetPixel jumps directly from one pixel's height to another.

    Use GetPixelBilinear (it blends or interpolates between pixels) and remove _heightmap.width and _heightmap.height because GetPixelBilinear requires values in the range of 0 to 1 (like a percentage):

    private float CalculateTextureHeight(int x, int y, int heightmapRes)
    {
        float noiseX = (float)x / heightmapRes;
        float noiseY = (float)y / heightmapRes;
    
        return _heightmap.GetPixelBilinear(noiseX, noiseY).grayscale;
    }