WorldWind源码剖析系列:WorldWind如何确定与视点相关的地形数据的LOD层级与范围
1、WorldWind如何确定与视点相关的地形数据的LOD层级与范围?
问题描述:WW中是如何判断LOD层次的呢,即在什么情况下获得哪一层级的数据?是否只通过相机视点的高度进行判断?
问题切入:要解决这个问题,我先说明一下WW的渲染机制,在渲染线程中,Render函数只负责渲染可渲染物体,而不负责视点的更新和Lod的判断。在m_World.Render(this.drawArgs)中可渲染的物体都是通过另一个更新线程WorkerThreadFunc来控制的,具体由m_World.Update(this.drawArgs)实现。因此要找到这个问题的答案需要从Update着手。重点注意m_World.Update(this.drawArgs)中的两段代码。如下所示:
可渲染物体的递归更新:
if (this.RenderableObjects != null)
{
this.RenderableObjects.Update(drawArgs);
}
if (this.m_WorldSurfaceRenderer != null)
{
this.m_WorldSurfaceRenderer.Update(drawArgs);
}
if (this.m_projectedVectorRenderer != null)
{
this.m_projectedVectorRenderer.Update(drawArgs);
}
相机视点的高度更新:
if (this.TerrainAccessor != null)
{
if (drawArgs.WorldCamera.Altitude < )
{
if (System.DateTime.Now - this.lastElevationUpdate > TimeSpan.FromMilliseconds())
{
drawArgs.WorldCamera.TerrainElevation = (short)this.TerrainAccessor.GetElevationAt(drawArgs.WorldCamera.Latitude.Degrees, drawArgs.WorldCamera.Longitude.Degrees, 100.0 / drawArgs.WorldCamera.ViewRange.Degrees);
this.lastElevationUpdate = System.DateTime.Now;
}
}
else
drawArgs.WorldCamera.TerrainElevation = ;
}
else
{
drawArgs.WorldCamera.TerrainElevation = ;
}
上面代码是WorldWind V1.4.0.0中实现的。但是在WorldWind V1.4.0.1中被移入worldwindow.cs文件的WorldWindow.Render()函数中。如下图选中的代码。

