Skip to content

Commit

Permalink
fix: draw 1px sub-pixel line correctly in webgl #1425
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoiver committed Jul 20, 2023
1 parent c86f507 commit 055eb10
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ void main() {
outputColor = u_Color;
#endif

float blur = smoothstep(0.0, v_Distance.y, 1.0 - abs(v_Distance.x));
float blur;
if (v_Distance.y < 1.0) {
blur = smoothstep(0.0, v_Distance.y, 1.0 - abs(v_Distance.x));
} else {
blur = 1.0 / v_Distance.y;
}
float u_dash_offset = v_Dash.y;
float u_dash_array = v_Dash.z;
float u_dash_ratio = v_Dash.w;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ void main() {
} else {
strokeWidth = u_StrokeWidth;
}
float clampedStrokeWidth = max(strokeWidth, 1.0);

float isBillboard = a_Dash.w;
bool isPerspective = isPerspectiveMatrix(u_ProjectionMatrix);
Expand All @@ -43,21 +44,21 @@ void main() {
vec2 screen1 = u_Viewport * (0.5 * clip1.xy / clip1.w + 0.5);
vec2 xBasis = normalize(screen1 - screen0);
vec2 yBasis = vec2(-xBasis.y, xBasis.x);
vec2 pt0 = screen0 + strokeWidth * (a_Position.x * xBasis + a_Position.y * yBasis);
vec2 pt1 = screen1 + strokeWidth * (a_Position.x * xBasis + a_Position.y * yBasis);
vec2 pt0 = screen0 + clampedStrokeWidth * (a_Position.x * xBasis + a_Position.y * yBasis);
vec2 pt1 = screen1 + clampedStrokeWidth * (a_Position.x * xBasis + a_Position.y * yBasis);
vec2 pt = mix(pt0, pt1, a_Position.z);
vec4 clip = mix(clip0, clip1, a_Position.z);
gl_Position = vec4(clip.w * (2.0 * pt / u_Viewport - 1.0), clip.z, clip.w);
} else {
vec2 xBasis = a_PointB.xy - a_PointA.xy;
vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));

vec2 point = a_PointA.xy + xBasis * a_Position.x + yBasis * strokeWidth * a_Position.y;
vec2 point = a_PointA.xy + xBasis * a_Position.x + yBasis * clampedStrokeWidth * a_Position.y;
point = point - u_Anchor.xy * abs(xBasis);

// round & square
if (a_Cap > 1.0) {
point += sign(a_Position.x - 0.5) * normalize(xBasis) * vec2(strokeWidth / 2.0);
point += sign(a_Position.x - 0.5) * normalize(xBasis) * vec2(clampedStrokeWidth / 2.0);
}
gl_Position = project(vec4(point, u_ZIndex, 1.0), u_ProjectionMatrix, u_ViewMatrix, u_ModelMatrix);
}
Expand Down
7 changes: 7 additions & 0 deletions site/examples/perf/animation/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
"zh": "在 WebGL 中更新 Text 的位置",
"en": "Update position of Text in WebGL"
}
},
{
"filename": "webgl-opacity.js",
"title": {
"zh": "在 WebGL 中更新 Opacity",
"en": "Update opacity of shapes in WebGL"
}
}
]
}
58 changes: 58 additions & 0 deletions site/examples/perf/animation/demo/webgl-opacity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Canvas, CanvasEvent, Rect, runtime } from '@antv/g';
import { Renderer } from '@antv/g-webgl';
import Stats from 'stats.js';

runtime.enableCSSParsing = false;

const canvas = new Canvas({
container: 'container',
width: 600,
height: 500,
renderer: new Renderer(),
});

canvas.addEventListener(CanvasEvent.READY, () => {
const rect1 = new Rect({
style: {
x: 200,
y: 200,
width: 200,
height: 200,
fill: 'blue',
},
});

const rect2 = new Rect({
style: {
x: 250,
y: 250,
width: 100,
height: 100,
fill: 'red',
},
});

canvas.appendChild(rect1);
canvas.appendChild(rect2);

rect2.animate([{ opacity: 0 }, { opacity: 1 }], {
duration: 2000,
fill: 'both',
iterations: Infinity,
});
});

