转载 WorldWind学习总结三

来源:互联网 发布:淘宝抓取商品 编辑:程序博客网 时间:2024/06/18 10:21

WorldWind学习总结三

转载自:http://blog.csdn.net/paul_xj/article/details/1792486 

 

       可能学习过程中给自己制造压力太大,有时候一天没有收获就会很难受,但是像WW这样的项目到一定程度会有个难过的坎而几天没有进步是很正常的,对自己要求太多而实现不了常有很坏的影响,待调整好再理清思路又过了不少时间,用一个好的心态来稳步进步,终有一天能完全理解,这也是个量变到质变的绝对过程,如果遇到了难以进步的地方,不如看看理论知识,再加强下3d知识,看看窗外的云,其实真正的收获也就是那“妙手偶得”的灵感瞬间:)

抛个砖,希望能引来朋友们更多更精采的技术性文档,共同学习进步

一、            LOD模型的补充说明

LOD模型mesh grid(网格)的生成(包括优化,视域剔除,背面剔除,遮挡剔除),对应纹理的UV坐标,对条带化后三角形格网及u,v坐标的缓存,World Wind1.4.1只使用了“视域剔除”使用BoundBox(包围盒)算法,下面对这些简要介绍。

有两种类型的mesh grid,可以quadTile.cs的定义里找到,对应两种mesh grid拆分的顶点数(实际拆分时还有些运算)

/// Number of points in child flat mesh grid (times 2)

        protected static int vertexCount = 40;

        /// Number of points in child terrain mesh grid (times 2)

         protectedstaticint vertexCountElevated = 40;

         两种mesh grid调用时机为:

     if (QuadTileSet.TerrainMapped &&Math.Abs(verticalExaggeration) > 1e-3)

             CreateElevatedMesh();

         else

             CreateFlatMesh();

     有地形数据,并且地形的高度以夸大值大于1e-3时创建带有高度的地形,否则平坦地形网格。

         实际渲染的纹理为512*512像素的,创建mesh grid时平均分为四个子树,在同一纹理里的分辩       率是相同的

         protected QuadTile northWestChild;

        protected QuadTile southWestChild;

        protected QuadTile northEastChild;

        protected QuadTile southEastChild;

         四个子树不再进行拆分,顶点和u,v座标信息保存在:

      protectedCustomVertex.PositionNormalTextured[] northWestVertices;

        protected CustomVertex.PositionNormalTextured[] southWestVertices;

        protected CustomVertex.PositionNormalTextured[] northEastVertices;

        protected CustomVertex.PositionNormalTextured[] southEastVertices;

          对于FlatMesh每个子树拆分为21*21个结点,对于ElevatedMesh每个子树拆分为23*23个结点,       分别计划顶点坐标和u,v坐标,由于每个顶点差不多被重用6次,在实际渲染中使用顶点索引,对mesh    grid数据进行三角形条带化后再直接渲染和缓存。

          ElevateMesh的高程数据从函数

     TerrainTile tile =QuadTileSet.World.TerrainAccessor.GetElevationArray()取得,返回的tile    的成员ElevationData保存一个43*43的二维数组。

       缓存机制:每个QuadTile对象都对应一个string类型的key变量,生成算法为:

     key = string.Format("{0,4}",this.Level)

                  + "_"

                  + string.Format("{0,4}",this.Col)

                  + string.Format("{0,4}",this.Row)

                  + this.QuadTileSet.Name

                  + this.QuadTileSet.ParentList.Name;可保证key的唯一性,每次计算完一区域的   mesh grid数据及u,v座标后,都保存在一静态变量中:internalstaticDictionary<string,CacheEntry> VerticeCache=new Dictionary<string,CacheEntry>();,缓存大小为internalstaticint CacheSize = 256;,类CashEntry的定义为:

     internal class CacheEntry

        {

            public CustomVertex.PositionNormalTextured[] northWestVertices;

            public CustomVertex.PositionNormalTextured[] southWestVertices;

            public CustomVertex.PositionNormalTextured[] northEastVertices;

            public CustomVertex.PositionNormalTextured[] southEastVertices;

            public short[] vertexIndexes;

            public DateTime EntryTime;

        }

     保存mesh grid及u,v值,对应的顶点索引缓冲vertexIndexes,网格信息的添加时间EntryTime,每增加一个网格信息都调用VerticeCache.Add(key, c);进行缓存,若超过CacheSize上限,则寻找过期的key进行剔除,可能这个剔除算法并不保证一定删除成员,因为在实际跟踪过程中发现VerticeCache的count值达到400以上,并且剔除算法没有删除任务成员。

     可以看到WW的LOD数据每个level是平均划分的,并没有做地形起伏LOD,只是根据视点的远近划分了不同的level

