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

  Ambient Occlusion简称AO,中文没有太确定的叫法,一般译作环境光遮蔽。百度百科上对AO的解释是这样的:AO是来描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果,可以解决或改善漏光、飘和阴影不实等问题,解决或改善场景中缝隙、褶皱与墙角、角线以及细小物体等的表现不清晰问题,综合改善细节尤其是暗部阴影,增强空间的层次感、真实感,同时加强和改善画面明暗对比,增强画面的艺术性。AO简单来说就是根据周围物体对光线的遮挡程度,改变明暗效果。AO具体理论原理在网上都可以找到,感兴趣的可以去查阅,此处只把Cesium中AO的实现过程作一下介绍。闲言少叙,直接上代码。

1. 开启AO及效果

  在Cesium中开启AO效果非常简单,和之前讲的开启Silhouette效果类似,代码如下:

1 var ambientOcclusion = viewer.scene.postProcessStages.ambientOcclusion;
2 ambientOcclusion.enabled = true;
3 ambientOcclusion.uniforms.ambientOcclusionOnly = false;
4 ambientOcclusion.uniforms.intensity = 3;
5 ambientOcclusion.uniforms.bias = 0.1;
6 ambientOcclusion.uniforms.lengthCap = 0.03;
7 ambientOcclusion.uniforms.stepSize = 1;
8 ambientOcclusion.uniforms.blurStepSize = 0.86;

没有开启AO效果如下图一,开启AO效果如下图二,单纯的AO图如图三:

2. JS内部代码实现

  在PostProcessStageLibrary类中添加AO功能的代码如下:

  1 PostProcessStageLibrary.createAmbientOcclusionStage = function() {
  2         var generate = new PostProcessStage({
  3             name : ‘czm_ambient_occlusion_generate‘,
  4             fragmentShader : AmbientOcclusionGenerate,
  5             uniforms : {
  6                 intensity : 3.0,
  7                 bias : 0.1,
  8                 lengthCap : 0.26,
  9                 stepSize : 1.95,
 10                 frustumLength : 1000.0,
 11                 randomTexture : undefined
 12             }
 13         });
 14         var blur = createBlur(‘czm_ambient_occlusion_blur‘);
 15         blur.uniforms.stepSize = 0.86;
 16         var generateAndBlur = new PostProcessStageComposite({
 17             name : ‘czm_ambient_occlusion_generate_blur‘,
 18             stages : [generate, blur]
 19         });
 20
 21         var ambientOcclusionModulate = new PostProcessStage({
 22             name : ‘czm_ambient_occlusion_composite‘,
 23             fragmentShader : AmbientOcclusionModulate,
 24             uniforms : {
 25                 ambientOcclusionOnly : false,
 26                 ambientOcclusionTexture : generateAndBlur.name
 27             }
 28         });
 29
 30         var uniforms = {};
 31         defineProperties(uniforms, {
 32             intensity : {
 33                 get : function() {
 34                     return generate.uniforms.intensity;
 35                 },
 36                 set : function(value) {
 37                     generate.uniforms.intensity = value;
 38                 }
 39             },
 40             bias : {
 41                 get : function() {
 42                     return generate.uniforms.bias;
 43                 },
 44                 set : function(value) {
 45                     generate.uniforms.bias = value;
 46                 }
 47             },
 48             lengthCap : {
 49                 get : function() {
 50                     return generate.uniforms.lengthCap;
 51                 },
 52                 set : function(value) {
 53                     generate.uniforms.lengthCap = value;
 54                 }
 55             },
 56             stepSize : {
 57                 get : function() {
 58                     return generate.uniforms.stepSize;
 59                 },
 60                 set : function(value) {
 61                     generate.uniforms.stepSize = value;
 62                 }
 63             },
 64             frustumLength : {
 65                 get : function() {
 66                     return generate.uniforms.frustumLength;
 67                 },
 68                 set : function(value) {
 69                     generate.uniforms.frustumLength = value;
 70                 }
 71             },
 72             randomTexture : {
 73                 get : function() {
 74                     return generate.uniforms.randomTexture;
 75                 },
 76                 set : function(value) {
 77                     generate.uniforms.randomTexture = value;
 78                 }
 79             },
 80             delta : {
 81                 get : function() {
 82                     return blur.uniforms.delta;
 83                 },
 84                 set : function(value) {
 85                     blur.uniforms.delta = value;
 86                 }
 87             },
 88             sigma : {
 89                 get : function() {
 90                     return blur.uniforms.sigma;
 91                 },
 92                 set : function(value) {
 93                     blur.uniforms.sigma = value;
 94                 }
 95             },
 96             blurStepSize : {
 97                 get : function() {
 98                     return blur.uniforms.stepSize;
 99                 },
100                 set : function(value) {
101                     blur.uniforms.stepSize = value;
102                 }
103             },
104             ambientOcclusionOnly : {
105                 get : function() {
106                     return ambientOcclusionModulate.uniforms.ambientOcclusionOnly;
107                 },
108                 set : function(value) {
109                     ambientOcclusionModulate.uniforms.ambientOcclusionOnly = value;
110                 }
111             }
112         });
113
114         return new PostProcessStageComposite({
115             name : ‘czm_ambient_occlusion‘,
116             stages : [generateAndBlur, ambientOcclusionModulate],
117             inputPreviousStageTexture : false,
118             uniforms : uniforms
119         });
120     };

  从上面的代码可以看出,代码创建了generate、blur、ambientOcclusionModulate三个处理阶段。generate负责计算屏幕上每个像素的遮挡值,并生成一张灰度图;blur是对generate生成的灰度图就行模糊平滑处理;ambientOcclusionModulate负责根据灰度图对原始场景颜色就行调整。下面分别对这三个阶段进行详细介绍。

