Giter VIP home page Giter VIP logo

Comments (7)

andrewlowndes avatar andrewlowndes commented on May 23, 2024

The approach described above uses a similar blending mechanism as the approach used in this repo, additive blending - this is likely causing the artefacts you have posted - similar can be seen in this demo when text overlaps: https://andrewlowndes.github.io/perfect-antialiasing/dist/custom_font.html.

To ensure the blending between multiple rendered characters is done correctly the existing pixel value should be used as the start value when rendering the next character. Also worth clamping the resultant colour to match the desired text colour, I have prepared a pr that fixes the demo above with this suggestion (#5) - hope this helps with your example.

Another cause may be that the winding order of the rendering vector is messed up due to the overlap - this could be the case if all of the characters are being rendered in the same pass, see http://blog.ivank.net/how-vector-graphics-work.html for a good explanation on the winding order and a possible solution.

from perfect-antialiasing.

defpis avatar defpis commented on May 23, 2024

The reason for the rendering error is computing the “winding number” or the number of times the outline winds around a point is incorrect, to get the correct results, must ensure that they do not overlap, but it is related to how to allocate space on the texture, things are getting more and more troublesome.

I changed the strategy and implemented it according to the this repo method, everything is working fine, but I additionally add matrix to achieve drag&zoom:

屏幕录制2021-07-27 上午11 02 50

When zoomed in to some cases, white stripes will appear

image

When zoomed to a very large scale, it will appear

image

I tried to solve it, but didn't get it. Do you know how this happened?

from perfect-antialiasing.

defpis avatar defpis commented on May 23, 2024

Here is my code:

font.ts

import "../index.scss";
import { canvasResize, loadShader } from "../utils";
import shaderSource from "../assets/shaders/font";
import { addDragListener } from "../mouse";
import opentype from "opentype.js";
import { pathToPoints } from "../geometry/pathToPoints";
import { pointsToPolygons } from "../geometry/pointsToPolygons";

const canvas = document.querySelector("#app") as HTMLCanvasElement;
const transform = addDragListener(canvas);
canvasResize(canvas, document.body);

const gl = canvas.getContext("webgl2", {
  antialias: false,
}) as WebGL2RenderingContext;

const shaderProgram = gl.createProgram()!;
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, shaderSource.vs);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, shaderSource.fs);
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);

const vao = gl.createVertexArray();
const currPosBuffer = gl.createBuffer();
const nextPosBuffer = gl.createBuffer();
const prevPosBuffer = gl.createBuffer();
const indexBuffer = gl.createBuffer();
const aCurrPos = gl.getAttribLocation(shaderProgram, "aCurrPos");
const aNextPos = gl.getAttribLocation(shaderProgram, "aNextPos");
const aPrevPos = gl.getAttribLocation(shaderProgram, "aPrevPos");
const aIndex = gl.getAttribLocation(shaderProgram, "aIndex");
let size = 0;

const uMatrix = gl.getUniformLocation(shaderProgram, "uMatrix");
const uScreen = gl.getUniformLocation(shaderProgram, "uScreen");
const uColor = gl.getUniformLocation(shaderProgram, "uColor");
const uScale = gl.getUniformLocation(shaderProgram, "uScale");

gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE);
gl.clearColor(0.0, 0.0, 0.0, 0.0);

const tick = () => {
  const { width, height } = canvas;
  const dpr = window.devicePixelRatio;

  gl.viewport(0, 0, width, height);
  gl.clear(gl.COLOR_BUFFER_BIT);

  const w = (2 * dpr) / width;
  const h = (2 * dpr) / height;

  // prettier-ignore
  const matrix = [
  	w * transform.z, 0, 0, 0,
  	0, -h * transform.z, 0, 0,
  	0, 0, 1, 0,
  	-1 + transform.x * w, 1 - transform.y * h, 0, 1
  ];

  gl.uniformMatrix4fv(uMatrix, false, matrix);
  gl.uniform2fv(uScreen, [width, height]);
  gl.uniform3fv(uColor, [1.0, 0.0, 0.0]);
  gl.uniform1f(uScale, transform.z);
  gl.bindVertexArray(vao);
  gl.drawArrays(gl.TRIANGLES, 0, size);

  console.log(transform.z);

  requestAnimationFrame(tick);
};

