之前就一直有写博客的想法,别人也建议写一写,但一直没有动手写,自己想了一下原因,就一个字:懒、懒、懒。为了改掉这个毛病,决定从今天开始写博客了,一方面对自己掌握的知识做一个梳理,另一方面和大家做一个交流,更能深化对问题的理解。废话好像有点多,好了,各位乘客,收起小桌板,系好安全带,要发车喽。

  Cesium作为一个开源的webgl三维地球渲染引擎,具备很多的基础功能和高级功能。之前已经有很多文章对Cesium做了相关的介绍以及如何使用API等等,我想和大家分享的是Cesium一些功能的底层实现。作为一个源码研究爱好者,希望能将底层优秀代码和大家分享。我们不是代码的生产者,我们只是代码世界的搬运工,哈哈。听说Cesium最近集成了平面剪裁功能,我们赶紧去看一看。

一 Cesium平面裁剪效果

  Cesium裁剪模型的效果如下:

              

  这就是Cesium中根据一个平面对模型进行裁剪的效果,看上去很神奇。除了可以对单个模型进行裁剪,还支持对3D Tiles模型、地形进行裁剪,裁剪面可以定义成单个面也可以设置成多个面。

二 Cesium平面裁剪调用

  在Cesium中添加模型以及对模型进行裁剪非常简单好用,只需下面几行代码就可以实现: 

 1 var modelEntityClippingPlanes;//定义的裁剪平面集合
2 function loadModel(url) {
3 var clippingPlanes = [
4 new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 0.0, -1.0), -100.0)
5 ];//裁剪平面数组
6
7 modelEntityClippingPlanes = new Cesium.ClippingPlaneCollection({
8 planes : clippingPlanes,
9 edgeWidth : viewModel.edgeStylingEnabled ? 1.0 : 0.0
10 });
11   //更新裁剪平面的位置
12 function updateClippingPlanes() {
13 return modelEntityClippingPlanes;
14 }
15   //添加要裁剪的飞机模型,并设置裁剪平面
16 var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 100.0);
17 var heading = Cesium.Math.toRadians(135.0);
18 var pitch = 0.0;
19 var roll = 0.0;
20 var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
21 var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
22 var entity = viewer.entities.add({
23 name : url,
24 position : position,
25 orientation : orientation,
26 model : {
27 uri : url,
28 scale : 8,
29 minimumPixelSize : 100.0,
30 clippingPlanes : new Cesium.CallbackProperty(updateClippingPlanes, false)//重要,设置裁剪平面的地方
31 }
32 });
33
34 viewer.trackedEntity = entity;
35   //将绘制的裁剪平面绘制到场景中
36 for (var i = 0; i < clippingPlanes.length; ++i) {
37 var plane = clippingPlanes[i];
38 var planeEntity = viewer.entities.add({
39 position : position,
40 plane : {
41 dimensions : new Cesium.Cartesian2(300.0, 300.0),
42 material : Cesium.Color.WHITE.withAlpha(0.1),
43 plane : new Cesium.CallbackProperty(createPlaneUpdateFunction(plane, Cesium.Matrix4.IDENTITY), false),
44 outline : true,
45 outlineColor : Cesium.Color.WHITE
46 }
47 });
48
49 planeEntities.push(planeEntity);
50 }
51 }

三 实现原理剖析

  通过分析Cesium源码发现裁剪的实现是在片源着色器中,在视空间坐标系下通过判断模型与裁剪位置构成向量与裁剪平面法向量点乘的正负来判断片源是否剔除。如果点乘为正,说明两个向量的夹角小于90度,在裁剪面要显示的一侧,保留,否则剔除。通过下面这张图应该能更容易理解一点。

  其中,绿色为裁剪平面,O点为裁剪平面的位置点,OA是裁剪平面的法向量,B点为模型的某个顶点,通过判断向量OA与OB点乘的结果就可以判断模型顶点是否需要剔除。下面分析一下Cesium中代码的实现。Cesium通过在绘制Model的片源着色器代码中追加一段代码实现平面裁剪,追加后的代码如下:

 1 precision highp float;
