为地图加装游戏引擎

地图绘制并不是一个神秘的存在。一个地图组件,无论它被宣称有多强大,性能有多可怕,我们都不应该被它绚丽的文案迷惑得眼花缭乱,而不去尝试了解它的本质组成。

计算机地图,本质是属于计算机图形学和显示技术的结合。而这两方面都有很大的想象空间。

其一,数据可以用更多形式去组织

之前的方式是设计Geometry和Feature等等概念来描述位置和地物。现在还诞生了更多的模型概念,比如:

用不同层级规格的四边形网格,并用不同的网格编码作为唯一标志描述地理范围;

用规则六边形剖分地理范围,利用其六个边的方向性,进行战场兵棋推演或者灾害态势预测等等更多的数据组织方式。

其二,显示可在各种介质以各种技术进行

在显示介质上,我们现在是以矩形电脑显示器做显示。现在还有一些圆形显示器,或者类似于像土豆这样的不规则表面显示(想象一种逼近地球真实形状的电子地球仪产品)

在显示技术上,例如我们上文使用的GDI+传统方式,还有如今流行的硬件加速等显示技术。

作为GIS系统的开发,不仅需需要紧跟技术潮流的发展,也更需要一种天马行空的想象,一种化人长为我有的灵气。

所以一次偶然的机会,有了一个这样的想法,便开始琢磨它的可能性--把游戏引擎搬到GIS技术的台面上来

来看下边这个为写本章,起早贪黑做的介绍。。

第一次做MG动画,感谢不吝支持

DirectX如何绘图

DirectX是包括Unity3D等很多Windows游戏引擎的底层渲染库。在调用层面,DirectX提供了很多基本的,如点线面的绘图函数;渐变、粒子效果等特效函数。

部分特效函数

图像特效(素材图来自msdn网站)

我们这里主要关注点线面的绘图,使用DirecX绘图,主要经过以下流程:

  1. 初始化设备(Device),实现最终到物理显卡的连接。

  2. 初始化设备上下文(DeviceContext),获得重要的绘图接口。

  3. 初始化交换链(SwapChain),将图像"交换"到帧缓冲区。

使用DirectX的地图控件

需要知道的是,DirectX的原生语言是C,不是C#。所以我们需要开发的新地图控件DirectxMapControl,采用了一种对DirectX的C#包装组件SharpDX。

直接引用它的dll库,不需要配置就能获得系统DirectX组件的引用。

DirectxMapControl这个用户控件和上篇的MapControl写法母胎一致,但这里对DirectX的管理:初始化、释放是必要的。

 1//对重名的对象的重新using,避免混淆 
 2using D2D1Device = SharpDX.Direct2D1.Device;
 3using D3D11Device = SharpDX.Direct3D11.Device;
 4using DeviceContext = SharpDX.Direct2D1.DeviceContext;
 5using DXGIDevice = SharpDX.DXGI.Device;
 6using DXGIFactory = SharpDX.DXGI.Factory;
 7
 8...
 9
