In this episode of the Game Engine Tutorial (we’ll return to the editor next week) we are going to build a better terrain, but the emphasis is more on implementing some simple HLSL effects.
We will cover:
- Better terrain construction
- Texture mapping
- Multiple texture mapping
- Directional lighting
- Fog
Better Terrain Construction
Our first shot at terrain was pretty good, but we are going to improve it here. The results will be completely unnoticable in the final image, but the functions for creating the terrain will be much better and more understandable. First, we need a new terrain class. I called mine Terrain2, but you can come up with something more creative. We will have it inherit from Component and I3DComponent, and add the standard values I3DComponent expects. They are of course filler values, because the terrain can’t rotate or scale or move to keep physics consistent.
public class Terrain2 : Component, I3DComponent
{
// Setting [Browsable(false)] will make the property not show up in a properties box (like the one in the editor)
[Browsable(false)]
public Vector3 Position { get; set; }
[Browsable(false)]
public Vector3 EulerRotation { get; set; }
[Browsable(false)]
public Matrix Rotation { get; set; }
[Browsable(false)]
public Vector3 Scale { get; set; }
[Browsable(false)]
public BoundingBox BoundingBox { get; set; }
}
Next we will add the values that help shape the terrain. We store the height values of the terrain, a float to scale them by.
float[,] heights; float heightMod = 1; VertexBuffer vertexBuffer; IndexBuffer indexBuffer; VertexDeclaration vertexDeclaration;
The last thing we need to store is our physics HeightMapObject, which acts as the interface between the raw height value and a physics simulation of the terrain based on those heights.
HeightMapObject physicsHeightmap;
Before I go any further, I want to point out that the functions for generating the vertices, indices, and vertex normals are based on those given in the book XNA 3.0 Game Programming Recipes, by Riemer Grootjans (http://www.riemers.net).
The function below builds the terrain with a two dimesional array (X, Z) of heights (Y values). It also takes a scaling float that it multiplies by all the heights to resize the terrain on the Y axis.
void PopulateBuffers(float[,] heights, float heightMod)
{
this.heights = heights;
if (heights == null)
return;
// Offset the heights entered by the height scaling value
float[,] heightsModded = new float[heights.GetLength(0), heights.GetLength(1)];
for (int z = 0; z < heights.GetLength(1); z++)
for (int x = 0; x < heights.GetLength(0); x++)
heightsModded[x, z] = heights[x, z] * heightMod;
// Generate the vertices and indices
VertexPositionNormalTexture[] vertices = BuildVertices(heightsModded);
int[] indices = BuildIndices(heightsModded);
// Generate normals for the index buffer
vertices = GenerateNormalsForTriangleStrip(vertices, indices);
// Create the vertex declaration
vertexDeclaration = new VertexDeclaration(Engine.GraphicsDevice, VertexPositionNormalTexture.VertexElements);
// Create the vertex buffer with the generated vertices
vertexBuffer = new VertexBuffer(Engine.GraphicsDevice, VertexPositionNormalTexture.SizeInBytes * vertices.Length, BufferUsage.WriteOnly);
vertexBuffer.SetData(vertices);
// Create the index buffer with the generated indices
indexBuffer = new IndexBuffer(Engine.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);
indexBuffer.SetData(indices);
float highest = 0;
// Find the highest vertex
for (int x = 0; x < heights.GetLength(0); x++)
for (int z = 0; z < heights.GetLength(1); z++)
if (heightsModded[x, z] > highest)
highest = heightsModded[x, z];
// Rebuild the BoundingBox centered around (0, 0, 0), with ends at 1/2 the width and depth of the terrain, and as high as the highest (scaled) vertex
BoundingBox = new BoundingBox(new Vector3(-heights.GetLength(0) / 2, 0, heights.GetLength(1) / 2), new Vector3(heights.GetLength(0) / 2, highest, -heights.GetLength(1) / 2));
// Rebuilt the physics heightmap simulation object
CreatePhysicsHeightmap(heightsModded);
}
The function above relies on two functions to build the vertices and indices. If you want more information on these functions, Riemer’s website has much more information:
// Builds the vertices for the terrain based on the given heights
VertexPositionNormalTexture[] BuildVertices(float[,] heights)
{
// Find the width and depth of the terrain by getting the lengths of the heights array
int width = heights.GetLength(0);
int depth = heights.GetLength(1);
// Create the list of vertices
VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[width * depth];
int i = 0;
// Move from front to back, row by row from the left to the right, creating the vertices and UV values
for (int z = 0; z < depth; z++)
for (int x = 0; x < width; x++)
{
Vector3 position = new Vector3(x - width / 2, heights[x, z], -z + depth / 2);
// Temporary normal will be recalculated later
Vector3 normal = new Vector3(0, 0, 0);
// Since we want a square texture to completely fill the terrain,
Vector2 uv = new Vector2((float)x / (float)width, (float)z / (float)depth);
vertices[i++] = new VertexPositionNormalTexture(position, normal, uv);
}
return vertices;
}
// Generates the indices for the heights given
int[] BuildIndices(float[,] heights)
{
int width = heights.GetLength(0);
int depth = heights.GetLength(1);
int[] indices = new int[width * 2 * (depth - 1)];
int i = 0;
int z = 0;
while (z < depth - 1)
{
for (int x = 0; x < width; x++)
{
indices[i++] = x + z * width;
indices[i++] = x + (z + 1) * width;
}
z++;
if (z < depth - 1)
{
for (int x = width - 1; x >= 0; x--)
{
indices[i++] = x + (z + 1) * width;
indices[i++] = x + z * width;
}
}
z++;
}
return indices;
}
// Calculates normals for the given list of vertices and indices
VertexPositionNormalTexture[] GenerateNormalsForTriangleStrip(VertexPositionNormalTexture[] vertices, int[] indices)
{
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal = new Vector3(0, 0, 0);
bool swappedWinding = false;
for (int i = 2; i < indices.Length; i++)
{
Vector3 firstVec = vertices[indices[i - 1]].Position - vertices[indices[i]].Position;
Vector3 secondVec = vertices[indices[i - 2]].Position - vertices[indices[i]].Position;
Vector3 normal = Vector3.Cross(firstVec, secondVec);
normal.Normalize();
if (swappedWinding)
normal *= -1;
if (!float.IsNaN(normal.X))
{
vertices[indices[i]].Normal += normal;
vertices[indices[i - 1]].Normal += normal;
vertices[indices[i - 2]].Normal += normal;
}
swappedWinding = !swappedWinding;
}
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal.Normalize();
return vertices;
}
The function we used to build the terrain also relies on another function to create the physics heightmap object.
// Rebuilds the HeightMapObject from the given heights
void CreatePhysicsHeightmap(float[,] heights)
{
// If the physics object has been created already with previous settings...
if (physicsHeightmap != null)
{
// And there is a physics simulator available
Physics p = Engine.Services.GetService
();
if (p != null)
{
// Disable the old height map object and remove it from the engine
physicsHeightmap.Body.DisableBody();
p.PhysicsSystem.RemoveBody(PhysicsHeightmap.Body);
physicsHeightmap.DisableComponent();
}
}
// Create the new HeightMapObject
physicsHeightmap = new HeightMapObject(new JigLibX.Geometry.HeightMapInfo(heights, 1), new Vector2(-1.0f, -1.0f), Engine);
}
OK, so far so good. Now we can build a terrain from a list of height values. We could give it values to work with by hand, but that would take forever, so we will add a way to build the terrain from a texture. The texture should be a gray scale map, and we will take the values, which will range from 0 – 1, and create an array of heights from all the pixels in the image.
// Accepts a texture and extracts the height values from it to create an array of heights
void BuildTerrainFromTexture(Texture2D Texture, float heightMod)
{
Texture2D t = Texture;
// Extract the pixel values into an array of Colors
Color[] colors = new Color[t.Width * t.Height];
t.GetData(colors);
// Create a two dimensional array of heights
float[,] heights = new float[t.Width, t.Height];
// Move from left to right,
for (int x = 0; x < t.Width; x++)
for (int z = 0; z < t.Height; z++)
heights[x, z] = colors[x + z * t.Width].R / 5f;
PopulateBuffers(heights, heightMod);
}
Let’s add a couple of public properties so that we can set these values from a PropertyGrid (in the editor).
string heightmap;
public string Heightmap
{
get { return heightmap;}
set { heightmap = value; BuildTerrainFromTexture(Engine.Content.Load(value), heightMod); }
}
public float HeightMod
{
get { return heightMod; }
set { heightMod = value; if (heights != null) PopulateBuffers(heights, value); }
}
Now let’s work on our Draw() method. Drawing the terrain is pretty simple, but we need an effect. For now we’ll just use BasicEffect.
Effect effect; // In the constructor effect = new BasicEffect(Engine.GraphicsDevice, null);
public override void Draw()
{
Camera c = Engine.Services.GetService();
if (c == null || heights == null || vertexBuffer == null || indexBuffer == null || vertexDeclaration == null)
return;
int width = heights.GetLength(0);
int depth = heights.GetLength(1);
// We need to flip the terrain across the z axis to make it fit the physics heightmap
effect.Parameters["World"].SetValue(Matrix.CreateScale(1, 1, -1));
effect.Parameters["View"].SetValue(c.View);
effect.Parameters["Projection"].SetValue(c.Projection);
((BasicEffect)effect).EnableDefaultLighting();
// The normals will be inverted by the z-flip so we need to switch the cullmode around to draw properly
Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
// Set the vertex source, index source, and VertexDeclaration
Engine.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
Engine.GraphicsDevice.Indices = indexBuffer;
Engine.GraphicsDevice.VertexDeclaration = vertexDeclaration;
// Draw a TriangleStrip using the vertices and indices we set
Engine.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, width * depth, 0, width * 2 * (depth - 1) - 2);
pass.End();
}
effect.End();
// Set the cullmode back
Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
}
Run it, and you should see a nice… silver shiny terrain.

