spite / THREE.MeshLine

Mesh replacement for THREE.Line
MIT License
2.16k stars 380 forks source link

Screen-space projected dashed lines #130

Closed capr closed 3 years ago

capr commented 3 years ago

Hi,

Can MeshLine do dashed lines that have constant length at any depth? If not, any idea on how to modify it to achieve that? So basically I want a sizeAttenuation-like parameter that applies to dash lengths.

Thanks!

makc commented 3 years ago

I think your best bet is maybe to discard pixels in the shader based on triangular grid: triangular grid

Square grid is super-easy to code but may look less uniform maybe?

makc commented 3 years ago

Just tried square grid - it is not uniform at all and super trippy :D

makc commented 3 years ago

Im out of cheap ideas, but what you require can be done exactly using a custom attribute that you will have to calculate and update every frame. This attribute would have to be a line length to given vertex in screen space, and then in shader you again discard pixels based on how far are you in the line

Jeremboo commented 3 years ago

The MeshLines doesn't support this behavior. They are supposed to respect the perspective. Have you think about using a OrthographicCamera ? Otherwise, you will have to try to hack the vertexShader by removing the modelMatrix but I'm not sure about the quality of the render.

makc commented 3 years ago

so I did a test of what I proposed above, and although technically it works Screen Shot 2021-02-02 at 1 42 52 you do not want to use this 😂 becaue as the distance from fixed point increases, these dashes move along the line faster and faster.

makc commented 3 years ago

if you do not mind the uniformity of dash length breaking at random points, you can limit that effect significantly

capr commented 3 years ago

I ended up using the solution from this SO question: https://stackoverflow.com/questions/54516794/three-js-uniform-dashed-line-relative-to-camera

... except it doesn't actually work in the general case because the distance must be computed in the fragment shader not in the vertex shader. I deleted my SO account so I can't fix the solution there, but I'll post it here if anyone's interested. I'm sure this can be adapted to work with MeshLine too, but for my needs (i.e. I didn't need fat lines, just dotted lines) this is enough.

let vshader = `
    flat out vec4 p0;
    out vec4 p;
    void main() {
        vec4 p1 = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        gl_Position = p1;
        p  = p1;
        p0 = p1;
    }
`

let fshader = `
    precision highp float;
    flat in vec4 p0;
    in vec4 p;
    uniform vec3  color;
    uniform vec2  canvas;
    uniform float dash;
    uniform float gap;
    void main(){
        vec2 dir = ((p.xyz / p.w).xy - (p0.xyz / p0.w).xy) * canvas.xy / 2.0;
        float dist = length(dir);
        if (fract(dist / (dash + gap)) > dash / (dash + gap))
            discard;
        gl_FragColor = vec4(color.rgb, 1.0);
    }
    `

let uniforms = {
        canvas : {type: 'v2', value: {x: canvas.width, y: canvas.height}},
        dash   : {type: 'f' , value: 1},
        gap    : {type: 'f' , value: 3},
        color  : {value: color3(color)},
}

let material = new THREE.ShaderMaterial({
    uniforms       : uniforms,
    vertexShader   : vshader,
    fragmentShader : fshader,
})

Note, you have to set the canvas uniform whenever the canvas size changes:

material.uniforms.canvas.value.x = canvas.width
material.uniforms.canvas.value.y = canvas.height
makc commented 3 years ago

@capr oh great, there is flat qualifier in glsl now - is it webgl2 only?

makc commented 3 years ago

ps I think SO version will only work within a single segment. default box edges are fine, but as soon as you have a subdivision or curves, you will have a problem 🙄

Jeremboo commented 3 years ago

Thanks for your solution @capr! I'm sure that will be helpful for other users. I close this issue then since a solution as been provided.