Blender 3D: Noob to Pro/Landscape Modeling III: Exporting as a Heightmap
|Applicable Blender version: 2.44.|
In this tutorial, I will show you how to export your beautiful terrain to a heightmap that can be used in most 3D graphics engines. The benefit to doing this as opposed to just exporting your mesh is that many engines have a special process for dealing with terrains as opposed to regular meshes where it divides the terrain up into different sections so that it can subdivide regions closer to the camera more for greater detail, and also cull (not render) regions outside the camera's field of view, saving precious processing time. This structure is called an oct tree and is a highly optimized way to render large meshes such as terrains.
Before I begin, I must give credit to the Creating a Heightmap from a Plane tutorial on the Blender wiki. This is how I learned to do this trick, and most of what I will cover came from this tutorial. I'm simply including it here with the other landscaping tutorials I've written for convenience.
A Word About Heightmaps
I will briefly cover what a heightmap is and how it's useful for those who may not know. If you are familiar with heightmaps, you can skip ahead to the next section.
A heightmap is a grayscale image that uses various shades of gray to represent different elevations across a map. Since the images are 8-bit (with the exception of some RAW formats that are 16-bit), you have 256 different shades of gray, ranging from pure black (0) to pure white (255). Black represents the lowest elevation on the map, while white represents the highest. Many 3D graphics engines already have functions to read in a heightmap and generate terrain from it. The way it does this is it creates a grid of vertices (like we are going to do in Blender in just a moment), and uses the heightmap to determine the elevation of each point on the grid. The more intense the pixel color (the closer it is to white), the higher the elevation of that vertex. In most cases, if the resolution of the heightmap is smaller than the resolution of the terrain, the engine will interpolate the vertices in between those set by the heightmap. For example, if your heightmap is 256x256 and your terrain is 1024x1024, your heightmap will determine the elevation of every fourth vertex, and the three in between will be interpolated.
Note that these values do not represent the absolute height of any given pixel, but rather the height relative to the rest of the map. That is, your heightmap does not represent a landscape with altitudes ranging from 0 to 256. When importing a heightmap, you can specify the minimum and maximum altitudes for the map, and the whole thing gets scaled. For example, suppose you decide the minimum is 1000 feet, and the maximum is 5000 feet. When the terrain is rendered, black pixels will represent 1000, white pixels will represent 5000, and a pixel that is exactly in between black and white (128) will represent 3000 feet (half of the sum of min and max). This makes heightmaps relatively flexible.
Unfortunately, as you may have already noticed, there is a limitation of using heightmaps. Since you only have 256 shades of gray, you only have 256 possible elevations. This causes a problem. No matter how precise your vertices may be in the mesh that you model, no matter how smooth you may have it looking, each point will get rounded off to an integer value between 0 and 255. When used to render a terrain, this can cause a "stair step" effect as the terrain goes from one discrete elevation to the next instead of making a smooth transition. But there are ways to avoid this, which I will cover later.
I apologize if you're scratching your head right now saying "Huh?" For a further explanation and example, check the Wikipedia page on heightmaps.
Creating the Material
To begin this tutorial, I will assume that you already have a landscape that you want to create a heightmap for. If not, read my first tutorial, Landscape Modeling I: Basic Terrain, to quickly create something. WORD OF CAUTION!!! If you've already created a landscape, try to remember the exact size of the plane that you used. If you're about to make one, it's okay to scale the plane to have a larger surface to work with, but REMEMBER how much you scale it by. This will make things easier later. I recommend just working with the default 2x2 plane or grid and zooming in on it. You may also want to backup your scene since we'll be changing a few things
To create the material we need, do the following steps:
- Remove any existing materials that may be applied to the terrain.
Note: to remove a material, I think you need to go to the links and pipeline window in the shader section and click the little "X" underneath the words "link to Object"
- Add a new material in the Shading tab (F5).
- Go to the Textures tab (F6) and add a new texture. This should be the only texture on your material.
- From the Texture Type drop down menu, select Blend.
- In the same window, find the Colors tab and select Colorband. This will let us define the blend pattern, which by default fades from black with full transparency to a solid cyan. Why cyan? I have no idea. It's an odd default.
- Select the black color by clicking on the left side of the colorband. You should see a little black and white bar selected.
- Increase the Alpha all the way up to 1 by adjusting the slider labeled "A".
- Select the cyan color and change this to a solid white.
- What you have now should look like my colorband below.
- Return to the Materials tab (the red ball icon next to the cheese looking icon).
- In the Material pane, select the Shadeless option to disable lighting on the material.
- To the right, find the pane labeled Map Input. In the bottom of this pane is a grid that by default reads:
- Set all three rows to Z so that the texture will map entirely to the Z coordinate.
- In the preview window to the left, if you select the sphere or cube you should see that it goes from black on the bottom to white on the top. That's the effect we want for the terrain. Remember, in a heightmap, black represents the lowest point and white the highest.
- Finally, switch back to the Editing tab (F9) and click the Set Smooth button to enable Gouraud shading so that everything appears smooth.
Setting up the Camera
That's all we need to do for the terrain itself. Now what we're going to do is setup the camera in such a way that when we render the scene we'll get an orthographic projection straight down on the terrain so that we can save it as our heightmap.
- If you still have any lights or cameras in the scene, delete them.
- Switch to the top view (NUM7) and place the 3D cursor at the origin (0, 0, 0) by left clicking near the origin, hitting SHIFT+SKEY and selecting Cursor to Grid. (An easier way is: hitting 'SHIFT+SKEY' and selecting 'Cursor to Center')
- Add a new camera, Space->Add->Camera. This will add a new camera to the scene that is looking straight down.
- Switch to a side view (NUM1 or NUM3) and move the camera up the Z axis so that it is above the highest point in your terrain. Even though the view is going to be orthographic, and therefore distance doesn't matter, the entire terrain still needs to be in front of the camera.
- Now, with the camera selected, go to the Editing tab (F9) and select the "Orthographic" button. This will change the camera to an orthographic projection which basically ignores the z-coordinate for all vertices (except for determining which pixels should be in front of others).
- Just above the orthographic button is a value labeled "Scale". Remember earlier when I told you to remember how big your terrain is? This is why.
- Change the Scale of the lens (default: 6) to match the size of your terrain. If you left the plane the default size, this should be 2. If you scaled the plane up by a factor of 20, as suggested in the "Landscape Modeling I: Basic Terrain" tutorial, this should be 40 (the plane is initially 2 units square).
- Now go to the scene tab (F10) and under Format, change the SizeX and SizeY to be the size you want your heightmap to be. For most graphics engines, this is required to be either a power of 2 (2^n) or one more than a power of 2 (2^n+1). So try something like 256 (or 257 if you need it to be 2^n+1).
- Now, if you switch to the camera view (NUM0) you should see a "flat" square that completely fills the dotted box which represents the camera's field of view.
- Render the scene (F12). You should be looking at your heightmap. Hit F3 to save the rendered image and you're good to go!
Since this image has no color, and some engines require that your heighmap be a grayscale image (only one color channel instead of three), select the BW button in the Format pane before rendering. This will render it as a grayscale image instead of a color image.
As mentioned earlier, exporting the heightmap as an 8-bit image (Blender doesn't support any 16-bit formats that I'm aware of) has the drawback that you only have 256 discrete height values which can cause a stair-step effect when the terrain is subdivided. Ideally, you could use an application that can export the image in a 16-bit raw format, such as Terragen. But this is a Blender tutorial, so we'll not go into that. =P
Reducing the resolution
One way you can "smooth" out the rendered terrain is to actually reduce the resolution of the heightmap. This may sound counter intuitive, but bear with me. Suppose you have a heightmap that is a simple blend from black to white going from left to right. If your heightmap is 1024x1024, you will get 4 columns of each height since you only have 256 different values. In other words, your grayscale will look something like this:
If you try to render terrain from this, you'll get 4 points with 0 altitude, 4 with 1, 4 with 2, and so on, creating the stair-step effect.
Now suppose you make the same heightmap, but reduce its size to 256x256. Now you'll have 1 column for each height, and your grayscale will look something like this:
You may be thinking, "But then my terrain will be smaller, or less detailed." But it turns out the opposite is actually true. Consider applying the above heightmap to a terrain that is 1024x1024. The values from your heightmap will be applied to every 4th vertex, and the ones in between will be interpolated. So instead of having something like:
0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0 2.0
which is what you would get with the 1024x1024 heightmap, you'll actually get something like this:
0.0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 2.0
Where the integers are the values from your heightmap and the fractions are interpolated values. This is assuming that whatever engine you are using will interpolate the values between heights read in from the heightmap.
At the present time, this is the only method I have verified that helps reduce the effects of stair-stepping. As I try different techniques I will update this page with them. For example, I know that Adobe Photoshop can export images in 16-bit raw formats, but I haven't tried it yet so I don't want to suggest it as a solution.