XNA Game Engine教程系列8:基本地形
这篇教程我们将利用高程图创建一个基本地形。这也是以后教程中创建一个更先进的地形的基础。这里没有涉及多贴图(multitexturing)和大气散射等高级技术。实现的构思是通过一个灰度图片的长和宽生成顶点,并通过这个图像的灰度值设置顶点的高度。地形上也有一个关于纹理和基本照明,我们使用BasicEffect绘制它。

首先,建立代码:
using System;
using JigLibX.Geometry;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Innovation
{
public class Terrain : Component, I3DComponent
{
// Height representation
public float[,] heightData;
// Physics height representation
HeightMapInfo heightMapInfo;
// Physics object
HeightMapObject heightMapObject;
// Terrain texture
Texture2D texture;
// Vertex and index buffers
VertexDeclaration myVertexDeclaration;
VertexBuffer terrainVertexBuffer;
IndexBuffer terrainIndexBuffer;
// Effect
BasicEffect basicEffect;
// I3DComponent values
Vector3 position = Vector3.Zero;
Matrix rotation = Matrix.Identity;
Vector3 scale = new Vector3(1, 1, -1);
BoundingBox boundingBox = new BoundingBox(new Vector3(-1), new Vector3(1));
public Vector3 Position { get { return position; } set { position = value; } }
public Vector3 EulerRotation
{
get { return MathUtil.MatrixToVector3(Rotation); }
set { this.Rotation = MathUtil.Vector3ToMatrix(value); }
}
public Matrix Rotation { get { return rotation; } set { rotation = value; } }
public Vector3 Scale { get { return scale; } set { scale = value; } }
public BoundingBox BoundingBox { get { return boundingBox; } }
// Constructors
public Terrain(Texture2D HeightMap, Texture2D Texture)
: base()
{
Setup(HeightMap, Texture);
}
public Terrain(Texture2D HeightMap, Texture2D Texture, GameScreen Parent)
: base(Parent)
{
Setup(HeightMap, Texture);
}
void Setup(Texture2D Heightmap, Texture2D Texture)
{
// Load height data
heightData = CreateTerrain(Heightmap, Texture);
// Create vertex and index buffers
myVertexDeclaration = new VertexDeclaration(Engine.GraphicsDevice,
VertexPositionNormalTexture.VertexElements);
VertexPositionNormalTexture[] terrainVertices = CreateVertices();
int[] terrainIndices = CreateIndices();
terrainVertices = GenerateNormalsForTriangleStrip(terrainVertices,
terrainIndices);
CreateBuffers(terrainVertices, terrainIndices);
// Setup effect
basicEffect = new BasicEffect(Engine.GraphicsDevice, null);
SetupEffect();
}
// Sets up terrain, texture, etc
private float[,] CreateTerrain(Texture2D heightMap, Texture2D texture)
{
// Minimum and maximum heights for terrain
float minimumHeight = 0;
float maximumHeight = 255;
// Width and height of terrain (from heightmap)
int width = heightMap.Width;
int height = heightMap.Height;
// Setup bounding box with width and height
boundingBox = new BoundingBox(
new Vector3(-width / 2, maximumHeight - minimumHeight, -height / 2),
new Vector3(width / 2, maximumHeight - minimumHeight, height / 2));
this.texture = texture;
// Get data from heightmap
Color[] heightMapColors = new Color[width * height];
heightMap.GetData<Color>(heightMapColors);
// Setup height data from heightmap data
float[,] heightData = new float[width, height];
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
heightData[x, y] = heightMapColors[x + y * width].R;
if (heightData[x, y] < minimumHeight)
minimumHeight = heightData[x, y];
if (heightData[x, y] > maximumHeight)
maximumHeight = heightData[x, y];
}
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
heightData[x, y] = (heightData[x, y] - minimumHeight)
/ (maximumHeight - minimumHeight) * 30.0f;
// Setup physics
heightMapInfo = new HeightMapInfo(heightData, 1);
if (heightMapObject != null)
{
heightMapObject.DisableComponent();
heightMapObject = null;
}
heightMapObject = new HeightMapObject(heightMapInfo, new Vector2(
heightMapInfo.Width / 2,
-heightMapInfo.Height / 2 + heightMapInfo.Height));
return heightData;
}
下列代码创建地形对象的顶点和索引:
// Set up vertices
private VertexPositionNormalTexture[] CreateVertices()
{
// Get width and height and create new vertex array
int width = heightData.GetLength(0);
int height = heightData.GetLength(1);
VertexPositionNormalTexture[] terrainVertices =
new VertexPositionNormalTexture[width * height];
// Calculate position, normal, and texcoords for vertices
int i = 0;
for (int z = 0; z < height; z++)
for (int x = 0; x < width; x++)
{
Vector3 position = new Vector3(x, heightData[x, z], -z);
Vector3 normal = new Vector3(0, 0, 1);
Vector2 texCoord = new Vector2((float)x / 30.0f,
(float)z / 30.0f);
terrainVertices[i++] = new VertexPositionNormalTexture(
position, normal, texCoord);
}
return terrainVertices;
}
// Set up indices
private int[] CreateIndices()
{
// Get width and height and create new index array
int width = heightData.GetLength(0);
int height = heightData.GetLength(1);
int[] terrainIndices = new int[(width) * 2 * (height - 1)];
// Calculate indices for triangle
int i = 0;
int z = 0;
while (z < height - 1)
{
for (int x = 0; x < width; x++)
{
terrainIndices[i++] = x + z * width;
terrainIndices[i++] = x + (z + 1) * width;
}
z++;
if (z < height - 1)
{
for (int x = width - 1; x >= 0; x--)
{
terrainIndices[i++] = x + (z + 1) * width;
terrainIndices[i++] = x + z * width;
}
}
z++;
}
return terrainIndices;
}
// Generates normals for a group of triangles
private 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;
}
以下代码设置BasicEffect并进行绘制:
// Sets up vertex and index buffers used for drawing
private void CreateBuffers(VertexPositionNormalTexture[] vertices,int[] indices)
{
terrainVertexBuffer= new VertexBuffer(Engine.GraphicsDevice,VertexPositionNormalTexture.SizeInBytes * vertices.Length,
BufferUsage.WriteOnly);
terrainVertexBuffer.SetData(vertices);
terrainIndexBuffer= new IndexBuffer(Engine.GraphicsDevice,typeof(int), indices.Length, BufferUsage.WriteOnly);
terrainIndexBuffer.SetData(indices);
}
// Setup BasicEffect
private void SetupEffect()
{
basicEffect.Texture = texture;
basicEffect.TextureEnabled = true;
basicEffect.EnableDefaultLighting();
basicEffect.DirectionalLight0.Direction= new Vector3(1, -1, 1);
basicEffect.DirectionalLight0.Enabled= true;
basicEffect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);
basicEffect.DirectionalLight1.Enabled= false;
basicEffect.DirectionalLight2.Enabled= false;
basicEffect.SpecularColor= new Vector3(0, 0, 0);
}
// Draw the terrain
public override void Draw()
{
// Require the camera
Camera camera = Engine.Services.GetService<Camera>();
if (camera== null)
throw new Exception("The engine services does not contain a "
+ "camera service. The terrain requires a camera to draw.");
// Set effect values
basicEffect.World= MathUtil.CreateWorldMatrix(position,rotation, scale);
basicEffect.View= camera.View;
basicEffect.Projection= camera.Projection;
// Get width and height
int width= heightData.GetLength(0);
int height= heightData.GetLength(1);
// Terrain uses different vertex winding than normal models,
// so set the new one
Engine.GraphicsDevice.RenderState.CullMode= CullMode.CullClockwiseFace;
// Start the effect
basicEffect.Begin();
// For each pass..
foreach(EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
// Begin the pass
pass.Begin();
// Draw the terrain vertices and indices
Engine.GraphicsDevice.Vertices[0].SetSource(terrainVertexBuffer, 0,VertexPositionNormalTexture.SizeInBytes);
Engine.GraphicsDevice.Indices = terrainIndexBuffer;
Engine.GraphicsDevice.VertexDeclaration =myVertexDeclaration;
Engine.GraphicsDevice.DrawIndexedPrimitives(Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleStrip,
0, 0, width * height, 0, width * 2* (height - 1) -2);
// End the pass
pass.End();
}
// End the effect
basicEffect.End();
// Set the vertex winding back
Engine.GraphicsDevice.RenderState.CullMode=CullMode.CullCounterClockwiseFace;
}
}
}
最后,我们创建一些叫做HeightmapObject的物理对象,这些对象通过使用JifLibX模拟高程图物理现象。
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using JigLibX.Collision;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Math;
using Microsoft.Xna.Framework.Graphics;
using JigLibX.Utils;
#endregion
namespace Innovation
{
public class HeightMapObject : PhysicsObject
{
public HeightMapInfo info;
public HeightMapObject(HeightMapInfo heightMapInfo, Vector2 shift)
{
Setup(heightMapInfo, shift);
InitializeComponent(Engine.DefaultScreen);
}
public HeightMapObject(HeightMapInfo heightMapInfo, Vector2 shift, GameScreen parent)
{
Setup(heightMapInfo, shift);
InitializeComponent(parent);
}
void Setup(HeightMapInfo heightMapInfo, Vector2 shift)
{
Body = new Body(); // just a dummy. The PhysicObject uses its position to get the draw pos
CollisionSkin = new CollisionSkin(null);
info = heightMapInfo;
Array2D field = new Array2D(heightMapInfo.Heights.GetUpperBound(0), heightMapInfo.Heights.GetUpperBound(1));
for (int x = 0; x < heightMapInfo.Heights.GetUpperBound(0); x++)
{
for (int z = 0; z < heightMapInfo.Heights.GetUpperBound(1); z++)
{
field.SetAt(x, z, heightMapInfo.Heights[x, z]);
}
}
// move the body. The body (because its not connected to the collision
// skin) is just a dummy. But the base class should know where to
// draw the model.
Body.MoveTo(new Vector3(shift.X, 0, shift.Y), Matrix.Identity);
CollisionSkin.AddPrimitive(new Heightmap(field, shift.X, shift.Y, 1, 1),
(int)MaterialTable.MaterialID.UserDefined, new MaterialProperties(0.7f, 0.7f, 0.6f));
PhysicsSystem.CurrentPhysicsSystem.CollisionSystem.AddCollisionSkin(CollisionSkin);
}
}
}
可通过以下代码使用地形类。
Terrain terrain = new Terrain(
Engine.Content.Load<Texture2D>("Content/heightmap"),
Engine.Content.Load<Texture2D>("Content/grass"));
运行程序后你将会看到地形。

发布时间:2009/2/4 下午1:50:05 阅读次数:10699
