31.高级地形AdvancedTerrainSceneNode类

AdvancedTerrainSceneNode类的代码主要参考自3D系列4.4 多纹理地形,与这篇文章的不同之处在于它创建的是TriangleList类型的顶点,而我使用的是数据量更少的TriangleStrip,使用TriangleStrip的解释可参见27.简单地形SimpleTerrainSceneNode类,代码如下,关键步骤请见代码注释:

namespace StunEngine.SceneNodes
{
    // 高级地形使用的自定义顶点格式,储存了顶点位置、法线和纹理坐标,还有每个顶点的4个权重
    public struct VertexMultitextured
    {
        public Vector3 Position;
        public Vector3 Normal;
        public Vector2 TextureCoordinate;
        public Vector4 TexWeights;

        public static int SizeInBytes = (3 + 3 + 2 + 4) * sizeof(float);
        public static VertexElement[] VertexElements = new VertexElement[]
          {
              new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
              new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
              new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
              new VertexElement( 0, sizeof(float) * 8, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
          };
    }

    /// 
    /// 基于高度图的地形。
    /// 
    public class AdvancedTerrainSceneNode : Renderable3DSceneNode
    {

        #region 构造函数和成员变量       

        /// 
        /// 地形使用的材质
        /// 
        private AdvancedTerrainMaterial material;        

        /// 
        /// 高度图文件名称
        /// 
        string heightMapName;

        /// 
        /// 高度图
        /// 
        Texture2D heightMap;

        /// 
        /// 地形最小高度
        /// 
        private float minimumHeight = float.MaxValue;
        
        /// 
        /// 地形最大高度
        /// 
        private float maximumHeight = float.MinValue;

        /// 
        /// 地形高度,默认为30,即高度介于0至30之间
        /// 
        private float height = 30.0f;        

        /// 
        /// 地形纹理宽度
        /// 
        private int terrainWidth;

        /// 
        /// 地形纹理高度
        /// 
        private int terrainHeight;        

        /// 
        /// 高度数据数组
        /// 
        float[,] heightData;   

        /// 
        /// 创建一个默认AdvancedTerrainSceneNode对象。使用引擎中的高度图和四张纹理,不进行缩放        
        /// 
        /// 引擎
        /// 所属场景
        public AdvancedTerrainSceneNode(StunXnaGE game, Scene setScene)
            : this(game, setScene, "Textures/heightmap128", "Textures/sand", "Textures/Grass", "Textures/rock", "Textures/snow", Vector3.One)
        {
            
        }

        /// 
        /// 创建一个AdvancedTerrainSceneNode对象。这个对象放置在(0,0,0) - (heightmap width, maxheight, -heightmap height)范围内        
        /// 
        /// 引擎
        /// 所属场景
        /// 高度贴图名称        
        /// 纹理1(沙地)
        /// 纹理2(草地)
        /// 纹理3(岩石)        
        /// 纹理4(雪地)          
        /// 地形缩放
        public AdvancedTerrainSceneNode(StunXnaGE game, Scene setScene,string setHeightMapName, string setTexture1,string setTexture2,string setTexture3,string setTexture4, Vector3 setScale): base(game,setScene)
        {
            // 设置材质
            material = new AdvancedTerrainMaterial(engine, engine.Content.Load("Effects\\AdvancedTerrain"));
            material .DiffuseTextureName=setTexture1;
            material .DetailTextureName =setTexture2;
            material .Texture3Name =setTexture3 ;
            material .Texture4Name =setTexture4 ;
            
            //将每张纹理平铺次数设为4次
            this.Material.DiffuseUVTile = this.Material.DetailUVTile =this.Material.Texture3UVTile =this.Material .Texture4UVTile =new Vector2(4.0f, 4.0f);

            // 实现IMaterial接口
            Imaterial = (IMaterial)material;

            this.heightMapName = setHeightMapName;
            //不对地形进行剔除操作
            this.DisableCulling = true;
            this.DisableUpdateCulling = true;
            
            this.pose.SetScale(ref setScale); 
        }
        
        #endregion

        #region 属性

        /// 
        /// 获取地形使用的材质
        /// 
        public AdvancedTerrainMaterial Material
        {
            get { return material; }
        }
        
        /// 
        /// 返回高度数据数组
        ///         
        public float[,] HeightData { get { return heightData; } }