2.1 generate代码实现

  计算像素遮蔽因子的过程可以概括为:在像素周围,计算采样点对中心像素的遮蔽值,然后对遮蔽值进行累加,最后得到中心像素的遮蔽值。具体实现可以分为两部分:

  1:在视空间下计算像素代表面片的法向量

  将中心像素以及上下左右的四个像素转换到视空间下,就得到了这五个像素在视空间下的三维位置,记为posInCamera、posInCameraUp、posInCameraDown、posInCameraLeft、posInCameraRight。通过上下左右四个点与中心点的差值得到up、down、left、right四个向量,分别从上下向量、左右向量中选取模较小的向量,记为DX、DY。最后通过normalize(cross(DY, DX))得到中心像素的法向量。整个过程如下图所示:

  

  glsl对应的代码如下:

 1 vec4 clipToEye(vec2 uv, float depth)
 2 {
 3     vec2 xy = vec2((uv.x * 2.0 - 1.0), ((1.0 - uv.y) * 2.0 - 1.0));
 4     vec4 posEC = czm_inverseProjection * vec4(xy, depth, 1.0);
 5     posEC = posEC / posEC.w;
 6     return posEC;
 7 }
 8
 9 //Reconstruct Normal Without Edge Removation
10 vec3 getNormalXEdge(vec3 posInCamera, float depthU, float depthD, float depthL, float depthR, vec2 pixelSize)
11 {
12     vec4 posInCameraUp = clipToEye(v_textureCoordinates - vec2(0.0, pixelSize.y), depthU);
13     vec4 posInCameraDown = clipToEye(v_textureCoordinates + vec2(0.0, pixelSize.y), depthD);
14     vec4 posInCameraLeft = clipToEye(v_textureCoordinates - vec2(pixelSize.x, 0.0), depthL);
15     vec4 posInCameraRight = clipToEye(v_textureCoordinates + vec2(pixelSize.x, 0.0), depthR);
16
17     vec3 up = posInCamera.xyz - posInCameraUp.xyz;
18     vec3 down = posInCameraDown.xyz - posInCamera.xyz;
19     vec3 left = posInCamera.xyz - posInCameraLeft.xyz;
20     vec3 right = posInCameraRight.xyz - posInCamera.xyz;
21
22     vec3 DX = length(left) < length(right) ? left : right;
23     vec3 DY = length(up) < length(down) ? up : down;
24
25     return normalize(cross(DY, DX));
26 }
27
28 void main(void)
29 {
30     float depth = czm_readDepth(depthTexture, v_textureCoordinates);
31     vec4 posInCamera = clipToEye(v_textureCoordinates, depth);
32
33     if (posInCamera.z > frustumLength)
34     {
35         gl_FragColor = vec4(1.0);
36         return;
37     }
38
39     vec2 pixelSize = 1.0 / czm_viewport.zw;
40     float depthU = czm_readDepth(depthTexture, v_textureCoordinates- vec2(0.0, pixelSize.y));
41     float depthD = czm_readDepth(depthTexture, v_textureCoordinates+ vec2(0.0, pixelSize.y));
42     float depthL = czm_readDepth(depthTexture, v_textureCoordinates- vec2(pixelSize.x, 0.0));
43     float depthR = czm_readDepth(depthTexture, v_textureCoordinates+ vec2(pixelSize.x, 0.0));
44     vec3 normalInCamera = getNormalXEdge(posInCamera.xyz, depthU, depthD, depthL, depthR, pixelSize);
45 }

  2: 计算周围空间对面片的遮蔽值

  在上一步得到了视空间下的面片法向量,接下来就是计算周围空间对面片的遮挡值。过程可以概括为以下几个步骤:

  (1)选取要参与遮蔽值计算的空间。周围空间范围的选择是通过在像素坐标系下以中心像素为圆心,以设定值为半径,得到一个圆,但并不是要取圆中的所有像素,因为这样会带来很大的计算量。取而代之的是在四个方向上进行采样,方向值引入一个随机扰动,可以避免出现特别规则的阴影效果。

  (2)在选取了方向后,就在该方向上根据设定的采样步长进行采样,得到新的像素,将该像素转换到视空间下,记为stepPosInCamera。

  (3)通过stepPosInCamera与posInCamera作差值得到向量diffVec,向量的长度len代表了该点与中心点的距离。通过lengthCap这个值对采样的空间距离进行限制,超出该值的采样点将作废。

  (4)通过向量与法向量的点乘,得到值dotVal。dotVal实际表示了两个向量之间夹角的大小,该值越大,表示与法向量夹角越小,遮蔽值越大。

  (5)通过len的长度计算该位置遮蔽值的权重,得到一个遮蔽值localAO。

  (6)在同一个方向上选取一个最大的localAO值作为该方向上的ao值,然后将四个方向上的ao值相加,并除以4,得到新的ao。

  (7)根据给定的intensity参数对ao值就行幂次变换,得到最终的ao值。

  选取采样方向的效果如下图所示:

  对应的glsl代码如下:

 1 float ao = 0.0;
 2     vec2 sampleDirection = vec2(1.0, 0.0);
 3     float gapAngle = 90.0 * czm_radiansPerDegree;
 4
 5     // RandomNoise
 6     float randomVal = texture2D(randomTexture, v_textureCoordinates).x;
 7
 8     float inverseViewportWidth = 1.0 / czm_viewport.z;
 9     float inverseViewportHeight = 1.0 / czm_viewport.w;
