3D系列4.6 天空球
在开始绘制水面前,让我们首先绘制一些水面上反射的东西。我们已经有了一个漂亮的地形,现在需要在黑色天空中添加一些东西。我们已经在系列2中使用了一个天空盒,它工作得非常好。但是,下面的章节我们要创建云贴图。但使用云贴图覆盖天空盒几乎是不可能的,这样的话六个面之间的边界就会可见,云贴图在边界上会终止,导致我们可以看到地形周围的盒子。
解决方法是使用天空球,它是一个包围地形的球,所以只需要一张纹理覆盖在球面上。但有个计算量很大的工作:计算正确的纹理坐标。墨卡托(译者注:Gerardus Mercator (1512-1594) –是法兰德斯的地理学家、数学家和地图绘制员,他于1538年绘制了他的第一幅世界地图。第一张以他的名字命名的投影地图出现于1569年,它比其他航海用的世界投影地图更受欢迎。) 用了几乎一生的时间解决这个复杂的问题,所以我想这已经超出了本章的范围了。
我使用天空球解决这个问题:它是球的顶部。我已经创建了一个叫做dome.x的模型,下图是模型的框架。
我们只需简单地将天空纹理放置在球顶部。所以添加以下变量:
Texture2D cloudMap; Model skyDome;
注意我们没有使用纹理数组,因为模型只有一张纹理。在LoadContent方法中加载模型:
skyDome = Content.Load<Model> ("dome"); skyDome.Meshes[0].MeshParts[0].Effect = effect.Clone(device);
我们从文件加载模型,使用自己的effect替换模型自带的。
还要在LoadTextures方法中加载天空纹理:
cloudMap = Content.Load<Texture2D> ("cloudMap");
让我们看一下Draw方法。我创建了一个新方法用来绘制天空球,它就是一个标准的带纹理的模型的绘制过程。制作模型时的大小为-1到+1,所以需要放大100倍避免近裁平面出现问题。
这里我们使用了和天空盒一样的两个技巧:我们需要重新根据相机位置定位天空球,我们需要关闭Z缓冲的写入。
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["SkyDome"]; currentEffect.Parameters["xWorld"].SetValue(worldMatrix); currentEffect.Parameters["xView"].SetValue(currentViewMatrix); currentEffect.Parameters["xProjection"].SetValue(projectionMatrix); currentEffect.Parameters["xTexture0"].SetValue(cloudMap); currentEffect.Parameters["xEnableLighting"].SetValue(false); } mesh.Draw(); } device.RenderState.DepthBufferWriteEnable = true; }
除了将天空球放大并根据相机移动位置,我们还要将它稍微向下移动一点,这样它的边缘就在相机之下。这会确保相机视景体的最大部分也会被天空球覆盖。然后,传入天空球纹理并使用SkyDome technique进行绘制。
别忘了在Draw 方法中调用这个方法。但是,和天空盒一样,你要确保在其他对象之前绘制天空球:
DrawSkyDome(viewMatrix);
运行代码结果如下:
下面是代码,红色为相对于上一章改动的部分:
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; Effect effect; Matrix viewMatrix; Matrix projectionMatrix; 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; Model skyDome; 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"); 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); 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); } 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"); } 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); } 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); } protected override void Draw(GameTime gameTime) { float time = (float)gameTime.TotalGameTime.TotalMilliseconds / 100.0f; device.RenderState.CullMode = CullMode.None; device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawSkyDome(viewMatrix); DrawTerrain(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; } } }
发布时间:2009/12/14 上午8:14:08 阅读次数:14054