Let’s make our own effect to replace BasicEffect. Add a new effect to your content project. (Add > New Item > Effect). You will be taken to the HLSL editor. Leave it the way it is for now and go back to your terrain. Change the line where we load the effect to:
effect = Engine.Content.Load("Content/TerrainEffect"); // Or whatever your effect is called
Next remove the following from the Draw() method:
((BasicEffect)effect).EnableDefaultLighting();
If you run it again, you’ll see that your effect has been applied. Of course, we haven’t done anything with it yet, so you’ll just see a big red blob.

Intro to HLSL
Before we can do more with the shader we need to understand a few things about HLSL. HLSL is the programming language for GPUs. There are other languages (like GLSL in OpenGL), but DirectX, and thus XNA, uses HLSL. HLSL stands for “High Level Shader Language”. The .fx file is a “Shader”, because it is responsible for taking in values form the program and GPU and deciding what color to output (shade) each pixel on the screen.
We’ll analyze each part of an HLSL file before we use it. First, have a look at the first chunk of code. These are your effect parameters. They are basically chunks of memory that sit on the graphics card, which it can access. However, the CPU can also access these parameters, which means we can share data between the two. (Although it is usually one way, because it is usually very, VERY, slow to read from the GPU. You should avoid this at all costs.)
In HLSL, a float4x4 is equivelent to XNA’s Matrix. float2, float3, and float4 are Vector2, Vector3, and Vector4. float, int, and bool are the same. texture is a Texture2D object.
float4x4 World; float4x4 View; float4x4 Projection;
We have already shown how to set these parameters from XNA in our draw method. Now you can see what is actually happening when we set parameters on the Effect like in the draw method:
effect.Parameters["World"].SetValue(Matrix.CreateScale(1, 1, -1)); effect.Parameters["View"].SetValue(c.View); effect.Parameters["Projection"].SetValue(c.Projection);
Next in the HLSL are some struct definitions:
struct VertexShaderInput
{
float4 Position : POSITION0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
};
These structs are used to send data between the GPU and the shader’s functions. You can have more than two, and use them for different things (for example you could have several versions of a shader, but if we look ahead you can see that the first function takes in a VertexShaderInput struct, and outputs a VertexShaderOutput struct, and the second functions accepts a VertexShaderOutput struct. values in an HLSL struct like these need to have a “Shader Semantic” assigned to them. This is done by adding a “: SemanticName” after the name of the parameter. For example, the Position vector is assigned to POSITION0, which can also be just POSITION. There are a lot of different semantics, but we will just use a few of them. I should note that the TEXCOORD semantic, which stores the UV layout of the input vertex, has several “layers”, ie: TEXCOORD0, TEXCOORD1, TEXCOORD4. TEXCOORD0 usually holds the UV information, but the rest are available to store other data, like light direction, normal, or whatever. There are a llimited number available, and that number changes from shader version to shader version, but there are usually enough to get most effects done. I’ll talk more about shader versions at the end of this section.
The next section of code is the “Vertex Shader”. This is a function that is primarily responsible for filling out the POSITION semantic, but a lot of other work can be done there too. For example, texture coordinate mapping, vertex coloring and lighting is handled here. The struct that is passed in to it will be filled out by the GPU, passing the information the vertex shader needs from the data the GPU is tracking.
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
return output;
}
In the example above, the shader is taking in the position of the vertex in “World Space”, offsetting it by the World matrix, then transforming it based on the View and Projection matrices. The View matrix basically moves the vertex based on the camera transformation, and the projection matrix “warps” the display so that it fits the screen, aspect ration, etc.
The second function is the “Pixel Shader”. It works on a pixel by pixel basis, taking in information from the vertex shader, such as position, texture coordinates, etc. and deciding what color the output pixel should be. In this example, it is just outputting the color red (255, 0, 0, 255) (Colors are shrunk from 0-255 to 0-1 in HLSL. The fourth parameter is transparency).
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
return float4(1, 0, 0, 1);
}
The last part of a shader is the definition of techniques. A technique is basically a list of “passes”, which define a vertex shader and pixel shader inside each of them. Normally there is only one pass, but for advanced shading techniques more than one can be used. The vertex and pixel shader definitions inside the technique define a pixel shader version and vertex shader version. These are used when compiling the shader (which XNA does automatically in the content build before running the game). By default they are vertex shader 1.0 (vs_1_1) and pixel shader 1.0 (ps_1_1). Most cards support pixel shader 2.0 and most cards also support 3.0. The difference is mainly in the number of available instructions and semantics. The instruction count is the number of things that can be done in a shader. Available semantics change from version to version, but the basic ones are available in all the versions XNA can use. You can define a number of different techniques, and use them based on available hardware capabilities for example.
HLSL Texture Mapping
Now that you have an understanding of HLSL, lets do something with it by adding a texture to the terrain. Add the following properties:
texture TextureMap;
sampler2D TextureMapSampler = sampler_state { texture = ; AddressU = CLAMP; AddressV = CLAMP; MinFilter = LINEAR; MipFilter = LINEAR; MagFilter = LINEAR; };
The first property is simple the Texture2D we want to use, but the second is a texture “sampler” this is used to extract the correct part of the texture when applying it to a pixel in the pixel shader. We can set a number of properties, like texture filtering, which handles the resizing of textures based on distince. “Linear” means it will do a straight resize. “Linear” is the lowest quality, but it still looks fine. The highest is “Anisotropic”, which takes the most processing power, but looks the best. The wrap mode (“AddressU”, “AddressV”), which tells the sampler what to do when the UV coordinate of the texture is great than 1 or less than 0. “Clamp” means that it will continue to use the texture at UV (1), or the edge of the texture. If you specified “Wrap” instead of “Clamp”, the texture would wrap back around and repeat. We also specify the texture that the sampler is sampling from.
We need to add the texture property to the C# code as well, and set it in the draw function.
string textureMap;
Texture2D texMap;
public string TextureMap
{
get { return textureMap; }
set { textureMap = value; texMap = Engine.Content.Load(value); }
}
if (texMap != null)
effect.Parameters["TextureMap"].SetValue(texMap);
Now we need to modify the shader’s vertex shader input/output structs, the vertex shader, and the pixel shader to draw the texture onto the terrain. We also need to move to Pixel Shader 2.0:
float4x4 World;
float4x4 View;
float4x4 Projection;
texture TextureMap;
sampler2D TextureMapSampler = sampler_state { texture = ; AddressU = CLAMP; AddressV = CLAMP; MinFilter = LINEAR; MipFilter = LINEAR; MagFilter = LINEAR; };
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.UV = input.UV;
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float4 tex = tex2D(TextureMapSampler, input.UV);
return tex;
}
technique Technique1
{
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_1_1 PixelShaderFunction();
}
}
Now, set the TextureMap property on the terrain to your image, run it, and you should see it spread over the terrain.