const start = async () => {
  const font = await opentype.load("assets/fonts/PingFang.ttf");
  const lines = ["Hello World!", "你好,世界!"];
  const fontSize = 128;
  const splitThreshold = 0.999;

  const currVertices: Array<number> = [];
  const nextVertices: Array<number> = [];
  const prevVertices: Array<number> = [];
  const indices: Array<number> = [];

  let topOffset = 0;
  lines.forEach((line) => {
    const glyphs = font.stringToGlyphs(line);

    let leftOffset = 0;
    let lineHeight = 0;
    glyphs.forEach((glyph) => {
      const path = glyph.getPath(leftOffset, topOffset, fontSize);
      const pointsGroups = pathToPoints(path.toPathData(3), splitThreshold);
      const polygons = pointsToPolygons(pointsGroups);

      leftOffset += (glyph.advanceWidth / 1000) * fontSize;
      lineHeight = Math.max(lineHeight, fontSize * 1.4); // TODO

      polygons.forEach((polygon) => {
        polygon.forEach(({ p1, p2, p3 }) => {
          currVertices.push(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
          nextVertices.push(p2.x, p2.y, p3.x, p3.y, p1.x, p1.y);
          prevVertices.push(p3.x, p3.y, p1.x, p1.y, p2.x, p2.y);
          indices.push(0, 1, 2);
        });
      });
    });
    topOffset += lineHeight;
  });

  gl.bindVertexArray(vao);

  gl.bindBuffer(gl.ARRAY_BUFFER, currPosBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array(currVertices),
    gl.STATIC_DRAW
  );
  gl.vertexAttribPointer(aCurrPos, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(aCurrPos);

  gl.bindBuffer(gl.ARRAY_BUFFER, nextPosBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array(nextVertices),
    gl.STATIC_DRAW
  );
  gl.vertexAttribPointer(aNextPos, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(aNextPos);

  gl.bindBuffer(gl.ARRAY_BUFFER, prevPosBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array(prevVertices),
    gl.STATIC_DRAW
  );
  gl.vertexAttribPointer(aPrevPos, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(aPrevPos);

  gl.bindBuffer(gl.ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(indices), gl.STATIC_DRAW);
  gl.vertexAttribPointer(aIndex, 1, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(aIndex);

  gl.bindVertexArray(null);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  size = indices.length;

  requestAnimationFrame(tick);
};

start();

shader.vert

void main() {
  vec2 currPos = (uMatrix * vec4(aCurrPos, 0.0, 1.0)).xy;
  vec2 nextPos = (uMatrix * vec4(aNextPos, 0.0, 1.0)).xy;
  vec2 prevPos = (uMatrix * vec4(aPrevPos, 0.0, 1.0)).xy;

  vec2 a = normalize(currPos - prevPos);
  vec2 b = normalize(currPos - nextPos);
  float angleSin = sqrt((1.0 - dot(a, b)) / 2.0);

  vec2 pos;
  if (abs(angleSin) > delta) {
    pos = currPos + normalize(a + b) / angleSin / uScreen.x * sqrt(2.0);
  } else {
    pos = currPos;
  }

  bbox = uScreen.xyxy * (vec4(min(min(currPos, prevPos), nextPos),
                              max(max(currPos, prevPos), nextPos)) /
                             2.0 +
                         0.5) +
         vec4(-0.5, -0.5, 0.5, 0.5);

  vec2 screenCurrPos = uScreen * (currPos / 2.0 + 0.5);
  vec2 screenNextPos = uScreen * (nextPos / 2.0 + 0.5);
  vec2 screenPrevPos = uScreen * (prevPos / 2.0 + 0.5);

  if (int(aIndex) == 0) {
    vPrevPos = screenCurrPos;
    vCurrPos = screenNextPos;
    vNextPos = screenPrevPos;
  }
  if (int(aIndex) == 1) {
    vPrevPos = screenPrevPos;
    vCurrPos = screenCurrPos;
    vNextPos = screenNextPos;
  }
  if (int(aIndex) == 2) {
    vPrevPos = screenNextPos;
    vCurrPos = screenPrevPos;
    vNextPos = screenCurrPos;
  }

  e1 = vCurrPos - vPrevPos; // p1->p2
  e2 = vNextPos - vCurrPos; // p2->p3
  e3 = vPrevPos - vNextPos; // p3->p1

  gl_Position = vec4(pos, 0.0, 1.0);
}

fragment shader is same as this repo

from perfect-antialiasing.

andrewlowndes avatar andrewlowndes commented on May 23, 2024

I will need some time to properly debug this issue however it is likely due to the fact the algorithm uses floats for determining coverage. I think editing the fragment shader to check whether the current pixel is fully contained within the triangle (then setting the alpha to 1) before determining the coverage may resolve this issue (and lead to much quicker rendering when fonts are zoomed in a lot) - was planning to implement this in the future when I have time to code it

from perfect-antialiasing.

defpis avatar defpis commented on May 23, 2024

I also analyzed the problem caused by the determining coverage. I tried to improve the accuracy in some way, but didn’t know how to do. Let me first try your method of judging whether the current pixel is completely contained in the triangle, and if there is progress, I will tell you😄.

from perfect-antialiasing.

defpis avatar defpis commented on May 23, 2024

In the process of debugging, I also found a phenomenon: different display devices will have different display effects. I switched from the macbook display to the external dell display, and the white stripes disappeared. But both will produce discontinuous squares when zoomed to a particularly large size.

from perfect-antialiasing.

andrewlowndes avatar andrewlowndes commented on May 23, 2024

I have enhanced the custom font demo (https://andrewlowndes.github.io/perfect-antialiasing/dist/custom_font_webgl.html) to include zooming like your example and can replicate the issue on my machine :) I have implemented a check in the fragment shader before calculating the alpha and it resolves the issue - however the problem likely still remains for triangle edges that are enclosed in the shape - I think a different solution will be needed for these.

from perfect-antialiasing.

Related Issues (3)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.