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