32.海面OceanSceneNode类

OceanSceneNode类的代码主要参考自5.15 在3D世界添加水面,代码如下,关键步骤请见代码注释:

namespace StunEngine.SceneNodes
{
    /// 
    /// 海面
    /// 
    public class OceanSceneNode : Renderable3DSceneNode
    {

        #region 构造函数和成员变量

        /// 
        /// 海面使用的材质
        /// 
        private OceanMaterial material;        

        /// 
        /// 海面宽度,默认为128
        /// 
        int oceanWidth = 128;

        /// 
        /// 海面长度,默认为128
        /// 
        int oceanHeight = 128;

        /// 
        /// 凹凸贴图强度,此值越大,海面的凹凸感越强
        /// 
        float bumpStrength = 0.5f;        

        /// 
        /// 波速
        /// 
        Vector4 waveSpeeds = new Vector4(1, 2, 0.5f, 1.5f);        

        /// 
        /// 振幅
        /// 
        Vector4 waveHeights = new Vector4(0.3f, 0.4f, 0.2f, 0.3f);
        //Vector4 waveHeights = new Vector4(2.3f, 0.0f, 0.0f, 0.0f);        
        
        /// 
        /// 波长
        /// 
        Vector4 waveLengths = new Vector4(10, 5, 15, 7);        

        /// 
        /// 第1个波的方向
        /// 
        Vector2 waveDir0 = new Vector2(1, 0);        

        /// 
        /// 第2个波的方向
        /// 
        Vector2 waveDir1 = new Vector2(1, 0.5f);        

        /// 
        /// 第3个波的方向
        /// 
        Vector2 waveDir2 = new Vector2(1, 0.7f);        

        /// 
        /// 第4个波的方向
        /// 
        Vector2 waveDir3 = new Vector2(1, -0.5f);

        /// 
        /// 创建一个默认海面,使用引擎内置的水面凹凸贴图和天空盒纹理
        /// 
        public OceanSceneNode(StunXnaGE engine, Scene setScene)
            : this(engine, setScene, "Textures/waterBump", "Textures/SkyCubeMap")
        {

        }

        /// 
        /// 创建一个海面        
        ///         
        /// 引擎
        /// 所属场景
        /// 水面凹凸贴图
        /// 天空盒纹理
        public OceanSceneNode(StunXnaGE engine, Scene setScene, string setBumpTexture, string setSkyboxTextureName)
            : base(engine, setScene)
        {
            //创建材质并设置材质属性的默认值
            this.material = new OceanMaterial(engine, engine.Content.Load("Effects\\ocean"));
            this.material.Texture1Name = setBumpTexture;
            this.material.CubeTextureName = setSkyboxTextureName;
            this.material.Texture1UVTile = new Vector2(4.0f, 4.0f);

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

            //初始化effect参数
            material.EffectInstance.Parameters["gBumpStrength"].SetValue(bumpStrength);
            material.EffectInstance.Parameters["gWaveSpeeds"].SetValue(waveSpeeds);
            material.EffectInstance.Parameters["gWaveHeights"].SetValue(waveHeights);
            material.EffectInstance.Parameters["gWaveLengths"].SetValue(waveLengths);
            material.EffectInstance.Parameters["gWaveDir0"].SetValue(waveDir0);
            material.EffectInstance.Parameters["gWaveDir1"].SetValue(waveDir1);
            material.EffectInstance.Parameters["gWaveDir2"].SetValue(waveDir2);
            material.EffectInstance.Parameters["gWaveDir3"].SetValue(waveDir3);

            //不对海面进行剔除操作
            this.DisableCulling = true;
            this.DisableUpdateCulling = true;
        }

        #endregion       

        #region 属性

        /// 
        /// 获取海面使用的材质
        /// 
        public OceanMaterial Material
        {
            get { return material; }
        }

        /// 
        /// 获取或设置凹凸贴图强度,此值越大,海面的凹凸感越强
        /// 
        public float BumpStrength
        {
            get { return bumpStrength; }
            set
            {
                bumpStrength = value;
                material.EffectInstance.Parameters["gBumpStrength"].SetValue(bumpStrength);
            }
        }

