Introduction
Terrain rendering is a very common technique used in 3D and 2D games. There are many approaches from simple vertex coloring to stitching together images, etc.
A realistic looking effect can be achieved by using multiple textures blended in using an alpha factor. Also if you want to obtain high resolution terrain, you can either use high detail textures or simply add the detail afterwards.
This article shows how to create and combine a couple of textures used in 2D sprites in order to obtain a realistic ground effect. It also adds a last ground detail texture which gives the impression of high resolution textures. It’s actually an old technique used in 3D Game Rendering especially when processing height maps. This example only uses 2D images and generates 2D terrain. If you want more advanced techniques, you should check for Height Maps and multi-texturing.
Topics Covered by this Project
- XNA Sprite rendering
- Alpha Blending
- Terrain Rendering (2D)
Specifications
The application includes the following features:
- Manipulation of alpha gradient in Images
- First steps in creating realistic looking terrain
- Texture processing and manipulation in XNA
- Resources found in other XNA samples or downloaded directly from Google
Technical Information
- Uses XNA Framework for 2D rendering
- Code written in C#
Requirements
- Visual C# Express or Visual Studio
- XNA Game Studio
- XNA-compatible hardware
Using the Application
The application is just a demo, the only interaction with the user is scrolling around the map. Just hold down the left mouse button in the desired corner of the main screen, and the map will start scrolling.
Using the Code
All you need for this to work is a set of images (same size) and a reference grayscale bitmap (fitting the given images) used to generate the alpha factor and a detail texture (any size).
You start by creating the alpha masks from the reference bitmap:
Bitmap alpha1 = BuildAlphaMap(bmp, 0, 84);
Then you have the combine the texture image with its alpha map:
groundTextures[1] = FromFileWithAlpha(gd, "Terrain/grass.bmp", alpha1);
The BuildAlphaMap
truncates the result for colors under the minimum and over the maximum interval and creates a smooth alpha transition in-between.
private Bitmap BuildAlphaMap(Bitmap bmp, int min, int max) {
Bitmap tmp = new Bitmap(bmp.Width, bmp.Height);
for (int i = 0; i < bmp.Width; i++) {
for (int j = 0; j < bmp.Height; j++) {
byte col = 0;
if (bmp.GetPixel(i, j).B < min) {
} else if (bmp.GetPixel(i, j).B > max) {
col = 255;
} else {
float rez = (float)(bmp.GetPixel(i, j).B - min) / (max - min);
col = (byte)(rez * 255);
}
tmp.SetPixel(i, j, System.Drawing.Color.FromArgb(col, col, col));
}
}
return tmp;
}
The following code takes a reference bitmap and another image used as the base texture and creates the alpha component given certain parameters.
public static Texture2D FromFileWithAlpha
(GraphicsDevice gd, string FileName, Bitmap AlphaMap) {
try {
Bitmap source = (Bitmap)Image.FromFile(FileName);
Bitmap alpha = new Bitmap
(AlphaMap, new Size(source.Width, source.Height));
Texture2D tex = new Texture2D
(gd, source.Width, source.Height, 1,
TextureUsage.Linear, SurfaceFormat.Color);
ColorData[] cd = new ColorData[source.Width * source.Height];
for (int i = 0; i < source.Height; i++) {
for (int j = 0; j < source.Width; j++) {
cd[i * source.Width + j].A = alpha.GetPixel(j,i).R;
cd[i * source.Width + j].R = source.GetPixel(j, i).R;
cd[i * source.Width + j].G = source.GetPixel(j, i).G;
cd[i * source.Width + j].B = source.GetPixel(j, i).B;
}
}
tex.SetData(cd);
return tex;
} catch (Exception e) {
throw e;
}
}
Finally, all we need is to render all the sprites from the same coordinates, with the same size, then add the detail texture. Note that the first image was not given any alpha component, because there was no need for this.
public void Render(int x, int y) {
sdr.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend);
for (int i = 0; i < 4; i++) {
sdr.SpriteBatch.Draw(groundTextures[i],
new Microsoft.Xna.Framework.Rectangle(-x + 0, -y + 0, width, height),
Microsoft.Xna.Framework.Graphics.Color.White);
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
sdr.SpriteBatch.Draw(detailTexture,
new Microsoft.Xna.Framework.Vector2(i * 512 - (x % 512),
j * 512 - (y % 512)),
Microsoft.Xna.Framework.Graphics.Color.White);
}
}
sdr.SpriteBatch.End();
}