问题答案:搜索全局Update,重点关注SurfaceTile的Update和QuadTile的Update。
SurfaceTile的Update:
if(drawArgs.WorldCamera.TrueViewRange < Angle.FromDegrees(3.0f*tileSize) && MathEngine.SphericalDistance( Angle.FromDegrees(centerLatitude), Angle.FromDegrees(centerLongitude), drawArgs.WorldCamera.Latitude, drawArgs.WorldCamera.Longitude) < Angle.FromDegrees( 2.9f*tileSize )& drawArgs.WorldCamera.ViewFrustum.Intersects(m_BoundingBox))
{
if(m_NorthWestChild == null || m_NorthEastChild == null || m_SouthWestChild == null || m_SouthEastChild == null)
{
ComputeChildrenTiles(drawArgs);
}
else
{
if(m_NorthEastChild != null)
{
m_NorthEastChild.Update(drawArgs);
}
if(m_NorthWestChild != null)
{
m_NorthWestChild.Update(drawArgs);
}
if(m_SouthEastChild != null)
{
m_SouthEastChild.Update(drawArgs);
}
if(m_SouthWestChild != null)
{
m_SouthWestChild.Update(drawArgs);
}
}
}
else
{
if(m_NorthWestChild != null)
{
m_NorthWestChild.Dispose();
m_NorthWestChild = null;
} if(m_NorthEastChild != null)
{
m_NorthEastChild.Dispose();
m_NorthEastChild = null;
} if(m_SouthEastChild != null)
{
m_SouthEastChild.Dispose();
m_SouthEastChild = null;
} if(m_SouthWestChild != null)
{
m_SouthWestChild.Dispose();
m_SouthWestChild = null;
}
}
QuadTile的Update:
if (DrawArgs.Camera.ViewRange < Angle.FromDegrees(QuadTileSet.TileDrawDistance * tileSize)&& MathEngine.SphericalDistance(CenterLatitude, CenterLongitude,DrawArgs.Camera.Latitude, DrawArgs.Camera.Longitude) < Angle.FromDegrees(QuadTileSet.TileDrawSpread * tileSize)&& DrawArgs.Camera.ViewFrustum.Intersects(BoundingBox))
{
if (northEastChild == null || northWestChild == null || southEastChild == null || southWestChild == null)
{
ComputeChildren(drawArgs);
} if (northEastChild != null)
{
northEastChild.Update(drawArgs);
} if (northWestChild != null)
{
northWestChild.Update(drawArgs);
} if (southEastChild != null)
{
southEastChild.Update(drawArgs);
} if (southWestChild != null)
{
southWestChild.Update(drawArgs);
}
}
else
{
if (northWestChild != null)
{
northWestChild.Dispose();
northWestChild = null;
} if (northEastChild != null)
{
northEastChild.Dispose();
northEastChild = null;
} if (southEastChild != null)
{
southEastChild.Dispose();
southEastChild = null;
} if (southWestChild != null)
{
southWestChild.Dispose();
southWestChild = null;
}
}
在这里我们看到判断Lod的级别主要有三个条件:
1、相机视角范围,视角范围越大,所包含的tileSize就越大;
2、相机与瓦片距离,距离越远,所包含的tileSize也就越大;
3、相机视锥与瓦片是否相交。
相对应我们可以把视角剔除方法理解成以下三步:
1、根据视角范围,画个大圈,把大圈里的大瓦片全加进来;
2、根据相机与瓦片的距离进行细化瓦片,如果太远的瓦片仍然保持很大,而近处的瓦片需要进一步更新,计算子瓦片;
3、每计算一个瓦片都需要判断它是否在视锥里,否则剔除身后看不到的瓦片。
在理解以上三个条件的前提下,需要理解的是视角、距离到底与tileSize有什么关系。对于不同级别影像或地形,ww中设置了相应的大小,根据一定的比例系数和经验参数寻找与距离和视角的关系。即通过调整参数 可控制在怎样的距离下可以看到怎样级别的地形或影像。例如在地形中设置为3.0、2.9等常数,在影像中设置为TileDrawDistance和TileDrawSpread等常数。只是反映了一定的经验参数而已。
2、WorldWind中对地块接边的处理是有裂缝的,采用裙摆设置的原理和方法实现是什么?
问题描述:对于不同级别的地形要进行接边是个很重要的问题,这个问题在我推荐的文献《全球多分辨率虚拟地形环境关键技术的研究》(作者杜莹2005博士)中有详细说明,而WW只是选择了最简单最原始的接地处理,也就是常说的裙摆处理。这种处理方法有很多的不利之处,但是实现方法简便,在此做简单说明。
问题切入:要想追踪这个问题,首先想到的是QuadTile的Update,而不从Render着手。Update就像在炒菜前把所有材料都准备的工作,如果没准备好就不能炒。在QuadTile的Update中,从CreateTileMesh切入,发现了CreateElevatedMesh和CreateFlatMesh的区别,显然裙摆是针对地形网格而做的处理在CreateElevatedMesh再深入到子函数,其中有一段很重要的说明:
/// <summary>
/// Builds flat or terrain mesh for current tile//为当前瓦片创建平坦或高程格网
/// </summary>
public virtual void CreateTileMesh()
{
verticalExaggeration = World.Settings.VerticalExaggeration;
m_CurrentOpacity = QuadTileSet.Opacity;
renderStruts = QuadTileSet.RenderStruts; if (QuadTileSet.TerrainMapped && Math.Abs(verticalExaggeration) > 1e-)
CreateElevatedMesh();
else
CreateFlatMesh();
}
在CreateElevatedMesh再深入到子函数,其中有一段很重要的说明:
/// <summary>
/// Build the elevated terrain mesh
/// </summary>
protected virtual void CreateElevatedMesh()
{
isDownloadingTerrain = true;
// Get height data with one extra sample around the tile
double degreePerSample = LatitudeSpan / vertexCountElevated;
TerrainTile tile = QuadTileSet.World.TerrainAccessor.GetElevationArray(North + degreePerSample, South - degreePerSample, West - degreePerSample, East + degreePerSample, vertexCountElevated + );
float[,] heightData = tile.ElevationData; int vertexCountElevatedPlus3 = vertexCountElevated / + ;
int totalVertexCount = vertexCountElevatedPlus3 * vertexCountElevatedPlus3;
northWestVertices = new CustomVertex.PositionNormalTextured[totalVertexCount];
southWestVertices = new CustomVertex.PositionNormalTextured[totalVertexCount];
northEastVertices = new CustomVertex.PositionNormalTextured[totalVertexCount];
southEastVertices = new CustomVertex.PositionNormalTextured[totalVertexCount];
double layerRadius = (double)QuadTileSet.LayerRadius; // Calculate mesh base radius (bottom vertices)
// Find minimum elevation to account for possible bathymetry
float minimumElevation = float.MaxValue;
float maximumElevation = float.MinValue;
foreach (float height in heightData)
{
if (height < minimumElevation)
minimumElevation = height;
if (height > maximumElevation)
maximumElevation = height;
}
minimumElevation *= verticalExaggeration;
maximumElevation *= verticalExaggeration; if (minimumElevation > maximumElevation)
{
// Compensate for negative vertical exaggeration
minimumElevation = maximumElevation;
maximumElevation = minimumElevation;
} double overlap = * verticalExaggeration; // 500m high tiles // Radius of mesh bottom grid
meshBaseRadius = layerRadius + minimumElevation - overlap; CreateElevatedMesh(ChildLocation.NorthWest, northWestVertices, meshBaseRadius, heightData);
CreateElevatedMesh(ChildLocation.SouthWest, southWestVertices, meshBaseRadius, heightData);
CreateElevatedMesh(ChildLocation.NorthEast, northEastVertices, meshBaseRadius, heightData);
CreateElevatedMesh(ChildLocation.SouthEast, southEastVertices, meshBaseRadius, heightData); BoundingBox = new BoundingBox((float)South, (float)North, (float)West, (float)East, (float)layerRadius, (float)layerRadius + * this.verticalExaggeration); QuadTileSet.IsDownloadingElevation = false; // Build common set of indexes for the 4 child meshes
int vertexCountElevatedPlus2 = vertexCountElevated / + ;
vertexIndexes = new short[ * vertexCountElevatedPlus2 * vertexCountElevatedPlus2 * ]; int elevated_idx = ;
for (int i = ; i < vertexCountElevatedPlus2; i++)
{
for (int j = ; j < vertexCountElevatedPlus2; j++)
{
vertexIndexes[elevated_idx++] = (short)(i * vertexCountElevatedPlus3 + j);
vertexIndexes[elevated_idx++] = (short)((i + ) * vertexCountElevatedPlus3 + j);
vertexIndexes[elevated_idx++] = (short)(i * vertexCountElevatedPlus3 + j + ); vertexIndexes[elevated_idx++] = (short)(i * vertexCountElevatedPlus3 + j + );
vertexIndexes[elevated_idx++] = (short)((i + ) * vertexCountElevatedPlus3 + j);
vertexIndexes[elevated_idx++] = (short)((i + ) * vertexCountElevatedPlus3 + j + );
}
} calculate_normals(ref northWestVertices, vertexIndexes);
calculate_normals(ref southWestVertices, vertexIndexes);
calculate_normals(ref northEastVertices, vertexIndexes);
calculate_normals(ref southEastVertices, vertexIndexes); isDownloadingTerrain = false;
}
这段注释的意思是说在:创建地形mesh的时候,在网格点的四周围多加一个点用于之后法向量的计算,而使用这种柱状顶点的效果将在法向量计算完后再折叠起来。这就是所谓的裙摆效果。在接下来的calculate_normals中,一眼就能看出折叠函数ProjectOnMeshBase,其具体实现如下:
// Project an elevated mesh point to the mesh base
private Point3d ProjectOnMeshBase(Point3d p)
{
p = p + this.localOrigin;
p = p.normalize();
p = p * meshBaseRadius - this.localOrigin;
return p;
}