2 varying vec3 v_normal;
3 varying vec2 v_texcoord0;
4 uniform sampler2D u_diffuse;
5 uniform vec4 u_specular;
6 uniform float u_shininess;
7 void gltf_clip_main() {
8 vec3 normal = normalize(v_normal);
9 vec4 color = vec4(0., 0., 0., 0.);
10 vec4 diffuse = vec4(0., 0., 0., 1.);
11 vec4 specular;
12 diffuse = texture2D(u_diffuse, v_texcoord0);
13 specular = u_specular;
14 diffuse.xyz *= max(dot(normal,vec3(0.,0.,1.)), 0.);
15 color.xyz += diffuse.xyz;
16 color = vec4(color.rgb * diffuse.a, diffuse.a);
17 gl_FragColor = color;
18 }
19 vec4 getClippingPlane(sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)
20 {
21 int pixY = clippingPlaneNumber / 1;
22 int pixX = clippingPlaneNumber - (pixY * 1);
23 float u = (float(pixX) + 0.5) * 1.0;
24 float v = (float(pixY) + 0.5) * 0.5;
25 vec4 plane = texture2D(packedClippingPlanes, vec2(u, v));
26 return czm_transformPlane(plane, transform);
27 }
28
29 float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)
30 {
31 bool clipped = true;
32 vec4 position = czm_windowToEyeCoordinates(fragCoord);
33 vec3 clipNormal = vec3(0.0);
34 vec3 clipPosition = vec3(0.0);
35 float clipAmount = 0.0;
36 float pixelWidth = czm_metersPerPixel(position);
37 for (int i = 0; i < 1; ++i)
38 {
39 vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);
40 clipNormal = clippingPlane.xyz;
41 clipPosition = -clippingPlane.w * clipNormal;
42 float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;
43 clipAmount = max(amount, clipAmount);
44 clipped = clipped && (amount <= 0.0);
45 }
46 if (clipped)
47 {
48 discard;
49 }
50 return clipAmount;
51 }
52
53 uniform sampler2D gltf_clippingPlanes;
54 uniform mat4 gltf_clippingPlanesMatrix;
55 uniform vec4 gltf_clippingPlanesEdgeStyle;
56 void main()
57 {
58 gltf_clip_main();
59 float clipDistance = clip(gl_FragCoord, gltf_clippingPlanes, gltf_clippingPlanesMatrix);
60 vec4 clippingPlanesEdgeColor = vec4(1.0);
61 clippingPlanesEdgeColor.rgb = gltf_clippingPlanesEdgeStyle.rgb;
62 float clippingPlanesEdgeWidth = gltf_clippingPlanesEdgeStyle.a;
63 if (clipDistance > 0.0 && clipDistance < clippingPlanesEdgeWidth)
64 {
65 gl_FragColor = clippingPlanesEdgeColor;
66 }
67 }

  其中,gltf_clip_main函数中的内容是没有追加平面裁剪之前片源着色器中的main函数中的代码,主要是负责绘制模型本身。我们看到和平面裁剪相关的uniform变量有gltf_clippingPlanes、gltf_clippingPlanesMatrix、gltf_clippingPlanesEdgeStyle三个,其中gltf_clippingPlanes是sampler2D类型,将所有的裁剪平面的position、normal放到一张图片中;gltf_clippingPlanesMatrix变量是将平面从世界坐标转换到视空间下的变换矩阵;gltf_clippingPlanesEdgeStyle存储了裁剪的样式信息,其中gltf_clippingPlanesEdgeStyle.rgb存储了裁剪衔接处模型的颜色,gltf_clippingPlanesEdgeStyle.a存储了裁剪边界处的像素宽度。

  在调用gltf_clip_main函数后,通过clip函数实现裁剪,并在像素没有剔除的情况下返回该片源与裁剪平面的像素距离。clip函数是整个裁剪功能实现的关键所在,我们将精力重点放在clip这个函数上。通过czm_windowToEyeCoordinates这个Cesium自带函数计算当前片源在视空间下的三维坐标position,然后通过czm_metersPerPixel这个函数计算视空间下position这个位置每个像素代表的空间长度。接下来就是通过一个for循环计算每个裁剪平面对该像素的影响。我们来分析一下for循环中的内部代码。首先通过getClippingPlane这个函数计算出在视空间下的平面坐标,clipNormal表示平面的法线,clipPosition代表平面的位置,然后position.xyz - clipPosition计算出了模型顶点和平面位置之间的向量,此处暂记为向量m,dot(clipNormal, (position.xyz - clipPosition))得到该向量和平面法线的点乘结果,由于clipNormal为单位向量,所以dot(clipNormal, (position.xyz - clipPosition))的结果就是向量m在法线方向上的投影长度,用这个长度除以pixelWidth转换为像素,记为amount。clipAmount取每次平面计算结果的最大值,对于单个的平面裁剪当amount <  0时,将该片源剔除,对于多个平面,通过clipped && (amount <= 0.0)进行判断,最后在没剔除的情况下返回clipAmount,这就是clip函数的所有内容。

  通过clip函数计算出了clipDistance(模型顶点和平面的像素距离),最后就是设置裁剪处的颜色gl_FragColor = clippingPlanesEdgeColor。好了,这就是模型平面裁剪的所有内容了。

