今天来看看GroundPrimitive,选择GroundPrimitive有三个目的:1 了解GroundPrimitive和Primitive的区别和关系 2 createGeometry的特殊处理 3 如何通过阴影体的方式实现贴地效果。

GroundPrimitive.prototype.update

可以认为GroundPrimitive是Primitive的扩展,通过Update我们可以很好的理解这个过程:

GroundPrimitive.prototype.update = function(frameState) {
if (!defined(this._primitive)) {
// Key 1
setMinMaxTerrainHeights(this, rectangle, frameState.mapProjection.ellipsoid); // Key 2
for (i = 0; i < length; ++i) {
instance = instances[i];
geometry = instance.geometry;
instanceType = geometry.constructor;
groundInstances[i] = new GeometryInstance({
geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this),
getComputeMaximumHeightFunction(this)),
attributes : instance.attributes,
id : instance.id,
pickPrimitive : this
});
} // Key 3
var that = this;
primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) {
createBoundingVolume(that, frameState, geometry);
};
primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) {
createRenderStates(that, context);
};
primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) {
createShaderProgram(that, frameState);
};
primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) {
createCommands(that, undefined, undefined, true, false, colorCommands, pickCommands);
};
primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses);
}; this._primitive = new Primitive(primitiveOptions);
} // Key 4
this._primitive.update(frameState);
}

Key 1是计算该Primitive所在GlobeTile对应地形的最高点和最低点,这里只是一个粗略的计算,假如该Primitive的范围覆盖了多个Tile则取最后一个Tile高度的最大最小值,另外Cesium有一个json文件,里面保存了全球前6层的地球切片,里面的信息是该Tile对应高度的极值。因为GroundPrimitive对应的几何对象要贴地,所以这个信息会在构建geometry的时候用到。

Key 2是构建适合的Geometry,因为需要实现贴地效果,所以该geometry做了一个类似拉伸的效果,从一个面拉伸为一个体,这也是为什么要获取该Entity对应地形的极值,因为要根据极值拉伸到对应的高度。这个后面详细介绍。

key 3是构建了primitiveOptions,重载了里面的Render模块实现方式,Key2在数据层面上为贴地做准备,那Key3则在渲染逻辑上根据对应的geometry实现阴影体,从渲染角度实现了贴地

Key4,一切准备就绪,最后还是构造的Primitive,通过update完成最后的执行过程,这个过程就和之前的Primitive相同。

可见,GroundPrimitive在渲染的流程上和Primitive并无本质的不同,经过Key1~3的步骤,构造出可以贴地的Primitive,最终还是以Primitive的方式来执行渲染。

createShadowVolume

什么叫阴影体,一图以蔽之:

图1

图2

我想到了大话西游里面,至尊宝在变成齐天大圣的时候跟观音说的那句话“以前我看事物,是用肉眼去看。但在我死去那一刹那,我开始用心眼去看这个世界”。图1是我们看到的效果,而图2才是这个geometry真实的样子。希望你也能看得前所未有的那么清楚。而在实现中这个过程是先有图2那样的阴影体,再有图1的贴地效果,先有真家伙在做视觉上的假效果。我们还是以Rectangle作为例子来说一下这个过程。这个最简单,其他的在思路上大同小异,这些几何算法就不深究了,太专业了,所以只能望而却步。

RectangleGeometryLibrary.computeOptions

首先就是做好准备工作。对于Rectangle,默认每个网格的大小是PI/180的弧度,根据其高宽来计算这个矩形需要多少个网格,每个网格大概的高宽,最终返回一个json对象,这就是该Rectangle的几何信息,下面计算网格的时候就是以此为依据。

constructRectangle

好比你面对一堵墙,根据之前computePosition的参数,你采购了对应了一批瓷砖,下面的任务就是把瓷砖贴到墙上去。constructRectangle就是贴瓷砖的这个过程。

row) { for (var col = 0; col < width; ++col) { RectangleGeometryLibrary.computePosition(options, row, col, position, st); positions[posIndex++] = position.x; positions[posIndex++] = position.y; positions[posIndex++] = position.z; } } // Key 2 var geo = calculateAttributes(positions, vertexFormat, ellipsoid, options.tangentRotationMatrix); // Key 3 var indicesSize = 6 * (width - 1) * (height - 1); var indices = IndexDatatype.createTypedArray(size, indicesSize); var index = 0; var indicesIndex = 0; for (var i = 0; i < height - 1; ++i) { for (var j = 0; j < width - 1; ++j) { var upperLeft = index; var lowerLeft = upperLeft + width; var lowerRight = lowerLeft + 1; var upperRight = upperLeft + 1; indices[indicesIndex++] = upperLeft; indices[indicesIndex++] = lowerLeft; indices[indicesIndex++] = upperRight; indices[indicesIndex++] = upperRight; indices[indicesIndex++] = lowerLeft; indices[indicesIndex++] = lowerRight; ++index; } ++index; } geo.indices = indices; return geo; }