        /// 
        /// 获取或设置地形高度的缩放值,默认为30,即高度介于0至30之间。这个值发生改变后需要重新创建地形顶点
        /// 
        public float Height
        {
            get { return height; }
            set
            {
                height = value;
                //加载高度图数据
                heightData = Utility.LoadHeightData(heightMap, ref terrainWidth, ref terrainHeight, ref minimumHeight, ref maximumHeight, height);
                CreateTerrain();
            }
        }

        /// 
        /// 获取范围在[0, infinite]之间的地形最小高度。
        /// 
        public float MinHeight
        { get { return minimumHeight; } }

        /// 
        /// 获取范围在[0, infinite]之间的地形最大高度。
        /// 
        public float MaxHeight{ get { return maximumHeight; } }        
        
        #endregion        
        
        public override void Initialize()
        {
            this.UpdateOrder = SceneNodeOrdering.Terrain.GetValue();           
            base.Initialize();            
            CreateTerrain();            
        }        
        
        internal override void LoadContent()
        {
            base.LoadContent();

            //加载高度图
            heightMap = engine.Content.Load(heightMapName);
            //加载高度图数据
            heightData = Utility.LoadHeightData(heightMap, ref terrainWidth, ref terrainHeight, ref minimumHeight, ref maximumHeight, height);
        }   
     
        /// 
        /// 绘制地形。
        /// 
        ///         
        public override int Draw(GameTime gameTime,bool useReflection)
        {
            //  因为地形上施加了缩放,所以WorldPose.WorldMatrix不正确,需要修正。
            this.pose.WorldMatrix = pose.TranslateMatrix;

            return base.Draw(gameTime, useReflection); 
            
        }

        /// 
        /// 如果重置了地形缩放则重新建立地形。
        /// 
        public override void OnScaleChange()
        {
           CreateTerrain();            
        }

        #region 生成地形使用的方法

        /// 
        /// 基于高度图和缩放创建顶点缓冲和索引缓冲。
        /// 
        private void CreateTerrain()
        {
            略...            
        }        

        /// 
        /// 创建地形顶点
        /// 
        /// 
        private VertexMultitextured[] CreateTerrainVertices()
        {
            //创建一个VertexMultitextured类型的数组保存所有顶点。地形需要terrainWidth * terrainHeight个顶点。 
            VertexMultitextured[] terrainVertices = new VertexMultitextured[terrainWidth * terrainHeight];

            // 在两个循环中创建所有顶点。里面的一个循环创建一行上的顶点,当一行完成后,第一个for循环切换到下一行,直到定义完所有行的顶点。            
            for (int x = 0; x < terrainWidth; x++)
            {
                for (int z = 0; z < terrainHeight; z++)
                {
                    //z值是负的,因此地形是建立在向前(-Z)方向的。而高度信息取自heightData数组。并乘以对应地形的缩放分量。
                    terrainVertices[x + z * terrainWidth].Position = new Vector3(x * pose.Scale.X, heightData[x, z] * pose.Scale.Y, -z * pose.Scale.Z);

                    // 指定纹理坐标,即将一张纹理平铺在地形上,纹理的平铺次数是由材质决定的,默认设置为4次
                    terrainVertices[x + z * terrainWidth].TextureCoordinate.X = (float)x / terrainWidth ;
                    terrainVertices[x + z * terrainWidth].TextureCoordinate.Y = (float)z / terrainHeight ;

                    // 将高度映射到纹理权重,以30高度为参考
                    // 如果地形最大高度为30,则因子factor为1,位于高度为15的像素第一张沙地纹理的权重为0,第二张草地纹理的权重为1/2,第三张岩石纹理的权重为1/6,第四张雪地纹理的权重为0
                    // 如果地形最大高度为60,则因子factor为2,位于高度为30的像素第一张沙地纹理的权重为0,第二张草地纹理的权重为/2,第三张岩石纹理的权重为1/6,第四张雪地纹理的权重为0
                    float factor = height / 30.0f;
                    terrainVertices[x + z * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, z]  - 0) / (factor*8.0f), 0, 1);
                    terrainVertices[x + z * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, z]  - 12*factor) / (factor*6.0f), 0, 1);
                    terrainVertices[x + z * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, z] - 20 * factor) / (factor * 6.0f), 0, 1);
                    terrainVertices[x + z * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, z]  - 30* factor) / (factor * 6.0f), 0, 1);
                    
                    // 将求每个顶点的权重之和,然后将权重除以这个和,保证每个顶点的权重和为1,否则颜色会偏亮或偏暗
                    // 如上面的例子数据,total为0+1/2+1/6+=2/3,下面的计算将权重归一化为0,3/4,1/4,0
                    float total = terrainVertices[x + z * terrainWidth].TexWeights.X;
                    total += terrainVertices[x + z * terrainWidth].TexWeights.Y;
                    total += terrainVertices[x + z * terrainWidth].TexWeights.Z;
                    total += terrainVertices[x + z * terrainWidth].TexWeights.W;
                    
                    terrainVertices[x + z * terrainWidth].TexWeights.X /= total;
                    terrainVertices[x + z * terrainWidth].TexWeights.Y /= total;
                    terrainVertices[x + z * terrainWidth].TexWeights.Z /= total;
                    terrainVertices[x + z * terrainWidth].TexWeights.W /= total;
                }
            }

            return terrainVertices;
        }

        /// 
        /// 创建地形顶点索引
        /// 
        /// 
        private int[] CreateTerrainIndices()
        {
            略...  
        }

        /// 
        /// 计算法线
        /// 
        private void GenerateNormals(ref VertexMultitextured[] vertices, ref int[] indices)
        {
            略...  
        }        

        /// 
        /// 判断是否在地形上
        /// 
        /// 给定x坐标
        /// 给定z坐标
        /// 
        private bool IsValidPosition(int x, int z)
        {
            return (x >= 0 && x < terrainWidth - 1 && z >= 0 && z < terrainHeight - 1);
        }        

        /// 
        /// 通过双线性插值获取指定点的精确高度
        /// 
        /// 
        /// 
        /// 
        public float GetExactHeightAt(float xCoord, float zCoord)
        {
            略...  
        }
        
        #endregion        

        #region 单元测试

