用Managed DirectX 9和C#实现3D地形可视化

来源:互联网 发布:上海金融大数据公司 编辑:程序博客网 时间:2024/05/29 11:45


介绍:
GIS(Geographical Information System) 是一种通过计算机系统实现地图显示的系统。可以让用户进入,显示并分析地理数据。由于大多数人不了解GIS,所以经常在IT市场上有几家大企业垄断该行业比如Intergraph, Bentley, MapInfo, Autodesk 和 ESRI,如今全球IT垄断企业微软,Google 和 Oracle 正在竞争建立一个虚拟地球的系统项目,Google Earth和Oracle Spatial。NASA也出了一个新版本,开源的GIS浏览软件叫做World Wind。
在这里,我将讲一下怎样建立一个独立的3D地形显示工具,该工具将使用C#和Managed DirectX 9.0c开发。该程序允许用户旋转视角并可以切换三种刷新模式:点,网格,平滑表面。
背景:
我最近刚刚完成一个城市级别的GIS系统。在该项目中我开发了一套软件思路,为了展现这种技术,使用在空中拍摄的一些照片作为地形的纹理。目的是和所有用.NET开发GIS的开发者分享经验。
运行条件:
开始前,我先所明该程序的运行条件:
Visual Studio .NET IDE (我用的是2005 beta 2)
Managed DirectX 9.0c SDK (我用的 August 2005 update)
.NET framework (我用的 v2.0 但在 v1.1下会运行的更好)
3D刷新方法:
首先先讲一下一般3D程序的设计思路。很抱歉,很多书写这个可是我不能给一个完整的解释,也不能对每行代码加注释,但是可以讲一下思路。
为了显示任何3D地形模型,需要一些地形的网格数据,这些数据包含一些由X,Y,Z标记的坐标点,非常重要的是Z值的存储方式,在 DirectX用的左手坐标系(想学习更多的坐标系统,可以在网上搜一下)。我定了一个网格的范围79x88,这就是我地形数据的存储方式,你也可以任意改变。同样我的数据使用20M的解决方案,这意味着实际相邻两点的距离是20M。
一旦你导入所有的地形坐标点,你将生成一个地形的模型。该模型是由一系列三角形组成。所以,很少的显卡的渲染工作由程序完成。这里有很有效的运算法,比如ROAM 或 PLOD(后来封装在DirectX 9里),这些和其它熟悉的算法是用来解决由距离决定三角面渲染数的问题。另一种办法是减少细节密度低的三角面数量,我们保留细节密度高的部分的三角面数量。我不会在这里用到这些算法,但你要了解并知道它们的用途。
最后,纹理是用来提供更加真实的效果。纹理使用自身的坐标系统,是顶靠左的0,0和底靠右的1,1。任何纹理点在该范围内(0,0--1,1)。
作为一个练习,可以进一步加深细节,比如加上天空盒,灯光,阴影和物理效果如碰撞检测。Managed DirectX 也包含DirectPlay 和 DirectSound支持并先进的网络功能和声音API,展开你的想象力,天空是无限的。
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectInput;
然后,我们声明网格的长度和宽度,显示和键盘设备,顶点缓冲和索引缓冲,纹理,顶点和三角面结构和一些其它变量。
private int GRID_WIDTH = 79;     // grid width
private int GRID_HEIGHT = 88;    // grid height
private Microsoft.DirectX.Direct3D.Device device = null;  // device object
private Microsoft.DirectX.DirectInput.Device keyb = null; // keyboard
private float angleZ = 0f;       // POV Z
private float angleX = 0f;       // POV X
private float[,] heightData;     // array storing our height data
private int[] indices;           // indices array
private IndexBuffer ib = null;
private VertexBuffer vb = null;
private Texture tex = null;
//Points (Vertices)
public struct dVertex
{
  public float x;
  public float y;
  public float z;
}
//Created Triangles, vv# are the vertex pointers
public struct dTriangle
{
  public long vv0;
  public long vv1;
  public long vv2;
}
private System.ComponentModel.Container components = null;
现在准备加载 Direct3D设备对象:
// define parameters for our Device object
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.EnableAutoDepthStencil = true;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
// declare the Device object
device = new Microsoft.DirectX.Direct3D.Device(0,
             Microsoft.DirectX.Direct3D.DeviceType.Hardware, this,
             CreateFlags.SoftwareVertexProcessing, presentParams);
