SOURCE

console 命令行工具 X clear

                    
>
console
/*
  Stroke with miter/bevel joints generated with vertex shader and fragment shader.
  Only very limited CPU processing is involved.
  
  References:
    https://developers.arcgis.com/javascript/latest/sample-code/custom-gl-animated-lines/index.html
*/

const W = 640;
const H = 360;

const vsSource = `
  uniform float uWidth;
  uniform float uTolerance;
  uniform vec3 uColor;

  attribute vec2 aPos;
  attribute vec2 aPrev;
  attribute vec2 aNext;
  attribute float aOrder;

  varying highp vec3 vColor;

  vec2 ndcFromScreen(vec2 screen) {
    return vec2(screen.x / ${W}. * 2. - 1., -screen.y / ${H}. * 2. + 1.);
  }

  void main() {
    vec2 vPrev = (aPos - aPrev); vPrev = normalize(vPrev);
    vec2 vNext = (aPos - aNext); vNext = normalize(vNext);
    vec2 nPrev = vec2(-vPrev.y, vPrev.x);
    vec2 nNext = vec2(vNext.y, -vNext.x);
    vec2 n = nPrev + nNext; n = normalize(n);
    float cosTheta = dot(n, nPrev) / length(n) / length(nPrev);
    float extrudedLen = abs(uWidth / 2. / cosTheta);

    bool turnDir = dot(vPrev, vNext) > 0.;
    bool isAbove = aOrder == 0. || aOrder == 2.;
    bool isLeft = aOrder <= 1.;

    n = n * extrudedLen;
    if (!isAbove) n = -n;

    vec2 pos = aPos + n;
    
    if ((isAbove && turnDir) || (!isAbove && !turnDir)) {
      float limit = uWidth / 2. * uTolerance;
      if (extrudedLen > limit) {
        vec2 nRef = (isLeft ? nPrev : nNext) * uWidth / 2.;
        if (!isAbove) nRef = -nRef;
        vec2 q = nRef - n;
        float x = extrudedLen * (extrudedLen - limit) / length(q);
        pos += normalize(q) * x;
      }
    }

    gl_Position = vec4(ndcFromScreen(pos), 0.0, 1.0);
    vColor = uColor;
  }
  `;

const fsSource = `
  varying highp vec3 vColor;
  void main() {
    gl_FragColor = vec4(vColor.rgb, 1.);
  }
  `;


const canvas = document.createElement("canvas");
canvas.width = W;
canvas.height = H;
document.body.appendChild(canvas);

const gl = canvas.getContext("webgl2");

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vsSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  alert(gl.getShaderInfoLog(vertexShader));
}

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fsSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  alert(gl.getShaderInfoLog(fragmentShader));
}

const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
  alert(gl.getProgramInfoLog(shaderProgram));
}

const polyline = [100, 100, 200, 100, 300, 200, 300, 50, 500, 50];
const polylineSize = polyline.length / 2;

// Extend the first and last points for prev/next information
const polylineExt = polyline.slice();
polylineExt.unshift(2 * polyline[1] - polyline[3]);
polylineExt.unshift(2 * polyline[0] - polyline[2]);
polylineExt.push(
  2 * polyline[polyline.length - 2] - polyline[polyline.length - 4]
);
polylineExt.push(
  2 * polyline[polyline.length - 1] - polyline[polyline.length - 3]
);
const polylineExtSize = polylineExt.length / 2;

// Every point on the polyline is extended to 4 points (some are redundant), two on each side.
const stroke = [];
for (let i = 0; i < polylineExtSize; i++) {
  for (let j = 0; j < 4; j++) {
    stroke.push(polylineExt[i * 2]);
    stroke.push(polylineExt[i * 2 + 1]);
  }
}

// Since gl_VertexID is not supported, we have to assign the
// vertex orders manually.
const order = []; // lt(left-top), lb, rt, rb
for (let i = 0; i < polylineSize; i++) {
  for (let j = 0; j < 4; j++) {
    order.push(j);
  }
}

const dists = []; // Distances along the polyline
for (let i = 0; i < polylineSize; i++) {
  for (let j = 0; j < 4; j++) {
    const dx = polyline[(i - 1) * 2] - polyline[i * 2];
    const dy = polyline[(i - 1) * 2 + 1] - polyline[i * 2 + 1];
    dists.push(i === 0 ? 0 : dists[(i - 1) * 4] + Math.sqrt(dx * dx + dy * dy));
  }
}

// To save storage, current/prev/next positions share the buffer
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(stroke), gl.STATIC_DRAW);

const aPos = gl.getAttribLocation(shaderProgram, "aPos");
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 8 * 4);

const aPrev = gl.getAttribLocation(shaderProgram, "aPrev");
gl.enableVertexAttribArray(aPrev);
gl.vertexAttribPointer(aPrev, 2, gl.FLOAT, false, 0, 0);

const aNext = gl.getAttribLocation(shaderProgram, "aNext");
gl.enableVertexAttribArray(aNext);
gl.vertexAttribPointer(aNext, 2, gl.FLOAT, false, 0, 8 * 4 * 2);

const aOrder = gl.getAttribLocation(shaderProgram, "aOrder");
gl.enableVertexAttribArray(aOrder);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(order), gl.STATIC_DRAW);
gl.vertexAttribPointer(aOrder, 1, gl.FLOAT, false, 0, 0);

const aDist = gl.getAttribLocation(shaderProgram, "aDist");
gl.enableVertexAttribArray(aDist);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(dists), gl.STATIC_DRAW);
gl.vertexAttribPointer(aDist, 1, gl.FLOAT, false, 0, 0);

const uWidth = gl.getUniformLocation(shaderProgram, "uWidth");
gl.uniform1f(uWidth, 30.0);

const uTolerance = gl.getUniformLocation(shaderProgram, "uTolerance");
gl.uniform1f(uTolerance, 1.5);

const uColor = gl.getUniformLocation(shaderProgram, "uColor");
gl.uniform3f(uColor, 1.0, 0.0, 0.0);

gl.drawArrays(gl.TRIANGLE_STRIP, 0, polylineSize * 4);

gl.uniform3f(uColor, 1.0, 1.0, 0.0);
gl.drawArrays(gl.LINE_STRIP, 0, polylineSize * 4);
<body></body>