diff --git a/dev-docs/TAA.md b/dev-docs/TAA.md new file mode 100644 index 0000000000..8483fdac7e --- /dev/null +++ b/dev-docs/TAA.md @@ -0,0 +1,101 @@ +# 在地理场景中应用 TAA + +## 问题背景 + +关于走样产生的原因以及常用的反走样手段,可以参考「知乎 - 反走样技术(一):几何反走样」[🔗](https://zhuanlan.zhihu.com/p/28800047)。 +我之前也简单总结了下 SSAA、MLAA/SMAA、FXAA 等反走样技术的实现细节。 + +其中 MSAA 作为浏览器内置实现,开发者使用起来很简单: + +> 相对于着色走样,人眼对几何走样更敏感。MSAA 的原理很简单,它仍然把一个像素划分为若干个子采样点,但是相较于 SSAA,每个子采样点的颜色值完全依赖于对应像素的颜色值进行简单的复制(该子采样点位于当前像素光栅化结果的覆盖范围内),不进行单独计算。此外它的做法和 SSAA 相同。由于 MSAA 拥有硬件支持,相对开销比较小,又能很好地解决几何走样问题,在游戏中应用非常广泛(我们在游戏画质选项中常看到的 4x/8x/16x 抗锯齿一般说的就是 MSAA 的子采样点数量分别为4/8/16个)。 + +下图为 4x MSAA 采样点示意: + +![](./screenshots/MSAA.png) + +在 Mapbox 中左图未开启 MSAA 而右图选择开启,观察立方体边缘可以发现明显的几何走样:相关 [ISSUE](https://github.com/mapbox/mapbox-gl-js/pull/8474)。 +![](./screenshots/mapbox-MSAA.png) + +但是 MSAA 存在一些限制: +* WebGL1 不支持对 FBO 进行,因此开启 post-processing 后处理时 MSAA 就失效了。当然 WebGL2 支持 🔗。 +* 即使开启,浏览器在某些情况下也不保证应用 🔗。 + +因此在需要后处理的场景中(例如 L7 的热力图需要 blur pass、PBR 中的 SSAO 环境光遮蔽),只能采用其他反走样手段。 + +## TAA(Temporal Anti-Aliasing) 原理 + +来自「知乎 - Experimentalize TAA with no code」🔗: + +> 严格来说 TAA 并不能算一个具体的算法,而是更像一个统一的算法框架。和 SSAA 一样,TAA 也能够同时减轻几何走样和着色走样的问题。 + +关于 TAA 的原理,「GDC - Temporal Reprojection +Anti-Aliasing in INSIDE」[🔗](http://twvideo01.ubm-us.net/o1/vault/gdc2016/Presentations/Pedersen_LasseJonFuglsang_TemporalReprojectionAntiAliasing.pdf) 讲的十分清晰。如果相机和物体的相对位置在当前帧之前发生过变化,那么当前帧就可以以若干前序帧进行修正。 + +![](./screenshots/taa-1.png) + +但如果在前序帧中相机和物体都没有发生过变化,那对于当前帧就无从修正了。因此可以对视锥进行抖动,在渲染每一帧之前,使用抖动矩阵对投影矩阵进行偏移,最终实现视锥的偏移: + +![](./screenshots/taa-step1.png) + +然后在 FS 中,最关键的就是 reproject 这一步: + +![](./screenshots/taa-step2.png) + +对于静止场景,「Three.js - TAA example」[🔗](https://threejs.org/examples/#webgl_postprocessing_taa)、「ECharts.GL - temporalSuperSampling」[🔗](https://echarts.apache.org/zh/option-gl.html#globe.temporalSuperSampling) 都采用了这种方法。 + +## 实现方法 + +由于需要对投影矩阵进行抖动,我们需要选取低差异序列。 +来自「知乎 - 低差异序列(一)- 常见序列的定义及性质」🔗,右图明显比左图纯随机生成覆盖面广: + +![](./screenshots/halton.png) + +参考 Echarts.GL,我们选择 `Halton(2,3)` 低差异序列: +```typescript +const offset = this.haltonSequence[this.frame % this.haltonSequence.length]; +this.cameraService.jitterProjectionMatrix( + ((offset[0] * 2.0 - 1.0) / width) * jitterScale, + ((offset[1] * 2.0 - 1.0) / height) * jitterScale, +); +``` + +在每一帧都会尝试进行累加。如果在连续运动过程中,TAA 的累加过程必然来不及完成,此时只需要输出当前帧原始结果即可,随后尝试继续轮询累加是否完成。因此在累加完成之前,都会输出当前帧未经 TAA 的结果。 + +最后我们需要进行加权平均,历史帧的权重应当越来越小: + +![](./screenshots/taa-step3.png) + +这里我们选择当前帧权重为 0.9,历史帧为 0.1,最终的混合结果供后续后处理模块继续处理: + +```typescript +useFramebuffer(this.outputRenderTarget, () => { + this.blendModel.draw({ + uniforms: { + u_Opacity: layerStyleOptions.opacity || 1, + u_MixRatio: this.frame === 0 ? 1 : 0.9, + u_Diffuse1: this.sampleRenderTarget, + u_Diffuse2: + this.frame === 0 + ? layer.multiPassRenderer.getPostProcessor().getReadFBO() + : this.prevRenderTarget, + }, + }); +}); +``` + +## 最终效果 + +为了更直观地看到效果,在 DEMO 中我们可以调节相机抖动范围: + +![](./screenshots/taa-result.gif) + +## 参考资料 + +* 「知乎 - 反走样技术(一):几何反走样」[🔗](https://zhuanlan.zhihu.com/p/28800047) +* 「知乎 - Experimentalize TAA with no code」[🔗](https://zhuanlan.zhihu.com/p/41642855) +* 「ECharts.GL - temporalSuperSampling」[🔗](https://echarts.apache.org/zh/option-gl.html#globe.temporalSuperSampling) +* 「Mapbox - set custom layers and extrusion examples to use antialias: true」[🔗](https://github.com/mapbox/mapbox-gl-js/pull/8474) +* 「Three.js - TAA example」[🔗](https://threejs.org/examples/#webgl_postprocessing_taa) +* 「Paper - Amortized Supersampling」[🔗](http://hhoppe.com/supersample.pdf) +* 「GDC - Temporal Reprojection Anti-Aliasing in INSIDE」[🔗](http://twvideo01.ubm-us.net/o1/vault/gdc2016/Presentations/Pedersen_LasseJonFuglsang_TemporalReprojectionAntiAliasing.pdf) +* 「知乎 - 低差异序列(一)- 常见序列的定义及性质」[🔗](https://zhuanlan.zhihu.com/p/20197323) \ No newline at end of file diff --git a/dev-docs/screenshots/MSAA.png b/dev-docs/screenshots/MSAA.png new file mode 100644 index 0000000000..205a8f2f61 Binary files /dev/null and b/dev-docs/screenshots/MSAA.png differ diff --git a/dev-docs/screenshots/halton.png b/dev-docs/screenshots/halton.png new file mode 100644 index 0000000000..7f8ff63842 Binary files /dev/null and b/dev-docs/screenshots/halton.png differ diff --git a/dev-docs/screenshots/mapbox-MSAA.png b/dev-docs/screenshots/mapbox-MSAA.png new file mode 100644 index 0000000000..8e79cc088b Binary files /dev/null and b/dev-docs/screenshots/mapbox-MSAA.png differ diff --git a/dev-docs/screenshots/taa-1.png b/dev-docs/screenshots/taa-1.png new file mode 100644 index 0000000000..4ce4460bc9 Binary files /dev/null and b/dev-docs/screenshots/taa-1.png differ diff --git a/dev-docs/screenshots/taa-result.gif b/dev-docs/screenshots/taa-result.gif new file mode 100644 index 0000000000..5beff2810b Binary files /dev/null and b/dev-docs/screenshots/taa-result.gif differ diff --git a/dev-docs/screenshots/taa-step1.png b/dev-docs/screenshots/taa-step1.png new file mode 100644 index 0000000000..0ae395bc6b Binary files /dev/null and b/dev-docs/screenshots/taa-step1.png differ diff --git a/dev-docs/screenshots/taa-step2.png b/dev-docs/screenshots/taa-step2.png new file mode 100644 index 0000000000..00d585ac91 Binary files /dev/null and b/dev-docs/screenshots/taa-step2.png differ diff --git a/dev-docs/screenshots/taa-step3.png b/dev-docs/screenshots/taa-step3.png new file mode 100644 index 0000000000..781a7f0164 Binary files /dev/null and b/dev-docs/screenshots/taa-step3.png differ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 349a16a5a1..8dc03033df 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,4 @@ -import container, { lazyInject } from './inversify.config'; +import container, { lazyInject, lazyMultiInject } from './inversify.config'; import ClearPass from './services/renderer/passes/ClearPass'; import MultiPassRenderer from './services/renderer/passes/MultiPassRenderer'; import PixelPickingPass from './services/renderer/passes/PixelPickingPass'; @@ -6,7 +6,7 @@ import BlurHPass from './services/renderer/passes/post-processing/BlurHPass'; import BlurVPass from './services/renderer/passes/post-processing/BlurVPass'; import CopyPass from './services/renderer/passes/post-processing/CopyPass'; import RenderPass from './services/renderer/passes/RenderPass'; -import SceneService from './services/scene/SceneService'; +import TAAPass from './services/renderer/passes/TAAPass'; import { TYPES } from './types'; import { packCircleVertex } from './utils/vertex-compression'; @@ -19,14 +19,11 @@ export { * lazy inject,供各个 Layer 使用 */ lazyInject, + lazyMultiInject, /** * 各个 Service 接口标识符 */ TYPES, - /** - * 各个 Service 接口 - */ - SceneService, packCircleVertex, /** pass */ MultiPassRenderer, @@ -36,6 +33,7 @@ export { BlurHPass, BlurVPass, CopyPass, + TAAPass, }; /** 暴露服务接口供其他 packages 实现 */ diff --git a/packages/core/src/inversify.config.ts b/packages/core/src/inversify.config.ts index 77993c1e9b..caacff0a30 100644 --- a/packages/core/src/inversify.config.ts +++ b/packages/core/src/inversify.config.ts @@ -16,6 +16,7 @@ import { IInteractionService } from './services/interaction/IInteractionService' import { ILayerService } from './services/layer/ILayerService'; import { IStyleAttributeService } from './services/layer/IStyleAttributeService'; import { ILogService } from './services/log/ILogService'; +import { ISceneService } from './services/scene/ISceneService'; import { IShaderModuleService } from './services/shader/IShaderModuleService'; /** Service implements */ @@ -29,6 +30,7 @@ import InteractionService from './services/interaction/InteractionService'; import LayerService from './services/layer/LayerService'; import StyleAttributeService from './services/layer/StyleAttributeService'; import LogService from './services/log/LogService'; +import SceneService from './services/scene/SceneService'; import ShaderModuleService from './services/shader/ShaderModuleService'; // @see https://github.com/inversify/InversifyJS/blob/master/wiki/container_api.md#defaultscope const container = new Container(); @@ -36,6 +38,10 @@ const container = new Container(); /** * bind services */ +container + .bind(TYPES.ISceneService) + .to(SceneService) + .inSingletonScope(); container .bind(TYPES.IGlobalConfigService) .to(GlobalConfigService) @@ -113,5 +119,25 @@ export const lazyInject = ( }; }; }; +export const lazyMultiInject = ( + serviceIdentifier: interfaces.ServiceIdentifier, +) => { + const original = DECORATORS.lazyMultiInject(serviceIdentifier); + // the 'descriptor' parameter is actually always defined for class fields for Babel, but is considered undefined for TSC + // so we just hack it with ?/! combination to avoid "TS1240: Unable to resolve signature of property decorator when called as an expression" + return function( + this: any, + proto: any, + key: string, + descriptor?: IBabelPropertyDescriptor, + ): void { + // make it work as usual + original.call(this, proto, key); + // return link to proto, so own value wont be 'undefined' after component's creation + descriptor!.initializer = () => { + return proto[key]; + }; + }; +}; export default container; diff --git a/packages/core/src/services/camera/CameraService.ts b/packages/core/src/services/camera/CameraService.ts index f66809afb0..be660885aa 100644 --- a/packages/core/src/services/camera/CameraService.ts +++ b/packages/core/src/services/camera/CameraService.ts @@ -1,3 +1,4 @@ +import { mat4 } from 'gl-matrix'; import { inject, injectable } from 'inversify'; import { ICameraService, IViewport } from './ICameraService'; @@ -10,6 +11,16 @@ export default class CameraService implements ICameraService { */ private overridedViewProjectionMatrix: number[] | undefined; + /** + * 抖动后的 VP 矩阵 + */ + private jitteredViewProjectionMatrix: number[] | undefined; + + /** + * 抖动后的 Projection 矩阵 + */ + private jitteredProjectionMatrix: number[] | undefined; + public init() { // } @@ -22,7 +33,8 @@ export default class CameraService implements ICameraService { } public getProjectionMatrix(): number[] { - return this.viewport.getProjectionMatrix(); + // 优先返回抖动后的 ProjectionMatrix + return this.jitteredProjectionMatrix || this.viewport.getProjectionMatrix(); } public getViewMatrix(): number[] { @@ -36,6 +48,7 @@ export default class CameraService implements ICameraService { public getViewProjectionMatrix(): number[] { return ( this.overridedViewProjectionMatrix || + this.jitteredViewProjectionMatrix || this.viewport.getViewProjectionMatrix() ); } @@ -70,4 +83,25 @@ export default class CameraService implements ICameraService { public setViewProjectionMatrix(viewProjectionMatrix: number[] | undefined) { this.overridedViewProjectionMatrix = viewProjectionMatrix; } + + public jitterProjectionMatrix(x: number, y: number) { + const translation = mat4.fromTranslation(mat4.create(), [x, y, 0]); + + this.jitteredProjectionMatrix = (mat4.multiply( + mat4.create(), + translation, + (this.viewport.getProjectionMatrix() as unknown) as mat4, + ) as unknown) as number[]; + + this.jitteredViewProjectionMatrix = (mat4.multiply( + mat4.create(), + (this.jitteredProjectionMatrix as unknown) as mat4, + (this.viewport.getViewMatrix() as unknown) as mat4, + ) as unknown) as number[]; + } + + public clearJitterProjectionMatrix() { + this.jitteredProjectionMatrix = undefined; + this.jitteredViewProjectionMatrix = undefined; + } } diff --git a/packages/core/src/services/camera/ICameraService.ts b/packages/core/src/services/camera/ICameraService.ts index 392bca7d2c..142c23b465 100644 --- a/packages/core/src/services/camera/ICameraService.ts +++ b/packages/core/src/services/camera/ICameraService.ts @@ -31,4 +31,6 @@ export interface ICameraService extends Omit { init(): void; update(viewport: IViewport): void; setViewProjectionMatrix(viewProjectionMatrix: number[] | undefined): void; + jitterProjectionMatrix(x: number, y: number): void; + clearJitterProjectionMatrix(): void; } diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index 2c17c661bf..ff8462936a 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -111,6 +111,14 @@ export interface ILayerInitializationOptions { * 高亮颜色 */ highlightColor: string | number[]; + /** + * 开启 TAA + */ + enableTAA: boolean; + /** + * 相机抖动程度 + */ + jitterScale: number; onHover(pickedFeature: IPickedFeature): void; onClick(pickedFeature: IPickedFeature): void; } diff --git a/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts b/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts index 2150f1392e..548ffe2eea 100644 --- a/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts +++ b/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts @@ -93,19 +93,6 @@ export default class BasePostProcessingPass }); }, ); - - // const useRenderTarget = (this.renderToScreen - // ? postProcessor.useScreenRenderTarget - // : postProcessor.useOffscreenRenderTarget - // ).bind(postProcessor); - - // useRenderTarget(async () => { - // this.model.draw({ - // uniforms: { - // u_Texture: postProcessor.getReadFBO(), - // }, - // }); - // }); } public isEnabled() { diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index 9406ed4ae5..b4a6f371b2 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -104,9 +104,9 @@ export default class PixelPickingPass implements IPass { const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag(); this.layer.multiPassRenderer.setRenderFlag(false); // trigger hooks - layer.hooks.beforeRender.call(); + layer.hooks.beforePickingEncode.call(); layer.render(); - layer.hooks.afterRender.call(); + layer.hooks.afterPickingEncode.call(); this.layer.multiPassRenderer.setRenderFlag(originRenderFlag); this.alreadyInRendering = false; @@ -207,15 +207,28 @@ export default class PixelPickingPass implements IPass { */ private highlightPickedFeature(pickedColors: Uint8Array | undefined) { const [r, g, b] = pickedColors; + const { clear, useFramebuffer } = this.rendererService; - // TODO: highlight pass 需要 multipass - const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag(); - this.layer.multiPassRenderer.setRenderFlag(false); + // 先输出到 PostProcessor + const readFBO = this.layer.multiPassRenderer.getPostProcessor().getReadFBO(); this.layer.hooks.beforeRender.call(); - this.layer.hooks.beforeHighlight.call([r, g, b]); - this.layer.render(); - this.layer.hooks.afterHighlight.call(); - this.layer.hooks.afterRender.call(); - this.layer.multiPassRenderer.setRenderFlag(originRenderFlag); + useFramebuffer(readFBO, () => { + clear({ + color: [0, 0, 0, 0], + depth: 1, + stencil: 0, + framebuffer: readFBO, + }); + + // TODO: highlight pass 需要 multipass + const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag(); + this.layer.multiPassRenderer.setRenderFlag(false); + this.layer.hooks.beforeHighlight.call([r, g, b]); + this.layer.render(); + this.layer.hooks.afterHighlight.call(); + this.layer.hooks.afterRender.call(); + this.layer.multiPassRenderer.setRenderFlag(originRenderFlag); + }); + this.layer.multiPassRenderer.getPostProcessor().render(this.layer); } } diff --git a/packages/core/src/services/renderer/passes/PostProcessor.ts b/packages/core/src/services/renderer/passes/PostProcessor.ts index f93e071175..0403904edf 100644 --- a/packages/core/src/services/renderer/passes/PostProcessor.ts +++ b/packages/core/src/services/renderer/passes/PostProcessor.ts @@ -52,6 +52,7 @@ export default class PostProcessor implements IPostProcessor { public async render(layer: ILayer) { for (let i = 0; i < this.passes.length; i++) { const pass = this.passes[i]; + // last pass should render to screen pass.setRenderToScreen(this.isLastEnabledPass(i)); await pass.render(layer); diff --git a/packages/core/src/services/renderer/passes/TAAPass.ts b/packages/core/src/services/renderer/passes/TAAPass.ts index 524b314ba5..f23e75af17 100644 --- a/packages/core/src/services/renderer/passes/TAAPass.ts +++ b/packages/core/src/services/renderer/passes/TAAPass.ts @@ -1,75 +1,333 @@ -// import { inject, injectable } from 'inversify'; -// import { ILayer, ILayerService } from '../../layer/ILayerService'; -// import { IPass, PassType } from '../IMultiPassRenderer'; +import { inject, injectable } from 'inversify'; +import { lazyInject } from '../../../index'; +import blendFS from '../../../shaders/post-processing/blend.glsl'; +import copyFS from '../../../shaders/post-processing/copy.glsl'; +import quadVS from '../../../shaders/post-processing/quad.glsl'; +import { TYPES } from '../../../types'; +import { ICameraService } from '../../camera/ICameraService'; +import { ILayer, ILayerService } from '../../layer/ILayerService'; +import { ILogService } from '../../log/ILogService'; +import { IShaderModuleService } from '../../shader/IShaderModuleService'; +import { gl } from '../gl'; +import { IFramebuffer } from '../IFramebuffer'; +import { IModel, IModelInitializationOptions } from '../IModel'; +import { IPass, PassType } from '../IMultiPassRenderer'; +import { IRendererService } from '../IRendererService'; -// // Generate halton sequence -// // https://en.wikipedia.org/wiki/Halton_sequence -// function halton(index: number, base: number) { -// let result = 0; -// let f = 1 / base; -// let i = index; -// while (i > 0) { -// result = result + f * (i % base); -// i = Math.floor(i / base); -// f = f / base; -// } -// return result; -// } +// Generate halton sequence +// https://en.wikipedia.org/wiki/Halton_sequence +function halton(index: number, base: number) { + let result = 0; + let f = 1 / base; + let i = index; + while (i > 0) { + result = result + f * (i % base); + i = Math.floor(i / base); + f = f / base; + } + return result; +} -// // 累加计数器 -// let accumulatingId = 1; +// 累加计数器 +let accumulatingId = 1; -// /** -// * TAA(Temporal Anti-Aliasing) -// * 在需要后处理的场景中(例如 L7 的热力图需要 blur pass、PBR 中的 SSAO 环境光遮蔽),无法使用浏览器内置的 MSAA, -// * 只能使用 TAA -// * @see https://yuque.antfin-inc.com/yuqi.pyq/fgetpa/ri52hv -// */ -// @injectable() -// export default class TAAPass implements IPass { -// private haltonSequence: Array<[number, number]> = []; +/** + * TAA(Temporal Anti-Aliasing) + * 在需要后处理的场景中(例如 L7 的热力图需要 blur pass、PBR 中的 SSAO 环境光遮蔽),无法使用浏览器内置的 MSAA, + * 只能使用 TAA + * @see https://yuque.antfin-inc.com/yuqi.pyq/fgetpa/ri52hv + */ +@injectable() +export default class TAAPass implements IPass { + @lazyInject(TYPES.IRendererService) + protected readonly rendererService: IRendererService; -// // 当前累加任务 ID -// private accumulatingId: number = 0; + @lazyInject(TYPES.IShaderModuleService) + protected readonly shaderModule: IShaderModuleService; -// private frame: number = 0; + @lazyInject(TYPES.ICameraService) + protected readonly cameraService: ICameraService; -// private timer: number | undefined = undefined; + @lazyInject(TYPES.ILogService) + protected readonly logger: ILogService; -// public getType() { -// return PassType.Normal; -// } + /** + * 低差异序列 + */ + private haltonSequence: Array<[number, number]> = []; -// public init(layer: ILayer) { -// this.scene = scene; -// this.camera = camera; + /** + * 当前累加任务 ID,例如用户连续拖拽时上一次累加很有可能没有结束,此时在开启新一轮累加之前需要结束掉之前未完成的 + */ + private accumulatingId: number = 0; -// for (let i = 0; i < 30; i++) { -// this.haltonSequence.push([halton(i, 2), halton(i, 3)]); -// } + /** + * 每一轮累加中的 frameID + */ + private frame: number = 0; -// // weigh accumulating -// this.blendPass = new ShaderPass(BlendShader); -// } + /** + * 每一轮累加中的 frame 定时器 + */ + private timer: number | undefined = undefined; -// public render(layer: ILayer) { -// // -// } + private sampleRenderTarget: IFramebuffer; + private prevRenderTarget: IFramebuffer; + private outputRenderTarget: IFramebuffer; + private copyRenderTarget: IFramebuffer; -// /** -// * 是否已经完成累加 -// * @return {boolean} isFinished -// */ -// private isFinished() { -// return this.frame >= this.haltonSequence.length; -// } + private blendModel: IModel; + private outputModel: IModel; + private copyModel: IModel; -// private resetFrame() { -// this.frame = 0; -// } + public getType() { + return PassType.Normal; + } -// private stopAccumulating() { -// this.accumulatingId = 0; -// window.clearTimeout(this.timer); -// } -// } + public init(layer: ILayer) { + const { createFramebuffer, createTexture2D } = this.rendererService; + this.sampleRenderTarget = createFramebuffer({ + color: createTexture2D({ + width: 1, + height: 1, + wrapS: gl.CLAMP_TO_EDGE, + wrapT: gl.CLAMP_TO_EDGE, + }), + }); + this.prevRenderTarget = createFramebuffer({ + color: createTexture2D({ + width: 1, + height: 1, + wrapS: gl.CLAMP_TO_EDGE, + wrapT: gl.CLAMP_TO_EDGE, + }), + }); + this.outputRenderTarget = createFramebuffer({ + color: createTexture2D({ + width: 1, + height: 1, + wrapS: gl.CLAMP_TO_EDGE, + wrapT: gl.CLAMP_TO_EDGE, + }), + }); + this.copyRenderTarget = createFramebuffer({ + color: createTexture2D({ + width: 1, + height: 1, + wrapS: gl.CLAMP_TO_EDGE, + wrapT: gl.CLAMP_TO_EDGE, + }), + }); + + for (let i = 0; i < 30; i++) { + this.haltonSequence.push([halton(i, 2), halton(i, 3)]); + } + + this.blendModel = this.createTriangleModel('blend-pass', blendFS); + this.outputModel = this.createTriangleModel('copy-pass', copyFS, { + blend: { + enable: true, + func: { + srcRGB: gl.ONE, + dstRGB: gl.ONE_MINUS_SRC_ALPHA, + srcAlpha: gl.ONE, + dstAlpha: gl.ONE_MINUS_SRC_ALPHA, + }, + equation: { + rgb: gl.FUNC_ADD, + alpha: gl.FUNC_ADD, + }, + }, + }); + this.copyModel = this.createTriangleModel('copy-pass', copyFS); + } + + public render(layer: ILayer) { + const { clear, getViewportSize, useFramebuffer } = this.rendererService; + const { width, height } = getViewportSize(); + this.sampleRenderTarget.resize({ width, height }); + this.prevRenderTarget.resize({ width, height }); + this.outputRenderTarget.resize({ width, height }); + this.copyRenderTarget.resize({ width, height }); + + this.resetFrame(); + // 首先停止上一次的累加 + this.stopAccumulating(); + + // 先输出到 PostProcessor + const readFBO = layer.multiPassRenderer.getPostProcessor().getReadFBO(); + useFramebuffer(readFBO, () => { + clear({ + color: [0, 0, 0, 0], + depth: 1, + stencil: 0, + framebuffer: readFBO, + }); + + // render to post processor + layer.multiPassRenderer.setRenderFlag(false); + layer.render(); + layer.multiPassRenderer.setRenderFlag(true); + }); + + const accumulate = (id: number) => { + // 在开启新一轮累加之前,需要先结束掉之前的累加 + if (!this.accumulatingId || id !== this.accumulatingId) { + return; + } + + if (!this.isFinished()) { + this.doRender(layer); + + requestAnimationFrame(() => { + accumulate(id); + }); + } + }; + + this.accumulatingId = accumulatingId++; + // @ts-ignore + this.timer = setTimeout(() => { + accumulate(this.accumulatingId); + }, 50); + } + + private doRender(layer: ILayer) { + this.logger.info(`accumulatingId: ${this.accumulatingId}`); + + const { clear, getViewportSize, useFramebuffer } = this.rendererService; + const { width, height } = getViewportSize(); + const { jitterScale = 1 } = layer.getStyleOptions(); + + // 使用 Halton 序列抖动投影矩阵 + const offset = this.haltonSequence[this.frame % this.haltonSequence.length]; + this.cameraService.jitterProjectionMatrix( + ((offset[0] * 2.0 - 1.0) / width) * jitterScale, + ((offset[1] * 2.0 - 1.0) / height) * jitterScale, + ); + + // 按抖动后的投影矩阵渲染 + layer.multiPassRenderer.setRenderFlag(false); + layer.hooks.beforeRender.call(); + useFramebuffer(this.sampleRenderTarget, () => { + clear({ + color: [0, 0, 0, 0], + depth: 1, + stencil: 0, + framebuffer: this.sampleRenderTarget, + }); + + layer.render(); + }); + layer.hooks.afterRender.call(); + layer.multiPassRenderer.setRenderFlag(true); + + // 混合 + const layerStyleOptions = layer.getStyleOptions(); + useFramebuffer(this.outputRenderTarget, () => { + this.blendModel.draw({ + uniforms: { + // @ts-ignore + u_Opacity: layerStyleOptions.opacity || 1, + u_MixRatio: this.frame === 0 ? 1 : 0.9, + u_Diffuse1: this.sampleRenderTarget, + u_Diffuse2: + this.frame === 0 + ? layer.multiPassRenderer.getPostProcessor().getReadFBO() + : this.prevRenderTarget, + }, + }); + }); + + // 输出累加结果 + if (this.frame === 0) { + clear({ + color: [0, 0, 0, 0], + depth: 1, + stencil: 0, + framebuffer: this.copyRenderTarget, + }); + } + + if (this.frame >= 1) { + useFramebuffer(this.copyRenderTarget, () => { + this.outputModel.draw({ + uniforms: { + u_Texture: this.outputRenderTarget, + }, + }); + }); + + useFramebuffer(null, () => { + this.copyModel.draw({ + uniforms: { + u_Texture: this.copyRenderTarget, + }, + }); + }); + } + + // 保存前序帧结果 + const tmp = this.prevRenderTarget; + this.prevRenderTarget = this.outputRenderTarget; + this.outputRenderTarget = tmp; + + this.frame++; + + // 恢复 jitter 后的相机 + this.cameraService.clearJitterProjectionMatrix(); + } + + /** + * 是否已经完成累加 + * @return {boolean} isFinished + */ + private isFinished() { + return this.frame >= this.haltonSequence.length; + } + + private resetFrame() { + this.frame = 0; + } + + private stopAccumulating() { + this.accumulatingId = 0; + window.clearTimeout(this.timer); + } + + private createTriangleModel( + shaderModuleName: string, + fragmentShader: string, + options?: Partial, + ) { + this.shaderModule.registerModule(shaderModuleName, { + vs: quadVS, + fs: fragmentShader, + }); + + const { vs, fs, uniforms } = this.shaderModule.getModule(shaderModuleName); + const { createAttribute, createBuffer, createModel } = this.rendererService; + return createModel({ + vs, + fs, + attributes: { + // 使用一个全屏三角形,相比 Quad 顶点数目更少 + a_Position: createAttribute({ + buffer: createBuffer({ + data: [-4, -4, 4, -4, 0, 4], + type: gl.FLOAT, + }), + size: 2, + }), + }, + uniforms: { + ...uniforms, + }, + depth: { + enable: false, + }, + count: 3, + ...options, + }); + } +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b6e2c78d7b..e9a13b853a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,4 +1,5 @@ const TYPES = { + ISceneService: Symbol.for('ISceneService'), IGlobalConfigService: Symbol.for('IGlobalConfigService'), ICameraService: Symbol.for('ICameraService'), ICoordinateSystemService: Symbol.for('ICoordinateSystemService'), @@ -14,6 +15,7 @@ const TYPES = { IInteractionService: Symbol.for('IInteractionService'), IControlService: Symbol.for('IControlService'), IStyleAttributeService: Symbol.for('IStyleAttributeService'), + ILayerPlugin: Symbol.for('ILayerPlugin'), /** multi-pass */ ClearPass: Symbol.for('ClearPass'), diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 05c7c8a9db..91faa909aa 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -17,6 +17,7 @@ import { IStyleAttributeService, IStyleAttributeUpdateOptions, lazyInject, + lazyMultiInject, StyleAttributeField, StyleAttributeOption, Triangulation, @@ -27,15 +28,7 @@ import { isFunction } from 'lodash'; // @ts-ignore import mergeJsonSchemas from 'merge-json-schemas'; import { SyncBailHook, SyncHook } from 'tapable'; -import ConfigSchemaValidationPlugin from '../plugins/ConfigSchemaValidationPlugin'; -import DataMappingPlugin from '../plugins/DataMappingPlugin'; -import DataSourcePlugin from '../plugins/DataSourcePlugin'; -import FeatureScalePlugin from '../plugins/FeatureScalePlugin'; -import MultiPassRendererPlugin from '../plugins/MultiPassRendererPlugin'; -import PixelPickingPlugin from '../plugins/PixelPickingPlugin'; -import RegisterStyleAttributePlugin from '../plugins/RegisterStyleAttributePlugin'; -import ShaderUniformPlugin from '../plugins/ShaderUniformPlugin'; -import UpdateStyleAttributePlugin from '../plugins/UpdateStyleAttributePlugin'; + import baseLayerSchema from './schema'; export interface ILayerModelInitializationOptions { @@ -60,6 +53,8 @@ const defaultLayerInitializationOptions: Partial< enablePicking: false, enableHighlight: false, highlightColor: 'red', + enableTAA: false, + jitterScale: 1, }; export default class BaseLayer implements ILayer { @@ -85,46 +80,10 @@ export default class BaseLayer implements ILayer { // 每个 Layer 都有一个 public multiPassRenderer: IMultiPassRenderer; - // 插件集 - public plugins: ILayerPlugin[] = [ - /** - * 校验传入参数配置项的正确性 - * @see /dev-docs/ConfigSchemaValidation.md - */ - new ConfigSchemaValidationPlugin(), - /** - * 获取 Source - */ - new DataSourcePlugin(), - /** - * 根据 StyleAttribute 创建 VertexAttribute - */ - new RegisterStyleAttributePlugin(), - /** - * 根据 Source 创建 Scale - */ - new FeatureScalePlugin(), - /** - * 使用 Scale 进行数据映射 - */ - new DataMappingPlugin(), - /** - * 负责属性更新 - */ - new UpdateStyleAttributePlugin(), - /** - * Multi Pass 自定义渲染管线 - */ - new MultiPassRendererPlugin(), - /** - * 传入相机坐标系参数 - */ - new ShaderUniformPlugin(), - /** - * 负责拾取过程中 Encode 以及 Highlight 阶段及结束后恢复 - */ - new PixelPickingPlugin(), - ]; + // 注入插件集 + @lazyMultiInject(TYPES.ILayerPlugin) + public plugins: ILayerPlugin[]; + public sourceOption: { data: any; options?: ISourceCFG; diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts index c8e229d59b..aa5ecc6e54 100644 --- a/packages/layers/src/index.ts +++ b/packages/layers/src/index.ts @@ -1,3 +1,4 @@ +import { container, ILayerPlugin, TYPES } from '@l7/core'; import BaseLayer from './core/BaseLayer'; import Point3dLayer from './point/extrude'; // import HeatMapLayer from './heatmap'; @@ -6,6 +7,59 @@ import PointLayer from './point/index'; // import Point from './point/point'; import PolygonLayer from './polygon'; // import ImageLayer from './raster'; + +import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin'; +import DataMappingPlugin from './plugins/DataMappingPlugin'; +import DataSourcePlugin from './plugins/DataSourcePlugin'; +import FeatureScalePlugin from './plugins/FeatureScalePlugin'; +import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin'; +import PixelPickingPlugin from './plugins/PixelPickingPlugin'; +import RegisterStyleAttributePlugin from './plugins/RegisterStyleAttributePlugin'; +import ShaderUniformPlugin from './plugins/ShaderUniformPlugin'; +import UpdateStyleAttributePlugin from './plugins/UpdateStyleAttributePlugin'; + +/** + * 校验传入参数配置项的正确性 + * @see /dev-docs/ConfigSchemaValidation.md + */ +container + .bind(TYPES.ILayerPlugin) + .to(ConfigSchemaValidationPlugin); +/** + * 获取 Source + */ +container.bind(TYPES.ILayerPlugin).to(DataSourcePlugin); +/** + * 根据 StyleAttribute 创建 VertexAttribute + */ +container + .bind(TYPES.ILayerPlugin) + .to(RegisterStyleAttributePlugin); +/** + * 根据 Source 创建 Scale + */ +container.bind(TYPES.ILayerPlugin).to(FeatureScalePlugin); +/** + * 使用 Scale 进行数据映射 + */ +container.bind(TYPES.ILayerPlugin).to(DataMappingPlugin); +/** + * 负责属性更新 + */ +container.bind(TYPES.ILayerPlugin).to(UpdateStyleAttributePlugin); +/** + * Multi Pass 自定义渲染管线 + */ +container.bind(TYPES.ILayerPlugin).to(MultiPassRendererPlugin); +/** + * 传入相机坐标系参数 + */ +container.bind(TYPES.ILayerPlugin).to(ShaderUniformPlugin); +/** + * 负责拾取过程中 Encode 以及 Highlight 阶段及结束后恢复 + */ +container.bind(TYPES.ILayerPlugin).to(PixelPickingPlugin); + export { BaseLayer, PointLayer, diff --git a/packages/layers/src/plugins/ConfigSchemaValidationPlugin.ts b/packages/layers/src/plugins/ConfigSchemaValidationPlugin.ts index 30321502ef..8674daef49 100644 --- a/packages/layers/src/plugins/ConfigSchemaValidationPlugin.ts +++ b/packages/layers/src/plugins/ConfigSchemaValidationPlugin.ts @@ -3,18 +3,19 @@ import { ILayer, ILayerPlugin, ILogService, - lazyInject, TYPES, } from '@l7/core'; +import { inject, injectable } from 'inversify'; /** * Layer 初始化阶段以及重绘阶段首先校验传入参数,如果校验失败则中断后续插件处理。 */ +@injectable() export default class ConfigSchemaValidationPlugin implements ILayerPlugin { - @lazyInject(TYPES.IGlobalConfigService) + @inject(TYPES.IGlobalConfigService) private readonly configService: IGlobalConfigService; - @lazyInject(TYPES.ILogService) + @inject(TYPES.ILogService) private readonly logger: ILogService; public apply(layer: ILayer) { diff --git a/packages/layers/src/plugins/DataMappingPlugin.ts b/packages/layers/src/plugins/DataMappingPlugin.ts index 886751e6bd..f864dfddfe 100644 --- a/packages/layers/src/plugins/DataMappingPlugin.ts +++ b/packages/layers/src/plugins/DataMappingPlugin.ts @@ -9,13 +9,15 @@ import { lazyInject, TYPES, } from '@l7/core'; +import { inject, injectable } from 'inversify'; import { rgb2arr } from '../utils/color'; +@injectable() export default class DataMappingPlugin implements ILayerPlugin { - @lazyInject(TYPES.IGlobalConfigService) + @inject(TYPES.IGlobalConfigService) private readonly configService: IGlobalConfigService; - @lazyInject(TYPES.ILogService) + @inject(TYPES.ILogService) private readonly logger: ILogService; public apply(layer: ILayer) { diff --git a/packages/layers/src/plugins/DataSourcePlugin.ts b/packages/layers/src/plugins/DataSourcePlugin.ts index bafcd483be..df7cfb0c9a 100644 --- a/packages/layers/src/plugins/DataSourcePlugin.ts +++ b/packages/layers/src/plugins/DataSourcePlugin.ts @@ -1,5 +1,8 @@ import { ILayer, ILayerPlugin } from '@l7/core'; import Source from '@l7/source'; +import { injectable } from 'inversify'; + +@injectable() export default class DataSourcePlugin implements ILayerPlugin { public apply(layer: ILayer) { layer.hooks.init.tap('DataSourcePlugin', () => { diff --git a/packages/layers/src/plugins/FeatureScalePlugin.ts b/packages/layers/src/plugins/FeatureScalePlugin.ts index ecd80266bc..e1ab069e0d 100644 --- a/packages/layers/src/plugins/FeatureScalePlugin.ts +++ b/packages/layers/src/plugins/FeatureScalePlugin.ts @@ -14,6 +14,7 @@ import { import { IParseDataItem } from '@l7/source'; import { extent } from 'd3-array'; import * as d3 from 'd3-scale'; +import { inject, injectable } from 'inversify'; import { isNil, isNumber, isString, uniq } from 'lodash'; const dateRegex = /^(?:(?!0000)[0-9]{4}([-/.]+)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))(\s+([01]|([01][0-9]|2[0-3])):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9]))?$/; @@ -33,11 +34,12 @@ const scaleMap = { /** * 根据 Source 原始数据为指定字段创建 Scale,保存在 StyleAttribute 上,供下游插件使用 */ +@injectable() export default class FeatureScalePlugin implements ILayerPlugin { - @lazyInject(TYPES.IGlobalConfigService) + @inject(TYPES.IGlobalConfigService) private readonly configService: IGlobalConfigService; - @lazyInject(TYPES.ILogService) + @inject(TYPES.ILogService) private readonly logger: ILogService; // key = field_attribute name diff --git a/packages/layers/src/plugins/MultiPassRendererPlugin.ts b/packages/layers/src/plugins/MultiPassRendererPlugin.ts index e6c3970139..953ef60b73 100644 --- a/packages/layers/src/plugins/MultiPassRendererPlugin.ts +++ b/packages/layers/src/plugins/MultiPassRendererPlugin.ts @@ -12,8 +12,10 @@ import { MultiPassRenderer, PixelPickingPass, RenderPass, + TAAPass, TYPES, } from '@l7/core'; +import { inject, injectable } from 'inversify'; const builtinPostProcessingPassMap: { [key: string]: new (config?: { [key: string]: any }) => IPostProcessingPass; @@ -47,11 +49,12 @@ function normalizePasses( * ], * }) */ +@injectable() export default class MultiPassRendererPlugin implements ILayerPlugin { - @lazyInject(TYPES.IGlobalConfigService) + @inject(TYPES.IGlobalConfigService) private readonly configService: IGlobalConfigService; - @lazyInject(TYPES.IRendererService) + @inject(TYPES.IRendererService) private readonly rendererService: IRendererService; private enabled: boolean; @@ -97,13 +100,23 @@ export default class MultiPassRendererPlugin implements ILayerPlugin { passes: Array, ) { const multiPassRenderer = new MultiPassRenderer(layer); + const { enablePicking, enableTAA } = layer.getStyleOptions(); + // clear first multiPassRenderer.add(new ClearPass()); - if (layer.getStyleOptions().enablePicking) { + // picking pass if enabled + if (enablePicking) { multiPassRenderer.add(new PixelPickingPass()); } - multiPassRenderer.add(new RenderPass()); + + // TAA pass if enabled + if (enableTAA) { + multiPassRenderer.add(new TAAPass()); + } else { + // render all layers in this pass + multiPassRenderer.add(new RenderPass()); + } // post processing normalizePasses(passes).forEach( @@ -112,6 +125,7 @@ export default class MultiPassRendererPlugin implements ILayerPlugin { multiPassRenderer.add(new PostProcessingPassClazz(pass[1])); }, ); + // 末尾为固定的 CopyPass multiPassRenderer.add(new CopyPass()); diff --git a/packages/layers/src/plugins/PixelPickingPlugin.ts b/packages/layers/src/plugins/PixelPickingPlugin.ts index ac0213b50c..1c3f1b341e 100644 --- a/packages/layers/src/plugins/PixelPickingPlugin.ts +++ b/packages/layers/src/plugins/PixelPickingPlugin.ts @@ -8,6 +8,7 @@ import { lazyInject, TYPES, } from '@l7/core'; +import { inject, injectable } from 'inversify'; import { rgb2arr } from '../utils/color'; function encodePickingColor(featureIdx: number): [number, number, number] { @@ -24,8 +25,9 @@ const PickingStage = { HIGHLIGHT: 2.0, }; +@injectable() export default class PixelPickingPlugin implements ILayerPlugin { - @lazyInject(TYPES.IRendererService) + @inject(TYPES.IRendererService) private readonly rendererService: IRendererService; public apply(layer: ILayer) { @@ -51,22 +53,28 @@ export default class PixelPickingPlugin implements ILayerPlugin { }); // 必须要与 PixelPickingPass 结合使用,因此必须开启 multiPassRenderer // if (layer.multiPassRenderer) { - layer.hooks.beforeRender.tap('PixelPickingPlugin', () => { - layer.models.forEach((model) => - model.addUniforms({ - u_PickingStage: PickingStage.ENCODE, - }), - ); + layer.hooks.beforePickingEncode.tap('PixelPickingPlugin', () => { + const { enablePicking } = layer.getStyleOptions(); + if (enablePicking) { + layer.models.forEach((model) => + model.addUniforms({ + u_PickingStage: PickingStage.ENCODE, + }), + ); + } }); - layer.hooks.afterRender.tap('PixelPickingPlugin', () => { - layer.models.forEach((model) => - model.addUniforms({ - u_PickingStage: PickingStage.NONE, - u_PickingColor: [0, 0, 0], - u_HighlightColor: [0, 0, 0, 0], - }), - ); + layer.hooks.afterPickingEncode.tap('PixelPickingPlugin', () => { + const { enablePicking } = layer.getStyleOptions(); + if (enablePicking) { + layer.models.forEach((model) => + model.addUniforms({ + u_PickingStage: PickingStage.NONE, + u_PickingColor: [0, 0, 0], + u_HighlightColor: [0, 0, 0, 0], + }), + ); + } }); layer.hooks.beforeHighlight.tap( diff --git a/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts b/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts index 64fe7e8732..0c75fd9229 100644 --- a/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts +++ b/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts @@ -5,16 +5,17 @@ import { ILayer, ILayerPlugin, ILogService, - IStyleAttributeService, lazyInject, TYPES, } from '@l7/core'; +import { inject, injectable } from 'inversify'; /** * 在初始化阶段完成属性的注册,以及首次根据 Layer 指定的三角化方法完成 indices 和 attribute 的创建 */ +@injectable() export default class RegisterStyleAttributePlugin implements ILayerPlugin { - @lazyInject(TYPES.ILogService) + @inject(TYPES.ILogService) private readonly logger: ILogService; public apply(layer: ILayer) { diff --git a/packages/layers/src/plugins/ShaderUniformPlugin.ts b/packages/layers/src/plugins/ShaderUniformPlugin.ts index 4918d71c1b..3171f75fcb 100644 --- a/packages/layers/src/plugins/ShaderUniformPlugin.ts +++ b/packages/layers/src/plugins/ShaderUniformPlugin.ts @@ -9,6 +9,7 @@ import { lazyInject, TYPES, } from '@l7/core'; +import { inject, injectable } from 'inversify'; /** * 在渲染之前需要获取当前 Shader 所需 Uniform,例如: @@ -17,14 +18,15 @@ import { * @see https://yuque.antfin-inc.com/yuqi.pyq/fgetpa/doml91 * 3. 当前 Layer 本身的样式属性 */ +@injectable() export default class ShaderUniformPlugin implements ILayerPlugin { - @lazyInject(TYPES.ICameraService) + @inject(TYPES.ICameraService) private readonly cameraService: ICameraService; - @lazyInject(TYPES.ICoordinateSystemService) + @inject(TYPES.ICoordinateSystemService) private readonly coordinateSystemService: ICoordinateSystemService; - @lazyInject(TYPES.IRendererService) + @inject(TYPES.IRendererService) private readonly rendererService: IRendererService; public apply(layer: ILayer) { diff --git a/packages/layers/src/plugins/UpdateStyleAttributePlugin.ts b/packages/layers/src/plugins/UpdateStyleAttributePlugin.ts index b11338575b..36618a1978 100644 --- a/packages/layers/src/plugins/UpdateStyleAttributePlugin.ts +++ b/packages/layers/src/plugins/UpdateStyleAttributePlugin.ts @@ -1,10 +1,12 @@ import { ILayer, ILayerPlugin, ILogService, lazyInject, TYPES } from '@l7/core'; +import { inject, injectable } from 'inversify'; /** * 在初始化阶段完成属性的注册,以及首次根据 Layer 指定的三角化方法完成 indices 和 attribute 的创建 */ +@injectable() export default class UpdateStyleAttributePlugin implements ILayerPlugin { - @lazyInject(TYPES.ILogService) + @inject(TYPES.ILogService) private readonly logger: ILogService; public apply(layer: ILayer) { diff --git a/packages/renderer/src/regl/ReglModel.ts b/packages/renderer/src/regl/ReglModel.ts index 9f0fc60873..16730973cb 100644 --- a/packages/renderer/src/regl/ReglModel.ts +++ b/packages/renderer/src/regl/ReglModel.ts @@ -171,9 +171,10 @@ export default class ReglModel implements IModel { enable: enable === undefined ? false : !!enable, func: { srcRGB: blendFuncMap[(func && func.srcRGB) || gl.SRC_ALPHA], - srcAlpha: func && func.srcAlpha, + srcAlpha: blendFuncMap[(func && func.srcAlpha) || gl.SRC_ALPHA], dstRGB: blendFuncMap[(func && func.dstRGB) || gl.ONE_MINUS_SRC_ALPHA], - dstAlpha: func && func.dstAlpha, + dstAlpha: + blendFuncMap[(func && func.dstAlpha) || gl.ONE_MINUS_SRC_ALPHA], }, equation: { rgb: blendEquationMap[(equation && equation.rgb) || gl.FUNC_ADD], diff --git a/packages/scene/src/index.ts b/packages/scene/src/index.ts index 3eaa19d9c2..db32528db2 100644 --- a/packages/scene/src/index.ts +++ b/packages/scene/src/index.ts @@ -17,12 +17,10 @@ import { MapType, Point, SceneEventList, - SceneService, TYPES, } from '@l7/core'; import { AMapService, MapboxService } from '@l7/maps'; import { ReglRendererService } from '@l7/renderer'; -import { inject, injectable } from 'inversify'; import { Map } from 'mapbox-gl'; // 绑定渲染引擎服务 @@ -83,7 +81,7 @@ class Scene { } // 依赖注入 - this.sceneService = container.resolve(SceneService); + this.sceneService = container.get(TYPES.ISceneService); this.sceneService.init(config); this.mapService = container.get(TYPES.IMapService); this.iconService = container.get(TYPES.IIconService); diff --git a/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx b/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx index fb004ece6b..9bcc5c8ce6 100644 --- a/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx +++ b/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx @@ -1,5 +1,8 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; -import Polygon from './components/Polygon'; +import Blur from './components/Blur'; +import TAA from './components/TAA'; // @ts-ignore -storiesOf('MultiPassRenderer', module).add('blur', () => ); +storiesOf('MultiPassRenderer', module) + .add('Blur', () => ) + .add('TAA(Temporal Anti-Aliasing)', () => ); diff --git a/stories/MultiPassRenderer/components/Polygon.tsx b/stories/MultiPassRenderer/components/Blur.tsx similarity index 96% rename from stories/MultiPassRenderer/components/Polygon.tsx rename to stories/MultiPassRenderer/components/Blur.tsx index 9e5efca203..f226418217 100644 --- a/stories/MultiPassRenderer/components/Polygon.tsx +++ b/stories/MultiPassRenderer/components/Blur.tsx @@ -34,7 +34,8 @@ export default class Mapbox extends React.Component { zoom: 3, }); const layer = new PolygonLayer({ - enablePicking: false, + enablePicking: true, + enableHighlight: true, passes: [ 'blurH', [ diff --git a/stories/MultiPassRenderer/components/TAA.tsx b/stories/MultiPassRenderer/components/TAA.tsx new file mode 100644 index 0000000000..d205cb946f --- /dev/null +++ b/stories/MultiPassRenderer/components/TAA.tsx @@ -0,0 +1,105 @@ +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore +import { Scene } from '@l7/scene'; +import * as dat from 'dat.gui'; +import * as React from 'react'; + +export default class TAA 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', + type: 'mapbox', + style: 'mapbox://styles/mapbox/streets-v9', + center: [110.19382669582967, 50.258134], + pitch: 0, + zoom: 3, + }); + const layer = new PolygonLayer({ + enablePicking: true, + enableHighlight: true, + enableTAA: true, + jitterScale: 1, + // passes: [ + // 'blurH', + // [ + // 'blurV', + // { + // blurRadius: 8, + // }, + // ], + // ], + }); + + layer + .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(layer); + scene.render(); + + this.scene = scene; + + /*** 运行时修改样式属性 ***/ + const gui = new dat.GUI(); + this.gui = gui; + const styleOptions = { + jitterScale: 1, + }; + const pointFolder = gui.addFolder('TAA 配置'); + pointFolder + .add(styleOptions, 'jitterScale', 0, 100) + .onChange((jitterScale: boolean) => { + layer.style({ + jitterScale, + }); + scene.render(); + }); + pointFolder.open(); + } + + public render() { + return ( +
+ ); + } +}