        /// 
        /// 获取或设置波速
        /// 
        public Vector4 WaveSpeeds
        {
            get { return waveSpeeds; }
            set
            {
                waveSpeeds = value;
                material.EffectInstance.Parameters["gWaveSpeeds"].SetValue(waveSpeeds);

            }
        }

        /// 
        /// 获取或设置振幅
        /// 
        public Vector4 WaveHeights
        {
            get { return waveHeights; }
            set
            {
                waveHeights = value;
                material.EffectInstance.Parameters["gWaveHeights"].SetValue(waveHeights);
            }
        }

        /// 
        /// 获取或设置波长
        /// 
        public Vector4 WaveLengths
        {
            get { return waveLengths; }
            set
            {
                waveLengths = value;
                material.EffectInstance.Parameters["gWaveLengths"].SetValue(waveLengths);
            }
        }

        /// 
        /// 获取或设置第1个波的方向
        /// 
        public Vector2 WaveDir0
        {
            get { return waveDir0; }
            set
            {
                waveDir0 = value;
                material.EffectInstance.Parameters["gWaveDir0"].SetValue(waveDir0);
            }
        }

        /// 
        /// 获取或设置第2个波的方向
        /// 
        public Vector2 WaveDir1
        {
            get { return waveDir1; }
            set
            {
                waveDir1 = value;
                material.EffectInstance.Parameters["gWaveDir1"].SetValue(waveDir1);
            }
        }

        /// 
        /// 获取或设置第3个波的方向
        /// 
        public Vector2 WaveDir2
        {
            get { return waveDir2; }
            set
            {
                waveDir2 = value;
                material.EffectInstance.Parameters["gWaveDir2"].SetValue(waveDir2);
            }
        }

        /// 
        /// 获取或设置第4个波的方向
        /// 
        public Vector2 WaveDir3
        {
            get { return waveDir3; }
            set
            {
                waveDir3 = value;
                material.EffectInstance.Parameters["gWaveDir3"].SetValue(waveDir3);
            }
        }

        #endregion

        public override void Initialize()
        {
            this.UpdateOrder = SceneNodeOrdering.Terrain.GetValue();
            base.Initialize();
            this.mesh = MeshBuilder.CreateOcean(engine.GraphicsDevice, oceanWidth, oceanHeight);
        }
        

        #region 单元测试

#if DEBUG

        /// 
        /// 测试OceanSceneNode类
        /// 
        public static void TestOceanSceneNode()
        {
            OceanSceneNode ocean = null;
            SkyBoxSceneNode skybox = null;

            TestGame.Start("测试OceanSceneNode类",
                delegate
                {
                    //添加一个天空盒
                    skybox = new SkyBoxSceneNode(TestGame.engine, TestGame.scene, "Textures/SkyCubeMap");
                    TestGame.scene.AddNode(skybox);
                    
                    //添加一个海面
                    ocean = new OceanSceneNode(TestGame.engine, TestGame.scene);
                    TestGame.scene.AddNode(ocean);
                    //将海面中心放置在坐标原点
                    Vector3 position = new Vector3(-64, 0, 64);
                    ocean.Pose.SetPosition(ref position);
                    
                    //不显示光标
                    TestGame.scene.IsShowMouse = false;
                    TestGame.scene.floor.Visible = false;
                },
                delegate
                {
                    // 按数字1键切换海面的缩放
                    if (Input.KeyboardKeyJustPressed(Keys.D1))
                    {
                        Vector3 scale = Vector3.One;
                        Vector3 position = new Vector3(-64f, 0f, 64f);
                        if (ocean.Pose.Scale == scale)
                        {
                            scale = new Vector3(2.0f, 1.0f, 2.0f);
                            ocean.Pose.SetScale(ref scale);
                            position = new Vector3(-128f, 0f, 128f);
                            ocean.Pose.SetPosition(ref position);
                        }
                        else
                        {
                            scale = Vector3.One;
                            ocean.Pose.SetScale(ref scale);
                            ocean.Pose.SetPosition(ref position);
                        }
                    }

                    // 按数字2键切换凹凸纹理的平铺次数
                    if (Input.KeyboardKeyJustPressed(Keys.D2))
                    {
                        if (ocean.Material .Texture1UVTile  == new Vector2 (4.0f,4.0f))
                        {
                            ocean.Material.Texture1UVTile = new Vector2(1.0f, 1.0f);
                        }
                        else
                        {
                            ocean.Material.Texture1UVTile = new Vector2(4.0f, 4.0f);
                        }
                    }

                    // 按数字3键切换凹凸纹理的强度
                    if (Input.KeyboardKeyJustPressed(Keys.D3))
                    {
                        if (ocean .BumpStrength  == 0.5f)
                        {
                            ocean .BumpStrength=1.0f;
                        }
                        else
                        {
                            ocean .BumpStrength  = 0.5f;
                        }
                    }
                });
        }
#endif
        #endregion
    }
}

