Three.js 道路流光教程

📅 2026/6/30 16:12:14
Three.js 道路流光教程
道路流光 ·Road Shader· ▶ 在线运行案例案例合集三维可视化功能案例threehub.cn开源仓库github地址https://github.com/z2586300277/three-cesium-examples400个案例代码:网盘链接你将学到什么ShaderMaterial 自定义着色器实现核心视觉效果EffectComposer 多 Pass 后期处理管线OrbitControls 相机轨道交互requestAnimationFrame渲染循环与resize自适应效果说明本案例演示道路流光效果原场景渲染后经 EffectComposer 叠加 Bloom/模糊等全屏后期核心用到 ShaderMaterial、EffectComposer、OrbitControls。建议先打开文首在线案例查看动态画面再对照下方源码逐步理解。核心概念Scene / Camera / WebGLRenderer构成最小渲染闭环大场景可开logarithmicDepthBuffer缓解 Z-fighting。ShaderMaterial通过uniforms 自定义 GLSL 控制逐像素/逐点效果透明粒子常配合depthTest: false。EffectComposer以多 Pass 链式渲染RenderPass → 特效 Pass → 输出屏幕替代直接renderer.render。OrbitControls提供轨道旋转/缩放开启enableDamping后需在 animate 中controls.update()。实现步骤搭建 Scene、PerspectiveCamera、WebGLRenderer挂载 canvas 并处理resize定义 uniforms / onBeforeCompile 或 ShaderMaterial编写 GLSL 与材质参数组装 EffectComposer Pass 链在 animate 中调用composer.render()创建 OrbitControls及 Raycaster 等交互控件若源码包含在requestAnimationFrame循环中更新状态并 renderCesium 为viewer.render或自动渲染代码要点import * as THREE from three;import { EffectComposer } from three/addons/postprocessing/EffectComposer.js; import { RenderPass } from three/addons/postprocessing/RenderPass.js; import { BloomPass } from three/addons/postprocessing/BloomPass.js; import { OutputPass } from three/addons/postprocessing/OutputPass.js; import { OrbitControls } from three/examples/jsm/controls/OrbitControls.js;class Base { initThree(el) { this.container el; this.renderer new THREE.WebGLRenderer({ antialias: true, alpha: true }); this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight); this.container.appendChild(this.renderer.domElement); this.scene new THREE.Scene(); this.camera new THREE.PerspectiveCamera( 45, this.container.offsetWidth / this.container.offsetHeight, 1, 2000 ); this.camera.position.set(0, 10, 50); new OrbitControls(this.camera, this.renderer.domElement); this.animate(); window.addEventListener(resize, this.onResize.bind(this)); } animate() { this.renderer.render(this.scene, this.camera); requestAnimationFrame(this.animate.bind(this)); } onResize() { if (this.container) { this.camera.aspect this.container.offsetWidth / this.container.offsetHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight); } } }class Road extends Base { constructor() { super(); this.speed 0.005; } animate() { if (this.materials) { this.materials.forEach((m) { m.uniforms.uTime.value this.speed; if (m.uniforms.uTime.value 1) { m.uniforms.uTime.value 0; } }) } if (this.composer) { this.renderer.autoClear false; this.renderer.clear(); this.normalObj.visible false; this.composer.render(); this.renderer.clearDepth(); this.normalObj.visible true; } this.renderer.render(this.scene, this.camera); this.threeAnim requestAnimationFrame(this.animate.bind(this)); } initBloom() { const params { threshold: 0, strength: 0.5, radius: 0.5, exposure: 0.5 }; const renderScene new RenderPass(this.scene, this.camera); const bloomPass new BloomPass(5, 20, 100); bloomPass.threshold params.threshold; bloomPass.strength params.strength; bloomPass.radius params.radius; const composer new EffectComposer(this.renderer); composer.addPass(renderScene); composer.addPass(bloomPass); composer.addPass(new OutputPass()); this.composer composer; } createChart(that) { this.initBloom(); const commonUniforms { uFade: { value: new THREE.Vector2(0, 0.6) }, uOffset: { value: new THREE.Vector2(40, 20) } }; const vertexMoveHeight float getMove(float u, float offset) { float a uPI2.0; return sin(a PI0.25)u * offset; } float getHeight(float u, float offset) { float a uPI3.0; return cos(a)uoffset; }; const spline new THREE.LineCurve3( new THREE.Vector3(0, 0, that.height * 0.25), new THREE.Vector3(0, 0, -that.height * 0.75) ); const geometry new THREE.TubeGeometry(spline, that.height, that.lineWidth, 8, false);const vertexShader float PI acos(-1.0); uniform vec2 uOffset; varying vec2 vUv; ${vertexMoveHeight} void main(void) { vUv uv; float m getMove(uv.x, uOffset.x); float h getHeight(uv.x, uOffset.y); vec3 newPosition position; newPosition.x m; newPosition.y h; gl_Position projectionMatrixmodelViewMatrixvec4(newPosition, 1.0); };const fragmentShader varying vec2 vUv; uniform float uSpeed; uniform float uTime; uniform vec2 uFade; uniform vec3 uColor; uniform float uDirection; void main() { vec3 color uColor; float s -uTime * uSpeed; float v (uDirection 1.0) ? vUv.x : -vUv.x; float d mod(v s, 1.0); if (d uFade.y) discard; else { float alpha smoothstep(uFade.x, uFade.y, d); if (alpha 0.0001) discard; gl_FragColor vec4(color, alpha); } };const materials []; const amount that.amount; const step (that.width - that.gap) / amount;for (let i 0; i amount; i) { const color new THREE.Color(); const v i / amount; color.setHSL( THREE.MathUtils.lerp(that.hueStart, that.hueEnd, v), 1, THREE.MathUtils.lerp(that.lightStart, that.lightEnd, v) );const material new THREE.ShaderMaterial({ side: THREE.DoubleSide, transparent: true, uniforms: { uColor: { value: color }, uTime: { value: THREE.MathUtils.lerp(-1, 1, Math.random()) }, uDirection: { value: i amount * 0.5 ? 1 : 0 }, uSpeed: { value: THREE.MathUtils.lerp(1, 1.5, Math.random()) }, ...commonUniforms }, vertexShader, fragmentShader });materials.push(material);const mesh new THREE.Mesh(geometry, material); mesh.position.x istep (i amount0.5 - 1 ? that.gap : 0); mesh.position.y Math.random() * 5; this.scene.add(mesh); }this.materials materials;const planeGeometry new THREE.PlaneGeometry( that.width, that.height, that.width * 0.25, that.height * 0.25 );const planeMaterial new THREE.ShaderMaterial({ side: THREE.DoubleSide, transparent: true, uniforms: { uColor: { value: new THREE.Color(blue) }, ...commonUniforms }, vertexShader:float PI acos(-1.0); uniform vec2 uOffset; ${vertexMoveHeight} void main(void) { float m getMove(uv.y, uOffset.x); float h getHeight(uv.y, uOffset.y); vec3 newPosition position; newPosition.x m; newPosition.z h; gl_Position projectionMatrixmodelViewMatrixvec4(newPosition, 1.0); }, fragmentShader:uniform vec3 uColor; void main() { gl_FragColor vec4(uColor, 0.6); }});this.planeMat planeMaterial;const plane new THREE.Mesh(planeGeometry, planeMaterial); plane.rotateX(-Math.PI * 0.5); plane.position.set(that.width0.5, -1, -that.height0.25); this.normalObj plane; this.scene.add(plane); } }var road new Road(); road.initThree(document.getElementById(box)); road.createChart({ lineWidth: 0.5, width: 48, height: 400, gap: 8, amount: 20, hueStart: 0.9, hueEnd: 0.1, lightStart: 0.5, lightEnd: 1.0 });完整源码GitHub小结本文提供道路流光完整 Three.js 源码与在线 Demo建议先运行案例再改 uniform/参数做二次实验更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库