RibbonLine.glsl.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /*
  2. All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. See https://js.arcgis.com/4.24/esri/copyright.txt for details.
  4. */
  5. import{unwrapOr as e}from"../core/maybe.js";import{ShaderOutput as t}from"../views/3d/webgl-engine/core/shaderLibrary/ShaderOutputOptions.js";import{SliceDraw as i}from"../views/3d/webgl-engine/core/shaderLibrary/Slice.glsl.js";import{RibbonVertexPosition as o}from"../views/3d/webgl-engine/core/shaderLibrary/attributes/RibbonVertexPosition.glsl.js";import{OutputDepth as n}from"../views/3d/webgl-engine/core/shaderLibrary/output/OutputDepth.glsl.js";import{LineStipple as r}from"../views/3d/webgl-engine/core/shaderLibrary/shading/LineStipple.glsl.js";import{multipassTerrainTest as a}from"../views/3d/webgl-engine/core/shaderLibrary/shading/MultipassTerrainTest.glsl.js";import{PiUtils as s}from"../views/3d/webgl-engine/core/shaderLibrary/shading/PiUtils.glsl.js";import{symbolAlphaCutoff as l}from"../views/3d/webgl-engine/core/shaderLibrary/util/AlphaCutoff.js";import{ColorConversion as p}from"../views/3d/webgl-engine/core/shaderLibrary/util/ColorConversion.glsl.js";import{addProjViewLocalOrigin as d}from"../views/3d/webgl-engine/core/shaderLibrary/util/View.glsl.js";import{Float2PassUniform as c}from"../views/3d/webgl-engine/core/shaderModules/Float2PassUniform.js";import{Float4PassUniform as g}from"../views/3d/webgl-engine/core/shaderModules/Float4PassUniform.js";import{FloatPassUniform as m}from"../views/3d/webgl-engine/core/shaderModules/FloatPassUniform.js";import{glsl as v}from"../views/3d/webgl-engine/core/shaderModules/interfaces.js";import{Matrix4PassUniform as f}from"../views/3d/webgl-engine/core/shaderModules/Matrix4PassUniform.js";import{ShaderBuilder as h}from"../views/3d/webgl-engine/core/shaderModules/ShaderBuilder.js";import{TransparencyPassType as u}from"../views/3d/webgl-engine/lib/basicInterfaces.js";import{VertexAttribute as D}from"../views/3d/webgl-engine/lib/VertexAttribute.js";import{CapType as S}from"../views/3d/webgl-engine/shaders/RibbonLineTechniqueConfiguration.js";const x=1;function b(b){const L=new h,w=b.hasMultipassTerrain&&(b.output===t.Color||b.output===t.Alpha);L.include(s),L.include(o,b),L.include(r,b),b.output===t.Depth&&L.include(n,b),d(L,b);const{vertex:y,fragment:C}=L;y.uniforms.add([new f("inverseProjectionMatrix",((e,t)=>t.camera.inverseProjectionMatrix)),new c("nearFar",((e,t)=>t.camera.nearFar)),new m("miterLimit",(e=>"miter"!==e.join?0:e.miterLimit)),new g("viewport",((e,t)=>t.camera.fullViewport))]),y.constants.add("LARGE_HALF_FLOAT","float",65500),L.attributes.add(D.POSITION,"vec3"),L.attributes.add(D.SUBDIVISIONFACTOR,"float"),L.attributes.add(D.UV0,"vec2"),L.attributes.add(D.AUXPOS1,"vec3"),L.attributes.add(D.AUXPOS2,"vec3"),L.varyings.add("vColor","vec4"),L.varyings.add("vpos","vec3"),L.varyings.add("linearDepth","float"),w&&L.varyings.add("depth","float");const j=b.capType===S.ROUND,R=b.stippleEnabled&&b.stippleScaleWithLineWidth||j;R&&L.varyings.add("vLineWidth","float");const A=b.stippleEnabled&&b.stippleScaleWithLineWidth;A&&L.varyings.add("vLineSizeInv","float");const F=b.innerColorEnabled||j;F&&L.varyings.add("vLineDistance","float");const P=b.stippleEnabled&&j,E=b.falloffEnabled||P;E&&L.varyings.add("vLineDistanceNorm","float"),j&&(L.varyings.add("vSegmentSDF","float"),L.varyings.add("vReverseSegmentSDF","float")),y.code.add(v`#define PERPENDICULAR(v) vec2(v.y, -v.x);
  6. float interp(float ncp, vec4 a, vec4 b) {
  7. return (-ncp - a.z) / (b.z - a.z);
  8. }
  9. vec2 rotate(vec2 v, float a) {
  10. float s = sin(a);
  11. float c = cos(a);
  12. mat2 m = mat2(c, -s, s, c);
  13. return m * v;
  14. }`),y.code.add(v`vec4 projectAndScale(vec4 pos) {
  15. vec4 posNdc = proj * pos;
  16. posNdc.xy *= viewport.zw / posNdc.w;
  17. return posNdc;
  18. }`),y.code.add(v`
  19. void clipAndTransform(inout vec4 pos, inout vec4 prev, inout vec4 next, in bool isStartVertex) {
  20. float vnp = nearFar[0] * 0.99;
  21. if(pos.z > -nearFar[0]) {
  22. //current pos behind ncp --> we need to clip
  23. if (!isStartVertex) {
  24. if(prev.z < -nearFar[0]) {
  25. //previous in front of ncp
  26. pos = mix(prev, pos, interp(vnp, prev, pos));
  27. next = pos;
  28. } else {
  29. pos = vec4(0.0, 0.0, 0.0, 1.0);
  30. }
  31. } else {
  32. if(next.z < -nearFar[0]) {
  33. //next in front of ncp
  34. pos = mix(pos, next, interp(vnp, pos, next));
  35. prev = pos;
  36. } else {
  37. pos = vec4(0.0, 0.0, 0.0, 1.0);
  38. }
  39. }
  40. } else {
  41. //current position visible
  42. if (prev.z > -nearFar[0]) {
  43. //previous behind ncp
  44. prev = mix(pos, prev, interp(vnp, pos, prev));
  45. }
  46. if (next.z > -nearFar[0]) {
  47. //next behind ncp
  48. next = mix(next, pos, interp(vnp, next, pos));
  49. }
  50. }
  51. ${w?"depth = pos.z;":""}
  52. linearDepth = (-pos.z - nearFar[0]) / (nearFar[1] - nearFar[0]);
  53. pos = projectAndScale(pos);
  54. next = projectAndScale(next);
  55. prev = projectAndScale(prev);
  56. }
  57. `),y.uniforms.add(new m("pixelRatio",((e,t)=>t.camera.pixelRatio))),y.code.add(v`
  58. void main(void) {
  59. // unpack values from uv0.y
  60. bool isStartVertex = abs(abs(uv0.y)-3.0) == 1.0;
  61. float coverage = 1.0;
  62. // Check for special value of uv0.y which is used by the Renderer when graphics
  63. // are removed before the VBO is recompacted. If this is the case, then we just
  64. // project outside of clip space.
  65. if (uv0.y == 0.0) {
  66. // Project out of clip space
  67. gl_Position = vec4(1e038, 1e038, 1e038, 1.0);
  68. }
  69. else {
  70. bool isJoin = abs(uv0.y) < 3.0;
  71. float lineSize = getSize();
  72. float lineWidth = lineSize * pixelRatio;
  73. ${R?v`vLineWidth = lineWidth;`:""}
  74. ${A?v`vLineSizeInv = 1.0 / lineSize;`:""}
  75. // convert sub-pixel coverage to alpha
  76. if (lineWidth < 1.0) {
  77. coverage = lineWidth;
  78. lineWidth = 1.0;
  79. }else{
  80. // Ribbon lines cannot properly render non-integer sizes. Round width to integer size if
  81. // larger than one for better quality. Note that we do render < 1 pixels more or less correctly
  82. // so we only really care to round anything larger than 1.
  83. lineWidth = floor(lineWidth + 0.5);
  84. }
  85. vec4 pos = view * vec4(position.xyz, 1.0);
  86. vec4 prev = view * vec4(auxpos1.xyz, 1.0);
  87. vec4 next = view * vec4(auxpos2.xyz, 1.0);
  88. clipAndTransform(pos, prev, next, isStartVertex);
  89. vec2 left = (pos.xy - prev.xy);
  90. vec2 right = (next.xy - pos.xy);
  91. float leftLen = length(left);
  92. float rightLen = length(right);
  93. `);(b.stippleEnabled||j)&&y.code.add(v`
  94. float isEndVertex = float(!isStartVertex);
  95. vec2 segmentOrigin = mix(pos.xy, prev.xy, isEndVertex);
  96. vec2 segment = mix(right, left, isEndVertex);
  97. ${j?v`vec2 segmentEnd = mix(next.xy, pos.xy, isEndVertex);`:""}
  98. `),y.code.add(v`left = (leftLen > 0.001) ? left/leftLen : vec2(0.0, 0.0);
  99. right = (rightLen > 0.001) ? right/rightLen : vec2(0.0, 0.0);
  100. vec2 capDisplacementDir = vec2(0, 0);
  101. vec2 joinDisplacementDir = vec2(0, 0);
  102. float displacementLen = lineWidth;
  103. if (isJoin) {
  104. bool isOutside = (left.x * right.y - left.y * right.x) * uv0.y > 0.0;
  105. joinDisplacementDir = normalize(left + right);
  106. joinDisplacementDir = PERPENDICULAR(joinDisplacementDir);
  107. if (leftLen > 0.001 && rightLen > 0.001) {
  108. float nDotSeg = dot(joinDisplacementDir, left);
  109. displacementLen /= length(nDotSeg * left - joinDisplacementDir);
  110. if (!isOutside) {
  111. displacementLen = min(displacementLen, min(leftLen, rightLen)/abs(nDotSeg));
  112. }
  113. }
  114. if (isOutside && (displacementLen > miterLimit * lineWidth)) {`),b.roundJoins?y.code.add(v`
  115. vec2 startDir = leftLen < 0.001 ? right : left;
  116. startDir = PERPENDICULAR(startDir);
  117. vec2 endDir = rightLen < 0.001 ? left : right;
  118. endDir = PERPENDICULAR(endDir);
  119. float factor = ${b.stippleEnabled?v`min(1.0, subdivisionFactor * ${v.float((x+2)/(x+1))})`:v`subdivisionFactor`};
  120. float rotationAngle = acos(clamp(dot(startDir, endDir), -1.0, 1.0));
  121. joinDisplacementDir = rotate(startDir, -sign(uv0.y) * factor * rotationAngle);
  122. `):y.code.add(v`if (leftLen < 0.001) {
  123. joinDisplacementDir = right;
  124. }
  125. else if (rightLen < 0.001) {
  126. joinDisplacementDir = left;
  127. }
  128. else {
  129. joinDisplacementDir = (isStartVertex || subdivisionFactor > 0.0) ? right : left;
  130. }
  131. joinDisplacementDir = PERPENDICULAR(joinDisplacementDir);`);const T=b.capType!==S.BUTT;return y.code.add(v`
  132. displacementLen = lineWidth;
  133. }
  134. } else {
  135. // CAP handling ---------------------------------------------------
  136. joinDisplacementDir = isStartVertex ? right : left;
  137. joinDisplacementDir = PERPENDICULAR(joinDisplacementDir);
  138. ${T?v`capDisplacementDir = isStartVertex ? -right : left;`:""}
  139. }
  140. `),y.code.add(v`
  141. // Displacement (in pixels) caused by join/or cap
  142. vec2 dpos = joinDisplacementDir * sign(uv0.y) * displacementLen + capDisplacementDir * displacementLen;
  143. ${E||F?v`float lineDistNorm = sign(uv0.y) * pos.w;`:""}
  144. ${F?v`vLineDistance = lineWidth * lineDistNorm;`:""}
  145. ${E?v`vLineDistanceNorm = lineDistNorm;`:""}
  146. pos.xy += dpos;
  147. `),j&&y.code.add(v`vec2 segmentDir = normalize(segment);
  148. vSegmentSDF = (isJoin && isStartVertex) ? LARGE_HALF_FLOAT : (dot(pos.xy - segmentOrigin, segmentDir) * pos.w) ;
  149. vReverseSegmentSDF = (isJoin && !isStartVertex) ? LARGE_HALF_FLOAT : (dot(pos.xy - segmentEnd, -segmentDir) * pos.w);`),b.stippleEnabled&&(b.draped?y.uniforms.add(new m("worldToScreenRatio",((e,t)=>1/t.screenToPCSRatio))):y.code.add(v`vec3 segmentCenter = mix((auxpos2 + position) * 0.5, (position + auxpos1) * 0.5, isEndVertex);
  150. float worldToScreenRatio = computeWorldToScreenRatio(segmentCenter);`),y.code.add(v`float segmentLengthScreenDouble = length(segment);
  151. float segmentLengthScreen = segmentLengthScreenDouble * 0.5;
  152. float discreteWorldToScreenRatio = discretizeWorldToScreenRatio(worldToScreenRatio);
  153. float segmentLengthRender = length(mix(auxpos2 - position, position - auxpos1, isEndVertex));
  154. vStipplePatternStretch = worldToScreenRatio / discreteWorldToScreenRatio;`),b.draped?y.code.add(v`float segmentLengthPseudoScreen = segmentLengthScreen / pixelRatio * discreteWorldToScreenRatio / worldToScreenRatio;
  155. float startPseudoScreen = uv0.x * discreteWorldToScreenRatio - mix(0.0, segmentLengthPseudoScreen, isEndVertex);`):y.code.add(v`float startPseudoScreen = mix(uv0.x, uv0.x - segmentLengthRender, isEndVertex) * discreteWorldToScreenRatio;
  156. float segmentLengthPseudoScreen = segmentLengthRender * discreteWorldToScreenRatio;`),y.code.add(v`
  157. float patternLength = ${b.stippleScaleWithLineWidth?"lineSize * ":""} stipplePatternPixelSize;
  158. // Compute the coordinates at both start and end of the line segment, because we need both to clamp to in the fragment shader
  159. vStippleDistanceLimits = computeStippleDistanceLimits(startPseudoScreen, segmentLengthPseudoScreen, segmentLengthScreen, patternLength);
  160. vStippleDistance = mix(vStippleDistanceLimits.x, vStippleDistanceLimits.y, isEndVertex);
  161. // Adjust the coordinate to the displaced position (the pattern is shortened/overextended on the in/outside of joins)
  162. if (segmentLengthScreenDouble >= 0.001) {
  163. // Project the actual vertex position onto the line segment. Note that the resulting factor is within [0..1] at the
  164. // original vertex positions, and slightly outside of that range at the displaced positions
  165. vec2 stippleDisplacement = pos.xy - segmentOrigin;
  166. float stippleDisplacementFactor = dot(segment, stippleDisplacement) / (segmentLengthScreenDouble * segmentLengthScreenDouble);
  167. // Apply this offset to the actual vertex coordinate (can be screen or pseudo-screen space)
  168. vStippleDistance += (stippleDisplacementFactor - isEndVertex) * (vStippleDistanceLimits.y - vStippleDistanceLimits.x);
  169. }
  170. // Cancel out perspective correct interpolation because we want this length the really represent the screen distance
  171. vStippleDistanceLimits *= pos.w;
  172. vStippleDistance *= pos.w;
  173. // Disable stipple distance limits on caps
  174. vStippleDistanceLimits = isJoin ?
  175. vStippleDistanceLimits :
  176. isStartVertex ?
  177. vec2(-1e038, vStippleDistanceLimits.y) :
  178. vec2(vStippleDistanceLimits.x, 1e038);
  179. `)),y.code.add(v`
  180. // Convert back into NDC
  181. pos.xy = (pos.xy / viewport.zw) * pos.w;
  182. vColor = getColor();
  183. vColor.a *= coverage;
  184. ${b.wireframe&&!b.draped?"pos.z -= 0.001 * pos.w;":""}
  185. // transform final position to camera space for slicing
  186. vpos = (inverseProjectionMatrix * pos).xyz;
  187. gl_Position = pos;
  188. }
  189. }
  190. `),w&&L.include(a,b),L.include(i,b),C.include(p),C.code.add(v`
  191. void main() {
  192. discardBySlice(vpos);
  193. ${w?"terrainDepthTest(gl_FragCoord, depth);":""}
  194. `),b.wireframe?C.code.add(v`vec4 finalColor = vec4(1.0, 0.0, 1.0, 1.0);`):(j&&C.code.add(v`
  195. float sdf = min(vSegmentSDF, vReverseSegmentSDF);
  196. vec2 fragmentPosition = vec2(
  197. min(sdf, 0.0),
  198. vLineDistance
  199. ) * gl_FragCoord.w;
  200. float fragmentRadius = length(fragmentPosition);
  201. float fragmentCapSDF = (fragmentRadius - vLineWidth) * 0.5; // Divide by 2 to transform from double pixel scale
  202. float capCoverage = clamp(0.5 - fragmentCapSDF, 0.0, 1.0);
  203. if (capCoverage < ${v.float(l)}) {
  204. discard;
  205. }
  206. `),P?C.code.add(v`
  207. vec2 stipplePosition = vec2(
  208. min(getStippleSDF() * 2.0 - 1.0, 0.0),
  209. vLineDistanceNorm * gl_FragCoord.w
  210. );
  211. float stippleRadius = length(stipplePosition * vLineWidth);
  212. float stippleCapSDF = (stippleRadius - vLineWidth) * 0.5; // Divide by 2 to transform from double pixel scale
  213. float stippleCoverage = clamp(0.5 - stippleCapSDF, 0.0, 1.0);
  214. float stippleAlpha = step(${v.float(l)}, stippleCoverage);
  215. `):C.code.add(v`float stippleAlpha = getStippleAlpha();`),C.uniforms.add(new g("intrinsicColor",(e=>e.color))),C.code.add(v`discardByStippleAlpha(stippleAlpha, stippleAlphaColorDiscard);
  216. vec4 color = intrinsicColor * vColor;`),b.innerColorEnabled&&(C.uniforms.add(new g("innerColor",(t=>e(t.innerColor,t.color)))),C.uniforms.add(new m("innerWidth",((e,t)=>e.innerWidth*t.camera.pixelRatio))),C.code.add(v`float distToInner = abs(vLineDistance * gl_FragCoord.w) - innerWidth;
  217. float innerAA = clamp(0.5 - distToInner, 0.0, 1.0);
  218. float innerAlpha = innerColor.a + color.a * (1.0 - innerColor.a);
  219. color = mix(color, vec4(innerColor.rgb, innerAlpha), innerAA);`)),C.code.add(v`vec4 finalColor = blendStipple(color, stippleAlpha);`),b.falloffEnabled&&(C.uniforms.add(new m("falloff",(e=>e.falloff))),C.code.add(v`finalColor.a *= pow(max(0.0, 1.0 - abs(vLineDistanceNorm * gl_FragCoord.w)), falloff);`))),C.code.add(v`
  220. if (finalColor.a < ${v.float(l)}) {
  221. discard;
  222. }
  223. ${b.output===t.Alpha?v`gl_FragColor = vec4(finalColor.a);`:""}
  224. ${b.output===t.Color?v`gl_FragColor = highlightSlice(finalColor, vpos);`:""}
  225. ${b.output===t.Color&&b.transparencyPassType===u.Color?"gl_FragColor = premultiplyAlpha(gl_FragColor);":""}
  226. ${b.output===t.Highlight?v`gl_FragColor = vec4(1.0);`:""}
  227. ${b.output===t.Depth?v`outputDepth(linearDepth);`:""}
  228. }
  229. `),L}const L=Object.freeze(Object.defineProperty({__proto__:null,NUM_ROUND_JOIN_SUBDIVISIONS:x,build:b},Symbol.toStringTag,{value:"Module"}));export{x as N,L as R,b};