创建海面顶点数据的操作由MeshBuilder类中的静态方法CreateOcean完成,代码如下:

/// 
/// 创建海面的顶点缓冲和索引缓冲
///         
public static Mesh CreateOcean(GraphicsDevice g,int oceanWidth,int oceanHeight)
{
    // 创建顶点
    VertexPositionTexture[] oceanVertices = new VertexPositionTexture[oceanWidth * oceanHeight];

    int i = 0;
    for (int z = 0; z < oceanHeight; z++)
    {
        for (int x = 0; x < oceanWidth; x++)
        {
            Vector3 position = new Vector3(x, 0, -z);
            Vector2 texCoord = new Vector2((float)x / oceanWidth, (float)z / oceanHeight);

            oceanVertices[i++] = new VertexPositionTexture(position, texCoord);
        }
    }            

    // 创建索引
    int[] oceanIndices = new int[(oceanWidth) * 2 * (oceanHeight - 1)];

    i = 0;
    int y = 0;
    while (y < oceanHeight - 1)
    {
        for (int x = 0; x < oceanWidth; x++)
        {
            oceanIndices[i++] = x + y * oceanWidth;
            oceanIndices[i++] = x + (y + 1) * oceanWidth;
        }
        y++;

        if (y < oceanHeight - 1)
        {
            for (int x = oceanWidth - 1; x >= 0; x--)
            {
                oceanIndices[i++] = x + (y + 1) * oceanWidth;
                oceanIndices[i++] = x + y * oceanWidth;
            }
        }
        y++;
    }

    VertexBuffer oceanVertexBuffer = new VertexBuffer(g, VertexPositionTexture.SizeInBytes * oceanVertices.Length, BufferUsage.WriteOnly);
    oceanVertexBuffer.SetData(oceanVertices);

    IndexBuffer oceanIndexBuffer = new IndexBuffer(g, typeof(int), oceanIndices.Length, BufferUsage.WriteOnly);
    oceanIndexBuffer.SetData(oceanIndices);

    VertexDeclaration oceanVertexDeclaration = new VertexDeclaration(g, VertexPositionTexture.VertexElements);

    return new Mesh(PrimitiveType.TriangleStrip, oceanVertexBuffer, oceanVertexDeclaration, oceanIndexBuffer, oceanWidth * oceanHeight, oceanWidth * 2 * (oceanHeight - 1) - 2);
}

其实这个方法与27.简单地形SimpleTerrainSceneNode类是相同的,都是使用TriangleStrip形式的图元。

有点难的地方在于effect文件,代码如下:

shared uniform extern float4x4	gProjection : PROJECTION;  //共享的投影矩阵
shared uniform extern float gTime;						   //共享的时间变量

uniform extern float3	gCameraPos;	            // 相机位置
uniform extern float4x4	gView : VIEW;			// 视矩阵
uniform extern float4x4	gWorld : WORLD;			// 世界矩阵
uniform extern float4 gWaveSpeeds;				// 波速
uniform extern float4 gWaveHeights;				// 振幅
uniform extern float4 gWaveLengths;				// 波长
uniform extern float2 gWaveDir0;                // 第1个波的方向
uniform extern float2 gWaveDir1;                // 第2个波的方向
uniform extern float2 gWaveDir2;                // 第3个波的方向
uniform extern float2 gWaveDir3;                // 第4个波的方向