Well, it’s a start but it looks pretty pixelated and blurry. Let’s make it repeat over the terrain instead of being stretched across it. We will use a simple float value and multiply the UVs by it. To better understand this, look at the diagram below to see how changing the UV values will change the way the texture is layed across it.
C#:
float uvScaling = 30.0f; // Repeat 30 times across the terrain
public float UVScaling
{
get { return uvScaling; }
set { uvScaling = value; }
}
// Draw method
effect.Parameters["UVScaling"].SetValue(uvScaling);
HLSL
float UVScaling = 30.0f; // Pixel shader float4 tex = tex2D(TextureMapSampler, input.UV * UVScaling);
Now if you run this you will see that the texture is tiled across the terrain.

Multiple Texture Mapping
This looks OK, but let’s extend it a little bit. We will change this so that we can have four textures on the terrain, laid out by a fifth’s Red, Green, Blue, and Alpha channels. The texture map will be stretched across the terrain, and the others will tile.
C#
float4 red = tex2D(RedSampler, input.UV * UVScaling);
float4 green = tex2D(GreenSampler, input.UV * UVScaling);
float4 blue = tex2D(BlueSampler, input.UV * UVScaling);
float4 alpha = tex2D(AlphaSampler, input.UV * UVScaling);
public string RedTexture
{
get { return redTexture; }
set { redTexture = value; if (string.IsNullOrEmpty(value)) rTex = null; else rTex = Engine.Content.Load(value); }
}
public string GreenTexture
{
get { return greenTexture; }
set { greenTexture = value; if (string.IsNullOrEmpty(value)) gTex = null; else gTex = Engine.Content.Load(value); }
}
public string BlueTexture
{
get { return blueTexture; }
set { blueTexture = value; if (string.IsNullOrEmpty(value)) bTex = null; else bTex = Engine.Content.Load(value); }
}
public string AlphaTexture
{
get { return alphaTexture; }
set { alphaTexture = value; if (string.IsNullOrEmpty(value)) aTex = null; else aTex = Engine.Content.Load(value); }
}
// Draw method
if (rTex != null)
effect.Parameters["RedTexture"].SetValue(rTex);
if (gTex != null)
effect.Parameters["GreenTexture"].SetValue(gTex);
if (bTex != null)
effect.Parameters["BlueTexture"].SetValue(bTex);
if (aTex != null)
effect.Parameters["AlphaTexture"].SetValue(aTex);
C#
texture RedTexture;
sampler2D RedSampler = sampler_state { texture = ; AddressU = WRAP; AddressV = WRAP; MinFilter = LINEAR; MipFilter = LINEAR; MagFilter = LINEAR; };
texture GreenTexture;
sampler2D GreenSampler = sampler_state { texture = ; AddressU = WRAP; AddressV = WRAP; MinFilter = LINEAR; MipFilter = LINEAR; MagFilter = LINEAR; };
texture BlueTexture;
sampler2D BlueSampler = sampler_state { texture = ; AddressU = WRAP; AddressV = WRAP; MinFilter = LINEAR; MipFilter = LINEAR; MagFilter = LINEAR; };
texture AlphaTexture;
sampler2D AlphaSampler = sampler_state { texture = ; AddressU = WRAP; AddressV = WRAP; MinFilter = LINEAR; MipFilter = LINEAR; MagFilter = LINEAR; };
// Pixel shader
float4 red = tex2D(RedSampler, input.UV * UVScaling);
float4 green = tex2D(GreenSampler, input.UV * UVScaling);
float4 blue = tex2D(BlueSampler, input.UV * UVScaling);
float4 alpha = tex2D(AlphaSampler, input.UV * UVScaling);
float4 map = tex2D(TextureMapSampler, input.UV);
float a = map.a;
float4 final = (map.r * a * red) + (map.g * a * green) + (map.b * a * blue) + ((1.0f - a) * alpha);return final;
Set those properties on the terrain, run it, and you will see the textures are spread across the terrain.
![]() |
![]() |
![]() |
![]() |
![]() |

