mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.18k stars 2.22k forks source link

Strange rendering artefacts with the line layer #10000

Open eslavnov opened 4 years ago

eslavnov commented 4 years ago

Hi guys,

Thank you for this awesome library!

Not sure if it's a bug or just me doing something wrong (probably the latter...), but I am experiencing some weird rendering issues when using the line layer.

At the maximum zoom level the line looks as I would expect it: 1

However, when I zoom out I start to get these strange artefacts where my line makes sharp turns: 2

I've tried tweaking various settings and also searched the issues, but no luck. I would really appreciate if somebody could point me in the right direction: to be honest, I am a bit lost now on how to debug/fix this issue.

Thanks in advance!

karimnaaji commented 4 years ago

Hey @eslavnov , could you provide an example with the styling used in code (jsfiddle or other) so we could reproduce this issue on our side?

eslavnov commented 4 years ago

Hi @karimnaaji!

Sorry that I did not provide an example from the very beginning, here it is: https://jsfiddle.net/gyrga/ku6wm3z8/

At the default zoom level, you can see these artefacts. If you zoom-in, at some point they go away and the line is displayed properly. While making this minimal example I've noticed that line-blur seems to be the culprit: removing it results in a line with no such artefacts. Looks like line-blur is struggling when there are small sharp corners and your view is zoomed-out...

Any pointers on how we could fix this issue will be highly appreciated!

karimnaaji commented 4 years ago

Thanks, @eslavnov that's very useful. Yes it does seem to be related to line-blur, it looks like there is overlapping geometry that is blending together at the line extremities, resulting in those overlays.

featmo commented 4 years ago

Hey, @eslavnov I was looking at your example on jsfiddle, I found that using an event with map.on and setPaintProperty you can dynamically change the map paint properties such as line-blur. here is my example based on yours: https://jsfiddle.net/featmo/p0huvzj9/379/

Sorry if my explanation and code are a bit rough, this is my first issue.

featmo commented 4 years ago

hey, @karimnaaji this artifact shows when using line-opacity

karimnaaji commented 4 years ago

Thanks for the example, looks great! I think that solution can provide a good workaround until we solve this type of issue within the library. @eslavnov have you considered and tried something like @featmo's solution?

eslavnov commented 3 years ago

Hi all,

Just wanted to share two possible mitigation strategies: 1) indeed, tweaking the line-blur depending on a zoom level helps quite a bit 2) for our particular use-case, tweaking the tolerance based on zoom helped a lot: most of these artifacts appear when the line makes a turn and only at specific zoom levels, so simplifying the lines for those zoom levels really helps.

This does not really solve the problem, but depending on your use case this might be an acceptable workaround.

rreusser commented 3 years ago

Just one additional note is that this is not strictly a problem with the line-blur property, though that's certainly one way to trigger it. It's more generally a problem with the combination of self-intersections caused by large line-widths and opacity < 1, as can be seen in the following image which uses no line-blur:

Screen Shot 2021-08-05 at 10 20 49 AM

The only two methods I can think of for resolving this are adjusting the line geometry to prevent self-intersections (for example, we could limit vertex movement tangent to line segments to half of each line segment length so that nearby line vertices would not cause this overlap—at the cost of line offset correctness, and although computing these limits and passing them as attributes to the shader seems prohibitive), or to draw the lines to a separate framerbuffer and then composite them onto the map—which is currently not possible because compositing was removed early on due to serious performance problems.

rreusser commented 3 years ago

I wrote up a somewhat lengthy description of exactly why this occurs and what can be done about it, which I'll copy below. The specific context was implementation of a glow or drop-shadow-like effect:

The problem

