Modifying terrain heights based on a texture (blending the 2, not making a heightmap)

I’m writing a tool that will add a racetrack to a terrain.

I can successfully read a Texture2D, and quite happily modify terrainData.

My problem lies with a calculation that I just cannot seem to formulate. So :

I want to set an average height for the racetrack. All these height references are normalized values (between 0 and 1).

    var averageHeight : float = 0.3;

Then read the height at specified point of the terrain :

    var heightMap : float = heightmapData[y,x];

Then read the height from my racetrack image :

    var pixelHeight : float = racetrackTexture.GetPixel( x, y ).grayscale;

From these 3 floats, I need to find out :

if the heightMap height is greater than the averageHeight, and the pixelHeight is greater than zero, then reduce the heightMap height, factoring in the pixelHeight.

if the heightMap height is less than the averageHeight, and the pixelHeight is greater than zero, then increase the heightMap height, factoring in the pixelHeight.

Example :

heightMap height is greater than the averageHeight,

so the heightMap height has to be reduced,

reduction is a factor of the pixelHeight,

if pixelHeight is high (close to 1) then dramatically reduce the heightMap

if pixelHeight is low (close to 0) then only slightly reduce the heightMap

So basically I want my racetrack to follow the profile of the terrain.

if the heightMap is greater than the averageHeight, then dig the racetrack into the terrain.

if the heightMap is less than the averageHeight, then raise the racetrack above the terrain.

If you managed to follow that description, then well done !

Here is an image for the heightMap being greater than the averageHeight :

Here is an image for the heightMap being less than the averageHeight :

I should point out while my most excellent paint jobs show the track being the same as the averageHeight, I don’t want a flat track, I want it to follow the terrain. But if the terrain is higher than the averageHeight then drop the track down a bit, and if the terrain is lower than the averageHeight then raise the track up a bit.

I totally understand if you are confused. If you want to see my code work-in-progress with all my experimentation, here it is :

#pragma strict

public var racetrackTexture : Texture2D;
private var textureSize : Vector2;
public var averageHeight : float = 0.2;
public var blendFactor : float = 0.5;

public var terrain : Terrain;
private var terrainData : TerrainData;
private var terrainSize : Vector3;

private var heightmapWidth : int;
private var heightmapHeight : int;
private var heightmapScale : Vector3;

private var heightmapData : float[,];
private var backupHeightmapData : float[,];

private var racetrackData : float[,];

enum terrainModifier
{
	Method_1,
	Method_2,
	Method_3,
	Method_4,
	Method_5,
	Method_6,
	Method_7,
	Restore_Backup
}

public var EditTerrain : terrainModifier;


function Start() 
{
	if ( !terrain )
	{
		terrain = Terrain.activeTerrain;
	}
	
	terrainData = terrain.terrainData;
	
	heightmapWidth = terrain.terrainData.heightmapWidth;
	heightmapHeight = terrain.terrainData.heightmapHeight;
	heightmapScale = terrain.terrainData.heightmapScale;
	
	backupHeightmapData = terrainData.GetHeights( 0, 0, heightmapWidth, heightmapHeight );
	
	
	racetrackData = new float[ racetrackTexture.width, racetrackTexture.height ];
	
	for ( var y : int = 0; y < racetrackTexture.height; y ++ )
	{
		for ( var x : int = 0; x < racetrackTexture.width; x ++ )
		{
			racetrackData[ x, y ] = racetrackTexture.GetPixel( x, y ).grayscale;
		}
	}
}

function Update() 
{
	if ( Input.GetMouseButtonDown(0) )
	{
		if ( EditTerrain == terrainModifier.Restore_Backup )
		{
			terrainData.SetHeights( 0, 0, backupHeightmapData );
		}
		else
		{
			TestingStuff();
		}
	}
	else if ( Input.GetMouseButtonDown(1) )
	{
		terrainData.SetHeights( 0, 0, backupHeightmapData );
	}
}