Directional Lighting
Our textures look nice, but everything looks really flat. Now we are going to add some simple ambient lighting, and directional lighting. Ambient lighting is basically a lowish light value that is multiplied by the texture, to simulate areas that are not lit directly but still are not completely black because of the way light bounces around. To perform directional lighting, we multiply the texture by the dot product of the light direction and vertex normal. Add the following parameters:
Vector3 lightDirection = new Vector3(1, 1, 1);
Vector3 ambientColor = new Vector3(.2f, .2f, .2f);
Vector3 lightColor = new Vector3(1, 1, 1);
public Vector3 LightDirection
{
get { return lightDirection; }
set { lightDirection = value; }
}
public Vector3 AmbientColor
{
get { return ambientColor; }
set { ambientColor = value; }
}
public Vector3 LightColor
{
get { return lightColor; }
set { lightColor = value; }
}
// Draw method
effect.Parameters["LightDirection"].SetValue(LightDirection);
effect.Parameters["Ambient"].SetValue(AmbientColor);
effect.Parameters["LightColor"].SetValue(LightColor);
HLSL
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : NORMAL0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : TEXCOORD1;
};
// Add to vertex shader
output.Normal = mul(input.Normal, World);
// Add to pixel shader
float3 normal = normalize(input.Normal);
float3 lightDir = normalize(LightDirection);
float3 lightMod = dot(normal, lightDir);
lightMod.r = clamp(lightMod.r * LightColor.r, Ambient.r, 1);
lightMod.g = clamp(lightMod.g * LightColor.g, Ambient.g, 1);
lightMod.b = clamp(lightMod.b * LightColor.b, Ambient.b, 1);
final *= float4(lightMod, 1);
![]() |
![]() |

