
Three.js 3D 渲染与赛博朋克风格 UI 实现从着色器到霓虹矩阵一、Web 3D 的赛博觉醒为什么前端需要第三维度Web 页面长期被困在二维平面里。CSS 动画再炫也只是平面的位移和变换。当你的 DApp 需要展示链上数据的三维可视化当你的产品需要赛博朋克风格的沉浸式体验当你的用户期望在浏览器中看到电影级的视觉效果——Three.js 是从平面跃入空间的桥梁。但 3D 渲染不是加个 3D 模型那么简单。着色器编程、光照模型、后处理管线、性能优化——每一项都是独立的学问。赛博朋克风格更是一个特殊的挑战它不是简单的加个霓虹灯而是需要精心设计色彩体系、粒子系统、故障效果和氛围渲染才能营造出那种高科技、低生活的视觉张力。二、Three.js 渲染管线与赛博朋克视觉体系2.1 渲染管线架构Three.js 的渲染管线从场景构建到像素输出每一步都可以定制。理解这个管线是实现自定义视觉效果的基础。graph TD A[Scene 场景图] -- B[Geometry 几何体] A -- C[Material 材质] A -- D[Light 光照] B -- E[Vertex Shader 顶点着色器] C -- E D -- E E -- F[光栅化] F -- G[Fragment Shader 片元着色器] G -- H[帧缓冲] H -- I[后处理管线] I -- J[Bloom 辉光] I -- K[Glitch 故障] I -- L[Film Grain 胶片噪点] J -- M[最终输出] K -- M L -- M2.2 赛博朋克色彩体系赛博朋克的视觉核心是高对比度 霓虹色。典型色彩方案主色调深蓝黑#0a0a1a作为背景营造暗夜氛围霓虹色青色#00fff2、品红#ff00ff、电紫#8b5cf6强调色热橙#ff6b35用于警告和交互元素辅助色冷灰#2a2a3a用于面板和边框这些颜色不是随意选择的。青色和品红的互补关系创造视觉张力深色背景让霓虹色发光热橙的暖色调在冷色系中形成焦点。2.3 自定义着色器基础赛博朋克效果的核心是自定义着色器。Three.js 的 ShaderMaterial 允许你完全控制顶点和片元处理。三、赛博朋克 UI 的完整实现3.1 霓虹网格地面// components/cyber-grid.ts import * as THREE from three; /** * 赛博朋克风格的无限网格地面 * 为什么用着色器而非几何体 * 几何体网格的线段数量随距离指数增长 * 着色器只需一个平面通过数学计算生成网格线 */ export function createCyberGrid(): THREE.Mesh { const geometry new THREE.PlaneGeometry(200, 200, 1, 1); geometry.rotateX(-Math.PI / 2); const material new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color(#00fff2) }, uFadeColor: { value: new THREE.Color(#0a0a1a) }, }, vertexShader: varying vec2 vUv; varying vec3 vWorldPos; void main() { vUv uv; // 计算世界坐标用于网格线计算 vec4 worldPos modelMatrix * vec4(position, 1.0); vWorldPos worldPos.xyz; gl_Position projectionMatrix * viewMatrix * worldPos; } , fragmentShader: uniform float uTime; uniform vec3 uColor; uniform vec3 uFadeColor; varying vec2 vUv; varying vec3 vWorldPos; void main() { // 网格线基于世界坐标的周期函数 // 为什么用 fmod 而非纹理 // 数学计算无限精度不会出现纹理模糊 float gridX abs(fract(vWorldPos.x * 0.1) - 0.5); float gridZ abs(fract(vWorldPos.z * 0.1) - 0.5); // 线宽距离越远线越细 float lineWidth 0.02; float lineX smoothstep(lineWidth, 0.0, gridX); float lineZ smoothstep(lineWidth, 0.0, gridZ); float grid max(lineX, lineZ); // 距离衰减远处网格线逐渐消失 float dist length(vWorldPos.xz); float fade 1.0 - smoothstep(10.0, 80.0, dist); // 脉冲动画网格线周期性闪烁 float pulse sin(dist * 0.3 - uTime * 2.0) * 0.3 0.7; vec3 color mix(uFadeColor, uColor, grid * fade * pulse); float alpha grid * fade * 0.8; gl_FragColor vec4(color, alpha); } , transparent: true, side: THREE.DoubleSide, }); return new THREE.Mesh(geometry, material); }3.2 全息粒子系统// components/hologram-particles.ts import * as THREE from three; /** * 全息风格粒子系统 * 为什么用 Points 而非独立 Mesh * 数千个粒子用独立 Mesh 性能不可接受 * Points 将所有粒子合并为一次绘制调用 */ export function createHologramParticles(count: number 2000): THREE.Points { const positions new Float32Array(count * 3); const sizes new Float32Array(count); const phases new Float32Array(count); // 每个粒子的相位偏移 for (let i 0; i count; i) { // 在圆柱体内随机分布——赛博朋克的城市天际线感 const angle Math.random() * Math.PI * 2; const radius 5 Math.random() * 40; const height Math.random() * 30 - 5; positions[i * 3] Math.cos(angle) * radius; positions[i * 3 1] height; positions[i * 3 2] Math.sin(angle) * radius; sizes[i] Math.random() * 3 0.5; phases[i] Math.random() * Math.PI * 2; } const geometry new THREE.BufferGeometry(); geometry.setAttribute(position, new THREE.BufferAttribute(positions, 3)); geometry.setAttribute(aSize, new THREE.BufferAttribute(sizes, 1)); geometry.setAttribute(aPhase, new THREE.BufferAttribute(phases, 1)); const material new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) }, }, vertexShader: attribute float aSize; attribute float aPhase; uniform float uTime; uniform float uPixelRatio; varying float vAlpha; void main() { vec3 pos position; // 粒子缓慢上升到达顶部后重置 // 为什么用 mod 而非 if // GPU 着色器中分支语句性能差 // 数学运算更高效 pos.y mod(pos.y uTime * 0.5, 30.0) - 5.0; // 呼吸效果粒子大小周期性变化 float breathe sin(uTime * 2.0 aPhase) * 0.3 1.0; vec4 mvPos modelViewMatrix * vec4(pos, 1.0); gl_PointSize aSize * breathe * uPixelRatio * (80.0 / -mvPos.z); gl_Position projectionMatrix * mvPos; // 透明度随高度变化 vAlpha smoothstep(-5.0, 5.0, pos.y) * smoothstep(30.0, 20.0, pos.y); } , fragmentShader: varying float vAlpha; void main() { // 圆形粒子距离中心越远越透明 float dist length(gl_PointCoord - vec2(0.5)); if (dist 0.5) discard; float alpha smoothstep(0.5, 0.1, dist) * vAlpha * 0.6; // 青色全息粒子 vec3 color vec3(0.0, 1.0, 0.95); gl_FragColor vec4(color, alpha); } , transparent: true, depthWrite: false, // 粒子不写入深度缓冲避免遮挡问题 blending: THREE.AdditiveBlending, // 叠加混合让粒子发光 }); return new THREE.Points(geometry, material); }3.3 故障效果后处理// postprocessing/glitch-effect.ts import { Effect } from postprocessing; /** * 自定义赛博朋克故障效果 * 为什么用后处理而非 CSS * 后处理在 GPU 上执行性能远优于 CSS filter * 且能与 3D 场景深度信息结合 */ export const glitchFragmentShader /* glsl */ uniform float uTime; uniform float uIntensity; uniform float uGlitchFrequency; // 伪随机函数——为什么不用 JS Math.random // 着色器在 GPU 上执行无法调用 JS 函数 float random(vec2 st) { return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453); } void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) { // 触发故障基于时间的随机脉冲 float glitchTrigger step( 0.95, random(vec2(floor(uTime * uGlitchFrequency), 1.0)) ); // 水平位移RGB 通道分别偏移 float offset glitchTrigger * uIntensity * 0.05; float r texture2D(inputBuffer, uv vec2(offset, 0.0)).r; float g texture2D(inputBuffer, uv).g; float b texture2D(inputBuffer, uv - vec2(offset, 0.0)).b; vec3 color vec3(r, g, b); // 扫描线效果 float scanline sin(uv.y * 800.0) * 0.04; color - scanline; // 随机色块——模拟数据损坏 float blockNoise glitchTrigger * step( 0.5, random(vec2(floor(uv.y * 50.0), floor(uTime * 10.0))) ); color mix(color, vec3(1.0, 0.0, 0.95), blockNoise * 0.3); outputColor vec4(color, inputColor.a); } ;3.4 场景组装与动画循环// scene/cyber-scene.ts import * as THREE from three; import { EffectComposer, BloomEffect, RenderPass, EffectPass } from postprocessing; import { createCyberGrid } from ../components/cyber-grid; import { createHologramParticles } from ../components/hologram-particles; export class CyberScene { private renderer: THREE.WebGLRenderer; private scene: THREE.Scene; private camera: THREE.PerspectiveCamera; private composer: EffectComposer; private clock: THREE.Clock; // 场景元素引用 private grid: THREE.Mesh; private particles: THREE.Points; constructor(container: HTMLElement) { // 渲染器 this.renderer new THREE.WebGLRenderer({ antialias: true, alpha: true, }); this.renderer.setSize(container.clientWidth, container.clientHeight); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); this.renderer.toneMapping THREE.ACESFilmicToneMapping; container.appendChild(this.renderer.domElement); // 场景 this.scene new THREE.Scene(); this.scene.fog new THREE.FogExp2(#0a0a1a, 0.015); // 为什么用雾赛博朋克场景需要深度感 // 雾效让远处物体逐渐消失增强空间纵深 // 相机 this.camera new THREE.PerspectiveCamera( 60, container.clientWidth / container.clientHeight, 0.1, 200 ); this.camera.position.set(0, 8, 25); this.camera.lookAt(0, 3, 0); // 创建场景元素 this.grid createCyberGrid(); this.scene.add(this.grid); this.particles createHologramParticles(3000); this.scene.add(this.particles); // 环境光——为什么用低强度 // 赛博朋克场景的光源应该是局部的霓虹灯 // 全局环境光只需提供最低可见度 const ambientLight new THREE.AmbientLight(#1a1a2e, 0.3); this.scene.add(ambientLight); // 点光源模拟霓虹灯 const neonLight1 new THREE.PointLight(#00fff2, 2, 50); neonLight1.position.set(10, 10, 10); this.scene.add(neonLight1); const neonLight2 new THREE.PointLight(#ff00ff, 1.5, 40); neonLight2.position.set(-15, 8, -5); this.scene.add(neonLight2); // 后处理管线 this.composer new EffectComposer(this.renderer); this.composer.addPass(new RenderPass(this.scene, this.camera)); // 辉光效果——赛博朋克的灵魂 // 为什么辉光如此重要霓虹灯的视觉特征就是光晕 // 没有 Bloom 效果霓虹色和平面色没有区别 const bloom new BloomEffect({ intensity: 1.5, luminanceThreshold: 0.2, luminanceSmoothing: 0.9, mipmapBlur: true, }); this.composer.addPass(new EffectPass(this.camera, bloom)); this.clock new THREE.Clock(); // 响应窗口大小变化 window.addEventListener(resize, this.onResize); } private onResize () { 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); }; public animate () { requestAnimationFrame(this.animate); const elapsed this.clock.getElapsedTime(); // 更新着色器时间 uniform const gridMat this.grid.material as THREE.ShaderMaterial; gridMat.uniforms.uTime.value elapsed; const particleMat this.particles.material as THREE.ShaderMaterial; particleMat.uniforms.uTime.value elapsed; // 相机缓慢旋转——为什么不用鼠标控制 // 展示场景用自动旋转更稳定 // 交互场景可替换为 OrbitControls this.camera.position.x Math.sin(elapsed * 0.1) * 25; this.camera.position.z Math.cos(elapsed * 0.1) * 25; this.camera.lookAt(0, 3, 0); this.composer.render(); }; public dispose() { window.removeEventListener(resize, this.onResize); this.renderer.dispose(); } }3.5 React 集成// components/CyberBackground.tsx use client; import { useEffect, useRef } from react; import { CyberScene } from /scene/cyber-scene; export function CyberBackground() { const containerRef useRefHTMLDivElement(null); const sceneRef useRefCyberScene | null(null); useEffect(() { if (!containerRef.current) return; // 创建场景 sceneRef.current new CyberScene(containerRef.current); sceneRef.current.animate(); // 清理——为什么需要清理 // React 严格模式下 useEffect 会执行两次 // 不清理会导致 WebGL 上下文泄漏 return () { sceneRef.current?.dispose(); sceneRef.current null; }; }, []); return ( div ref{containerRef} classNamefixed inset-0 -z-10 style{{ background: #0a0a1a }} / ); }四、架构权衡3D 渲染的代价4.1 视觉冲击 vs 性能开销赛博朋克风格依赖大量后处理效果Bloom、Glitch、粒子这些效果在低端设备上可能导致帧率下降。建议根据设备性能动态调整高端设备启用全部效果低端设备关闭 Bloom 和粒子只保留基础网格。4.2 自定义着色器 vs 内置材质ShaderMaterial 提供完全控制但维护成本高——着色器代码不可调试跨平台兼容性需要手动测试。MeshStandardMaterial 后处理在大多数场景下够用只有需要特殊效果如全息投影、数据流时才用自定义着色器。4.3 实时渲染 vs 预渲染背景动画可以预渲染为视频用 CSS 播放性能远优于实时渲染。但预渲染失去了交互性——鼠标移动无法影响场景。对于纯装饰性背景预渲染是更务实的选择对于交互式 3D 产品实时渲染不可替代。4.4 Bundle 体积Three.js 完整包约 600KBgzip 后约 150KB加上后处理库和着色器代码3D 场景的 JS 体积可能超过 300KB。对于内容型网站这个体积不可接受。建议用动态 import 按需加载 3D 模块首屏不阻塞。五、总结Three.js 赛博朋克 UI 的实现本质上是用代码绘制氛围。网格地面营造空间感粒子系统制造生命感故障效果注入不安感辉光后处理赋予霓虹灵魂。每一个视觉元素都在讲述同一个故事高科技与低生活的碰撞。但 3D 渲染的每一帧都是性能的消耗。自定义着色器、后处理管线、粒子系统——这些效果的叠加会迅速耗尽 GPU 资源。在赛博朋克的视觉追求和用户设备的现实限制之间找到平衡点才是工程能力的体现。在赛博空间的前端战场Three.js 是你的画笔着色器是你的颜料。但记住——最好的视觉效果是让用户沉浸其中而忘记技术存在的那种。