LineMaterial.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. import {
  2. ShaderLib,
  3. ShaderMaterial,
  4. UniformsLib,
  5. UniformsUtils,
  6. Vector2,
  7. } from 'three';
  8. UniformsLib.line = {
  9. worldUnits: { value: 1 },
  10. linewidth: { value: 1 },
  11. resolution: { value: new Vector2( 1, 1 ) },
  12. dashOffset: { value: 0 },
  13. dashScale: { value: 1 },
  14. dashSize: { value: 1 },
  15. gapSize: { value: 1 } // todo FIX - maybe change to totalSize
  16. };
  17. ShaderLib[ 'line' ] = {
  18. uniforms: UniformsUtils.merge( [
  19. UniformsLib.common,
  20. UniformsLib.fog,
  21. UniformsLib.line
  22. ] ),
  23. vertexShader:
  24. /* glsl */`
  25. #include <common>
  26. #include <color_pars_vertex>
  27. #include <fog_pars_vertex>
  28. #include <logdepthbuf_pars_vertex>
  29. #include <clipping_planes_pars_vertex>
  30. uniform float linewidth;
  31. uniform vec2 resolution;
  32. attribute vec3 instanceStart;
  33. attribute vec3 instanceEnd;
  34. attribute vec3 instanceColorStart;
  35. attribute vec3 instanceColorEnd;
  36. #ifdef WORLD_UNITS
  37. varying vec4 worldPos;
  38. varying vec3 worldStart;
  39. varying vec3 worldEnd;
  40. #ifdef USE_DASH
  41. varying vec2 vUv;
  42. #endif
  43. #else
  44. varying vec2 vUv;
  45. #endif
  46. #ifdef USE_DASH
  47. uniform float dashScale;
  48. attribute float instanceDistanceStart;
  49. attribute float instanceDistanceEnd;
  50. varying float vLineDistance;
  51. #endif
  52. void trimSegment( const in vec4 start, inout vec4 end ) {
  53. // trim end segment so it terminates between the camera plane and the near plane
  54. // conservative estimate of the near plane
  55. float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
  56. float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
  57. float nearEstimate = - 0.5 * b / a;
  58. float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
  59. end.xyz = mix( start.xyz, end.xyz, alpha );
  60. }
  61. void main() {
  62. #ifdef USE_COLOR
  63. vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
  64. #endif
  65. #ifdef USE_DASH
  66. vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
  67. vUv = uv;
  68. #endif
  69. float aspect = resolution.x / resolution.y;
  70. // camera space
  71. vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
  72. vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
  73. #ifdef WORLD_UNITS
  74. worldStart = start.xyz;
  75. worldEnd = end.xyz;
  76. #else
  77. vUv = uv;
  78. #endif
  79. // special case for perspective projection, and segments that terminate either in, or behind, the camera plane
  80. // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
  81. // but we need to perform ndc-space calculations in the shader, so we must address this issue directly
  82. // perhaps there is a more elegant solution -- WestLangley
  83. bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
  84. if ( perspective ) {
  85. if ( start.z < 0.0 && end.z >= 0.0 ) {
  86. trimSegment( start, end );
  87. } else if ( end.z < 0.0 && start.z >= 0.0 ) {
  88. trimSegment( end, start );
  89. }
  90. }
  91. // clip space
  92. vec4 clipStart = projectionMatrix * start;
  93. vec4 clipEnd = projectionMatrix * end;
  94. // ndc space
  95. vec3 ndcStart = clipStart.xyz / clipStart.w;
  96. vec3 ndcEnd = clipEnd.xyz / clipEnd.w;
  97. // direction
  98. vec2 dir = ndcEnd.xy - ndcStart.xy;
  99. // account for clip-space aspect ratio
  100. dir.x *= aspect;
  101. dir = normalize( dir );
  102. #ifdef WORLD_UNITS
  103. vec3 worldDir = normalize( end.xyz - start.xyz );
  104. vec3 tmpFwd = normalize( mix( start.xyz, end.xyz, 0.5 ) );
  105. vec3 worldUp = normalize( cross( worldDir, tmpFwd ) );
  106. vec3 worldFwd = cross( worldDir, worldUp );
  107. worldPos = position.y < 0.5 ? start: end;
  108. // height offset
  109. float hw = linewidth * 0.5;
  110. worldPos.xyz += position.x < 0.0 ? hw * worldUp : - hw * worldUp;
  111. // don't extend the line if we're rendering dashes because we
  112. // won't be rendering the endcaps
  113. #ifndef USE_DASH
  114. // cap extension
  115. worldPos.xyz += position.y < 0.5 ? - hw * worldDir : hw * worldDir;
  116. // add width to the box
  117. worldPos.xyz += worldFwd * hw;
  118. // endcaps
  119. if ( position.y > 1.0 || position.y < 0.0 ) {
  120. worldPos.xyz -= worldFwd * 2.0 * hw;
  121. }
  122. #endif
  123. // project the worldpos
  124. vec4 clip = projectionMatrix * worldPos;
  125. // shift the depth of the projected points so the line
  126. // segments overlap neatly
  127. vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd;
  128. clip.z = clipPose.z * clip.w;
  129. #else
  130. vec2 offset = vec2( dir.y, - dir.x );
  131. // undo aspect ratio adjustment
  132. dir.x /= aspect;
  133. offset.x /= aspect;
  134. // sign flip
  135. if ( position.x < 0.0 ) offset *= - 1.0;
  136. // endcaps
  137. if ( position.y < 0.0 ) {
  138. offset += - dir;
  139. } else if ( position.y > 1.0 ) {
  140. offset += dir;
  141. }
  142. // adjust for linewidth
  143. offset *= linewidth;
  144. // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
  145. offset /= resolution.y;
  146. // select end
  147. vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
  148. // back to clip space
  149. offset *= clip.w;
  150. clip.xy += offset;
  151. #endif
  152. gl_Position = clip;
  153. vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
  154. #include <logdepthbuf_vertex>
  155. #include <clipping_planes_vertex>
  156. #include <fog_vertex>
  157. }
  158. `,
  159. fragmentShader:
  160. /* glsl */`
  161. uniform vec3 diffuse;
  162. uniform float opacity;
  163. uniform float linewidth;
  164. #ifdef USE_DASH
  165. uniform float dashOffset;
  166. uniform float dashSize;
  167. uniform float gapSize;
  168. #endif
  169. varying float vLineDistance;
  170. #ifdef WORLD_UNITS
  171. varying vec4 worldPos;
  172. varying vec3 worldStart;
  173. varying vec3 worldEnd;
  174. #ifdef USE_DASH
  175. varying vec2 vUv;
  176. #endif
  177. #else
  178. varying vec2 vUv;
  179. #endif
  180. #include <common>
  181. #include <color_pars_fragment>
  182. #include <fog_pars_fragment>
  183. #include <logdepthbuf_pars_fragment>
  184. #include <clipping_planes_pars_fragment>
  185. vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) {
  186. float mua;
  187. float mub;
  188. vec3 p13 = p1 - p3;
  189. vec3 p43 = p4 - p3;
  190. vec3 p21 = p2 - p1;
  191. float d1343 = dot( p13, p43 );
  192. float d4321 = dot( p43, p21 );
  193. float d1321 = dot( p13, p21 );
  194. float d4343 = dot( p43, p43 );
  195. float d2121 = dot( p21, p21 );
  196. float denom = d2121 * d4343 - d4321 * d4321;
  197. float numer = d1343 * d4321 - d1321 * d4343;
  198. mua = numer / denom;
  199. mua = clamp( mua, 0.0, 1.0 );
  200. mub = ( d1343 + d4321 * ( mua ) ) / d4343;
  201. mub = clamp( mub, 0.0, 1.0 );
  202. return vec2( mua, mub );
  203. }
  204. void main() {
  205. #include <clipping_planes_fragment>
  206. #ifdef USE_DASH
  207. if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
  208. if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
  209. #endif
  210. float alpha = opacity;
  211. #ifdef WORLD_UNITS
  212. // Find the closest points on the view ray and the line segment
  213. vec3 rayEnd = normalize( worldPos.xyz ) * 1e5;
  214. vec3 lineDir = worldEnd - worldStart;
  215. vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd );
  216. vec3 p1 = worldStart + lineDir * params.x;
  217. vec3 p2 = rayEnd * params.y;
  218. vec3 delta = p1 - p2;
  219. float len = length( delta );
  220. float norm = len / linewidth;
  221. #ifndef USE_DASH
  222. #ifdef USE_ALPHA_TO_COVERAGE
  223. float dnorm = fwidth( norm );
  224. alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm );
  225. #else
  226. if ( norm > 0.5 ) {
  227. discard;
  228. }
  229. #endif
  230. #endif
  231. #else
  232. #ifdef USE_ALPHA_TO_COVERAGE
  233. // artifacts appear on some hardware if a derivative is taken within a conditional
  234. float a = vUv.x;
  235. float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
  236. float len2 = a * a + b * b;
  237. float dlen = fwidth( len2 );
  238. if ( abs( vUv.y ) > 1.0 ) {
  239. alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 );
  240. }
  241. #else
  242. if ( abs( vUv.y ) > 1.0 ) {
  243. float a = vUv.x;
  244. float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
  245. float len2 = a * a + b * b;
  246. if ( len2 > 1.0 ) discard;
  247. }
  248. #endif
  249. #endif
  250. vec4 diffuseColor = vec4( diffuse, alpha );
  251. #include <logdepthbuf_fragment>
  252. #include <color_fragment>
  253. gl_FragColor = vec4( diffuseColor.rgb, alpha );
  254. #include <tonemapping_fragment>
  255. #include <colorspace_fragment>
  256. #include <fog_fragment>
  257. #include <premultiplied_alpha_fragment>
  258. }
  259. `
  260. };
  261. /**
  262. * A material for drawing wireframe-style geometries.
  263. *
  264. * Unlike {@link LineBasicMaterial}, it supports arbitrary line widths and allows using world units
  265. * instead of screen space units. This material is used with {@link LineSegments2} and {@link Line2}.
  266. *
  267. * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer},
  268. * use {@link Line2NodeMaterial}.
  269. *
  270. * @augments ShaderMaterial
  271. * @three_import import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
  272. */
  273. class LineMaterial extends ShaderMaterial {
  274. /**
  275. * Constructs a new line segments geometry.
  276. *
  277. * @param {Object} [parameters] - An object with one or more properties
  278. * defining the material's appearance. Any property of the material
  279. * (including any property from inherited materials) can be passed
  280. * in here. Color values can be passed any type of value accepted
  281. * by {@link Color#set}.
  282. */
  283. constructor( parameters ) {
  284. super( {
  285. type: 'LineMaterial',
  286. uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ),
  287. vertexShader: ShaderLib[ 'line' ].vertexShader,
  288. fragmentShader: ShaderLib[ 'line' ].fragmentShader,
  289. clipping: true // required for clipping support
  290. } );
  291. /**
  292. * This flag can be used for type testing.
  293. *
  294. * @type {boolean}
  295. * @readonly
  296. * @default true
  297. */
  298. this.isLineMaterial = true;
  299. this.setValues( parameters );
  300. }
  301. /**
  302. * The material's color.
  303. *
  304. * @type {Color}
  305. * @default (1,1,1)
  306. */
  307. get color() {
  308. return this.uniforms.diffuse.value;
  309. }
  310. set color( value ) {
  311. this.uniforms.diffuse.value = value;
  312. }
  313. /**
  314. * Whether the material's sizes (width, dash gaps) are in world units.
  315. *
  316. * @type {boolean}
  317. * @default false
  318. */
  319. get worldUnits() {
  320. return 'WORLD_UNITS' in this.defines;
  321. }
  322. set worldUnits( value ) {
  323. if ( value === true ) {
  324. this.defines.WORLD_UNITS = '';
  325. } else {
  326. delete this.defines.WORLD_UNITS;
  327. }
  328. }
  329. /**
  330. * Controls line thickness in CSS pixel units when `worldUnits` is `false` (default),
  331. * or in world units when `worldUnits` is `true`.
  332. *
  333. * @type {number}
  334. * @default 1
  335. */
  336. get linewidth() {
  337. return this.uniforms.linewidth.value;
  338. }
  339. set linewidth( value ) {
  340. if ( ! this.uniforms.linewidth ) return;
  341. this.uniforms.linewidth.value = value;
  342. }
  343. /**
  344. * Whether the line is dashed, or solid.
  345. *
  346. * @type {boolean}
  347. * @default false
  348. */
  349. get dashed() {
  350. return 'USE_DASH' in this.defines;
  351. }
  352. set dashed( value ) {
  353. if ( ( value === true ) !== this.dashed ) {
  354. this.needsUpdate = true;
  355. }
  356. if ( value === true ) {
  357. this.defines.USE_DASH = '';
  358. } else {
  359. delete this.defines.USE_DASH;
  360. }
  361. }
  362. /**
  363. * The scale of the dashes and gaps.
  364. *
  365. * @type {number}
  366. * @default 1
  367. */
  368. get dashScale() {
  369. return this.uniforms.dashScale.value;
  370. }
  371. set dashScale( value ) {
  372. this.uniforms.dashScale.value = value;
  373. }
  374. /**
  375. * The size of the dash.
  376. *
  377. * @type {number}
  378. * @default 1
  379. */
  380. get dashSize() {
  381. return this.uniforms.dashSize.value;
  382. }
  383. set dashSize( value ) {
  384. this.uniforms.dashSize.value = value;
  385. }
  386. /**
  387. * Where in the dash cycle the dash starts.
  388. *
  389. * @type {number}
  390. * @default 0
  391. */
  392. get dashOffset() {
  393. return this.uniforms.dashOffset.value;
  394. }
  395. set dashOffset( value ) {
  396. this.uniforms.dashOffset.value = value;
  397. }
  398. /**
  399. * The size of the gap.
  400. *
  401. * @type {number}
  402. * @default 0
  403. */
  404. get gapSize() {
  405. return this.uniforms.gapSize.value;
  406. }
  407. set gapSize( value ) {
  408. this.uniforms.gapSize.value = value;
  409. }
  410. /**
  411. * The opacity.
  412. *
  413. * @type {number}
  414. * @default 1
  415. */
  416. get opacity() {
  417. return this.uniforms.opacity.value;
  418. }
  419. set opacity( value ) {
  420. if ( ! this.uniforms ) return;
  421. this.uniforms.opacity.value = value;
  422. }
  423. /**
  424. * The size of the viewport, in screen pixels. This must be kept updated to make
  425. * screen-space rendering accurate.The `LineSegments2.onBeforeRender` callback
  426. * performs the update for visible objects.
  427. *
  428. * @type {Vector2}
  429. */
  430. get resolution() {
  431. return this.uniforms.resolution.value;
  432. }
  433. set resolution( value ) {
  434. this.uniforms.resolution.value.copy( value );
  435. }
  436. /**
  437. * Whether to use alphaToCoverage or not. When enabled, this can improve the
  438. * anti-aliasing of line edges when using MSAA.
  439. *
  440. * @type {boolean}
  441. */
  442. get alphaToCoverage() {
  443. return 'USE_ALPHA_TO_COVERAGE' in this.defines;
  444. }
  445. set alphaToCoverage( value ) {
  446. if ( ! this.defines ) return;
  447. if ( ( value === true ) !== this.alphaToCoverage ) {
  448. this.needsUpdate = true;
  449. }
  450. if ( value === true ) {
  451. this.defines.USE_ALPHA_TO_COVERAGE = '';
  452. } else {
  453. delete this.defines.USE_ALPHA_TO_COVERAGE;
  454. }
  455. }
  456. }
  457. export { LineMaterial };