#if DEBUG

        /// 
        /// 测试AdvancedTerrainSceneNode类
        /// 
        public static void TestAdvancedTerrainSceneNode()
        {
            AdvancedTerrainSceneNode terrain = null;
       
            // 相机上一帧的位置。
            Vector3 oldPosition=Vector3 .Zero;

            TestGame.Start("测试AdvancedTerrainSceneNode类",
                delegate
                {
                    //
                    terrain = new AdvancedTerrainSceneNode(TestGame.engine, TestGame.scene);
                    TestGame.scene.AddNode(terrain);
                    //将地形中心放置在坐标原点
                    Vector3 position=new Vector3(-64,0,64);
                    terrain.Pose.SetPosition(ref position);

                    // 开启地形的雾化
                    terrain .Material.FogEnabled = true;                    

                    // 设置雾化颜色
                    TestGame.scene.FogColor = new Vector4(0.8f, 0.8f, 0.8f, 1.0f);
                    TestGame.engine.BackGroundColor = Color.CornflowerBlue;

                    //不显示光标
                    TestGame.scene.IsShowMouse = false;

                    // 设置点光源和聚光灯
                    TestGame.scene.sunModel.Visible = false;
                    position=new Vector3(-15, 20, 0);
                    TestGame.scene.pointLight.Position = position;                    
                    TestGame.scene.pointLightModel.Pose.SetPosition(ref position);
                    
                    position = new Vector3(-5, 15, 0);
                    TestGame.scene.spotLight .Position = position;
                    TestGame.scene.spotLightModel .Pose.SetPosition(ref position);
                    
                    TestGame.scene.floor.Visible = false;
                },
                delegate
                {
                    
                    
                    // 按数字1键则切换地形上的纹理
                    if (Input.KeyboardKeyJustPressed(Keys.D1))
                    {
                        if (terrain.Material.DiffuseTextureName == "Textures\\sand")
                            terrain.Material.DiffuseTextureName = "Textures\\Rock";
                        else
                            terrain.Material.DiffuseTextureName = "Textures\\sand";
                    }                    
                    

                    // 按数字2键切换地形的高度
                    if (Input.KeyboardKeyJustPressed(Keys.D2))
                    {
                        if (terrain.Height == 30.0f)
                            terrain.Height = 60.0f;
                        else
                            terrain.Height = 30.0f;
                    }
                    
                    // 按数字3键切换地形的缩放
                    if (Input.KeyboardKeyJustPressed(Keys.D3))
                    {
                        Vector3 scale=Vector3 .One ;
                        Vector3 position = new Vector3(-64f, 0f, 64f);
                        if (terrain.Pose.Scale == scale)
                        {
                            scale = new Vector3(2.0f, 2.0f, 2.0f);
                            terrain.Pose.SetScale(ref scale);
                            position = new Vector3(-128f, 0f, 128f);
                            terrain.Pose.SetPosition(ref position);
                        }
                        else
                        {
                            scale = Vector3.One;
                            terrain.Pose.SetScale(ref scale );                            
                            terrain.Pose.SetPosition(ref position);
                        }
                    }

                    // 实现相机跟随地形的移动
                    Vector3 camPosition, tempPosition;
                    TestGame.scene.Camera.GetPosition(out camPosition);

                    // 如果相机发生移动
                    if ((camPosition - oldPosition) != Vector3.Zero)
                    {
                        //  检查相机与地形的高度差 
                        float height = 5.0f + terrain.GetExactHeightAt(camPosition.X, -camPosition.Z);
                        float diff = height - camPosition.Y;
                        if (diff != 0)
                        {
                            tempPosition = new Vector3(camPosition.X, height, camPosition.Z);
                            TestGame.scene.Camera.Pose.SetPosition(ref tempPosition);
                            TestGame.scene.Camera.UpdateViewMatrix(false);
                        }
                        TestGame.scene.Camera.GetPosition(out oldPosition);
                    }                        
                });
        }
