SakeTami
Kruithne
Kruithne

patreon


WoW 3D: Generating Heightmaps

Introduction

When it comes to exporting terrain from World of Warcraft for use in 3D projects or beyond, the 3D mesh of the terrain exported from software such as wow.export is more than suitable for 95% of use-cases. However recently someone was asking me about heightmaps and how they could be generated for WoW terrain; so that's what we're going to do here today.

For those unfamiliar, a heightmap is a texture (or often multiple textures) that defines the height of terrain. The implementation may vary between software and engines, but for the purposes of this guide, we're going to be working with the simple white to black heightmaps. 

The values on the texture range from white being the highest point, and black being the lowest point. A large map is going to be made up of multiple chunks of terrain - just like in WoW - so it's important to clamp everything to the same mid-level (more on this later).

Why would you want to generate a heightmap? If you're not sure, the chances are that you probably don't. The use-case that was presented to me recently and inspired this article was for use in Unreal Engine.

With most things, this is not going to be the only way to do things, but this article is going to detail step-by-step my method to generate heightmaps from WoW terrain.

You will need the following:
- Blender
- wow.export 

Generating Heightmaps (Manually)

To start with, open up wow.export with a valid installation or CDN and head over to the 'Maps' tab. From there, select your map of choice. For this example, I'm going to be using the Darkmoon Faire map.

Select the tile(s) you would like and then export them as OBJ. For the sake of speed, I've disabled everything on the 'Export Options' and set the 'Terrain Texture Quality' to 'None'. 

Once your export is completed, move over to Blender and import your tile(s) using File → Import → Wavefront (.obj) and locating your tile for import. In this example, our tile is ../maps/darkmoonfaire/adt_20_40.obj relative to our configured export directory in wow.export.

If you're used to using wow.export, you might be used to using WoW OBJ (.obj), but since we don't need any doodads or WMOs for this, Wavefront (.obj) is fine.

When the tile has imported, there's a good chance it will be out of view since map tiles export based on their in-game position. To bring it into view select the tile (likely called 'Mesh') in the Outliner panel and then, with your cursor back over the 3D viewport, press NUMPAD DEL.

Now that you have your tile, the first thing we want to do is flatten the UV. To do this, switch your viewport to Top Orthographic 90° (0, -90 and 180 will work, but will change the orientation of your resulting bake).

Switch into Edit Mode and ensure the entire mesh is selected (Press A to Select All). Press U to bring up the UV Mapping menu and select Project from View (Bounds).

Now that our UV is flattened, let's add a heightmap shader. Switch back to Object Mode and under the Materials tab, add a new material slot, and assign a new material. For this, I'll call the new material heightmap_20_40.

It's very possible that you may already have a material slot (or multiple, if you picked different export options), you can simply delete or re-use it.

Switch over to the Shader Editor delete the default Principled BSDF node that will be on your graph. Now add a Texture Coordinate node and a Separate XYZ node. Connect the Generated output of the Texture Coordinate node to the Vector input of the Separate XYZ node.

Add a ColorRamp node and connect the Y output of the Separate XYZ node into the Fac input of the ColorRamp node. Add a Diffuse BSDF node and connect the Color output of the ColorRamp to the Color input of the Diffuse BSDF node, and the BSDF output of the Diffuse BSDF node to the Surface input of the Material Output node.

You may be wondering why we're using Y and not Z, given in Blender Z is the height. This is because it's relative to the mesh itself, which has an UP based on the WoW system.

Add an Image Texture node, but don't connect it to anything. Click the New button on it, enter a width/height to your suiting (this will be the final dimensions of your heightmap image). Uncheck the Alpha box, and then click OK. Giving it a name (such as heightmap_20_40) may be useful for finding it later!

Remember, don't connect the Image Texture node to anything!

Switch back to the 3D Viewport and select the Material Preview shading mode (top right). You should now see that the mesh is black at the very lowest point, white at the very highest, with a gradient between the two.

Success! We have a heightmap. That's not much use to us on a mesh though, so let's convert that to an image that we can use elsewhere.

Start by going into World PropertiesSurface and ensure the Color value is set to pure white. By default, it will be grey which will affect the baking.

Next set your rendering device to CPU under Render Properties. At the time of writing this article, texture baking is not available while in GPU mode.

Ensure you have your mesh selected and then under Render PropertiesBake, choose the Bake Type of Diffuse and then hit Bake.

After a few moments (you'll get a progress bar at the bottom of your screen) your bake will be complete. Switch to the Image Editor panel and find whatever you named your image. You should now have a heightmap.

In the Image Editor, click Image → Save As... to save it as a PNG (or whatever format you prefer).

You may notice some triangular artifacts, as shown in the output example above. This is caused by incorrect normals or duplicated vertices. To resolve incorrect normals, use Shift + N in Edit Mode. To remove duplicate vertices, enter Edit Mode and select Mesh → Clean Up → Merge By Distance. Set the Merge Distance to 0.01 m.


Heightmap Balance

If you've made it this far, the chances are you'll want to be cranking out heightmaps for multiple tiles. This is where a balance issue comes into play. If you put two tiles side by side, you'll notice that they don't connect seamlessly.

This is because for each tile, the highest point starts at white and then gradients down to black at the lowest point, but only for that tile. Most tiles have different highest and lowest points. For heightmaps to work, they need to share the same mid-point.

A quick and easy solution to this? First make sure that each tile has a UNIQUE version of the material we created. This ensures that when we bake, we produce a different image for each tile. This can be done by clicking the users button under a material.

Now with both of your tiles selected in Object Mode, press CTRL + J to merge them together. In an instant, you should now have a smooth transition between your tiles.


Closing Notes

This guide isn't perfect, but hopefully it serves as some help to those who have been asking me about generating heightmaps from WoW terrain, even if it doesn't serve as a definitive guide.

If you're intending to generate a lot of heightmaps, I highly recommend getting your hands dirty with Python, as this entire process can be automated in Blender.

Good luck!

WoW 3D: Generating Heightmaps

More Creators