Loading…

// 1. 首先添加Three.js库 document.addEventListener('DOMContentLoaded', function() { // 创建并加载Three.js脚本 var threeScript = document.createElement('script'); threeScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/0.152.2/three.min.js'; threeScript.onload = loadPostProcessing; // 当Three.js加载完成后,加载postprocessing document.head.appendChild(threeScript); // 2. 加载postprocessing库的函数 function loadPostProcessing() { var postprocessingScript = document.createElement('script'); postprocessingScript.src = 'https://cdn.jsdelivr.net/npm/postprocessing@6.32.2/build/postprocessing.min.js'; postprocessingScript.onload = initHyperspeed; // 当postprocessing加载完成后,初始化Hyperspeed document.head.appendChild(postprocessingScript); } // 3. 初始化Hyperspeed function initHyperspeed() { // Hyperspeed代码 const effectOptions = { onSpeedUp: function() { }, onSlowDown: function() { }, distortion: 'turbulentDistortion', length: 400, roadWidth: 10, islandWidth: 2, lanesPerRoad: 4, fov: 90, fovSpeedUp: 150, speedUp: 2, carLightsFade: 0.4, totalSideLightSticks: 20, lightPairsPerRoadWay: 40, shoulderLinesWidthPercentage: 0.05, brokenLinesWidthPercentage: 0.1, brokenLinesLengthPercentage: 0.5, lightStickWidth: [0.12, 0.5], lightStickHeight: [1.3, 1.7], movingAwaySpeed: [60, 80], movingCloserSpeed: [-120, -160], carLightsLength: [400 * 0.03, 400 * 0.2], carLightsRadius: [0.05, 0.14], carWidthPercentage: [0.3, 0.5], carShiftX: [-0.8, 0.8], carFloorSeparation: [0, 5], colors: { roadColor: 0x080808, islandColor: 0x0a0a0a, background: 0x000000, shoulderLines: 0xFFFFFF, brokenLines: 0xFFFFFF, leftCars: [0xD856BF, 0x6750A2, 0xC247AC], rightCars: [0x03B3C3, 0x0E5EA5, 0x324555], sticks: 0x03B3C3, } }; // 辅助函数 const random = function(base) { if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0]; return Math.random() * base; }; const pickRandom = function(arr) { if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)]; return arr; }; function lerp(current, target, speed, limit) { if (speed === undefined) speed = 0.1; if (limit === undefined) limit = 0.001; let change = (target - current) * speed; if (Math.abs(change) < limit) { change = target - current; } return change; } function resizeRendererToDisplaySize(renderer, setSize) { const canvas = renderer.domElement; const width = canvas.clientWidth; const height = canvas.clientHeight; const needResize = canvas.width !== width || canvas.height !== height; if (needResize) { setSize(width, height, false); } return needResize; } let nsin = function(val) { return Math.sin(val) * 0.5 + 0.5; }; // 扭曲效果定义 const mountainUniforms = { uFreq: { value: new THREE.Vector3(3, 6, 10) }, uAmp: { value: new THREE.Vector3(30, 30, 20) } }; const xyUniforms = { uFreq: { value: new THREE.Vector2(5, 2) }, uAmp: { value: new THREE.Vector2(25, 15) } }; const LongRaceUniforms = { uFreq: { value: new THREE.Vector2(2, 3) }, uAmp: { value: new THREE.Vector2(35, 10) } }; const turbulentUniforms = { uFreq: { value: new THREE.Vector4(4, 8, 8, 1) }, uAmp: { value: new THREE.Vector4(25, 5, 10, 10) } }; const deepUniforms = { uFreq: { value: new THREE.Vector2(4, 8) }, uAmp: { value: new THREE.Vector2(10, 20) }, uPowY: { value: new THREE.Vector2(20, 2) } }; const distortions = { mountainDistortion: { uniforms: mountainUniforms, getDistortion: ` uniform vec3 uAmp; uniform vec3 uFreq; #define PI 3.14159265358979 float nsin(float val){ return sin(val) * 0.5 + 0.5; } instanced.setAttribute( "aOffset", new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false) ); instanced.setAttribute( "aMetrics", new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false) ); instanced.setAttribute( "aColor", new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false) ); let material = new THREE.ShaderMaterial({ fragmentShader: carLightsFragment, vertexShader: carLightsVertex, transparent: true, uniforms: Object.assign( { uTime: { value: 0 }, uTravelLength: { value: options.length }, uFade: { value: this.fade } }, this.webgl.fogUniforms, options.distortion.uniforms ) }); material.onBeforeCompile = function(shader) { // 修复: 直接添加getDistortion函数到顶点着色器 shader.vertexShader = shader.vertexShader.replace( 'void main() {', options.distortion.getDistortion + 'nvoid main() {' ); }; let mesh = new THREE.Mesh(instanced, material); mesh.frustumCulled = false; this.webgl.scene.add(mesh); this.mesh = mesh; } update(time) { this.mesh.material.uniforms.uTime.value = time; } } class App { constructor(container, options) { if (options === undefined) options = {}; this.options = options; if (this.options.distortion == null) { this.options.distortion = { uniforms: distortion_uniforms, getDistortion: distortion_vertex }; } this.container = container; this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true }); this.renderer.setSize(container.offsetWidth, container.offsetHeight, false); this.renderer.setPixelRatio(window.devicePixelRatio); this.composer = new postprocessing.EffectComposer(this.renderer); container.append(this.renderer.domElement); this.camera = new THREE.PerspectiveCamera( options.fov, container.offsetWidth / container.offsetHeight, 0.1, 10000 ); this.camera.position.z = -5; this.camera.position.y = 8; this.camera.position.x = 0; this.scene = new THREE.Scene(); this.scene.background = null; // Ensure scene background is transparent let fog = new THREE.Fog( options.colors.background, options.length * 0.2, options.length * 500 ); this.scene.fog = fog; this.fogUniforms = { fogColor: { value: fog.color }, fogNear: { value: fog.near }, fogFar: { value: fog.far } }; this.clock = new THREE.Clock(); this.assets = {}; this.disposed = false; this.road = new Road(this, options); this.leftCarLights = new CarLights( this, options, options.colors.leftCars, options.movingAwaySpeed, new THREE.Vector2(0, 1 - options.carLightsFade) ); this.rightCarLights = new CarLights( this, options, options.colors.rightCars, options.movingCloserSpeed, new THREE.Vector2(1, 0 + options.carLightsFade) ); this.leftSticks = new LightsSticks(this, options); this.fovTarget = options.fov; this.speedUpTarget = 0; } update(delta) { let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta); this.speedUp += lerp( this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001 ); this.timeOffset += this.speedUp * delta; let time = this.clock.elapsedTime + this.timeOffset; this.rightCarLights.update(time); this.leftCarLights.update(time); this.leftSticks.update(time); this.road.update(time); let updateCamera = false; let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage); if (fovChange !== 0) { this.camera.fov += fovChange * delta * 6; updateCamera = true; } if (this.options.distortion.getJS) { const distortion = this.options.distortion.getJS(0.025, time); this.camera.lookAt( new THREE.Vector3( this.camera.position.x + distortion.x, this.camera.position.y + distortion.y, this.camera.position.z + distortion.z ) ); updateCamera = true; } if (updateCamera) { this.camera.updateProjectionMatrix(); } } render(delta) { this.composer.render(delta); } dispose() { this.disposed = true; } setSize(width, height, updateStyles) { this.composer.setSize(width, height, updateStyles); } tick() { if (this.disposed || !this) return; if (resizeRendererToDisplaySize(this.renderer, this.setSize)) { const canvas = this.renderer.domElement; this.camera.aspect = canvas.clientWidth / canvas.clientHeight; this.camera.updateProjectionMatrix(); } const delta = this.clock.getDelta(); this.render(delta); this.update(delta); requestAnimationFrame(this.tick); } } this.speedUp = 0; this.timeOffset = 0; this.tick = this.tick.bind(this); this.init = this.init.bind(this); this.setSize = this.setSize.bind(this); this.onMouseDown = this.onMouseDown.bind(this); this.onMouseUp = this.onMouseUp.bind(this); } initPasses() { this.renderPass = new postprocessing.RenderPass(this.scene, this.camera); this.bloomPass = new postprocessing.EffectPass( this.camera, new postprocessing.BloomEffect({ luminanceThreshold: 0.2, luminanceSmoothing: 0, resolutionScale: 1 }) ); const smaaPass = new postprocessing.EffectPass( this.camera, new postprocessing.SMAAEffect({ preset: postprocessing.SMAAPreset.MEDIUM, searchImage: postprocessing.SMAAEffect.searchImageDataURL, areaImage: postprocessing.SMAAEffect.areaImageDataURL }) ); this.renderPass.renderToScreen = false; this.bloomPass.renderToScreen = false; smaaPass.renderToScreen = true; this.composer.addPass(this.renderPass); this.composer.addPass(this.bloomPass); this.composer.addPass(smaaPass); } loadAssets() { const assets = this.assets; return new Promise(function(resolve) { const manager = new THREE.LoadingManager(resolve); const searchImage = new Image(); const areaImage = new Image(); assets.smaa = {}; searchImage.addEventListener("load", function() { assets.smaa.search = this; manager.itemEnd("smaa-search"); }); areaImage.addEventListener("load", function() { assets.smaa.area = this; manager.itemEnd("smaa-area"); }); manager.itemStart("smaa-search"); manager.itemStart("smaa-area"); searchImage.src = postprocessing.SMAAEffect.searchImageDataURL; areaImage.src = postprocessing.SMAAEffect.areaImageDataURL; }); } init() { this.initPasses(); const options = this.options; this.road.init(); this.leftCarLights.init(); this.leftCarLights.mesh.position.setX( -options.roadWidth / 2 - options.islandWidth / 2 ); this.rightCarLights.init(); this.rightCarLights.mesh.position.setX( options.roadWidth / 2 + options.islandWidth / 2 ); this.leftSticks.init(); this.leftSticks.mesh.position.setX( -(options.roadWidth + options.islandWidth / 2) ); this.container.addEventListener("mousedown", this.onMouseDown); this.container.addEventListener("mouseup", this.onMouseUp); this.container.addEventListener("mouseout", this.onMouseUp); this.tick(); } onMouseDown(ev) { if (this.options.onSpeedUp) this.options.onSpeedUp(ev); this.fovTarget = this.options.fovSpeedUp; this.speedUpTarget = this.options.speedUp; } onMouseUp(ev) { if (this.options.onSlowDown) this.options.onSlowDown(ev); this.fovTarget = this.options.fov; this.speedUpTarget = 0; } update(time) { this.mesh.material.uniforms.uTime.value = time; } } class LightsSticks { constructor(webgl, options) { this.webgl = webgl; this.options = options; } init() { const options = this.options; const geometry = new THREE.PlaneGeometry(1, 1); let instanced = new THREE.InstancedBufferGeometry().copy(geometry); let totalSticks = options.totalSideLightSticks; instanced.instanceCount = totalSticks; let stickoffset = options.length / (totalSticks - 1); const aOffset = []; const aColor = []; const aMetrics = []; let colors = options.colors.sticks; if (Array.isArray(colors)) { colors = colors.map(function(c) { return new THREE.Color(c); }); } else { colors = new THREE.Color(colors); } for (let i = 0; i < totalSticks; i++) { let width = random(options.lightStickWidth); let height = random(options.lightStickHeight); aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random()); let color = pickRandom(colors); aColor.push(color.r); aColor.push(color.g); aColor.push(color.b); aMetrics.push(width); aMetrics.push(height); } instanced.setAttribute( "aOffset", new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false) ); instanced.setAttribute( "aColor", new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false) ); instanced.setAttribute( "aMetrics", new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false) ); const material = new THREE.ShaderMaterial({ fragmentShader: sideSticksFragment, vertexShader: sideSticksVertex, side: THREE.DoubleSide, uniforms: Object.assign( { uTravelLength: { value: options.length }, uTime: { value: 0 } }, this.webgl.fogUniforms, options.distortion.uniforms ) }); material.onBeforeCompile = function(shader) { // 修复: 直接添加getDistortion函数到顶点着色器 shader.vertexShader = shader.vertexShader.replace( 'void main(){', options.distortion.getDistortion + 'nvoid main(){' ); }; const mesh = new THREE.Mesh(instanced, material); mesh.frustumCulled = false; this.webgl.scene.add(mesh); this.mesh = mesh; vec3 getDistortion(float progress){ float movementProgressFix = 0.02; return vec3( cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x, nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y, nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z ); } `, getJS: function(progress, time) { let movementProgressFix = 0.02; let uFreq = mountainUniforms.uFreq.value; let uAmp = mountainUniforms.uAmp.value; let distortion = new THREE.Vector3( Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x - Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x, nsin(progress * Math.PI * uFreq.y + time) * uAmp.y - nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y, nsin(progress * Math.PI * uFreq.z + time) * uAmp.z - nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z ); let lookAtAmp = new THREE.Vector3(2, 2, 2); let lookAtOffset = new THREE.Vector3(0, 0, -5); return distortion.multiply(lookAtAmp).add(lookAtOffset); } }, xyDistortion: { uniforms: xyUniforms, getDistortion: ` uniform vec2 uFreq; uniform vec2 uAmp; #define PI 3.14159265358979 vec3 getDistortion(float progress){ float movementProgressFix = 0.02; return vec3( cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x, sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y, 0. ); } `, getJS: function(progress, time) { let movementProgressFix = 0.02; let uFreq = xyUniforms.uFreq.value; let uAmp = xyUniforms.uAmp.value; let distortion = new THREE.Vector3( Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x - Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x, Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y - Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y, 0 ); let lookAtAmp = new THREE.Vector3(2, 0.4, 1); let lookAtOffset = new THREE.Vector3(0, 0, -3); return distortion.multiply(lookAtAmp).add(lookAtOffset); } }, LongRaceDistortion: { uniforms: LongRaceUniforms, getDistortion: ` uniform vec2 uFreq; uniform vec2 uAmp; #define PI 3.14159265358979 vec3 getDistortion(float progress){ float camProgress = 0.0125; return vec3( sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x, sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y, 0. ); } `, getJS: function(progress, time) { let camProgress = 0.0125; let uFreq = LongRaceUniforms.uFreq.value; let uAmp = LongRaceUniforms.uAmp.value; let distortion = new THREE.Vector3( Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x - Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x, Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y - Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y, 0 ); let lookAtAmp = new THREE.Vector3(1, 1, 0); let lookAtOffset = new THREE.Vector3(0, 0, -5); return distortion.multiply(lookAtAmp).add(lookAtOffset); } }, turbulentDistortion: { uniforms: turbulentUniforms, getDistortion: ` uniform vec4 uFreq; uniform vec4 uAmp; float nsin(float val){ return sin(val) * 0.5 + 0.5; } #define PI 3.14159265358979 float getDistortionX(float progress){ return ( cos(PI * progress * uFreq.r + uTime) * uAmp.r + pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g ); } float getDistortionY(float progress){ return ( -nsin(PI * progress * uFreq.b + uTime) * uAmp.b + -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a ); } vec3 getDistortion(float progress){ return vec3( getDistortionX(progress) - getDistortionX(0.0125), getDistortionY(progress) - getDistortionY(0.0125), 0. ); } `, getJS: function(progress, time) { const uFreq = turbulentUniforms.uFreq.value; const uAmp = turbulentUniforms.uAmp.value; const getX = function(p) { return Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x + Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y; }; const getY = function(p) { return -nsin(Math.PI * p * uFreq.z + time) * uAmp.z - Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w; }; let distortion = new THREE.Vector3( getX(progress) - getX(progress + 0.007), getY(progress) - getY(progress + 0.007), 0 ); let lookAtAmp = new THREE.Vector3(-2, -5, 0); let lookAtOffset = new THREE.Vector3(0, 0, -10); return distortion.multiply(lookAtAmp).add(lookAtOffset); } }, deepDistortion: { uniforms: deepUniforms, getDistortion: ` uniform vec4 uFreq; uniform vec4 uAmp; uniform vec2 uPowY; float nsin(float val){ return sin(val) * 0.5 + 0.5; } #define PI 3.14159265358979 float getDistortionX(float progress){ return ( sin(progress * PI * uFreq.x + uTime) * uAmp.x ); } float getDistortionY(float progress){ return ( pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y ); } vec3 getDistortion(float progress){ return vec3( getDistortionX(progress) - getDistortionX(0.02), getDistortionY(progress) - getDistortionY(0.02), 0. ); } `, getJS: function(progress, time) { const uFreq = deepUniforms.uFreq.value; const uAmp = deepUniforms.uAmp.value; const uPowY = deepUniforms.uPowY.value; const getX = function(p) { return Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x; }; const getY = function(p) { return Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y; }; let distortion = new THREE.Vector3( getX(progress) - getX(progress + 0.01), getY(progress) - getY(progress + 0.01), 0 ); let lookAtAmp = new THREE.Vector3(-2, -4, 0); let lookAtOffset = new THREE.Vector3(0, 0, -10); return distortion.multiply(lookAtAmp).add(lookAtOffset); } } }; // 定义基础distortion const distortion_uniforms = { uDistortionX: { value: new THREE.Vector2(80, 3) }, uDistortionY: { value: new THREE.Vector2(-40, 2.5) } }; const distortion_vertex = ` #define PI 3.14159265358979 uniform vec2 uDistortionX; uniform vec2 uDistortionY; float nsin(float val){ return sin(val) * 0.5 + 0.5; } vec3 getDistortion(float progress){ progress = clamp(progress, 0., 1.); float xAmp = uDistortionX.r; float xFreq = uDistortionX.g; float yAmp = uDistortionY.r; float yFreq = uDistortionY.g; return vec3( xAmp * nsin(progress * PI * xFreq - PI / 2.), yAmp * nsin(progress * PI * yFreq - PI / 2.), 0. ); } `; // Shader变量 const roadmarkings_vars = ` uniform float uLanes; uniform vec3 uBrokenLinesColor; uniform vec3 uShoulderLinesColor; uniform float uShoulderLinesWidthPercentage; uniform float uBrokenLinesWidthPercentage; uniform float uBrokenLinesLengthPercentage; highp float random(vec2 co) { highp float a = 12.9898; highp float b = 78.233; highp float c = 43758.5453; highp float dt = dot(co.xy, vec2(a, b)); highp float sn = mod(dt, 3.14); return fract(sin(sn) * c); } `; // Shader片段 const roadmarkings_fragment = ` uv.y = mod(uv.y + uTime * 0.05, 1.); // Adjust speed of markings float laneWidth = 1.0 / uLanes; float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage; float laneEmptySpace = 1. - uBrokenLinesLengthPercentage; float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0)); // Dashes in the middle float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x); // Side continuous lines brokenLines = mix(brokenLines, sideLines, uv.x); // color = mix(color, uBrokenLinesColor, brokenLines); // vec2 noiseFreq = vec2(4., 7000.); // float roadNoise = random(floor(uv * noiseFreq) / noiseFreq) * 0.02 - 0.01; // color += roadNoise; `; // 修复: 不使用include系统,改用直接替换完整的着色器代码 // 岛屿片段着色器 const islandFragment = ` #define USE_FOG; varying vec2 vUv; uniform vec3 uColor; uniform float uTime; ${THREE.ShaderChunk["fog_pars_fragment"]} void main() { vec2 uv = vUv; vec3 color = vec3(uColor); gl_FragColor = vec4(color, 1.); ${THREE.ShaderChunk["fog_fragment"]} } `; // 道路片段着色器 const roadFragment = ` #define USE_FOG; varying vec2 vUv; uniform vec3 uColor; uniform float uTime; ${roadmarkings_vars} ${THREE.ShaderChunk["fog_pars_fragment"]} void main() { vec2 uv = vUv; vec3 color = vec3(uColor); ${roadmarkings_fragment} gl_FragColor = vec4(color, 1.); ${THREE.ShaderChunk["fog_fragment"]} } `; // 道路顶点着色器 const roadVertex = ` #define USE_FOG; uniform float uTime; ${THREE.ShaderChunk["fog_pars_vertex"]} uniform float uTravelLength; varying vec2 vUv; void main() { vec3 transformed = position.xyz; vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength); transformed.x += distortion.x; transformed.z += distortion.y; transformed.y += -1. * distortion.z; vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.); gl_Position = projectionMatrix * mvPosition; vUv = uv; ${THREE.ShaderChunk["fog_vertex"]} } `; // 车灯片段着色器 const carLightsFragment = ` #define USE_FOG; ${THREE.ShaderChunk["fog_pars_fragment"]} varying vec3 vColor; varying vec2 vUv; uniform vec2 uFade; void main() { vec3 color = vec3(vColor); float alpha = smoothstep(uFade.x, uFade.y, vUv.x); gl_FragColor = vec4(color, alpha); if (gl_FragColor.a < 0.0001) discard; ${THREE.ShaderChunk["fog_fragment"]} } `; // 车灯顶点着色器 const carLightsVertex = ` #define USE_FOG; ${THREE.ShaderChunk["fog_pars_vertex"]} attribute vec3 aOffset; attribute vec3 aMetrics; attribute vec3 aColor; uniform float uTravelLength; uniform float uTime; varying vec2 vUv; varying vec3 vColor; void main() { vec3 transformed = position.xyz; float radius = aMetrics.r; float myLength = aMetrics.g; float speed = aMetrics.b; transformed.xy *= radius; transformed.z *= myLength; transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength); transformed.xy += aOffset.xy; float progress = abs(transformed.z / uTravelLength); transformed.xyz += getDistortion(progress); vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.); gl_Position = projectionMatrix * mvPosition; vUv = uv; vColor = aColor; ${THREE.ShaderChunk["fog_vertex"]} } `; // 路灯柱顶点着色器 const sideSticksVertex = ` #define USE_FOG; ${THREE.ShaderChunk["fog_pars_vertex"]} attribute float aOffset; attribute vec3 aColor; attribute vec2 aMetrics; uniform float uTravelLength; uniform float uTime; varying vec3 vColor; mat4 rotationY( in float angle ) { return mat4( cos(angle), 0, sin(angle), 0, 0, 1.0, 0, 0, -sin(angle), 0, cos(angle), 0, 0, 0, 0, 1); } void main(){ vec3 transformed = position.xyz; float width = aMetrics.x; float height = aMetrics.y; transformed.xy *= vec2(width, height); float time = mod(uTime * 60. * 2. + aOffset, uTravelLength); transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz; transformed.z += - uTravelLength + time; float progress = abs(transformed.z / uTravelLength); transformed.xyz += getDistortion(progress); transformed.y += height / 2.; transformed.x += -width / 2.; vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.); gl_Position = projectionMatrix * mvPosition; vColor = aColor; ${THREE.ShaderChunk["fog_vertex"]} } `; // 路灯柱片段着色器 const sideSticksFragment = ` #define USE_FOG; ${THREE.ShaderChunk["fog_pars_fragment"]} varying vec3 vColor; void main(){ vec3 color = vec3(vColor); gl_FragColor = vec4(color,1.); ${THREE.ShaderChunk["fog_fragment"]} } `; // 类定义 class Road { constructor(webgl, options) { this.webgl = webgl; this.options = options; this.uTime = { value: 0 }; } createPlane(side, width, isRoad) { const options = this.options; let segments = 100; const geometry = new THREE.PlaneGeometry( isRoad ? options.roadWidth : options.islandWidth, options.length, 20, segments ); let uniforms = { uTravelLength: { value: options.length }, uColor: { value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor) }, uTime: this.uTime }; if (isRoad) { uniforms = Object.assign(uniforms, { uLanes: { value: options.lanesPerRoad }, uBrokenLinesColor: { value: new THREE.Color(options.colors.brokenLines) }, uShoulderLinesColor: { value: new THREE.Color(options.colors.shoulderLines) }, uShoulderLinesWidthPercentage: { value: options.shoulderLinesWidthPercentage }, uBrokenLinesLengthPercentage: { value: options.brokenLinesLengthPercentage }, uBrokenLinesWidthPercentage: { value: options.brokenLinesWidthPercentage } }); } const material = new THREE.ShaderMaterial({ fragmentShader: isRoad ? roadFragment : islandFragment, vertexShader: roadVertex, side: THREE.DoubleSide, uniforms: Object.assign( uniforms, this.webgl.fogUniforms, options.distortion.uniforms ) }); // 修复: 不再使用标签替换,而是在定义着色器时直接包含distortion逻辑 material.onBeforeCompile = function(shader) { // 添加getDistortion函数到顶点着色器 shader.vertexShader = shader.vertexShader.replace( 'void main() {', options.distortion.getDistortion + 'nvoid main() {' ); }; const mesh = new THREE.Mesh(geometry, material); mesh.rotation.x = -Math.PI / 2; mesh.position.z = -options.length / 2; mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side; this.webgl.scene.add(mesh); return mesh; } init() { this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true); this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true); this.island = this.createPlane(0, this.options.islandWidth, false); } update(time) { this.uTime.value = time; } } class CarLights { constructor(webgl, options, colors, speed, fade) { this.webgl = webgl; this.options = options; this.colors = colors; this.speed = speed; this.fade = fade; } init() { const options = this.options; let curve = new THREE.LineCurve3( new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1) ); let geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false); let instanced = new THREE.InstancedBufferGeometry().copy(geometry); instanced.instanceCount = options.lightPairsPerRoadWay * 2; let laneWidth = options.roadWidth / options.lanesPerRoad; let aOffset = []; let aMetrics = []; let aColor = []; let colors = this.colors; if (Array.isArray(colors)) { colors = colors.map(function(c) { return new THREE.Color(c); }); } else { colors = new THREE.Color(colors); } for (let i = 0; i < options.lightPairsPerRoadWay; i++) { let radius = random(options.carLightsRadius); let length = random(options.carLightsLength); let speed = random(this.speed); let carLane = i % options.lanesPerRoad; // Fix lane assignment to spread across lanes let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2; let carWidth = random(options.carWidthPercentage) * laneWidth; let carShiftX = random(options.carShiftX) * laneWidth; laneX += carShiftX; let offsetY = random(options.carFloorSeparation) + radius * 1.3; let offsetZ = -random(options.length); aOffset.push(laneX - carWidth / 2); aOffset.push(offsetY); aOffset.push(offsetZ); aOffset.push(laneX + carWidth / 2); aOffset.push(offsetY); aOffset.push(offsetZ); aMetrics.push(radius); aMetrics.push(length); aMetrics.push(speed); aMetrics.push(radius); aMetrics.push(length); aMetrics.push(speed); let color = pickRandom(colors); aColor.push(color.r); aColor.push(color.g); aColor.push(color.b); aColor.push(color.r); aColor.push(color.g); aColor.push(color.b); }