#endif
        #endregion
    }
}

其中创建顶点的方法与27.简单地形SimpleTerrainSceneNode类是类似的,不同之处在于使用了自定义的顶点结构VertexMultitextured,额外添加了纹理的权重信息,但3D系列4.4 多纹理地形一文中自定义顶点结构有个问题:TextureCoordinate数据类型应是Vector2,此文中设为了Vector4,好像没有必要,增加了数据量,我改成了Vector2没有发生问题。而SimpleTerrain中使用的是XNA框架预定义的VertexPositionNormalTexture结构。

创建地形顶点索引的CreateTerrainIndices方法、计算法线的GenerateNormals方法、判断是否在地形上的IsValidPosition方法和通过双线性插值获取指定点的精确高度的GetExactHeightAt方法与27.简单地形SimpleTerrainSceneNode类是相同的,不再赘述。(好像应该创建一个地形基类让代码可以重用,还是以后实现吧)

高级地形使用的effect文件AdvancedTerrain.fx代码如下:

#include "Standard.inc"
#include "Lights.inc"

uniform extern texture	gTexture3;		//	第三张纹理
uniform extern  float2 gTexture3UVTile;	//  第三张纹理在UV方向的平铺次数
uniform extern texture	gTexture4;		//	第四张纹理
uniform extern  float2 gTexture4UVTile;	//  第四张纹理在UV方向的平铺次数

//	第三张纹理的采样器
sampler2D texture3Sampler = sampler_state
{
    Texture =
;
    magfilter = LINEAR; 
    minfilter = LINEAR; 
    mipfilter = LINEAR;
    AddressU = mirror; 
    AddressV = mirror;     
};

//	第四张纹理的采样器
sampler2D texture4Sampler = sampler_state
{
    Texture = ;
    magfilter = LINEAR; 
    minfilter = LINEAR; 
    mipfilter = LINEAR; 
    AddressU = mirror; 
    AddressV = mirror;   
};

// 顶点着色器的输入结构
struct VS_INPUT
{
	float4 Position         : POSITION0;
    float3 Normal           : NORMAL;
    float2 TextureCoords    : TEXCOORD0;
    float4 TextureWeights   : TEXCOORD1;
};

// 顶点着色器的输出结构
struct VS_OUTPUT
{
    float4 Position         : POSITION0;    
    float3 WorldNormal      : TEXCOORD0;
    float2 TextureCoords    : TEXCOORD1;
    float4 WorldPosition    : TEXCOORD2;
    float4 TextureWeights   : TEXCOORD3;
    float Depth             : TEXCOORD4;	 // 像素的深度值
};												


