Search code examples
c#unity-game-enginetexture2dterrainheightmap

Trying to set heightmaps depending on image texture colors through code


Well I'm trying to set the heightmap of a terrain from an image / texture colors (from pixels), I made this simple example:

using UnityEngine;

public class TerrainExample : MonoBehaviour
{
    public Texture2D map, grassTexture;
    public float amp = 8;
    public Color waterColor = new Color(0.427f, 0.588f, 0.737f); //new Color32(127, 168, 200, 255); //0.427, 0.588, 0.737

    private Vector3 mapPlane = new Vector3(4200, 0, 3000);

    public void Start()
    {
        GameObject TerrainObj = new GameObject("TerrainObj");
        TerrainData _TerrainData = new TerrainData();
        Debug.Log(new Vector3(mapPlane.x, 600, mapPlane.z));
        _TerrainData.size = new Vector3(mapPlane.x / (1.6f * amp), 600, mapPlane.z / (1.6f * amp));
        _TerrainData.heightmapResolution = 4096;
        _TerrainData.baseMapResolution = 1024;
        _TerrainData.SetDetailResolution(1024, 16);

        //Set terrain data
        int _heightmapWidth = _TerrainData.heightmapWidth,
            _heightmapHeight = _TerrainData.heightmapHeight;
        float[,] heights = new float[_heightmapWidth, _heightmapHeight];
        float stepX = (float)map.width / _heightmapWidth, stepY = (float)map.height / _heightmapHeight;
        int w = 0;
        for (float i = 0; i < map.width; i += stepX)
            for (float k = 0; k < map.height; k += stepY)
            {
                int ii = (int)i, kk = (int)k, i2 = (int)(i / stepX), k2 = (int)(k / stepY);
                heights[i2, k2] = map.GetPixel(ii, kk) == waterColor ? .25f : .5f;
            }
        _TerrainData.SetHeights(0, 0, heights);

        //Set terrain grass texture
        SplatPrototype terrainTexture = new SplatPrototype();
        terrainTexture.texture = grassTexture;
        SplatPrototype[] splatPrototype = new SplatPrototype[1] { terrainTexture };
        _TerrainData.splatPrototypes = splatPrototype;
        TerrainCollider _TerrainCollider = TerrainObj.AddComponent<TerrainCollider>();
        Terrain _Terrain2 = TerrainObj.AddComponent<Terrain>();
        _TerrainCollider.terrainData = _TerrainData;
        _Terrain2.terrainData = _TerrainData;
        TerrainObj.transform.position = -mapPlane * 10 / 2 + Vector3.up * 100;
    }
}

My map texture is the following:

enter image description here

The original map has 7000x5000 px, and my heightmap has 4096 units. So, I have to make a little translation, by calculating the step of every iteration (as you can see in line 25)

What I do is simple, I only put a 0.25f of the height (600/4 = 150) when there is water and 0.5f of the height (600 / 2 = 300) when there is something different from water. (Line 31)

But for some reason I only get this weird lines across the terrain:

enter image description here

What I'm missing???

Here is a Unitypackage.


Solution

  • I didn't really bother assigning the texture, I'll let you do that.

    • create a new scene
    • add a terrain
    • add the following component

    Code:

    using UnityEngine;
    
    namespace Assets
    {
        public class Test : MonoBehaviour
        {
            public Texture2D Texture2D;
    
            private void OnEnable()
            {
                var terrain = GetComponent<Terrain>();
                var data = terrain.terrainData;
                var hw = data.heightmapWidth;
                var hh = data.heightmapHeight;
                var heights = data.GetHeights(0, 0, hw, hh);
    
                for (var y = 0; y < hh; y++)
                {
                    for (var x = 0; x < hw; x++)
                    {
                        // normalize coordinates
                        var x1 = 1.0f / hw * x * Texture2D.width;
                        var y1 = 1.0f / hh * y * Texture2D.height;
    
                        // get color height
                        var pixel = Texture2D.GetPixel((int) x1, (int) y1);
                        var f = pixel.grayscale; // defines height
                        var g = f * f * f; // some smoothing
                        var s = 0.025f; // some scaling
    
                        heights[x, y] = g * s;
                    }
                }
    
                data.SetHeights(0, 0, heights);
            }
        }
    }
    

    enter image description here

    enter image description here

    The most interesting thing in this is the smoothing, it dramatically improves the output.

    Additionally, you could find which color the pixel is close to from a pre-defined list (e.g. water, grass) and interpolate/smooth.

    Also, you might need to adjust the code since I'm not very familiar with TerrainData.