瀏覽代碼

Feature Suggestion: Add perspective size attenuation option to LineMaterial (#16912)

* Add feature to LineMaterial to support toggling screen-space line width

* Change lines parameter

* remove magic number scaling

* change if to use mix function

* build jsm

* rename variable to size attenuation, use defines

* Add perspective attenuation with width in world units

* Update comments

* Add jsm build

* revert statement position

* Change sizeAttenuation to worldUnits

* Fix endcaps not being perpendicular to line direction

* Update js examples version

* Make line look like a capsule (#6)

* update js file

* Update comments

* Add a tighter containment for capsule

* update module

* small adjustment
Garrett Johnson 3 年之前
父節點
當前提交
38ce808ae9
共有 2 個文件被更改,包括 234 次插入65 次删除
  1. 222 62
      examples/jsm/lines/LineMaterial.js
  2. 12 3
      examples/webgl_lines_fat.html

+ 222 - 62
examples/jsm/lines/LineMaterial.js

@@ -1,11 +1,3 @@
-import {
-	ShaderLib,
-	ShaderMaterial,
-	UniformsLib,
-	UniformsUtils,
-	Vector2
-} from '../../../build/three.module.js';
-
 /**
  * parameters = {
  *  color: <hex>,
@@ -13,21 +5,28 @@ import {
  *  dashed: <boolean>,
  *  dashScale: <float>,
  *  dashSize: <float>,
- *  dashOffset: <float>,
  *  gapSize: <float>,
  *  resolution: <Vector2>, // to be set by renderer
  * }
  */
 
+import {
+	ShaderLib,
+	ShaderMaterial,
+	UniformsLib,
+	UniformsUtils,
+	Vector2
+} from "../../../build/three.module.js";
+
+
 UniformsLib.line = {
 
+	worldUnits: { value: 1 },
 	linewidth: { value: 1 },
 	resolution: { value: new Vector2( 1, 1 ) },
 	dashScale: { value: 1 },
 	dashSize: { value: 1 },
-	dashOffset: { value: 0 },
-	gapSize: { value: 1 }, // todo FIX - maybe change to totalSize
-	opacity: { value: 1 }
+	gapSize: { value: 1 } // todo FIX - maybe change to totalSize
 
 };
 
@@ -39,7 +38,8 @@ ShaderLib[ 'line' ] = {
 		UniformsLib.line
 	] ),
 
-	vertexShader: /* glsl */`
+	vertexShader:
+		/* glsl */`
 		#include <common>
 		#include <color_pars_vertex>
 		#include <fog_pars_vertex>
@@ -56,6 +56,9 @@ ShaderLib[ 'line' ] = {
 		attribute vec3 instanceColorEnd;
 
 		varying vec2 vUv;
+		varying vec4 worldPos;
+		varying vec3 worldStart;
+		varying vec3 worldEnd;
 
 		#ifdef USE_DASH
 
@@ -103,6 +106,9 @@ ShaderLib[ 'line' ] = {
 			vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
 			vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
 
+			worldStart = start.xyz;
+			worldEnd = end.xyz;
+
 			// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
 			// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
 			// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
@@ -129,50 +135,108 @@ ShaderLib[ 'line' ] = {
 			vec4 clipEnd = projectionMatrix * end;
 
 			// ndc space
-			vec2 ndcStart = clipStart.xy / clipStart.w;
-			vec2 ndcEnd = clipEnd.xy / clipEnd.w;
+			vec3 ndcStart = clipStart.xyz / clipStart.w;
+			vec3 ndcEnd = clipEnd.xyz / clipEnd.w;
 
 			// direction
-			vec2 dir = ndcEnd - ndcStart;
+			vec2 dir = ndcEnd.xy - ndcStart.xy;
 
 			// account for clip-space aspect ratio
 			dir.x *= aspect;
 			dir = normalize( dir );
 
-			// perpendicular to dir
-			vec2 offset = vec2( dir.y, - dir.x );
+			#ifdef WORLD_UNITS
 
-			// undo aspect ratio adjustment
-			dir.x /= aspect;
-			offset.x /= aspect;
+				// get the offset direction as perpendicular to the view vector
+				vec3 worldDir = normalize( end.xyz - start.xyz );
+				vec3 offset;
+				if ( position.y < 0.5 ) {
 
-			// sign flip
-			if ( position.x < 0.0 ) offset *= - 1.0;
+					offset = normalize( cross( start.xyz, worldDir ) );
 
-			// endcaps
-			if ( position.y < 0.0 ) {
+				} else {
 
-				offset += - dir;
+					offset = normalize( cross( end.xyz, worldDir ) );
 
-			} else if ( position.y > 1.0 ) {
+				}
 
-				offset += dir;
+				// sign flip
+				if ( position.x < 0.0 ) offset *= - 1.0;
 
-			}
+				float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) );
+
+				// don't extend the line if we're rendering dashes because we
+				// won't be rendering the endcaps
+				#ifndef USE_DASH
 
-			// adjust for linewidth
-			offset *= linewidth;
+					// extend the line bounds to encompass  endcaps
+					start.xyz += - worldDir * linewidth * 0.5;
+					end.xyz += worldDir * linewidth * 0.5;
+
+					// shift the position of the quad so it hugs the forward edge of the line
+					offset.xy -= dir * forwardOffset;
+					offset.z += 0.5;
+
+				#endif
+
+				// endcaps
+				if ( position.y > 1.0 || position.y < 0.0 ) {
+
+					offset.xy += dir * 2.0 * forwardOffset;
+
+				}
 
-			// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
-			offset /= resolution.y;
+				// adjust for linewidth
+				offset *= linewidth * 0.5;
 
-			// select end
-			vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
+				// set the world position
+				worldPos = ( position.y < 0.5 ) ? start : end;
+				worldPos.xyz += offset;
 
-			// back to clip space
-			offset *= clip.w;
+				// project the worldpos
+				vec4 clip = projectionMatrix * worldPos;
 
-			clip.xy += offset;
+				// shift the depth of the projected points so the line
+				// segements overlap neatly
+				vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd;
+				clip.z = clipPose.z * clip.w;
+
+			#else
+
+				vec2 offset = vec2( dir.y, - dir.x );
+				// undo aspect ratio adjustment
+				dir.x /= aspect;
+				offset.x /= aspect;
+
+				// sign flip
+				if ( position.x < 0.0 ) offset *= - 1.0;
+
+				// endcaps
+				if ( position.y < 0.0 ) {
+
+					offset += - dir;
+
+				} else if ( position.y > 1.0 ) {
+
+					offset += dir;
+
+				}
+
+				// adjust for linewidth
+				offset *= linewidth;
+
+				// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
+				offset /= resolution.y;
+
+				// select end
+				vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
+
+				// back to clip space
+				offset *= clip.w;
+
+				clip.xy += offset;
+
+			#endif
 
 			gl_Position = clip;
 
@@ -182,21 +246,26 @@ ShaderLib[ 'line' ] = {
 			#include <clipping_planes_vertex>
 			#include <fog_vertex>
 
-		}`,
+		}
+		`,
 
-	fragmentShader: /* glsl */`
+	fragmentShader:
+		/* glsl */`
 		uniform vec3 diffuse;
 		uniform float opacity;
+		uniform float linewidth;
 
 		#ifdef USE_DASH
 
 			uniform float dashSize;
-			uniform float dashOffset;
 			uniform float gapSize;
 
 		#endif
 
 		varying float vLineDistance;
+		varying vec4 worldPos;
+		varying vec3 worldStart;
+		varying vec3 worldEnd;
 
 		#include <common>
 		#include <color_pars_fragment>
@@ -206,6 +275,35 @@ ShaderLib[ 'line' ] = {
 
 		varying vec2 vUv;
 
+		vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) {
+
+			float mua;
+			float mub;
+
+			vec3 p13 = p1 - p3;
+			vec3 p43 = p4 - p3;
+
+			vec3 p21 = p2 - p1;
+
+			float d1343 = dot( p13, p43 );
+			float d4321 = dot( p43, p21 );
+			float d1321 = dot( p13, p21 );
+			float d4343 = dot( p43, p43 );
+			float d2121 = dot( p21, p21 );
+
+			float denom = d2121 * d4343 - d4321 * d4321;
+
+			float numer = d1343 * d4321 - d1321 * d4343;
+
+			mua = numer / denom;
+			mua = clamp( mua, 0.0, 1.0 );
+			mub = ( d1343 + d4321 * ( mua ) ) / d4343;
+			mub = clamp( mub, 0.0, 1.0 );
+
+			return vec2( mua, mub );
+
+		}
+
 		void main() {
 
 			#include <clipping_planes_fragment>
@@ -214,54 +312,90 @@ ShaderLib[ 'line' ] = {
 
 				if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
 
-				if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
+				if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
 
 			#endif
 
-			float alpha = 1.0;
+			float alpha = opacity;
 
-			#ifdef ALPHA_TO_COVERAGE
+			#ifdef WORLD_UNITS
 
-			// artifacts appear on some hardware if a derivative is taken within a conditional
-			float a = vUv.x;
-			float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
-			float len2 = a * a + b * b;
-			float dlen = fwidth( len2 );
+				// Find the closest points on the view ray and the line segment
+				vec3 rayEnd = normalize( worldPos.xyz ) * 1e5;
+				vec3 lineDir = worldEnd - worldStart;
+				vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd );
 
-			if ( abs( vUv.y ) > 1.0 ) {
+				vec3 p1 = worldStart + lineDir * params.x;
+				vec3 p2 = rayEnd * params.y;
+				vec3 delta = p1 - p2;
+				float len = length( delta );
+				float norm = len / linewidth;
 
-				alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 );
+				#ifndef USE_DASH
 
-			}
+					#ifdef ALPHA_TO_COVERAGE
+
+						float dnorm = fwidth( norm );
+						alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm );
+
+					#else
+
+						if ( norm > 0.5 ) {
+
+							discard;
+
+						}
+
+					#endif
+
+				#endif
 
 			#else
 
-			if ( abs( vUv.y ) > 1.0 ) {
+				#ifdef ALPHA_TO_COVERAGE
 
-				float a = vUv.x;
-				float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
-				float len2 = a * a + b * b;
+					// artifacts appear on some hardware if a derivative is taken within a conditional
+					float a = vUv.x;
+					float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
+					float len2 = a * a + b * b;
+					float dlen = fwidth( len2 );
 
-				if ( len2 > 1.0 ) discard;
+					if ( abs( vUv.y ) > 1.0 ) {
 
-			}
+						alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 );
+
+					}
+
+				#else
+
+					if ( abs( vUv.y ) > 1.0 ) {
+
+						float a = vUv.x;
+						float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
+						float len2 = a * a + b * b;
+
+						if ( len2 > 1.0 ) discard;
+
+					}
+
+				#endif
 
 			#endif
 