声明:本文的原创著作权利属于武汉大学GIS(地理信息系统)专业2009级本科毕业生杨建顺学长,本文的成文仅用于个人学习和研究,在此对原著者表示感谢。
WorldWind源码剖析系列:WorldWind如何确定与视点相关的地形数据的LOD层级与范围的更多相关文章
- WorldWind源码剖析系列:星球类World
星球类World代表通用的星球类,因为可能需要绘制除地球之外的其它星球,如月球.火星等.该类的类图如下. 需要说明的是,在WorldWind中星球球体的渲染和经纬网格的渲染时分别绘制的.经纬网格的渲染 ...
- WorldWind源码剖析系列:星球球体的加载与渲染
WorldWind源码剖析系列:星球球体的加载与渲染 WorldWind中主函数Main()的分析 在文件WorldWind.cs中主函数Main()阐明了WorldWind的初始化运行机制(如图1所 ...
- WorldWind源码剖析系列:表面影像类SurfaceImage
表面影像类SurfaceImage描述星球类(如地球)表面纹理影像.该类的类图如下. 表面影像类SurfaceImage包含的主要的字段.属性和方法如下: string m_ImageFilePath ...
- WorldWind源码剖析系列:星球表面渲染类WorldSurfaceRenderer
星球表面渲染类WorldSurfaceRenderer描述如何渲染星球类(如地球)表面影像纹理.该类的类图如下. 星球类World包含的主要的字段.属性和方法如下: public const int ...
- WorldWind源码剖析系列:影像存储类ImageStore、Nlt影像存储类NltImageStore和WMS影像存储类WmsImageStore
影像存储类ImageStore 影像存储类ImageStore提供了计算本地影像路径和远程影像影像URL访问的各种接口,是WmsImageStore类和NltImageStore类的基类.当划分完层次 ...
- WorldWind源码剖析系列:设置类SettingsBase
PluginSDK中的星球设置类WorldSettings 和WorldWind.程序设置类WorldWindSettings均继承自父类SettingsBase.类图如下所示.其中父类Setting ...
- WorldWind源码剖析系列:表面瓦片类SurfaceTile
表面瓦片类SurfaceTile描述星球类(如地球)表面纹理影像的瓦片模型.其类图如下. 表面瓦片类SurfaceTile包含的主要的字段.属性和方法如下: int m_Level;//该瓦片所属金字 ...
- WorldWind源码剖析系列:挂件类Widgets
WorldWindow用户定制控件类中所包含的的挂件类Widgets控件主要有如下图所示的派生类.它们的类图如下所示. 鉴于挂件类Widgets及其派生类,相对简单,基本上都是些利用DirectX3D ...
- WorldWind源码剖析系列:窗口定制控件类WorldWindow
在WorldWindow定制控件是从Control类派生出来的,需要自己操纵GDI+绘制所需要的界面效果,这种自定义控件比较耗费精力,需要比较深厚的GDI+和DirectX 3D开发功底.(区别于用户 ...
随机推荐
- HDFS主要特性和体系结构
引言 Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统.它和现有的分布式文件系统有很多共同点.但同时,它和其他的分布式文件系统 ...
- CoreAnimation-08-CATransition
概述 简介 CATransition又称转场动画,是CAAnimation的子类,可以直接使用 转场动画主要用于为图层提供移入/移出屏幕的动画效果 转场动画常见的应用是UINavigationCont ...
- iOS之UI--自定义IOS的HYCheckBox源码的使用
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- 如何在linux系统中设置静态ip地址
在终端中输入:vi /etc/sysconfig/network-scripts/ifcfg-eth0 开始编辑,填写ip地址.子网掩码.网关.DNS等.其中"红框内的信息"是必须 ...
- 设计模式C#实现(八)——原型模式
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.(要创建一个对象,这个对象为实现原型接口,方法是原型克隆.克隆只是方法而不是原型模式的目的,创建对象才是目的) UML类图: ...
- (一)openwrt源码目录概述
前言 这段时间总是在和openwrt打交道,之前也零零散散地写过一点,还是希望能有点体系.还记得我刚看到源代码的时候,觉得无从下手.我想从Makefile的整个执行过程入手,搞清楚编译源代码的几个小时 ...
- PAT之我要通过
题目描述 “答案正确”是自动判题系统给出的最令人欢喜的回复.本题属于PAT的“答案正确”大派送 —— 只要读入的字符串满足下列条件,系统就输 出“答案正确”,否则输出“答案错误”. 得到“答案正确”的 ...
- zookeeper适用场景:配置文件同步
问题导读:1.本文三个角色之间是什么关系?2.三个角色的作用是什么?3.如何代码实现这三个角色的作用? 在 zookeeper适用场景:zookeeper解决了哪些问题有关于分布式集群配置文件同步问题 ...
- HttpClient如何解决302重定向问题
最近的接口测试,发现接口地址报302错误,通过上网搜索,发现问题所在,解决办法是需要请求重定向后的URI. package com.btv; import org.apache.http.Header ...
- selenium如何识别验证码
一:前面的文章写了如何右键另存为图片,把验证码存为图片后,接下来就是要做,怎么把图片上的内容获取到,借住tesseract工具 1.下载tesseract:http://sourceforge.net ...