Download

Making Heightmaps with c++?

I am trying to find a way to set the heightmap of a landscape from a AlevelScripterActor C++ class. I think I have found a way to do this by setting the heightmap in the landscape component.
“landscape->LandscapeComponents[0]->SetHeightmap(test);”
But now I am trying to find a way to create a heightmap using C++. I am planning on combining perlin noise with different frequencies to create fractal noise and use that as the heightmap. Now I know that you can create the images this way but are they the same as UTexture2D (which is what the SetHeightmap is requesting).

In short is there a way to make a UTexture2D from a heightmap. I have tried understanding what UTexture2D is but ue4 is very confusing for me.

Thanks in advance

update
I think i have found a possible way. As I said i want to try and set the heightmap using SetHeightmap. The only problem was i did not know how to make a UTexture2D object to put in it. I have found a way to make it based upon an image using FImageUtils::CreateTexture2D(). I first tryed testing this out with FImageUtils::CreateCheckerboardTexture(FColor(255, 255, 255), FColor(0,0,0),10) is this required less variables. But now the problem is actually setting this as the height map. I have tried doing it for 1 an all components of the landscape and even same update method’s which sound like they would work but nothing. If someone knows any way of setting the heightmap in C++ the correct way then please let me know. I will also add the code that i have so far. I hope it will help someone.

//my input is a landscape object
int ACPP_TestLevel::VeranderLandschap(ALandscape* landscape)
{
	//testing out some code to get heightmap
	//UTexture2D* heightMap = landscape->LandscapeComponents[0]->GetHeightmap();
	//EPixelFormat formatPixel = heightMap->GetPixelFormat();
	// figuring out how to make UTexture2D
	/*UTexture2D* test = UTexture2D::CreateTransient(90,100, PF_B8G8R8A8);*/
	int aantal = landscape->LandscapeComponents.Num();

	//first attempt at setting the heightmap
	//landscape->LandscapeComponents[0]->SetHeightmap(test);
	// testing code to see if this would load the changes
	landscape->LandscapeComponents[0]->RequestHeightmapUpdate();


	// creating CreateCheckerboardTexture to use as heightmap
	//UTexture2D* bob = FImageUtils::CreateTexture2D();
	UTexture2D* bob2 = FImageUtils::CreateCheckerboardTexture(FColor(255, 255, 255), FColor(0,0,0),10);
	landscape->LandscapeComponents[1]->SetHeightmap(bob2);
	landscape->LandscapeComponents[1]->RequestHeightmapUpdate();
	// testing if PostLoad would load the changes
	landscape->LandscapeComponents[1]->PostLoad();

	// updating al the landscape component
	for (int i = 0; i < aantal; ++i)
	{
		landscape->LandscapeComponents[i]->SetHeightmap(bob2);
		landscape->LandscapeComponents[i]->RequestHeightmapUpdate();
		landscape->LandscapeComponents[i]->PostLoad();
	}


	// this is code from https://isaratech.com/save-a-procedurally-generated-texture-as-a-new-asset/
	// it is about saving textures to a local folder.
	// tested this code out to see if i actualy created the texture.
	int TextureWidth = 256;
	int TextureHeight = 256;
	FString TextureName = "testtexture";
	FString PackageName = TEXT("/Game/ProceduralTextures/");
	PackageName += TextureName;
	UPackage* Package = CreatePackage(NULL, *PackageName);
	Package->FullyLoad();

	UTexture2D* NewTexture = NewObject<UTexture2D>(Package, *TextureName, RF_Public | RF_Standalone | RF_MarkAsRootSet);

	NewTexture->AddToRoot();				// This line prevents garbage collection of the texture
	NewTexture->PlatformData = new FTexturePlatformData();	// Then we initialize the PlatformData
	NewTexture->PlatformData->SizeX = TextureWidth;
	NewTexture->PlatformData->SizeY = TextureHeight;
	//NewTexture->PlatformData->NumSlices = 1;
	NewTexture->PlatformData->PixelFormat = EPixelFormat::PF_B8G8R8A8;

	uint8* Pixels = new uint8[TextureWidth * TextureHeight * 4];
	for (int32 y = 0; y < TextureHeight; y++)
	{
		for (int32 x = 0; x < TextureWidth; x++)
		{
			int32 curPixelIndex = ((y * TextureWidth) + x);
			Pixels[4 * curPixelIndex] = 0;
			Pixels[4 * curPixelIndex + 1] = 0;
			Pixels[4 * curPixelIndex + 2] = 0;
			Pixels[4 * curPixelIndex + 3] = 0;
		}
	}

	// Allocate first mipmap.
	FTexture2DMipMap* Mip = new(NewTexture->PlatformData->Mips) FTexture2DMipMap();
	Mip->SizeX = TextureWidth;
	Mip->SizeY = TextureHeight;

	// Lock the texture so it can be modified
	Mip->BulkData.Lock(LOCK_READ_WRITE);
	uint8* TextureData = (uint8*)Mip->BulkData.Realloc(TextureWidth * TextureHeight * 4);
	FMemory::Memcpy(TextureData, Pixels, sizeof(uint8) * TextureHeight * TextureWidth * 4);
	Mip->BulkData.Unlock();

	NewTexture->Source.Init(TextureWidth, TextureHeight, 1, 1, ETextureSourceFormat::TSF_BGRA8, Pixels);

	NewTexture->UpdateResource();
	Package->MarkPackageDirty();
	FAssetRegistryModule::AssetCreated(NewTexture);

	FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
	bool bSaved = UPackage::SavePackage(Package, NewTexture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName, GError, nullptr, true, true, SAVE_NoError);

	delete[] Pixels;	// Don't forget to free the memory here
	

	for (int i = 0; i < aantal; ++i)
	{
		landscape->LandscapeComponents[i]->SetHeightmap(NewTexture);
		landscape->LandscapeComponents[i]->RequestHeightmapUpdate();
		landscape->LandscapeComponents[i]->PostLoad();
	}
	// just a number i return that i desplay in the game to see if things worked.
	if (bSaved) aantal = 0;
	return aantal;
}

