在高德地图实现后期效果

介绍

最近在做可视化图层开发的时候,发现我们自己开发的图层一些优秀的案例比起来,总是有一定的差距。差了后期效果合成环节,就比如个人晒图前忘了用美图秀秀修图。于是花了些时间研究了高德地图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实现分区辉光

相关推荐
Daitu_Adam10 小时前
2024年中国研究生数学建模竞赛C题——解题思路
数学建模·matlab·数据分析·数据可视化
William数据分析13 小时前
[Python数据可视化]探讨数据可视化的实际应用:三个案例分析
python·信息可视化·数据分析·数据可视化
Face1 天前
GoJS布局
前端·数据可视化
B站计算机毕业设计超人3 天前
计算机毕业设计Python+Flask微博情感分析 微博舆情预测 微博爬虫 微博大数据 舆情分析系统 大数据毕业设计 NLP文本分类 机器学习 深度学习 AI
爬虫·python·深度学习·算法·机器学习·自然语言处理·数据可视化
Book_熬夜!3 天前
Python基础(六)——PyEcharts数据可视化初级版
开发语言·python·信息可视化·echarts·数据可视化
字节跳动数据平台4 天前
火山引擎数智平台:高性能ChatBI的技术解读和落地实践
大数据·大模型·数据可视化·bi
William数据分析4 天前
[Python数据可视化] Plotly:交互式数据可视化的强大工具
python·数据分析·数据可视化
邢博士谈科教5 天前
比传统机器学习更先进的深度学习神经网络的二分类建模全流程教程
数据挖掘·r语言·数据可视化
GHUIJS5 天前
【Echarts】vue3打开echarts的正确方式
前端·vue.js·echarts·数据可视化
乐吾乐科技6 天前
【乐吾乐大屏可视化组态编辑器】API接口文档(pgsql)
前端·开源·编辑器·流程图·交互·数据可视化