在高德地图实现后期效果

介绍

最近在做可视化图层开发的时候,发现我们自己开发的图层一些优秀的案例比起来,总是有一定的差距。差了后期效果合成环节,就比如个人晒图前忘了用美图秀秀修图。于是花了些时间研究了高德地图JSAPI2.0和GLCustomLayer,探索如何将后期特效接入到3D图层中。

后期特效其实有点类似照片的后期滤镜处理,是对渲染结果的二次处理,可以实现发光、模糊、色调调整、镜头暗角、模拟环境光遮蔽等各种效果,为了方便理解,下面的讲解我将以辉光效果为例,学会了一种其他效果思路类似。

方案调研

Three官方提供了非常简单的方法实现后期特效,貌似仅需要完成以下两个步骤就可以完成我们想要的需求,代码也非常清晰简单:

jsx 复制代码
import * as THREE from 'three'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing//UnrealBloomPass.js'
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'

...

// 1.在图层初始化完成后,创建效果合成器
onLayerInit(){
	const { scene, camera, renderer } = this

    const renderScene = new RenderPass(scene, camera)

    // 后期泛光特效
    bloomPass = new UnrealBloomPass(new THREE.Vector2(this.container.clientWidth, this.container.clientHeight), 1, 0, 0)
    bloomPass.threshold = params.threshold
    bloomPass.strength = params.strength
    bloomPass.radius = params.radius

    composer = new EffectComposer(renderer)
    // 以下代码会遮盖地图
    composer.addPass(renderScene)
    composer.addPass(bloomPass)
}

// 2.更新合成器
onRender () {
  if (composer) {
    composer.render()
  }
}

本以为这样做就可以开心收工了,燃鹅事情并没有那么简单,把这套方案移入高德的GLCustomLayer中,出现了这样的情况,后期效果直接把地图底图盖住了。

出现这种情况的原因是实现辉光效果而编写的着色器,它会直接修改整个画面的alpha通道而导致透明效果丢失,因此需要单独修改UnrealBloomPass.js。

然而光是这样还不够,经过各种尝试,仍无法直接在GLCustomLayer上解决地图被遮盖的问题,后来咨询了高德地图开发团队的技术大佬,他给我的建议是后期效果层独立展示,于是就沿着这个思路进行了第二轮尝试。

这里面有几个关键步骤是必须的:

  1. 修改UnrealBloomPass着色器代码
  2. 使用输出通道new OutputPass()置于特效通道的后面
  3. 在customLayer图层中,每次渲染就更新特效合成器EffectComposer

由于我这边是不希望之前开发的可视化图层做太多的修改去迁就这个后期效果的,也有对性能较差的终端机器优雅降级的考虑,索性把后期效果独立为EffectLayer层,以方便灵活地装载或剥离,最终实现了这个效果。

实现步骤

  1. 修改 UnrealBloomPass.js,由于这个文件在npm包中不能随意修改,我另外写了一个UnrealBloomPass1 继承并覆盖了UnrealBloomPass的方法

    jsx 复制代码
    import * as THREE from 'three'
    import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
    
    class UnrealBloomPass1 extends UnrealBloomPass {
      constructor (resolution, strength, radius, threshold) {
        super(resolution, strength, radius, threshold)
      }
    
      getSeperableBlurMaterial (kernelRadius) {
         ...
          fragmentShader:
            `#include <common>
    				varying vec2 vUv;
    				uniform sampler2D colorTexture;
    				uniform vec2 invSize;
    				uniform vec2 direction;
    				uniform float gaussianCoefficients[KERNEL_RADIUS];
    
    				void main() {
    					float weightSum = gaussianCoefficients[0];
    					vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum;
    					float alphaSum;
    					for( int i = 1; i < KERNEL_RADIUS; i ++ ) {
    						float x = float(i);
    						float w = gaussianCoefficients[i];
    						vec2 uvOffset = direction * invSize * x;
    						vec4 sample1 = texture2D( colorTexture, vUv + uvOffset );
    						vec4 sample2 = texture2D( colorTexture, vUv - uvOffset );
    						diffuseSum += (sample1.rgb + sample2.rgb) * w;
    						alphaSum += (sample1.a + sample2.a) * w; //
    						weightSum += 2.0 * w;
    					}
    					// gyrate: overwrite this line for alpha pass
    					// gl_FragColor = vec4(diffuseSum/weightSum, 1.0);
    					gl_FragColor = vec4(diffuseSum/weightSum, alphaSum/weightSum);
    				}`
        })
      }
    }
    
    export { UnrealBloomPass1 }
  2. 编写EffectLayer

    jsx 复制代码
    import * as THREE from 'three'
    import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
    import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
    import { UnrealBloomPass1 } from '../plugins/three/examples/jsm/postprocessing/UnrealBloomPass.js'
    import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'
    import _ from 'lodash'
    
    class EffectLayer {
      
      // 此处省去一些内部变量  
    
      _style = {
        // 光照强度阈值
        threshold: 0.0,
        // 泛光强度
        strength: 1.0,
        // 泛光半径
        radius: 1.5
      }
      
    	/**
       * 创建一个实例
       * @param {Object} config
       * @param {Layer} config.layer 目标图层,要求是Layer的相关子类
       * @param {Number} [config.zIndex=120] 图层的层级
       * @param {EffectStyle} [config.style] 后期特效的配置项
       */
      constructor (config) {
        const conf = _.merge(this._conf, config)
        this._style = _.merge(this._style, conf.style)
    
        if (!conf.layer.scene || !conf.layer.camera) {
          console.error('缺少场景和相机')
          return
        }
        this.init()
      }
    
      init () {
        this.createLayer()
        this.addEffect()
      }
    }
  3. 创建自定义图层customLayer

    jsx 复制代码
    createLayer () {
        const canvas = document.createElement('canvas')
        this._customLayer = new AMap.CustomLayer(canvas, {
          zooms: [3, 22],
          zIndex: this._conf.zIndex,
          alwaysRender: true
        })
    
        this._canvas = canvas
      }
  4. 创建特效合成器

    jsx 复制代码
    addEffect () {
        const { scene, camera, container, renderer, map } = this._conf.layer
        const { clientWidth, clientHeight } = container
    
        // 创建渲染器
        const effectRender = new THREE.WebGLRenderer({
          canvas: this._canvas,
          alpha: true,
          antialias: false,
          stencil: false,
          depth: false
        })
        // renderer.setClearColor(0xff0000);
        effectRender.autoClear = false
        effectRender.setSize(clientWidth, clientHeight)
    
        // 后期效果
        const renderScene = new RenderPass(scene, camera)
    
        // 后期辉光特效
        const bloomPass = new UnrealBloomPass1(new THREE.Vector2(clientWidth, clientHeight), 1, 0, 0)
        bloomPass.clear = false
    
        // 输出通道
        const outputPass = new OutputPass()
        outputPass.clear = false
    
        this.updatePass()
    
        const composer = new EffectComposer(effectRender)
        composer.addPass(renderScene)
        composer.addPass(bloomPass)
        composer.addPass(outputPass)
    
        this._composer = composer
        this._bloomPass = bloomPass
    
        this._customLayer.render = function () {
          if (composer) {
    				// 每次渲染就更新特效合成器
            composer.render()
          }
        }
    
        map.add(this._customLayer)
      }
    
      updatePass() {
        const {_bloomPass} = this
        if (_bloomPass) {
          _bloomPass.threshold = this._style.threshold
          _bloomPass.strength = this._style.strength
          _bloomPass.radius = this._style.radius
        }
        // 添加其他特效通道...
       }
  5. 使用EffectLayer

    jsx 复制代码
    //之前编写的可视化图层
    const layer = new GLlayers.POI3dLayer({
      map: getMap(),
      zooms: [10, 22]
    })
    
    layer.on('complete', (layer) => {
       let effectLayer = new GLlayers.EffectLayer({
          layer: layer, //把图层传入effectLayer
          style:{
            threshold: 0.0,
            strength: 1.0,
            radius: 0.5,
          }
       })
    })

