XNA Game Engine教程系列8:基本地形

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

地形1

首先,建立代码:

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"));

运行程序后你将会看到地形。

地形2


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

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号