二、WW的渲染结构

       WW中除了标题和菜单的工作区中的button,layer,line,point,line,model,当然还有planet默认为earth的地形纹理,及其上所有信息,都做为WW的渲染对象,保存在World.cs中的RenderableObjectList _renderableObjects; RenderableObjectList的继承关系如下:

IRenderable

IComparable

RenderableObjectList

GPSTrackerOverlay

Icons

PathList

RenderableObject

实际的点线,平面纹理等渲染对象都是从RenderableObject继承,最终的渲染实现也是在从它继续下来的类中,RenderableObjectList的成员m_children(protectedArrayList m_children = newArrayList();)包含WW中所有的渲染对象,绘制过程中按如下的优先级进行:

public enumRenderPriority

     {

         SurfaceImages = 0,

         TerrainMappedImages = 100,

         AtmosphericImages = 200,

         LinePaths = 300,

         Icons = 400,

         Placenames = 500,

         Custom = 600

     }

这里对WW调试过程中的m_children的成员做个截图,需要注意的是m_children的成员大部分还是RenderableObjectList对象,向下包含的层次很多,但只有最底层的从RenderableObject继续的对象才是渲染的最终实现:

从下图可以看到里面的成员和Layer界面下的组织结构很相似,其实最终的结时会是一样的!

 

 

RenderableObject继承的对象列表如下(红色部分是需要着重看的,WW的主要算法部分);

RenderableObject

StereoLayer

AtmosphereLayer

CompassLayer

FloodLevel

GPSTrackerOverlay

GPSTrackerFixInfo

GPSTrackLine

GPSIcon

GPSGeoFence

WaitMessage

Stars3DLayer

BoundaryLayer

DownloadableImageFromIconSet

DownloadableIcon

Icon

ImageLayer

LatLongGrid

MeshLayer

PathLine

PolygonLayer

QuadTileSet

Bar3D

WavingFlagLayer

ShapeLayer

TerrainPath

TiledPlacenameSet

TiledWFSPlacenameSet

World

WaitMessage

ClassificationBanner

RenderableObject

QuadTile

n…

1

BoundingBox

1

1

RenderableObjectList

1

1

MeshLayer

