OpenGL之三维GIS

本文介绍了如何使用OpenGL进行三维GIS开发,探讨了三维地形地貌的生成技术,包括DEM数据的处理和三维显示。通过OpenGL的基本图形操作接口,如光照、材质、帖图等,实现了逼真的三维效果。并分享了作者在开发过程中的几点心得,如内存映射加速数据加载、规则三角网与不规则三角网的处理等。

  相对于二维图形开发,大家一般认为三维开发要难很多。其实也不一定。相比二维开发,三维开发一般都是在三维图形引擎的基础之上进行的,比如常用的OpenGL或DirectX,而在工程和行业应用领域中,OpenGL比较流行一样(DX在多媒体相关开发中比较流行)。三维引擎一般提供了比较好的底层接口,包括显存/内存数据交换、快速体绘制算法、视场、光照、帖图及一些工具箱等的支持,大家在此基础上很方便地就能显示一个旋转的立方体、茶壶等,更复杂的模型如体育馆、火车头、人脸等,就需要用专业软件做建模工作(如3DMax、AutoCAD等);而在地理领域,要生成三维的地形地貌,所涉及的技术要点就稍微有些不同。我们可以首先看看CS中的三维场景,大家都会感觉CS中的场景精美、视图变换与刷新很快,操作敏捷,其实CS每一场景中的三维要素并不复杂,都是一些加了帖图的墙面、斜坡等(用专业的说法就是需要生成和刷新的三角面并不多),而要生成一段简单的山坡地形,就有可能涉及成万上亿的三角面,因为地理世界是分形的(fractal),没有规律的,三维引擎首先要生成这么多的三角面,再计算其法向量(光照要用的),再在上面帖图,还要保证很好的体绘制速度(还没考虑地貌上加树林、民居等),这样就为计算机系统带来了存储量和计算时间的要求,这两点,才真正是三维GIS要考虑的重点之重点。

  让我们来先看看下面的两个DEM(数字高程模型),这些模型都是来自于卫星遥感或其他测量工作:

  有时DEM也以点黑白位图的方式进行提供,每一像素的灰度值就是代表该点的高程采样,这种灰度图也是进行了坐标配准的,也就是说位图上的每一个点都是与某一地理坐标值相对应。如果我们要把这个DEM用三维的方式展现出来,就需要调用OpenGL的相应图形操作接口来完成。

  回到OpenGL,三维开发之前首先要对场景进行一些初始化,包括激活三维场景、初始化视点、光照和材质等工作,然后才能进行地物的绘制,如下是一个比较标准的OpenGL操作代码:
  glEnable(GL_DEPTH_TEST);

  glEnable(GL_LIGHT0);

  glenable (GL_COLOR_MATERIAL);
  gldisable (GL_NORMALIZE);

   PaintStruct ps;
   BeginPaint (Handle, ps);
   glClear (GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

    glPushMatrix;

    glRotatef (Anglex, 1.0,0.0 , 0.0);
    glRotatef (Angley, 0.0,1.0 , 0.0);

    //绘制图形
    //MyMesh.Draw;  //MyMesh是一个经封装的DEM类

    glPopMatrix;

    SwapBuffers (DC);
  EndPaint (Handle, ps);

  OpenGL提供了一些基本的画点、画线、画折线、画多边形以及画连续Strip地物等的函数接口,这些接口可以通过嵌套在
  glBegin(mode)
  glEnd();
  的代码段中运行,如下就是一个代码片断:
  glBegin(MeshConfig.mode);
                    n1 := GetNormal(pts[I].dX, pts[I].dY, Pts[I].dZ, pts[I + nXWidth].dX, pts[I + nXWidth].dY, Pts[I+ nXWidth].dZ ,pts[I + 1].dX, pts[I + 1].dY, Pts[I + 1].dZ);
    //             n1 := GetNormal(pts[I].dX, pts[I].dY, Pts[I].dZ, pts[I + 1].dX, pts[I + 1].dY, Pts[I+ 1].dZ ,pts[I + nXWidth].dX, pts[I + nXWidth].dY, Pts[I + nXWidth].dZ);
                    glNormal3fv(@n1);
                    glTexCoord2f((I mod nYWidth) / nXWidth, (I div nXWidth) / nYWidth);
                    glVertex3f(pts[I].dX, pts[i].dy, pts[i].dZ);
                    glTexCoord2f(((I + nXWidth) mod nXWidth) / nXWidth, ((I + nXWidth) div nYWidth) / nYWidth);
                    glVertex3f(pts[I + nXWidth].dX, pts[I + nXWidth].dy, pts[I + nXWidth].dZ);
                    glTexCoord2f(((I + 1) mod nXWidth) / nXWidth, ((I + 1) div nYWidth) / nYWidth);
                    glVertex3f(pts[I + 1].dX, pts[I + 1].dy, pts[I + 1].dZ);
                glEnd;

  是不是很简单就可以把一个多边形画出来?是的。接下来如果我们要旋转这个多边形,画是想“凑近”看看这个多边形,怎么办?是不是把多边形的坐标进行旋转变换就可以了?没必要这么复杂,OpenGL是通过改变“观众”视角的方式来实现地物的旋转和变化的,看看如下代码片断:

 dLength := 0.1;
 gluLookAt(0,0,dLength,0,0,-100,0,1,0);

  是不是很简单啊?一个LookAt就解决问题了。

  地物可以动起来了,我们下面关心的就是地物显示的逼真。大家知道要使三维地物和场景显示得真实,逼真的光影表现是最重要的,也就是说地物和场景要有真实的明暗对比,这就需要我们为地物照上一些光。OpenGL中可以为地物设置多个光源,还可以为光源设置平行光、点光及相应的颜色等,如下:

  glEnable(GL_LIGHTING);

  还是这么简单!当然,要细致控制光源的效果,还有很多的函数需要调用。并且要记住二点:一是要为光源计算每个面的法向量(Normal vector),二是每一个面是由“正反”两面构成,别忘记了还有背面,否则会造成一个面旋转到背面,就神秘地消失了!

  接下来是帖图,也很简单。

  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, Texture_pointer);

  可以为场景设置多个素材,每个素材同地物的坐标一配,就可以自动地“套上”地物了。

  这样说是不是太简单了?呵,要做到工业级的产品,肯定还是少不了精细的开发。大家可以到这里下载我的程序和演示数据:
 
  http://www.delphibbs.com/keylife/images/u43/3Dbuilder_.rar

  另外说几点我的心得:

  1,可以通过一些内存映射的方式来提高数据载入速度和占用内存的空间,如我用FileMapping的方式,直接将三维模型文件映射到内存中进行显示,速度比较快,不过这个三维模型和材质要预先处理好,以便于在映射时字节对齐;
  2,用规则三角网来生成和显示DEM比较简单,但数据有一些冗余,我在DEMO中试着做了一些四叉树的三角合并,在有些面的接鏠处还处理得不好,也没有进一步研究了;不规则三角网(TIN)会好一些,也可以通过算法将规则三角网生成为TIN;
  3,空间选取这块还没有太多的研究,不是太清楚OPENGL提供的SELECT功能能到什么地步,还是需要自已开发?
  4,我在DEMO做的是单键操作,即一个鼠标完成旋转、平移、缩放操作,效果比较好;
   

 OpenGL-自主高性能三维GIS平台架构与实现/第二季:实现三维GIS球体+ 高程数据章节名称DEM基础1DEM基础知识1.介绍基本的DEM知识2.什么是DEM,作用是什么2DEM数据1.如何获取/ 传统测量/激光扫描/无人机测量/ 点云数据/ 倾斜摄影2.如何使用/局部小规模(栅格数据,图片/tif),3. 组织方式4. 根据使用目的不同,介绍多种优化方法3DEM图层的实现原理14DEM数据结构定义struct  V3U3N4顶点数据的生成和计算WGS84投影计算5wgs84 投影球体被切成一个个小圆弧,一共60个投影带,分别为01,02.........60WGS的最新版本为WGS 84(也称作WGS 1984、EPSG:4326),1984年定义、最后修订于2004年。接口定义坐标转换Wgs84 数据加载6瓦片编号计算生成算法1. 经纬度到大地坐标的转换2.大地坐标到经纬度坐标转换3. 根据经纬度获取瓦片编号框架重构7智能指针重构框架1. 基类定义(所有的类继承自基类),基类派生自 std::enbale_shared_from_this2. 实现智能指针的动态转换接口3. 实现向下转换4. 已有的类实现全部使用智能指针重构5. 任务系统(多线程加载任务)8引入图层(Layer)1. 介绍图层的概念以及重要性2. 图层类实现3. 修改框架(使用图层的方式重构框架)9Layer-bug排查(绘制过程中出现错位,偶发)1. 框架重构后遇到问题(绘制结果错误)2. 瓦片索引方式发生变化,多线程中引起内存问题3. 修改索引方式,解决绘制偶发错误问题10引入数据源(TileSource)1. 数据源的作用与设计目的2. 当前存在的问题,数据调度中存在问题3. 数据源(TileSource)类实现11数据格式管理(FormatMgr)1. 数据格式管理(FormatMgr) 提出的目的,需要解决的问题2. CELLFormat基类接口抽象3. 实现几个标准格式类4. 修改框架流程,使用FormatMgr重构流程5. 扩展支持,后续支持任务格式数据加入系统12Task(任务)优化1. 任务中低耦合数据结构,目的是让Task更加的通用2. 修改任务读取代码与任务处理代码,完善处理流程DEM高程13DEM-数字高程定义1. 什么是数字化高程数据2. 当下GIS系统中有哪些常见的高程格式3. 课程体体系中使用的哪种格式4. 高程类定义以及实现,并加入到FormatMgr 管理系统中14高程瓦片数据读取1. 介绍GIS系统相关的工具(在数据转换)数据生成方面可以解决大量时间2. 自定义高程瓦片格式说明3. 自定义高程格式文件解析,并以智能对象的方式引入到系统中4. 完善框架代码,适配高程数据15高程瓦片文件的读取1. 实现基本的读取算法2. 增加格式化组件,并加入到系统中3. 配置高程图层以及高程数据源,并加载数据,验证数据正确性16瓦片数据结构重构1.顶点生成2.UV坐标计算3.面数据生成17DEM重构绘制流程1. 修改绘制数据结构,去除无用字段2. 增加Mesh类,实现光栅数据转换成三角面数据,计算UV数据,提炼接口3. 修改系统调度,实现顶点数据,UV数据,以及面数据的生成与更新4. 按需更新数据,而不是每一帧更新18DEM-数据精度问题(CPU)1. 因为瓦片数据使用大地坐标作为系统输入,造成瓦片坐标很大,单浮点数据精度不够2. 使用局部坐标的方式解决单浮点精度问题3. 调整相机参数,解决投影矩阵数据计算深度精度问题4. 修改绘制shader 实现对瓦片数据的绘制19DEM-数据精度问题(LogDepth)1. 使用对数深度(log depth )算法在GPU中 计算解决单浮点经纬计算问题2. 修改shader ,增加对(logDepth)算法支持3. 修改C++端代码,实现对shader数据的输入20DEM-数据结构优化1.当下使用CPU端数据通过接口的方式传递给GPU,速度慢2. 使用Instance 方式降低Vertex Buffer 的大小,优化渲染系统21DEM-GPU缓冲区优化1. 使用Vertex Buffer Object / Index Buffer Object  / Instance  方式优化渲染系统2. 修改绘制接口,使用DrawElementsInstanceBaseInstance方式提升系统性能内存池与对象池22瓦片生成优化/对象池1. 相机移动过程中会频繁的建立与释放瓦片,对CPU有较大的消耗2. 引入内存池,避免频繁的内存申请与释放,降低CPU时间3. 改造智能指针对象,对象释放通知到内存管理,回收对象内存23改造任务系统支持对象池1. 任务系统是一个公用模块,被多个模块使用,避免频繁的内存操作,引起的内存碎片2. 实现对象池,并应用到任务模块法线计算24法线计算1. 修改现有顶点结构,增加法线支持2. 修改shader,增加法线顶点输入,使用平行光光照模型3. 修改绘制流程,支持光照计算,使用探照灯作为光源输入25顶点法线计算/共享法线计算1. 增加数据结构保存顶点数据被多个面共享的次数2. 计算面法线,并累加到顶点法线中3. 根据顶点被面共享的次数做平均法线计算4. 修改流程,按需更新法线数据26法线数据压缩1. 法线数据使用3 * float 数据存储,大大的增加了系统的数据2. 实现算法,将3 * float 数据压缩成4字节数据3. 改造绘制代码,支持压缩数据输入27GPU中计算产生法线数据(去掉CPU中计算)1. 引擎支持 Geometry Shader 阶段2. 编写 Geometry Shader,实现法线计算系统功能优化28重构CPU拾取流程1. 当下的拾取流程,只支撑二维数据拾取,无法准群的拾取三维数据2. Terrain中增加拾取接口,输入射线,输出拾取到顶点数据29绘制拾取结果1. 增加一个绘制点的方法,实现绘制代码2. 修改shader,增加logdepth3. 调试代码,花费了很多时间排查错误,最总排查到是因为uniform参数笔误写错造成。30任务系统完善,避免任务队列无线膨胀1. 任务系统中,没有限制队列的大小,生产者的能力远大于消费者的能力,造成任务队列膨胀2. 处理办法,限制生产者的生产能力,而不是限制任务队列大小(这种方式会造成业务逻辑异常复杂)3. 使用sleep休眠方式(这种方式是严重错误的)31如何避免瓦片数据抖动1. 产生瓦片抖动的原因 ? 分裂算法与回退算法中间没有过度2. 引入过度流程,避免内存抖动,参数因子是一个重要的数据,需要谨慎使用3. 有必要结合瓦片自身数据动态计算参数因子32瓦片数据管理-fepk文件格式支持-全球数据加载1. 支持fepk文件格式,增加fepk读取组件,适配fepk文件2. fepk管理数据方式:一般情况选择全球前10级别作为基础级别,因数据量不大(1G)左右,后续以8级作为基础级别,全球19级别数据被划分为 2^8 * 2^7(512 * 256)个块。每个块中包含了256 * 256 张小瓦片33fepk高程数据读取 34高程分裂处理当瓦片没有高程数据,那么子节点以及其他后代节点该如何共享父节点的数据35lesson-734-高程瓦片分裂处理(2)-算法实现高程数据分裂算法实现实现对高程数据的切分,并对特殊数据进行处理36高程瓦片分裂处理(3)-问题排查 37高程瓦片分裂处理(4)-(后代节点更新问题)当一个瓦片高程数据更新后,他的儿子节点,孙子节点...该如何处理?38瓦片视锥裁剪错误高程数据更新后,没有技术计算瓦片包围盒信息,造成包围盒错误,进而引视锥计算错误39http支持1.引入三方库 Libcurl2.http类封装,支持http读取数据40fepk.server使用 生成三维地球41改造四叉树-统一使用经纬度输入42地形网络生成算法重构 43引入球体坐标系 44使用球体坐标改造瓦片 45多图层(加载标签数据) 课时截图:镜头拉近后,显示细节数据加载矢量SHP国界线数据:加载矢量三维白膜数据截图高程数据加载点云数据 加载倾斜摄影数据 
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值