Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f6212a3

Browse files
chrispricecprice-scottlogicColinEberhardt
authored
feat: simplify webgl line series to use SDF (#1718)
Co-authored-by: Chris Price <[email protected]> Co-authored-by: Colin Eberhardt <[email protected]>
1 parent f25c748 commit f6212a3

File tree

6 files changed

+110
-193
lines changed

6 files changed

+110
-193
lines changed

packages/d3fc-series/src/webgl/line.js

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,19 @@ import { rebindAll, exclude, rebind } from '@d3fc/d3fc-rebind';
1010
export default () => {
1111
const base = xyBase();
1212

13-
const crossValueAttribute = webglAdjacentAttribute(-1, 2);
14-
const crossPreviousValueAttribute = crossValueAttribute.offset(-1);
13+
const crossValueAttribute = webglAdjacentAttribute(0, 1);
1514
const crossNextValueAttribute = crossValueAttribute.offset(1);
16-
const crossNextNextValueAttribute = crossValueAttribute.offset(2);
17-
const mainValueAttribute = webglAdjacentAttribute(-1, 2);
18-
const mainPreviousValueAttribute = mainValueAttribute.offset(-1);
15+
const mainValueAttribute = webglAdjacentAttribute(0, 1);
1916
const mainNextValueAttribute = mainValueAttribute.offset(1);
20-
const mainNextNextValueAttribute = mainValueAttribute.offset(2);
21-
const definedAttribute = webglAdjacentAttribute(0, 1).type(webglTypes.UNSIGNED_BYTE);
17+
const definedAttribute = webglAdjacentAttribute(0, 1)
18+
.type(webglTypes.UNSIGNED_BYTE);
2219
const definedNextAttribute = definedAttribute.offset(1);
2320

2421
const draw = webglSeriesLine()
25-
.crossPreviousValueAttribute(crossPreviousValueAttribute)
2622
.crossValueAttribute(crossValueAttribute)
2723
.crossNextValueAttribute(crossNextValueAttribute)
28-
.crossNextNextValueAttribute(crossNextNextValueAttribute)
29-
.mainPreviousValueAttribute(mainPreviousValueAttribute)
3024
.mainValueAttribute(mainValueAttribute)
3125
.mainNextValueAttribute(mainNextValueAttribute)
32-
.mainNextNextValueAttribute(mainNextNextValueAttribute)
3326
.definedAttribute(definedAttribute)
3427
.definedNextAttribute(definedNextAttribute);
3528

packages/d3fc-webgl/src/series/line.js

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import baseScale from '../scale/base';
22
import programBuilder from '../program/programBuilder';
3-
import lineShader from '../shaders/line/baseShader';
3+
import shaderBuilder, {
4+
vertexShaderBase,
5+
fragmentShaderBase
6+
} from '../shaders/shaderBuilder';
47
import drawModes from '../program/drawModes';
58
import { rebind } from '@d3fc/d3fc-rebind';
69
import lineWidthShader from '../shaders/lineWidth';
7-
import * as vertexShaderSnippets from '../shaders/vertexShaderSnippets';
810
import attribute from '../buffer/attribute';
911
import elementIndices from '../buffer/elementIndices';
1012
import types from '../buffer/types';
@@ -13,47 +15,112 @@ import rebindCurry from '../rebindCurry';
1315
export default () => {
1416
const program = programBuilder()
1517
.mode(drawModes.TRIANGLES)
16-
.subInstanceCount(12);
18+
.subInstanceCount(6);
1719
let xScale = baseScale();
1820
let yScale = baseScale();
1921
let decorate = () => {};
2022
const lineWidth = lineWidthShader();
2123

24+
/*
25+
Line segment from a to b has vertices A, B, C, D -
26+
27+
A |-------| B
28+
| \ |
29+
|a \ b|
30+
| \ |
31+
D |-------| C
32+
33+
|AD| = uStrokeWidth
34+
|AB| = |ab| + uStrokeWidth
35+
36+
Fragment shader implemented using line segment SDF
37+
simplified for starting at the origin (a) -
38+
https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
39+
*/
2240
const cornerAttribute = attribute()
2341
.divisor(0)
24-
.size(3)
42+
.size(4)
2543
.type(types.BYTE)
2644
.data([
27-
[-1, 0, 0],
28-
[1, 1, 0],
29-
[1, -1, 1],
30-
[-1, 0, 1],
31-
[1, 1, 1]
45+
[-1, +1, 1, 0],
46+
[+1, +1, 0, 1],
47+
[+1, -1, 0, 1],
48+
[-1, -1, 1, 0]
3249
]);
3350

3451
program
3552
.buffers()
36-
.elementIndices(elementIndices([0, 1, 2, 1, 2, 3, 0, 2, 3, 2, 3, 4]))
53+
.elementIndices(elementIndices([0, 1, 2, 2, 3, 0]))
3754
.attribute('aCorner', cornerAttribute);
3855

3956
const draw = numElements => {
40-
const shaderBuilder = lineShader();
41-
program
42-
.vertexShader(shaderBuilder.vertex())
43-
.fragmentShader(shaderBuilder.fragment());
44-
45-
xScale(program, 'prev', 0);
46-
yScale(program, 'prev', 1);
47-
xScale(program, 'curr', 0);
48-
yScale(program, 'curr', 1);
49-
xScale(program, 'gl_Position', 0);
50-
yScale(program, 'gl_Position', 1);
51-
xScale(program, 'nextNext', 0);
52-
yScale(program, 'nextNext', 1);
53-
54-
program
55-
.vertexShader()
56-
.appendBody(vertexShaderSnippets.postScaleLine.body);
57+
const vertexShader = shaderBuilder(vertexShaderBase);
58+
const fragmentShader = shaderBuilder(fragmentShaderBase);
59+
60+
program.vertexShader(vertexShader).fragmentShader(fragmentShader);
61+
62+
vertexShader.appendHeader(`
63+
attribute vec4 aCorner;
64+
attribute float aCrossValue;
65+
attribute float aCrossNextValue;
66+
attribute float aMainValue;
67+
attribute float aMainNextValue;
68+
attribute float aDefined;
69+
attribute float aDefinedNext;
70+
71+
uniform float uStrokeWidth;
72+
uniform vec2 uScreen;
73+
74+
varying float vLength;
75+
varying vec2 vPosition;
76+
`);
77+
78+
vertexShader.appendBody(`
79+
vec4 value = vec4(aCrossValue, aMainValue, 0.0, 1.0);
80+
vec4 nextValue = vec4(aCrossNextValue, aMainNextValue, 0.0, 1.0);
81+
`);
82+
83+
xScale(program, 'value', 0);
84+
xScale(program, 'nextValue', 0);
85+
yScale(program, 'value', 1);
86+
yScale(program, 'nextValue', 1);
87+
88+
vertexShader.appendBody(`
89+
vec2 position = aCorner[2] * value.xy + aCorner[3] * nextValue.xy;
90+
91+
vec2 direction = normalize((nextValue.xy - value.xy) * uScreen);
92+
vec2 normal = vec2(direction.y, -direction.x);
93+
vec2 padding = ((uStrokeWidth / 2.0) / (uScreen / 2.0));
94+
95+
padding *= aDefined * aDefinedNext;
96+
position += (aCorner[0] * direction + aCorner[1] * normal) * padding;
97+
98+
gl_Position = vec4(position.x, position.y, 0.0, 1.0);
99+
100+
vLength = length((nextValue.xy - value.xy) * (uScreen / 2.0));
101+
vPosition = aCorner.xy * (uStrokeWidth / 2.0);
102+
vPosition.x += aCorner[3] * vLength;
103+
`);
104+
105+
// all fragment shader inputs are pixel denominated
106+
107+
fragmentShader.appendHeader(`
108+
uniform float uStrokeWidth;
109+
varying float vLength;
110+
varying vec2 vPosition;
111+
112+
float canFill = 0.0;
113+
float canStroke = 1.0;
114+
`);
115+
116+
fragmentShader.appendBody(`
117+
vec2 position = vPosition;
118+
position.x -= clamp(position.x, 0.0, vLength);
119+
float sdf = length(position) - uStrokeWidth / 2.0;
120+
if (sdf > 0.5) {
121+
discard;
122+
}
123+
`);
57124

58125
lineWidth(program);
59126

@@ -88,13 +155,6 @@ export default () => {
88155

89156
rebind(draw, program, 'context', 'pixelRatio');
90157
rebind(draw, lineWidth, 'lineWidth');
91-
rebindCurry(
92-
draw,
93-
'crossPreviousValueAttribute',
94-
program.buffers(),
95-
'attribute',
96-
'aCrossPrevValue'
97-
);
98158
rebindCurry(
99159
draw,
100160
'crossValueAttribute',
@@ -109,20 +169,6 @@ export default () => {
109169
'attribute',
110170
'aCrossNextValue'
111171
);
112-
rebindCurry(
113-
draw,
114-
'crossNextNextValueAttribute',
115-
program.buffers(),
116-
'attribute',
117-
'aCrossNextNextValue'
118-
);
119-
rebindCurry(
120-
draw,
121-
'mainPreviousValueAttribute',
122-
program.buffers(),
123-
'attribute',
124-
'aMainPrevValue'
125-
);
126172
rebindCurry(
127173
draw,
128174
'mainValueAttribute',
@@ -137,13 +183,6 @@ export default () => {
137183
'attribute',
138184
'aMainNextValue'
139185
);
140-
rebindCurry(
141-
draw,
142-
'mainNextNextValueAttribute',
143-
program.buffers(),
144-
'attribute',
145-
'aMainNextNextValue'
146-
);
147186
rebindCurry(
148187
draw,
149188
'definedAttribute',

packages/d3fc-webgl/src/shaders/fragmentShaderSnippets.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -279,13 +279,3 @@ export const strokeColor = {
279279
header: `varying vec4 vStrokeColor;`,
280280
body: `gl_FragColor = (canStroke * vStrokeColor) + ((1.0 - canStroke) * gl_FragColor);`
281281
};
282-
283-
export const line = {
284-
header: `varying float vDefined;`,
285-
body: `
286-
float canFill = 0.0;
287-
float canStroke = 1.0;
288-
if (vDefined < 0.5) {
289-
discard;
290-
}`
291-
};

packages/d3fc-webgl/src/shaders/line/baseShader.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

packages/d3fc-webgl/src/shaders/vertexShaderSnippets.js

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -259,78 +259,6 @@ export const bar = {
259259
gl_Position = vec4(aCrossValue, yValue, 0, 1);`
260260
};
261261

262-
export const preScaleLine = {
263-
header: `
264-
attribute vec3 aCorner;
265-
attribute float aCrossNextNextValue;
266-
attribute float aMainNextNextValue;
267-
attribute float aCrossNextValue;
268-
attribute float aMainNextValue;
269-
attribute float aCrossValue;
270-
attribute float aMainValue;
271-
attribute float aCrossPrevValue;
272-
attribute float aMainPrevValue;
273-
attribute float aDefined;
274-
attribute float aDefinedNext;
275-
276-
uniform float uStrokeWidth;
277-
uniform vec2 uScreen;
278-
279-
varying float vDefined;`,
280-
body: `
281-
vDefined = aDefined * aDefinedNext;
282-
vec4 prev = vec4(aCrossPrevValue, aMainPrevValue, 0, 0);
283-
vec4 curr = vec4(aCrossValue, aMainValue, 0, 0);
284-
gl_Position = vec4(aCrossNextValue, aMainNextValue, 0, 1);
285-
vec4 nextNext = vec4(aCrossNextNextValue, aMainNextNextValue, 0, 0);`
286-
};
287-
288-
export const postScaleLine = {
289-
body: `
290-
vec4 currVertexPosition = gl_Position;
291-
vec4 nextVertexPosition = gl_Position;
292-
293-
if (all(equal(curr.xy, prev.xy))) {
294-
prev.xy = curr.xy + normalize(curr.xy - currVertexPosition.xy);
295-
}
296-
if (all(equal(curr.xy, currVertexPosition.xy))) {
297-
currVertexPosition.xy = curr.xy + normalize(curr.xy - prev.xy);
298-
}
299-
vec2 A = normalize(normalize(curr.xy - prev.xy) * uScreen);
300-
vec2 B = normalize(normalize(currVertexPosition.xy - curr.xy) * uScreen);
301-
vec2 tangent = normalize(A + B);
302-
vec2 miter = vec2(-tangent.y, tangent.x);
303-
vec2 normalA = vec2(-A.y, A.x);
304-
float miterLength = 1.0 / dot(miter, normalA);
305-
vec2 point = normalize(A - B);
306-
if (miterLength > 10.0 && sign(aCorner.x * dot(miter, point)) > 0.0) {
307-
currVertexPosition.xy = curr.xy - (aCorner.x * aCorner.y * uStrokeWidth * normalA) / uScreen.xy;
308-
} else {
309-
currVertexPosition.xy = curr.xy + (aCorner.x * miter * uStrokeWidth * miterLength) / uScreen.xy;
310-
}
311-
312-
if (all(equal(nextVertexPosition.xy, curr.xy))) {
313-
curr.xy = nextVertexPosition.xy + normalize(nextVertexPosition.xy - nextNext.xy);
314-
}
315-
if (all(equal(nextVertexPosition.xy, nextNext.xy))) {
316-
nextNext.xy = nextVertexPosition.xy + normalize(nextVertexPosition.xy - curr.xy);
317-
}
318-
vec2 C = normalize(normalize(nextVertexPosition.xy - curr.xy) * uScreen);
319-
vec2 D = normalize(normalize(nextNext.xy - nextVertexPosition.xy) * uScreen);
320-
vec2 tangentCD = normalize(C + D);
321-
vec2 miterCD = vec2(-tangentCD.y, tangentCD.x);
322-
vec2 normalC = vec2(-C.y, C.x);
323-
float miterCDLength = 1.0 / dot(miterCD, normalC);
324-
vec2 pointCD = normalize(C - D);
325-
if (miterCDLength > 10.0 && sign(aCorner.x * dot(miterCD, pointCD)) > 0.0) {
326-
nextVertexPosition.xy = nextVertexPosition.xy - (aCorner.x * aCorner.y * uStrokeWidth * normalC) / uScreen.xy;
327-
} else {
328-
nextVertexPosition.xy = nextVertexPosition.xy + (aCorner.x * miterCD * uStrokeWidth * miterCDLength) / uScreen.xy;
329-
}
330-
331-
gl_Position.xy = ((1.0 - aCorner.z) * currVertexPosition.xy) + (aCorner.z * nextVertexPosition.xy);`
332-
};
333-
334262
export const errorBar = {
335263
header: `
336264
attribute vec3 aCorner;

0 commit comments

Comments
 (0)