deck.gl icon indicating copy to clipboard operation
deck.gl copied to clipboard

`PathLayer` with `billboard:true` has broken `vPathPosition` interpolation

Open JannikGM opened this issue 2 years ago • 0 comments

This issue originated from a Slack discussion I originally wanted to create a PR, but while trying to fix it, I was unsure how to implement my hack properly.

Description

The PathLayer with billboard: true has bad interpolation of vPathPosition. Even splitting the line into smaller segments to avoid precision issues is not enough to fully mitigate this problem:

https://user-images.githubusercontent.com/72194488/129048323-5b1b7eed-fa5f-4276-91c8-67bc5e2a8d95.mov

I noticed that it looked like perspective correction errors, so I added if (billboard) { gl_Position /= gl_Position.w; } to the vs:#main-end - which worked:

https://user-images.githubusercontent.com/72194488/129048346-0b9a9d8d-d6e4-45a7-803b-d550ec4387b9.mov

I assume the perspective division leads to precision issues when looking along the line. The problem only seems to occur when pitching the map, and only when looking along the line.

However, I'm not sure if my proposed change is correct, because deck.gl explicitly does its own nextPositionScreen.w multiplication after lineJoin (and my hack just undoes that).

I wonder if there’s a better way, like multiplying vPathPosition by W? However, deck.gl calculates vPathPosition and geometry.uv as part of lineJoin, so I'm not sure where to do this modification cleanly. I could also imagine that the depth values see the same interpolation problem, so my proposed solution of keeping W=1.0 might be better after all.

I have some trouble wrapping my head around the existing transformation code in deck.gl, so I'd appreciate if someone else could look into it. Specifically, the existing code was written by @Pessimistress.

Expected Behavior

A line which is colored if abs(vPathPosition.x) < 0.5 should remain colored near the center, even when the camera moves. However, in practice, the colored portion of the line keeps shifting around.

Repro Steps

The video above was made with these settings:

getWidth: 50.0,
widthUnits: 'pixels',
widthScale: 1.0,
billboard: true,

I also had this hack to visualize the problem:

if (vPathPosition.y / vPathLength < 0.01) {
  discard; // Cut off 1% of line segment, so it's clear where each segment starts
}
gl_FragColor = vec4(vec3(1.0-abs(vPathPosition.x)), 1.0); // Center of line should be white, rest black

A full test-case is this (based on the PathLayer codepen that is reachable through the docs):

/*
* https://deck.gl/docs/api-reference/layers/path-layer
*/
const {DeckGL, PathLayer} = deck;

class DebugPathLayer extends PathLayer {
    getShaders() {
    return {
      ...super.getShaders(),
      inject: {
        'vs:#main-end': `if (billboard) { gl_Position /= gl_Position.w; }`, // Hack to undo perspective (Disable this line to see the bug)
        'fs:#main-end': `gl_FragColor = vec4(vec3(1.0-abs(vPathPosition.x)), 1.0);` // Force a white center, black edge
        }
      }
    }
}

const layer = new DebugPathLayer({
  id: 'PathLayer',
  data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-lines.json',
  
  /* props from PathLayer class */
  
  // billboard: false,
  // capRounded: false,
  getColor: d => {
    const hex = d.color;
    // convert to RGB
    return hex.match(/[0-9a-f]{2}/g).map(x => parseInt(x, 16));
  },
  billboard: true,
  getPath: d => d.path,
  getWidth: d => 50,
  // jointRounded: false,
  // miterLimit: 4,
  // widthMaxPixels: Number.MAX_SAFE_INTEGER,
  widthMinPixels: 2,
  widthScale: 50,
  // widthUnits: 'meters',
  
  /* props inherited from Layer class */
  
  // autoHighlight: false,
  // coordinateOrigin: [0, 0, 0],
  // coordinateSystem: COORDINATE_SYSTEM.LNGLAT,
  // highlightColor: [0, 0, 128, 128],
  // modelMatrix: null,
  // opacity: 1,
  parameters: {
    depthMask: false
  },
  pickable: true,
  // visible: true,
  // wrapLongitude: false,
});

new DeckGL({
  mapStyle: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
  initialViewState: {
    longitude: -122.4,
    latitude: 37.74,
    zoom: 11,
    maxZoom: 20,
    pitch: 30,
    bearing: 0
  },
  controller: true,
  getTooltip: ({object}) => object && object.name,
  layers: [layer]
});

Environment

  • Framework Version: deck.gl 8.4.20
  • Browser Version: Browser independent (but Chrome 92.0.4515.131 used for tests)
  • OS: Probably OS independent (but macOS Big Sur 11.5.1 used for tests)

Logs

None

JannikGM avatar Aug 11 '21 14:08 JannikGM