Even if you pull it off, you will be in trouble because most of the functions are resticted and for the editor. Landscape module is for the editor mostly.

I managed to get things compiled with an array for landscapes to get landscapes.
The function you have is part of the ULandscape to set landscapes, you need a 2d texture for it.
Did you actually manage to set the landscape from C++ ?

Then what are you going to do ? since all the functions are restricted, if you want to make dynamic objects for your landscape forget it.

landscape->LandscapeComponents[i]->SetHeightmap(NewTexture);
and I got setheightmap does not accept just one argument, but it all it has one argument.

NewTexture->AddToRoot(); // This line prevents garbage collection of the texture
NewTexture->PlatformData = new FTexturePlatformData(); // Then we initialize the PlatformData
NewTexture->PlatformData->SizeX = TextureWidth;
NewTexture->PlatformData->SizeY = TextureHeight;
//NewTexture->PlatformData->NumSlices = 1;
NewTexture->PlatformData->PixelFormat = EPixelFormat::PF_B8G8R8A8;

You are building the texture and the result is ? did you actually set the landscape with your texture ?

https://docs.unrealengine.com/4.27/en-US/API/Runtime/Landscape/ULandscapeComponent/
SetHeightMap(UTexture2D* NewHeightmap)
Function is availble and not restricted.

So SetHeightMap(Your2Dtexture) and declare it in the header as a 2dtexture.
You set it with a component, where did you get that from, where did you find the documentation.
Makes sense UlandcapeComponent is a component.

1 Like

After some searching I have given up on trying to find a way to set the heightmap using c++. It just is not worth it. I instead focused on making a heightmap using c++. This has worked a litle bit. Combining the info from redblobgames (Making maps with noise functions) and Isara Tech (UE4 - Save a procedurally generated texture as a new asset - Isara Tech.) I was able to create a 2 dimensonal array containing perlin noise values. I then used these values to set the gray colors of a texture (make shure u are using PF_G8 as pixel format). This texture is then saved to the /Game/ProceduralTextures/ folder. After that u have to export the texture out of the map, open the file using paint and the save the file as a png. The result I got from the code where les than optimal but it something. If you find this I hope you have better luck automating this then I did.

