/** * parameters = { * color: , * lineWidth: , * dashed: , * dashScale: , * dashSize: , * dashOffset: , * gapSize: , * resolution: , // to be set by renderer * } */ import { ShaderLib, ShaderMaterial, UniformsLib, UniformsUtils, Vector2, Color } from '../build/three.module.js'; UniformsLib.line = { /* worldUnits: { value: 1 }, lineWidth: { value: 1 }, resolution: { value: new Vector2( 1, 1 ) }, dashOffset: { value: 0 }, dashScale: { value: 1 }, dashSize: { value: 1 }, gapSize: { value: 1 } // todo FIX - maybe change to totalSize */ worldUnits: { value: 1 }, lineWidth: { value: 1 }, resolution: { value: new Vector2( 1, 1 ) }, viewportOffset: { value: new Vector2(0, 0 ) }, //left, top devicePixelRatio:{ value:window.devicePixelRatio}, dashScale: { value: 1 }, dashSize: { value: 1 }, dashOffset: { value: 0 }, gapSize: { value: 1 }, opacity: { value: 1 }, backColor: {type:'v3', value: new Color("#ddd")}, clipDistance : { type: 'f', value: 4}, //消失距离 occlusionDistance : { type: 'f', value: 1 }, //变为backColor距离 maxClipFactor : { type: 'f', value: 1 }, //0-1 depthTexture:{ value: null }, nearPlane:{value: 0.1}, farPlane:{value: 100000}, }; ShaderLib[ 'line' ] = { uniforms: UniformsUtils.merge( [ UniformsLib.common, UniformsLib.fog, UniformsLib.line ] ), vertexShader: /* glsl */` #include #include #include #include #include uniform float lineWidth; uniform vec2 resolution; uniform float devicePixelRatio; //add attribute vec3 instanceStart; attribute vec3 instanceEnd; attribute vec3 instanceColorStart; attribute vec3 instanceColorEnd; #ifdef WORLD_UNITS varying vec4 worldPos; varying vec3 worldStart; varying vec3 worldEnd; #ifdef USE_DASH varying vec2 vUv; #endif #else varying vec2 vUv; #endif #ifdef USE_DASH uniform float dashScale; attribute float instanceDistanceStart; attribute float instanceDistanceEnd; varying float vLineDistance; #endif void trimSegment( const in vec4 start, inout vec4 end ) { // trim end segment so it terminates between the camera plane and the near plane // conservative estimate of the near plane float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column float nearEstimate = - 0.5 * b / a; float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); end.xyz = mix( start.xyz, end.xyz, alpha ); } void main() { #ifdef USE_COLOR vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; #endif #ifdef USE_DASH vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; vUv = uv; #endif float aspect = resolution.x / resolution.y; // camera space vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); #ifdef WORLD_UNITS worldStart = start.xyz; worldEnd = end.xyz; #else vUv = uv; #endif // 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 // perhaps there is a more elegant solution -- WestLangley bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column if ( perspective ) { if ( start.z < 0.0 && end.z >= 0.0 ) { trimSegment( start, end ); } else if ( end.z < 0.0 && start.z >= 0.0 ) { trimSegment( end, start ); } } // clip space vec4 clipStart = projectionMatrix * start; vec4 clipEnd = projectionMatrix * end; // ndc space vec3 ndcStart = clipStart.xyz / clipStart.w; vec3 ndcEnd = clipEnd.xyz / clipEnd.w; // direction vec2 dir = ndcEnd.xy - ndcStart.xy; // account for clip-space aspect ratio dir.x *= aspect; dir = normalize( dir ); #ifdef WORLD_UNITS // get the offset direction as perpendicular to the view vector vec3 worldDir = normalize( end.xyz - start.xyz ); vec3 offset; if ( position.y < 0.5 ) { offset = normalize( cross( start.xyz, worldDir ) ); } else { offset = normalize( cross( end.xyz, worldDir ) ); } // 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 // 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 lineWidth offset *= lineWidth * 0.5; // set the world position worldPos = ( position.y < 0.5 ) ? start : end; worldPos.xyz += offset; // project the worldpos vec4 clip = projectionMatrix * worldPos; // shift the depth of the projected points so the line // segments 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 * devicePixelRatio; // select end vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; // back to clip space offset *= clip.w; clip.xy += offset; #endif gl_Position = clip; vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation #include #include #include } `, fragmentShader: /* glsl */` uniform vec3 diffuse; uniform float opacity; uniform float lineWidth; uniform vec3 backColor; uniform float occlusionDistance; uniform float clipDistance; uniform float maxClipFactor; #ifdef USE_DASH uniform float dashOffset; uniform float dashSize; uniform float gapSize; #endif //加 #if defined(GL_EXT_frag_depth) && defined(useDepth) uniform sampler2D depthTexture; uniform float nearPlane; uniform float farPlane; uniform vec2 resolution; uniform vec2 viewportOffset; #endif varying float vLineDistance; #ifdef WORLD_UNITS varying vec4 worldPos; varying vec3 worldStart; varying vec3 worldEnd; #ifdef USE_DASH varying vec2 vUv; #endif #else varying vec2 vUv; #endif #include #include #include #include #include #if defined(GL_EXT_frag_depth) && defined(useDepth) float convertToLinear(float zValue) { float z = zValue * 2.0 - 1.0; return (2.0 * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane)); } #endif 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 /*#ifdef USE_DASH if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX #endif*/ #ifdef USE_DASH if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps bool unvisible = mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize; //加 #ifdef DASH_with_depth #else if (unvisible) discard; // todo - FIX #endif #endif float alpha = opacity; #ifdef WORLD_UNITS // 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 ); vec3 p1 = worldStart + lineDir * params.x; vec3 p2 = rayEnd * params.y; vec3 delta = p1 - p2; float len = length( delta ); float norm = len / lineWidth; #ifndef USE_DASH #ifdef USE_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 #ifdef USE_ALPHA_TO_COVERAGE // 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 ( 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, alpha ); //加 #if defined(GL_EXT_frag_depth) && defined(useDepth) float mixFactor = 0.0; float clipFactor = 0.0; float fragDepth = convertToLinear(gl_FragCoord.z); //gl_FragCoord大小为 viewport client大小 vec2 depthTxtCoords = vec2(gl_FragCoord.x - viewportOffset.x, gl_FragCoord.y - viewportOffset.y) / resolution; float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r); float delta = fragDepth - textureDepth; if (delta > 0.0) { mixFactor = clamp(delta / occlusionDistance, 0.0, 1.0); clipFactor = clamp(delta / clipDistance, 0.0, maxClipFactor); } if (clipFactor == 1.0) { discard; } vec4 backColor_ = vec4(backColor, opacity); //vec4(0.8,0.8,0.8, 0.8*opacity); #ifdef DASH_with_depth // 只在被遮住的部分显示虚线, 所以若同时是虚线不可见部分和被遮住时, a为0 if(unvisible) backColor_.a = 0.0; #endif //vec4 diffuseColor = vec4(mix(diffuse, backColor_, mixFactor), opacity*(1.0 - clipFactor)); diffuseColor = mix(diffuseColor, backColor_ , mixFactor); diffuseColor.a *= (1.0 - clipFactor); #endif #include #include //gl_FragColor = vec4( diffuseColor.rgb, alpha ); gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a ); #include #include #include #include } ` }; class LineMaterial extends ShaderMaterial { constructor( parameters ) { super( { type: 'LineMaterial', uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), vertexShader: ShaderLib[ 'line' ].vertexShader, fragmentShader: ShaderLib[ 'line' ].fragmentShader, clipping: true // required for clipping support } ); this.isLineMaterial = true; this.lineWidth_ = 0 this.supportExtDepth = parameters.supportExtDepth this.depthTestWhenPick = false //pick时是否识别点云等 if(parameters.color){ this.color = new Color(parameters.color) } if(parameters.backColor){ this.uniforms.backColor.value = new Color(parameters.backColor) } if(parameters.clipDistance){ this.uniforms.clipDistance.value = parameters.clipDistance } if(parameters.occlusionDistance){ this.uniforms.occlusionDistance.value = parameters.occlusionDistance } if(parameters.maxClipFactor){ this.uniforms.maxClipFactor.value = parameters.maxClipFactor } Object.defineProperties( this, { color: { enumerable: true, get: function () { return this.uniforms.diffuse.value; }, set: function ( value ) { this.uniforms.diffuse.value = value; } }, 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, get: function () { return this.lineWidth_;//this.uniforms.lineWidth.value; }, set: function ( value ) { this.uniforms.lineWidth.value = value * window.devicePixelRatio; this.lineWidth_ = value } }, dashed: { enumerable: true, get: function () { return Boolean( 'USE_DASH' in this.defines ); }, set( value ) { if ( Boolean( value ) !== Boolean( 'USE_DASH' in this.defines ) ) { this.needsUpdate = true; } if ( value === true ) { this.defines.USE_DASH = ''; } else { delete this.defines.USE_DASH; } } }, dashScale: { enumerable: true, get: function () { return this.uniforms.dashScale.value; }, set: function ( value ) { this.uniforms.dashScale.value = value; } }, dashSize: { enumerable: true, get: function () { return this.uniforms.dashSize.value; }, set: function ( value ) { this.uniforms.dashSize.value = value; } }, dashOffset: { enumerable: true, get: function () { return this.uniforms.dashOffset.value; }, set: function ( value ) { this.uniforms.dashOffset.value = value; } }, gapSize: { enumerable: true, get: function () { return this.uniforms.gapSize.value; }, set: function ( value ) { this.uniforms.gapSize.value = value; } }, opacity: { enumerable: true, get: function () { return this.uniforms.opacity.value; }, set: function ( value ) { this.uniforms.opacity.value = value; } }, resolution: { enumerable: true, get: function () { return this.uniforms.resolution.value; }, set: function ( value ) { this.uniforms.resolution.value.copy( value ); } }, alphaToCoverage: { enumerable: true, get: function () { return Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ); }, set: function ( value ) { if ( Boolean( value ) !== Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ) ) { this.needsUpdate = true; } if ( value === true ) { this.defines.USE_ALPHA_TO_COVERAGE = ''; this.extensions.derivatives = true; } else { delete this.defines.USE_ALPHA_TO_COVERAGE; this.extensions.derivatives = false; } } } , useDepth:{//add enumerable: true, get: function () { return 'useDepth' in this.defines }, set: function ( value ) { value = value && !!this.supportExtDepth if(value != this.useDepth){ if(value){ this.defines.useDepth = '' this.updateDepthParams() }else{ delete this.defines.useDepth } this.needsUpdate = true } } }, dashWithDepth:{//add enumerable: true, get: function () { return 'DASH_with_depth' in this.defines }, set: function ( value ) { value = value && !!this.supportExtDepth if(value != this.dashWithDepth){ if(value){ this.defines.DASH_with_depth = '' }else{ delete this.defines.DASH_with_depth } this.needsUpdate = true } } }, } ); this.setValues( parameters ); let setSize = (e)=>{ let viewport = e.viewport let viewportOffset = viewport.offset || new Vector2() this.uniforms.resolution.value.copy(viewport.resolution) this.uniforms.viewportOffset.value.copy(viewportOffset) this.uniforms.devicePixelRatio.value = window.devicePixelRatio this.lineWidth = this.lineWidth_ } let viewport = viewer.mainViewport; setSize({viewport}) viewer.addEventListener('resize',(e)=>{ setSize(e) }) if(this.supportExtDepth){ //add this.updateDepthParams() /* viewer.addEventListener('camera_changed', (e)=>{ if(e.viewport.name != 'mapViewport') this.updateDepthParams(e) }) */ viewer.addEventListener("render.begin", (e)=>{//before render 如果有大于两个viewport的话,不同viewport用不同的depthTex if(e.viewport.camera.isPerspectiveCamera) this.updateDepthParams(e) }) } } updateDepthParams(e={}){ if(this.useDepth){ var viewport = e.viewport || viewer.mainViewport; var camera = viewport.camera; this.uniforms.depthTexture.value = viewer.getPRenderer().getRtEDL(viewport).depthTexture //viewer.getPRenderer().rtEDL.depthTexture this.uniforms.nearPlane.value = camera.near; //似乎因为这个所以对OrthographicCamera 无效 this.uniforms.farPlane.value = camera.far; } } } export { LineMaterial };