10
11     //Loop for each direction
12     for (int i = 0; i < 4; i++)
13     {
14         float newGapAngle = gapAngle * (float(i) + randomVal);
15         float cosVal = cos(newGapAngle);
16         float sinVal = sin(newGapAngle);
17
18         //Rotate Sampling Direction
19         vec2 rotatedSampleDirection = vec2(cosVal * sampleDirection.x - sinVal * sampleDirection.y, sinVal * sampleDirection.x + cosVal * sampleDirection.y);
20         float localAO = 0.0;
21         float localStepSize = stepSize;
22
23         //Loop for each step
24         for (int j = 0; j < 6; j++)
25         {
26             vec2 directionWithStep = vec2(rotatedSampleDirection.x * localStepSize * inverseViewportWidth, rotatedSampleDirection.y * localStepSize * inverseViewportHeight);
27             vec2 newCoords = directionWithStep + v_textureCoordinates;
28
29             //Exception Handling
30             if(newCoords.x > 1.0 || newCoords.y > 1.0 || newCoords.x < 0.0 || newCoords.y < 0.0)
31             {
32                 break;
33             }
34
35             float stepDepthInfo = czm_readDepth(depthTexture, newCoords);
36             vec4 stepPosInCamera = clipToEye(newCoords, stepDepthInfo);
37             vec3 diffVec = stepPosInCamera.xyz - posInCamera.xyz;
38             float len = length(diffVec);
39
40             if (len > lengthCap)
41             {
42                 break;
43             }
44
45             float dotVal = clamp(dot(normalInCamera, normalize(diffVec)), 0.0, 1.0 );
46             float weight = len / lengthCap;
47             weight = 1.0 - weight * weight;
48
49             if (dotVal < bias)
50             {
51                 dotVal = 0.0;
52             }
53
54             localAO = max(localAO, dotVal * weight);
55             localStepSize += stepSize;
56         }
57         ao += localAO;
58     }
59
60     ao /= 4.0;
61     ao = 1.0 - clamp(ao, 0.0, 1.0);
62     ao = pow(ao, intensity);
63     gl_FragColor = vec4(vec3(ao), 1.0);

