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

  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. python 字符编码讲解

    ANSI不是一种具体的编码格式 ANSI在中文Windows操作系统代码指的是GBK编码 ANSI在中文Mac操作系统代码指的是UTF-8编码 ANSI在其他国家的操作系统中有其他的编码格式 #ASC ...

  2. 利用struts2上传文件时,如果文件名中含有-符号,那么会出错

    利用struts2上传文件时,如果文件名中含有-符号,那么会出错 报错如下: HTTP Status 500 - C:\Program Files\Apache Software Foundation ...

  3. IPtables 之“四表五链”

    目录 架构图 IP tables 简介 包过滤防火墙 Iptables如何过滤 "四表" "五链" Iptables流程 架构图 公司架构模式(酒店迎宾比喻) ...

  4. 页面图片懒加载、延迟加载(lazyload)

    文档:http://www.h-ui.net/lib/jQuery.lazyload.js.shtml github地址:https://github.com/jieyou/lazyload Lazy ...

  5. 【LeetCode】589. N-ary Tree Preorder Traversal 解题报告 (Python&C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 迭代 日期 题目地址:https://leetc ...

  6. Java不可变类与final类

    概念 Java的不可变类是指八个基础类型的包装类和String,他们的数据成员是不可变的.使用加法等操作时,其实是创建了一个新的对象. Java的final类是对类用关键字final进行修饰,说明该类 ...

  7. E. Number With The Given Amount Of Divisors

    E. Number With The Given Amount Of Divisors time limit per test 2 seconds memory limit per test 256 ...

  8. Categorical Reparameterization with Gumbel-Softmax

    目录 概 主要内容 Gumbel distribution Jang E., Gu S. and Poole B. Categorical reparameterization with gumbel ...

  9. Generative Adversarial Nets (GAN)

    目录 目标 框架 理论 数值实验 代码 Generative Adversarial Nets 这篇文章,引领了对抗学习的思想,更加可贵的是其中的理论证明,证明很少却直击要害. 目标 GAN,译名生成 ...

  10. ZooKeeper基础知识总结

    数据模型 ZooKeeper数据模型是一个树状的数据结构,类似于文件系统:和文件系统的区别在于树中的每一个节点(叶子节点与非叶子节点)都可以保存数据,且每个节点的访问都必须从根节点开始,以斜线作为分隔 ...