Technorati 标签: xna,water effect,C#,.net,hlsl
几乎所有的室外场景游戏都少不了水面效果的渲染,基中主要包括波浪效果、反射效果、折射效果等,在这之前我对它可是一无所知,现在总算是有了初步的了解,主要是归功于XNA的快速。当然,我的主要目的还是对算法进行学习。
原理我还是不说,Google一下,讲的很好的。我这里主要有1个Water类和一个VertexMultitextured结构体,还有一个Effect。
用它做法线
Water类是实现水面效果的主要类
public class Water:DrawableGameComponent
{
#region 字段
Camera camera;
private VertexBuffer vb;
private IndexBuffer ib;
VertexMultitextured[] myVertices;
private int myHeight = 128;
private int myWidth = 128;
private Vector3 myPosition;
private Vector3 myScale;
private Quaternion myRotation;
Effect effect;
private Vector3 basePosition;
public Vector3 Position
{
get { return basePosition; }
set { basePosition = value; }
}
private string EnvAsset;
float bumpHeight = 0.5f;
Vector2 textureScale = new Vector2(4, 4);
Vector2 bumpSpeed = new Vector2(0, .05f);
float fresnelBias = .025f;
float fresnelPower = 1.0f;
float hdrMultiplier = 1.0f;
Color deepWaterColor = Color.Black;
Color shallowWaterColor = Color.SkyBlue;
Color reflectionColor = Color.White;
float reflectionAmount = 0.5f;
float waterAmount = 0f;
float waveAmplitude = 0.5f;
float waveFrequency = 0.1f;
/// <summary>
/// 水面凹凸纹理高度,最小0,最大2,默认0.5
/// </summary>
public float BumpHeight
{
get { return bumpHeight; }
set { bumpHeight = value; }
}
/// <summary>
/// 凹凸纹理缩放倍数
/// </summary>
public Vector2 TextureScale
{
get { return textureScale; }
set { textureScale = value; }
}
/// <summary>
/// 水流速度
/// </summary>
public Vector2 BumpSpeed
{
get { return bumpSpeed; }
set { bumpSpeed = value; }
}
/// <summary>
/// 0到1, 默认 0.025
/// </summary>
public float FresnelBias
{
get { return fresnelBias; }
set { fresnelBias = value; }
}
/// <summary>
///0到10, 默认 1.0;
/// </summary>
public float FresnelPower
{
get { return FresnelPower; }
set { fresnelPower = value; }
}
/// <summary>
/// 0到100, 默认1.0
/// </summary>
public float HDRMultiplier
{
get { return hdrMultiplier; }
set { hdrMultiplier = value; }
}
/// <summary>
/// 深水处颜色;
/// </summary>
public Color DeepWaterColor
{
get { return deepWaterColor; }
set { deepWaterColor = value; }
}
/// <summary>
///浅水处颜色
/// </summary>
public Color ShallowWaterColor
{
get { return shallowWaterColor; }
set { shallowWaterColor = value; }
}
/// <summary>
/// 反射默认颜色
/// </summary>
public Color ReflectionColor
{
get { return reflectionColor; }
set { reflectionColor = value; }
}
/// <summary>
/// 0.0 到2.0
/// </summary>
public float ReflectionAmount
{
get { return reflectionAmount; }
set { reflectionAmount = value; }
}
public float WaterAmount
{
get { return waterAmount; }
set { waterAmount = value; }
}
public float WaveAmplitude
{
get { return waveAmplitude; }
set { waveAmplitude = value; }
}
public float WaveFrequency
{
get { return waveFrequency; }
set { waveFrequency = value; }
}
public int Height
{
get { return myHeight; }
set { myHeight = value; }
}
public int Width
{
get { return myWidth; }
set { myWidth = value; }
}
#endregion
public Water(Game game, string Environment,Camera camera): base(game)
{
myWidth = 256;
myHeight = 256;
myPosition = new Vector3(0, 0, 0);
myScale = new Vector3(80,1,80);
myRotation = new Quaternion(0, 0, 0, 1);
EnvAsset = Environment;
this.camera = camera;
}
protected override void LoadContent()
{
effect = Game.Content.Load<Effect>("Water");
effect.Parameters["tEnvMap"].SetValue(Game.Content.Load<TextureCube>(EnvAsset));
effect.Parameters["tNormalMap"].SetValue(Game.Content.Load<Texture2D>("waves"));
myPosition = new Vector3(basePosition.X - (myWidth / 2), basePosition.Y, basePosition.Z - (myHeight / 2));
// 顶点
myVertices = new VertexMultitextured[myWidth * myHeight];
for (int x = 0; x < myWidth; x++)
for (int y = 0; y < myHeight; y++)
{
myVertices[x + y * myWidth].Position = new Vector3(y, 0, x);
myVertices[x + y * myWidth].Normal = new Vector3(0, -1, 0);
myVertices[x + y * myWidth].TextureCoordinate.X = (float)x / 30.0f;
myVertices[x + y * myWidth].TextureCoordinate.Y = (float)y / 30.0f;
}
// 计算切线法线
for (int x = 0; x < myWidth; x++)
for (int y = 0; y < myHeight; y++)
{
// 切线数据.
if (x != 0 && x < myWidth - 1)
myVertices[x + y * myWidth].Tangent = myVertices[x - 1 + y * myWidth].Position - myVertices[x + 1 + y * myWidth].Position;
else
if (x == 0)
myVertices[x + y * myWidth].Tangent = myVertices[x + y * myWidth].Position - myVertices[x + 1 + y * myWidth].Position;
else
myVertices[x + y * myWidth].Tangent = myVertices[x - 1 + y * myWidth].Position - myVertices[x + y * myWidth].Position;
// 法线数据.
if (y != 0 && y < myHeight - 1)
myVertices[x + y * myWidth].BiNormal = myVertices[x + (y - 1) * myWidth].Position - myVertices[x + (y + 1) * myWidth].Position;
else
if (y == 0)
myVertices[x + y * myWidth].BiNormal = myVertices[x + y * myWidth].Position - myVertices[x + (y + 1) * myWidth].Position;
else
myVertices[x + y * myWidth].BiNormal = myVertices[x + (y - 1) * myWidth].Position - myVertices[x + y * myWidth].Position;
}
vb = new VertexBuffer(Game.GraphicsDevice, VertexMultitextured.SizeInBytes * myWidth * myHeight, BufferUsage.WriteOnly);
vb.SetData(myVertices);
short[] terrainIndices = new short[(myWidth - 1) * (myHeight - 1) * 6];
for (short x = 0; x < myWidth - 1; x++)
{
for (short y = 0; y < myHeight - 1; y++)
{
terrainIndices[(x + y * (myWidth - 1)) * 6] = (short)((x + 1) + (y + 1) * myWidth);
terrainIndices[(x + y * (myWidth - 1)) * 6 + 1] = (short)((x + 1) + y * myWidth);
terrainIndices[(x + y * (myWidth - 1)) * 6 + 2] = (short)(x + y * myWidth);
terrainIndices[(x + y * (myWidth - 1)) * 6 + 3] = (short)((x + 1) + (y + 1) * myWidth);
terrainIndices[(x + y * (myWidth - 1)) * 6 + 4] = (short)(x + y * myWidth);
terrainIndices[(x + y * (myWidth - 1)) * 6 + 5] = (short)(x + (y + 1) * myWidth);
}
}
ib = new IndexBuffer(Game.GraphicsDevice, typeof(short), (myWidth - 1) * (myHeight - 1) * 6, BufferUsage.WriteOnly);
ib.SetData(terrainIndices);
Game.GraphicsDevice.VertexDeclaration = new VertexDeclaration(Game.GraphicsDevice, VertexMultitextured.VertexElements);
base.LoadContent();
}
public override void Update(GameTime gameTime)
{
effect.Parameters["fTime"].SetValue((float)gameTime.TotalRealTime.TotalSeconds);
if (Keyboard.GetState().IsKeyDown(Keys.D1))
WaterAmount = 0;
if (Keyboard.GetState().IsKeyDown(Keys.D2))
{
WaterAmount = 1;
ShallowWaterColor = Color.DarkSeaGreen;
DeepWaterColor = Color.Navy;
ReflectionColor = Color.DarkGray;
}
if (Keyboard.GetState().IsKeyDown(Keys.D3))
{
WaterAmount = 1;
ShallowWaterColor = Color.Gold;
DeepWaterColor = Color.Red;
ReflectionColor = Color.White;
}
if (Keyboard.GetState().IsKeyDown(Keys.D4))
{
WaveFrequency = .5f;
WaveAmplitude = .3f;
BumpHeight = 1f;
}
if (Keyboard.GetState().IsKeyDown(Keys.D5))
{
WaveFrequency = .1f;
WaveAmplitude = .1f;
BumpHeight = .1f;
}
if (Keyboard.GetState().IsKeyDown(Keys.D6))
{
WaveFrequency = .1f;
WaveAmplitude = .5f;
BumpHeight = .1f;
}
if (Keyboard.GetState().IsKeyDown(Keys.D7))
{
WaveFrequency = .1f;
WaveAmplitude = 2f;
BumpHeight = 1f;
}
if (Keyboard.GetState().IsKeyDown(Keys.D8))
{
WaveFrequency = 0;
WaveAmplitude = 0;
BumpHeight = 0;
}
if (Keyboard.GetState().IsKeyDown(Keys.D9))
{
WaveFrequency = 0;
WaveAmplitude = 0;
BumpHeight = 1;
}
if (Keyboard.GetState().IsKeyDown(Keys.D0))
SetDefault();
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
Matrix World = Matrix.CreateScale(myScale) *
Matrix.CreateFromQuaternion(myRotation) *
Matrix.CreateTranslation(myPosition);
Matrix WVP = World * camera.View * camera.Projection;
Matrix WV = World * camera.View;
Matrix viewI = Matrix.Invert(camera.View);
effect.Parameters["matWorldViewProj"].SetValue(WVP);
effect.Parameters["matWorld"].SetValue(World);
effect.Parameters["matWorldView"].SetValue(WV);
effect.Parameters["matViewI"].SetValue(viewI);
effect.Parameters["fBumpHeight"].SetValue(bumpHeight);
effect.Parameters["vTextureScale"].SetValue(textureScale);
effect.Parameters["vBumpSpeed"].SetValue(bumpSpeed);
effect.Parameters["fFresnelBias"].SetValue(fresnelBias);
effect.Parameters["fFresnelPower"].SetValue(fresnelPower);
effect.Parameters["fHDRMultiplier"].SetValue(hdrMultiplier);
effect.Parameters["vDeepColor"].SetValue(deepWaterColor.ToVector4());
effect.Parameters["vShallowColor"].SetValue(shallowWaterColor.ToVector4());
effect.Parameters["vReflectionColor"].SetValue(reflectionColor.ToVector4());
effect.Parameters["fReflectionAmount"].SetValue(reflectionAmount);
effect.Parameters["fWaterAmount"].SetValue(waterAmount);
effect.Parameters["fWaveAmp"].SetValue(waveAmplitude);
effect.Parameters["fWaveFreq"].SetValue(waveFrequency);
Game.GraphicsDevice.Vertices[0].SetSource(vb, 0, VertexMultitextured.SizeInBytes);
Game.GraphicsDevice.Indices = ib;
//Game.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
effect.Begin(SaveStateMode.SaveState);
for (int p = 0; p < effect.CurrentTechnique.Passes.Count; p++)
{
effect.CurrentTechnique.Passes[p].Begin();
Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, myWidth * myHeight, 0, (myWidth - 1) * (myHeight - 1) * 2);
effect.CurrentTechnique.Passes[p].End();
}
effect.End();
//Game.GraphicsDevice.RenderState.FillMode = FillMode.Solid;
base.Draw(gameTime);
}
public void SetDefault()
{
bumpHeight = 0.5f;
textureScale = new Vector2(4, 4);
bumpSpeed = new Vector2(0, .05f);
fresnelBias = .025f;
fresnelPower = 1.0f;
hdrMultiplier = 1.0f;
deepWaterColor = Color.Black;
shallowWaterColor = Color.SkyBlue;
reflectionColor = Color.White;
reflectionAmount = 0.5f;
waterAmount = 0f;
waveAmplitude = 0.5f;
waveFrequency = 0.1f;
}
}
而VertexMultitextured结构体主要是水面的数据
public struct VertexMultitextured
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
public Vector3 Tangent;
public Vector3 BiNormal;
public static int SizeInBytes = (3 + 3 + 2 + 3 + 3) * 4;
public static VertexElement[] VertexElements = new VertexElement[]
{
new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
new VertexElement( 0, sizeof(float) * 8, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Tangent, 0 ),
new VertexElement( 0, sizeof(float) * 11, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Binormal, 0 ),
};
}
最重要的是Shader
float fTime : Time;
//--------------------------------------------------
// 全局变量
//--------------------------------------------------
float4x4 matWorldViewProj : WorldViewProjection;
float4x4 matWorld : World;
float4x4 matWorldView : WorldView;
float4x4 matViewI : ViewInverse;
float fBumpHeight
<
string UIName = "Bump Height";
string UIWidget = "Slider";
float UIMin = 0.0f;
float UIMax = 2.0f;
float UIStep = 0.1f;
> = 0.5f;
float2 vTextureScale
<
string UIName = "Texture Scale";
string UIWidget = "Vector";
> = { 4.0f, 4.0f };
float2 vBumpSpeed
<
string UIName = "Bump Speed";
string UIWidget = "Vector";
> = { -0.0f, 0.05f };
float fFresnelBias
<
string UIName = "Fresnel Bias";
string UIWidget = "Slider";
float UIMin = 0.0f;
float UIMax = 1.0f;
float UIStep = 0.1f;
> = 0.025f;
float fFresnelPower
<
string UIName = "Fresnel Exponent";
string UIWidget = "Slider";
float UIMin = 1.0f;
float UIMax = 10.0f;
float UIStep = 0.1f;
> = 1.0f;
float fHDRMultiplier
<
string UIName = "HDR Multiplier";
string UIWidget = "Slider";
float UIMin = 0.0f;
float UIMax = 100.0f;
float UIStep = 1.0f;
> = 1.0f;
float4 vDeepColor : Diffuse
<
string UIName = "Deep Water Color";
string UIWidget = "Vector";
> = { 0.0f, 0.40f, 0.50f, 1.0f };
float4 vShallowColor : Diffuse
<
string UIName = "Shallow Water Color";
string UIWidget = "Vector";
> = { 0.55f, 0.75f, 0.75f, 1.0f };
float4 vReflectionColor : Diffuse
<
string UIName = "Reflection Color";
string UIWidget = "Vector";
> = { 1.0f, 1.0f, 1.0f, 1.0f };
float fReflectionAmount
<
string UIName = "Reflection Amount";
string UIWidget = "Slider";
float UIMin = 0.0f;
float UIMax = 2.0f;
float UIStep = 0.1f;
> = 0.5f;
float fWaterAmount
<
string UIName = "Water Color Amount";
string UIWidget = "Slider";
float UIMin = 0.0f;
float UIMax = 2.0f;
float UIStep = 0.1f;
> = 0.5f;
float fWaveAmp
<
string UIName = "Wave Amplitude";
string UIWidget = "Slider";
float UIMin = 0.0f;
float UIMax = 10.0f;
float UIStep = 0.1f;
> = 0.5f;
float fWaveFreq
<
string UIName = "Wave Frequency";
string UIWidget = "Slider";
float UIMin = 0.0f;
float UIMax = 1.0f;
float UIStep = 0.001f;
> = 0.1f;
texture tNormalMap : Normal
<
string UIName = "Normal Map";
string ResourceName = "waves2.dds";
string TextureType = "2D";
>;
texture tEnvMap : Environment
<
string UIName = "Environment Map";
string ResourceName = "CloudyHillsCubemap2.dds";
string TextureType = "Cube";
>;
sampler2D s0 = sampler_state {
Texture = (tNormalMap);
MipFilter = Linear;
MinFilter = Linear;
MagFilter = Linear;
};
samplerCUBE s1 = sampler_state {
Texture = (tEnvMap);
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};
//--------------------------------------------------
// Vertex shader
//--------------------------------------------------
struct VSOUTPUT {
float4 vPos : POSITION;
float2 vTex : TEXCOORD0;
float3 vTanToCube[3] : TEXCOORD1;
float2 vBump0 : TEXCOORD4;
float2 vBump1 : TEXCOORD5;
float2 vBump2 : TEXCOORD6;
float3 vView : TEXCOORD7;
};
// 水波
struct Wave {
float fFreq; // 频率 (2PI / 波长)
float fAmp; // 振幅
float fPhase; // 相伴 (速度 * 2PI / 波长)
float2 vDir; // 方向
};
#define NUMWAVES 3
Wave Waves[NUMWAVES] = {
{ 1.0f, 1.00f, 0.50f, float2( -1.0f, 0.0f ) },
{ 2.0f, 0.50f, 1.30f, float2( -0.7f, 0.7f ) },
{ .50f, .50f, 0.250f, float2( 0.2f, 0.1f ) },
};
float EvaluateWave( Wave w, float2 vPos, float fTime ) {
return w.fAmp * sin( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase );
}
float EvaluateWaveDifferential( Wave w, float2 vPos, float fTime ) {
return w.fAmp * w.fFreq * cos( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase );
}
float EvaluateWaveSharp( Wave w, float2 vPos, float fTime, float fK )
{
return w.fAmp * pow( sin( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase )* 0.5 + 0.5 , fK );
}
float EvaluateWaveSharpDifferential( Wave w, float2 vPos, float fTime, float fK )
{
return fK * w.fFreq * w.fAmp * pow( sin( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase )* 0.5 + 0.5 , fK - 1 ) * cos( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase );
}
VSOUTPUT VS_Water( float4 inPos : POSITION, float3 inNor : NORMAL, float2 inTex : TEXCOORD0,
float3 inTan : TANGENT, float3 inBin : BINORMAL ) {
VSOUTPUT OUT = (VSOUTPUT)0;
// 产生水波
Waves[0].fFreq = fWaveFreq;
Waves[0].fAmp = fWaveAmp;
Waves[1].fFreq = fWaveFreq * 2.0f;
Waves[1].fAmp = fWaveAmp * 0.5f;
Waves[2].fFreq = fWaveFreq * 3.0f;
Waves[2].fAmp = fWaveAmp * 1.0f;
// 水波总数
inPos.y = 0.0f;
float ddx = 0.0f, ddy = 0.0f;
for( int i = 0; i < NUMWAVES; i++ ) {
inPos.y += EvaluateWave( Waves[i], inPos.xz, fTime );
float diff = EvaluateWaveDifferential( Waves[i], inPos.xz, fTime);
ddx += diff * Waves[i].vDir.x;
ddy += diff * Waves[i].vDir.y;
}
// 输出position
OUT.vPos = mul( inPos, matWorldViewProj );
// 门生法线图texture coordinates
OUT.vTex = inTex * vTextureScale;
fTime = fmod( fTime, 100.0 );
OUT.vBump0 = inTex * vTextureScale + fTime * vBumpSpeed;
OUT.vBump1 = inTex * vTextureScale * 2.0f + fTime * vBumpSpeed * 4.0;
OUT.vBump2 = inTex * vTextureScale * 4.0f + fTime * vBumpSpeed * 8.0;
// 计算切线
float3 vB = float3( 1, ddx, 0 );
float3 vT = float3( 0, ddy, 1 );
float3 vN = float3( -ddx, 1, -ddy );
// Compute the tangent space to object space matrix
float3x3 matTangent = float3x3( fBumpHeight * normalize( vT ),
fBumpHeight * normalize( vB ),
normalize( vN ) );
OUT.vTanToCube[0] = mul( matTangent, matWorld[0].xyz );
OUT.vTanToCube[1] = mul( matTangent, matWorld[1].xyz );
OUT.vTanToCube[2] = mul( matTangent, matWorld[2].xyz );
// 计算世界空间向量
float4 vWorldPos = mul( inPos, matWorld );
OUT.vView = matViewI[3].xyz - vWorldPos;
return OUT;
}
//--------------------------------------------------
// Pixel shader
//--------------------------------------------------
float3 Refract( float3 vI, float3 vN, float fRefIndex, out bool fail )
{
float fIdotN = dot( vI, vN );
float k = 1 - fRefIndex * fRefIndex * ( 1 - fIdotN * fIdotN );
fail = k < 0;
return fRefIndex * vI - ( fRefIndex * fIdotN + sqrt(k) )* vN;
}
float4 PS_Water( VSOUTPUT IN ) : COLOR0 {
// Fetch the normal maps (with signed scaling)
float4 t0 = tex2D( s0, IN.vBump0 ) * 2.0f - 1.0f;
float4 t1 = tex2D( s0, IN.vBump1 ) * 2.0f - 1.0f;
float4 t2 = tex2D( s0, IN.vBump2 ) * 2.0f - 1.0f;
float3 vN = t0.xyz + t1.xyz + t2.xyz;
// Compute the tangent to world matrix
float3x3 matTanToWorld;
matTanToWorld[0] = IN.vTanToCube[0];
matTanToWorld[1] = IN.vTanToCube[1];
matTanToWorld[2] = IN.vTanToCube[2];
float3 vWorldNormal = mul( matTanToWorld, vN );
vWorldNormal = normalize( vWorldNormal );
// 计算反射向量
IN.vView = normalize( IN.vView );
float3 vR = reflect( -IN.vView, vWorldNormal );
// Sample the cube map
float4 vReflect = texCUBE( s1, vR.zyx );
vReflect = texCUBE( s1, vR );
// Exaggerate the HDR effect
vReflect.rgb *= ( 1.0 + vReflect.a * fHDRMultiplier );
// Compute the Fresnel term
float fFacing = 1.0 - max( dot( IN.vView, vWorldNormal ), 0 );
float fFresnel = fFresnelBias + ( 1.0 - fFresnelBias ) * pow( fFacing, fFresnelPower);
// 计算最终颜色
float4 vWaterColor = lerp( vDeepColor, vShallowColor, fFacing );
return vWaterColor * fWaterAmount + vReflect * vReflectionColor * fReflectionAmount * fFresnel;
}
//--------------------------------------------------
// Techniques
//--------------------------------------------------
technique techDefault {
pass p0 {
CullMode = None;
VertexShader = compile vs_2_0 VS_Water();
PixelShader = compile ps_2_0 PS_Water();
}
}
好了,代码出来了,下面可以看下实现后的效果。
有波浪的水面
平静的水面
这里也只算是一个比较基本的水面效果,而且出不够真实。还可以去完善一下。