diff --git a/packages/core/src/inversify.config.ts b/packages/core/src/inversify.config.ts index df983aef77..585f9c9b4b 100644 --- a/packages/core/src/inversify.config.ts +++ b/packages/core/src/inversify.config.ts @@ -51,6 +51,7 @@ import { import ClearPass from './services/renderer/passes/ClearPass'; import MultiPassRenderer from './services/renderer/passes/MultiPassRenderer'; import PixelPickingPass from './services/renderer/passes/PixelPickingPass'; +import BloomPass from './services/renderer/passes/post-processing/BloomPass'; import BlurHPass from './services/renderer/passes/post-processing/BlurHPass'; import BlurVPass from './services/renderer/passes/post-processing/BlurVPass'; import ColorHalfTonePass from './services/renderer/passes/post-processing/ColorHalfTonePass'; @@ -221,6 +222,10 @@ export function createSceneContainer() { .bind>(TYPES.IPostProcessingPass) .to(CopyPass) .whenTargetNamed('copy'); + sceneContainer + .bind>(TYPES.IPostProcessingPass) + .to(BloomPass) + .whenTargetNamed('bloom'); sceneContainer .bind>(TYPES.IPostProcessingPass) .to(BlurHPass) diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index d2281ea830..7b9368df04 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -199,6 +199,9 @@ export interface ILayer { // animate(field: string, option: any): ILayer; renderLayers(): void; render(): ILayer; + + renderMultiPass(): any; + clear(): void; clearModels(): void; destroy(): void; diff --git a/packages/core/src/services/layer/LayerService.ts b/packages/core/src/services/layer/LayerService.ts index c7f6ef8389..f098d69e76 100644 --- a/packages/core/src/services/layer/LayerService.ts +++ b/packages/core/src/services/layer/LayerService.ts @@ -116,19 +116,31 @@ export default class LayerService implements ILayerService { this.enableRender = flag; } - public renderLayers() { + public async renderLayers() { if (this.alreadyInRendering || !this.enableRender) { return; } this.alreadyInRendering = true; this.clear(); - this.layerList.forEach((layer) => { + for (const layer of this.layerList) { layer.hooks.beforeRenderData.call(); layer.hooks.beforeRender.call(); - layer.render(); + if (layer.getLayerConfig().enableMultiPassRenderer) { + // multiPassRender 不是同步渲染完成的 + await layer.renderMultiPass(); + } else { + layer.render(); + } layer.hooks.afterRender.call(); - }); + } + + // this.layerList.forEach((layer) => { + // layer.hooks.beforeRenderData.call(); + // layer.hooks.beforeRender.call(); + // layer.render(); + // layer.hooks.afterRender.call(); + // }); this.alreadyInRendering = false; } diff --git a/packages/core/src/services/renderer/passes/PostProcessor.ts b/packages/core/src/services/renderer/passes/PostProcessor.ts index 1e25b6e31a..19253c6881 100644 --- a/packages/core/src/services/renderer/passes/PostProcessor.ts +++ b/packages/core/src/services/renderer/passes/PostProcessor.ts @@ -33,12 +33,19 @@ export default class PostProcessor implements IPostProcessor { const pass = this.passes[i]; // last pass should render to screen pass.setRenderToScreen(this.isLastEnabledPass(i)); - await pass.render(layer); + await pass.render(layer); // pingpong if (i !== this.passes.length - 1) { this.swap(); } + + if (pass.getName() === 'bloom') { + await pass.render(layer); + this.swap(); + await pass.render(layer); + this.swap(); + } } } diff --git a/packages/core/src/services/renderer/passes/RenderPass.ts b/packages/core/src/services/renderer/passes/RenderPass.ts index ef084ace3f..972f0a5e2f 100644 --- a/packages/core/src/services/renderer/passes/RenderPass.ts +++ b/packages/core/src/services/renderer/passes/RenderPass.ts @@ -35,7 +35,14 @@ export default class RenderPass< }); // render to post processor layer.multiPassRenderer.setRenderFlag(false); - layer.render(); + // layer.render(); + + layer.models.forEach((model) => { + model.draw({ + uniforms: layer.layerModel.getUninforms(), + }); + }); + layer.multiPassRenderer.setRenderFlag(true); }); } diff --git a/packages/core/src/services/renderer/passes/post-processing/BloomPass.ts b/packages/core/src/services/renderer/passes/post-processing/BloomPass.ts new file mode 100644 index 0000000000..68ef839621 --- /dev/null +++ b/packages/core/src/services/renderer/passes/post-processing/BloomPass.ts @@ -0,0 +1,53 @@ +import { injectable } from 'inversify'; +import { isNil } from 'lodash'; +import 'reflect-metadata'; +import blur from '../../../../shaders/post-processing/bloom.glsl'; +import quad from '../../../../shaders/post-processing/quad.glsl'; +import { IUniform } from '../../IUniform'; +import BasePostProcessingPass from '../BasePostProcessingPass'; + +export interface IBloomPassConfig { + bloomRadius: number; +} + +@injectable() +export default class BloomPass extends BasePostProcessingPass< + IBloomPassConfig +> { + protected setupShaders() { + this.shaderModuleService.registerModule('blur-pass', { + vs: quad, + fs: blur, + }); + + const { vs, fs, uniforms } = this.shaderModuleService.getModule( + 'blur-pass', + ); + const { width, height } = this.rendererService.getViewportSize(); + + return { + vs, + fs, + uniforms: { + ...uniforms, + u_ViewportSize: [width, height], + }, + }; + } + + protected convertOptionsToUniforms( + options: Partial, + ): { + [uniformName: string]: IUniform; + } | void { + const uniforms: { + [key: string]: IUniform; + } = {}; + + if (!isNil(options.bloomRadius)) { + uniforms.u_radius = options.bloomRadius; + } + + return uniforms; + } +} diff --git a/packages/core/src/shaders/post-processing/bloom.glsl b/packages/core/src/shaders/post-processing/bloom.glsl new file mode 100644 index 0000000000..3fcc3007a9 --- /dev/null +++ b/packages/core/src/shaders/post-processing/bloom.glsl @@ -0,0 +1,68 @@ +varying vec2 v_UV; + +uniform sampler2D u_Texture; + +uniform vec2 u_ViewportSize: [1.0, 1.0]; +uniform float u_radius: 5.0; + +// https://github.com/Jam3/glsl-fast-gaussian-blur/blob/master/9.glsl +vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { + vec4 color = vec4(0.0); + vec2 off1 = vec2(1.3846153846) * direction; + vec2 off2 = vec2(3.2307692308) * direction; + color += texture2D(image, uv) * 0.2270270270; + color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162; + color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162; + color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703; + color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703; + return color; +} + +void main() { + float r = sqrt(u_radius); + + vec4 c1 = blur9(u_Texture, v_UV, u_ViewportSize, vec2(u_radius, 0.0)); + vec4 c2 = blur9(u_Texture, v_UV, u_ViewportSize, vec2(0.0, u_radius)); + vec4 c3 = blur9(u_Texture, v_UV, u_ViewportSize, vec2(r, r)); + vec4 c4 = blur9(u_Texture, v_UV, u_ViewportSize, vec2(r, -r)); + vec4 inbloomColor = c1 * 0.25 + c2 * 0.25 + c3 * 0.25 + c4 * 0.25; + + + + float h = 0.01; + vec4 baseColor = texture2D(u_Texture, v_UV); + + vec4 color11 = texture2D( u_Texture, vec2( v_UV.x - 1.0 * h, v_UV.y + 1.0 * h) ); + vec4 color12 = texture2D( u_Texture, vec2( v_UV.x - 0.0 * h, v_UV.y + 1.0 * h) ); + vec4 color13 = texture2D( u_Texture, vec2( v_UV.x + 1.0 * h, v_UV.y + 1.0 * h) ); + + vec4 color21 = texture2D( u_Texture, vec2( v_UV.x - 1.0 * h, v_UV.y) ); + vec4 color23 = texture2D( u_Texture, vec2( v_UV.x + 1.0 * h, v_UV.y) ); + + vec4 color31 = texture2D( u_Texture, vec2( v_UV.x - 1.0 * h, v_UV.y-1.0*h) ); + vec4 color32 = texture2D( u_Texture, vec2( v_UV.x - 0.0 * h, v_UV.y-1.0*h) ); + vec4 color33 = texture2D( u_Texture, vec2( v_UV.x + 1.0 * h, v_UV.y-1.0*h) ); + + vec4 bloomColor = + color11 + + color12 + + color13 + + color21 + + color21 + + color23 + + color31 + + color32 + + color33; + + if(baseColor.a > 0.0) { + + gl_FragColor.r = min(bloomColor.r, baseColor.r); + gl_FragColor.g = min(bloomColor.g, baseColor.g); + gl_FragColor.b = min(bloomColor.b, baseColor.b); + gl_FragColor.a = min(bloomColor.a, baseColor.a); + + gl_FragColor = mix(inbloomColor, gl_FragColor, 0.7); + } else { + gl_FragColor = bloomColor/9.0; + } +} \ No newline at end of file diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index f3cff0f1aa..5b92eaad16 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -560,37 +560,28 @@ export default class BaseLayer extends EventEmitter } public render(): ILayer { - // if ( - // this.needPick() && - // this.multiPassRenderer && - // this.multiPassRenderer.getRenderFlag() - // ) { - // this.multiPassRenderer.render(); - // } else if (this.needPick() && this.multiPassRenderer) { - // this.renderModels(); - // } else { - // this.renderModels(); - // } - // TODO: this.getEncodedData().length !== 0 这个判断是为了解决在 2.5.x 引入数据纹理后产生的 空数据渲染导致 texture 超出上限问题 + if (this.getEncodedData().length !== 0) { + this.renderModels(); + } + return this; + } + + /** + * renderMultiPass 专门用于渲染支持 multipass 的 layer + */ + public async renderMultiPass() { if (this.getEncodedData().length !== 0) { if (this.multiPassRenderer && this.multiPassRenderer.getRenderFlag()) { // multi render 开始执行 multiPassRender 的渲染流程 - this.multiPassRenderer.render(); + await this.multiPassRenderer.render(); } else if (this.multiPassRenderer) { // renderPass 触发的渲染 this.renderModels(); } else { this.renderModels(); } - - // this.renderModels(); } - // this.renderModels(); - - // this.multiPassRenderer.render(); - // this.renderModels(); - return this; } public active(options: IActiveOption | boolean) { @@ -1025,6 +1016,10 @@ export default class BaseLayer extends EventEmitter throw new Error('Method not implemented.'); } + public async renderMulPass(multiPassRenderer: IMultiPassRenderer) { + await multiPassRenderer.render(); + } + public renderModels(isPicking?: boolean) { // TODO: this.getEncodedData().length > 0 这个判断是为了解决在 2.5.x 引入数据纹理后产生的 空数据渲染导致 texture 超出上限问题 if (this.getEncodedData().length > 0) { diff --git a/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx b/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx index 8a2d49673a..67c5cf5d91 100644 --- a/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx +++ b/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx @@ -1,5 +1,6 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; +import Bloom from './components/Bloom'; import Blur from './components/Blur'; import ColorHalftone from './components/ColorHalftone'; import CustomPostProcessing from './components/CustomPostProcessing'; @@ -13,6 +14,7 @@ storiesOf('MultiPassRenderer', module) .add('ColorHalftone', () => ) .add('HexagonalPixelate', () => ) .add('Ink', () => ) + .add('Bloom', () => ) .add('Blur', () => ) .add('Noise', () => ) .add('Sepia', () => ) diff --git a/stories/MultiPassRenderer/components/Bloom.tsx b/stories/MultiPassRenderer/components/Bloom.tsx new file mode 100644 index 0000000000..8c86fe6769 --- /dev/null +++ b/stories/MultiPassRenderer/components/Bloom.tsx @@ -0,0 +1,101 @@ +import { PolygonLayer, PointLayer, Scene } from '@antv/l7'; +import { Mapbox } from '@antv/l7-maps'; +import * as dat from 'dat.gui'; +import * as React from 'react'; + +export default class Bloom extends React.Component { + private gui: dat.GUI; + private $stats: Node; + private scene: Scene; + + public componentWillUnmount() { + if (this.gui) { + this.gui.destroy(); + } + if (this.$stats) { + document.body.removeChild(this.$stats); + } + this.scene.destroy(); + } + + public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); + const data = await response.json(); + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + // style: 'mapbox://styles/mapbox/streets-v9', + style: 'blank', + center: [110.19382669582967, 50.258134], + pitch: 0, + zoom: 3, + }), + }); + scene.setBgColor('#000'); + const layer = new PolygonLayer({ + zIndex: 0, + // enablePicking: true, + // enableHighlight: true, + enableMultiPassRenderer: true, + passes: [ + [ + 'bloom', + { + bloomRadius: 8, + }, + ], + ], + }); + + layer + .source(data) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .active(true) + .style({ + // opacity: 0.8, + }); + + scene.addLayer(layer); + + let pointLayer = new PointLayer({ zIndex: 1 }) + .source([{ lng: 122, lat: 30 }], { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }) + .shape('circle') + .size(20) + .color('red'); + scene.addLayer(pointLayer); + + this.scene = scene; + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/MultiPassRenderer/components/Blur.tsx b/stories/MultiPassRenderer/components/Blur.tsx index 925e79cff5..17237862c3 100644 --- a/stories/MultiPassRenderer/components/Blur.tsx +++ b/stories/MultiPassRenderer/components/Blur.tsx @@ -33,8 +33,8 @@ export default class Blur extends React.Component { }), }); const layer = new PolygonLayer({ - enablePicking: true, - enableHighlight: true, + // enablePicking: true, + // enableHighlight: true, enableMultiPassRenderer: true, passes: [ [ @@ -64,12 +64,30 @@ export default class Blur extends React.Component { '#CF1D49', ]) .shape('fill') + .active(true) .style({ opacity: 0.8, }); scene.addLayer(layer); + // const layer2 = new PolygonLayer() + // .source(data) + // .size('name', [0, 10000, 50000, 30000, 100000]) + // .color('name', [ + // '#2E8AE6', + // '#69D1AB', + // '#DAF291', + // '#FFD591', + // '#FF7A45', + // '#CF1D49', + // ]) + // .shape('fill') + // .style({ + // opacity: 0.8, + // }); + // scene.addLayer(layer2); + this.scene = scene; /*** 运行时修改样式属性 ***/