Three.js 赛博朋克风格 UI:霓虹光效与粒子系统的 WebGL 渲染实战

📅 2026/6/28 21:12:02
Three.js 赛博朋克风格 UI:霓虹光效与粒子系统的 WebGL 渲染实战
Three.js 赛博朋克风格 UI霓虹光效与粒子系统的 WebGL 渲染实战一、赛博朋克 UI 的渲染挑战从视觉概念到 WebGL 像素赛博朋克风格的 Web 界面有三个标志性视觉元素霓虹光效Neon Glow、全息粒子场Holographic Particle Field和故障艺术Glitch Art。这些效果在 2D CSS 中可以通过 box-shadow、filter 和 animation 近似模拟但当场景复杂度上升——数百个发光元素、数千个粒子、实时交互响应——CSS 方案的性能会急剧恶化帧率跌破 30fps。根本原因在于 CSS 的渲染管线无法利用 GPU 的并行计算能力。box-shadow 的模糊计算在 CPU 上执行每个阴影元素独立计算无法批量处理。而 Three.js 基于 WebGL 的渲染管线天然运行在 GPU 上通过着色器Shader程序将光效和粒子计算并行化可以在 60fps 下渲染数万个粒子。本文将深入探讨如何用 Three.js 构建生产级的赛博朋克风格 UI。二、赛博朋克渲染管线后处理与粒子系统的协同架构赛博朋克风格的核心渲染管线由三层组成场景层3D 几何体与粒子、后处理层Bloom 辉光、色差、故障效果和交互层鼠标追踪、滚动驱动。三层通过 EffectComposer 串联每帧依次执行。flowchart LR subgraph 场景层 Scene[Three.js Scene] -- Geometry[霓虹几何体] Scene -- Particles[粒子系统] Scene -- Grid[无限网格地面] end subgraph 渲染通道 Geometry -- RenderPass[基础渲染通道] Particles -- RenderPass Grid -- RenderPass RenderPass -- UnrealBloom[Bloom 辉光通道] UnrealBloom -- GlitchPass[故障效果通道] GlitchPass -- Chromatic[色差通道] Chromatic -- Output[最终输出] end subgraph 交互层 Mouse[鼠标位置] -- |uniform| Particles Mouse -- |uniform| Geometry Scroll[滚动偏移] -- |transform| Grid end style 场景层 fill:#0a0a23,stroke:#00ffff,color:#eee style 渲染通道 fill:#1a0a3e,stroke:#ff00ff,color:#eee style 交互层 fill:#0d1b2a,stroke:#00ff88,color:#eee上图展示了渲染管线的三层架构。关键设计点在于 Bloom 辉光通道的参数调优——赛博朋克风格的霓虹光效需要高阈值threshold配合高强度strength只让最亮的区域发光避免整个画面泛白。粒子系统则通过自定义 ShaderMaterial 实现每个粒子的颜色和大小根据距摄像机的距离动态变化模拟全息投影的远近衰减效果。三、赛博朋克 UI 的 Three.js 工程实现import * as THREE from three; import { EffectComposer } from three/examples/jsm/postprocessing/EffectComposer; import { RenderPass } from three/examples/jsm/postprocessing/RenderPass; import { UnrealBloomPass } from three/examples/jsm/postprocessing/UnrealBloomPass; import { ShaderPass } from three/examples/jsm/postprocessing/ShaderPass; import { GlitchPass } from three/examples/jsm/postprocessing/GlitchPass; // 赛博朋克场景管理器 // 封装 Three.js 的初始化、渲染循环和后处理管线 class CyberpunkScene { private scene: THREE.Scene; private camera: THREE.PerspectiveCamera; private renderer: THREE.WebGLRenderer; private composer: EffectComposer; private particles: THREE.Points; private neonObjects: THREE.Group; private mouse: THREE.Vector2; private clock: THREE.Clock; constructor(container: HTMLElement) { // 初始化渲染器——开启抗锯齿和 HDR 输出 // HDR 是 Bloom 辉光效果的必要前提否则光效会丢失亮度信息 this.renderer new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: high-performance, }); this.renderer.setSize(container.clientWidth, container.clientHeight); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); this.renderer.toneMapping THREE.ACESFilmicToneMapping; this.renderer.toneMappingExposure 1.2; container.appendChild(this.renderer.domElement); // 场景与相机 this.scene new THREE.Scene(); this.scene.fog new THREE.FogExp2(0x0a0a23, 0.015); // 指数雾——模拟赛博朋克的城市雾霾 this.camera new THREE.PerspectiveCamera( 75, container.clientWidth / container.clientHeight, 0.1, 1000 ); this.camera.position.set(0, 2, 8); this.mouse new THREE.Vector2(0, 0); this.clock new THREE.Clock(); // 构建场景元素 this.particles this.createParticleSystem(5000); this.neonObjects this.createNeonGeometry(); this.scene.add(this.particles, this.neonObjects, this.createInfiniteGrid()); // 构建后处理管线 this.composer this.createPostProcessing(); // 事件监听 window.addEventListener(mousemove, this.onMouseMove); window.addEventListener(resize, this.onResize); // 启动渲染循环 this.animate(); } /** * 创建粒子系统——赛博朋克的全息粒子场 * 使用自定义 ShaderMaterial 实现粒子的颜色渐变和大小衰减 * 每个粒子有独立的速度向量在 animate 中更新位置 */ private createParticleSystem(count: number): THREE.Points { const geometry new THREE.BufferGeometry(); // 粒子位置——随机分布在空间中 const positions new Float32Array(count * 3); // 粒子颜色——从青色到品红的渐变赛博朋克标志色 const colors new Float32Array(count * 3); // 粒子大小——根据距摄像机距离衰减 const sizes new Float32Array(count); // 粒子速度——用于动画更新 const velocities new Float32Array(count * 3); for (let i 0; i count; i) { const i3 i * 3; // 位置在 20x20x20 的空间内随机分布 positions[i3] (Math.random() - 0.5) * 20; positions[i3 1] (Math.random() - 0.5) * 20; positions[i3 2] (Math.random() - 0.5) * 20; // 颜色在青色(#00ffff)和品红(#ff00ff)之间随机插值 const t Math.random(); colors[i3] t; // R: 0 → 1 colors[i3 1] 1 - t; // G: 1 → 0 colors[i3 2] 1; // B: 固定 1 sizes[i] Math.random() * 3 0.5; // 速度缓慢漂浮 velocities[i3] (Math.random() - 0.5) * 0.01; velocities[i3 1] (Math.random() - 0.5) * 0.01; velocities[i3 2] (Math.random() - 0.5) * 0.01; } geometry.setAttribute(position, new THREE.BufferAttribute(positions, 3)); geometry.setAttribute(color, new THREE.BufferAttribute(colors, 3)); geometry.setAttribute(size, new THREE.BufferAttribute(sizes, 1)); // 自定义着色器——实现粒子大小随距离衰减和颜色脉冲 const material new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uMouse: { value: new THREE.Vector2(0, 0) }, uPixelRatio: { value: this.renderer.getPixelRatio() }, }, vertexShader: attribute float size; varying vec3 vColor; uniform float uTime; uniform vec2 uMouse; uniform float uPixelRatio; void main() { vColor color; vec4 mvPosition modelViewMatrix * vec4(position, 1.0); // 粒子大小随距离衰减——近大远小 gl_PointSize size * uPixelRatio * (200.0 / -mvPosition.z); // 鼠标靠近时粒子膨胀——交互反馈 float distToMouse length(position.xy - uMouse * 5.0); float mouseInfluence smoothstep(3.0, 0.0, distToMouse); gl_PointSize * (1.0 mouseInfluence * 2.0); gl_Position projectionMatrix * mvPosition; } , fragmentShader: varying vec3 vColor; uniform float uTime; void main() { // 圆形粒子——丢弃圆外的像素 float dist length(gl_PointCoord - vec2(0.5)); if (dist 0.5) discard; // 中心亮、边缘暗——模拟发光效果 float glow 1.0 - dist * 2.0; glow pow(glow, 1.5); // 颜色脉冲——随时间微弱闪烁 float pulse 0.8 0.2 * sin(uTime * 2.0 vColor.r * 10.0); gl_FragColor vec4(vColor * glow * pulse, glow * 0.8); } , transparent: true, vertexColors: true, blending: THREE.AdditiveBlending, // 加法混合——粒子叠加更亮 depthWrite: false, // 不写深度——避免粒子互相遮挡 }); // 将速度数据存储在 userData 中供动画循环使用 const points new THREE.Points(geometry, material); points.userData.velocities velocities; return points; } /** * 创建霓虹几何体——赛博朋克的标志性发光线框 * 使用 EdgesGeometry 只渲染边缘线配合高亮度颜色触发 Bloom */ private createNeonGeometry(): THREE.Group { const group new THREE.Group(); // 霓虹色板——青色、品红、黄色三原色 const neonColors [0x00ffff, 0xff00ff, 0xffff00]; // 创建多个旋转的线框几何体 const geometries [ new THREE.IcosahedronGeometry(1.5, 1), new THREE.OctahedronGeometry(1.2, 0), new THREE.TorusGeometry(1.0, 0.3, 8, 32), ]; geometries.forEach((geo, index) { // EdgesGeometry 只提取边缘线——比 WireframeGeometry 更干净 const edges new THREE.EdgesGeometry(geo); const lineMaterial new THREE.LineBasicMaterial({ color: neonColors[index], // 高亮度是触发 Bloom 辉光的关键 // Three.js 的 Bloom 基于亮度阈值超过阈值的像素才会发光 }); const line new THREE.LineSegments(edges, lineMaterial); line.position.set( (index - 1) * 3.5, Math.sin(index) * 0.5, 0 ); group.add(line); }); return group; } /** * 创建无限网格地面——赛博朋克的透视参考线 * 使用 ShaderMaterial 宓现网格线的渐隐效果 */ private createInfiniteGrid(): THREE.Mesh { const gridMaterial new THREE.ShaderMaterial({ uniforms: { uColor: { value: new THREE.Color(0x00ffff) }, uFadeDistance: { value: 30.0 }, }, vertexShader: varying vec3 vWorldPos; void main() { vec4 worldPos modelMatrix * vec4(position, 1.0); vWorldPos worldPos.xyz; gl_Position projectionMatrix * viewMatrix * worldPos; } , fragmentShader: uniform vec3 uColor; uniform float uFadeDistance; varying vec3 vWorldPos; void main() { // 网格线——基于世界坐标的整数边界 vec2 grid abs(fract(vWorldPos.xz - 0.5) - 0.5); float line min(grid.x, grid.y); float gridAlpha 1.0 - smoothstep(0.0, 0.05, line); // 距离衰减——远处网格线逐渐消失 float dist length(vWorldPos.xz); float fade 1.0 - smoothstep(0.0, uFadeDistance, dist); gl_FragColor vec4(uColor, gridAlpha * fade * 0.3); } , transparent: true, side: THREE.DoubleSide, depthWrite: false, }); const gridGeometry new THREE.PlaneGeometry(100, 100, 1, 1); const gridMesh new THREE.Mesh(gridGeometry, gridMaterial); gridMesh.rotation.x -Math.PI / 2; gridMesh.position.y -2; return gridMesh; } /** * 创建后处理管线——赛博朋克视觉效果的最终合成 * Bloom 辉光是核心Glitch 和色差是锦上添花 */ private createPostProcessing(): EffectComposer { const composer new EffectComposer(this.renderer); // 基础渲染通道 const renderPass new RenderPass(this.scene, this.camera); composer.addPass(renderPass); // Bloom 辉光——赛博朋克的灵魂效果 // threshold: 只有亮度超过此值的像素才发光避免全屏泛白 // strength: 辉光强度越高越梦幻 // radius: 辉光扩散半径 const bloomPass new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, // strength 0.4, // radius 0.2 // threshold——低阈值让更多元素发光 ); composer.addPass(bloomPass); // 故障效果——赛博朋克的数字失真感 // 不常触发只在特定交互时激活 const glitchPass new GlitchPass(); glitchPass.goWild false; // 默认关闭交互时临时开启 composer.addPass(glitchPass); // 自定义色差着色器——RGB 通道偏移 const chromaticAberrationShader { uniforms: { tDiffuse: { value: null }, uOffset: { value: 0.002 }, // 色差偏移量 }, vertexShader: varying vec2 vUv; void main() { vUv uv; gl_Position projectionMatrix * modelViewMatrix * vec4(position, 1.0); } , fragmentShader: uniform sampler2D tDiffuse; uniform float uOffset; varying vec2 vUv; void main() { // RGB 三个通道分别偏移——模拟镜头色差 vec2 dir vUv - vec2(0.5); float dist length(dir); vec2 offset dir * dist * uOffset; float r texture2D(tDiffuse, vUv offset).r; float g texture2D(tDiffuse, vUv).g; float b texture2D(tDiffuse, vUv - offset).b; gl_FragColor vec4(r, g, b, 1.0); } , }; const chromaticPass new ShaderPass(chromaticAberrationShader); composer.addPass(chromaticPass); return composer; } // 渲染循环 private animate (): void { requestAnimationFrame(this.animate); const elapsed this.clock.getElapsedTime(); // 更新粒子位置 this.updateParticles(elapsed); // 旋转霓虹几何体 this.neonObjects.children.forEach((obj, i) { obj.rotation.x elapsed * 0.3 * (i 1) * 0.5; obj.rotation.y elapsed * 0.2 * (i 1) * 0.5; }); // 更新着色器 uniform const particleMaterial this.particles.material as THREE.ShaderMaterial; particleMaterial.uniforms.uTime.value elapsed; particleMaterial.uniforms.uMouse.value.copy(this.mouse); // 渲染 this.composer.render(); }; private updateParticles(elapsed: number): void { const positions this.particles.geometry.attributes.position.array as Float32Array; const velocities this.particles.userData.velocities as Float32Array; for (let i 0; i positions.length; i 3) { positions[i] velocities[i]; positions[i 1] velocities[i 1]; positions[i 2] velocities[i 2]; // 边界回弹——粒子超出范围后反向运动 for (let j 0; j 3; j) { if (Math.abs(positions[i j]) 10) { velocities[i j] * -1; } } } this.particles.geometry.attributes.position.needsUpdate true; } private onMouseMove (event: MouseEvent): void { this.mouse.x (event.clientX / window.innerWidth) * 2 - 1; this.mouse.y -(event.clientY / window.innerHeight) * 2 1; }; private onResize (): void { const width window.innerWidth; const height window.innerHeight; this.camera.aspect width / height; this.camera.updateProjectionMatrix(); this.renderer.setSize(width, height); this.composer.setSize(width, height); }; }四、WebGL 渲染的性能边界与设备兼容性移动端 GPU 的算力瓶颈。上述场景在桌面端 GPU如 RTX 3060上可以稳定 60fps但在中端移动设备上帧率可能降至 20-30fps。主要瓶颈在于粒子系统的 Fragment Shader——每个粒子需要计算发光衰减和颜色脉冲5000 个粒子意味着每帧执行 5000 次片段着色器调用。移动端优化策略包括降低粒子数至 1000-2000、使用 InstancedBufferGeometry 替代 Points、关闭 AdditiveBlending 改用普通透明混合。Bloom 辉光的性能开销。UnrealBloomPass 需要对渲染结果做多次降采样和高斯模糊每增加一级降采样就多一次全屏 Pass。在 1080p 分辨率下5 级 Bloom 大约增加 3-4ms 的渲染时间。对于低端设备可以减少 Bloom 级数至 3 级或降低 Bloom Pass 的分辨率至屏幕的 1/2。Shader 编译的冷启动延迟。自定义 ShaderMaterial 在首次使用时需要编译 GLSL 着色器编译时间取决于着色器复杂度和 GPU 驱动。在低端 Android 设备上冷启动编译可能需要 500ms-2s期间页面会卡住。解决方案是在页面加载后立即渲染一帧不可见的场景触发着色器预编译。WebGL 上下文丢失。移动浏览器在内存压力下可能回收 WebGL 上下文导致整个 3D 场景崩溃。Three.js 提供了renderer.info监控机制但上下文恢复需要重新创建所有 GPU 资源。生产环境中必须监听webglcontextlost事件实现优雅降级——3D 场景切换为 CSS 静态背景。五、总结Three.js 为赛博朋克风格 UI 提供了远超 CSS 的渲染能力和性能上限。通过自定义 ShaderMaterial 实现粒子系统的颜色脉冲和大小衰减通过 UnrealBloomPass 实现霓虹辉光效果通过色差着色器模拟镜头失真三层后处理管线协同工作构建出完整的赛博朋克视觉体验。落地路线建议第一步使用 EffectComposer 搭建基础后处理管线优先调通 Bloom 辉光参数第二步将粒子系统和霓虹几何体封装为独立的 React 组件通过 R3FReact Three Fiber集成到现有页面中第三步针对移动端实现 LODLevel of Detail策略——根据设备 GPU 等级动态调整粒子数量和 Bloom 级数。始终需要在视觉冲击力与渲染性能之间找到平衡点——60fps 的流畅体验比华丽的静态截图更有价值。