注意:以上方案three.js版本为0.157, 该版本对three/example/jsm/postprocessing目录中的后期效果通道相关文件做了较多调整,如果是用之前的three.js版本,修改内容可能有所不同。

至此我们就可以在之前的可视化图层基础上,加入几行代码实现辉光效果,以下是挑选一部分图层加上EffectLayer之后的效果,肉眼可见还是有很明显区别的。当然在使用过程中也发现了个别图层原有的问题需要做进一步优化。

待解决问题

使用独立图层展示后期特效层有个明显缺点,无法关联默认基本图层的场景要素深度信息,最主要的影响是高德的建筑白模图层和自定义可视化图层的远近遮挡关系会丢失,导致可视化图层永远在最前面。比如下面这个城市主要道路的辉光效果,这个是需要后面花时间去解决的,写这篇文章的时候又找到几个方案,有时间再试一把,毕竟上面留给我的时间不多了。

相关链接

three.js后期处理

three.js效果合成器文档和示例

实现模型材质局部辉光效果和解决辉光影响场景背景图显示的问题

Three.js带Depth实现分区辉光

相关推荐
B站计算机毕业设计超人1 天前
计算机毕业设计Python+Neo4j中华古诗词可视化 古诗词智能问答系统 古诗词数据分析 古诗词情感分析 PyTorch Tensorflow LSTM
pytorch·python·深度学习·机器学习·知识图谱·neo4j·数据可视化
B站计算机毕业设计超人1 天前
计算机毕业设计Python+大模型斗鱼直播可视化 直播预测 直播爬虫 直播数据分析 直播大数据 大数据毕业设计 机器学习 深度学习
爬虫·python·深度学习·机器学习·数据分析·课程设计·数据可视化
知行行行2 天前
手把手教学系列之R语言绘图——饼图
数据可视化
FreedomLeo12 天前
Python数据分析NumPy和pandas(二十九、其他Python可视化工具)
python·数据分析·数据可视化·numpy和pandas
B站计算机毕业设计超人2 天前
计算机毕业设计Python+图神经网络考研院校推荐系统 考研分数线预测 考研推荐系统 考研爬虫 考研大数据 Hadoop 大数据毕设 机器学习 深度学习
爬虫·python·深度学习·机器学习·知识图谱·数据可视化·推荐算法
希艾席蒂恩3 天前
选择适合你的报表工具,山海鲸报表与Tableau深度对比
信息可视化·数据挖掘·数据分析·数据可视化·报表工具·免费软件
qingyunliushuiyu3 天前
智能数据分析系统-助力企业迈向数字化转型时代
数据分析·数据可视化·数据分析系统·智能数据分析系统·bi数据分析系统
RestCloud3 天前
ETLCloud支持的数据处理类型包括哪些?
数据库·数据分析·数据可视化
阡之尘埃3 天前
Python数据分析案例64——杭帮菜美食探索数据分析可视化
python·数据挖掘·数据分析·pandas·数据可视化·美食·杭帮菜
叫我:松哥3 天前
基于python的天气数据采集与可视化分析,对20个城市的天气适宜出行度分析
开发语言·爬虫·python·数据分析·matplotlib·数据可视化·天气