四 总结

  模型的平面裁剪都是在片源着色器中完成的,空间位置的计算都是在视空间下进行。视空间在一些GPU效果实现中发挥着很大作用,很多计算都是在视空间下进行的。第一篇博客,语言组织,页面布局都没有经验,不足之处请大家谅解,哈哈。睡觉,睡觉,睡觉!!!

PS:Cesium交流可以扫码加群,期待你的加入!!!

Cesium源码剖析---Clipping Plane的更多相关文章

  1. Cesium源码剖析---Post Processing之物体描边(Silhouette)

    Cesium在1.46版本中新增了对整个场景的后期处理(Post Processing)功能,包括模型描边.黑白图.明亮度调整.夜视效果.环境光遮蔽等.对于这么炫酷的功能,我们绝不犹豫,先去翻一翻它的 ...

  2. Cesium源码剖析---视频投影

    Cesium中的视频投影是指将视频作为一种物体材质,实现在物体上播放视频的效果.这个功能在Cesium早期版本中就支持了,在Code Example中有一个示例.今天就来分析一下其内部实现原理. 1. ...

  3. Cesium源码剖析---Ambient Occlusion(环境光遮蔽)

    Ambient Occlusion简称AO,中文没有太确定的叫法,一般译作环境光遮蔽.百度百科上对AO的解释是这样的:AO是来描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果,可以解决或改善漏光 ...

  4. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  5. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

  6. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  7. 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析

    项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...

  8. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  9. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

随机推荐

  1. 【LeetCode】1150. Check If a Number Is Majority Element in a Sorted Array 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字典 二分查找 日期 题目地址:https://lee ...

  2. 【LeetCode】820. 单词的压缩编码 Short Encoding of Words(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode-cn.com/problems/short- ...

  3. python学习第五天:python基础(string、list、tuple)

    首先,什么是sequence(序列)操作? 字符串的特性被称为sequence(序列) H o w a r e y o u ? 就好像存储在一个个连续的单元格里面,每个单元格存储一个字符,每个字符就是 ...

  4. 洛谷 P1439 【模板】最长公共子序列(DP,LIS?)

    题目描述 给出1-n的两个排列P1和P2,求它们的最长公共子序列. 输入输出格式 输入格式: 第一行是一个数n, 接下来两行,每行为n个数,为自然数1-n的一个排列. 输出格式: 一个数,即最长公共子 ...

  5. Java并发:五种线程安全类型、线程安全的实现、枚举类型

    1. Java中的线程安全 Java线程安全:狭义地认为是多线程之间共享数据的访问. Java语言中各种操作共享的数据有5种类型:不可变.绝对线程安全.相对线程安全.线程兼容.线程独立 ① 不可变 不 ...

  6. Pydantic使用

    Pydantic可以在代码运行时提供类型提示, 数据校验失败时提供友好的错误提示, 使用Python的类型注解来进行数据校验和settings管理 一般使用 from datetime import ...

  7. <数据结构>XDOJ332.二叉排序树的判定

    问题与解答 问题描述 给定一个二叉树,判断其是否是一个有效的二叉排序树. 假设一个二叉排序树具有如下特征: 结点的左子树只包含小于当前结点的树. 结点的右子树只包含大于当前结点的树. 所有左子树和右子 ...

  8. <数据结构>图的最短路径问题

    目录 最短路径问题 Dijstra算法:中介点优化 基本步骤 伪代码 在实现过程中的关键问题 代码实现 邻接矩阵版 邻接表版 时间复杂度:O(VlogV+E) 算法存在的问题:存在负权边时会失效 Be ...

  9. VMware客户端vSphere Web Client新建虚拟机

    1.说明 vSphere Web Client是为管理员提供的一款通用的. 基于浏览器的VMware管理工具, 能够监控并管理VMware基础设施. 由于需要登录的宿主机安装的是ESXi-6.5.0, ...

  10. Ranger-Hdfs插件安装

    Ranger-Hdfs插件ranger-0.6.0-hdfs-plugin安装到Hdfs的所有NameNode节点, 其他的DataNode节点不需要安装. 1. 登陆hdfs安装的用户,hdfs/z ...