feat(multi-pass): support TAA(Temporal Anti-Aliasing)
we can enable TAA in layer's options now, but there're something wrong when picking enabled at same time.
|
@ -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)
|
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 790 KiB |
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 3.1 MiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 206 KiB |
|
@ -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 实现 */
|
||||
|
|
|
@ -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<ISceneService>(TYPES.ISceneService)
|
||||
.to(SceneService)
|
||||
.inSingletonScope();
|
||||
container
|
||||
.bind<IGlobalConfigService>(TYPES.IGlobalConfigService)
|
||||
.to(GlobalConfigService)
|
||||
|
@ -113,5 +119,25 @@ export const lazyInject = (
|
|||
};
|
||||
};
|
||||
};
|
||||
export const lazyMultiInject = (
|
||||
serviceIdentifier: interfaces.ServiceIdentifier<any>,
|
||||
) => {
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,4 +31,6 @@ export interface ICameraService extends Omit<IViewport, 'syncWithMapCamera'> {
|
|||
init(): void;
|
||||
update(viewport: IViewport): void;
|
||||
setViewProjectionMatrix(viewProjectionMatrix: number[] | undefined): void;
|
||||
jitterProjectionMatrix(x: number, y: number): void;
|
||||
clearJitterProjectionMatrix(): void;
|
||||
}
|
||||
|
|
|
@ -107,6 +107,14 @@ export interface ILayerInitializationOptions {
|
|||
* 高亮颜色
|
||||
*/
|
||||
highlightColor: string | number[];
|
||||
/**
|
||||
* 开启 TAA
|
||||
*/
|
||||
enableTAA: boolean;
|
||||
/**
|
||||
* 相机抖动程度
|
||||
*/
|
||||
jitterScale: number;
|
||||
onHover(pickedFeature: IPickedFeature): void;
|
||||
onClick(pickedFeature: IPickedFeature): void;
|
||||
}
|
||||
|
|
|
@ -93,19 +93,6 @@ export default class BasePostProcessingPass<InitializationOptions = {}>
|
|||
});
|
||||
},
|
||||
);
|
||||
|
||||
// const useRenderTarget = (this.renderToScreen
|
||||
// ? postProcessor.useScreenRenderTarget
|
||||
// : postProcessor.useOffscreenRenderTarget
|
||||
// ).bind(postProcessor);
|
||||
|
||||
// useRenderTarget(async () => {
|
||||
// this.model.draw({
|
||||
// uniforms: {
|
||||
// u_Texture: postProcessor.getReadFBO(),
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
public isEnabled() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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++;
|
||||
|
||||
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<IModelInitializationOptions>,
|
||||
) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
IStyleAttributeService,
|
||||
IStyleAttributeUpdateOptions,
|
||||
lazyInject,
|
||||
lazyMultiInject,
|
||||
StyleAttributeField,
|
||||
StyleAttributeOption,
|
||||
Triangulation,
|
||||
|
@ -26,15 +27,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 {
|
||||
|
@ -59,6 +52,8 @@ const defaultLayerInitializationOptions: Partial<
|
|||
enablePicking: false,
|
||||
enableHighlight: false,
|
||||
highlightColor: 'red',
|
||||
enableTAA: false,
|
||||
jitterScale: 1,
|
||||
};
|
||||
|
||||
export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||
|
@ -84,46 +79,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> 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;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { container, ILayerPlugin, TYPES } from '@l7/core';
|
||||
import BaseLayer from './core/BaseLayer';
|
||||
// import HeatMapLayer from './heatmap';
|
||||
// import Line from './line';
|
||||
|
@ -5,6 +6,59 @@ import BaseLayer from './core/BaseLayer';
|
|||
// 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<ILayerPlugin>(TYPES.ILayerPlugin)
|
||||
.to(ConfigSchemaValidationPlugin);
|
||||
/**
|
||||
* 获取 Source
|
||||
*/
|
||||
container.bind<ILayerPlugin>(TYPES.ILayerPlugin).to(DataSourcePlugin);
|
||||
/**
|
||||
* 根据 StyleAttribute 创建 VertexAttribute
|
||||
*/
|
||||
container
|
||||
.bind<ILayerPlugin>(TYPES.ILayerPlugin)
|
||||
.to(RegisterStyleAttributePlugin);
|
||||
/**
|
||||
* 根据 Source 创建 Scale
|
||||
*/
|
||||
container.bind<ILayerPlugin>(TYPES.ILayerPlugin).to(FeatureScalePlugin);
|
||||
/**
|
||||
* 使用 Scale 进行数据映射
|
||||
*/
|
||||
container.bind<ILayerPlugin>(TYPES.ILayerPlugin).to(DataMappingPlugin);
|
||||
/**
|
||||
* 负责属性更新
|
||||
*/
|
||||
container.bind<ILayerPlugin>(TYPES.ILayerPlugin).to(UpdateStyleAttributePlugin);
|
||||
/**
|
||||
* Multi Pass 自定义渲染管线
|
||||
*/
|
||||
container.bind<ILayerPlugin>(TYPES.ILayerPlugin).to(MultiPassRendererPlugin);
|
||||
/**
|
||||
* 传入相机坐标系参数
|
||||
*/
|
||||
container.bind<ILayerPlugin>(TYPES.ILayerPlugin).to(ShaderUniformPlugin);
|
||||
/**
|
||||
* 负责拾取过程中 Encode 以及 Highlight 阶段及结束后恢复
|
||||
*/
|
||||
container.bind<ILayerPlugin>(TYPES.ILayerPlugin).to(PixelPickingPlugin);
|
||||
|
||||
export {
|
||||
BaseLayer,
|
||||
// PointLayer,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -12,6 +12,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]))?$/;
|
||||
|
@ -31,11 +32,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;
|
||||
|
||||
private scaleCache: {
|
||||
|
|
|
@ -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<string | [string, { [key: string]: unknown }]>,
|
||||
) {
|
||||
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());
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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<ISceneService>(TYPES.ISceneService);
|
||||
this.sceneService.init(config);
|
||||
this.mapService = container.get<IMapService>(TYPES.IMapService);
|
||||
this.iconService = container.get<IIconService>(TYPES.IIconService);
|
||||
|
|
|
@ -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';
|
||||
|
||||
storiesOf('MultiPassRenderer', module).add('blur', () => <Polygon />);
|
||||
storiesOf('MultiPassRenderer', module)
|
||||
.add('Blur', () => <Blur />)
|
||||
.add('TAA(Temporal Anti-Aliasing)', () => <TAA />);
|
||||
|
|
|
@ -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',
|
||||
[
|
|
@ -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 (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|