function TestingStuff() 
{
	heightmapData = terrainData.GetHeights( 0, 0, heightmapWidth, heightmapHeight );
	textureSize = new Vector2( racetrackTexture.width, racetrackTexture.height );
	
	var calc : float;
	var calcNorm : float;
	
	for ( var y : int = 0; y < textureSize.y; y ++ )
	{
		for ( var x : int = 0; x < textureSize.x; x ++ )
		{
			var pixelHeight : float = racetrackData[ x, y ];
			var terrainHeight : float = heightmapData[ y, x ];
			
			switch( EditTerrain )
			{
				case terrainModifier.Method_1 :
				
					pixelHeight = racetrackTexture.GetPixel( x, y ).grayscale;
					
					// find the difference
					var difference : float = terrainHeight - averageHeight;
					
					// adjusted height is pixel as a percentage of difference
					var adjustHeight : float = difference * pixelHeight;
					
					// apply to heightmapData[y,x]
					heightmapData[y,x] = heightmapData[y,x] + adjustHeight;
					
				break;
				
				case terrainModifier.Method_2 :
					
					pixelHeight = racetrackTexture.GetPixel( x, y ).grayscale;
					
					if ( terrainHeight > averageHeight  pixelHeight > 0.0 )
					{
						heightmapData[y,x] = terrainHeight - ( pixelHeight * terrainHeight );
					}
					/*
					else
					{
						heightmapData[y,x] = terrainHeight + ( pixelHeight * terrainHeight );
					}
					*/
					
				break;
				
				case terrainModifier.Method_3 :
					
					if ( terrainHeight > averageHeight )
					{
						heightmapData[y,x] = Mathf.Clamp( heightmapData[y,x] - 0.1, 0.0, 1.0 );
					}
					else
					{
						heightmapData[y,x] = Mathf.Clamp( heightmapData[y,x] + 0.1, 0.0, 1.0 );
					}
					
				break;
				
				case terrainModifier.Method_4 :
					
					//var diff : float = pixelHeight - terrainHeight;
					var diff : float = 1.0 - ( pixelHeight - terrainHeight );
					
					calc = Mathf.Clamp( terrainHeight + ( diff * 0.5 ), 0.0, 1.0 );
				
				heightmapData[y,x] = calc;
					
				break;
				
				case terrainModifier.Method_5 :
					
					// AVERAGE
					calc = ( pixelHeight * 0.5 ) + ( terrainHeight * 0.5 );
					
					// BLEND FACTOR
					pixelHeight *= blendFactor;
					terrainHeight *= 1.0 - blendFactor;
					
					//calc = pixelHeight + terrainHeight;
					calcNorm = Mathf.Clamp( calc, 0.0, 1.0 );
					
					heightmapData[y,x] = calcNorm;
					
				break;
				
				case terrainModifier.Method_6 :
					
					if ( terrainHeight < averageHeight )
					{
						calc = terrainHeight + ( pixelHeight * 0.1 );
					}
					else
					{
						//calc = terrainHeight;
						
						calc = terrainHeight - ( pixelHeight * 0.1 );
						//calc = terrainHeight - ( 1.0 - ( pixelHeight * 0.1 ) );
						//calc = terrainHeight - ( ( 1.0 - pixelHeight ) * 0.1 );
					}
					
					calcNorm = Mathf.Clamp( calc, 0.0, 1.0 );
					
					heightmapData[y,x] = calcNorm;
					
				break;
				
				case terrainModifier.Method_7 :
					
					if (terrainHeight > averageHeight)
					{
						calc = terrainHeight - (terrainHeight * pixelHeight);
					}
					else if (terrainHeight < averageHeight)
					{
						calc = terrainHeight + (terrainHeight * pixelHeight);
					}
					
					heightmapData[y,x] = calc;
					
				break;
			}
		}
	}
	
	terrainData.SetHeights( 0, 0, heightmapData );
}

Here is my test terrain : http://www.alucardj.net16.net/unityquestions2012/TestTerrain.asset

Here is my test RaceTrack image : http://www.alucardj.net16.net/unityquestions2012/RaceTrack0_Inverted.jpg

Couldnt view your question nor images… you probably figured this out?

What ever i understood :slight_smile: i think this is what you need:

    if (heightMap > averageHeight){
        newHeight = heightMap - (heightMap * pixelHeight);
    }
    else if (heightMap < averageHeight){
        newHeight = heightMap + (heightMap * pixelHeight);
    }

Thank you for responding, I had actually given up hope and deleted the question from Unity Answers.

I have updated the original post to include all the images, my current code (as I am still very much stuck), and my test terrain and racetrack image links. I shall try your method and post some screenshots with the results.

Edit : unfortunately this doesn’t seem to be working either. It is method 7 in the script. Method 6 seems to give the closest result but has this shear where the the average height is.

Original Terrain :

1237064--52557--$before_sm.png

Method 7 :

1237064--52558--$method7_sm.png

Method 6 :

1237064--52559--$method6_sm.png

Dammit, this is my 4th edit. Why wont the images show?

http://www.alucardj.net16.net/unityquestions2012/terrrain_before_sm.png
http://www.alucardj.net16.net/unityquestions2012/terrrain_method7_sm.png
http://www.alucardj.net16.net/unityquestions2012/terrrain_method6_sm.png

Okay… now i see what is the problem :slight_smile:

you cant use if / else condition with this matter.

see
if height > average
newHeight = terrainHeight - pixelHeight;
else
newHeight = terrainHeight + pixelHeight;

causes kind of black and white fx.

You need only one line:
newHeight = terrainHeight + ( averageHeight - terrainHeight ) * pixelHeight;

Edit: i tried it, and i guess it works. I couldnt add your terrain, though, so i made one and added some heights. You probably should export your terrain with “Export package”… unity probably do some magic there. When i copied that testTerrain.asset to asset folder, i got error msg “Terrain splat 0 is null.”

I used that racetrack.jpg …not entirely sure, but i think you should use 16bit images to get smoother heights when dealing with heightmaps.

1 Like

Your one-line formula helped me a lot, thank you very much.