import { ShaderLib, ShaderMaterial, UniformsLib, UniformsUtils, Vector2 } from '../build/three.module.js'; /** * parameters = { * color: , * lineWidth: , * dashed: , * dashScale: , * dashSize: , * dashOffset: , * gapSize: , * resolution: , // to be set by renderer * } */ UniformsLib.line = { lineWidth: { value: 1 }, resolution: { value: new Vector2( 1, 1 ) }, viewportOffset: { value: new Vector2(0, 0 ) }, //left, top dashScale: { value: 1 }, dashSize: { value: 1 }, dashOffset: { value: 0 }, gapSize: { value: 1 }, // todo FIX - maybe change to totalSize opacity: { value: 1 }, depthTexture:{ value: null }, nearPlane:{value: 0.1}, farPlane:{value: 100000}, }; ShaderLib[ 'line' ] = { uniforms: UniformsUtils.merge( [ UniformsLib.common, UniformsLib.fog, UniformsLib.line ] ), vertexShader: ` #include #include #include #include #include uniform float lineWidth; uniform vec2 resolution; attribute vec3 instanceStart; attribute vec3 instanceEnd; attribute vec3 instanceColorStart; attribute vec3 instanceColorEnd; varying vec2 vUv; #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; #endif float aspect = resolution.x / resolution.y; vUv = uv; // camera space vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); // 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 vec2 ndcStart = clipStart.xy / clipStart.w; vec2 ndcEnd = clipEnd.xy / clipEnd.w; // direction vec2 dir = ndcEnd - ndcStart; // account for clip-space aspect ratio dir.x *= aspect; dir = normalize( dir ); // perpendicular to dir 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; gl_Position = clip; vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation #include #include #include } `, fragmentShader: ` uniform vec3 diffuse; uniform float opacity; #ifdef USE_DASH uniform float dashSize; uniform float dashOffset; 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; #include #include #include #include #include varying vec2 vUv; #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 void main() { #include #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 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; } vec4 diffuseColor = vec4( diffuse, opacity ); //加 #if defined(GL_EXT_frag_depth) && defined(useDepth) float mixFactor = 0.0; float clipFactor = 0.0; float fragDepth = convertToLinear(gl_FragCoord.z); vec2 depthTxtCoords = vec2(gl_FragCoord.x - viewportOffset.x, gl_FragCoord.y) / resolution; float textureDepth = convertToLinear(texture2D(depthTexture, depthTxtCoords).r); float delta = textureDepth - fragDepth; if (delta < 0.0) { float occlusionDistance = - 1.0; float clipDistance = - 4.0; mixFactor = clamp(delta / occlusionDistance, 0.0, 1.0); clipFactor = clamp(delta / clipDistance, 0.0, 1.0); } if (clipFactor == 1.0) { discard; } vec4 backColor = vec4(0.8,0.8,0.8, 0.8*opacity); #ifdef DASH_with_depth // 只在被遮住的部分显示虚线 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, diffuseColor.a ); #include #include #include #include } ` }; var LineMaterial = function ( parameters ) { ShaderMaterial.call( this, { type: 'LineMaterial', uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), vertexShader: ShaderLib[ 'line' ].vertexShader, fragmentShader: ShaderLib[ 'line' ].fragmentShader, clipping: true // required for clipping support } ); this.dashed = false; this.lineWidth_ = 0 Object.defineProperties( this, { dashed:{//add enumerable: true, get: function () { return 'USE_DASH' in this.defines }, set: function ( value ) { if(value){ this.defines.USE_DASH = '' }else{ delete this.defines.USE_DASH } this.needsUpdate = true } }, useDepth:{//add enumerable: true, get: function () { return 'useDepth' in this.defines }, set: function ( value ) { 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 ) { if(value != this.dashWithDepth){ if(value){ this.defines.DASH_with_depth = '' }else{ delete this.defines.DASH_with_depth } this.needsUpdate = true } } }, color: { enumerable: true, get: function () { return this.uniforms.diffuse.value; }, set: function ( value ) { this.uniforms.diffuse.value = value; } }, 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 } }, 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 ); } } } ); this.setValues( parameters ); //add this.updateDepthParams() viewer.addEventListener('camera_changed', (e)=>{ if(e.viewport.name != 'mapViewport') this.updateDepthParams(e) }) let setSize = (e)=>{ let viewport = e.viewport let viewportOffset = viewport.offset || new Vector2() this.uniforms.resolution.value.copy(viewport.resolution2) this.uniforms.viewportOffset.value.copy(viewportOffset) this.lineWidth = this.lineWidth_ } let viewport = viewer.mainViewport; setSize({viewport}) viewer.addEventListener('resize',(e)=>{ if(!e.viewport || e.viewport.name != 'mapViewport'){ setSize(e) //console.log(this.name + viewportOffset.toArray()) } }) /* viewer.addEventListener("render.begin", (e)=>{//before render 如果有大于两个viewport的话可能要 if(e.viewport.name != 'mapViewport') this.updateDepthParams({camera:e.viewport.camera}) }) */ }; LineMaterial.prototype = Object.create( ShaderMaterial.prototype ); LineMaterial.prototype.constructor = LineMaterial; LineMaterial.prototype.isLineMaterial = true; LineMaterial.prototype.updateDepthParams = function(e={}){ if(this.useDepth){ var camera = e.camera || viewer.scene.getActiveCamera() this.uniforms.depthTexture.value = viewer.getPRenderer().rtEDL.depthTexture this.uniforms.nearPlane.value = camera.near; this.uniforms.farPlane.value = camera.far; } } export { LineMaterial };