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 镜面高光反射-平面凹凸映射。
单元测试截图如下:
文件下载(已下载 1616 次)
发布时间:2010/5/10 下午1:06:22 阅读次数:7131