10public partial class DirectxMapControl : UserControl
11{
12  private Map _map;
13
14  public DirectxMapControl()
15    : base()
16  {
17    InitializeComponent();
18    InitDeviceContext();
19    _map = new Map();
20  }
21
22  Object lockObj = new Object();
23
24  // 设备上下文
25  private DeviceContext deviceContext;
26
27  // DXGI SwapChain。
28  private SwapChain swapChain;
29
30  // SwapChain 缓冲区。
31  private Surface backBuffer;
32
33  // 渲染的目标位图。
34  private Bitmap1 targetBitmap;
35
36  private void InitDeviceContext()
37  {
38    // 创建 Dierect3D 设备。
39    D3D11Device d3DDevice = new D3D11Device(
40        DriverType.Hardware, DeviceCreationFlags.BgraSupport);
41
42    DXGIDevice dxgiDevice = d3DDevice.QueryInterface<D3D11Device>()
43        .QueryInterface<DXGIDevice>();
44
45    // 创建 Direct2D 设备和工厂。
46    D2D1Device d2DDevice = new D2D1Device(dxgiDevice);
47    this.deviceContext = new DeviceContext(d2DDevice, 
48        DeviceContextOptions.None);
49    // 创建 DXGI SwapChain。
50    SwapChainDescription swapChainDesc = new SwapChainDescription()
51    {
52      BufferCount = 1,
53      Usage = Usage.RenderTargetOutput,
54      //图像输出的窗口句柄->本控件
55      OutputHandle = this.Handle,
56      IsWindowed = true,
57      // 这里宽度和高度都是 0,表示自动获取。
58      ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.B8G8R8A8_UNorm),
59      SampleDescription = new SampleDescription(1, 0),
60      SwapEffect = SwapEffect.Discard
61    };
62
63    this.swapChain = new SwapChain(
64         dxgiDevice.GetParent<Adapter>().GetParent<DXGIFactory>(),
65         d3DDevice, swapChainDesc);
66    // 创建 BackBuffer。
67    this.backBuffer = Surface.FromSwapChain(this.swapChain, 0);
68    // 从 BackBuffer 创建 DeviceContext 可用的目标。
69    this.targetBitmap = new Bitmap1(this.deviceContext, backBuffer);
70    this.deviceContext.Target = targetBitmap;
71  }
72}
73
74//释放对象,重绘前执行
75private void DisposeContext()
76{
77  lock (lockObj)
78  {
79    this.deviceContext.Target = null;
80    this.backBuffer.Dispose();
81    this.targetBitmap.Dispose();
82    this.swapChain.ResizeBuffers(1, Width, Height, Format.B8G8R8A8_UNorm, SwapChainFlags.None);
83    this.backBuffer = Surface.FromSwapChain(this.swapChain, 0);
84    this.targetBitmap = new Bitmap1(this.deviceContext, backBuffer);
85    this.deviceContext.Target = targetBitmap;
86  }
87}

适配Graphics

你可能还记得此前基于GDI+绘图的Layer设计,绘制点线面的函数是由System.Drawing.Graphics提供的。这无法适配我们今天的DirectxMapControl,或者以后可能出现的种种MapControl。

所以需要对绘图函数做一个抽象。下面是抽象出的绘图接口:

1public interface IShapeGraphics
2{
3  void DrawPoint(System.Drawing.Point point);
4  void DrawLine(System.Drawing.Point[] points);
5  void DrawPolygon(System.Drawing.Point[] points);
6}

这样,原来的Layer的绘图函数需要变化:

1public void DrawPoint(Graphics g, Map map);

Graphics参数由对象形式变为接口

1public void DrawPoint(IShapeGraphics g, Map map);