Fog
We are going to add one more effect to our shader. Fog can look very realistic, especially on terrain. It can also be useful for a multitude of other things- weather effects, light flares, etc. Don’t believe me? Just ask XNA Framework Developer Shawn Hargreaves (http://blogs.msdn.com/shawnhar). In his series on MotoGP, he describes 5 or 6 ways they used the fog system, other than just fog.
To calculate fog, just pick a start point, end point, and density. Then, in the pixel shader, you figure out how far away the pixel is from the camera, figure out how far into the fog it is, and multiply that by the fog density, then fog color.
C#
public float FogDensity { get; set; }
public float MaxFog { get; set; }
public float MinFog { get; set; }
public float FogStart { get; set; }
public float FogEnd { get; set; }
public Vector3 FogColor { get; set; }
// Draw function
if (Data.ContainsData("Terrain.FogStart"))
FogStart= Data.GetData("Terrain.FogStart");
if (Data.ContainsData("Terrain.FogEnd"))
FogEnd = Data.GetData("Terrain.FogEnd");
if (Data.ContainsData("Terrain.FogDensity"))
FogDensity = Data.GetData("Terrain.FogDensity");
if (Data.ContainsData("Terrain.FogColor"))
FogColor = Data.GetData("Terrain.FogColor");
if (Data.ContainsData("Terrain.MaxFog"))
MaxFog = Data.GetData("Terrain.MaxFog");
if (Data.ContainsData("Terrain.MinFog"))
MinFog = Data.GetData("Terrain.MinFog");
HLSL
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : TEXCOORD1;
float Depth : TEXCOORD2;
};
// Add to vertex shader
output.Depth = output.Position.z;
// Add to pixel shader
float depth = input.Depth;
float fog = (depth - FogStart) / (FogEnd - FogStart);
fog *= FogDensity;
fog = clamp(fog, MinFog, MaxFog);
final = lerp(final, float4(FogColor, 1), fog);

