Export Terrain Heightmap 2 PNG

I had to find a way to edit a terrain heightmap in Gimp on a Mac which doesn’t like 16 bit as far as I can tell. Searching the forums turned up not so much. So I wrote this script to use in conjunction with Eric5h5’s apply heightmap script on the wiki.

Works fine except for a couple things:

  • I didn’t try it on a scene with more than one terrain. The selection stuff was copied out of another script (the raw one?). Doesn’t look like there’s a check for multiple terrains but it didn’t matter for my project. So I’m not sure how it will behave if you have several and don’t select one.
  • The biggest problem is it outputs an 8 bit image. The result is 256 levels of gray. When applying the exported PNG to a new terrain you’ll get the general shape but banding causes it to stair step. Using the smooth brush fixes it but just isn’t good enough for some(most?) projects. Scaling it down to 256 x 256 helps some but still not enough.

I went another route in the end. So, my problem’s solved. Don’t think it’s worth the wiki at this point but feel free to put it up there if you want. I’m not going to mess with it anymore. Anyone else who wants to can have at it.

Save it as HeightmapExportPNG.js and put it in an Assets/Editor folder in your project. Export Height Map as PNG should show up under the Terrain menu. Selecting it will generate a PNG and save it to the Assets folder as DuplicateHeightmap.png. See Eric’s script on the wiki to apply it to a new terrain:

import UnityEngine;
import UnityEditor;
import System.Collections;
import System.IO;
 
class HeightmapExportPNG extends EditorWindow 
{
    static var terraindata : TerrainData;
    
    @MenuItem ("Terrain/Export Height Map as PNG")
    static function Init () {
        terraindata = null;
        var terrain : Terrain = null;
        
        if ( Selection.activeGameObject )
        	terrain = Selection.activeGameObject.GetComponent( Terrain );

        if (!terrain) {
            terrain = Terrain.activeTerrain;
        }
        if (terrain) {
            terraindata = terrain.terrainData;
   	    }
		if (terraindata == null) { 
			EditorUtility.DisplayDialog("No terrain selected", "Please select a terrain.", "Cancel"); 
			return; 
	    }
        
        //// get the terrain heights into an array and apply them to a texture2D
		var myBytes : byte[];
		var myIndex : int = 0;
		var rawHeights = new Array(0.0,0.0);
		var duplicateHeightMap = new Texture2D(terraindata.heightmapWidth, terraindata.heightmapHeight, TextureFormat.ARGB32, false);
		rawHeights = terraindata.GetHeights(0, 0, terraindata.heightmapWidth, terraindata.heightmapHeight);

		/// run through the array row by row
	    for (y=0; y < duplicateHeightMap.height; ++y)
	    {
    	    for (x=0; x < duplicateHeightMap.width; ++x)
    	    {
    	    	/// for wach pixel set RGB to the same so it's gray
				var color = Vector4(rawHeights[myIndex], rawHeights[myIndex], rawHeights[myIndex], 1.0);
				duplicateHeightMap.SetPixel (x, y, color);
				myIndex++;
	        }
    	}
   	    // Apply all SetPixel calls
    	duplicateHeightMap.Apply();

		/// make it a PNG and save it to the Assets folder
		myBytes = duplicateHeightMap.EncodeToPNG();
		var filename : String = "DupeHeightMap.png";
		File.WriteAllBytes(Application.dataPath + "/" + filename, myBytes);
		EditorUtility.DisplayDialog("Heightmap Duplicated", "Saved as PNG in Assets/ as: " + filename, "");
	}
}
1 Like

Works great in 4.2

Thanks for sharing. I was going RAR with RAWs

Works great in 4.3.3 as well. Super helpful, A++++++ would shop here again.

PNG files actually can support 16-bit per channel, but Unity’s PNG saving routines are only 8-bit.

Hi, i am using Unity 2017.1.0b1 (latest for may 2017), and i cant find the button to export. Is this code outdated (Terrain menu was removed, isn’t it?) and can i update it myself?

For anyone looking for this I made it so you can choose how you want to save the image… You must type yourfilename.yourextension jpg/png file saves out as 512x512. Same as before only does one terrain in the scene also you can find the extension in the editor under Window/Terrain to image.

using UnityEngine;
using UnityEditor;
using System.IO;

class HeightmapExportPNG : EditorWindow
{
    static TerrainData terraindata;


    [MenuItem("Window/Terrain to image")]
    static void Init()
    {
        terraindata = null;
        Terrain terrain = null;

        if (Selection.activeGameObject)
            terrain = Selection.activeGameObject.GetComponent<Terrain>();

        if (!terrain)
        {
            terrain = Terrain.activeTerrain;
        }
        if (terrain)
        {
            terraindata = terrain.terrainData;
        }
        if (terraindata == null)
        {
            EditorUtility.DisplayDialog("No terrain selected", "Please select a terrain.", "Cancel");
            return;
        }

        //// get the terrain heights into an array and apply them to a texture2D
        byte[] myBytes;
        int myIndex = 0;
        Texture2D duplicateHeightMap = new Texture2D(terraindata.heightmapWidth, terraindata.heightmapHeight, TextureFormat.ARGB32, false);
        float[,] rawHeights = terraindata.GetHeights(0, 0, terraindata.heightmapWidth, terraindata.heightmapHeight);

        /// run through the array row by row
        for (int y = 0; y < duplicateHeightMap.height; y++)
        {
            for (int x = 0; x < duplicateHeightMap.width; x++)
            {
                /// for wach pixel set RGB to the same so it's gray
                var color = new Vector4(rawHeights[x,y], rawHeights[x,y], rawHeights[x,y], 1);
                duplicateHeightMap.SetPixel(x, y, color);
                myIndex++;
            }
        }
        // Apply all SetPixel calls
        duplicateHeightMap.Apply();

        string path = EditorUtility.SaveFilePanel(
                "Save texture as",
                "",
                "Rename Me",
                "png, jpg");

        var extension = Path.GetExtension(path);
        byte[] pngData = null;// duplicateHeightMap.EncodeToPNG();

        switch(extension)
        {
            case ".jpg":
                pngData = duplicateHeightMap.EncodeToJPG();
                break;

            case ".png":
                pngData = duplicateHeightMap.EncodeToPNG();
                break;
        }

        if (pngData != null)
        {
            File.WriteAllBytes(path, pngData);
            EditorUtility.DisplayDialog("Heightmap Duplicated", "Saved as" + extension + " in " + path, "Awesome");
        }else
        {
            EditorUtility.DisplayDialog("Failed to duplicate height map", "eh something happen hu? lol", "Check Script");
        }

        AssetDatabase.Refresh();
    }
}

-Levon

3 Likes