position就是每块瓷砖的左上角位置,也就是vbo中对应的顶点数据,同时在calculateAttributes会根据需要创建法线,切线等数据;每一个瓷砖都可以通过两个三角形构成,就是一条对角线的工作,这就是indices的事情,对应vbo中的顶点索引:

constructExtrudedRectangle

假设我们生活在一个小镇,在这个小镇里面,所有人都生活在二维坐标系下,也就是点线面的几何形状,其实这个时候我们眼中的面也不过是线。突然有一天,一个球体来到这个小镇。在这个小镇居民的眼中,只能看到这个球的一个切面而已,换句话说,即使真的有一个三维的物体来到这个小镇,在小镇居民的眼里,他们也和二维物体一样并无差别(很多人由于自身能力的限制导致了无法突破自身的觉悟)。这个球不停的上下浮动,在小镇居民的眼里,会发现这条线的长度不停的变化,这时候有一个居民突然开窍了,于是骑在了这个球的身上,看到了一个全新的世界---三维的世界。

这个故事说的比较仓促,里面蕴涵了一个降维的思想,反其道而行之也可以做到二维升级到三维的过程。这也是constructExtrudedRectangle的思路。

function constructExtrudedRectangle(options) {
var topBottomGeo = constructRectangle(options); // 沿椭球切线上移到最高点
var topPositions = PolygonPipeline.scaleToGeodeticHeight(topBottomGeo.attributes.position.values, maxHeight, ellipsoid, false);
var length = topPositions.length;
var newLength = length*2;
var positions = new Float64Array(newLength);
positions.set(topPositions);
// 沿椭球切线上移到最低点
var bottomPositions = PolygonPipeline.scaleToGeodeticHeight(topBottomGeo.attributes.position.values, minHeight, ellipsoid);
positions.set(bottomPositions, length);
topBottomGeo.attributes.position.values = positions; for (i = 0; i < indicesLength; i += 3) {
newIndices[i + indicesLength] = indices[i + 2] + posLength;
newIndices[i + 1 + indicesLength] = indices[i + 1] + posLength;
newIndices[i + 2 + indicesLength] = indices[i] + posLength;
} // 立方体的四个围墙
for (i = 0; i < area; i+=width) {
wallPositions = addWallPositions(wallPositions, posIndex, i*3, topPositions, bottomPositions);
posIndex += 6;
if (vertexFormat.st) {
wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i*2, topSt);
stIndex += 4;
}
} for (i = area-width; i < area; i++) {
} for (i = area-1; i > 0; i-=width) {
} for (i = width-1; i >= 0; i--) {
}
}

代码注释如上,首先,不难理解矩形拉伸的结果是立方体。我们先把该Rectangle上移到最高点,这样有了一个顶,然后下移到最低点,这样就有了一个低,这里对低的indice的顺序求逆,用来区分正反面。这里,这个上移和下移的方向则是沿着地球椭球体的法线方向,由scaleToGeodeticHeight方法来实现。有了上下两个面,然后下来四个for循环就是构建这个围墙。

围墙构建的思路大致这样,如上123是顶面的一个边,abc则是地面对应的边,最终这面墙的顺序为:(1 a 2 b 3 c)组成其顶点数据,然后对应的构建其顶点索引,如绿色的线,最终构建完这面墙,同理构建其他三面围墙。这样实现了面拉伸为体。

Shadow Volume Rendering

上图取自GPUgems,你会有一个详细的了解。版本一:大致上就是通过模版缓冲区先渲染一遍,这样通过正反面的区分,最终获取该立方体对应的阴影部分。换人类的语言来重新描述一下:版本二:因为我们的立方体的顶面和低面都是取的当前Tile中的极值,所以该立方体肯定是和地球完全相交的,就好比齐天大圣把他的如意金箍棒插到地上,贴地的结果就是这个棍子和地面相交的部分。再看看上图,是否就能理解了。

通过版本二的描述,我们理解了这个思路,下面解释一下版本一,我们可以明白计算机,WebGL的方式来理解这个过程。

如上图,是一个立方体,下面正好贴地,上面黄色部分是一个光源,照射该物体后,我们能够看到的区域在模版缓冲区中+1,我们用蓝色的标识:

接着,面对我们的视线它的背面区域,我们对这个区域-1,用棕色来标记,如下图:

we got it!两个区域合并在一起,相同的区域,在缓冲期里面中和掉,值为0,而剩下的不为零(为1)的区域就是阴影部分,也是该立方体和地面相交的部分。因此,在阴影体渲染中,第一,前提是地球部分要优先绘制,这样就有深度信息,接着对阴影体渲染两次,一次是在模版缓冲区中,标识出阴影区域,然后在渲染到纹理或屏幕,这时进行过滤,只对模版缓冲区不为零(或等于1)的部分渲染,完成阴影体渲染。

如下图,第一个stencilPreloadRenderState是渲染到模版缓冲区中,可以看到正面为increase,反面为decrease;第二个colorRenderState是渲染到纹理,注意渲染条件为not equal,对应的mask为~0,这里,改为0x1更准确一下,效果也是一样的:

