audulus/vger

Missing antialiasing on some edges in VgerDemo...

Closed this issue · 20 comments

I noticed it in VgerDemo on iPad Pro 11 from 2018, and ipadOS 14.8
This also happens on very simple svg, like this:
<path d='M10,10v50h50z' fill='#22ff22'/>

IMG_344AD2AF55DD-1

Here is a basic triangle (M10,10v50h50z) showing both antialiasing issue, and precision problem
IMG_90FA58D57001-1
.

Yeah I've noticed that too... what do you think is happening there? Shader debugger might shed some light on it.

I turned off the SDF AA and bezier inside-outside test, leaving only the linear test, and that exhibits numerical issues:

image

Here's the line test returning what is probably the wrong result for a pixel:

image

Actual x coordinate of p is 36.4999961853 which causes the test to give the wrong result. I'd guess the issue here is that the coordinates coming in are slightly inaccurate, not the line test itself.

So the pixel coordinates are exact, it's just the "texture" coordinates (which in the case of path fills are just the local coordinates in the path space) are approximate.

image

If I use the pixel coordinates, it's better:

image

Rendering this triangle without aa will always display wrong pixels. Pixels on the edge are located exactly on the trinagle edge, so sometimes gpu rasterizer, due to rounding, will treat them as inside and sometimes outside the triangle.
However antialiasing should render perfect edges.

Maybe you should increase rendered triangle by one or half pixel?

@luckyclan you're right, it's an AA issue. It seems that it's an issue with my SDF approximation (sdBezierApprox2). If I set exact=true then I get good results:

image

Much better now. Does it affect the performance?

Yep. Tiger goes from .66ms in the fragment shader to 1.2ms on my machine.

Still very good! Did you try to tesselate bezier to lines? I wonder if this is faster or slower.

I didn't. I think the algorithm would be fairly different if I went that route.

So I got that SDF approximation from this paper: https://hhoppe.com/ravg.pdf

and the paper notes that they perturb the points (section 4.4):

image

Earlier I was lazy and just threw in a flatness check here:

if(abs(det) < 0.01) {

but I wasn't using it for some reason (was using sdBezierApprox2 instead).

Unfortunately, I didn't document why I stopped using the flatness check: 4370abb

Did you try sdf bezier segment from this page / rendertoy: https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm

@luckyclan Yeah, that's what it uses when exact=true

inline float sdBezier(float2 pos, float2 A, float2 B, float2 C )

Here's my old shader toy of that approximation: https://www.shadertoy.com/view/XsX3zf

It's pretty easy to have a degenerate case, or something close where the DF is too steep, so I'm not sure how they got it to work well in the paper.

Yes, approx ver doesn’t look good.

I also wonder if you use naive or smart bounding box calculation for bezier: https://www.shadertoy.com/view/lsyfWc

I bet there is also optimization in sdf bezier so you would use sdf only for some selected pixels…

@luckyclan Yeah, I had some trivial rejection code in there already. It seems like using it with the exact bezier calculation doesn't result in too big of a performance regression. I can live with that :)

Could you try to work directly on cubic curve (without splitting it to two quadratic), using method from this:
https://www.shadertoy.com/view/4sKyzW

It uses iterations so it will be probably slower, but maybe it is worth trying.

Btw, check my methods of converting cubic to quadratic:
https://www.shadertoy.com/view/7dlfRN

Could you try to work directly on cubic curve (without splitting it to two quadratic), using method from this: https://www.shadertoy.com/view/4sKyzW

It uses iterations so it will be probably slower, but maybe it is worth trying.

We'd also need an inside/outside test along the lines of bezierTest for cubics:

inline bool bezierTest(float2 p, float2 A, float2 B, float2 C) {

I think Loop-Blinn does that but I haven't had the time to implement it.

Your cubic to quadratic conversion looks really good. Seems like it would be an easy thing to do in vgerCubicApproxTo if that interests you :)

Yep. Tiger goes from .66ms in the fragment shader to 1.2ms on my machine.
...
@luckyclan Yeah, I had some trivial rejection code in there already. It seems like using it with the exact bezier calculation doesn't result in too big of a performance regression. I can live with that :)

Now that you made it the default I wonder how much is "not too big"? Is it around .66ms or rather 1.2ms?

@dumblob it made the fragment shader 2x slower. .66ms to 1.2ms.