2.2 blur代码实现

  对上一步得到的遮蔽值图需要进行平滑,采用的是高斯平滑算法。实现思路就是在水平方向和垂直方向上根据权重叠加相邻像素值,算法讲解可以查询https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch40.html。glsl实现代码如下:

 1 #define SAMPLES 8
 2
 3 uniform float delta;
 4 uniform float sigma;
 5 uniform float direction; // 0.0 for x direction, 1.0 for y direction
 6
 7 uniform sampler2D colorTexture;
 8
 9 #ifdef USE_STEP_SIZE
10 uniform float stepSize;
11 #else
12 uniform vec2 step;
13 #endif
14
15 varying vec2 v_textureCoordinates;
16 void main()
17 {
18     vec2 st = v_textureCoordinates;
19     vec2 dir = vec2(1.0 - direction, direction);
20
21 #ifdef USE_STEP_SIZE
22     vec2 step = vec2(stepSize / czm_viewport.zw);
23 #else
24     vec2 step = step;
25 #endif
26
27     vec3 g;
28     g.x = 1.0 / (sqrt(czm_twoPi) * sigma);
29     g.y = exp((-0.5 * delta * delta) / (sigma * sigma));
30     g.z = g.y * g.y;
31
32     vec4 result = texture2D(colorTexture, st) * g.x;
33     for (int i = 1; i < SAMPLES; ++i)
34     {
35         g.xy *= g.yz;
36
37         vec2 offset = float(i) * dir * step;
38         result += texture2D(colorTexture, st - offset) * g.x;
39         result += texture2D(colorTexture, st + offset) * g.x;
40     }
41     gl_FragColor = result;
42 }

2.3 ambientOcclusionModulate代码实现

  ambientOcclusionModulate非常简单,就是根据ao值对原始场景的图片就行明暗处理,产生阴影效果。glsl代码如下:

 1 uniform sampler2D colorTexture;
 2 uniform sampler2D ambientOcclusionTexture;
 3 uniform bool ambientOcclusionOnly;
 4 varying vec2 v_textureCoordinates;
 5
 6 void main(void)
 7 {
 8     vec3 color = texture2D(colorTexture, v_textureCoordinates).rgb;
 9     vec3 ao = texture2D(ambientOcclusionTexture, v_textureCoordinates).rgb;
10     gl_FragColor.rgb = ambientOcclusionOnly ? ao : ao * color;
11 }

3. 总结

  Cesium中AO的实现方式属于HBAO(Horizon-based Ambient Occlusion),相对于传统的SSAO,HBAO对阴影的处理效果更好,使场景更加真实。终于把HDAO的实现原理彻底搞明白了,一个字:爽,哈哈哈!!!

原文地址:https://www.cnblogs.com/webgl-angela/p/9330526.html

时间: 2024-10-08 13:39:04

Cesium源码剖析---Ambient Occlusion(环境光遮蔽)的相关文章

Cesium源码剖析---Clipping Plane

