地形部分的原理介绍的差不多了,但之前还有一个刻意忽略的地方,就是地形的重采样。通俗的讲,如果当前Tile没有地形数据的话,则会从他父类的地形数据中取它所对应的四分之一的地形数据。打个比方,当我们快速缩放影像的时候,下一级的影像还没来得及更新,所以会暂时把当前Level的影像数据放大显示, 一旦对应的影像数据下载到当前客户端后再更新成精细的数据。Cesium中对地形也采用了这样的思路。下面我们具体介绍其中的详细内容。

       上图是一个大概流程,在创建Tile的时候(prepareNewTile),第一时间会获取该Tile父节点的地形数据(upsampleTileDetails),然后构造出upsampledTerrain对象,它是TileTerrain对象,只是一个包含父类地形信息的空壳。接着,开始创建地形网格(processTerrainStateMachine)。

       这里就有两个逻辑,如果当前没有地形数据,也就是EllipsoidTerrainProvider的情况,这样会直接创建HeightmapTerrainData。因此状态是TerrainState.RECEIVED,这种情况下不需要重采样;如果请求了真实的地形数据,比如CesiumTerrainProvider,无论是请求高度图还是STK,只要有异步请求,则会执行processUpsampleStateMachine韩式,最终实现重采样(sourceData.upsample)。

HeightmapTerrainData.prototype.upsample

       我们先了解一下高度图下的实现。高度图,顾名思义也是一种图了,所以这个重采样的方式和普通的图片拉伸算法一致。比如一个2*2的图片,放大至4*4的大小,这里就有一个插值的过程。比如线性差值,会取相邻的两个像素颜色,加权求值,或者双线性插值,取周边四个像素,加权求值。这让我想到了GDI中对图片是采用线性了,而Photoshop里面则有很多专业的选项,某些逗逼用户经常拿着PS拉伸的效果来做对比,说我们图片拉伸的效果不如PS。等我们做完了,又拿CorelDraw来对比矢量效果。但你有很难从技术和产品的角度来和用户沟通其中的利弊。这是题外话了,我们来看一下Cesium具体的代码:

for (var j = 0; j < height; ++j) {
var latitude = CesiumMath.lerp(destinationRectangle.north, destinationRectangle.south, j / (height - 1));
for (var i = 0; i < width; ++i) {
var longitude = CesiumMath.lerp(destinationRectangle.west, destinationRectangle.east, i / (width - 1));
var heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration);
setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, j * width + i, heightSample);
}
}

       这是两个for循环,遍历目标图片中每一个经纬度对应父类图片中该位置的高度值。下面是一个切片四叉树的示意图,高度图也是一个思路,只是其中每一个像素不是颜色,而是高度值:

 

 

       如同可见,子类切片的像素大小和父类是一样的,一般都是256*256的切片,但具体到地理范围上则只有父类的四分之一,所以顾名思义是四叉树。这样,从父类到子类放大的过程中,父类的一个像素,在子类中占了4个像素。也就是一个1:4的映射关系。尽管像素都是整数的,但我们在插值的过程中会有一个亚像素的概念。这样,同一个位置,经纬度都是相同的,但在子类和父类中的uv是不一样的,在对子类的遍历中,获取同一个经纬度对应父类uv的位置,进而得知在父类中相邻的四个像素和权重,进而插值获取其高度(颜色),如下是一个示意代码:

function interpolateMeshHeight(){
var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west);
var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south); var widthEdge = (skirtHeight > 0) ? width - 1 : width;
var westInteger = fromWest | 0;
var eastInteger = westInteger + 1;
if (eastInteger >= widthEdge) {
eastInteger = width - 1;
westInteger = width - 2;
} var dx = fromWest - westInteger; return southwestHeight + (dX * (northeastHeight - northwestHeight)) + (dY * (northwestHeight - southwestHeight))
}

QuantizedMeshTerrainData.prototype.upsample

       高度图毕竟还都是离散的点值,并没有构网,因而节点之间还没有建立关联,差值算法也相对容易一些。而STK的数据,本身已经是TIN的三角网结构了。这时,在父类中切割出四分之一来就有点复杂了。再打个比方,如果高度图相当于一个棋盘上均匀的大米,然后你四等分,取走其中的一份,而TIN三角网则相当于一个错综复杂的下水管,你要切走四分之一。这要怎么做到呢。假设此时我们有一把利刃,把这个TIN网格横一刀竖一刀,这时,我们迅速的把漏水的管道密封(形成新的节点),这样就实现了TIN三角网重采样的过程。

      当然,这个过程相比高度图要复杂的多,因此Cesium中创建了Worker线程,切割的过程都是在线程中完成。具体到算法则如下:

