spite/THREE.MeshLine

Screen-space projected dashed lines

capr opened this issue · 10 comments

capr commented

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

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

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

makc commented

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.

makc commented

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

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

capr commented

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

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

makc commented

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 🙄

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.