三、WW启动过程

       程序的入口点当然是在WorldWind.cs里的main函数,在这里将创建一个WW程序的实例MainApplication,调用LoadSettings进行一些必要的初始化,加载除了WW工作区以外的所有菜单,及点击菜单项弹出来的对话框项,声明一个WorldWindow实例来处理WW工作区内所有图层,按扭的渲染,WorldWindow里的World对象专门用来处理planet的所有渲染信息,当然现在默认的是处理earth的所有渲染信息。

       WW的启动过程需要访问系统的默认设置,启动默认行星的设置(一般默认为earth,WW并不直接调用本地config文件夹里earth的设置,而是先生成一个URL值访问服务器的xml文件并下载到本地,分别保存为tmp,uri,xml为扩展名的三个同名文件,根据配置信息再加载插件,和需要做为渲染对象的图层信息,并保存在World对象的_renderableObjects成员中,比如启动WW呈现在我们面前的地球图形就是根据这些信息进行渲染的,每一种卫星(Landsat, USGS, etc)的最大level值,最大经纬度值..

    因为启动时需要访问WW服务器的xml信息,所以网速慢的朋友启动也会慢不少,我测试了下自己的电脑访问WW的服务器网速只有6k/s左右,启动时间有三分之一都耗费在这上面,其余大部分时间都花在加载配置文件上xml上,因为需要将配置文件里的所有图层都加载到WW中

    配置文件的加载实现在ConfigurationLoader.cs中,其中主要是图层信息的加载,函数getRenderableFromLayerFile中分别执行不同类型图层,初始地形信息的加载,如下:

addImageLayersFromXPathNodeIterator(iter.Current.Select("ImageLayer"), parentWorld, parentRenderable);

addQuadTileLayersFromXPathNodeIterator(iter.Current.Select("QuadTileSet"), parentWorld, parentRenderable, cache);

addPathList(iter.Current.Select("PathList"), parentWorld, parentRenderable);

addPolygonFeature(iter.Current.Select("PolygonFeature"), parentWorld, parentRenderable);

addLineFeature(iter.Current.Select("LineFeature"), parentWorld, parentRenderable);

addModelFeature(iter.Current.Select("ModelFeature"), parentWorld, parentRenderable);

//addWater(iter.Current.Select("Water"), parentWorld, parentRenderable);

addTiledPlacenameSet(iter.Current.Select("TiledPlacenameSet"), parentWorld, parentRenderable);

addTiledWFSPlacenameSet(iter.Current.Select("TiledWFSPlacenameSet"), parentWorld, parentRenderable, cache);

addIcon(iter.Current.Select("Icon"), parentWorld, parentRenderable, cache);

addScreenOverlays(iter.Current.Select("ScreenOverlay"), parentWorld, parentRenderable, cache);

addChildLayerSet(iter.Current.Select("ChildLayerSet"), parentWorld, parentRenderable, cache);

addExtendedInformation(iter.Current.Select("ExtendedInformation"), parentRenderable);

最终结构会和WW的Layer层的结构类似,如下所示(WW出调出了BUG看不到layer暂时)

 

四、视域剔除

       WW的视域剔除并没有精确到每个三角形,而是每个纹理对应的四个子树形成的包围盒BoundBox,看代码中也有包围球BoundSphere的实现,但却没有调用这部分代码,可能是做为下一个版本的扩充,具体实现可以在ViewFrustum.cs中找到,代码量很少,publicbool Intersects(BoundingBox bb) 根据返回的bool值确定是否绘制。

     在代码中除了m_Device3d.RenderState.CullMode = Cull.Clockwise或=Cull.CounterClockwise形成的背面剔除(根本就没有背面的图吧)和z缓冲形成的遮挡外,没有其他的剪裁算法。

五、旋转变换

       这要关系到基础的数学知识,也是WW很多算法的基础了,重要看的代码在Camera.cs,WorldCamera.cs,MathEngine.cs, Quaterniond.cs中,不但包含WWplanet的旋转变换,还有漫游,放大放小,切换视角,球面坐标和笛卡尔坐标的转换。

       旋转一般都离不开四元数,WW的算法也是使用算定义的四元数类Quaternion4d,(另外Slerp实现了球面的线性插值)

下图是旋转的三个基本方向,所有的旋转都能分解成这三个方向的分量的和

      

Right(pitch)

LookAt(Roll)

CameraWorldCamera,MomentumCamera三个类都重载了旋转函数RotationYawPitchRoll(Angle yaw,Angle pitch, Angle roll)

传入三个方面的分量,从而实现planet的旋转效果

 

ZoomStepped( float ticks )和Zoom(float percent)两个函数实现当前视点地形的放大缩小效果,类似于摄像机的镜头推近、远离地球的效果,会引起变量TargetDistance的变化进而影响当前图形的level的变化

六、快速查找及定位的实现

(本来想写有level的确定,突然发现还有点理解不足,时间也不多先把以前的粘贴下来)

       WWPlace Finder功能中,或者网友给我们一个地理信息的URI值,如下所示:

 worldwind://goto/world=Earth&lat=31.23372&lon=121.45974&alt=13473只需要对着WW的工作区窗口粘贴就能直接到目标位置,包含目标位置的经,纬度,海拔信息,将直接调用WorldWindow.csGoto(WorldWind.Net.WorldWindUri uri)函数,进而调用

this.drawArgs.WorldCamera.SetPosition(latitude, longitude,

                this.drawArgs.WorldCamera.Heading.Degrees,

                altitude,

                this.drawArgs.WorldCamera.Tilt.Degrees);转向目标位置

    当然,所有的定位信息都最终转向摄像机类来进行处理,所以三个摄像机类camera,worldcamera,momentumcamera都有SetPosition的各种重载。

 

七、摄像机类的一些其他需要注意的

     三种坐标转换:当然是世界坐标,view坐标,投影坐标了:)首先看camera.cs里的声明,

     protected Matrix m_ProjectionMatrix; // Projection matrix used in last render.

     protected Matrix m_ViewMatrix; // View matrix used in last render.

     protected Matrix m_WorldMatrix =Matrix.Identity;

     View坐标和世界坐标分别在函数ComputeViewMatrix和ComputeProjectionMatrix里实现,基础而且重要的东西,需要深刻理解,ComputeAbsoluteMatrices则包含对三种坐标的处理,明显可以看到坐标系统是使用右手笛卡尔坐标系,

x

y

z

    投影,view变换时有个很重要的要素就是视口的宽高比,也是确定需要显示给用户的WW工作区的宽高比,全屏时为屏幕的宽高比,窗口时要重新计算。float aspectRatio =  (float)viewPort.Width / viewPort.Height;

     坐标转换是个很复杂的东西,当然不是一两句话就能讲清楚的,现在只是列出来一些,等感觉理解已经足够的时候再补足,_tilt是planet的倾角,是切换视角时的关键变量,正视图时其值为0,角度范围为0-85度,摘抄部分计算tilt的代码:

// Compute camera tilt from vertical at the camera position

              double tilt = camera.Tilt.Radians * 2;

              if (camera.Altitude > 10000)

              {

                   double a = camera.WorldRadius;                      // World radius

                   double b = camera.WorldRadius + camera.Altitude;  // Camera to center of planet

                   double c = camera.Distance;                       // Distance to target

                   tilt = Math.Abs(Math.Acos((a * a - c * c - b * b) / (-2 * c * b))) * 2;

                   if (double.IsNaN(tilt)) tilt = 0;

              }

 
原创粉丝点击