//my input is a landscape object
void ACPP_TestLevel::VeranderLandschap(ALandscape* landscape)
{
	// this is code from https://isaratech.com/save-a-procedurally-generated-texture-as-a-new-asset/
	// it is about saving textures to a local folder.
	// tested this code out to see if i actualy created the texture.
	int TextureWidth = 256;
	int TextureHeight = 256;

	FString TextureName = "testtexture5";
	FString PackageName = TEXT("/Game/ProceduralTextures/");
	PackageName += TextureName;
	UPackage* Package = CreatePackage(NULL, *PackageName);
	Package->FullyLoad();

	UTexture2D* NewTexture = NewObject<UTexture2D>(Package, *TextureName, RF_Public | RF_Standalone | RF_MarkAsRootSet);

	NewTexture->AddToRoot();				// This line prevents garbage collection of the texture
	NewTexture->PlatformData = new FTexturePlatformData();	// Then we initialize the PlatformData
	NewTexture->PlatformData->SizeX = TextureWidth;
	NewTexture->PlatformData->SizeY = TextureHeight;
	//NewTexture->PlatformData->NumSlices = 1;
	NewTexture->PlatformData->PixelFormat = EPixelFormat::PF_G8;


	int height = TextureHeight;
	int width = TextureWidth;

	double value[256][256];

	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			float nx = (x/(width-0.5));
			float ny = (y/(height-0.5));

			float e = 1*FMath::PerlinNoise2D(FVector2D((1*nx), (1*ny)));
			e = (e+0.5*FMath::PerlinNoise2D(FVector2D((2*nx), (2*ny))));
			e = (e+0.25 *FMath::PerlinNoise2D(FVector2D((4*nx),(4*ny))));
			e = (e /(1 + 0.5 + 0.25));
			value[y][x] = pow(e, 1.00);
			
		}
	}

	

	uint8* Pixels = new uint8[TextureWidth * TextureHeight];
	for (int32 y = 0; y < TextureHeight; y++)
	{
		for (int32 x = 0; x < TextureWidth; x++)
		{
			//int color = FMath::PerlinNoise1D(static_cast <float> (rand()) / static_cast <float> (RAND_MAX))*255;

			int32 curPixelIndex = ((y * TextureWidth) + x);
			Pixels[curPixelIndex] = (value[y][x])*255;
		}
	}
	// Allocate first mipmap.
	FTexture2DMipMap* Mip = new(NewTexture->PlatformData->Mips) FTexture2DMipMap();
	Mip->SizeX = TextureWidth;
	Mip->SizeY = TextureHeight;

	// Lock the texture so it can be modified
	Mip->BulkData.Lock(LOCK_READ_WRITE);
	uint8* TextureData = (uint8*)Mip->BulkData.Realloc(TextureWidth * TextureHeight * 4);
	FMemory::Memcpy(TextureData, Pixels, sizeof(uint8) * TextureHeight * TextureWidth * 4);
	Mip->BulkData.Unlock();

	NewTexture->Source.Init(TextureWidth, TextureHeight, 1, 1, ETextureSourceFormat::TSF_G8, Pixels);

	NewTexture->UpdateResource();
	Package->MarkPackageDirty();
	FAssetRegistryModule::AssetCreated(NewTexture);

	FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
	bool bSaved = UPackage::SavePackage(Package, NewTexture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName, GError, nullptr, true, true, SAVE_NoError);

	delete[] Pixels;	// Don't forget to free the memory here
	
	int aantal = landscape->LandscapeComponents.Num();
	for (int i = 0; i < aantal; ++i)
	{
		landscape->LandscapeComponents[i]->SetHeightmap(NewTexture);
		landscape->LandscapeComponents[i]->RequestHeightmapUpdate();
		landscape->LandscapeComponents[i]->PostLoad();
	}
}

You can upload a png file 8bits, convert to texture and then use the set function on it with the component I guess.

You can use the image wrapper from unreal that loads pngs, you can load an 8bit png grayscale as your heightmap.