之前就一直有写博客的想法,别人也建议写一写,但一直没有动手写,自己想了一下原因,就一个字:懒.懒.懒.为了改掉这个毛病,决定从今天开始写博客了,一方面对自己掌握的知识做一个梳理,另一方面和大家做一个交流,更能深化对问题的理解.废话好像有点多,好了,各位乘客,收起小桌板,系好安全带,要发车喽. Cesium作为一个开源的webgl三维地球渲染引擎,具备很多的基础功能和高级功能.之前已经有很多文章对Cesium做了相关的介绍以及如何使用API等等,我想和大家分享的是Cesium一些功能的底层实现.作

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

Cesium在1.46版本中新增了对整个场景的后期处理(Post Processing)功能,包括模型描边.黑白图.明亮度调整.夜视效果.环境光遮蔽等.对于这么炫酷的功能,我们绝不犹豫,先去翻一翻它的源码,掌握它的实现原理. 1 后期处理的原理 后期处理的过程有点类似于照片的PS.生活中拍摄了一张自拍照,看到照片后发现它太暗了,于是我们增加亮度得到了一张新的照片.在增加亮度后发现脸上的痘痘清晰可见,这可不是我们希望的效果,于是再进行一次美肤效果处理.在这之后可能还会进行n次别的操作,直到满足我们

下载-深入浅出Netty源码剖析、Netty实战高性能分布式RPC、NIO+Netty5各种RPC架构实战演练三部曲视频教程

下载-深入浅出Netty源码剖析.Netty实战高性能分布式RPC.NIO+Netty5各种RPC架构实战演练三部曲视频教程 第一部分:入浅出Netty源码剖析 第二部分:Netty实战高性能分布式RPC 第三部分:NIO+Netty5各种RPC架构实战演练

Phaser实现源码剖析

在这里首先说明一下,由于Phaser在4.3代码里是存在,但并没有被开放出来供使用,但已经被本人大致研究了,因此也一并进行剖析. Phaser是一个可以重复利用的同步栅栏,功能上与CyclicBarrier和CountDownLatch相似,不过提供更加灵活的用法.也就是说,Phaser的同步模型与它们差不多.一般运用的场景是一组线程希望同时到达某个执行点后(先到达的会被阻塞),执行一个指定任务,然后这些线程才被唤醒继续执行其它任务. Phaser一般是定义一个parties数(parties一

【Java集合源码剖析】HashMap源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap. HashMap 实现了Serializable接口,因此它支持序列化,

转:【Java集合源码剖析】Vector源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/35793865   Vector简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是相对安全,有些时候还是要加入同步语句来保证线程的安全),可以用于多线程环境. Vector没有丝线Serializable接口,因此它不支持序列化,实现了Cloneable接口,能被克隆,实

下载BootStrap企业级应用培训课程(零基础、源码剖析,内部教材,项目实训)

全套500多课,附赠JS OOP编程,转一播放码.下载地址:http://pan.baidu.com/s/1kVLdZmf 第一季:基础篇,侧重于BootStrap 相关 API 详解.主要包含以下内容:Brackets前端开发工具详解.BootStrap框架三大核心-CSS.BootStrap框架三大核心-布局组件.BootStrap框架三大核心-JavaScript插件.附-BootStrap编码规范第二季:高级篇,侧重于BootStap源码解析与第三方扩展.主要包含以下内容:BootStr

菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程(转)

俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔.对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中的奥秘,下面我们就一起来研究一下nginx的main函数. 1.nginx的main函数解读 nginx启动显然是由main函数驱动的,main函数在在core/nginx.c文件中,其源代码解析如下,涉及到的数据结构在本节仅指出其作用,将在第二节中详细解释. nginx main函数的流程图如下:

HashMap(2) 源码剖析(推荐)

今天看代码,想到去年发生的HashMap发生的CPU使用率100%的事件,转载下当时看的三个比较不错的博客(非常推荐) 参考:http://coolshell.cn/articles/9606.html   http://github.thinkingbar.com/hashmap-analysis/ http://developer.51cto.com/art/201102/246431.htm 在 Java 集合类中,使用最多的容器类恐怕就是 HashMap 和 ArrayList 了,所以