var stencilPreloadRenderState = {
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
},
stencilTest : {
enabled : true,
frontFunction : StencilFunction.ALWAYS,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.DECREMENT_WRAP,
zPass : StencilOperation.DECREMENT_WRAP
},
backFunction : StencilFunction.ALWAYS,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.INCREMENT_WRAP,
zPass : StencilOperation.INCREMENT_WRAP
},
reference : 0,
mask : ~0
},
depthTest : {
enabled : false
},
depthMask : false
}; var colorRenderState = {
stencilTest : {
enabled : true,
frontFunction : StencilFunction.NOT_EQUAL,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
backFunction : StencilFunction.NOT_EQUAL,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
reference : 0,
mask : ~0
},
depthTest : {
enabled : false
},
depthMask : false,
blending : BlendingState.ALPHA_BLEND
};

渲染的过程在GroundPrimitive中createColorCommands中,可以看到对一个Primitive也会渲染两次,先渲染模版缓冲期,在渲染color,两个的顺序不能反。这个就是构建两个DrawCommand的过程,基于以前的章节,这个应该不难理解,这里就不再赘述了。

总结

至此,我们了解了Cesium在Worker线程中createGeometry的大致流程,也通过Rectangle为例,看到了构建阴影体的算法思路,最后也了解了通过模版缓冲区实现渲染阴影体的过程。这一个完成的流程,可以很好的体现Cesium在算法和OpenGL渲染中很全面,很专业的水准,而且这一套规范是以开源的方式来贡献出来,这也是难能可贵的。仿佛一枚绚丽的明珠放在了人类象牙塔尖,人人都有拥有它的机会,而且没有人可以据为己有。

原文地址:https://www.cnblogs.com/fuckgiser/p/6216050.html

Cesium原理篇:GroundPrimitive【转】的更多相关文章

  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原理篇:GroundPrimitive

    今天来看看GroundPrimitive,选择GroundPrimitive有三个目的:1 了解GroundPrimitive和Primitive的区别和关系 2 createGeometry的特殊处 ...

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

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

  6. Cesium原理篇:3D Tiles(2)数据结构

    上一节介绍3D Tiles渲染调度的时候,我们提到目前Cesium支持的Cesium3DTileContent目前支持如下类型: Batched3DModel3DTileContent Instanc ...

  7. Cesium原理篇:Property

    之前主要是Entity的一个大概流程,本文主要介绍Cesium的属性,比如defineProperties,Property(ConstantProperty,CallbackProperty,Con ...

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

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

  9. Cesium原理篇:3最长的一帧之地形(4:重采样)

           地形部分的原理介绍的差不多了,但之前还有一个刻意忽略的地方,就是地形的重采样.通俗的讲,如果当前Tile没有地形数据的话,则会从他父类的地形数据中取它所对应的四分之一的地形数据.打个比方 ...

随机推荐

  1. Java语法知识点

    1. 特殊字符 a) \n   换行符 b)  \t   制表符 <--------------------------------------------------------------- ...

  2. 【Python】列表推导式

    1. 列表推导式 list1 = [1, 3, 5, 6, 8] list2 = [x * 2 for x in list1] print(list2) # [2, 6, 10, 12, 16]

  3. 【转载】自定义View,有这一篇就够了

    为了扫除学习中的忙点,尽可能多的覆盖Android知识的边边角角,决定对自定义View做一个稍微全面一点的使用方法总结,在内容上面并没有什么独特的地方,其他大神们博客上面基本上都有讲这方面的内容,如果 ...

  4. python笔记43-加解密AES/CBC/pkcs7padding

    前言 有些公司对接口的安全要求比较高,传参数的时候,不会明文的传输,先对接口加密,返回的数据也加密返回. 目前比较常见的加密方式是AES/CBC/pkcs7padding. AES五种加密模式 在AE ...

  5. JS实现俄罗斯方块

    在80后.90后的儿时记忆里,俄罗斯方块是必备的消遣小游戏,它的玩法非常简单基本大家都懂,但如何用编程语言开发一款儿时同款「俄罗斯方块」,恐怕知道的同学就很少啦. 位置掩码和旋转掩码 俄罗斯方块游戏中 ...

  6. pace.js[转载]

    pace.js监控了什么: pace.js对于加载进度监控了什么呢?通过阅读源码,我们看到整体的进度有四个部分组成:document,elements,eventLag和ajax这四种监视器(Moni ...

  7. Brief Introduction to SDK – JRE – JVM – JIT

    Brief Introduction to SDK – JRE – JVM – JIT SDK This is complete collection of Java stuff, as it has ...

  8. react的优点:兼容了dsl语法与UI的组件化管理

    react的优点:兼容了dsl语法与UI的组件化管理. 组件化管理的dsl描述 UI: 虚拟dom:

  9. React 顶层 API

    概览 组件 使用 React 组件可以将 UI 拆分为独立且复用的代码片段,每部分都可独立维护.你可以通过子类 React.Component 或 React.PureComponent 来定义 Re ...

  10. Linux安装pycharm并添加图标到桌面

    安装: 1.到pycharm官网下载Linux版本的pycharm包. 2.打开中端 cd到下载的文件夹,默认为 ~/Downloads/ 文件夹下 3.执行命令 tar -xvzf pycharm- ...