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 阅读次数:9807