为实现三维模型的更炫、更酷、更美观,Cesium在1.46的版本中新增了场景的后期处理(Post Processing)功能,包括模型描边、黑白图、明亮度调整、夜市效果、环境光遮蔽,也包括雷达扫描、原型扩散等一些特效。今天我们来学习一下场景后期处理的基础知识和实现流程。

场景后期处理流程

场景的后期处理这个词比较陌生,但说起照片的PS大家都很熟悉,这两个过程非常类似。日常生活中我们拍摄完照片之后,发现太亮或太暗,又或者是皮肤不够白、脸上痘痘明显,我们可以调整亮度、修复一下嫩白的脸蛋,经过几波操作之后,得到了一张我们非常满意的照片。

android 边缘外发光效果_Boo

我们可以把照片的修复过程简单理解成场景的后期处理过程,修图的过程就比喻成对三维场景中初始渲染的效果进行再处理,比如添加物体描边、明暗度调整、夜市效果等,最终把综合之后的效果在场景中渲染出来。Cesium中的场景后期处理的大概流程如下图所示:

android 边缘外发光效果_三维GIS_02


下面结合Cesium本身的PostProcess类,详细的说明一下处理流程:

第一步:通过PostProcessStageLibrary创建一个或者多个后处理效果对象,得到多个PostProcessStage或PostProcessStageComposite;

第二步:将他们加入到PostProcessStageCollection对象中,并设置PostProcessStage或PostProcessStageComposite一些参数,如uniforms;

第三步:PostProcessStageCollection对象就会按照加入的顺序进行屏幕后期处理,在所有的效果都处理完毕后,最后绘制到屏幕上。

当然也可以省略第一步,直接利用PostProcessStageCollection实例化对象中已有的处理效果去实现,如ambientOcclusion、bloom、fxaa。

场景后期处理相关类

上述提到了PostProcess类,基本上涉及到4个类文件,具体每个类的作用又是什么呢?我们来说明一下。
(1)PostProcessStage
对应于某个具体的后期处理效果,它的输入为场景渲染图或者上一个后期处理的结果图,输出结果是一张处理后的图片。

// Simple stage to change the colorvar 
fs =
    'uniform sampler2D colorTexture;\n' +
    'varying vec2 v_textureCoordinates;\n' +
    'uniform float scale;\n' +
    'uniform vec3 offset;\n' +
    'void main() {\n' +
    '    vec4 color = texture2D(colorTexture, v_textureCoordinates);\n' +
    '    gl_FragColor = vec4(color.rgb * scale + offset, 1.0);\n' +
    '}\n';
scene.postProcessStages.add(new Cesium.PostProcessStage({
    fragmentShader : fs,
    uniforms : {
        scale : 1.1,
        offset : function() {
            return new Cesium.Cartesian3(0.1, 0.2, 0.3);
        }
    }}));

fragmentShader:片源着色器代码字符串,它是GLSL代码语言,需要成对配置顶点着色器和片元着色器。
uniforms:片源着色器代码字符串中需要在前端传入的变量。
(2)PostProcessStageComposite
一个集合对象,按顺序存储了不同的场景处理对象,存储类型为PostProcessStage或者PostProcessStageComposite的元素,并存储在stages属性中。

// Example 1: separable blur filter
// The input to blurXDirection is the texture rendered to by the scene or the output of the previous stage.
// The input to blurYDirection is the texture rendered to by blurXDirection.
scene.postProcessStages.add(new Cesium.PostProcessStageComposite({
    stages : [blurXDirection, blurYDirection]}));

(3)PostProcessStageLibrary
负责创建具体的后期处理效果,提供了一些创建常用场景特效的方法,包括createBlackAndWhiteStage-黑色和白色渐变渲染、createBlurStage-高斯模、createBrightnessStage-纹理饱和、createDepthOfFieldStage-景深效果等,创建返回的结果是PostProcessStageComposite或者PostProcessStage类型。相对来说比较简单,直接调用即可。

var stages = viewer.scene.postProcessStages;
    var silhouette = stages.add(
      Cesium.PostProcessStageLibrary.createSilhouetteStage()
    );

(4)PostProcessStageCollection
是一个集合类型的类,负责管理和维护放到集合中的PostProcessStage或PostProcessStageComposite类型对象,实例化对象可通过viewer.scene.postProcessStages直接获取,提供了一些常用的方法,如add、contains、destroy、remove等。
但需要注意的是,该集合中也设定了三个ambientOcclusion、bloom、fxaa效果,如果此类中的环境光遮挡-ambientOcclusion或发光效果-bloom被启用,它们将在所有其他阶段之前执行,优先级最高;如果近似抗锯齿-fxaa被启用,它将在所有其他阶段之后执行,优先级最低。

