Screen-space projected dashed lines
capr opened this issue · 10 comments
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!
Just tried square grid - it is not uniform at all and super trippy :D
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
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.
if you do not mind the uniformity of dash length breaking at random points, you can limit that effect significantly
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
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 🙄