uniform extern float gBumpStrength;

uniform extern texture	gTexture1;		        //	海面的凹凸纹理
uniform extern float2	gTexture1UVTile;        //  凹凸纹理的平铺次数

//	凹凸纹理采样器
sampler2D textureSampler = sampler_state
{
    Texture = ;
    magfilter = LINEAR;
    minfilter = LINEAR;
    mipfilter = LINEAR;
    AddressU = mirror; 
    AddressV = mirror;     
};

// 被海面反射的立方贴图
uniform extern texture gCubeTexture;

// 立方贴图采样器
sampler CubeTextureSampler = sampler_state 
{
	texture = ; 
	magfilter = LINEAR; 
	minfilter = LINEAR; 
	mipfilter=LINEAR; 
	AddressU = mirror; 
	AddressV = mirror;
};

struct VS_OUTPUT
{
	float4 Position : POSITION;
	float2 TexCoord : TEXCOORD0;
	float3 WorldPosition: TEXCOORD1;
	float3x3 TTW	: TEXCOORD2;	
};
    
VS_OUTPUT OWVertexShader(float4 inPos: POSITION0, float2 inTexCoord: TEXCOORD0)
{
	VS_OUTPUT Output = (VS_OUTPUT)0;	
	
	// 将顶点的X,Z位置与传播方向点乘,对于垂直于波的传播方向上一直线上的所有顶点来说,这个点乘值是相同的。
	// 如果参数相同,那么正弦函数也相同,所以它们的高度也是相同的。 
	float4 dotProducts;
    dotProducts.x = dot(gWaveDir0, inPos.xz);
    dotProducts.y = dot(gWaveDir1, inPos.xz);
    dotProducts.z = dot(gWaveDir2, inPos.xz);
    dotProducts.w = dot(gWaveDir3, inPos.xz);        
    
    // shader可以同时将四个正弦函数求和,这需要对顶点的XZ坐标和波的方向进行四次点乘。
    // 在使用这些点乘作为正弦函数的参数之前,需要将它们除以xWaveLengths变量,这样可以调整波长。
	// 要使水波移动,需要在参数中添加当前时间。
	// 将xWaveSpeeds 变量乘以当前时间,可以定义每列波的波速,让某些波可以比其他波运动得更快或是更慢。
    float4 arguments = gTime*gWaveSpeeds-dotProducts/gWaveLengths;
    float4 heights = gWaveHeights*sin(arguments);
    
    float4 final3DPos = inPos;
    final3DPos.y += heights.x;
    final3DPos.y += heights.y;
    final3DPos.y += heights.z;
    final3DPos.y += heights.w;
	
	float4x4 preViewProjection = mul(gView, gProjection);
	float4x4 preWorldViewProjection = mul(gWorld, preViewProjection);
	Output.Position = mul(final3DPos, preWorldViewProjection);	
	
	float4 final3DPosW = mul(final3DPos, gWorld);
	Output.WorldPosition = final3DPosW;	
	
	// 对波形函数求导
	float4 derivatives = gWaveHeights*cos(arguments)/gWaveLengths;
	float2 deviations = 0;
    deviations += derivatives.x*gWaveDir0;
    deviations += derivatives.y*gWaveDir1;
    deviations += derivatives.z*gWaveDir2;
    deviations += derivatives.w*gWaveDir3;	
	
	// 根据前面得出的导数计算法线、副法线和切线
	float3 Normal = float3(-deviations.x, 1, -deviations.y);	
	float3 Binormal = float3(1, deviations.x, 0);
    float3 Tangent = float3(0, deviations.y, 1);    
	
	// 创建切线矩阵
	float3x3 tangentToObject;
	tangentToObject[0] = normalize(Binormal);
	tangentToObject[1] = normalize(Tangent);
	tangentToObject[2] = normalize(Normal);		
	
	float3x3 tangentToWorld = mul(tangentToObject,gWorld);
	Output.TTW = tangentToWorld;		
	
	Output.TexCoord = inTexCoord+gTime/50.0f*float2(-1,0);
	
	return Output;
}