for (i = 0; i < parentIndices.length; i += 3) {
var i0 = parentIndices[i];
var i1 = parentIndices[i + 1];
var i2 = parentIndices[i + 2]; var u0 = parentUBuffer[i0];
var u1 = parentUBuffer[i1];
var u2 = parentUBuffer[i2]; triangleVertices[0].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i0);
triangleVertices[1].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i1);
triangleVertices[2].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i2); // Clip triangle on the east-west boundary.
var clipped = Intersections2D.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isEastChild, u0, u1, u2, clipScratch); // Get the first clipped triangle, if any.
clippedIndex = 0; if (clippedIndex >= clipped.length) {
continue;
}
clippedIndex = clippedTriangleVertices[0].initializeFromClipResult(clipped, clippedIndex, triangleVertices); if (clippedIndex >= clipped.length) {
continue;
}
clippedIndex = clippedTriangleVertices[1].initializeFromClipResult(clipped, clippedIndex, triangleVertices); if (clippedIndex >= clipped.length) {
continue;
}
clippedIndex = clippedTriangleVertices[2].initializeFromClipResult(clipped, clippedIndex, triangleVertices); // Clip the triangle against the North-south boundary.
clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isNorthChild, clippedTriangleVertices[0].getV(), clippedTriangleVertices[1].getV(), clippedTriangleVertices[2].getV(), clipScratch2); if(clipped2.length == 10 && clipped.length == 10)
var i = 10; addClippedPolygon(uBuffer, vBuffer, heightBuffer, normalBuffer, indices, vertexMap, clipped2, clippedTriangleVertices, hasVertexNormals); // If there's another vertex in the original clipped result,
// it forms a second triangle. Clip it as well.
if (clippedIndex < clipped.length) {
clippedTriangleVertices[2].clone(clippedTriangleVertices[1]);
clippedTriangleVertices[2].initializeFromClipResult(clipped, clippedIndex, triangleVertices); clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isNorthChild, clippedTriangleVertices[0].getV(), clippedTriangleVertices[1].getV(), clippedTriangleVertices[2].getV(), clipScratch2);
addClippedPolygon(uBuffer, vBuffer, heightBuffer, normalBuffer, indices, vertexMap, clipped2, clippedTriangleVertices, hasVertexNormals);
}
}

      这个算法有点复杂,但思路清楚了,也就迎刃而解。首先,遍历顶点索引,每次+3,因为三个点构成一个三角形,所以完成了对所有三角网遍历切割的过程。在每次循环中,u0,u1,u2是三角形对应的三个点,然后通过Intersections2D.clipTriangleAtAxisAlignedThreshold实现三角形的切割算法。

      这个切割的过程其实就是三角形和直线求交的过程,但更直观一些,因为这个直线是竖直或水平的,如果直线和三角形没有交点,那表示该三角形要么全在子类,要么全不在,不需要切割,我们不讨论这种情况。如果相交,则有两种情况:

 

 

       第一种情况,只保留了原三角形一个顶点,但会产生两个新的节点(蓝色),最终形成一个三角形。针对这种情况,我们在做一次水平的切合(水平线和蓝色三角形的相交计算),这个算法是一致的。

 

 

       第二种情况,保留了原三角形两个顶点,同时也产生了两个新的节点(必然是偶数节点,哥尼斯堡七问题),这时,会形成两个三角形,则我们需要对这两个三角形单独做一次水平切割。

      经过如上的逻辑,我们就完成了一个三角形的两刀切,当然,算法只提供了一个思路,并没有考虑特殊情况,正好经过顶点对半切,这个在实际中需要做一次额外的判断,避免少算或重复算。细的说里面有两个过程,求交点,构造新的三角形,分别通过clipTriangleAtAxisAlignedThreshold和addClippedPolygon函数实现。这时,如是是第二种情况,则还有一个三角形需要进行水平的切割和构造新三角形的过程。这是为什么会多一个if判断。

       如上,新的,经过重采样的TIN三角网构建完成,Cesium会先渲染这个略微粗糙的地形,等待精细的地形下载完后在更新。当然,通过这个过程,我们能意识到,Cesium并不硬性的要求每一个地形Tile都能够获取到,如果其中一个Tile没有下载到(网络异常或环境限制),也能很好的自适应,而且也不方案该Tile的子类也可以正常渲染和更新。但前提是,根节点的地形数据是必须的。不管是蛋生鸡还是鸡生蛋,你总得现有一样。

