Three.js 赛博雨夜汽车大灯体积光(Volumetric Light)与丁达尔效应模拟
在潮湿阴冷的赛博朋克雨夜街道中,悬浮跑车疾驰而过时,前大灯刺破暴雨雾气所形成的锥形光束(Light Shafts / Volumetric Light Cone),以及悬浮在空气中的微小水汽颗粒所激发的丁达尔散射效应(Tyndall Effect),是营造未来科幻史诗感的王牌视觉元素。
如果使用传统的三维光线步进体积渲染(Raymarching Volume),需要每帧在视椎体内进行数十次采样计算,移动端浏览器的 GPU 会瞬间过热掉帧。
通过使用轻量级几何圆锥(Cone Mesh)+ 自定义衰减 Shader(包含深度衰减、边缘柔化与噪声尘埃扰动),我们可以在0 额外光线追踪开销、仅需 1 个 Draw Call的极简算力下,模拟出极其细腻通透的赛博大灯体积光。
一、轻量级体积光锥数学衰减模型
体积光锥的片元透明度 $\alpha$ 取决于三个核心维度的乘积:
$$\alpha = \text{DistanceFade}(d) \times \text{RadialFalloff}(\theta) \times \text{DustNoise}(x, y, z, t)$$
- 纵向距离衰减(Distance Fade):光束从灯头(亮斑最高)沿光锥轴线向前平滑衰减至 0;
- 径向边缘柔化(Radial Edge Softness):光锥圆柱边缘使用余弦柔化,彻底消除硬多边形切面;
- 空气水汽尘埃噪点(Microscopic Dust Particles):在光束内部叠加动态流动的 3D 噪声,模拟雨夜空气中漂浮的水汽团。
graph LR ConeGeometry[轻量 ConeGeometry 圆锥几何体] --> VShader[顶点着色器: 传递光束局部坐标与相机观察方向] VShader --> FShader[片元着色器: 计算 DistanceFade * FresnelSoftness * Noise] FShader --> AdditiveBlend[THREE.AdditiveBlending 叠加光度] AdditiveBlend --> ScreenFinal[输出极具穿透力的赛博汽车大灯体积光束]二、体积光自定义 ShaderMaterial 完整实现
// shaders/volumetricLightShader.ts import * as THREE from 'three'; export function createVolumetricSpotlightMaterial() { return new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uLightColor: { value: new THREE.Color(0x00f3ff) }, // 赛博青大灯 uSpotPosition: { value: new THREE.Vector3() }, uConeLength: { value: 25.0 }, // 光束射程 uIntensity: { value: 1.8 }, }, vertexShader: ` varying vec3 vWorldPos; varying vec3 vNormal; varying vec2 vUv; varying vec3 vLocalPos; void main() { vNormal = normalize(normalMatrix * normal); vUv = uv; vLocalPos = position; vec4 worldPos = modelMatrix * vec4(position, 1.0); vWorldPos = worldPos.xyz; gl_Position = projectionMatrix * viewMatrix * worldPos; } `, fragmentShader: ` uniform float uTime; uniform vec3 uLightColor; uniform float uConeLength; uniform float uIntensity; varying vec3 vWorldPos; varying vec3 vNormal; varying vec3 vLocalPos; varying vec2 vUv; // 轻量伪随机噪声 float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); } void main() { // 1. 纵向轴向衰减:从圆锥顶点 (y=0) 到底部 (y=-coneLength) 渐隐 float distFactor = clamp(1.0 - (-vLocalPos.y / uConeLength), 0.0, 1.0); float longitudinalFade = pow(distFactor, 1.5); // 2. 径向边缘柔化:基于视线与圆锥法线的夹角 (菲涅尔边缘半透) vec3 viewDir = normalize(cameraPosition - vWorldPos); float edgeSoftness = pow(abs(dot(viewDir, vNormal)), 1.2); // 3. 空气微观水汽扰动 (Tyndall Dust) float noise = hash(vUv * 8.0 + vec2(uTime * 0.1, uTime * 0.05)); float dustEffect = 0.85 + noise * 0.3; // 4. 综合光度 Alpha float finalAlpha = longitudinalFade * edgeSoftness * dustEffect * uIntensity; // 5. 核心高光增益 (近灯头处极其明亮) vec3 finalColor = uLightColor * (1.0 + pow(distFactor, 4.0) * 2.0); gl_FragColor = vec4(finalColor, finalAlpha); } `, transparent: true, blending: THREE.AdditiveBlending, // 关键:叠加混合实现真实光线穿透 depthWrite: false, // 关闭深度写入以防遮挡远景雨滴 side: THREE.DoubleSide, // 双面可见保证内外通透 }); }三、场景组装与跑车大灯挂载实战
// scene/cyberCarHeadlights.ts import * as THREE from 'three'; import { createVolumetricSpotlightMaterial } from '../shaders/volumetricLightShader'; export function createCyberHeadlightRig(scene: THREE.Scene) { // 1. 创建圆锥几何体 (半径 3.5, 长度 25, 沿 Y 轴向下展开) const coneGeo = new THREE.ConeGeometry(3.5, 25, 32, 1, true); coneGeo.translate(0, -12.5, 0); // 将旋转支点移动到圆锥顶点 const lightMatLeft = createVolumetricSpotlightMaterial(); const lightMatRight = createVolumetricSpotlightMaterial(); const leftBeam = new THREE.Mesh(coneGeo, lightMatLeft); const rightBeam = new THREE.Mesh(coneGeo, lightMatRight); // 调整大灯倾角向前照射 (绕 X 轴旋转 75 度) leftBeam.rotation.x = Math.PI / 2 + 0.15; rightBeam.rotation.x = Math.PI / 2 + 0.15; // 左右车灯间距 leftBeam.position.set(-1.2, 0.8, 0); rightBeam.position.set(1.2, 0.8, 0); const carHeadlightGroup = new THREE.Group(); carHeadlightGroup.add(leftBeam); carHeadlightGroup.add(rightBeam); scene.add(carHeadlightGroup); return { group: carHeadlightGroup, update: (delta: number) => { lightMatLeft.uniforms.uTime.value += delta; lightMatRight.uniforms.uTime.value += delta; }, }; }四、极客调优三大秘诀
- 真实雨丝穿透联动(Rain-Light Interaction):
当雨滴粒子系统掉落进大灯圆锥的包围盒区域时,Shader 可以自动将雨丝粒子的亮度乘以 3.0,瞬间呈现出雨水穿过大灯光束时的暴雨飞溅感; - 地面高光投影光斑(Ground Cookie Light):
在圆锥光束正下方的地面上放置一个带柔化边缘的透明贴图(Decal / Projected Texture),与体积光锥完美呼应; - 极低开销移动端友好:
该方案不需要昂贵的光线采样循环,全场景仅消耗几十个面片,即使在千元安卓机上也能 60 FPS 满帧奔跑。
用极简的数学着色器模拟复杂的物理大气光学,这是现代 WebGL 场景调优的极致智慧。