float4 OWPixelShader(VS_OUTPUT Input) : COLOR0					
{		
	// 通过多次滚动凹凸贴图,每次使用一个不同的缩放,不同的滚动速度,并交换X和Y纹理坐标,可以避免看到凹凸贴图块
	float3 bumpColor1 = tex2D(textureSampler, gTexture1UVTile*Input.TexCoord)-0.5f;	
	float3 bumpColor2 = tex2D(textureSampler, gTexture1UVTile*1.8*Input.TexCoord.yx)-0.5f;
	float3 bumpColor3 = tex2D(textureSampler, gTexture1UVTile*3.1*Input.TexCoord)-0.5f;
	
	float3 normalT = bumpColor1 + bumpColor2 + bumpColor3;
	
	// 通过gBumpStrength调整凹凸贴图强度
	normalT.rg *= gBumpStrength;
	normalT = normalize(normalT);
	float3 normalW = mul(normalT, Input.TTW);	
	
	// 采样天空盒颜色
	float3 eyeVector = normalize(Input.WorldPosition - gCameraPos);	
	float3 reflection = reflect(eyeVector, normalW);		
	float4 reflectiveColor = texCUBE(CubeTextureSampler, reflection);
	
	// 计算菲尼尔系数,并把它的范围设置为0.5至1之间
	float fresnelTerm = dot(-eyeVector, normalW);	
	fresnelTerm = fresnelTerm/2.0f+0.5f;		
	
	// 添加镜面高光反射颜色
	float sunlight = reflectiveColor.r;
	sunlight += reflectiveColor.g;
	sunlight += reflectiveColor.b;
	sunlight /= 3.0f;
	float specular = pow(sunlight,30);	
	
	// 水面颜色设置为暗蓝色
	float4 waterColor = float4(0,0.15,0.4,1);	
	// 输出最终的水面颜色
	return  waterColor*fresnelTerm + reflectiveColor*(1-fresnelTerm) + specular;
}

technique OceanWater
{
	pass Pass0
    {  
    	VertexShader = compile vs_3_0 OWVertexShader();
        PixelShader  = compile ps_3_0 OWPixelShader();
    }
}

具体解释请参见5.15 在3D世界添加水面,我认为最难的是第一步:即通过叠加4个正弦波生成复杂的海面,并求出波浪的切线、法线和副法线。参考了大学物理教材和《GPU精粹1》,现总结如下:

物理上一维波的函数为:

一维波函数

式中A为振幅,v为波速,λ为波长,φ0为初相位,t为时间,x为离开振源的距离。为简化起见,可以将φ0设为0,并使用sin函数,即使用以下形式:

简化方程

对于二维空间中的波,此函数应修正为:

二维波函数

注意:XNA中水平面为XZ平面。其中D为波的传播方向,D•(x,z)为传播方向与点坐标的点乘。有了物理知识,现在可以看一下shader的顶点着色器代码了。首先是dotProducts.x = dot(gWaveDir0, inPos.xz),计算的就是D•(x,z),然后float4 arguments = gTime*gWaveSpeeds-dotProducts/gWaveLengths,即 参数,对比可知gWaveSpeeds实际上是波速 。然后由数学知识可知副法线是点坐标在x方向上的偏导,切线为z方向的偏导,法线为在副法线和切线的叉乘,所以有:

float4 derivatives = gWaveHeights*cos(arguments)/gWaveLengths; 

float3 Normal = float3(-deviations.x, 1, -deviations.y);
float3 Binormal = float3(1, deviations.x, 0); 
float3 Tangent = float3(0, deviations.y, 1); 

具体数学式子请参见《GPU 精粹1》的第一章:用物理模型进行高效的水模拟(http://http.developer.nvidia.com/GPUGems/gpugems_ch01.html)。 Effect的其余代码我觉得不难,就不再赘述了。单元测试截图如下:

单元测试

感觉上还不够漂亮,海面有点像塑料,而且波太圆润,要加以改进还是可以参考《GPU 精粹1》的第一章:用物理模型进行高效的水模拟(http://http.developer.nvidia.com/GPUGems/gpugems_ch01.html)。

文件下载(已下载 1097 次)

发布时间:2010/5/7 上午9:46:10  阅读次数:6517

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号