Cesium原理篇:3最长的一帧之地形(4:重采样)的更多相关文章

  1. Cesium原理篇:5最长的一帧之影像

    如果把地球比做一个人,地形就相当于这个人的骨骼,而影像就相当于这个人的外表了.之前的几个系列,我们全面的介绍了Cesium的地形内容,详见: Cesium原理篇:1最长的一帧之渲染调度 Cesium原 ...

  2. Cesium原理篇:3最长的一帧之地形(2:高度图)

           这一篇,接着上一篇,内容集中在高度图方式构建地球网格的细节方面.        此时,Globe对每一个切片(GlobeSurfaceTile)创建对应的TileTerrain类,用来维 ...

  3. Cesium原理篇:7最长的一帧之Entity(下)

    上一篇,我们介绍了当我们添加一个Entity时,通过Graphics封装其对应参数,通过EntityCollection.Add方法,将EntityCollection的Entity传递到DataSo ...

  4. Cesium原理篇:7最长的一帧之Entity(上)

    之前的最长的一帧系列,我们主要集中在地形和影像服务方面.简单说,之前我们都集中在地球是怎么造出来的,从这一系列开始,我们的目光从GLOBE上解放出来,看看球面上的地物是如何渲染的.本篇也是先开一个头, ...

  5. Cesium原理篇:3最长的一帧之地形(1)

    前面我们从宏观上分析了Cesium的整体调度以及网格方面的内容,通过前两篇,读者应该可以比较清楚的明白一个Tile是怎么来的吧(如果还不明白全是我的错).接下来,在前两篇的基础上,我们着重讨论一下地形 ...

  6. cesium原理篇(三)--地形(1)【转】

    转自:http://www.cnblogs.com/fuckgiser/p/5824743.html 简述 前面我们从宏观上分析了Cesium的整体调度以及网格方面的内容,通过前两篇,读者应该可以比较 ...

  7. Cesium原理篇:3最长的一帧之地形(3:STK)

    有了之前高度图的基础,再介绍STK的地形相对轻松一些.STK的地形是TIN三角网的,基于特征值,坦白说,相比STK而言,高度图属于淘汰技术,但高度图对数据的要求相对简单,而且支持实时构建网格,STK具 ...

  8. Cesium原理篇:1最长的一帧之渲染调度

    原计划开始着手地形系列,但发现如果想要从逻辑上彻底了解地形相关的细节,那还是需要了解Cesium的数据调度过程,这样才能更好的理解,因此,打算先整体介绍一下Cesium的渲染过程,然后在过渡到其中的两 ...

  9. Cesium原理篇:2最长的一帧之网格划分

    上一篇我们从宏观上介绍了Cesium的渲染过程,本章延续上一章的内容,详细介绍一下Cesium网格划分的一些细节,包括如下几个方面: 流程 Tile四叉树的构建 LOD 流程 首先,通过上篇的类关系描 ...

随机推荐

  1. ajax 全套

    ajax简介 AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术.Ajax不是一种新的编程语言, ...

  2. html中的图像动态加载问题

    首先要说明下文档加载完成是什么概念 一个页面http请求访问时,浏览器会将它的html文件内容请求到本地解析,从窗口打开时开始解析这个document,页面初始的html结构和里面的文字等内容加载完成 ...

  3. hibernate不同版本获取获取sessionFactory

    hibernate4时,我们采用以下方式获取会话工厂: // 1. 解析我们在hibernate.cfg.xml中的配置 Configuration configuration = new Confi ...

  4. redis配置文件英译汉

    # By default Redis asynchronously dumps the dataset on disk. This mode is # good enough in many appl ...

  5. Swift库运行崩溃

    报错如下: 解决方法: 退出 Xcode 找到 DerivedData 文件夹 删除 (路径: ~/Library/Developer/Xcode/DerivedData) 删除 com.apple. ...

  6. java 心得

    11. 最后的笑声 package javaBookPractice; public class LastLaugh { public static void main(String[] args) ...

  7. 第一章-第十五题(谈谈你对压力的看法,以及怎么和别人合作, 帮助别人,把压力转化为动力,在互相帮助的环境中成长。)--By林培文

    压力是一把双刃剑,有害也有利,重点是如何看待压力,从而做到趋利避害,化压力为动力.对于当前的我来说,压力利大于弊.形成这一想法,我经历了好几个阶段.第一个阶段是小学时代,那会整天就知道玩,也没什么压力 ...

  8. U盘安装Ubuntu 10.4 Server

    试了几次的U盘安装Ubuntu Server失败后,只能求助网络了,找了半天终于安装成功了.问题就是虽然从U盘启动了安装程序,但是在系统安装到一半的过程中,会进行CD-ROM的检查,检查不到安装程序, ...

  9. 禁用nested loop join里的spool

    禁用nested loop join里的spool 转载自: https://blogs.msdn.microsoft.com/psssql/2015/12/15/spool-operator-and ...

  10. 介绍.NET 开发必备工具 .NET Portability Analyzer

    随着.NET的原来越开放,不仅仅是开源这么简单了,也意味着.NET程序员要关注越来越多的平台,涵盖.NET Mic Framework, Xamarin,Mono,.NET等等,从windows到li ...