The last thing I’m going to throw in at the end here are the serialization and deserialization functions for this terrain:
public override void SaveSerializationData(SerializationData Data)
{
base.SaveSerializationData(Data);
if (AlphaTexture != null)
Data.AddData("Terrain.AlphaTexture", AlphaTexture);
if (RedTexture != null)
Data.AddData("Terrain.RedTexture", RedTexture);
if (GreenTexture != null)
Data.AddData("Terrain.GreenTexture", GreenTexture);
if (BlueTexture != null)
Data.AddData("Terrain.BlueTexture", BlueTexture);
if (TextureMap != null)
Data.AddData("Terrain.TextureMap", TextureMap);
if (Heightmap != null)
Data.AddData("Terrain.Heightmap", Heightmap);
Data.AddData("Terrain.UVScaling", uvScaling);
Data.AddData("Terrain.LightDirection", LightDirection);
Data.AddData("Terrain.AmbientColor", AmbientColor);
Data.AddData("Terrain.LightColor", LightColor);
Data.AddData("Terrain.FogStart", FogStart);
Data.AddData("Terrain.FogEnd", FogEnd);
Data.AddData("Terrain.FogDensity", FogDensity);
Data.AddData("Terrain.FogColor", FogColor);
Data.AddData("Terrain.MinFog", MinFog);
Data.AddData("Terrain.MaxFog", MaxFog);
Data.AddData("Terrain.HeightMod", HeightMod);
}
public override void LoadFromSerializationData(SerializationData Data)
{
base.LoadFromSerializationData(Data);
if (Data.ContainsData("Terrain.HeightMod"))
HeightMod = Data.GetData("Terrain.HeightMod");
if (Data.ContainsData("Terrain.Heightmap"))
Heightmap = Data.GetData("Terrain.Heightmap");
if (Data.ContainsData("Terrain.AlphaTexture"))
AlphaTexture = Data.GetData("Terrain.AlphaTexture");
if (Data.ContainsData("Terrain.RedTexture"))
RedTexture = Data.GetData("Terrain.RedTexture");
if (Data.ContainsData("Terrain.GreenTexture"))
GreenTexture = Data.GetData("Terrain.GreenTexture");
if (Data.ContainsData("Terrain.BlueTexture"))
BlueTexture = Data.GetData("Terrain.BlueTexture");
if (Data.ContainsData("Terrain.TextureMap"))
TextureMap = Data.GetData("Terrain.TextureMap");
if (Data.ContainsData("Terrain.UVScaling"))
uvScaling = Data.GetData("Terrain.UVScaling");
if (Data.ContainsData("Terrain.LightDirection"))
LightDirection = Data.GetData("Terrain.LightDirection");
if (Data.ContainsData("Terrain.AmbientColor"))
AmbientColor = Data.GetData("Terrain.AmbientColor");
if (Data.ContainsData("Terrain.LightColor"))
LightColor = Data.GetData("Terrain.LightColor");
if (Data.ContainsData("Terrain.FogStart"))
FogStart= Data.GetData("Terrain.FogStart");
if (Data.ContainsData("Terrain.FogEnd"))
FogEnd = Data.GetData("Terrain.FogEnd");
if (Data.ContainsData("Terrain.FogDensity"))
FogDensity = Data.GetData("Terrain.FogDensity");
if (Data.ContainsData("Terrain.FogColor"))
FogColor = Data.GetData("Terrain.FogColor");
if (Data.ContainsData("Terrain.MaxFog"))
MaxFog = Data.GetData("Terrain.MaxFog");
if (Data.ContainsData("Terrain.MinFog"))
MinFog = Data.GetData("Terrain.MinFog");
}
« XNA Game Editor Tutorial #2 – Tool Box pt. 1 / 2 Dynamic Sky & Rain Effect by Jan Vytlačil »