有了这个接口,Layer的绘图方式就可以脱离具体绘图平台了。现在对标这个接口,用DirectX的方式,编写实现类DirectxGraphics,实现IShapeGraphics

  1public class DirectxGraphics : IShapeGraphics
  2{
  3  DeviceContext deviceContext;
  4  SwapChain swapChain;
  5
  6  public DirectxGraphics(DeviceContext deviceContext, SwapChain swapChain)
  7  {
  8    this.deviceContext = deviceContext;
  9    this.swapChain = swapChain;
 10  }
 11
 12  //色值转换
 13  Color4 ColorToRaw4(SharpDX.Color color)
 14  {
 15    const float n = 255f;
 16    return new Color4(color.R / n, color.G / n,
 17      color.B / n, color.A / n);
 18  }
 19
 20  //渐变色顶点描述
 21  GradientStopCollection gradientStopCollection = null;
 22
 23  GradientStopCollection GradientStopCollection
 24  {
 25    get
 26    {
 27      if (gradientStopCollection != null)
 28        return gradientStopCollection;
 29
 30      var gradientStop0 = new GradientStop()
 31      {
 32        Color = this.ColorToRaw4(SharpDX.Color.Yellow),
 33        Position = 0f
 34      };
 35      var gradientStop1 = new GradientStop()
 36      {
 37        Color = this.ColorToRaw4(SharpDX.Color.ForestGreen),
 38        Position = 0.5f
 39      };
 40      var gradientStop2 = new GradientStop()
 41      {
 42        Color = this.ColorToRaw4(SharpDX.Color.White),
 43        Position = 1f
 44      };
 45      var gradientStops = new GradientStop[]
 46          {
 47              gradientStop0,
 48              gradientStop1,
 49              gradientStop2,
 50          };
 51
 52      gradientStopCollection = new GradientStopCollection(deviceContext, gradientStops);
 53      return gradientStopCollection;
 54    }
 55  }
 56
 57  //实现画点
 58  public void DrawPoint(System.Drawing.Point point)
 59  {
 60    deviceContext.BeginDraw();
 61
 62    Brush brush = new SolidColorBrush(this.deviceContext, 
 63         SharpDX.Color.Orange);
 64
 65    Vector2 vector = new Vector2(point.X, point.Y);
 66    Ellipse p = new Ellipse(vector, 2, 2);
 67    deviceContext.FillEllipse(p, brush);
 68
 69    deviceContext.EndDraw();
 70  }
 71
 72  //实现画线
 73  public void DrawLine(System.Drawing.Point[] points)
 74  {
 75    Brush brush = new SolidColorBrush(this.deviceContext, 
 76        SharpDX.Color.Blue);
 77
 78    deviceContext.BeginDraw();
 79
 80    for (int i = 0; i < points.Length - 1; i++)
 81    {
 82      Vector2 p1 = new Vector2(points[i].X, points[i].Y);
 83      Vector2 p2 = new Vector2(points[i + 1].X, points[i + 1].Y);
 84      deviceContext.DrawLine(p1, p2, brush, 2);
 85    }
 86
 87    deviceContext.EndDraw();
 88  }
 89
 90  //实现画面
 91  public void DrawPolygon(System.Drawing.Point[] points)
 92  {
 93    //开始绘图
 94    deviceContext.BeginDraw();
 95
 96    SharpDX.Direct2D1.Factory factory = deviceContext.Factory;
 97
 98    PathGeometry pg = new PathGeometry(factory);
 99    GeometrySink gs = pg.Open();
100    {
101      //开始记录起点
102      gs.BeginFigure(
103        new Vector2(points[0].X, points[0].Y), FigureBegin.Filled);
104
105      for (int i = 1; i < points.Length; i++)
106      {
107        //添加节点
108        gs.AddLine(
109          new Vector2((float)points[i].X, (float)points[i].Y));
110      }
111      //形成闭环
112      gs.EndFigure(FigureEnd.Closed);
113    }
114
115    gs.Close();
116    pg.Outline(gs);
117
118    //图形外框
119    //用于描述渐变效果范围
120    RectangleF bounds = pg.GetBounds();
121
122    gs.Dispose();
123
124    var gbp = new LinearGradientBrushProperties()
125    {
126      StartPoint = new Vector2(bounds.Left, bounds.Top),
127      EndPoint = new Vector2(bounds.Right, bounds.Bottom)
128    };
129
130    //渐变效果笔刷
131    var lnBrush = new LinearGradientBrush(deviceContext,
132                                      gbp,
133                                      GradientStopCollection);
134
135    var style = new StrokeStyleProperties();
136    style.LineJoin = LineJoin.Round;
137    style.StartCap = CapStyle.Round;
138    style.EndCap = CapStyle.Round;
139    var stroke = new StrokeStyle(factory, style);
140
141    //图形边缘笔刷
142    Brush edgeBrush = new SolidColorBrush(this.deviceContext, SharpDX.Color.Black);
143
144    deviceContext.DrawGeometry(pg, edgeBrush, 2, stroke);
145    deviceContext.FillGeometry(pg, lnBrush);
146
147    //结束绘图
148    deviceContext.EndDraw();
149    pg.Dispose();
150  }
151}

有了这个新的Graphics,也就代表有了绘图表面,这样地图控件就可以将此对象传递给Layer进行绘图。

