LineMaterial.js 14 KB

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