From d0476b748461586f8e781eba8ae5ecaf5c7e9b82 Mon Sep 17 00:00:00 2001 From: YiQianYao <42212176+2912401452@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:08:01 +0800 Subject: [PATCH] Shihui (#987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加 bloomPass1.0、修改渲染流程,让 multiPass 有正确的渲染顺序 * style: lint style * feat: 取消 bloom 在 postprocessor 中的多次渲染(没有明显优化) * feat: polygon extrude 模式支持配置固定高度 * style: lint style * feat: 优化后处理 bloom 的效果 * feat: 修改交替绘制 bloom 的写法 * style: lint style * feat: 完善 iconService 加载渲染和销毁 * style: lint style * feat: 补全 mapbox 模式下等面积点 * style: lint style * fix: 修复 pointLayer animate 模式 opacity 失效 * style: lint style * feat: 拆分 pointLayer 的 shader * style: lint sytle * feat: 拆分 lineLayer 的 linear 模式 * style: lint style * feat: 优化点击的拾取判断 * style: lint style * feat: 取消圆柱 shader 中的三元表达式、增强健壮性 * feat: 点图层圆柱体支持固定高度配置 heightfixed * feat: 点图层圆柱体支持拾取高亮颜色的光照计算 * style: lint style * style: lint style * feat: 拆分 lintLayer line 模式下的 dash line * style: lint style * feat: lineLayer simpleline 的 linear shader 代码拆分 * style: lint style * feat: 拆分 lineLayer arcLine linear shader 代码 * style: line style * feat: lineLayer arc line 在 shader 中移除 linear 部分计算 * feat: 拆分 lineLayer arc dash 虚线的 shader 代码 * style: lint style * feat: 拆分 lineLayer arc3d linear 部分的 shader 代码 * style: lint style * style: lint style * feat: 完善 isMiniAli 的判断,兼容 smallfish H5+ 的模式 * style: lint style * style: adjust mulpass demo * feat: 提供 getScale 方法 * style: lint style * feat: 修复支付宝小程序h5+开发模式下引入l7样式失效问题 * feat: 修改 l7hammerjs 的导入 * fix: 恢复原有的 picking shader 代码,解决移动端高亮存在冲突破面的情况 * feat: 重新设置 l7hammerjs 的导入方式 * fix: 修复 createTexture 的数据类型在 支付宝 环境中使用 Uint8ClampedArray 存在数据类型不兼容的现象 * style: lint style * feat: 兼容高德地图 2.x 在部分安卓手机上点击拾取失效的情况 * style: lint style * feat: 优化经典热力图显示效果 * style: lint style * feat: 修改 setBlend 方法、返回当前的 layer 对象 * feat: 完善在点图层未传入数据的情况下判断 shape 类型 * style: lint style * feat: 修复 MultiPassRender 在高德1.x 底图下,缩放地图时存在可视化层不同步的现象 * fix: 修复 getModelTypeWillEmptyData 的 bug * feat: 提供图层空数据的情况下提前指定 layer 类型的参数方法(暂时只在 pointlayer) * style: lint style * fix: 修复 l7-component Supercluster 依赖的缺失 * feat: 新增大小不受限制的点图层贴图类型 fillimage * style: lint style --- .../core/src/services/layer/ILayerService.ts | 1 + packages/layers/src/point/index.ts | 5 + packages/layers/src/point/models/fillmage.ts | 323 ++++++++++++++++++ packages/layers/src/point/models/index.ts | 3 + .../point/shaders/image/fillImage_frag.glsl | 24 ++ .../point/shaders/image/fillImage_vert.glsl | 110 ++++++ stories/Map/components/bugfix.tsx | 74 +--- 7 files changed, 474 insertions(+), 66 deletions(-) create mode 100644 packages/layers/src/point/models/fillmage.ts create mode 100644 packages/layers/src/point/shaders/image/fillImage_frag.glsl create mode 100644 packages/layers/src/point/shaders/image/fillImage_vert.glsl diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index 7288188355..8464856b3c 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -343,6 +343,7 @@ export interface ILayerConfig { enableMultiPassRenderer: boolean; passes: Array; + // layerType 指定 shape 的类型 layerType?: string | undefined; forward: boolean; // 正方向 diff --git a/packages/layers/src/point/index.ts b/packages/layers/src/point/index.ts index cc068961fc..d6c0e825a2 100644 --- a/packages/layers/src/point/index.ts +++ b/packages/layers/src/point/index.ts @@ -53,6 +53,7 @@ export default class PointLayer extends BaseLayer { protected getDefaultConfig() { const type = this.getModelType(); const defaultConfig = { + fillImage: {}, normal: { blend: 'additive', }, @@ -70,6 +71,7 @@ export default class PointLayer extends BaseLayer { protected getModelType(): PointType { const PointTypes = [ + 'fillImage', 'fill', 'image', 'normal', @@ -100,6 +102,9 @@ export default class PointLayer extends BaseLayer { if (shape === 'simple') { return 'simplePoint'; } + if (shape === 'fillImage') { + return 'fillImage'; + } if (shape2d?.indexOf(shape as string) !== -1) { return 'fill'; } diff --git a/packages/layers/src/point/models/fillmage.ts b/packages/layers/src/point/models/fillmage.ts new file mode 100644 index 0000000000..0fcdcf431a --- /dev/null +++ b/packages/layers/src/point/models/fillmage.ts @@ -0,0 +1,323 @@ +import { + AttributeType, + gl, + IAttribute, + IElements, + IEncodeFeature, + IModel, + IModelUniform, + ITexture2D, +} from '@antv/l7-core'; +import { getMask } from '@antv/l7-utils'; +import BaseModel from '../../core/BaseModel'; +import { IPointLayerStyleOptions } from '../../core/interface'; +import { PointFillTriangulation } from '../../core/triangulation'; +// static pointLayer shader - not support animate +import pointFillFrag from '../shaders/image/fillImage_frag.glsl'; +import pointFillVert from '../shaders/image/fillImage_vert.glsl'; + +import { isNumber } from 'lodash'; + +import { Version } from '@antv/l7-maps'; + +export default class FillImageModel extends BaseModel { + public meter2coord: number = 1; + private texture: ITexture2D; + private isMeter: boolean = false; + public getUninforms(): IModelUniform { + const { + opacity = 1, + strokeOpacity = 1, + strokeWidth = 0, + stroke = 'rgba(0,0,0,0)', + offsets = [0, 0], + blend, + } = this.layer.getLayerConfig() as IPointLayerStyleOptions; + + if (this.rendererService.getDirty()) { + this.texture.bind(); + } + + if ( + this.dataTextureTest && + this.dataTextureNeedUpdate({ + opacity, + strokeOpacity, + strokeWidth, + stroke, + offsets, + }) + ) { + // 判断当前的样式中哪些是需要进行数据映射的,哪些是常量,同时计算用于构建数据纹理的一些中间变量 + this.judgeStyleAttributes({ + opacity, + strokeOpacity, + strokeWidth, + stroke, + offsets, + }); + + const encodeData = this.layer.getEncodedData(); + const { data, width, height } = this.calDataFrame( + this.cellLength, + encodeData, + this.cellProperties, + ); + this.rowCount = height; // 当前数据纹理有多少行 + + this.dataTexture = + this.cellLength > 0 && data.length > 0 + ? this.createTexture2D({ + flipY: true, + data, + format: gl.LUMINANCE, + type: gl.FLOAT, + width, + height, + }) + : this.createTexture2D({ + flipY: true, + data: [1], + format: gl.LUMINANCE, + type: gl.FLOAT, + width: 1, + height: 1, + }); + } + return { + u_isMeter: Number(this.isMeter), + + u_additive: blend === 'additive' ? 1.0 : 0.0, + + u_dataTexture: this.dataTexture, // 数据纹理 - 有数据映射的时候纹理中带数据,若没有任何数据映射时纹理是 [1] + u_cellTypeLayout: this.getCellTypeLayout(), + + u_texture: this.texture, + u_textSize: [1024, this.iconService.canvasHeight || 128], + + u_opacity: isNumber(opacity) ? opacity : 1.0, + u_offsets: this.isOffsetStatic(offsets) + ? (offsets as [number, number]) + : [0, 0], + }; + } + + public getAttribute(): { + attributes: { + [attributeName: string]: IAttribute; + }; + elements: IElements; + } { + return this.styleAttributeService.createAttributesAndIndices( + this.layer.getEncodedData(), + PointFillTriangulation, + ); + } + + public initModels(): IModel[] { + this.updateTexture(); + this.iconService.on('imageUpdate', this.updateTexture); + + const { + unit = 'l7size', + } = this.layer.getLayerConfig() as IPointLayerStyleOptions; + const { version } = this.mapService; + if ( + unit === 'meter' && + version !== Version.L7MAP && + version !== Version.GLOBEL + ) { + this.isMeter = true; + this.calMeter2Coord(); + } + + return this.buildModels(); + } + + /** + * 计算等面积点图层(unit meter)笛卡尔坐标标度与世界坐标标度的比例 + * @returns + */ + public calMeter2Coord() { + // @ts-ignore + const [minLng, minLat, maxLng, maxLat] = this.layer.getSource().extent; + const center = [(minLng + maxLng) / 2, (minLat + maxLat) / 2]; + + const { version } = this.mapService; + if (version === Version.MAPBOX && window.mapboxgl.MercatorCoordinate) { + const coord = window.mapboxgl.MercatorCoordinate.fromLngLat( + { lng: center[0], lat: center[1] }, + 0, + ); + const offsetInMeters = 1; + const offsetInMercatorCoordinateUnits = + offsetInMeters * coord.meterInMercatorCoordinateUnits(); + const westCoord = new window.mapboxgl.MercatorCoordinate( + coord.x - offsetInMercatorCoordinateUnits, + coord.y, + coord.z, + ); + const westLnglat = westCoord.toLngLat(); + + this.meter2coord = center[0] - westLnglat.lng; + return; + } + + // @ts-ignore + const m1 = this.mapService.meterToCoord(center, [minLng, minLat]); + // @ts-ignore + const m2 = this.mapService.meterToCoord(center, [ + maxLng === minLng ? maxLng + 0.1 : maxLng, + maxLat === minLat ? minLat + 0.1 : maxLat, + ]); + this.meter2coord = (m1 + m2) / 2; + if (!Boolean(this.meter2coord)) { + // Tip: 兼容单个数据导致的 m1、m2 为 NaN + this.meter2coord = 7.70681090738883; + } + } + + public buildModels(): IModel[] { + const { + mask = false, + maskInside = true, + } = this.layer.getLayerConfig() as IPointLayerStyleOptions; + const { frag, vert, type } = this.getShaders(); + return [ + this.layer.buildLayerModel({ + moduleName: 'pointfill-' + type, + vertexShader: vert, + fragmentShader: frag, + triangulation: PointFillTriangulation, + depth: { enable: false }, + blend: this.getBlend(), + stencil: getMask(mask, maskInside), + }), + ]; + } + + public getShaders(): { frag: string; vert: string; type: string } { + return { + frag: pointFillFrag, + vert: pointFillVert, + type: 'normal', + }; + } + + public clearModels() { + this.dataTexture?.destroy(); + } + + // overwrite baseModel func + protected registerBuiltinAttributes() { + this.styleAttributeService.registerStyleAttribute({ + name: 'uv', + type: AttributeType.Attribute, + descriptor: { + name: 'a_Uv', + buffer: { + // give the WebGL driver a hint that this buffer may change + usage: gl.DYNAMIC_DRAW, + data: [], + type: gl.FLOAT, + }, + size: 2, + update: ( + feature: IEncodeFeature, + featureIdx: number, + vertex: number[], + attributeIdx: number, + ) => { + const iconMap = this.iconService.getIconMap(); + const { shape } = feature; + const { x, y } = iconMap[shape as string] || { x: 0, y: 0 }; + return [x, y]; + }, + }, + }); + + this.styleAttributeService.registerStyleAttribute({ + name: 'extrude', + type: AttributeType.Attribute, + descriptor: { + name: 'a_Extrude', + buffer: { + // give the WebGL driver a hint that this buffer may change + usage: gl.DYNAMIC_DRAW, + data: [], + type: gl.FLOAT, + }, + size: 3, + update: ( + feature: IEncodeFeature, + featureIdx: number, + vertex: number[], + attributeIdx: number, + ) => { + const extrude = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0]; + + const extrudeIndex = (attributeIdx % 4) * 3; + return [ + extrude[extrudeIndex], + extrude[extrudeIndex + 1], + extrude[extrudeIndex + 2], + ]; + }, + }, + }); + + // point layer size; + this.styleAttributeService.registerStyleAttribute({ + name: 'size', + type: AttributeType.Attribute, + descriptor: { + name: 'a_Size', + buffer: { + // give the WebGL driver a hint that this buffer may change + usage: gl.DYNAMIC_DRAW, + data: [], + type: gl.FLOAT, + }, + size: 1, + update: ( + feature: IEncodeFeature, + featureIdx: number, + vertex: number[], + attributeIdx: number, + ) => { + const { size = 5 } = feature; + // console.log('featureIdx', featureIdx, feature) + return Array.isArray(size) + ? [size[0] * this.meter2coord] + : [(size as number) * this.meter2coord]; + }, + }, + }); + } + + private updateTexture = () => { + const { createTexture2D } = this.rendererService; + if (this.texture) { + this.texture.update({ + data: this.iconService.getCanvas(), + mag: 'linear', + min: 'linear mipmap nearest', + mipmap: true, + }); + // this.layer.render(); + // TODO: 更新完纹理后在更新的图层的时候需要更新所有的图层 + this.layer.renderLayers(); + return; + } + this.texture = createTexture2D({ + data: this.iconService.getCanvas(), + mag: gl.LINEAR, + // min: gl.LINEAR, + min: gl.LINEAR_MIPMAP_LINEAR, + premultiplyAlpha: false, + width: 1024, + height: this.iconService.canvasHeight || 128, + mipmap: true, + }); + }; +} diff --git a/packages/layers/src/point/models/index.ts b/packages/layers/src/point/models/index.ts index 3f7112484d..1276ce8050 100644 --- a/packages/layers/src/point/models/index.ts +++ b/packages/layers/src/point/models/index.ts @@ -1,5 +1,6 @@ import ExtrudeModel from './extrude'; import FillModel from './fill'; +import FillImageModel from './fillmage'; import IconModel from './icon-font'; import IMageModel from './image'; import NormalModel from './normal'; @@ -7,6 +8,7 @@ import SimplePopint from './simplePoint'; import TextModel from './text'; export type PointType = + | 'fillImage' | 'fill' | 'image' | 'normal' @@ -16,6 +18,7 @@ export type PointType = | 'icon'; const PointModels: { [key in PointType]: any } = { + fillImage: FillImageModel, fill: FillModel, image: IMageModel, normal: NormalModel, diff --git a/packages/layers/src/point/shaders/image/fillImage_frag.glsl b/packages/layers/src/point/shaders/image/fillImage_frag.glsl new file mode 100644 index 0000000000..25795e37f6 --- /dev/null +++ b/packages/layers/src/point/shaders/image/fillImage_frag.glsl @@ -0,0 +1,24 @@ +uniform sampler2D u_texture; +uniform vec2 u_textSize; + +uniform float u_additive; + +varying mat4 styleMappingMat; // 传递从片元中传递的映射数据 + +varying float v_radius; + +#pragma include "sdf_2d" +#pragma include "picking" +varying vec2 v_uv; // 本身的 uv 坐标 +varying vec2 v_Iconuv; + +void main() { + + float opacity = styleMappingMat[0][0]; + + vec2 pos = v_Iconuv / u_textSize + v_uv / u_textSize * 64.; + gl_FragColor = texture2D(u_texture, pos); + gl_FragColor.a *= opacity; + + gl_FragColor = filterColor(gl_FragColor); +} diff --git a/packages/layers/src/point/shaders/image/fillImage_vert.glsl b/packages/layers/src/point/shaders/image/fillImage_vert.glsl new file mode 100644 index 0000000000..178b4e2057 --- /dev/null +++ b/packages/layers/src/point/shaders/image/fillImage_vert.glsl @@ -0,0 +1,110 @@ +attribute vec4 a_Color; +attribute vec3 a_Position; +attribute vec3 a_Extrude; +attribute float a_Size; +attribute vec2 a_Uv; + +varying mat4 styleMappingMat; // 用于将在顶点着色器中计算好的样式值传递给片元 + +uniform mat4 u_ModelMatrix; +uniform mat4 u_Mvp; +uniform float u_isMeter; + +varying float v_radius; +varying vec2 v_uv; // 本身的 uv 坐标 +varying vec2 v_Iconuv; // icon 贴图的 uv 坐标 + +uniform float u_opacity : 1; +uniform vec2 u_offsets; + +#pragma include "styleMapping" +#pragma include "styleMappingCalOpacity" + +#pragma include "projection" +#pragma include "picking" + + +void main() { + vec3 extrude = a_Extrude; + + v_uv = (a_Extrude.xy + 1.0)/2.0; + v_uv.y = 1.0 - v_uv.y; + v_Iconuv = a_Uv; + + // cal style mapping - 数据纹理映射部分的计算 + styleMappingMat = mat4( + 0.0, 0.0, 0.0, 0.0, // opacity - empty - empty - empty + 0.0, 0.0, 0.0, 0.0, // empty - empty - empty - empty + 0.0, 0.0, 0.0, 0.0, // offsets[0] - offsets[1] + 0.0, 0.0, 0.0, 0.0 + ); + + float rowCount = u_cellTypeLayout[0][0]; // 当前的数据纹理有几行 + float columnCount = u_cellTypeLayout[0][1]; // 当看到数据纹理有几列 + float columnWidth = 1.0/columnCount; // 列宽 + float rowHeight = 1.0/rowCount; // 行高 + float cellCount = calCellCount(); // opacity - strokeOpacity - strokeWidth - stroke - offsets + float id = a_vertexId; // 第n个顶点 + float cellCurrentRow = floor(id * cellCount / columnCount) + 1.0; // 起始点在第几行 + float cellCurrentColumn = mod(id * cellCount, columnCount) + 1.0; // 起始点在第几列 + + // cell 固定顺序 opacity -> strokeOpacity -> strokeWidth -> stroke ... + // 按顺序从 cell 中取值、若没有则自动往下取值 + float textureOffset = 0.0; // 在 cell 中取值的偏移量 + + vec2 opacityAndOffset = calOpacityAndOffset(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset, columnWidth, rowHeight); + styleMappingMat[0][0] = opacityAndOffset.r; + textureOffset = opacityAndOffset.g; + + vec2 textrueOffsets = vec2(0.0, 0.0); + if(hasOffsets()) { + vec2 valueXPos = nextPos(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset); + textrueOffsets.r = pos2value(valueXPos, columnWidth, rowHeight); // x + textureOffset += 1.0; + + vec2 valueYPos = nextPos(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset); + textrueOffsets.g = pos2value(valueYPos, columnWidth, rowHeight); // x + textureOffset += 1.0; + } else { + textrueOffsets = u_offsets; + } + + // cal style mapping + + // radius(16-bit) + v_radius = a_Size; + + // TODO: billboard + // anti-alias + + + vec2 offset = (extrude.xy * (a_Size) + textrueOffsets); + vec3 aPosition = a_Position; + if(u_isMeter < 1.0) { + // 不以米为实际单位 + offset = project_pixel(offset); + } else { + // 以米为实际单位 + + if(u_CoordinateSystem == COORDINATE_SYSTEM_LNGLAT || u_CoordinateSystem == COORDINATE_SYSTEM_LNGLAT_OFFSET) { + aPosition.xy += offset; + offset.x = 0.0; + offset.y = 0.0; + } + } + + + // vec4 project_pos = project_position(vec4(a_Position.xy, 0.0, 1.0)); + vec4 project_pos = project_position(vec4(aPosition.xy, 0.0, 1.0)); + // gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, project_pixel(setPickingOrder(0.0)), 1.0)); + + if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x + gl_Position = u_Mvp * vec4(project_pos.xy + offset, 0.0, 1.0); + } else { + gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, project_pixel(setPickingOrder(0.0)), 1.0)); + } + + // gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, 0.0, 1.0)); + + setPickingColor(a_PickingColor); +} diff --git a/stories/Map/components/bugfix.tsx b/stories/Map/components/bugfix.tsx index b6ce2d05e2..d34c0cf4d8 100644 --- a/stories/Map/components/bugfix.tsx +++ b/stories/Map/components/bugfix.tsx @@ -129,77 +129,19 @@ export default class Amap2demo extends React.Component { ); scene.on('loaded', () => { - // fetch( - // 'https://gw.alipayobjects.com/os/basement_prod/893d1d5f-11d9-45f3-8322-ee9140d288ae.json', - // ) - // .then((res) => res.json()) - // .then((data) => { - // const imageLayer = new PointLayer() - // .source(data, { - // parser: { - // type: 'json', - // x: 'longitude', - // y: 'latitude', - // }, - // }) - // .shape('name', ['00']) - // .size(10); - - // let d = { - // coordinates: (2)[(121.4318415, 31.25682515)], - // count: 2, - // id: '5011000005647', - // latitude: 31.25682515, - // longitude: 121.4318415, - // name: '石泉路140弄', - // unit_price: 52256.3, - // }; - // const imageLayer1 = new PointLayer() - // .source([], { - // parser: { - // type: 'json', - // x: 'longitude', - // y: 'latitude', - // }, - // }) - // .shape('name', ['00']) - // .size(25); - - // scene.addLayer(imageLayer); - // scene.addLayer(imageLayer1); - - // imageLayer.on('click', (e) => { - // // console.log(e); - // // imageLayer1.setBlend('normal') - // imageLayer1.setData([e.feature]); - // }); - // }); - - const imageLayer = new PointLayer() + const imageLayer = new PointLayer({ layerType: 'fillImage' }) .source(data) + // .shape('fillImage', s => s) .shape('s', (s) => s) - .size(10) + // .color('#f00') + .size(100) + .active({ + color: '#f00', + mix: 0.5, + }) .fitBounds(); - const imageLayer1 = new PointLayer({ layerType: 'image' }) - .source({ - features: [], - type: 'FeatureCollection', - }) - .shape('s', (s) => s) - .size(20); - scene.addLayer(imageLayer); - scene.addLayer(imageLayer1); - - imageLayer.on('click', (e) => { - console.log(e.feature); - - imageLayer1.setData({ - features: [e.feature], - type: 'FeatureCollection', - }); - }); }); }