Skip to content

Instantly share code, notes, and snippets.

@koenbollen
Created April 26, 2011 12:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koenbollen/942155 to your computer and use it in GitHub Desktop.
Save koenbollen/942155 to your computer and use it in GitHub Desktop.
A multitextured heightmap.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace KXNA.Environment
{
public class Terrain : DrawableGameComponent
{
public struct VertexMultitextured
{
public Vector3 Position;
public Vector3 Normal;
public Vector4 TextureCoordinate;
public Vector4 TexWeights;
public static int SizeInBytes = (3 + 3 + 4 + 4) * sizeof(float);
public static VertexElement[]
VertexElements = new[]
{
new VertexElement(0, VertexElementFormat.Vector3,
VertexElementUsage.Position, 0),
new VertexElement(sizeof (float)*3,
VertexElementFormat.Vector3,
VertexElementUsage.TextureCoordinate, 0),
new VertexElement(sizeof (float)*6,
VertexElementFormat.Vector4,
VertexElementUsage.TextureCoordinate, 1),
new VertexElement(sizeof (float)*10,
VertexElementFormat.Vector4,
VertexElementUsage.TextureCoordinate, 2),
};
}
public int Width { get; private set; }
public int Height { get; private set; }
private Effect effect;
private Texture2D bitmap;
private Texture2D[] textures;
private string asset;
private string[] textureAssets;
private float[,] data;
private int minheight;
private int maxheight;
private VertexMultitextured[] vertices;
private int[] indices;
private VertexDeclaration vertexDeclaration;
private Matrix world;
public Terrain(Game game, string heightmap, string[] textures )
: base(game)
{
if (textures.Length < 4)
throw new ArgumentException("Need four terrain textures.");
this.asset = heightmap;
this.textureAssets = textures;
}
protected override void LoadContent()
{
this.effect = Game.Content.Load<Effect>("FX/terrain");
this.bitmap = Game.Content.Load<Texture2D>(this.asset);
this.textures = new Texture2D[4];
for (int i = 0; i < 4; i++)
textures[i] = Game.Content.Load<Texture2D>(this.textureAssets[i]);
this.LoadHeightData(); // Width & Height are available from this point.
this.SetUpVertices();
this.SetUpIndices();
this.InitializeNormals();
world = Matrix.CreateTranslation(-Width / 2.0f, 0, Height / 2.0f);
KXNA.Console.WriteLine("Terrain: heightmap '" + this.asset + "' loaded.");
base.LoadContent();
}
private void LoadHeightData()
{
Width = bitmap.Width;
Height = bitmap.Height;
Color[] pixels = new Color[Width * Height];
bitmap.GetData(pixels);
data = new float[Width, Height];
minheight = int.MaxValue;
maxheight = int.MinValue;
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
data[x, y] = pixels[x + y * Width].R / 5.0f; // using red channel only.
minheight = (int)Math.Min(data[x, y], minheight);
maxheight = (int)Math.Max(data[x, y], maxheight);
}
}
}
private void SetUpVertices()
{
if (data == null)
throw new InvalidOperationException("Call LoadHeightData() first!");
float step = (maxheight - minheight) / 3;
vertices = new VertexMultitextured[Width * Height];
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
vertices[x + y * Width].Position = new Vector3(x, data[x, y], -y);
vertices[x + y * Width].TextureCoordinate.X = x;
vertices[x + y * Width].TextureCoordinate.Y = y;
vertices[x + y * Width].TexWeights = Vector4.Zero;
vertices[x + y * Width].TexWeights.X =
MathHelper.Clamp(1.0f - Math.Abs(data[x, y]) / step, 0, 1);
vertices[x + y * Width].TexWeights.Y =
MathHelper.Clamp(1.0f - Math.Abs(data[x, y] - step) / step, 0, 1);
vertices[x + y * Width].TexWeights.Z =
MathHelper.Clamp(1.0f - Math.Abs(data[x, y] - 2 * step) / step, 0, 1);
vertices[x + y * Width].TexWeights.W =
MathHelper.Clamp(1.0f - Math.Abs(data[x, y] - 3 * step) / step, 0, 1);
float total = vertices[x + y * Width].TexWeights.X;
total += vertices[x + y * Width].TexWeights.Y;
total += vertices[x + y * Width].TexWeights.Z;
total += vertices[x + y * Width].TexWeights.W;
vertices[x + y * Width].TexWeights.X /= total;
vertices[x + y * Width].TexWeights.Y /= total;
vertices[x + y * Width].TexWeights.Z /= total;
vertices[x + y * Width].TexWeights.W /= total;
}
}
vertexDeclaration = new VertexDeclaration(VertexMultitextured.VertexElements);
}
private void SetUpIndices()
{
if (vertices == null)
throw new InvalidOperationException("Call SetUpVertices() first!");
indices = new int[(Width - 1) * (Height - 1) * 6];
int counter = 0;
for (int y = 0; y < Height - 1; y++)
{
for (int x = 0; x < Width - 1; x++)
{
int lowerLeft = x + y * Width;
int lowerRight = (x + 1) + y * Width;
int topLeft = x + (y + 1) * Width;
int topRight = (x + 1) + (y + 1) * Width;
indices[counter++] = topLeft;
indices[counter++] = lowerRight;
indices[counter++] = lowerLeft;
indices[counter++] = topLeft;
indices[counter++] = topRight;
indices[counter++] = lowerRight;
}
}
}
private void InitializeNormals()
{
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal = Vector3.Zero;
for (int i = 0; i < indices.Length / 3; i++)
{
int index0 = indices[i * 3];
int index1 = indices[i * 3 + 1];
int index2 = indices[i * 3 + 2];
Vector3 side0 = vertices[index0].Position - vertices[index2].Position;
Vector3 side1 = vertices[index0].Position - vertices[index1].Position;
Vector3 normal = Vector3.Cross(side0, side1);
vertices[index0].Normal += normal;
vertices[index1].Normal += normal;
vertices[index2].Normal += normal;
}
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal.Normalize();
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
effect.CurrentTechnique = effect.Techniques["Multitextured"];
effect.Parameters["World"].SetValue(this.world);
// Set camera (KXNA is the game class):
effect.Parameters["View"].SetValue(KXNA.Camera.View);
effect.Parameters["Projection"].SetValue(KXNA.Camera.Projection);
effect.Parameters["LightDirection"].SetValue(new Vector3(-0.5f, -1, -0.5f));
effect.Parameters["Ambient"].SetValue(0.4f);
for (int i = 0; i < 4; i++)
effect.Parameters["Texture" + i].SetValue(this.textures[i]);
//*/
Game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
//*/
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
Game.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3, this.vertexDeclaration);
}
base.Draw(gameTime);
}
}
}
float4x4 World;
float4x4 View;
float4x4 Projection;
float3 LightDirection;
float Ambient;
Texture Texture0;
sampler TextureSampler0 = sampler_state {
texture = <Texture0> ;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};
Texture Texture1;
sampler TextureSampler1 = sampler_state {
texture = <Texture1> ;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = wrap;
AddressV = wrap;
};
Texture Texture2;
sampler TextureSampler2 = sampler_state {
texture = <Texture2> ;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};
Texture Texture3;
sampler TextureSampler3 = sampler_state {
texture = <Texture3> ;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = wrap;
AddressV = wrap;
};
struct VertexShaderInput
{
float4 Position : POSITION0;
float3 Normal : TEXCOORD0;
float2 TexCoords : TEXCOORD1;
float4 TexWeights : TEXCOORD2;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float3 Normal : TEXCOORD0;
float2 TexCoords : TEXCOORD1;
float4 TexWeights : TEXCOORD2;
float4 Light : TEXCOORD3;
};
VertexShaderOutput MultitexturedVertexShader(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.Normal = input.Normal;
output.TexCoords = input.TexCoords;
output.TexWeights = input.TexWeights;
output.Light.xyz = -LightDirection;
output.Light.w = 1;
return output;
}
float4 MultitexturedPixelShader(VertexShaderOutput input) : COLOR0
{
float4 output;
float lighting = saturate(saturate(dot(input.Normal, input.Light)) + Ambient);
output = tex2D(TextureSampler0, input.TexCoords)*input.TexWeights.x;
output += tex2D(TextureSampler1, input.TexCoords)*input.TexWeights.y;
output += tex2D(TextureSampler2, input.TexCoords)*input.TexWeights.z;
output += tex2D(TextureSampler3, input.TexCoords)*input.TexWeights.w;
output.rgb *= lighting;
return output;
}
technique Multitextured
{
pass Pass1
{
VertexShader = compile vs_2_0 MultitexturedVertexShader();
PixelShader = compile ps_2_0 MultitexturedPixelShader();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment