visgl / visgl/deck.gl

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

Open
#6,092 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
14.6k
Forks
2.3k
Avg merge
2d 9h
Merged PRs (30d)
42

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with modules/layers/src/path-layer/path-layer-vertex.glsl.js, especially the lineJoin logic around lines 150–154 and the nextPositionScreen.w multiplication near line 215. Reproduce the issue using the supplied PathLayer example with billboard enabled and the vPathPosition fragment-color injection. Done means vPathPosition interpolation remains stable while pitching the camera and viewing along the line, without relying on the proposed hack.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
computer-graphics, data-visualization
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.