3D系列4.15 圆柱形Billboarding
你可以在3.11 Billboarding:在3D世界绘制2D图像使它们总是面向相机中看到Billboarding的详细解释,那个教程介绍了球形billboarding和圆柱形billboarding ,可以在CPU上计算,也可以在GPU上计算。所以,本章只简短讨论一下理论,重点放在代码的实现上。
创建了多纹理地形和水面后,现在需要在地形上放置一些树木。
我们可以通过加载一个3D模型,在地形上绘制几百次。问题是这样做帧频会大大下降甚至低于1帧/秒。
这个问题可以通过使用billboarding加以解决,billboarding是一个使用地非常广泛并十分重要的技术。Billboarding使用一个简单的2D图像代替3D模型,在树木的例子中,我们使用一张树的2D图像代替树的3D模型,它的位置与在3D世界中的位置相同。剩下的唯一一件事就是确保图像总是面朝相机,下图解释了为什么:
左图显示了3D世界中不同位置的5张图像,右图显示了同样的5张图像,但是面朝相机。可以想象如果图像包含树木的话,右图才是我们需要的结果。你使用两个三角形,六个顶点绘制billboard。对每个billboard,六个顶点需要包含正确的纹理坐标,但只需包含相同的位置:billboard的底边中点的位置,如下图所示。这很容易指定,因为这个位置就是树干与地形的交点。
下面我们会创建一个包含位置数据的集合。这个方法以这个集合为参数,集合中的每个位置都会生成6个顶点,代码来自于3.11 Billboarding:在3D世界绘制2D图像使它们总是面向相机:
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); }
顶点数组存储在VertexBuffer中, 我们需要在代码顶部将它作为变量: VertexBuffer treeVertexBuffer; VertexDeclaration treeVertexDeclaration; 这些顶点通过顶点着色器转换到正确的位置,这样billboards会始终面朝相机。计算的HLSL代码来自于3.11 Billboarding:在3D世界绘制2D图像使它们总是面向相机。那个教程中的位置指定为billboard 的中央,而本例中我们指定为底边中点,所以我改变了顶点着色器中这行代码:
finalPosition += (1.5f-inTexCoord.y*1.5f)*upVector;
确保加载bbEffect.fx文件并添加以下变量:
Effect bbEffect;
并在LoadContent方法中加载:
bbEffect = Content.Load<Effect> ("bbEffect");
创建一个简单的方法生成一个集合,这个集合包含一些树的位置信息:
private List<Vector3> GenerateTreePositions(VertexMultitextured[] terrainVertices) { List<Vector3> treeList = new List<Vector3> (); treeList.Add(terrainVertices[3310].Position); treeList.Add(terrainVertices[3315].Position); treeList.Add(terrainVertices[3320].Position); treeList.Add(terrainVertices[3325].Position); return treeList; }
这个方法创建了一个集合,包含我在地形上随机取的4个位置。下一章我们会扩展这个方法,现在将绘制4棵树。确保在LoadVertices方法中调用这两个方法:
List<Vector3> treeList = GenerateTreePositions(terrainVertices); CreateBillboardVerticesFromList(treeList);
我们还需要导入一张树的2D图像并添加以下变量:
Texture2D treeTexture;
在LoadTextures方法中加载:
treeTexture = Content.Load<Texture2D> ("tree");
有了effect,纹理和顶点,就做好了绘制三角形的准备。下面是绘制代码:
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(); foreach (EffectPass pass in bbEffect.CurrentTechnique.Passes) { pass.Begin(); device.Vertices[0].SetSource(treeVertexBuffer, 0, VertexPositionTexture.SizeInBytes); device.VertexDeclaration = treeVertexDeclaration; int noVertices = treeVertexBuffer.SizeInBytes / VertexPositionTexture.SizeInBytes; int noTriangles = noVertices / 3; device.DrawPrimitives(PrimitiveType.TriangleList, 0, noTriangles); pass.End(); } bbEffect.End(); }
我们选择.fx文件中的圆柱形billboarding technique并设置必需的XNA-to-HLSL变量。最重要的是xAllowedRotDir变量,它指定了2D图像绕着哪根轴旋转。球形和圆柱形billboarding的不同在于:在球形billboarding中,2D图像,2D图像可以绕着所有轴旋转。而本例中我们处理的数,我们想让树绕着树干旋转,即(0,1,0) Up轴旋转。
现在在Draw method方法的最后调用这个方法:
DrawBillboards(viewMatrix);
现在运行代码,你可以看到4棵树!试着将相机在树周围移动,它们总是朝向相机的。但是你还可以看到矩形的图像,这是因为树周围的黑色边界。幸运的是,我们的树纹理包含透明信息,所以在DrawBillboards方法中使用以下代码开启alpha混合:
device.RenderState.AlphaBlendEnable = true; device.RenderState.SourceBlend = Blend.SourceAlpha; device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
这会开启常规的alpha混合。
finalColor=pixelShaderColor*pixelShaderAlpha+colorAlreadyPresent*(1-pixelshader)
别忘了在方法的最后关闭alpha混合,否则下一帧你的3D世界都会变得透明!
device.RenderState.AlphaBlendEnable = false;
运行代码截图如下:
本章展示了基本的billboarding。当你绘制上百个billboards就会显示它的威力,我们会在下一章中实现一些树林。
本章的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; Model skyDome; const float waterHeight = 5.0f; RenderTarget2D refractionRenderTarget; Texture2D refractionMap; RenderTarget2D reflectionRenderTarget; Texture2D reflectionMap; 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); 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); List<Vector3> treeList = GenerateTreePositions(terrainVertices); CreateBillboardVerticesFromList(treeList); } 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"); } 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(VertexMultitextured[] terrainVertices) { List<Vector3> treeList = new List<Vector3> (); treeList.Add(terrainVertices[3310].Position); treeList.Add(terrainVertices[3315].Position); treeList.Add(terrainVertices[3320].Position); treeList.Add(terrainVertices[3325].Position); return treeList; } 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(); 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); DrawTerrain(reflectionViewMatrix); DrawSkyDome(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); device.RenderState.AlphaBlendEnable = true; device.RenderState.SourceBlend = Blend.SourceAlpha; device.RenderState.DestinationBlend = Blend.InverseSourceAlpha; bbEffect.Begin(); foreach (EffectPass pass in bbEffect.CurrentTechnique.Passes) { pass.Begin(); device.Vertices[0].SetSource(treeVertexBuffer, 0, VertexPositionTexture.SizeInBytes); device.VertexDeclaration = treeVertexDeclaration; int noVertices = treeVertexBuffer.SizeInBytes / VertexPositionTexture.SizeInBytes; int noTriangles = noVertices / 3; device.DrawPrimitives(PrimitiveType.TriangleList, 0, noTriangles); pass.End(); } bbEffect.End(); device.RenderState.AlphaBlendEnable = false; } } }
bbEffect.fx文件的代码如下:
//------- XNA interface -------- float4x4 xView; float4x4 xProjection; float4x4 xWorld; float3 xCamPos; float3 xAllowedRotDir; //------- Texture Samplers -------- Texture xBillboardTexture; sampler textureSampler = sampler_state { texture = <xBillboardTexture> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = CLAMP; AddressV = CLAMP;}; struct BBVertexToPixel { float4 Position : POSITION; float2 TexCoord : TEXCOORD0; }; struct BBPixelToFrame { float4 Color : COLOR0; }; //------- Technique: CylBillboard -------- BBVertexToPixel CylBillboardVS(float3 inPos: POSITION0, float2 inTexCoord: TEXCOORD0) { BBVertexToPixel Output = (BBVertexToPixel)0; float3 center = mul(inPos, xWorld); float3 eyeVector = center - xCamPos; float3 upVector = xAllowedRotDir; upVector = normalize(upVector); float3 sideVector = cross(eyeVector,upVector); sideVector = normalize(sideVector); float3 finalPosition = center; finalPosition += (inTexCoord.x-0.5f)*sideVector; finalPosition += (1.5f-inTexCoord.y*1.5f)*upVector; float4 finalPosition4 = float4(finalPosition, 1); float4x4 preViewProjection = mul (xView, xProjection); Output.Position = mul(finalPosition4, preViewProjection); Output.TexCoord = inTexCoord; return Output; } BBPixelToFrame BillboardPS(BBVertexToPixel PSIn) : COLOR0 { BBPixelToFrame Output = (BBPixelToFrame)0; Output.Color = tex2D(textureSampler, PSIn.TexCoord); return Output; } technique CylBillboard { pass Pass0 { VertexShader = compile vs_1_1 CylBillboardVS(); PixelShader = compile ps_1_1 BillboardPS(); } }
发布时间:2009/12/29 上午9:15:11 阅读次数:11076