fbpx Skip to content

Generate Tiled Height Map for Unreal Engine 4 Landscape with PowerShell

If you simply want to download the tool click here to download the completed project so you can generate tiles from a heightmap.

Overview

While we are ServerAcademy.com are NOT game developers we still love to mess around with tools like Unreal Engine 4 and Unity. Recently I was messing around with creating landscapes for World Composition (a tool within UE4) which requires that you use an external tool to export a height map into several smaller images (see image below):

example

I would need to convert this one height map above into four smaller height maps like shown below:

tile_x0_y0

tile_x0_y0.png

tile_x1_y0

tile_x1_y0.png

tile_x0_y1

tile_x0_y1.png

tile_x1_y1

tile_x1_y1.png

Now you might think, this is only four images... why not just crop and export them manually in Photoshop? Well the example only uses four images because that is easy to understand - but in actual practice we will need to create hundreds of images and the end result would look like below (400 tile images were created):

400 tiles

This would NOT be fun if you did this manually and imagine if you needed to make an adjustment on the height map which would require you to recreate the tiles... NOT fun!

It appears that the accepted way to automate this is to use a tool called "World Machine" which costs anywhere from $119 to $1,999 depending on your studio size. Yikes!

Instead of breaking down and buying a license for World Machine, I decided to simply write a PowerShell script that would create these images for free.

This will be a fun project for my students and it will come in helpful for indi developers also! Sounds like a win, win 😀

Tutorial setup

Now PowerShell cannot directly crop images by itself so we need to find a utility that can. A quick Google search turns up a tool called Image Magick - download and install this tool (they have options for Windows or Linux on their download page).

I am going to create a new folder on the C:\ drive of my computer. Here I will place the example.jpg height map we will be using for this tutorial AND the magick.exe file that we just installed (located at: C:\Program Files\ImageMagick-7.0.8-Q16\magick.exe on my PC).

Finally I will create a PowerShell script (.ps1 file) called heightmap_tile_generator.ps1. Your directory should resemble the image below:

Working Directory

According to the documentation, we can run use Image Magick to crop an image through the PowerShell command line as shown below:

C:\heightmap\magick.exe "C:\heightmap\example.jpg" -crop 100x100 "C:\heightmap\output.jpg"

This will crop the image "sourcefile.jpg" to 100x100 pixels and save it to output.jpg. Next we need to add an offset so we can incrementally crop the image. We can add an offset like this:

C:\heightmap\magick.exe "C:\heightmap\example.jpg" -crop 100x100+100+100 "C:\heightmap\output.jpg"

So the first 100x100 means export a cropped image that is 100 by 100 pixels, and set the origin at 100 pixels to the right and 100 pixels downward (+100+100).

We are going to start the script by declaring System.Drawing, specifying the source file location variable, and using System.Drawing.Image to obtain the information about the source image (image like resolution).

# Prep System.Drawing
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

# Get the file
$file = Read-Host -Prompt "Type the path to your source heightmap"
$image = [System.Drawing.Image]::FromFile($file)

Next we will declare a prefix for our tile images and output some information about the file we are about to use to generate tiles:

# File output prefix
$output_prefix = "tile_"

# Output what we are about to do
Write-Host "File prefix: $output_prefix"
Write-Host "Source file: $file"
Write-Host "Image Dimensions: $($image.Width) x $($image.Height)"

Next we will declare a prefix for our tile images and we will get the users desired width and height (in pixels) for each tile:

# See what dimenion the user wants to use
$tile_x=Read-Host -Prompt "Please enter the width in pixels each tile should be..."
$tile_y=Read-Host -Prompt "Please enter the height in pixels each tile should be..."

Now we will write create some output that tells the user how many tiles will be created based on their desired tile height and width. Keep in mind that we want this to be a whole number and not a decimal.

For example, we don't want to split a 1000x1000 image by 300x300 tiles. This would result in 11.1111111111111 tiles.

Partial tiles are no good. We only want full tiles. So instead we would want to split a 1000x1000 image into 250x250 tiles. This would result in 16 tiles.

If you're a little rusty on your math, remember that 1000 / 250 = 4. This means that we would have 4 horizontal tiles * 4 vertical tiles.