The line-blur property can be used to accomplish a glow or drop-shadow-like effect but results in jagged artifacts near sharp corners. These artifacts worsen as the line width (including blur) increases. A single sharp corner does not trigger the effect (edit: a single sharp corner can trigger this. See: #10925 for a partial resolution of this behavior, though it would not solve the case of nearby overlapping corners like those which would occur on a windy road). The image at the top of this issue illustrates this well. Note that most of these corners are isolated, though a small part of the artifacts in the lower left result from two overlapping corners:

94139866-a5362900-fe6a-11ea-97c1-12e73a07a47a-1

It’s important to note that this is not strictly related to the line-blur property and more generally occurs on all lines with large width and opacity less than one. The image below shows two line layers. The transparent purple line layer exhibits the artifact even though there is no line-blur. See, for example:

128393854-0fd7c38f-f91e-4cf0-83c7-b71c5312871c

Why does this occur?

This behavior occurs because of how Mapbox GL draws lines. For a detailed explanation of how lines are drawn, see the Mapbox blog post, Drawing Antialiased Lines in OpenGL.

Drawing lines first requires breaking up the line into triangles. The following diagram shows an example of a triangulation. (The exact triangulation Mapbox uses is very slightly different— it uses just two triangles—though the difference is not important with respect to line-blur artifacts.)

Screen Shot 2021-08-11 at 1 30 55 PM

Lines of finite width must be joined with a miter or a round. The diagram below shows the geometry of a single corner.

Screen Shot 2021-08-11 at 1 31 15 PM

The problem arises when triangulating two nearby corners. It is not straightforward, in general, to produce a triangulation of two nearby corners which does not self-intersect. Some self-intersection can be worked around easily (See in progress issue #10925 which I believe will significantly reduce these artifacts), but a more general solution would need to be performed on every frame as the map zooms and pitches, so that it’s not currently feasible to fully resolve the issue by performing this more complex geometric computation at each corner, on every frame.

Solutions and workarounds

There are workarounds, but not a general solution.

Layers and blending modes

For a brief time, Mapbox GL supported layer blending modes by performing additional compositing steps during rendering. In this approach, fully opaque lines would be drawn to a separate image. The image would then be composited with transparency back onto the rendered map. However, this feature was removed in 2014 (see: https://github.com/mapbox/mapbox-gl-js/issues/523#issuecomment-51731405 )as a single blended layer could degrade performance of the entire map by 33%. Due to critical performance considerations, it is not currently planned to bring this feature back.

Note additionally that compositing alone would not fully solve the problem. A single self-intersecting, blurred line would still exhibit these artifacts, even if drawn on a separate layer. The key ingredient for perfect drop shadows would be to draw unblurred lines to a separate layer, then apply a blur filter to that layer, then composite the layer onto the main map. If the cost of compositing alone may be acceptable with graphics performance in 2021, I believe these additional blur filter passes would push the cost back into the infeasible range.

Reducing the width of line-blur

The artifacts worsen as line widths increase and mitered corner geometries intersect. The simplest way to reduce the artifacts is to adjust the style to minimize total line widths (including the width of line-blur).

Use zoom expressions

Appearance of the artifacts depends most critically on line width and distance between line vertices. Both of these may change as the map is zoomed. Thus, it may be possible with careful use of expressions to reduce the line-blur shadowing effect in configuration ranges where the artifacts are most visible.

Increase line simplification

GeoJSON sources have a tolerance property. Increasing the tolerance increases line simplification, which in turn increases the distance between adjacent line vertices. This has the effect of reducing the artifacts. However, increasing simplification impacts overall rendering of the map and may be too indirect a solution to be advisable.

Viewport-anchored offset

There is more than one way to accomplish the appearance of a shadow. In addition to just the line-blur property (which increasing causes the artifacts under consideration), it is possible to add a viewport-anchored translation to the lines. The line-translate and line-translate-anchor properties provide a mechanism for applying a similar depth cue without increasing the total line width.

One downside of this approach is that the intersecting lines as well as line-caps still combine additively during rendering, resulting in potentially undesirable behavior at line caps. The image below shows the effect of different line-cap choices. I believe rounded line caps may be the best option in this approach.

See: sample style

line-cap

Multiple shadows

@asheemmamoowala pointed out that it might be possible to use multiple shadows in different viewport-anchored directions, e.g. [1, 0], [0, 1], [-1, 0], [0, -1]. I believe performance would be acceptable, but I suspect this would introduce angular dependence on the shadow strength, as well as unpleasant patterns at line ends or where lines meet.