Comments (7)
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.
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:
When zoomed in to some cases, white stripes will appear
When zoomed to a very large scale, it will appear
I tried to solve it, but didn't get it. Do you know how this happened?
from perfect-antialiasing.
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.
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.
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.
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.
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from perfect-antialiasing.