33.水面WaterSceneNode类
要看懂这个教程,请首先看懂3D系列4.7 水面,3D系列4.8 绘制折射贴图,3D系列4.9 绘制反射贴图,3D系列4.10 完美镜像,3D系列4.11 波浪 初级凹凸映射,3D系列4.12 使用菲涅尔项混合折射,3D系列4.13 让水面移动添加风向,3D系列4.14 镜面高光反射-平面凹凸映射。
相对于上一个教程的海面,创建水面简单的地方在于水面的起伏没有海面这么大,所以水面实际上是一个平面,水波凹凸感是用一张法线贴图模拟的,而海面上的大波浪是真实有起伏的。但海面通常很辽阔,所以它一般不反射岸边的物体,只反射天空,而且海水通常很深,也无需绘制海面之下的场景,而水面就要考虑绘制反射和折射,这也是难点所在。水面WaterSceneNode类本身很简单,代码如下:
namespace StunEngine.SceneNodes
{
///
/// 水面
///
public class WaterSceneNode : Renderable3DSceneNode
{
#region 构造函数和成员变量
///
/// 水面使用的材质
///
private SingleTextureMaterial material;
///
/// 默认的水面的长和宽为128*128
///
int width = 128;
int height = 128;
///
/// 振幅,默认为0.3
///
float waveHeight = 0.3f;
///
/// 波长,默认为0.1
///
float waveLength = 0.1f;
///
/// 水流方向,默认为向前
///
Vector3 windDirection = new Vector3(0, 0, 1);
///
/// 风的强度,即水流的移动速度,默认值为0.002
///
float windForce = 0.02f;
///
/// 创建一个默认水面,使用引擎中的水面凹凸贴图,水面高度为5
///
/// 引擎
/// 所属场景
public WaterSceneNode(StunXnaGE engine, Scene setScene)
: this(engine, setScene,"Textures/waterBump",5)
{
}
///
/// 创建一个水面。
///
/// 引擎
public WaterSceneNode(StunXnaGE engine, Scene setScene, string setBumpTextureName, float setWaterHeight)
: base(engine, setScene)
{
//创建材质
this.material =new SingleTextureMaterial (engine ,engine.Content .Load ("Effects\\water"));
this.material .Texture1Name = setBumpTextureName;
//不对水面进行剔除操作
this.DisableCulling = true;
this.DisableUpdateCulling = true;
//设置effect参数的默认值
material.EffectInstance.Parameters["gTexture1UVTile"].SetValue(1.0f / waveLength);
material.EffectInstance.Parameters["gWaveHeight"].SetValue(waveHeight);
material.EffectInstance.Parameters["gWindForce"].SetValue(windForce);
material.EffectInstance.Parameters["gWindDirection"].SetValue(windDirection);
// 实现IMaterial接口
Imaterial = (IMaterial)material;
}
#endregion
#region 属性
///
/// 获取或设置风的强度,即水流的移动速度,默认值为0.002。
///
public float WindForce
{
get { return windForce; }
set
{
windForce = value;
material.EffectInstance.Parameters["gWindForce"].SetValue(windForce);
}
}
///
/// 获取或设置振幅,默认为0.3
///
public float WaveHeight
{
get { return waveHeight; }
set
{
waveHeight = value;
material.EffectInstance.Parameters["gWaveHeight"].SetValue(waveHeight);
}
}
///
/// 获取或设置波长,默认为0.1,其实改变的凹凸纹理的平铺次数,若波长为0.1,则平铺次数为倒数10次
///
public float WaveLength
{
get { return waveLength; }
set
{
waveLength = value;
material.EffectInstance.Parameters["gTexture1UVTile"].SetValue(1.0f/waveLength);
}
}
///
/// 水流方向,默认为向前
///
public Vector3 WindDirection
{
get { return windDirection; }
set {
windDirection = value;
material.EffectInstance.Parameters["gWindDirection"].SetValue(windDirection);
}
}
#endregion
public override void Initialize()
{
this.UpdateOrder = SceneNodeOrdering.Terrain .GetValue();
base.Initialize();
//调用MeshBuilder的静态CreateWater方法创建水面的顶点数据
this.mesh = MeshBuilder.CreateWater(engine.GraphicsDevice, width, height);
}
///
/// 绘制水面。
///
///
public override int Draw(GameTime gameTime,bool useReflection)
{
material.EffectInstance.Parameters["gReflectionView"].SetValue(scene.Camera.ReflectionViewMatrix);
material.EffectInstance.Parameters["gReflectionMap"].SetValue(scene.ReflectionMap);
material.EffectInstance.Parameters["gRefractionMap"].SetValue(scene.RefractionMap);
return base.Draw(gameTime, useReflection);
}
#region 单元测试
#if DEBUG
///
/// 测试WaterSceneNode类
///
public static void TestWaterSceneNode()
{
WaterSceneNode water = null;
SkyBoxSceneNode skybox = null;
CubeSceneNode cube1 = null;
SphereSceneNode sphere2 = null;
SkyDomeSceneNode skyDome = null;
Vector3 position;
TestGame.Start("测试WaterSceneNode类",
delegate
{
//添加一个天空球
skyDome = new SkyDomeSceneNode(TestGame.engine, TestGame.scene, "Textures/SkyDome", 16, 32, MathHelper.Pi * 0.6f);
TestGame.scene.AddNode(skyDome);
skyDome.IsReflected = true;
skyDome.Visible = false;
//添加一个天空盒
skybox = new SkyBoxSceneNode(TestGame.engine, TestGame.scene, "Textures/SkyCubeMap");
TestGame.scene.AddNode(skybox);
skybox.IsReflected = true;
//添加一个水面
water = new WaterSceneNode(TestGame.engine, TestGame.scene);
TestGame.scene.AddNode(water);
//将水面中心放置在坐标原点
position = new Vector3(-64, 0, 64);
water.Pose.SetPosition(ref position);
//在右侧添加一个白色不带纹理的球体,需要反射和折射
sphere2 = new SphereSceneNode(TestGame.engine, TestGame.scene, null);
TestGame.scene.AddNode(sphere2);
position = new Vector3(5f, 0f, 0.0f);
sphere2.Pose.SetPosition(ref position);
sphere2.Material.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
sphere2.Material.SpecularColor = new Vector3(1.0f, 1.0f, 1.0f);
sphere2.Material.SpecularPower = 50;
sphere2.IsReflected = true;
sphere2.IsRefracted = true;
//在左侧添加一个覆盖有岩石纹理的正方体,需要反射和折射
cube1 = new CubeSceneNode(TestGame.engine, TestGame.scene, "Textures\\Rock");
TestGame.scene.AddNode(cube1);
Vector3 scale = new Vector3(4.0f, 4.0f, 4.0f);
cube1.Pose.SetScale(ref scale);
position = new Vector3(-5f, 0f,0f);
cube1.Pose.SetPosition(ref position);
cube1.Material.SpecularColor = new Vector3(1.0f, 1.0f, 1.0f);
cube1.Material.SpecularPower = 50;
cube1.IsReflected = true;
cube1.IsRefracted = true;
// 将地板向下移动并施加折射效果
position = new Vector3(0.0f, -2f, 0.0f);
TestGame.scene.floor.Pose.SetPosition(ref position);
TestGame.scene.floor.IsRefracted = true;
TestGame.scene.IsShowMouse = false;
TestGame.scene.CanReflect = true;
TestGame.scene.CanRefract = true;
},
delegate
{
// 按数字1键切换水面的缩放
if (Input.KeyboardKeyJustPressed(Keys.D1))
{
Vector3 scale = Vector3.One;
position = new Vector3(-64f, 0f, 64f);
if (water.Pose.Scale == scale)
{
scale = new Vector3(2.0f, 1.0f, 2.0f);
water.Pose.SetScale(ref scale);
position = new Vector3(-128f, 1f, 128f);
water.Pose.SetPosition(ref position);
TestGame.scene.WaterHeight = 1.0f;
}
else
{
scale = Vector3.One;
water.Pose.SetScale(ref scale);
water.Pose.SetPosition(ref position);
TestGame.scene.WaterHeight = 0.0f;
}
}
// 按数字2键切换波长
if (Input.KeyboardKeyJustPressed(Keys.D2))
{
if (water.WaveLength == 0.1f)
{
water.WaveLength = 0.2f;
}
else
{
water.WaveLength = 0.1f;
}
}
// 按数字3键切换正方体高度
if (Input.KeyboardKeyJustPressed(Keys.D3))
{
position = new Vector3(-5f, -0.0f, 0f);
if (cube1.Pose.Position == position )
{
position = new Vector3(-5f, 4.0f, 0f);
cube1.Pose.SetPosition (ref position);
}
else
{
cube1.Pose.SetPosition (ref position );
}
}
// 按数字4键切换显示天空盒和天空球
if (Input.KeyboardKeyJustPressed(Keys.D4))
{
if (skybox.Visible)
{
skybox.Visible = false;
skyDome.Visible = true;
}
else
{
skybox.Visible = true;
skyDome.Visible = false;
}
}
},
delegate
{
//在屏幕上绘制反射贴图和折射贴图
StunXnaGE.SpriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate,
SaveStateMode.SaveState);
StunXnaGE.SpriteBatch.Draw(TestGame.scene.RefractionMap, new Rectangle(500, 20, 128, 128), Color.White);
StunXnaGE.SpriteBatch.Draw(TestGame.scene.ReflectionMap, new Rectangle(650, 20, 128, 128), Color.White);
StunXnaGE.SpriteBatch.End();
});
}
#endif
#endregion
}
}
创建水面顶点的方法位于MeshBuilder类的CreateWater静态方法中,也很简单,就是创建四个顶点,图元类型为TriangleStrip,其实和创建平面的CreateQuad方法是类似的,而且它无需设置法线信息,相比起来更加简单,代码如下:
////// 创建水面顶点 /// /// 图像设备 /// 水面宽度 /// 水面高度 public static Mesh CreateWater(GraphicsDevice g, int Width, int Height) { //定义四个顶点创建覆盖整个水面的两个三角形。 VertexPositionTexture[] waterVertices = new VertexPositionTexture[4]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, 0, -Height), new Vector2(0, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(Width, 0, -Height), new Vector2(1, 0)); waterVertices[2] = new VertexPositionTexture(new Vector3(0, 0, 0), new Vector2(0, 1)); waterVertices[3] = new VertexPositionTexture(new Vector3(Width, 0, 0), new Vector2(1, 1)); VertexBuffer waterVertexBuffer = new VertexBuffer(g, waterVertices.Length * VertexPositionTexture.SizeInBytes, BufferUsage.WriteOnly); waterVertexBuffer.SetData(waterVertices); VertexDeclaration waterVertexDeclaration = new VertexDeclaration(g, VertexPositionTexture.VertexElements); //创建水面的mesh return new Mesh(PrimitiveType.TriangleStrip, waterVertexBuffer, waterVertexDeclaration, 4, 2); }
根据文章3D系列4.8 绘制折射贴图,3D系列4.9 绘制反射贴图,要绘制反射贴图和折射贴图,需要定义渲染目标,剪裁平面并将反射贴图和折射贴图绘制到水面上,这些操作我都放在了场景scene类中了。首先在scene类中添加以下变量:
////// 是否实现反射 /// private bool canReflect = false; ////// 是否实现折射 /// private bool canRefract = false; ////// 用于折射的渲染目标 /// RenderTarget2D refractionRenderTarget; ////// 折射贴图 /// Texture2D refractionMap; ////// 用于反射的渲染目标 /// RenderTarget2D reflectionRenderTarget; ////// 反射贴图 /// Texture2D reflectionMap; ////// 场景中的水面高度,默认为0 /// float waterHeight = 0.0f;
并添加用于创建剪裁平面的CreatePlane方法、用于绘制折射贴图的DrawRefractionMap方法和DrawReflectionMap方法:
////// 根据参数创建一个平面 /// /// 平面离开XZ平面的高度 /// 法线方向 /// 当前视矩阵 /// 如果设为ture,则此平面之上的部分会被剪裁,如果设为false,则此平面之下的部分被剪裁 ///private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { // 归一化法线 planeNormalDirection.Normalize(); // 创建平面系数 Vector4 planeCoeffs = new Vector4(planeNormalDirection, -height); if (clipSide) planeCoeffs *= -1; // 剪裁平面的四个系数必须定义在剪裁空间中(这样显卡可以容易地判断哪些物体需要被绘制哪些需要被剪裁)。 // 要将系数从3D空间映射到剪裁空间,所以需要通过ViewProjection的反置(inverse-transpose)矩阵变换系数 Matrix worldViewProjection = currentViewMatrix * this.Camera.ProjectionMatrix; Matrix inverseWorldViewProjection = Matrix.Invert(worldViewProjection); inverseWorldViewProjection = Matrix.Transpose(inverseWorldViewProjection); // 将平面系数转换到正确的空间中并创建平面 planeCoeffs = Vector4.Transform(planeCoeffs, inverseWorldViewProjection); Plane finalPlane = new Plane(planeCoeffs); return finalPlane; } /// /// 绘制折射贴图 /// /// private void DrawRefractionMap(GameTime gameTime) { // 创建剪裁平面,设置为只绘制此平面之下的场景 Plane refractionPlane = CreatePlane(waterHeight, new Vector3(0, 1, 0), this.Camera.ViewMatrix, true); // 激活剪裁平面 engine.GraphicsDevice.ClipPlanes[0].Plane = refractionPlane; engine.GraphicsDevice.ClipPlanes[0].IsEnabled = true; // 将refractionRenderTarget作为当前渲染目标并绘制需要折射的节点 engine.GraphicsDevice.SetRenderTarget(0, refractionRenderTarget); engine.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); for (int i = 0; i < renderList.Count; i++) { Renderable3DSceneNode rsn = (Renderable3DSceneNode)nodes[renderList[i]]; if (rsn.IsRefracted) rsn.Draw(gameTime, false); } // 关闭剪裁平面 engine.GraphicsDevice.ClipPlanes[0].IsEnabled = false; // 将渲染目标重新设置为后备缓冲 engine.GraphicsDevice.SetRenderTarget(0, null); // 将渲染目标的内容复制到折射贴图中 refractionMap = refractionRenderTarget.GetTexture(); } ////// 绘制反射贴图 /// /// private void DrawReflectionMap(GameTime gameTime) { // 创建剪裁平面,设置为只绘制此平面之上的场景 Plane reflectionPlane = CreatePlane(waterHeight, new Vector3(0, 1, 0), this.Camera.ReflectionViewMatrix, false ); // 激活剪裁平面 engine.GraphicsDevice.ClipPlanes[0].Plane = reflectionPlane; engine.GraphicsDevice.ClipPlanes[0].IsEnabled = true; // 将reflectionRenderTarget作为当前渲染目标并绘制需要反射的节点 engine.GraphicsDevice.SetRenderTarget(0, reflectionRenderTarget); engine.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); for (int i = 0; i < renderList.Count; i++) { Renderable3DSceneNode rsn = (Renderable3DSceneNode)nodes[renderList[i]]; if (rsn.IsReflected) { rsn.Draw(gameTime, true); } } // 关闭剪裁平面 engine.GraphicsDevice.ClipPlanes[0].IsEnabled = false; // 将渲染目标重新设置为后备缓冲 engine.GraphicsDevice.SetRenderTarget(0, null); // 将渲染目标的内容复制到反射贴图中 reflectionMap = reflectionRenderTarget.GetTexture(); }
并在scene类的draw方法中调用DrawRefractionMap和DrawReflectionMap方法。
有些细节还要参考3.13 创建一面镜子:投影纹理。
下面我补充关于平面Plane的创建的内容,这可以参见XNA的帮助文件,有这样一个图:

Plane构造函数有多个重载方法,其中一个是
public Plane (Vector4 value)
Vector4类型的value参数的X, Y和Z分量定义了Plane的法线,W分量定义了沿法线方向上原点离开平面的距离。 因此要定义一个位于XZ平面上方距离为1,正面朝上平面的代码为:
Plane p = new Plane(new Vector4(0,1,0,-1));
Effect文件
比较难的是水面的effect代码,位于water.fx文件内:
//-----------------------------------------------------------------------------------------------
// Technique: Water(水面)
//
// 作者: www.riemers.net
//
// -----------------------------------------------------------------------------------------------
shared uniform extern float4x4 gProjection : PROJECTION; // 共享的投影矩阵
shared uniform extern float gTime; // 共享的时间变量
shared uniform extern int gTotalLights; // 共享的光源数量
// 包含光源数据的结构
struct Light
{
float enabled; //光源是否打开
float lightType; //光源类型
float3 color; //光源颜色
float3 position; //光源位置
float3 direction; //光线方向
float4 spotData; //四个分量分别保存range,falloff,theta,phi数据
};
//光源数组
shared Light gLights[8];
uniform extern float3 gCameraPos; // 相机位置
uniform extern float4x4 gView : VIEW; // 视矩阵
uniform extern float4x4 gReflectionView; // 反射视矩阵
uniform extern float4x4 gWorld : WORLD; // 世界矩阵
uniform extern float gWaveHeight; // 振幅
uniform extern float gWindForce; // 风力大小,即波速
uniform extern float3 gWindDirection; // 风向,即水流方向
uniform extern texture gTexture1; // 水面的凹凸贴图
uniform extern float2 gTexture1UVTile; // 凹凸纹理的平铺次数
sampler BumpMapSampler = sampler_state { texture = ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};
uniform extern texture gReflectionMap; //反射贴图
sampler ReflectionSampler = sampler_state { texture = ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};
uniform extern texture gRefractionMap; //折射贴图
sampler RefractionSampler = sampler_state { texture = ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};
struct VS_OUTPUT
{
float4 Position : POSITION;
float4 ReflectionMapSamplingPos : TEXCOORD1;
float2 BumpMapSamplingPos : TEXCOORD2;
float4 RefractionMapSamplingPos : TEXCOORD3;
float4 WorldPosition : TEXCOORD4;
};
VS_OUTPUT WaterVS(float4 inPos : POSITION, float2 inTex: TEXCOORD)
{
VS_OUTPUT Output = (VS_OUTPUT)0;
float4x4 preViewProjection = mul (gView, gProjection);
float4x4 preWorldViewProjection = mul (gWorld, preViewProjection);
Output.Position = mul(inPos, preWorldViewProjection);
Output.WorldPosition = mul(inPos, gWorld);
// 计算反射纹理的采样坐标
float4x4 preReflectionViewProjection = mul (gReflectionView, gProjection);
float4x4 preWorldReflectionViewProjection = mul (gWorld, preReflectionViewProjection);
Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection);
// 设置纹理的采样坐标
Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection);
// 归一化水流方向
float3 windDir = normalize(gWindDirection);
// 获取垂直于水流的方向
float3 perpDir = cross(gWindDirection, float3(0,1,0));
// 获取经水流方向修正的纹理uv坐标
float ydot = dot(inTex, gWindDirection.xz);
float xdot = dot(inTex, perpDir.xz);
float2 moveVector = float2(xdot, ydot);
// 让纹理的v坐标随时间移动
moveVector.y += gTime*gWindForce;
// 获取最终的凹凸纹理采样坐标
Output.BumpMapSamplingPos = moveVector*gTexture1UVTile;
return Output;
}
float4 WaterPS(VS_OUTPUT Input):COLOR0
{
// 采样凹凸纹理颜色
float4 bumpColor = tex2D(BumpMapSampler, Input.BumpMapSamplingPos);
float2 perturbation = gWaveHeight*(bumpColor.rg - 0.5f)*2.0f;
// 将反射贴图采样坐标从2D屏幕空间映射到纹理坐标
float2 ProjectedReflectTexCoords;
ProjectedReflectTexCoords.x = Input.ReflectionMapSamplingPos.x/Input.ReflectionMapSamplingPos.w/2.0f + 0.5f;
ProjectedReflectTexCoords.y = -Input.ReflectionMapSamplingPos.y/Input.ReflectionMapSamplingPos.w/2.0f + 0.5f;
float2 perturbatedTexCoords = ProjectedReflectTexCoords + perturbation;
float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords);
// 将折射贴图采样坐标从2D屏幕空间映射到纹理坐标
float2 ProjectedRefrTexCoords;
ProjectedRefrTexCoords.x = Input.RefractionMapSamplingPos.x/Input.RefractionMapSamplingPos.w/2.0f + 0.5f;
ProjectedRefrTexCoords.y = -Input.RefractionMapSamplingPos.y/Input.RefractionMapSamplingPos.w/2.0f + 0.5f;
float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation;
float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords);
// 从凹凸贴图获取法线向量
float3 eyeVector = normalize(gCameraPos - Input.WorldPosition);
float3 normalVector = (bumpColor.rbg-0.5f)*2.0f;
// 计算菲涅尔系数,并根据这个系数混合反射和折射颜色
float fresnelTerm = dot(eyeVector, normalVector);
float4 combinedColor = lerp(reflectiveColor, refractiveColor, fresnelTerm);
// 在水面再添加蓝灰色让它变得“脏”一点
float4 dullColor = float4(0.3f, 0.3f, 0.5f, 1.0f);
// 将蓝灰色混合到最终颜色
float4 color = lerp(combinedColor, dullColor, 0.2f);
// 设置光源方向,为简化起见,只使用场景中的单向光方向
float3 gLightDirection=float3(-1,-1,-1);
//----------------------------
// 遍历所有光源
//----------------------------
for(int i=0; i < gTotalLights; i++)
{
//只处理可用的光源
if(gLights[i].enabled&&gLights[i].lightType==0)
{
gLightDirection=gLights[i].direction;
}
}
//添加水面的镜面反射颜色
float3 reflectionVector = -reflect(gLightDirection, normalVector);
float specular = pow(dot(normalize(reflectionVector), normalize(eyeVector)), 256);
color.rgb += specular;
return color;
}
technique Water
{
pass Pass0
{
VertexShader = compile vs_3_0 WaterVS();
PixelShader = compile ps_3_0 WaterPS();
}
}
具体解释可参见3D系列4.10 完美镜像,3D系列4.11 波浪 初级凹凸映射,3D系列4.12 使用菲涅尔项混合折射,3D系列4.13 让水面移动添加风向,3D系列4.14 镜面高光反射-平面凹凸映射。
单元测试截图如下:

发布时间:2010/5/10 下午1:06:22 阅读次数:7814