传递路径是DirectxMapControl-->Map-->Layer,所以需要对下层的对象做一定的改动。

首先,控件自身传递Graphics的方式预先定义出来:

1public partial class DirectxMapControl : UserControl
2{
3  public IShapeGraphics CreateShapeGraphics()
4  {
5    return new DirectxGraphics(this.deviceContext, swapChain);
6  }
7  ...
8}

Map对象的渲染方法改为接收IShapeGraphics参数:

1 public void Render(IShapeGraphics g);

Layer对象的绘制方法,变化不大:

 1//例:Layer类点绘制
 2public void DrawPoint(IShapeGraphics g, Map map)
 3{
 4  foreach (var f in features)
 5  {
 6    if (!map.IsRenderingEnabled)
 7      throw new RenderingAbortedException("Point Drawing Aborted");
 8
 9    Vertex p = f.GetGeometry().Centroid;
10    System.Drawing.Point point = map.ToScreenPoint(p);
11    g.DrawPoint(point);
12  }
13}

DirectxMapControl渲染图层

为了节省篇幅,上篇MapControl的其余部分就不列出了,依然采用每次重绘启动一个线程的方式。这里只对渲染方法作出改动

 1public partial class DirectxMapControl : UserControl
 2{
 3  Object lockObj = new Object();
 4
 5  ...
 6
 7  private void RenderMapInternal(Extent extent)
 8  {
 9    try
10    {
11      //停止已有线程的绘图任务
12      if (_map.IsRendering)
13        _map.AbortRendering();
14
15      lock (lockObj)
16      {
17        _map.UpdateExtent(extent,
18          new Rectangle(0, 0, Width, Height));
19
20        //释放对象
21        DisposeContext();
22
23        //绘制白色背景
24        deviceContext.BeginDraw();
25        deviceContext.Clear(SharpDX.Color.White);
26        deviceContext.EndDraw();
27
28        IShapeGraphics g = this.CreateShapeGraphics();
29        //将地图绘制到空白图片上
30        _map.Render(g);
31
32        //帧缓冲区图像交换
33        swapChain.Present(0, PresentFlags.None);
34      }
35    }
36
37    catch (Exception e)
38    {
39      DisposeContext();
40    }
41  }
42  ...
43}

这里说一下显卡绘图的最后流程:

显卡每一帧图像的绘制,都是在两种缓存的切换中实现的。其中一种叫后置缓存(back buffer),另一种叫前置缓存(front buffer)

显示器正在显示的图像处于前置缓冲之中,同时CPU读取到的图像会被置于后置缓存中。

当前置缓存中的图像显示完后,两个缓存的关系翻转,前置缓存变成后置缓存,后置缓存变成前置缓存。

这个交换的过程称为呈现(presenting)。图像的刷新就是由此产生。

所以,这里的swapChain.Present一定是必要的。否则前面所有的绘制过程都不会生效在屏幕上。

调用呈现

Directx地图控件已经准备好了,现在把它拖入到Form里,替换之前的MapControl。同时shp打开按钮的调用,相应的变成了这样:

 1private void btn_OpenShp_Click(object sender, EventArgs e)
 2{
 3  OpenFileDialog ofd = new OpenFileDialog();
 4  ofd.Filter = "(*.shp)|*.shp";
 5  if (ofd.ShowDialog() == DialogResult.OK)
 6  {
 7    ShapeFile shp = new ShapeFile();
 8    Layer layer = shp.ReadShapeFile(ofd.FileName);
 9    directxMapControl.Map.Layers.Add(layer);
10    directxMapControl.RenderMap();
11  }
12}

底层的变化波涛汹涌,上层的调用波澜不惊~

最后来看看加载起来的渐变特效吧:

把线也加进去:

这一切还是跑得通的,并且画风明显炫酷!

Ok本章到站,支持的朋友请点赞下车

发布了12 篇原创文章 · 获赞 9 · 访问量 1734

猜你喜欢

转载自blog.csdn.net/qq_37141536/article/details/105238152