visgl / deck.gl

WebGL2 powered visualization framework
https://deck.gl
MIT License
12.29k stars 2.09k forks source link

[Bug] Rounding issues in TerrainLayer #7527

Open janichk opened 1 year ago

janichk commented 1 year ago

Description

I am attempting to visualize high resolution terrain data using the TerrainLayer, but it seems that at higher zoom levels the layer is unable to correctly render the tiles. The cause of this seems to be a rounding error due to use of float32.

The problem starts at zoom level 17 in my case, but I guess this will depend on the cartesian offset.

image Wireframe showing the rounding error.

While debugging this issue, the first problem was actually found in the @loaders.gl / parse_terrain.ts /getMeshAttributes where the 32 bit resolution forces multiple x and y coordinates into the same values. I overcame this issue by modifying the code to use zero based bounds and a scaling factor, then communicating these parameters with the generated mesh so that the creation of the SimpleMeshLayers in TerrainLayer.renderSubLayers can compensate through getPosition and getScale.

Now I can verify that the mesh generated by the TerrainLoader is ok, but the rendering is still messy like in the picture above, and i assume this is caused by later mesh processing. I have spent a few days digging, but I'm still unable to pinpoint exactly where the error lies. Suspecting it could be mesh_layers/utils/matrix.ts ...

Flavors

Expected Behavior

At a minimum, the limitations in the TerrainLayer and TerrainLoader should be documented, e.g. max supported zoom level..

I would love to get some pointers on how to solve this issue in my current project short term, and of course get a long term solution in place in an upcoming release.

Steps to Reproduce

Problem should be visually apparent

Environment

Logs

No response

kylebarron commented 1 year ago

@loaders.gl / parse_terrain.ts /getMeshAttributes where the 32 bit resolution forces multiple x and y coordinates into the same values

Maybe we could add an option there for whether to use float32 or float64

but the rendering is still messy like in the picture above

I can't tell what you're saying is wrong in the wireframe. If anything, it looks from that image to be "blocky" instead of a smooth terrain. I think that's usually an artifact from setting meshMaxError to 0, since you don't have any simplification across similar nearby pixel values.

kylebarron commented 1 year ago

@loaders.gl / parse_terrain.ts /getMeshAttributes where the 32 bit resolution forces multiple x and y coordinates into the same values

Maybe we could add an option there for whether to use float32 or float64

but the rendering is still messy like in the picture above

I can't tell what you're saying is wrong in the wireframe. If anything, it looks from that image to be "blocky" instead of a smooth terrain. I think that's usually an artifact from setting meshMaxError to 0, since you don't have any simplification across similar nearby pixel values.

kylebarron commented 1 year ago

@loaders.gl / parse_terrain.ts /getMeshAttributes where the 32 bit resolution forces multiple x and y coordinates into the same values

Maybe we could add an option there for whether to use float32 or float64

but the rendering is still messy like in the picture above

I can't tell what you're saying is wrong in the wireframe. If anything, it looks from that image to be "blocky" instead of a smooth terrain. I think that's usually an artifact from setting meshMaxError to 0, since you don't have any simplification across similar nearby pixel values.

janichk commented 1 year ago

What we're seeing in the wireframe, is multiple z coordinates in the same x-y positions. It is very apparent as I zoom in beyond zoom level 17 that there is a granularity issue with the x-y coordinates. Setting meshMaxError to zero is not necessary, its just a means to make the problem more visible. In our application we need to show fine details in the terrain, 25 cm resolution or finer. I have tested our tile-server with CesiumJS, and the visualization there is showing the fine details. We would much rater use deck.gl for our application though..

What I suspect is that all tiles are visualized in the same larger CRS, where eventually the granularity of float32 is insufficient to represent the subdivisions in x-y direction. Beyond a certain zoom level, the size of those blocks in the wireframe are the same size, while more and more z values are stacking up.

Unfortunately we are unable to share our tile-server, but the problem can easily be reproduced using a single terrain png and narrowing in the bounds until you see the same "blocking" as in the image above. My expectation is that the terrain representation would be the same regardless of the size of the bounds, but at different zoom levels.

janichk commented 1 year ago

I modified the Codepen from https://deck.gl/docs/api-reference/geo-layers/terrain-layer to illustrate the issue. Although in this case delatin meshing is used, the result is the same.

https://codepen.io/janichk/pen/JjBYwGx

Pessimistress commented 1 year ago

Same as https://github.com/visgl/deck.gl/issues/6620 ?

norkystjan commented 1 year ago

It is definitely related, but the response in that thread is incorrect. At zoom level 22, the bounds in my case are [264.1475830078125, 360.439208984375, 264.147705078125, 360.4393310546875] in double representation, which translates to [264.14758, 360.43920, 264.14770, 360.43933] in 32 bit representation (assuming 8 significant digits, IEEE float 32 has 6-9 with an average of 7 significant digits). Now see how many distinct x and y coordinates can be represented in those ranges given the 32 bit limitation. As mentioned initially, I found a workaround for the issue in getMeshAttributes, using relative bounds instead of absolute (freeing up some digits), but the visualization of the tiles is still the same. My conclusion is therefore that the 32 bit issue is not limited to the function mentioned in #6620

birkskyum commented 1 year ago

Is this related to the sudden change in elevation that happen when zooming out and in in the terrain layer example https://deck.gl/examples/terrain-layer ?

Jonas-Witt commented 8 months ago

I stumbed upon the same issue while trying to display a custom (dense) terrain mesh with a cell size of 0.5m. This is what I get due to the 32bit resolution (this is actually a 100x100 cell grid). I created a modified TerrainLayer that instantiates the SimpleMeshLayer like this:

  renderLayers(): Layer | null | LayersList {
    const { color, material, elevationData, texture, wireframe, bounds } =
      this.props;

    if (!elevationData) {
      return null;
    }

    const [minX, minY, maxX, maxY] = bounds;
    const xScale = maxX - minX;
    const yScale = maxY - minY;

    const SubLayerClass = this.getSubLayerClass('mesh', SimpleMeshLayer);
    return new SubLayerClass(
      this.getSubLayerProps({
        id: 'mesh',
      }),
      {
        data: DUMMY_DATA,
        mesh: this.state.terrain,
        texture,
        _instanced: false,
        getPosition: () => [bounds?.[0] ?? 0, bounds?.[1] ?? 0, 0],
        getScale: () => [xScale, yScale, 1],
        getColor: color,
        material,
        wireframe,
      },
    );
  }

Screenshot from 2024-03-27 14-34-02

My question is, whether we can somehow circumvent the precision issues since the large coordinates could be avoided in the camera frame for rendering. The grid vertices are already normalized to the range [0,1] and then only scaled via getScale and the offset is provided by getPosition.