You can think of this like 4 columns and 4 rows (see image below):

columns_rows

We will use Write-Host and divide the image width by the desired tile height and width then multiplying them together. Next we are going to pause (wait for a key press before continuing), then set some variables that we will be using a for loop we are about to create.

# Output how many tiles there will be
Write-Host "There will be" (($image.width / $tile_x) * ($image.height / $tile_y)) " tiles."
Pause

# Starting coordinates
$x_coord=0
$y_coord=0

We will get started by iterating over all the tiles we need to create for the y axis (found by dividing the image height by the desired tile height):

# Iterate over Y
for ($y_index = 0; $y_index -lt ($image.Height / $tile_y); $y_index++) {

Now inside the first for loop let's immediately repeat that for the X axis:

# Begin iterating over X
for ($x_index = 0; $x_index -lt ($image.width / $tile_x); $x_index++) {

We are going to create the $dim variable which is going to contain the values that we will use for the crop command. It will look like this:

width,height+offsetx+offsety

...or

250,250,0,0

Next we will log what we are creating with the Write-Host command:

# Current position
$dim = "{0}x{1}+{2}+{3}" -f $tile_x,$tile_y,$x_coord,$y_coord

# Log what we are doing
Write-Host "Exporting x${x_index} y${y_index} to ${output_prefix}x${x_index}_y${y_index}.png"

Next we are going to crop our image using our position and dimensions, then we will increase our $x_coord variable by $tile_x (the desired tile width). Finally we will close the second for loop with the closing brace:

# Output file
magick.exe $file -crop $dim "${output_prefix}x${x_index}_y${y_index}.png" # File output name

# Increment x coordinates
$x_coord += $tile_x

}

After we are done iterating over the X values for this Y position, we need to reset the X coordinates so it will restart from the left hand side of the screen and move our y coordinates down one tile before closing the first loop that iterates over the Y axis:

# Reset x coord since we are moving from left to right for every 1 y movement
$x_coord = 0

# Increment y coordinates
$y_coord += $tile_y

}

Here is the entire script (source and download):

# Prep System.Drawing
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 

# Get the file
$file = Read-Host -Prompt "Type the path to your source heightmap"
$image = [System.Drawing.Image]::FromFile($file)

# File output prefix
$output_prefix = "tile_"

# Output what we are about to do
Write-Host "File prefix: $output_prefix"
Write-Host "Source file: $file"
Write-Host "Image Dimensions: $($image.Width) x $($image.Height)"

# See what dimenion the user wants to use
$tile_x=Read-Host -Prompt "Please enter the width in pixels each tile should be..."
$tile_y=Read-Host -Prompt "Please enter the height in pixels each tile should be..."

# Output how many tiles there will be
Write-Host "There will be" (($image.width / $tile_x) * ($image.height / $tile_y)) " tiles."
Pause

# Starting coordinates
$x_coord=0
$y_coord=0

# Iterate over Y
for ($y_index = 0; $y_index -lt ($image.Height / $tile_y); $y_index++) {

    # Begin iterating over X
    for ($x_index = 0; $x_index -lt ($image.width / $tile_x); $x_index++) { 
        
        # Current position
        $dim = "{0}x{1}+{2}+{3}" -f $tile_x,$tile_y,$x_coord,$y_coord

        # Log what we are doing
        Write-Host "Exporting x${x_index} y${y_index} to ${output_prefix}x${x_index}_y${y_index}.png"

        # Output file
        magick.exe $file -crop $dim "${output_prefix}x${x_index}_y${y_index}.png" # File output name

        # Increment x coordinates
        $x_coord += $tile_x
    }

    # Reset x coord since we are moving from left to right for every 1 y movement
    $x_coord = 0

    # Increment y coordinates
    $y_coord += $tile_y

}

Now let's save the script and launch the PS1 file in our folder. Since the example image is 1000x1000, I am going make tiles that are 500x500:

Script Executed
complete_directory

As you can see in the image above the script created the tiled images!

This is what being a good IT administrator is all about. If you did IT support at a game development company, you could create this script and save your company thousands of dollars.

Hope you enjoyed this tutorial and we will see you in the next one!

Facebook Comments