Three.js 赛博朋克 UI 实战:后处理管线与着色器驱动的霓虹视觉系统
2026/6/22 15:21:03 网站建设 项目流程

Three.js 赛博朋克 UI 实战:后处理管线与着色器驱动的霓虹视觉系统

一、Web 3D 的视觉天花板——当 CSS 渐变无法承载赛博朋克美学

赛博朋克风格的视觉核心是霓虹光晕、故障效果和雨夜反射。在传统 Web 开发中,这些效果通过 CSS filter 和 SVG 滤镜实现,但存在两个根本性限制:其一,CSS 滤镜无法实现基于法线方向的光晕扩散——霓虹灯管的发光强度应该随观察角度变化,而 CSS 的box-shadow是均匀扩散的;其二,CSS 无法实现屏幕空间的反射效果——雨夜地面的霓虹倒影需要基于深度缓冲的射线追踪,这超出了 CSS 的能力范围。

Three.js 的后处理管线(Post-Processing Pipeline)提供了在 GPU 上对渲染结果进行逐像素操作的能力。通过自定义 Shader Pass,可以实现 CSS 无法企及的视觉效果,同时保持 60fps 的渲染性能。本文将从后处理管线的底层机制出发,构建一套完整的赛博朋克视觉系统。

二、Three.js 后处理管线的渲染机制

Three.js 的后处理管线基于 EffectComposer,其核心思想是:将场景渲染到帧缓冲(FBO),然后通过多个 Shader Pass 逐级处理,最终输出到屏幕。

flowchart LR subgraph 渲染阶段["场景渲染"] A[Scene + Camera] --> B[RenderPass<br/>渲染到 FBO] end subgraph 后处理管线["后处理 Pass 链"] B --> C[BloomPass<br/>霓虹光晕提取与扩散] C --> D[GlitchPass<br/>数字故障效果] D --> E[Custom: RainReflection<br/>雨夜反射着色器] E --> F[Custom: Scanline<br/>扫描线与色差] end subgraph 输出["最终输出"] F --> G[ShaderPass: Copy<br/>输出到屏幕] end style 后处理管线 fill:#0a0a1a,stroke:#00ffff,color:#fff style 渲染阶段 fill:#1a0a0a,stroke:#ff00ff,color:#fff

帧缓冲的链式传递:每个 Pass 接收上一个 Pass 的输出纹理(readBuffer),将处理结果写入另一个帧缓冲(writeBuffer)。两个缓冲区在 Pass 之间交替角色(ping-pong),避免读写冲突。这意味着每个 Pass 都是一次完整的 GPU 渲染调用,N 个 Pass 就是 N 次 Draw Call。

Bloom 的两阶段实现:Three.js 的 BloomPass 分为亮度提取和高斯模糊两个阶段。首先,一个阈值 Pass 将亮度超过阈值的像素提取到单独的纹理中;然后,对提取的纹理进行多级降采样高斯模糊(通常 5 次),模拟光晕的扩散效果。最后,将模糊后的光晕纹理与原始场景叠加。

性能关键点:每个 Pass 的分辨率决定了 GPU 的工作量。Bloom 的模糊阶段通常使用 1/4 分辨率,因为光晕本身是低频信息,全分辨率模糊是浪费。但 Glitch 和 Scanline 等效果需要全分辨率,否则会出现像素化。

三、赛博朋克视觉系统的代码实现

3.1 后处理管线搭建