// stats
const stats = new Stats();
stats.showPanel(0);
const $stats = stats.dom;
$stats.style.position = 'absolute';
$stats.style.left = '0px';
$stats.style.top = '0px';
const $wrapper = document.getElementById('container');
$wrapper.appendChild($stats);
canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => {
if (stats) {
stats.update();
}
});
8 changes: 8 additions & 0 deletions site/examples/shape/line/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
"en": "Marker"
},
"screenshot": "https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*X5W_TYz-2SIAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "thin-line.js",
"title": {
"zh": "绘制宽度小于 1px 的线",
"en": "Thin Line"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*FNTzSp46rwgAAAAAAAAAAAAADmJ7AQ/original"
}
]
}
124 changes: 124 additions & 0 deletions site/examples/shape/line/demo/thin-line.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Canvas, CanvasEvent, Line, Path } from '@antv/g';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Renderer as CanvaskitRenderer } from '@antv/g-canvaskit';
import { Renderer as SVGRenderer } from '@antv/g-svg';
import { Renderer as WebGLRenderer } from '@antv/g-webgl';
import { Renderer as WebGPURenderer } from '@antv/g-webgpu';
import * as lil from 'lil-gui';
import Stats from 'stats.js';

// create a renderer
const canvasRenderer = new CanvasRenderer();
const webglRenderer = new WebGLRenderer();
const svgRenderer = new SVGRenderer();
const canvaskitRenderer = new CanvaskitRenderer({
wasmDir: '/',
fonts: [
{
name: 'Roboto',
url: '/Roboto-Regular.ttf',
},
{
name: 'sans-serif',
url: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/file/A*064aSK2LUPEAAAAAAAAAAAAADmJ7AQ/NotoSansCJKsc-VF.ttf',
},
],
});
const webgpuRenderer = new WebGPURenderer({
shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm',
});

// create a canvas
const canvas = new Canvas({
container: 'container',
width: 600,
height: 500,
renderer: canvasRenderer,
});

// create a line
const line1 = new Line({
style: {
x1: 200,
y1: 100,
x2: 400,
y2: 100,
stroke: '#1890FF',
lineWidth: 0.1,
cursor: 'pointer',
},
});
const line2 = line1.cloneNode();
const line3 = line1.cloneNode();
const line4 = line1.cloneNode();
line2.style.lineWidth = 0.5;
line3.style.lineWidth = 1;
line4.style.lineWidth = 2;
line2.translate(0, 50);
line3.translate(0, 100);
line4.translate(0, 150);

const path = new Path({
style: {
lineWidth: 0.5,
stroke: '#54BECC',
d: 'M 0,40 C 5.5555555555555545,40,22.222222222222218,44.44444444444445,33.33333333333333,40 C 44.444444444444436,35.55555555555556,55.55555555555554,14.66666666666667,66.66666666666666,13.333333333333336 C 77.77777777777777,12.000000000000002,88.88888888888887,32,100,32 C 111.11111111111113,32,122.22222222222221,14.66666666666667,133.33333333333331,13.333333333333336 C 144.44444444444443,12.000000000000002,155.55555555555557,24,166.66666666666669,24 C 177.7777777777778,24,188.8888888888889,11.111111111111114,200,13.333333333333336 C 211.1111111111111,15.555555555555557,222.22222222222226,35.111111111111114,233.33333333333334,37.333333333333336 C 244.44444444444443,39.55555555555555,255.55555555555551,31.22222222222223,266.66666666666663,26.66666666666667 C 277.77777777777777,22.111111111111114,294.4444444444444,12.777777777777779,300,10',
transform: 'translate(100, 100)',
},
});

canvas.addEventListener(CanvasEvent.READY, () => {
canvas.appendChild(line1);
canvas.appendChild(line2);
canvas.appendChild(line3);
canvas.appendChild(line4);
canvas.appendChild(path);
});

// stats
const stats = new Stats();
stats.showPanel(0);
const $stats = stats.dom;
$stats.style.position = 'absolute';
$stats.style.left = '0px';
$stats.style.top = '0px';
const $wrapper = document.getElementById('container');
$wrapper.appendChild($stats);
canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => {
if (stats) {
stats.update();
}
});

// GUI
const gui = new lil.GUI({ autoPlace: false });
$wrapper.appendChild(gui.domElement);
const rendererFolder = gui.addFolder('renderer');
const rendererConfig = {
renderer: 'canvas',
};

rendererFolder
.add(rendererConfig, 'renderer', [
'canvas',
'svg',
'webgl',
'webgpu',
'canvaskit',
])
.onChange((rendererName) => {
let renderer;
if (rendererName === 'canvas') {
renderer = canvasRenderer;
} else if (rendererName === 'svg') {
renderer = svgRenderer;
} else if (rendererName === 'webgl') {
renderer = webglRenderer;
} else if (rendererName === 'webgpu') {
renderer = webgpuRenderer;
} else if (rendererName === 'canvaskit') {
renderer = canvaskitRenderer;
}
canvas.setRenderer(renderer);
});
rendererFolder.open();

0 comments on commit 055eb10

Please sign in to comment.