VS_OUTPUT MultiTexturedVS(VS_INPUT Input)
{    
    VS_OUTPUT Output = (VS_OUTPUT)0;
    float4x4 preViewProjection = mul (gView, gProjection);
    float4x4 preWorldViewProjection = mul (gWorld, preViewProjection);
    
    Output.Position = mul(Input.Position, preWorldViewProjection);
    Output.WorldNormal = mul(normalize(Input.Normal), gWorld);
    Output.TextureCoords = Input.TextureCoords;
    
    float4 worldPosition =  mul(Input.Position, gWorld);
    Output.WorldPosition = worldPosition / worldPosition.w;    
    
    Output.TextureWeights = Input.TextureWeights;
    
    // 深度值就是顶点在屏幕空间中的z坐标,因为这是一个4x4矩阵乘法的结果,所以使用前需要首先除以w分量:
    Output.Depth = Output.Position.z/Output.Position.w;
    
    return Output;    
}

float4 MultiTexturedPS(VS_OUTPUT Input):COLOR0
{
    // 定义两个变量处理混合:混合开始时离开相机的距离blendDistance,混合初末位置的范围blendWidth    
    float blendDistance = 0.99f;
    float blendWidth = 0.005f;
    
    // 所有像素都有一个介于0和1之间的深度值,0对应近裁平面,1对应远裁平面。
    // 使用以下代码,所有离开相机的距离小于0.99的像素的blendfactor为0,所有大于0.99+0.005=0.995的像素的blendfactor为1,两者之间的像素会获得一个经过线性插值的blendfactor
    float blendFactor = clamp((Input.Depth-blendDistance)/blendWidth, 0, 1);
     
    // 计算每个像素的颜色   
    float4 farColor;
    farColor = tex2D(textureSampler, Input.TextureCoords* gDiffuseUVTile)*Input.TextureWeights.x;
    farColor += tex2D(detailSampler, Input.TextureCoords* gDetailUVTile)*Input.TextureWeights.y;
    farColor += tex2D(texture3Sampler, Input.TextureCoords*gTexture3UVTile)*Input.TextureWeights.z;
    farColor += tex2D(texture4Sampler, Input.TextureCoords*gTexture4UVTile)*Input.TextureWeights.w;
    
    // 对近距离的像素,将纹理坐标乘以3,这样纹理会缩小3倍,即细节增加3倍。
    float4 nearColor;
    float2 nearTextureCoords = Input.TextureCoords*3;
    nearColor = tex2D(textureSampler, nearTextureCoords*gDiffuseUVTile)*Input.TextureWeights.x;
    nearColor += tex2D(detailSampler, nearTextureCoords* gDetailUVTile)*Input.TextureWeights.y;
    nearColor += tex2D(texture3Sampler, nearTextureCoords*gTexture3UVTile)*Input.TextureWeights.z;
    nearColor += tex2D(texture4Sampler, nearTextureCoords*gTexture4UVTile)*Input.TextureWeights.w;

    // 有了像素的nearColor和farColor,根据混合因子blendFactor将它们混合在一起获得漫反射颜色
    float4 diffuseColor = lerp(nearColor, farColor, blendFactor);
    
    // 将最终输出的颜色设为0
    float3 finalColor = 0;    
    
    // 归一化法线
    float3 N = normalize(Input.WorldNormal);     
	
	//----------------------------
	//	遍历所有光源
	//----------------------------   
	for(int i=0; i < gTotalLights; i++)
	{    
		// 只处理可用的光源
		if(gLights[i].enabled)
		{
			finalColor+= CalculateSingleLight(gLights[i], Input.WorldPosition, N,diffuseColor,gMaterialSpecular,gMaterialSpecPower);
		}
	}    
    
	//----------------------------------------
	//	添加环境光颜色和自发光颜色
	//----------------------------------------
	finalColor += (gAmbient * diffuseColor.rgb);
	finalColor += gMaterialEmissive;
     
	//---------------------------------------------------------------------------------
	//	如果打开雾化,则计算雾化颜色
	//---------------------------------------------------------------------------------
	if(gFogEnabled)
		finalColor = LinearFog(finalColor, Input.WorldPosition);	

	return float4(finalColor, diffuseColor.a);
}

technique MultiTextured
{
    pass Pass0
    {
        VertexShader = compile vs_3_0 MultiTexturedVS();
        PixelShader = compile ps_3_0 MultiTexturedPS();
    }
}

具体解释还是请参见3D系列4.4 多纹理地形3D系列4.5 在靠近相机的地方添加更多细节,还有代码的注释。

单元测试截图如下:

单元测试

文件下载(已下载 1095 次)

发布时间:2010/4/29 下午4:08:55  阅读次数:6754

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号