Procedural Terrain Texture Generation

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Frosty Topaz
Posts: 107
Joined: Sat Nov 04, 2006 9:42 pm

Procedural Terrain Texture Generation

Post by Frosty Topaz »

Just like the subject says... procedural terrain texture generation. The colours in the texture are based on the height and slope of the terrain. It would not be hard to adjust the formulas used to determine the colours; even to make snowy mountains, sandy lake/sea/ocean beds, or hot pink grassy planes on some alien world.

Code: Select all

ITexture *generateTerrainTexture(IVideoDriver *driver, ITerrainSceneNode *terrain)
{
	// Create the texture we'll use on the terrain
	// NOTE: The texture is 128 by 128 while the terrain is 129 by 129.  There are a couple of reasons
	// for this.  First it's better for texture sizes to be a power of 2.  Second this size lets us work
	// with the texture and terrain together as if each pair of triangles on the terrain is one pixel on
	// the texture.  It also gives us a bit of room when calculating slopes.
	ITexture *texture = driver->addTexture(dimension2d<s32>(128,128), "texture", ECF_A8R8G8B8);

	// Check if the colour format is what we expect.  IVideoDriver::addTexture() does not have to
	// use the colour format you specify so it must be checked.  Obviously it would be better to
	// generate a texture for all the colour formats but for this example I wanted to keep the whole
	// process simple.
	if (texture->getColorFormat() != ECF_A8R8G8B8)
		return NULL;

	// Lock the texture we created earlier so we can work on the data
	u32 *texData = static_cast<u32 *>(texture->lock());

	// Base color of black
	u32 color = 0xff000000;

	// For each point on the texture
	for (int x = 0; x < 128; x++)
	{
		for (int y = 0; y < 128; y++)
		{
			// NOTE: X and Y for the texture are X and Z respectively for the terrain.  I've tried to
			// keep my variable naming consistant based on whether the variable is referring more to the
			// terrain scene node or the texture.  But most things refer to both so Y and Z are used
			// somewhat interchangably.

			// Grab the scale of the terrain
			f32 xScale = terrain->getScale().X;
			f32 yScale = terrain->getScale().Y;
			f32 zScale = terrain->getScale().Z;

			// Calculate the slopes in the x and y directions
			// Another option would be to find the normal vector for the area being worked on
			// and using the y component as the slope.  This would also let you skip the next step
			// as the y component of the normal would always be positive.
			f32 xSlope = terrain->getHeight(x*xScale, y*zScale) - terrain->getHeight(x*xScale + 1.0f, y*zScale);
			f32 ySlope = terrain->getHeight(x*xScale, y*zScale) - terrain->getHeight(x*xScale, y*zScale + 1.0f);
			
			// Calculate the absolute values of the slopes
			xSlope = abs_<f32>(xSlope);
			ySlope = abs_<f32>(ySlope);

			// Calculate the red color based on slope
			// Steeper = more red
			s16 red = static_cast<s16>((xSlope > ySlope ? xSlope : ySlope) * 128);

			// Calculate the green value based on height
			// Lower = more green
			s16 green = 255 - static_cast<u8>(terrain->getHeight(x*xScale, y*zScale) / yScale);

			// Just set the blue
			s16 blue = 0x00;

			// NOTE: You can change the way the colours are calculated to adjust the results
			// but you should make sure to leave in the clamping I have applied so the colour
			// compilation below will work.
			clampColours(red, green, blue);

			// Build the colour and set it in the correct place
			texData[x+128*y] = color + (red << 16) + (green << 8) + blue;
		}
	}

	// Unlock the texture so it can be used
	texture->unlock();

	// Return our pointer to the newely created texture.
	return texture;
}
EDIT: clampColours() just fixes each channel into the rage of 0 to 255.

Ignore the black artifacts on the ridge. This picture was taken on my laptop with its crap video hardware.
Image
Last edited by Frosty Topaz on Sun Dec 02, 2007 8:12 pm, edited 1 time in total.
Frosty Topaz
=========
It isn't easy being ice-encrusted aluminum silicate fluoride hydroxide...
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

this is cool, thanks for sharing code. really appreciate it.
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

by the way, it would be nice to experiment with shadowmapping or lightmapping against your code, though i'm still figuring out how that will get implemented.

right at the moment, my gut feel is to use a fragment shader using the light source.

anyway, let me know if there are ways of doing this, i'm sure blender 2.45 can do shadowmapping as well.

see http://blenderartists.org/forum/showthread.php?t=17440

Image
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

here's a sample lightmap i played with using blender 2.45

Image

see: http://www.blender.org/development/rele ... er-baking/
Frosty Topaz
Posts: 107
Joined: Sat Nov 04, 2006 9:42 pm

Post by Frosty Topaz »

Well a simple lightmap would be easy. Just calculate normals, choose a direction/colour for the light and a quick dot product tells you all you need to know.

Doing a proper shadowmap would be more complicated. I'd probly use ray tracing. You could even do it in Irrlicht without too much difficulty. But it may be slow enough that generating it first and then saving it would be the only reasonable way of using it.

Do note though that with my implementation the texture is very coarse. You only get 1 pixel per "tile" of terrain. But that should be pretty easy to overcome.

If you're interested give it a try. And if you need a hand let me know.
Frosty Topaz
=========
It isn't easy being ice-encrusted aluminum silicate fluoride hydroxide...
Diggsey
Posts: 3
Joined: Tue Jul 31, 2007 10:43 am
Location: Here
Contact:

Post by Diggsey »

For terrains, there is an extremely fast way to do shadow mapping, but it only works if the light rays are travelling parallel to one edge of the terrain. Basically, for each row, you set the current y position the first height value of the first vertex. Then, you go through all the vertices in that row. Every time you advance a vertex, you decrease the current height by the gradient of the light rays. If the new vertex is higher than the current y position, you set the current y position to the new height. If the height of a vertex is below the current height, it is in shadow.
wowdevver24
Posts: 26
Joined: Mon Jan 04, 2010 8:02 pm

Post by wowdevver24 »

very cool indeed, I will be modifying this once I get my uvmapped texture to work on terrain and I'll post the source here if you don't mind, basically once the multi-texturing works with uv mapping I can use the gradient of the map, play with the levels and create a uvmap from the heightmap to have textures for different terrain levels with obviously rock as the base non uv-mapped default +rep for a cool example
Remember all information is contextual and if I do not understand the context I cannot gras the information
Post Reply