From 6ec70396b9f0358766af61663b76aaf987f4e3c7 Mon Sep 17 00:00:00 2001 From: xiaoiver Date: Tue, 18 Jun 2019 18:23:51 +0800 Subject: [PATCH] feat(point-layer): use 2d sdf functions --- demos/01_point_circle.html | 9 ++- src/geom/buffer/point/circleBuffer.js | 23 ++++++-- src/geom/shader/circle_frag.glsl | 43 ++++++++++++-- src/geom/shader/circle_vert.glsl | 15 +++-- src/geom/shader/index.js | 2 + src/geom/shader/shaderChunks/decode.glsl | 13 +++++ src/geom/shader/shaderChunks/sdf_2d.glsl | 74 ++++++++++++++++++++++++ src/global.js | 2 +- src/layer/pointLayer.js | 8 +-- src/layer/render/point/drawCircle.js | 2 +- 10 files changed, 167 insertions(+), 24 deletions(-) create mode 100644 src/geom/shader/shaderChunks/sdf_2d.glsl diff --git a/demos/01_point_circle.html b/demos/01_point_circle.html index 88efe97d33..a013fe9dc0 100644 --- a/demos/01_point_circle.html +++ b/demos/01_point_circle.html @@ -59,7 +59,14 @@ scene.on('loaded', () => { .source(data,{ isCluster:true }) - .shape('circle') + // .shape('circle') + .shape('point_count', [ 'circle', 'triangle', 'hexagon' ]) + // .shape('triangle') + // .shape('square') + // .shape('hexagon') + // .shape('octogon') + // .shape('hexagram') + // .shape('pentagon') .size('point_count', [ 5, 40]) // default 1 //.size('value', [ 10, 300]) // default 1 .active(true) diff --git a/src/geom/buffer/point/circleBuffer.js b/src/geom/buffer/point/circleBuffer.js index 165a655e89..1a477b2dee 100644 --- a/src/geom/buffer/point/circleBuffer.js +++ b/src/geom/buffer/point/circleBuffer.js @@ -1,13 +1,24 @@ import { packUint8ToFloat } from '../../../util/vertex-compress'; +import Global from '../../../global'; +const { pointShape } = Global; -const LEFT_SHIFT18 = 262144.0; -const LEFT_SHIFT20 = 1048576.0; +const LEFT_SHIFT17 = 131072.0; +// const LEFT_SHIFT18 = 262144.0; +// const LEFT_SHIFT19 = 524288.0; +// const LEFT_SHIFT20 = 1048576.0; +const LEFT_SHIFT21 = 2097152.0; +// const LEFT_SHIFT22 = 4194304.0; +const LEFT_SHIFT23 = 8388608.0; +// const LEFT_SHIFT24 = 16777216.0; export default function circleBuffer(layerData) { const index = []; const aPosition = []; const aPackedData = []; - layerData.forEach(({ size = 0, color, id, coordinates }, i) => { + + layerData.forEach(({ size = 0, color, id, coordinates, shape }, i) => { + + const shapeIndex = pointShape['2d'].indexOf(shape) || 0; if (isNaN(size)) { size = 0; @@ -26,10 +37,12 @@ export default function circleBuffer(layerData) { [ 1, 1 ], [ -1, 1 ] ].forEach(extrude => { - // vec4(color, color, (4-bit extrude, 16-bit size), id) + // vec4(color, color, (4-bit extrude, 4-bit shape, 16-bit size), id) aPackedData.push( ...packedColor, - (extrude[0] + 1) * LEFT_SHIFT20 + (extrude[1] + 1) * LEFT_SHIFT18 + size, + (extrude[0] + 1) * LEFT_SHIFT23 + (extrude[1] + 1) * LEFT_SHIFT21 + + shapeIndex * LEFT_SHIFT17 + + size, id ); }); diff --git a/src/geom/shader/circle_frag.glsl b/src/geom/shader/circle_frag.glsl index db81ca186f..82b8a77362 100644 --- a/src/geom/shader/circle_frag.glsl +++ b/src/geom/shader/circle_frag.glsl @@ -4,22 +4,57 @@ uniform float u_stroke_width : 1; uniform vec4 u_stroke_color : [1, 1, 1, 1]; uniform float u_stroke_opacity : 1; -varying vec3 v_data; +varying vec4 v_data; varying vec4 v_color; varying float v_radius; +#pragma include "sdf_2d" + void main() { - float extrude_length = length(v_data.xy); + int shape = int(floor(v_data.w + 0.5)); lowp float antialiasblur = v_data.z; float antialiased_blur = -max(u_blur, antialiasblur); + float r = v_radius / (v_radius + u_stroke_width); - float opacity_t = smoothstep(0.0, antialiased_blur, extrude_length - 1.0); + float outer_df; + float inner_df; + // 'circle', 'triangle', 'square', 'pentagon', 'hexagon', 'octogon', 'hexagram', 'rhombus', 'vesica' + if (shape == 0) { + outer_df = sdCircle(v_data.xy, 1.0); + inner_df = sdCircle(v_data.xy, r); + } else if (shape == 1) { + outer_df = sdEquilateralTriangle(1.1 * v_data.xy); + inner_df = sdEquilateralTriangle(1.1 / r * v_data.xy); + } else if (shape == 2) { + outer_df = sdBox(v_data.xy, vec2(1.)); + inner_df = sdBox(v_data.xy, vec2(r)); + } else if (shape == 3) { + outer_df = sdPentagon(v_data.xy, 0.8); + inner_df = sdPentagon(v_data.xy, r * 0.8); + } else if (shape == 4) { + outer_df = sdHexagon(v_data.xy, 0.8); + inner_df = sdHexagon(v_data.xy, r * 0.8); + } else if (shape == 5) { + outer_df = sdOctogon(v_data.xy, 1.0); + inner_df = sdOctogon(v_data.xy, r); + } else if (shape == 6) { + outer_df = sdHexagram(v_data.xy, 0.52); + inner_df = sdHexagram(v_data.xy, r * 0.52); + } else if (shape == 7) { + outer_df = sdRhombus(v_data.xy, vec2(1.0)); + inner_df = sdRhombus(v_data.xy, vec2(r)); + } else if (shape == 8) { + outer_df = sdVesica(v_data.xy, 1.1, 0.8); + inner_df = sdVesica(v_data.xy, r * 1.1, r * 0.8); + } + + float opacity_t = smoothstep(0.0, antialiased_blur, outer_df); float color_t = u_stroke_width < 0.01 ? 0.0 : smoothstep( antialiased_blur, 0.0, - extrude_length - v_radius / (v_radius + u_stroke_width) + inner_df ); gl_FragColor = opacity_t * mix(v_color * u_opacity, u_stroke_color * u_stroke_opacity, color_t); diff --git a/src/geom/shader/circle_vert.glsl b/src/geom/shader/circle_vert.glsl index e6320e7f7e..f9382ad0aa 100644 --- a/src/geom/shader/circle_vert.glsl +++ b/src/geom/shader/circle_vert.glsl @@ -5,7 +5,7 @@ uniform float u_stroke_width : 2; uniform float u_activeId : 0; uniform vec4 u_activeColor : [ 1.0, 0, 0, 1.0 ]; -varying vec3 v_data; +varying vec4 v_data; varying vec4 v_color; varying float v_radius; @@ -22,14 +22,17 @@ void main() { // extrude(4-bit) vec2 extrude; - extrude.x = floor(compressed * SHIFT_RIGHT20); - compressed -= extrude.x * SHIFT_LEFT20; + extrude.x = floor(compressed * SHIFT_RIGHT23); + compressed -= extrude.x * SHIFT_LEFT23; extrude.x = extrude.x - 1.; - extrude.y = floor(compressed * SHIFT_RIGHT18); - compressed -= extrude.y * SHIFT_LEFT18; + extrude.y = floor(compressed * SHIFT_RIGHT21); + compressed -= extrude.y * SHIFT_LEFT21; extrude.y = extrude.y - 1.; + float shape_type = floor(compressed * SHIFT_RIGHT17); + compressed -= shape_type * SHIFT_LEFT17; + // radius(16-bit) float radius = compressed; v_radius = radius; @@ -42,7 +45,7 @@ void main() { float antialiasblur = 1.0 / (radius + u_stroke_width); // construct point coords - v_data = vec3(extrude, antialiasblur); + v_data = vec4(extrude, antialiasblur, shape_type); // picking if(picking_id == u_activeId) { diff --git a/src/geom/shader/index.js b/src/geom/shader/index.js index a23f341538..2b17a38656 100644 --- a/src/geom/shader/index.js +++ b/src/geom/shader/index.js @@ -52,11 +52,13 @@ import common from './common.glsl'; import { registerModule } from '../../util/shaderModule'; import pick_color from './shaderChunks/pick_color.glsl'; import decode from './shaderChunks/decode.glsl'; +import sdf_2d from './shaderChunks/sdf_2d.glsl'; export function compileBuiltinModules() { registerModule('point', { vs: point_vert, fs: point_frag }); registerModule('common', { vs: common, fs: common }); registerModule('decode', { vs: decode, fs: '' }); + registerModule('sdf_2d', { vs: '', fs: sdf_2d }); registerModule('pick_color', { vs: pick_color, fs: pick_color }); registerModule('circle', { vs: circle_vert, fs: circle_frag }); registerModule('polygon', { vs: polygon_vert, fs: polygon_frag }); diff --git a/src/geom/shader/shaderChunks/decode.glsl b/src/geom/shader/shaderChunks/decode.glsl index afa198e6e4..07818873f9 100644 --- a/src/geom/shader/shaderChunks/decode.glsl +++ b/src/geom/shader/shaderChunks/decode.glsl @@ -1,7 +1,20 @@ +#define SHIFT_RIGHT17 1.0 / 131072.0 #define SHIFT_RIGHT18 1.0 / 262144.0 +#define SHIFT_RIGHT19 1.0 / 524288.0 #define SHIFT_RIGHT20 1.0 / 1048576.0 +#define SHIFT_RIGHT21 1.0 / 2097152.0 +#define SHIFT_RIGHT22 1.0 / 4194304.0 +#define SHIFT_RIGHT23 1.0 / 8388608.0 +#define SHIFT_RIGHT24 1.0 / 16777216.0 + +#define SHIFT_LEFT17 131072.0 #define SHIFT_LEFT18 262144.0 +#define SHIFT_LEFT19 524288.0 #define SHIFT_LEFT20 1048576.0 +#define SHIFT_LEFT21 2097152.0 +#define SHIFT_LEFT22 4194304.0 +#define SHIFT_LEFT23 8388608.0 +#define SHIFT_LEFT24 16777216.0 vec2 unpack_float(const float packedValue) { int packedIntValue = int(packedValue); diff --git a/src/geom/shader/shaderChunks/sdf_2d.glsl b/src/geom/shader/shaderChunks/sdf_2d.glsl new file mode 100644 index 0000000000..4498415879 --- /dev/null +++ b/src/geom/shader/shaderChunks/sdf_2d.glsl @@ -0,0 +1,74 @@ +/** + * 2D signed distance field functions + * @see http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + */ + +float ndot(vec2 a, vec2 b ) { return a.x*b.x - a.y*b.y; } + +float sdCircle(vec2 p, float r) { + return length(p) - r; +} + +float sdEquilateralTriangle(vec2 p) { + const float k = sqrt(3.0); + p.x = abs(p.x) - 1.0; + p.y = p.y + 1.0/k; + if( p.x + k*p.y > 0.0 ) p = vec2(p.x-k*p.y,-k*p.x-p.y)/2.0; + p.x -= clamp( p.x, -2.0, 0.0 ); + return -length(p)*sign(p.y); +} + +float sdBox(vec2 p, vec2 b) { + vec2 d = abs(p)-b; + return length(max(d,vec2(0))) + min(max(d.x,d.y),0.0); +} + +float sdPentagon(vec2 p, float r) { + const vec3 k = vec3(0.809016994,0.587785252,0.726542528); + p.x = abs(p.x); + p -= 2.0*min(dot(vec2(-k.x,k.y),p),0.0)*vec2(-k.x,k.y); + p -= 2.0*min(dot(vec2( k.x,k.y),p),0.0)*vec2( k.x,k.y); + p -= vec2(clamp(p.x,-r*k.z,r*k.z),r); + return length(p)*sign(p.y); +} + +float sdHexagon(vec2 p, float r) { + const vec3 k = vec3(-0.866025404,0.5,0.577350269); + p = abs(p); + p -= 2.0*min(dot(k.xy,p),0.0)*k.xy; + p -= vec2(clamp(p.x, -k.z*r, k.z*r), r); + return length(p)*sign(p.y); +} + +float sdOctogon(vec2 p, float r) { + const vec3 k = vec3(-0.9238795325, 0.3826834323, 0.4142135623 ); + p = abs(p); + p -= 2.0*min(dot(vec2( k.x,k.y),p),0.0)*vec2( k.x,k.y); + p -= 2.0*min(dot(vec2(-k.x,k.y),p),0.0)*vec2(-k.x,k.y); + p -= vec2(clamp(p.x, -k.z*r, k.z*r), r); + return length(p)*sign(p.y); +} + +float sdHexagram(vec2 p, float r) { + const vec4 k=vec4(-0.5,0.8660254038,0.5773502692,1.7320508076); + p = abs(p); + p -= 2.0*min(dot(k.xy,p),0.0)*k.xy; + p -= 2.0*min(dot(k.yx,p),0.0)*k.yx; + p -= vec2(clamp(p.x,r*k.z,r*k.w),r); + return length(p)*sign(p.y); +} + +float sdRhombus(vec2 p, vec2 b) { + vec2 q = abs(p); + float h = clamp((-2.0*ndot(q,b)+ndot(b,b))/dot(b,b),-1.0,1.0); + float d = length( q - 0.5*b*vec2(1.0-h,1.0+h) ); + return d * sign( q.x*b.y + q.y*b.x - b.x*b.y ); +} + +float sdVesica(vec2 p, float r, float d) { + p = abs(p); + float b = sqrt(r*r-d*d); // can delay this sqrt + return ((p.y-b)*d>p.x*b) + ? length(p-vec2(0.0,b)) + : length(p-vec2(-d,0.0))-r; +} \ No newline at end of file diff --git a/src/global.js b/src/global.js index b6f5e5341e..c0a2c994f2 100644 --- a/src/global.js +++ b/src/global.js @@ -23,7 +23,7 @@ const Global = { shape: 'circle', snapArray: [ 0, 1, 2, 4, 5, 10 ], pointShape: { - '2d': [ 'circle', 'square', 'hexagon', 'triangle' ], + '2d': [ 'circle', 'triangle', 'square', 'pentagon', 'hexagon', 'octogon', 'hexagram', 'rhombus', 'vesica' ], '3d': [ 'cylinder', 'triangleColumn', 'hexagonColumn', 'squareColumn' ] }, sdfHomeUrl: 'https://sdf.amap.com', diff --git a/src/layer/pointLayer.js b/src/layer/pointLayer.js index 8439ccaf79..2fc8038ca8 100644 --- a/src/layer/pointLayer.js +++ b/src/layer/pointLayer.js @@ -30,13 +30,9 @@ export default class PointLayer extends Layer { } // 2D circle 特殊处理 - if (shape === 'circle') { + if (pointShape['2d'].indexOf(shape) !== -1) { return 'circle'; - } - if ( - pointShape['2d'].indexOf(shape) !== -1 || - pointShape['3d'].indexOf(shape) !== -1 - ) { + } else if (pointShape['3d'].indexOf(shape) !== -1) { return 'fill'; } else if (this.scene.image.imagesIds.indexOf(shape) !== -1) { return 'image'; diff --git a/src/layer/render/point/drawCircle.js b/src/layer/render/point/drawCircle.js index 6f8df6f48b..9f938d2802 100644 --- a/src/layer/render/point/drawCircle.js +++ b/src/layer/render/point/drawCircle.js @@ -1,5 +1,5 @@ /** - * 针对绘制圆形的优化 + * 绘制 SDF,不仅是圆形 * 手动构建点阵坐标系,便于实现描边、反走样效果 */ import * as THREE from '../../../core/three';