/** * 赛博朋克后处理管线 * 架构:EffectComposer + 自定义 ShaderPass * 性能策略:Bloom 使用降采样,其余 Pass 全分辨率 */ 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'; // ---- 自定义扫描线 + 色差着色器 ---- const CyberpunkShader = { uniforms: { tDiffuse: { value: null }, // 输入纹理(由 EffectComposer 自动注入) uTime: { value: 0.0 }, // 时间(驱动动画) uScanlineDensity: { value: 800.0 }, // 扫描线密度 uScanlineIntensity: { value: 0.08 }, // 扫描线强度 uChromaticAberration: { value: 0.003 }, // 色差偏移量 uVignetteIntensity: { value: 0.4 }, // 暗角强度 uFlickerSpeed: { value: 3.0 }, // 闪烁频率 }, vertexShader: /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: /* glsl */ ` uniform sampler2D tDiffuse; uniform float uTime; uniform float uScanlineDensity; uniform float uScanlineIntensity; uniform float uChromaticAberration; uniform float uVignetteIntensity; uniform float uFlickerSpeed; varying vec2 vUv; void main() { vec2 uv = vUv; // 1. 色差效果:RGB 三个通道分别偏移采样 // 模拟廉价 CRT 显示器的色彩分离 float aberration = uChromaticAberration * (1.0 + 0.5 * sin(uTime * 0.5)); float r = texture2D(tDiffuse, uv + vec2(aberration, 0.0)).r; float g = texture2D(tDiffuse, uv).g; float b = texture2D(tDiffuse, uv - vec2(aberration, 0.0)).b; vec3 color = vec3(r, g, b); // 2. 扫描线:基于屏幕 Y 坐标的明暗条纹 float scanline = sin(uv.y * uScanlineDensity) * 0.5 + 0.5; scanline = pow(scanline, 1.5); color -= scanline * uScanlineIntensity; // 3. 暗角效果:屏幕边缘变暗 float vignette = 1.0 - length(uv - 0.5) * uVignetteIntensity * 1.414; vignette = clamp(vignette, 0.0, 1.0); color *= vignette; // 4. 微弱闪烁:模拟霓虹灯管的不稳定供电 float flicker = 1.0 - 0.02 * sin(uTime * uFlickerSpeed) * sin(uTime * 7.3); color *= flicker; gl_FragColor = vec4(color, 1.0); } `, }; // ---- 自定义雨夜反射着色器 ---- const RainReflectionShader = { uniforms: { tDiffuse: { value: null }, uTime: { value: 0.0 }, uRainSpeed: { value: 1.0 }, uReflectionStrength: { value: 0.3 }, uDistortion: { value: 0.02 }, }, vertexShader: /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: /* glsl */ ` uniform sampler2D tDiffuse; uniform float uTime; uniform float uRainSpeed; uniform float uReflectionStrength; uniform float uDistortion; varying vec2 vUv; // 简化的噪声函数,用于水面波纹扭曲 float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } float noise(vec2 p) { vec2 i = floor(p); vec2 f = fract(p); f = f * f * (3.0 - 2.0 * f); float a = hash(i); float b = hash(i + vec2(1.0, 0.0)); float c = hash(i + vec2(0.0, 1.0)); float d = hash(i + vec2(1.0, 1.0)); return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); } void main() { vec2 uv = vUv; // 只在屏幕下半部分应用反射(模拟地面) float reflectionZone = smoothstep(0.5, 0.55, uv.y); if (reflectionZone > 0.01) { // 计算反射的采样坐标:Y 轴翻转 vec2 reflectUv = vec2(uv.x, 1.0 - uv.y); // 水面波纹扭曲 float wave = noise(uv * 15.0 + uTime * uRainSpeed * 0.5); reflectUv.x += (wave - 0.5) * uDistortion * reflectionZone; // 采样反射颜色 vec3 reflected = texture2D(tDiffuse, reflectUv).rgb; // 反射强度随距离地面中心递减 float distFromCenter = abs(uv.x - 0.5) * 2.0; float reflectionFalloff = 1.0 - distFromCenter * 0.5; // 混合原始颜色和反射颜色 vec3 original = texture2D(tDiffuse, uv).rgb; vec3 color = mix( original, reflected * vec3(0.6, 0.8, 1.0), // 反射偏冷色调 uReflectionStrength * reflectionZone * reflectionFalloff ); gl_FragColor = vec4(color, 1.0); } else { gl_FragColor = texture2D(tDiffuse, uv); } } `, }; /** * 创建赛博朋克后处理管线 */ export function createCyberpunkPipeline( renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.PerspectiveCamera ): { composer: EffectComposer; update: (delta: number) => void; } { const composer = new EffectComposer(renderer); // Pass 1: 场景渲染 const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); // Pass 2: 霓虹光晕(Bloom) // 使用 UnrealBloomPass,参数经过调优以匹配赛博朋克风格 const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, // 强度:较高的值让霓虹更突出 0.4, // 半径:控制光晕扩散范围 0.85 // 阈值:只有亮度超过此值的像素才产生光晕 ); composer.addPass(bloomPass); // Pass 3: 雨夜反射 const rainPass = new ShaderPass(RainReflectionShader); composer.addPass(rainPass); // Pass 4: 扫描线 + 色差 + 暗角 const cyberPass = new ShaderPass(CyberpunkShader); composer.addPass(cyberPass); // 更新函数:每帧调用,驱动时间相关的动画 const update = (delta: number) => { const elapsed = performance.now() / 1000; rainPass.uniforms.uTime.value = elapsed; cyberPass.uniforms.uTime.value = elapsed; }; return { composer, update }; }

