- Home /
How are the bytes in a 16bit heightmap organized?
I'm writing a script that reads a heightmap from a binary text file, and programmatically sets said heightmap to the active terrain. To do this for an 8bit heightmap, it's pretty simple: I just read each byte in the text file, store it in a byte[] array, translate that into a float[][] matrix, and load the heightmap using terrain.terrainData.SetHeights(0, 0, float[][]) (See code at the end for actual implementation).
This works, but the resolution of the terrain is pretty bad, as the elevations look kind of like stairs. Because of this, I'm going to have to use a 16bit heightmap, but I don't know how to interpret the bytes from the binary text file, as I don't know how they're organized. I'm assuming the corresponding pair of bytes used to represent each elevation value is side by side...?
Here's the current (working) code for the 8bit heightmap:
using UnityEngine;
using System.Collections;
public class TerrainGenerate : MonoBehaviour
{
public TextAsset binaryData;
private Terrain terrain; // terrain to modify
protected int heightmapWidth; // heightmap width
protected int heightmapHeight; // heightmap height
void Start ()
{
terrain = Terrain.activeTerrain;
heightmapWidth = terrain.terrainData.heightmapWidth;
heightmapHeight = terrain.terrainData.heightmapHeight;
byte[] bytes = binaryData.bytes; // Loads the whole heightmap file into this array
float[,] heightmap = new float[heightmapWidth,heightmapHeight];
int byteNumber = 0; // Will be used to iterate through the 'bytes' array
for(int width = 0; width < bytes.Length/heightmapHeight; width++)
{
for(int height = 0; height < bytes.Length/heightmapHeight; height++)
{
heightmap[width, height] = bytes[byteNumber]/256.0f; // Sets value in 'bytes' array to matrix
byteNumber++;
}
}
terrain.terrainData.SetHeights(0, 0, heightmap); // Loads matrix into terrain.
}
}
Answer by Carey403 · Jun 22, 2013 at 12:41 AM
I'm somewhat unfamiliar with C#, but assuming you can only save byte arrays then you could do the follow, assuming you already have your heightmap stored in an array of integers:
byte[] byteArray = new byte[intArray.length*2];
for(int i=0; i<intArray.length; i+=2)
{
bytearray[i] = intArray[i] & 0xFF;
bytearray[i+1] = (intArray[i] & 0xFF00) >> 0x08;
}
To get it back into int form you can this:
//Assuming byteArray contains bytes read from serialized data
int[] intArray = new int[byteArray.length/2];
for(int i=0; i<byteArray.length; i+=2)
{
intArray[i] = (byteArray[i+1] << 0x08) | (byteArray[i]);
}
Then just transform that int array into your float matrix, should work.
There wouldn't be any integers involved; ints are signed 32-bits and the heightmap is 16-bits, unsigned (i.e., ushort). Also, as I mentioned, both big-endian and little-endian storage are valid and need to be accounted for.
I am well aware ints are larger than 16bits, but 16bit variables still fit in an int. What I'm suggesting the OP do is manually handle data conversion between integer and byte, that way the endianness becomes irrelevant for him.
He could store them in ushort if its 16bit, as I said, I am unfamiliar with C# and its data types. Integer would work as well
Well, it works nicely, thanks to both of you. Here's the end code. This loads up the 16bit heightmap correctly:
using UnityEngine;
using System.Collections;
public class TerrainGenerate : $$anonymous$$onoBehaviour
{
public TextAsset binaryData;
private Terrain terrain; // terrain to modify
protected int heightmapWidth; // heightmap width
protected int heightmapHeight; // heightmap height
void Start ()
{
terrain = Terrain.activeTerrain;
heightmapWidth = terrain.terrainData.heightmapWidth;
heightmapHeight = terrain.terrainData.heightmapHeight;
byte[] bytes = binaryData.bytes; // Loads the whole heightmap file into this array
float[,] heightmap = new float[heightmapWidth,heightmapHeight];
int byteNumber = 0; // Will be used to iterate through the 'bytes' array
for(int width = 0; width < bytes.Length/(heightmapHeight*2); width++)
{
for(int height = 0; height < bytes.Length/(heightmapHeight*2); height++)
{
heightmap[width, height] = ((bytes[byteNumber] << 0x08) | bytes[byteNumber+1])/65536f; // Sets value in 'bytes' array to matrix
byteNumber += 2;
}
}
terrain.terrainData.SetHeights(0, 0, heightmap);
}
}
Thank you so much @fparedesg! This helped me load a 16-bit .raw heightmap onto a terrain. It worked! I had to make a small change in your code because my heightmap was having little-endian byte order (windows). So for it to work all I had to do was make a small change in line 25.
heightmap[width, height] = ((bytes[byteNumber+1] << 0x08) | bytes[byteNumber])/65536f;
Also, I had to set the heightmap resolution in the code because of some exception I was facing. When the TerrainData was constructed, for some reason, the resolution was set to 33 by default. I knew my heightmap resolution, so I set it to that:
terrainData.heightmapResolution = 257;
int heightmapWidth = terrainData.heightmapWidth;
int heightmapHeight = terrainData.heightmapHeight;
Answer by Eric5h5 · Jun 21, 2013 at 11:19 PM
With a 16-bit heightmap, you have 2 bytes for each pixel, organized as either little-endian (where the least significant byte is first) or big-endian (where the most significant byte is first). There's no way to know which is used in the file, so it would be best to support both.
Answer by tds dave · Nov 02, 2014 at 09:27 PM
Bit late to the party. Converting byte arrays ( binary data ) into other simple types (ushorts etc )in c# use System.BitConverter. New to unity though not c# so I'm presume this is available. It has functionality to help with architecture endian also. http://msdn.microsoft.com/en-us/library/system.bitconverter(v=vs.110).aspx
Your answer
Follow this Question
Related Questions
Import 8bit .raw file, and 16bit explanation. 2 Answers
Modifying an heightmap at runtime 2 Answers
How to get heightmap of unity terrain ? 1 Answer
Heightmap is spiky, unsolvable for me 3 Answers
Gradual Terrain Smoothing from a Flat Area (at Runtime) 0 Answers