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