3.2 霓虹材质工厂

/** * 霓虹材质工厂:创建自发光的赛博朋克风格材质 * 核心思路:使用 MeshStandardMaterial 的 emissive 属性实现自发光, * 配合 Bloom 后处理产生光晕效果 */ import * as THREE from 'three'; // 预定义的赛博朋克配色方案 const NEON_PALETTES = { cyan: new THREE.Color(0x00ffff), magenta: new THREE.Color(0xff00ff), hotPink: new THREE.Color(0xff1493), electricBlue: new THREE.Color(0x0080ff), toxicGreen: new THREE.Color(0x39ff14), }; export function createNeonMaterial( color: keyof typeof NEON_PALETTES, intensity: number = 2.0 ): THREE.MeshStandardMaterial { /** * emissiveIntensity 是关键参数: * - 值为 1.0 时,自发光颜色与基础颜色相同,不产生 Bloom * - 值为 2.0+ 时,自发光亮度超过 Bloom 阈值,产生光晕 * - 值过高(>5.0)会导致 Bloom 过曝,失去细节 */ return new THREE.MeshStandardMaterial({ color: 0x000000, // 基础颜色为黑色,让 emissive 完全主导 emissive: NEON_PALETTES[color], emissiveIntensity: intensity, metalness: 0.9, // 高金属度增强反射 roughness: 0.1, // 低粗糙度让表面更光滑 }); } /** * 创建霓虹灯管几何体 * 使用 TubeGeometry 沿路径生成管状网格, * 配合自发光材质实现灯管效果 */ export function createNeonTube( points: THREE.Vector3[], color: keyof typeof NEON_PALETTES, radius: number = 0.02 ): THREE.Mesh { const curve = new THREE.CatmullRomCurve3(points); const geometry = new THREE.TubeGeometry(curve, 64, radius, 8, false); const material = createNeonMaterial(color, 3.0); return new THREE.Mesh(geometry, material); }

四、后处理管线的性能代价与视觉权衡

Bloom 的 GPU 开销:UnrealBloomPass 的多级降采样模糊,每次降采样都是一次全屏 Draw Call。5 级降采样意味着 10 次 Draw Call(5 次降采样 + 5 次上采样叠加)。在移动端 GPU 上,这可能导致帧率从 60fps 降至 30fps。缓解方案是减少降采样级数(从 5 降至 3),或使用 Kawase Blur 替代高斯模糊,后者只需 4 次 Pass 即可近似高斯效果。

着色器复杂度与编译时间:自定义 Fragment Shader 的指令数直接影响 GPU 执行时间。雨夜反射着色器中的噪声函数(hash + noise)约 20 条指令,在 1080p 分辨率下每帧执行约 200 万次。在低端 GPU 上,这可能导致 5-10ms 的额外渲染时间。优化方案是将噪声纹理预计算到 LUT(Look-Up Table)中,用纹理采样替代实时计算。

色差效果的视觉疲劳:持续的色差效果会导致视觉疲劳,尤其是长时间阅读文本内容时。建议将色差强度与场景动态关联——仅在快速移动或故障触发时增强色差,静态场景降低至接近零。

移动端的兼容性陷阱:WebGL 2 在 iOS Safari 上的帧缓冲格式支持有限。部分自定义着色器使用的浮点纹理(gl_FragColor输出 float 值)可能在某些设备上被降级为 8 位精度,导致色带(banding)问题。必须使用OES_texture_float扩展检测,并在不支持时回退到半精度。

适用边界:赛博朋克后处理管线适合展示型页面、3D 作品集和游戏化 UI。对于信息密集型应用(如数据仪表盘),扫描线和色差效果会降低可读性,应仅用于装饰性元素。

五、总结

Three.js 后处理管线的核心价值在于:将视觉效果从 CSS 的限制中解放出来,通过 GPU 着色器实现基于物理的视觉表现。Bloom 提供了霓虹光晕的基础,自定义着色器补充了扫描线、色差和雨夜反射等赛博朋克标志性元素。但后处理管线的性能开销不容忽视——每个 Pass 都是一次完整的 GPU 渲染调用,必须根据目标设备的性能合理调整 Pass 数量和着色器复杂度。

落地路线建议:首先搭建基础管线(RenderPass + BloomPass),确保光晕效果符合预期。其次,逐步添加自定义着色器,每添加一个 Pass 都进行帧率测试。最后,实现基于设备性能的动态降级策略——在低端设备上自动跳过雨夜反射等高开销 Pass,保证 30fps 的最低帧率。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询