device.RenderState.FillMode = FillMode.Solid;
device.RenderState.CullMode = Cull.None;
// Hook the device reset event
device.DeviceReset += new EventHandler(this.OnDeviceReset);
this.OnDeviceReset(device, null);
this.Resize += new EventHandler(this.OnResize);
你看到我调用OnDeviceReset,在用户每次重新改变窗口大小时调用。我们的顶点数据保存在顶点缓冲。
// create VertexBuffer to store the points
vb = new VertexBuffer(typeof(CustomVertex.PositionTextured),
         GRID_WIDTH * GRID_HEIGHT, device, Usage.Dynamic | Usage.WriteOnly,
         CustomVertex.PositionTextured.Format, Pool.Default);
vb.Created += new EventHandler(this.OnVertexBufferCreate);
OnVertexBufferCreate(vb, null);
然后我们需要建立索引缓冲,将所有三角面数据保存在里面,索引缓冲通过索引访问顶点数据:
ib = new IndexBuffer(typeof(int), (GRID_WIDTH - 1) *
     (GRID_HEIGHT - 1) * 6, device, Usage.WriteOnly, Pool.Default);
ib.Created += new EventHandler(this.OnIndexBufferCreate);
OnIndexBufferCreate(ib, null);

Also pay attention to InitialiseIndices() and LoadHeightData() functions in the source code attached, where we load and "triangulate" our data. Next, we initialise the keyboard device:
要注意加载InitialiseIndices()和LoadHeightData()函数,用来三角话数据,最后加载键盘设备:
public void InitialiseKeyboard()
{
  keyb = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Keyboard);
  keyb.SetCooperativeLevel(this, CooperativeLevelFlags.Background |
                           CooperativeLevelFlags.NonExclusive);
  keyb.Acquire();
}
然后放置相机:
private void CameraPositioning()
{
  device.Transform.Projection =
     Matrix.PerspectiveFovLH((float)Math.PI/4,  
     this.Width/this.Height, 1f, 350f);
  device.Transform.View =
     Matrix.LookAtLH(new Vector3(0, -70, -35), new Vector3(0, -5, 0),
     new Vector3(0, 1, 0));
  device.RenderState.Lighting = false;
  device.RenderState.CullMode = Cull.None;
}
快完了,还需一小步,在OnPaint中添加刷新代码:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
  device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.LightBlue , 1.0f, 0);
  // set the camera position
  CameraPositioning();
  // draw the scene    
  device.BeginScene();
  device.SetTexture(0, tex);
  device.VertexFormat = CustomVertex.PositionTextured.Format;
  device.SetStreamSource(0, vb, 0);
  device.Indices = ib;
  device.Transform.World =
         Matrix.Translation(-GRID_WIDTH/2, -GRID_HEIGHT/2, 0) *  
         Matrix.RotationZ(angleZ)*Matrix.RotationX(angleX);
  device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, GRID_WIDTH *
                               GRID_HEIGHT, 0, indices.Length/3);
  device.EndScene();
  device.Present();
  this.Invalidate();
  ReadKeyboard();
}
最后,编写主进程:
static void Main()
{
  using (WinForm directx_form = new WinForm())
  {
    directx_form.LoadHeightData();
    directx_form.InitialiseIndices();
    directx_form.InitialiseDevice();
    directx_form.InitialiseKeyboard();
    directx_form.CameraPositioning();
    directx_form.Show();
    Application.Run(directx_form);
  }
}
现在编译运行。你将看到一个渲染好的地形。使用箭头控制方向,用P,W和S切换渲染模式:点,网线和平滑。
几点说明:
你需要关注一下3D渲染的资源消耗问题,以至于一个小小的细节和特效导致性能下降而带来几天的痛苦时光。但是一旦你把问题解决将得到欣慰。我建议使用网络多搜索这类问题的解决办法。有些人已经解决了你要做的事,而且会更好的建立了文档,如果你幸运的话,还会得到一个一步步的教程,和原代码,这些将帮助你解决问题。 

原创粉丝点击