It’s just more easy, there are a lot of software out there to generate your png grayscale heightmap. But if you want to make your own perlin noise then you can do that.

Did you actually manage to set the texture as a new height map ? Did your texture generate the new land scape ?

So you make a flat surface with the unreal editor from the unreal editor where you have options to make landscapes. Then you bring that in with an array from the level and then you set new heightmap on it with your texture, your texture is generated from your png file you imported with image wrapper.

You have to import it with image wrapper so you get variables on it.

Just asking, did you actually manage to set the new height map. It’s what this function is for, for flat surface then set new, it is so you can import your blank landscape in C+ and create new with this function, a new heightmap based on your texture.

I did not look in the function to see what it contains, it may contain x,y minimal and maximal and reading pixel brightness maybe.

If it does not you have to do this on your own. If the function does not contain these things.
If you manage to set it, maybe you want to post, up to you.
Anyway next it depends on what you want to do with it, I wanted to create dynamic layers but it’s very hard using functions from the landscape module because most of them are restricted for the editor.

So you have to be C++ god like to build it in c++ without anything at all from unreal, and you may get restrictions from unreal.
So the best way I think is create a hightmap with the editor, then some how convert it to some kind of a mesh, and then you can edit the mesh how you like, the editor supports png landscapes.

So you make your landscape then convert to mesh maybe, then you can do whatever you like with the mesh, it’s a static mesh, procedural mesh, whatever . So this is another way of doing it.

This is what I wanted to look on, to convert landscapes to regular mesh and then do C++ on the mesh for dynamic layers, the landscape mesh is already made with geography it has, and you just make clones of it and create them as dynamic layers, you can set dynamic layers in C++ unreal, set them on objects, boxes and so on, you can change them on the fly or make them fluctuate over the land, so you have these cool effects. :slight_smile:

Kingbaslik where do you get the landscape in your code, that is not inside your code.
You got the perlin noise, the new texture, but you did not get a landscape to do work on, to set it.

From what I was reading you need to fetch a landscape that is in the scene with an array, search in the level for it.

1 Create heightmap either by direct png you already have a png grayscale that is a heightmap.
Or create noise with perlin.

2 Turn your png or perlin to 2dtexture

3 Get a landscape from the level of the scene, you have to fetch it with an array

So far so good, we get what we want.

Number 4 problematic set the new landscape with your texture.
My best guess is the component that you set it with is the landscape that you found and you have a variable on it now.

TArray<ALandscape*> landscapes;
ULevel level = GetLevel();
for( int i = 0; i < level->Actors.Num(); i++ )
if( ALandscape
land = Cast( level->Actors[ i ] ) )
landscapes.Push( land );
return landscapes;

ALandscape* Landscape = landscapes[0];

Now you have a variable to set the function with Landscape should be the result of the array, and 0 is the number of landscape in the level, 0 representing the number of the landscape among other landscapes, since it’s only one, it’s the first number 0, if you had another one then that landscape would be 1, and so on.

So… it should sort of work but it does not.
Landscape->LandscapeComponents[0]->SetHeightmap(yourtexture);
Landscape->LandscapeComponents[0]->RequestHeightmapUpdate();
Landscape->LandscapeComponents[0]->PostLoad();

If you manage to make it work with what I posted here please share with the rest of us.

You see what this does

TArray<ALandscape*> landscapes;
ULevel level = GetLevel();
for( int i = 0; i < level->Actors.Num(); i++ )
if( ALandscape
land = Cast( level->Actors[ i ] ) )
landscapes.Push( land );
return landscapes;
ALandscape* Landscape = landscapes[0];

It’s a fetch array, it then get’s the level, it counts the actors ++
then there is a condition there that if it finds a landscape among the actors it counted and seen to cast it and store to ALandscape* land
So maybe land is better to set the component with, tho the return is landscapes.

So maybe your code will work if you try to get a landscape , I don’t think you can do it other way because you have to have a landscape, otherwise where are you casting your landscape ? on what surface ? if there is no landscape format present. I think the landscape you generate from the editor has to be flat, so you can raise it with the set function.