3D系列4.18 2D Perlin噪点
噪点(Noise)是游戏编程中一个非常重要的内容。Noise不同于static(干扰)。Static是一组完全随机的数据点,每个点与邻近点无关。2D static贴图的例子如下左图所示。
在噪点贴图中,每个点的值是平滑变化的。所以虽然每个点的值不同,但一个点的值可能接近于邻近点的值,噪点贴图的例子如下右图所示。
其实在这个系列中,我们已经使用过多次噪点贴图了。天空球纹理就是一个很好的例子,放置树林的贴图也是一张噪点贴图。但噪点贴图用处还有很多,例如,如何生成高度图,其实高度图就是一张平滑的噪点贴图。噪点贴图还可以用来定义地形的边界。
让我们看一下如何创建一个高度图,最后,我会编写一个HLSL effect用来生成Perlin Noise。首先使用多张static贴图,因为static贴图只包含随机值,所以很容易生成。这些static 贴图的分辨率是相同的,但是,每张纹理的细节层次依次为前者的2倍:
现在将这些图像组合在一起,你会获得以下图像:
很简单,不是吗?希望你也这样认为。这里有一个难点:高分辨率图像只基于5x5的值,但是图像的所有像素是这些值的线性插值。但是这完全没有问题,因为显卡会自动进行纹理的线性插值!而且,我们使用相同的贴图六次(但纹理坐标的缩放不同),而不是使用六张不同的贴图。
所以在XNA代码中,首先编写一个小方法生成一个低分辨率的static贴图:
private Texture2D CreateStaticMap(int resolution) { Random rand = new Random(); Color[] noisyColors = new Color[resolution * resolution]; for (int x = 0; x < resolution; x++) for (int y = 0; y < resolution; y++) noisyColors[x + y * resolution] = new Color(new Vector3((float)rand.Next(1000) / 1000.0f, 0, 0)); Texture2D noiseImage = new Texture2D(device, resolution, resolution, 1, TextureUsage.None, SurfaceFormat.Color); noiseImage.SetData(noisyColors); return noiseImage; }
只需指定分辨率,这个方法就会返回一个方块纹理,这个方块纹理的所有像素的红色通道中存储了随机值。因为我们会以HLSL effect 绘制噪点贴图,所以需要覆盖整个屏幕的两个三角形,使用TrianleList绘制两个三角形需要6个顶点,或者使用TriangleStrip需要4个顶点(可参见系列3):
private VertexPositionTexture[] SetUpFullscreenVertices() { VertexPositionTexture[] vertices = new VertexPositionTexture[4]; vertices[0] = new VertexPositionTexture(new Vector3(-1, 1, 0f), new Vector2(0, 1)); vertices[1] = new VertexPositionTexture(new Vector3(1, 1, 0f), new Vector2(1, 1)); vertices[2] = new VertexPositionTexture(new Vector3(-1, -1, 0f), new Vector2(0, 0)); vertices[3] = new VertexPositionTexture(new Vector3(1, -1, 0f), new Vector2(1, 0)); return vertices; }
我们需要一个独立的渲染目标,所以在代码顶部添加以下变量:
RenderTarget2D cloudsRenderTarget; Texture2D cloudStaticMap; VertexPositionTexture[] fullScreenVertices; VertexDeclaration fullScreenVertexDeclaration;
我们使用cloudStaticMap保存基本的static贴图。在LoadContent方法中初始化渲染目标:
cloudsRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format);
在LoadTextures 方法中生成static贴图:
cloudStaticMap = CreateStaticMap(32);
这会生成一张32x32的static贴图。如果你想要一个更小的云,需要指定更大的数字,如64。
最后,在LoadVertices方法中加载覆盖全屏的顶点和对应的VertexDeclaration:
fullScreenVertices = SetUpFullscreenVertices(); fullScreenVertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements);
初始化结束后,就可以开始编写HLSL代码了。Effect中没有什么特别的东西,顶点着色器只是简单地将纹理坐标传递到像素着色器。所以将以下代码添加到Series4Effects.fx文件的最后:
//------- Technique: PerlinNoise -------- struct PNVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; }; struct PNPixelToFrame { float4 Color : COLOR0; };
顶点着色器是最简单的,因为我们已经指定了顶点在屏幕空间中的坐标,只需将它直接传递到输出结构中即可:
PNVertexToPixel PerlinVS(float4 inPos : POSITION, float2 inTexCoords: TEXCOORD) { PNVertexToPixel Output = (PNVertexToPixel)0; Output.Position = inPos; Output.TextureCoords = inTexCoords; return Output; }
像素着色器还有点趣:
PNPixelToFrame PerlinPS(PNVertexToPixel PSIn) { PNVertexToPixel Output = (PNVertexToPixel)0; float4 perlin = tex2D(TextureSampler, (PSIn.TextureCoords))/2; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*2)/4; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*4)/8; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*8)/16; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*16)/32; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*32)/32; }
这对应上面的六张图像:你获取static图像,以不同的分辨率对它进行六次采样!因为你想让perlin值位于0和1之间,所以需要对结果进行缩放。第一行代码是最低的分辨率,影响最大:为最终结果的50%,接下来一行代码为25%,然后是12.5%直至最高分辨率。确保将所有影响相加正好为100%。
如果此时运行代码并获取输出,我们已经可以获得一个漂亮的噪点贴图了!但我们可以做得更好。假设你将这个噪点贴图作为天空的纹理,要使它移动,你只需将它沿着天空球移动就可以了。这很好,但如果从外部观察,云不仅会移动,还会改变形状,这不是你想要的结果!
这可以在像素着色器中进行编程,技巧是将不同分辨率的图像以不同的速度移动。以下是实现代码:
PixelToFrame PerlinPS(VertexToPixel PSIn) { PNVertexToPixel Output = (PNVertexToPixel)0; float2 move = float2(0,1); float4 perlin = tex2D(TextureSampler, (PSIn.TextureCoords)+xTime*move)/2; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*2+xTime*move)/4; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*4+xTime*move)/8; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*8+xTime*move)/16; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*16+xTime*move)/32; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*32+xTime*move)/32; }
根据当前时间,图像会以不同的速度滚动!对应新的xTime值会有一个新的切换效果。
显然,像素着色器缺少一个输出,所以添加以下代码:
Output.Color = 1.0f-pow(perlin, xOvercast)*2.0f; return Output;
因为perlin值在0和1之间,你可以将它进行大于1的幂计算锐化图像。xOvercast值越大,云的大小和锐化越小,但是因为你要用1减去这个值,所以结果相反:xOvercast的值越大,云越多。别忘了在代码顶部添加这个xOvercast变量:
float xOvercast;
还有technique定义:
technique PerlinNoise { pass Pass0 { VertexShader = compile vs_1_1 PerlinVS(); PixelShader = compile ps_2_0 PerlinPS(); } }
以上就是HLSL代码!
让我们看一下XNA文件,这里我们还需要编写一个简单的方法运行这个effect:
private void GeneratePerlinNoise(float time) { device.SetRenderTarget(0, cloudsRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); effect.CurrentTechnique = effect.Techniques["PerlinNoise"]; effect.Parameters["xTexture"].SetValue(cloudStaticMap); effect.Parameters["xOvercast"].SetValue(1.1f); effect.Parameters["xTime"].SetValue(time/1000.0f); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.VertexDeclaration = fullScreenVertexDeclaration; device.DrawUserPrimitives(PrimitiveType.TriangleStrip, fullScreenVertices, 0, 2); pass.End(); } effect.End(); device.SetRenderTarget(0, null); cloudMap = cloudsRenderTarget.GetTexture(); }
你激活了自定义的渲染目标并用黑色清除它。然后,选择PerlinNoise technique并传入基本static贴图和xOvercast、xTime的值,然后会在两个覆盖整个渲染目标的2个三角形,将渲染目标的内容存储在cloudMap纹理中,这样就做好了进入下一章的准备!别忘了Draw方法中调用这个方法:
GeneratePerlinNoise(time);
如果将cloudMap纹理保存到文件,可看到以下图像:
本章讨论了噪点贴图的生成,它有点长,但我想这是很重要的一章,而且在我的书中并没有提到这个内容。下一章也是本系列的最后一章会使用这张噪点贴图作为天空球的纹理。
译者注:还可以参考4.4 模拟水的方法,GPU上的柏林噪声(http://www.qilouding.cn/post/198.html)。
下面是XNA代码,红色代码为相对于上一章改变的部分:
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace XNAseries4 { public struct VertexMultitextured { public Vector3 Position; public Vector3 Normal; public Vector4 TextureCoordinate; public Vector4 TexWeights; public static int SizeInBytes = (3 + 3 + 4 + 4) * sizeof(float); 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.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ), new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ), }; } public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; GraphicsDevice device; int terrainWidth; int terrainLength; float[,] heightData; VertexBuffer terrainVertexBuffer; IndexBuffer terrainIndexBuffer; VertexDeclaration terrainVertexDeclaration; VertexBuffer waterVertexBuffer; VertexDeclaration waterVertexDeclaration; VertexBuffer treeVertexBuffer; VertexDeclaration treeVertexDeclaration; Effect effect; Effect bbEffect; Matrix viewMatrix; Matrix projectionMatrix; Matrix reflectionViewMatrix; Vector3 cameraPosition = new Vector3(130, 30, -50); float leftrightRot = MathHelper.PiOver2; float updownRot = -MathHelper.Pi / 10.0f; const float rotationSpeed = 0.3f; const float moveSpeed = 30.0f; MouseState originalMouseState; Texture2D grassTexture; Texture2D sandTexture; Texture2D rockTexture; Texture2D snowTexture; Texture2D cloudMap; Texture2D waterBumpMap; Texture2D treeTexture; Texture2D treeMap; Model skyDome; const float waterHeight = 5.0f; RenderTarget2D refractionRenderTarget; Texture2D refractionMap; RenderTarget2D reflectionRenderTarget; Texture2D reflectionMap; RenderTarget2D cloudsRenderTarget; Texture2D cloudStaticMap; VertexPositionTexture[] fullScreenVertices; VertexDeclaration fullScreenVertexDeclaration; Vector3 windDirection = new Vector3(0, 0, 1); public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { graphics.PreferredBackBufferWidth = 500; graphics.PreferredBackBufferHeight = 500; graphics.ApplyChanges(); Window.Title = "Riemer's XNA Tutorials -- Series 4"; base.Initialize(); } protected override void LoadContent() { device = GraphicsDevice; effect = Content.Load<Effect> ("Series4Effects"); bbEffect = Content.Load<Effect> ("bbEffect"); UpdateViewMatrix(); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.3f, 1000.0f); Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); originalMouseState = Mouse.GetState(); skyDome = Content.Load<Model> ("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone(device); PresentationParameters pp = device.PresentationParameters; refractionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format); reflectionRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format); cloudsRenderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format); LoadVertices(); LoadTextures(); } private void LoadVertices() { Texture2D heightMap = Content.Load<Texture2D> ("heightmap"); LoadHeightData(heightMap); VertexMultitextured[] terrainVertices = SetUpTerrainVertices(); int[] terrainIndices = SetUpTerrainIndices(); terrainVertices = CalculateNormals(terrainVertices, terrainIndices); CopyToTerrainBuffers(terrainVertices, terrainIndices); terrainVertexDeclaration = new VertexDeclaration(device, VertexMultitextured.VertexElements); SetUpWaterVertices(); waterVertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements); Texture2D treeMap = Content.Load<Texture2D> ("treeMap"); List<Vector3> treeList = GenerateTreePositions(treeMap, terrainVertices); CreateBillboardVerticesFromList(treeList); fullScreenVertices = SetUpFullscreenVertices(); fullScreenVertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements); } private void LoadTextures() { grassTexture = Content.Load<Texture2D> ("grass"); sandTexture = Content.Load<Texture2D> ("sand"); rockTexture = Content.Load<Texture2D> ("rock"); snowTexture = Content.Load<Texture2D> ("snow"); cloudMap = Content.Load<Texture2D> ("cloudMap"); waterBumpMap = Content.Load<Texture2D> ("waterbump"); treeTexture = Content.Load<Texture2D> ("tree"); treeMap = Content.Load<Texture2D> ("treeMap"); cloudStaticMap = CreateStaticMap(32); } private void LoadHeightData(Texture2D heightMap) { float minimumHeight = float.MaxValue; float maximumHeight = float.MinValue; terrainWidth = heightMap.Width; terrainLength = heightMap.Height; Color[] heightMapColors = new Color[terrainWidth * terrainLength]; heightMap.GetData(heightMapColors); heightData = new float[terrainWidth, terrainLength]; for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) { heightData[x, y] = heightMapColors[x + y * terrainWidth].R; if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y]; if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y]; } for (int x = 0; x < terrainWidth; x++) for (int y = 0; y < terrainLength; y++) heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * 30.0f; } private VertexMultitextured[] SetUpTerrainVertices() { VertexMultitextured[] terrainVertices = new VertexMultitextured[terrainWidth * terrainLength]; for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { terrainVertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y); terrainVertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / 30.0f; terrainVertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / 30.0f; terrainVertices[x + y * terrainWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 0) / 8.0f, 0, 1); terrainVertices[x + y * terrainWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 12) / 6.0f, 0, 1); terrainVertices[x + y * terrainWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 20) / 6.0f, 0, 1); terrainVertices[x + y * terrainWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - 30) / 6.0f, 0, 1); float total = terrainVertices[x + y * terrainWidth].TexWeights.X; total += terrainVertices[x + y * terrainWidth].TexWeights.Y; total += terrainVertices[x + y * terrainWidth].TexWeights.Z; total += terrainVertices[x + y * terrainWidth].TexWeights.W; terrainVertices[x + y * terrainWidth].TexWeights.X /= total; terrainVertices[x + y * terrainWidth].TexWeights.Y /= total; terrainVertices[x + y * terrainWidth].TexWeights.Z /= total; terrainVertices[x + y * terrainWidth].TexWeights.W /= total; } } return terrainVertices; } private int[] SetUpTerrainIndices() { int[] indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6]; int counter = 0; for (int y = 0; y < terrainLength - 1; y++) { for (int x = 0; x < terrainWidth - 1; x++) { int lowerLeft = x + y * terrainWidth; int lowerRight = (x + 1) + y * terrainWidth; int topLeft = x + (y + 1) * terrainWidth; int topRight = (x + 1) + (y + 1) * terrainWidth; indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; } } return indices; } private VertexMultitextured[] CalculateNormals(VertexMultitextured[] vertices, int[] indices) { for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = new Vector3(0, 0, 0); for (int i = 0; i < indices.Length / 3; i++) { int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2]; Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2); vertices[index1].Normal += normal; vertices[index2].Normal += normal; vertices[index3].Normal += normal; } for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize(); return vertices; } private void CopyToTerrainBuffers(VertexMultitextured[] vertices, int[] indices) { terrainVertexBuffer = new VertexBuffer(device, vertices.Length * VertexMultitextured.SizeInBytes, BufferUsage.WriteOnly); terrainVertexBuffer.SetData(vertices); terrainIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); terrainIndexBuffer.SetData(indices); } private void SetUpWaterVertices() { VertexPositionTexture[] waterVertices = new VertexPositionTexture[6]; waterVertices[0] = new VertexPositionTexture(new Vector3(0, waterHeight, 0), new Vector2(0, 1)); waterVertices[2] = new VertexPositionTexture(new Vector3(terrainWidth, waterHeight, -terrainLength), new Vector2(1, 0)); waterVertices[1] = new VertexPositionTexture(new Vector3(0, waterHeight, -terrainLength), new Vector2(0, 0)); waterVertices[3] = new VertexPositionTexture(new Vector3(0, waterHeight, 0), new Vector2(0, 1)); waterVertices[5] = new VertexPositionTexture(new Vector3(terrainWidth, waterHeight, 0), new Vector2(1, 1)); waterVertices[4] = new VertexPositionTexture(new Vector3(terrainWidth, waterHeight, -terrainLength), new Vector2(1, 0)); waterVertexBuffer = new VertexBuffer(device, waterVertices.Length * VertexPositionTexture.SizeInBytes, BufferUsage.WriteOnly); waterVertexBuffer.SetData(waterVertices); } private void CreateBillboardVerticesFromList(List<Vector3> treeList) { VertexPositionTexture[] billboardVertices = new VertexPositionTexture[treeList.Count * 6]; int i = 0; foreach (Vector3 currentV3 in treeList) { billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(0, 0)); billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(1, 0)); billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(1, 1)); billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(0, 0)); billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(1, 1)); billboardVertices[i++] = new VertexPositionTexture(currentV3, new Vector2(0, 1)); } treeVertexBuffer = new VertexBuffer(device, billboardVertices.Length * VertexPositionTexture.SizeInBytes, BufferUsage.WriteOnly); treeVertexBuffer.SetData(billboardVertices); treeVertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements); } private List<Vector3> GenerateTreePositions(Texture2D treeMap, VertexMultitextured[] terrainVertices) { Color[] treeMapColors = new Color[treeMap.Width * treeMap.Height]; treeMap.GetData(treeMapColors); int[,] noiseData = new int[treeMap.Width, treeMap.Height]; for (int x = 0; x < treeMap.Width; x++) for (int y = 0; y < treeMap.Height; y++) noiseData[x, y] = treeMapColors[y + x * treeMap.Height].R; List<Vector3> treeList = new List<Vector3> (); Random random = new Random(); for (int x = 0; x < terrainWidth; x++) { for (int y = 0; y < terrainLength; y++) { float terrainHeight = heightData[x, y]; if ((terrainHeight > 8) && (terrainHeight < 14)) { float flatness = Vector3.Dot(terrainVertices[x + y * terrainWidth].Normal, new Vector3(0, 1, 0)); float minFlatness = (float)Math.Cos(MathHelper.ToRadians(15)); if (flatness > minFlatness) { float relx = (float)x / (float)terrainWidth; float rely = (float)y / (float)terrainLength; float noiseValueAtCurrentPosition = noiseData[(int)(relx * treeMap.Width), (int)(rely * treeMap.Height)]; float treeDensity; if (noiseValueAtCurrentPosition > 200) treeDensity = 5; else if (noiseValueAtCurrentPosition > 150) treeDensity = 4; else if (noiseValueAtCurrentPosition > 100) treeDensity = 3; else treeDensity = 0; for (int currDetail = 0; currDetail < treeDensity; currDetail++) { float rand1 = (float)random.Next(1000) / 1000.0f; float rand2 = (float)random.Next(1000) / 1000.0f; Vector3 treePos = new Vector3((float)x - rand1, 0, -(float)y - rand2); treePos.Y = heightData[x, y]; treeList.Add(treePos); } } } } } return treeList; } private Texture2D CreateStaticMap(int resolution) { Random rand = new Random(); Color[] noisyColors = new Color[resolution * resolution]; for (int x = 0; x < resolution; x++) for (int y = 0; y < resolution; y++) noisyColors[x + y * resolution] = new Color(new Vector3((float)rand.Next(1000) / 1000.0f, 0, 0)); Texture2D noiseImage = new Texture2D(device, resolution, resolution, 1, TextureUsage.None, SurfaceFormat.Color); noiseImage.SetData(noisyColors); return noiseImage; } private VertexPositionTexture[] SetUpFullscreenVertices() { VertexPositionTexture[] vertices = new VertexPositionTexture[4]; vertices[0] = new VertexPositionTexture(new Vector3(-1, 1, 0f), new Vector2(0, 1)); vertices[1] = new VertexPositionTexture(new Vector3(1, 1, 0f), new Vector2(1, 1)); vertices[2] = new VertexPositionTexture(new Vector3(-1, -1, 0f), new Vector2(0, 0)); vertices[3] = new VertexPositionTexture(new Vector3(1, -1, 0f), new Vector2(1, 0)); return vertices; } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(timeDifference); base.Update(gameTime); } private void ProcessInput(float amount) { MouseState currentMouseState = Mouse.GetState(); if (currentMouseState != originalMouseState) { float xDifference = currentMouseState.X - originalMouseState.X; float yDifference = currentMouseState.Y - originalMouseState.Y; leftrightRot -= rotationSpeed * xDifference * amount; updownRot -= rotationSpeed * yDifference * amount; Mouse.SetPosition(device.Viewport.Width / 2, device.Viewport.Height / 2); UpdateViewMatrix(); } Vector3 moveVector = new Vector3(0, 0, 0); KeyboardState keyState = Keyboard.GetState(); if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W)) moveVector += new Vector3(0, 0, -1); if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S)) moveVector += new Vector3(0, 0, 1); if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D)) moveVector += new Vector3(1, 0, 0); if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A)) moveVector += new Vector3(-1, 0, 0); if (keyState.IsKeyDown(Keys.Q)) moveVector += new Vector3(0, 1, 0); if (keyState.IsKeyDown(Keys.Z)) moveVector += new Vector3(0, -1, 0); AddToCameraPosition(moveVector * amount); } private void AddToCameraPosition(Vector3 vectorToAdd) { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation); cameraPosition += moveSpeed * rotatedVector; UpdateViewMatrix(); } private void UpdateViewMatrix() { Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot); Vector3 cameraOriginalTarget = new Vector3(0, 0, -1); Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0); Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation); Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget; Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation); viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); Vector3 reflCameraPosition = cameraPosition; reflCameraPosition.Y = -cameraPosition.Y + waterHeight * 2; Vector3 reflTargetPos = cameraFinalTarget; reflTargetPos.Y = -cameraFinalTarget.Y + waterHeight * 2; Vector3 cameraRight = Vector3.Transform(new Vector3(1, 0, 0), cameraRotation); Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition); reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector); } protected override void Draw(GameTime gameTime) { float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f; DrawRefractionMap(); DrawReflectionMap(); GeneratePerlinNoise(time); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1.0f, 0); DrawSkyDome(viewMatrix); DrawTerrain(viewMatrix); DrawWater(time); DrawBillboards(viewMatrix); base.Draw(gameTime); } private void DrawTerrain(Matrix currentViewMatrix) { effect.CurrentTechnique = effect.Techniques["MultiTextured"]; effect.Parameters["xTexture0"].SetValue(sandTexture); effect.Parameters["xTexture1"].SetValue(grassTexture); effect.Parameters["xTexture2"].SetValue(rockTexture); effect.Parameters["xTexture3"].SetValue(snowTexture); Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(currentViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xEnableLighting"].SetValue(true); effect.Parameters["xAmbient"].SetValue(0.4f); effect.Parameters["xLightDirection"].SetValue(new Vector3(-0.5f, -1, -0.5f)); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.Vertices[0].SetSource(terrainVertexBuffer, 0, VertexMultitextured.SizeInBytes); device.Indices = terrainIndexBuffer; device.VertexDeclaration = terrainVertexDeclaration; int noVertices = terrainVertexBuffer.SizeInBytes / VertexMultitextured.SizeInBytes; int noTriangles = terrainIndexBuffer.SizeInBytes / sizeof(int) / 3; device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, noVertices, 0, noTriangles); pass.End(); } effect.End(); } private void DrawSkyDome(Matrix currentViewMatrix) { device.RenderState.DepthBufferWriteEnable = false; Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count]; skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms); Matrix wMatrix = Matrix.CreateTranslation(0, -0.3f, 0) * Matrix.CreateScale(100) * Matrix.CreateTranslation(cameraPosition); foreach (ModelMesh mesh in skyDome.Meshes) { foreach (Effect currentEffect in mesh.Effects) { Matrix worldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix; currentEffect.CurrentTechnique = currentEffect.Techniques["Textured"]; currentEffect.Parameters["xWorld"].SetValue(worldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } device.RenderState.DepthBufferWriteEnable = true; } private Plane CreatePlane(float height, Vector3 planeNormalDirection, Matrix currentViewMatrix, bool clipSide) { planeNormalDirection.Normalize(); Vector4 planeCoeffs = new Vector4(planeNormalDirection, height); if (clipSide) planeCoeffs *= -1; Matrix worldViewProjection = currentViewMatrix * 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() { Plane refractionPlane = CreatePlane(waterHeight + 1.5f, new Vector3(0, -1, 0), viewMatrix, false); device.ClipPlanes[0].Plane = refractionPlane; device.ClipPlanes[0].IsEnabled = true; device.SetRenderTarget(0, refractionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawTerrain(viewMatrix); device.ClipPlanes[0].IsEnabled = false; device.SetRenderTarget(0, null); refractionMap = refractionRenderTarget.GetTexture(); } private void DrawReflectionMap() { Plane reflectionPlane = CreatePlane(waterHeight - 0.5f, new Vector3(0, -1, 0), reflectionViewMatrix, true); device.ClipPlanes[0].Plane = reflectionPlane; device.ClipPlanes[0].IsEnabled = true; device.SetRenderTarget(0, reflectionRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(reflectionViewMatrix); DrawTerrain(reflectionViewMatrix); DrawBillboards(reflectionViewMatrix); device.ClipPlanes[0].IsEnabled = false; device.SetRenderTarget(0, null); reflectionMap = reflectionRenderTarget.GetTexture(); } private void DrawWater(float time) { effect.CurrentTechnique = effect.Techniques["Water"]; Matrix worldMatrix = Matrix.Identity; effect.Parameters["xWorld"].SetValue(worldMatrix); effect.Parameters["xView"].SetValue(viewMatrix); effect.Parameters["xReflectionView"].SetValue(reflectionViewMatrix); effect.Parameters["xProjection"].SetValue(projectionMatrix); effect.Parameters["xReflectionMap"].SetValue(reflectionMap); effect.Parameters["xRefractionMap"].SetValue(refractionMap); effect.Parameters["xWaterBumpMap"].SetValue(waterBumpMap); effect.Parameters["xWaveLength"].SetValue(0.1f); effect.Parameters["xWaveHeight"].SetValue(0.3f); effect.Parameters["xCamPos"].SetValue(cameraPosition); effect.Parameters["xTime"].SetValue(time); effect.Parameters["xWindForce"].SetValue(0.002f); effect.Parameters["xWindDirection"].SetValue(windDirection); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.Vertices[0].SetSource(waterVertexBuffer, 0, VertexPositionTexture.SizeInBytes); device.VertexDeclaration = waterVertexDeclaration; int noVertices = waterVertexBuffer.SizeInBytes / VertexPositionTexture.SizeInBytes; device.DrawPrimitives(PrimitiveType.TriangleList, 0, noVertices / 3); pass.End(); } effect.End(); } private void DrawBillboards(Matrix currentViewMatrix) { bbEffect.CurrentTechnique = bbEffect.Techniques["CylBillboard"]; bbEffect.Parameters["xWorld"].SetValue(Matrix.Identity); bbEffect.Parameters["xView"].SetValue(currentViewMatrix); bbEffect.Parameters["xProjection"].SetValue(projectionMatrix); bbEffect.Parameters["xCamPos"].SetValue(cameraPosition); bbEffect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0, 1, 0)); bbEffect.Parameters["xBillboardTexture"].SetValue(treeTexture); bbEffect.Begin(); device.Vertices[0].SetSource(treeVertexBuffer, 0, VertexPositionTexture.SizeInBytes); device.VertexDeclaration = treeVertexDeclaration; int noVertices = treeVertexBuffer.SizeInBytes / VertexPositionTexture.SizeInBytes; int noTriangles = noVertices / 3; { device.RenderState.AlphaTestEnable = true; device.RenderState.AlphaFunction = CompareFunction.GreaterEqual; device.RenderState.ReferenceAlpha = 200; bbEffect.CurrentTechnique.Passes[0].Begin(); device.DrawPrimitives(PrimitiveType.TriangleList, 0, noTriangles); bbEffect.CurrentTechnique.Passes[0].End(); } { device.RenderState.DepthBufferWriteEnable = false; device.RenderState.AlphaBlendEnable = true; device.RenderState.SourceBlend = Blend.SourceAlpha; device.RenderState.DestinationBlend = Blend.InverseSourceAlpha; device.RenderState.AlphaTestEnable = true; device.RenderState.AlphaFunction = CompareFunction.Less; device.RenderState.ReferenceAlpha = 200; bbEffect.CurrentTechnique.Passes[0].Begin(); device.DrawPrimitives(PrimitiveType.TriangleList, 0, noTriangles); bbEffect.CurrentTechnique.Passes[0].End(); } device.RenderState.AlphaBlendEnable = false; device.RenderState.DepthBufferWriteEnable = true; device.RenderState.AlphaTestEnable = false; bbEffect.End(); } private void GeneratePerlinNoise(float time) { device.SetRenderTarget(0, cloudsRenderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); effect.CurrentTechnique = effect.Techniques["PerlinNoise"]; effect.Parameters["xTexture"].SetValue(cloudStaticMap); effect.Parameters["xOvercast"].SetValue(1.1f); effect.Parameters["xTime"].SetValue(time/1000.0f); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.VertexDeclaration = fullScreenVertexDeclaration; device.DrawUserPrimitives(PrimitiveType.TriangleStrip, fullScreenVertices, 0, 2); pass.End(); } effect.End(); device.SetRenderTarget(0, null); cloudMap = cloudsRenderTarget.GetTexture(); } } }
Series4Effect.fx文件:
//---------------------------------------------------- //-- -- //-- www.riemers.net -- //-- Series 4: Advanced terrain -- //-- Shader code -- //-- -- //---------------------------------------------------- //------- Constants -------- float4x4 xView; float4x4 xReflectionView; float4x4 xProjection; float4x4 xWorld; float3 xLightDirection; float xAmbient; bool xEnableLighting; float xWaveLength; float xWaveHeight; float3 xCamPos; float xTime; float xWindForce; float3 xWindDirection; float xOvercast; //------- Texture Samplers -------- Texture xTexture; sampler TextureSampler = sampler_state { texture = <xTexture> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};Texture xTexture0; sampler TextureSampler0 = sampler_state { texture = <xTexture0> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = wrap; AddressV = wrap;};Texture xTexture1; sampler TextureSampler1 = sampler_state { texture = <xTexture1> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = wrap; AddressV = wrap;};Texture xTexture2; sampler TextureSampler2 = sampler_state { texture = <xTexture2> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};Texture xTexture3; sampler TextureSampler3 = sampler_state { texture = <xTexture3> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};Texture xReflectionMap; sampler ReflectionSampler = sampler_state { texture = <xReflectionMap> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};Texture xRefractionMap; sampler RefractionSampler = sampler_state { texture = <xRefractionMap> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};Texture xWaterBumpMap; sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; //------- Technique: Textured -------- struct TVertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float LightingFactor: TEXCOORD0; float2 TextureCoords: TEXCOORD1; }; struct TPixelToFrame { float4 Color : COLOR0; }; TVertexToPixel TexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0) { TVertexToPixel Output = (TVertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.TextureCoords = inTexCoords; float3 Normal = normalize(mul(normalize(inNormal), xWorld)); Output.LightingFactor = 1; if (xEnableLighting) Output.LightingFactor = saturate(dot(Normal, -xLightDirection)); return Output; } TPixelToFrame TexturedPS(TVertexToPixel PSIn) { TPixelToFrame Output = (TPixelToFrame)0; Output.Color = tex2D(TextureSampler, PSIn.TextureCoords); Output.Color.rgb *= saturate(PSIn.LightingFactor + xAmbient); return Output; } technique Textured_2_0 { pass Pass0 { VertexShader = compile vs_2_0 TexturedVS(); PixelShader = compile ps_2_0 TexturedPS(); } } technique Textured { pass Pass0 { VertexShader = compile vs_1_1 TexturedVS(); PixelShader = compile ps_1_1 TexturedPS(); } } //------- Technique: Multitextured -------- struct MTVertexToPixel { float4 Position : POSITION; float4 Color : COLOR0; float3 Normal : TEXCOORD0; float2 TextureCoords : TEXCOORD1; float4 LightDirection : TEXCOORD2; float4 TextureWeights : TEXCOORD3; float Depth : TEXCOORD4; }; struct MTPixelToFrame { float4 Color : COLOR0; }; MTVertexToPixel MultiTexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float2 inTexCoords: TEXCOORD0, float4 inTexWeights: TEXCOORD1) { MTVertexToPixel Output = (MTVertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.Normal = mul(normalize(inNormal), xWorld); Output.TextureCoords = inTexCoords; Output.LightDirection.xyz = -xLightDirection; Output.LightDirection.w = 1; Output.TextureWeights = inTexWeights; Output.Depth = Output.Position.z/Output.Position.w; return Output; } MTPixelToFrame MultiTexturedPS(MTVertexToPixel PSIn) { MTPixelToFrame Output = (MTPixelToFrame)0; float lightingFactor = 1; if (xEnableLighting) lightingFactor = saturate(saturate(dot(PSIn.Normal, PSIn.LightDirection)) + xAmbient); float blendDistance = 0.99f; float blendWidth = 0.005f; float blendFactor = clamp((PSIn.Depth-blendDistance)/blendWidth, 0, 1); float4 farColor; farColor = tex2D(TextureSampler0, PSIn.TextureCoords)*PSIn.TextureWeights.x; farColor += tex2D(TextureSampler1, PSIn.TextureCoords)*PSIn.TextureWeights.y; farColor += tex2D(TextureSampler2, PSIn.TextureCoords)*PSIn.TextureWeights.z; farColor += tex2D(TextureSampler3, PSIn.TextureCoords)*PSIn.TextureWeights.w; float4 nearColor; float2 nearTextureCoords = PSIn.TextureCoords*3; nearColor = tex2D(TextureSampler0, nearTextureCoords)*PSIn.TextureWeights.x; nearColor += tex2D(TextureSampler1, nearTextureCoords)*PSIn.TextureWeights.y; nearColor += tex2D(TextureSampler2, nearTextureCoords)*PSIn.TextureWeights.z; nearColor += tex2D(TextureSampler3, nearTextureCoords)*PSIn.TextureWeights.w; Output.Color = lerp(nearColor, farColor, blendFactor); Output.Color *= lightingFactor; return Output; } technique MultiTextured { pass Pass0 { VertexShader = compile vs_1_1 MultiTexturedVS(); PixelShader = compile ps_2_0 MultiTexturedPS(); } } //------- Technique: Water -------- struct WVertexToPixel { float4 Position : POSITION; float4 ReflectionMapSamplingPos : TEXCOORD1; float2 BumpMapSamplingPos : TEXCOORD2; float4 RefractionMapSamplingPos : TEXCOORD3; float4 Position3D : TEXCOORD4; }; struct WPixelToFrame { float4 Color : COLOR0; }; WVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex: TEXCOORD) { WVertexToPixel Output = (WVertexToPixel)0; float4x4 preViewProjection = mul (xView, xProjection); float4x4 preWorldViewProjection = mul (xWorld, preViewProjection); float4x4 preReflectionViewProjection = mul (xReflectionView, xProjection); float4x4 preWorldReflectionViewProjection = mul (xWorld, preReflectionViewProjection); Output.Position = mul(inPos, preWorldViewProjection); Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection); Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection); Output.Position3D = mul(inPos, xWorld); float3 windDir = normalize(xWindDirection); float3 perpDir = cross(xWindDirection, float3(0,1,0)); float ydot = dot(inTex, xWindDirection.xz); float xdot = dot(inTex, perpDir.xz); float2 moveVector = float2(xdot, ydot); moveVector.y += xTime*xWindForce; Output.BumpMapSamplingPos = moveVector/xWaveLength; return Output; } WPixelToFrame WaterPS(WVertexToPixel PSIn) { WPixelToFrame Output = (WPixelToFrame)0; float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos); float2 perturbation = xWaveHeight*(bumpColor.rg - 0.5f)*2.0f; float2 ProjectedTexCoords; ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f; ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f; float2 perturbatedTexCoords = ProjectedTexCoords + perturbation; float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords); float2 ProjectedRefrTexCoords; ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f; ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f; float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation; float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords); float3 eyeVector = normalize(xCamPos - PSIn.Position3D); 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); Output.Color = lerp(combinedColor, dullColor, 0.2f); float3 reflectionVector = -reflect(xLightDirection, normalVector); float specular = dot(normalize(reflectionVector), normalize(eyeVector)); specular = pow(specular, 256); Output.Color.rgb += specular; return Output; } technique Water { pass Pass0 { VertexShader = compile vs_1_1 WaterVS(); PixelShader = compile ps_2_0 WaterPS(); } } //------- Technique: PerlinNoise -------- struct PNVertexToPixel { float4 Position : POSITION; float2 TextureCoords : TEXCOORD0; }; struct PNPixelToFrame { float4 Color : COLOR0; }; PNVertexToPixel PerlinVS(float4 inPos : POSITION, float2 inTexCoords: TEXCOORD) { PNVertexToPixel Output = (PNVertexToPixel)0; Output.Position = inPos; Output.TextureCoords = inTexCoords; return Output; } PNPixelToFrame PerlinPS(PNVertexToPixel PSIn) { PNPixelToFrame Output = (PNPixelToFrame)0; float2 move = float2(0,1); float4 perlin = tex2D(TextureSampler, (PSIn.TextureCoords)+xTime*move)/2; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*2+xTime*move)/4; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*4+xTime*move)/8; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*8+xTime*move)/16; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*16+xTime*move)/32; perlin += tex2D(TextureSampler, (PSIn.TextureCoords)*32+xTime*move)/32; Output.Color.rgb = 1.0f-pow(perlin.r, xOvercast)*2.0f; Output.Color.a =1; return Output; } technique PerlinNoise { pass Pass0 { VertexShader = compile vs_1_1 PerlinVS(); PixelShader = compile ps_2_0 PerlinPS(); } }
发布时间:2009/12/29 上午10:06:16 阅读次数:7328