-			vec4 diffuseColor = vec4( diffuse, opacity * alpha );
+			vec4 diffuseColor = vec4( diffuse, alpha );
 
 			#include <logdepthbuf_fragment>
 			#include <color_fragment>
 
-			gl_FragColor = diffuseColor;
+			gl_FragColor = vec4( diffuseColor.rgb, alpha );
 
 			#include <tonemapping_fragment>
 			#include <encodings_fragment>
 			#include <fog_fragment>
 			#include <premultiplied_alpha_fragment>
 
-		}`
-
+		}
+		`
 };
 
 class LineMaterial extends ShaderMaterial {
@@ -301,6 +435,32 @@ class LineMaterial extends ShaderMaterial {
 
 			},
 
+			worldUnits: {
+
+				enumerable: true,
+
+				get: function () {
+
+					return 'WORLD_UNITS' in this.defines;
+
+				},
+
+				set: function ( value ) {
+
+					if ( value === true ) {
+
+						this.defines.WORLD_UNITS = '';
+
+					} else {
+
+						delete this.defines.WORLD_UNITS;
+
+					}
+
+				}
+
+			},
+
 			linewidth: {
 
 				enumerable: true,

+ 12 - 3
examples/webgl_lines_fat.html

@@ -95,8 +95,9 @@
 				matLine = new LineMaterial( {
 
 					color: 0xffffff,
-					linewidth: 5, // in pixels
+					linewidth: 5, // in world units with size attenuation, pixels otherwise
 					vertexColors: true,
+					
 					//resolution:  // to be set by renderer, eventually
 					dashed: false,
 					alphaToCoverage: true,
@@ -205,7 +206,8 @@
 
 				const param = {
 					'line type': 0,
-					'width (px)': 5,
+					'world units': false,
+					'width': 5,
 					'alphaToCoverage': true,
 					'dashed': false,
 					'dash scale': 1,
@@ -234,7 +236,14 @@
 
 				} );
 
-				gui.add( param, 'width (px)', 1, 10 ).onChange( function ( val ) {
+				gui.add( param, 'world units' ).onChange( function ( val ) {
+
+					matLine.worldUnits = val;
+					matLine.needsUpdate = true;
+
+				} );
+
+				gui.add( param, 'width', 1, 10 ).onChange( function ( val ) {
 
 					matLine.linewidth = val;