场景后期处理效果

Cesium为我们提供了一些默认的示例效果,但基本上可分为如下三类:
(1)利用PostProcessStageCollection集合类提供的三个效果
包括ambientOcclusion环境光遮挡、bloom发光效果、fxaa近似抗锯齿,我们挑选前两个为例进行说明。

  • ambientOcclusion环境光遮挡
function updatePostProcess() {
  const ambientOcclusion =
    viewer.scene.postProcessStages.ambientOcclusion;
  ambientOcclusion.enabled =
    Boolean(viewModel.show) || Boolean(viewModel.ambientOcclusionOnly);
  ambientOcclusion.uniforms.ambientOcclusionOnly = Boolean(
    viewModel.ambientOcclusionOnly
  );
  ambientOcclusion.uniforms.intensity = Number(viewModel.intensity);
  ambientOcclusion.uniforms.bias = Number(viewModel.bias);
  ambientOcclusion.uniforms.lengthCap = Number(viewModel.lengthCap);
  ambientOcclusion.uniforms.stepSize = Number(viewModel.stepSize);
  ambientOcclusion.uniforms.blurStepSize = Number(
    viewModel.blurStepSize
  );
}

android 边缘外发光效果_环境光_03

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

  • bloom发光效果
function updatePostProcess() {
  const bloom = viewer.scene.postProcessStages.bloom;
  bloom.enabled = Boolean(viewModel.show);
  bloom.uniforms.glowOnly = Boolean(viewModel.glowOnly);
  bloom.uniforms.contrast = Number(viewModel.contrast);
  bloom.uniforms.brightness = Number(viewModel.brightness);
  bloom.uniforms.delta = Number(viewModel.delta);
  bloom.uniforms.sigma = Number(viewModel.sigma);
  bloom.uniforms.stepSize = Number(viewModel.stepSize);
}

android 边缘外发光效果_Boo_04

(2)直接调用PostProcessStageLibrary中提供的方法去渲染场景特效
Cesium在场景处理库中默认为我们提供了如下8个效果,其实也是非常简单的,直接调用即可。

下面是一个切换动画小人效果的简单示例:

const stages = viewer.scene.postProcessStages;
const silhouette = stages.add(
  Cesium.PostProcessStageLibrary.createSilhouetteStage()
);
const blackAndWhite = stages.add(
  Cesium.PostProcessStageLibrary.createBlackAndWhiteStage()
);
const brightness = stages.add(
  Cesium.PostProcessStageLibrary.createBrightnessStage()
);
const nightVision = stages.add(
  Cesium.PostProcessStageLibrary.createNightVisionStage()
);

function updatePostProcess() {
  silhouette.enabled = Boolean(viewModel.silhouette);
  silhouette.uniforms.color = Cesium.Color.YELLOW;
  blackAndWhite.enabled = Boolean(viewModel.blackAndWhiteShow);
  blackAndWhite.uniforms.gradations = Number(
    viewModel.blackAndWhiteGradations
  );
  brightness.enabled = Boolean(viewModel.brightnessShow);
  brightness.uniforms.brightness = Number(viewModel.brightnessValue);
  nightVision.enabled = Boolean(viewModel.nightVisionShow);
}

android 边缘外发光效果_环境光_05

(3)编写自定义Shader实现场景特效
要想实现自定义Shader,不仅需要开发者了解顶点着色器和片元着色器,openGL,还需要会编写GLSL(GL Shading Language)语言,通过自定义Shader可以表达更多的场景特效。关于GLSL编程语法,这里就不多赘述了,感兴趣的可以查看其官网。下面是一个简单的给动画小人打马赛克的示例:

const fragmentShaderSource = `
  uniform sampler2D colorTexture; 
  varying vec2 v_textureCoordinates; 
  const int KERNEL_WIDTH = 16; 
  void main(void) 
  { 
      vec2 step = czm_pixelRatio / czm_viewport.zw; 
      vec2 integralPos = v_textureCoordinates - mod(v_textureCoordinates, 8.0 * step); 
      vec3 averageValue = vec3(0.0); 
      for (int i = 0; i < KERNEL_WIDTH; i++) 
      { 
          for (int j = 0; j < KERNEL_WIDTH; j++) 
          { 
              averageValue += texture2D(colorTexture, integralPos + step * vec2(i, j)).rgb; 
          } 
      } 
      averageValue /= float(KERNEL_WIDTH * KERNEL_WIDTH); 
      gl_FragColor = vec4(averageValue, 1.0); 
  }
  `;
viewer.scene.postProcessStages.add(
  new Cesium.PostProcessStage({
    fragmentShader: fragmentShaderSource,
  })

android 边缘外发光效果_环境光_06