This is the easiest to understand multitexturing tutorial I’ve yet seen. I can’t wait to try it out.
First off, great tutorial!
My question is: what is the GenerateNormalsForTriangleStrip method referring to? I can not run the game because of it and can not find the method anywhere.
Nevermind. I realized it is a method from the Terrain class. What exactly is taken from the Terrain class and what is not? Is there anyway you can post this new terrain class? Thanks.
No, everything is a new class, I just forgot to include GenerateNormalsForTriangleStrip. It’s been updated now.
Thanks Sean. Is there anything special to add to the constructor? Because when I try to use the new Terrain, I don’t have anything rendering.
You need to set at least the heightmap, a texture MAP, and at least one texture. To start off you can just set a solid red texture as the map, and a grass or something to the red slot, then add a heightmap and you will see something, like in the last two examples.
Hey Sean. Cheers for the awesome tutorials! Seems like wordpress has maybe nommed some more &tl; > bits? getting errors in CreatePhysicsHeightMap
fixed?
Engine.Serivces.GetService<Physics>();
and
Engine.DefaultScreen
as the last parameter for
physicsHeightmap = new HeightMapObject…
errors are gone, but not sure if i’ve guessed bits right
In the .fx file, in the line:
sampler2D TextureMapSampler = sampler_state { texture = ; AdressU = CLAMP; AddressV = CLAMP; MinFilter = LINEAR; MagFilter = LINEAR; }
what should “texture =” be set equal to?
Sorry. I must be getting annoying. Figured out what my error meant.
But now I can’t fix it. Sorry. It is referring to the same line, but the error says:
Error 2 Errors compiling C:\Users\David\Documents\Visual Studio 2008\C#\Projects\Illuzion\Illuzion\Content\AdvancedTerrain\AdvancedTerrain.fx:
C:\Users\David\Documents\Visual Studio 2008\C#\Projects\Illuzion\Illuzion\Content\AdvancedTerrain\AdvancedTerrain.fx(6): error X3000: syntax error: unexpected token ‘;’ C:\Users\David\Documents\Visual Studio 2008\C#\Projects\Illuzion\Illuzion\Content\AdvancedTerrain\AdvancedTerrain.fx 6 1 Illuzion
Any ideas. I triple checked what I typed and can’t figure it out.
sampler2D TextureMapSampler = sampler_state { texture = <TextureMap>;
Another victim of wordpress’ safe html parsing. the rest of the line is fine.
Hmm, I’m failing to get the terrain texturing atm. VS is throwing up no errors right now, but even though i have a Terrain2 declared (terr), and a HeightMap and TextureMap specified, it seems to ignore the texture… Just renders the terrain all black (PS 2 and 3) or all white (PS 1.1)
Changing the HLSL stuff to apply a colour rather than the texture doesn’t seem to work either…
yay, texturing works. in your multitexturing section sean, there are some bits of HLSL mixed in with C# code sections.
Think you accidentally copied some float4 declarations instead of some strings or other such that belong in the C#
Is there some way you could post your code somewhere, Jon? I have a feeling mine is completely messed up and just want a reference to make sure my class is set up correctly. If you want to email me, you can send it to 3dcg.drich@gmail.com. Thanks!
Sorry. It’s 3dgc.drich@gmail.com
Hi Dave, I’ve set up a blog (been meaning to for aaaages on xna) and have hosted my current source for the engine. It’s got some extra bits, but all the code for this tutorial is in and working, with the exception of fog, which i can’t quite get right. I’ve had to write my own Draw function for it, and obviously it’s not completely correct. either way: http://xactly.randomnation.co.uk for what i’m doing to the engine, and also some helpful XNA posts of my own, in the near future
Thanks so much Jon. It was such a huge help. I’m also going to watch for blog posts on your site, because I am also very interested in audio management.
Thanks again,
Dave
No probs, glad I could help. and yeah, bear with me while i get posts going, but i will certainly be covering at least some basics soon
Is it possible to update the “XNA Engine Tutorial link” to include Tutorial 12 as well?
Hi guys I fixed the fog and clarified where everything should go, hopefully helps someone.
C#
// add the Terrain properties and values
Vector3 fogColor = new Vector3(1, 1, 1);
float fogDensity = 0.2f;
float maxFog = 0.6f;
float minFog = 0.1f;
float fogStart = 30.1f;
float fogEnd = 80.2f;
public Vector3 FogColor {
get { return fogColor; }
set { fogColor = value; }
}
public float FogDensity {
get{return fogDensity;}
set { fogDensity = value; }
}
public float MaxFog {
get { return maxFog; }
set { maxFog = value; }
}
public float MinFog {
get { return minFog; }
set { minFog = value; }
}
public float FogStart {
get { return fogStart; }
set { fogStart = value; }
}
public float FogEnd {
get { return fogEnd; }
set { fogEnd = value; }
}
// Add In Terrain Draw method before the translation effect calls
effect.Parameters["FogDensity"].SetValue(FogDensity);
effect.Parameters["MaxFog"].SetValue(MaxFog);
effect.Parameters["MinFog"].SetValue(MinFog);
effect.Parameters["FogStart"].SetValue(FogStart);
effect.Parameters["FogEnd"].SetValue(FogEnd);
effect.Parameters["FogColor"].SetValue(FogColor);
HLSL
// HLSL add at top
float3 FogColor;
float FogDensity;
float MaxFog;
float MinFog;
float FogStart;
float FogEnd;
// Change vertext shader output function to
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : TEXCOORD1;
float Depth : TEXCOORD2;
};
// Add to VertexShaderFunction
output.Depth = output.Position.z;
// Add to PixelShaderFunction just before the return final
float depth = input.Depth;
float fog = (depth – FogStart) /(FogEnd – FogStart);
fog *= FogDensity;
fog = clamp(fog, MinFog, MaxFog);
final = lerp(final, float4(FogColor, 1), fog);
Cheers
Mal
Has anyone else had a problem with the blur being used with this terrain? I am seeing through the terrain from some angles, but not from others. Also, should the fog be applied only to the terrain, or is it supposed to affect everything in the environment?
Make sure your renderstates are set correctly.
The fog should only apply to the terrain, because we are only using that shader on the terrain. You would have to add the fog code to the rest of the shaders used in the scene to see it everywhere.
Just wanted to make sure about the fog. Any ideas about the blur, though? I have pictures here:
http://img10.imageshack.us/img10/7553/blur1.jpg
http://img14.imageshack.us/img14/2718/blur2c.jpg
Note that in the second one, you not only see through terrain at parts, but it also adds a black bar to the bottom of the screen.
Thanks Sean!
Do you know any good references for making a terrain system that can take color map with more than 4 colors.
That seems to be were the common knowledge ends in terms of anyone’s tutorials.
In fact, I am looking for something that can teach me how to add bumpmap and a colormap at least to a heightmap with shaders.
You could use some pretty similar code to do more than 4 textures, but you would have to draw the terrain multiple times. Basically:
- Keep a list of textures and texture maps.
- Break the list into sets of 4 textures with their corresponding texture maps
- For each set of textures, draw the terrain using the textures and texture map
- While drawing, draw a completely transparent pixel when there is no texture mapped to it (black on the texture map)
- Make sure transarency is enabled when drawing, so that the different ‘layers’ of terrains blend together
My colormap is not a reference, sorry forgot to add that, my color is 2048 by 2048 I can also make it any other size, and is the entire texture file for the heightmap, so I need to wrap the colormap over the heightmap.
If anyone else has this problem, just add this to the draw method:
Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace; Already there.
Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
Good luck!
Hi! I´m getting the same error than Ilya Ostrovskiy at tutorial 9 (http://i44.tinypic.com/2expl4i.png) but only with this tutorial (I draw ok Terrain class, but I get the error whith Terrain2 class when UVScaling > 1). What could be wrong?
Thanks, and sorry about my english.
Don`t worry about my problem. I solve it whith:
Engine.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
Engine.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
after:
pass.Begin();
As far as I can make out FogStart and FogEnd do not correlate to distances as you think of them mathematically in XNA, it’s more about separating similar distances from thoes who are closer and further away so the distances you see (especially when you have pixels that have “infinite” depth (the sky)) the values are less than linear. for the results above, you may want to try a FogStart slightly smaller than you think you want, and FogEnd like ten times what you think you want. From that you can adjust and get what you really want for your given setting,
Every time you make a new setting with fog you’ll have to readjust these figured I imagine. I suppose a good idea would be to make a skybox component that would limit this odd disparity with the depth buffer, make it a bit more linear.
”
Sean Says:
June 14, 2009 at 4:56 pm
You could use some pretty similar code to do more than 4 textures, but you would have to draw the terrain multiple times. Basically:
”
Would you be able to create a short tutorial on this?
I would love to be able to use more than 4 textures, and have them blending nicely, however I cannot get my head around it all!
Any chance also of a code download for this chapter, I am doing many things wrong and can’t seam to get it to work lol!
For some reason my terrain is rendering the texturemap tiled. By this I mean, rather than having sand in one corner, with grass painted over the rest, it is taking that and repeating it all over the terrain.
ScreenShot Of Terrain:
http://euphoricnation.com/images/terrain_texturemap_odd.jpg
There is no errors/bugs and so I cannot seam to find the solution, I’ve had a tinker with the .fx file but have had no luck.
Does anyone know why my terrain2 component is rendering like this?
ps: I have also added the following lines after ‘pass.Begin()’…
Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
Engine.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
Engine.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
..However these have made no difference!
Any ideas?
Thanks in advance!
Its okay now, I have no idea what I did different this time but I recoded the terrain2 class from scratch. I must have either missed out something, or pershaps put something in the wrong order.
Either way it is working now
Thought that Chris may like to see this following screen shot since he was on about multi-texturing more that just 4 textures.
I finally have cracked the terrain problems I was having and now just finished a layering function for the textures on the terrain.
http://www.euphoricnation.com/images/terrain_layered.jpg
Hey, I used to follow X-Engine a while back (maybe 1.5 years). So, I decided to check back on it after that long. Then, I notice that you’ve got this totally new thing going and it looks really awesome. Therefore, I have to say that your decisions on what to do with X-Engine have gone a good way. Good job, and it looks great!
Is it possible to download this example? i really need help in my project.
Although this is a very late reply to Eric and that the comments have be quite for a long time but…
ERIC: If you are on about a example project for my terrain with multiple textures, well I don’t have any of the old code anymore; I’ve moved between computers/laptops alot recently.
However, I am currently working on a 3D terrain with LOD (quadtrees), and multi textures, Basically same display as the above, however the performance is alot greaters with such a lot less to render to the screen.
All good, I have no idea how long it will take, ideally I wanted to make it for engine 2.0 on this website. However the tutorials are taking alot longer to come out than I first expected. I will probably make a simple project without the engine for now, and adapt it for Engine 2.0 later.
Dylan Dalrymple: Thank you very much for you kind comments. Thanks!
I am very glad to see that you are putting so much of effort for encouraging the readers , Ohhh what do you all think about 3d tv?