欢迎光临
我们一直在努力

WebGPU 与 Web Audio API 结合的可视化音频频谱

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

WebGPU 与 Web Audio API 结合的可视化音频频谱

封面信息图

在音乐流媒体、数字音频工作站(DAW)以及多媒体互动展厅中,实时音频可视化是一项兼具工程挑战与艺术美感的技术。传统的实现往往依赖 Canvas2D 绘制单薄的 2D 柱状条,或者在 WebGL 中通过 CPU 逐帧遍历 FFT 频域数组构造网格。这种做法不仅因频繁的 CPU-GPU 数据传输和垃圾回收(GC)导致帧率受限,而且极难呈现具备三维纵深感、流体波动与光影折射的高阶视听画卷。

借助现代浏览器标准中的 Web Audio API 与 WebGPU,我们可以将音频的实时离散傅里叶变换(FFT)数据直接写入 GPU 纹理或 Storage Buffer,利用 WGSL 顶点着色器执行三维曲面置换(Vertex Displacement),并在片元着色器中依据声波能量实时合成辉光与色彩动力学。

Web Audio 与 WebGPU 渲染管线拓扑:
[AudioContext / MediaStream]
| (解码与音轨播放)
[AnalyserNode (FFT 频域分析)]
| (每帧提取 Uint8Array / Float32Array 频点数据)
v
[WebGPU Texture / Storage Buffer (1D/2D 音频特征图谱)]
|
v
[WGSL Vertex Shader] —-> (根据频段能量驱动三维曲面起伏/波浪置换)
|
v
[WGSL Fragment Shader] –> (根据重低音与高频瞬态叠加能量辉光与色差)
|
v
[Canvas 60fps 4K 极清音频三维瀑布流]

Web Audio API 分析节点搭建与数据提取

Web Audio API 的 AnalyserNode 能够在频域(Frequency Domain)与时域(Time Domain)提取音频切片。通过设定 fftSize = 2048,我们可以获得 1024 个频段的能量数据。

// audio-analyzer.ts
export class AudioSpectrumSource {
private audioCtx: AudioContext;
private analyser: AnalyserNode;
private frequencyData: Uint8Array;

constructor() {
this.audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
this.analyser = this.audioCtx.createAnalyser();
this.analyser.fftSize = 2048;
this.analyser.smoothingTimeConstant = 0.85; // 平滑衰减因子,消除剧烈跳变

this.frequencyData = new Uint8Array(this.analyser.frequencyBinCount);
}

public async loadAudio(url: string): Promise<void> {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await this.audioCtx.decodeAudioData(arrayBuffer);

const sourceNode = this.audioCtx.createBufferSource();
sourceNode.buffer = audioBuffer;
sourceNode.connect(this.analyser);
this.analyser.connect(this.audioCtx.destination);
sourceNode.start(0);
}

public getFrequencySnapshot(): Uint8Array {
this.analyser.getByteFrequencyData(this.frequencyData);
return this.frequencyData;
}
}

WebGPU 显存同步与 1D 频谱纹理更新

在每一帧渲染循环中,将分析得到的 1024 字节音频频点数组直接通过 writeTexture 上传到 GPU 的 1D 采样纹理中。这仅占用极微小的带宽(约 1KB/帧),几乎为零开销。

// audio-texture-bridge.ts
export class AudioTextureBridge {
private device: GPUDevice;
private spectrumTexture: GPUTexture;

constructor(device: GPUDevice) {
this.device = device;
this.spectrumTexture = device.createTexture({
size: [1024, 1, 1],
format: 'r8unorm', // 8位无符号单通道归一化格式 (0.0 ~ 1.0)
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
});
}

public updateSpectrum(data: Uint8Array): void {
this.device.queue.writeTexture(
{ texture: this.spectrumTexture },
data,
{ bytesPerRow: 1024 },
{ width: 1024, height: 1, depthOrArrayLayers: 1 }
);
}

public getTexture(): GPUTexture {
return this.spectrumTexture;
}
}

WGSL 频谱曲面置换与能量光影着色器

在着色器端,平面网格(Grid Mesh)的顶点在 X 轴方向的 UV 坐标直接映射到 1D 音频纹理的采样位置。重低音(低频区,UV.x 接近 0)驱动网格产生深沉厚重的巨幅起伏,高频音(高频区,UV.x 接近 1.0)驱动细密的尖峰微振动。

// spectrum-shader.wgsl
struct Uniforms {
viewProjMatrix : mat4x4<f32>,
time : f32,
bassEnergy : f32,
};

@group(0) @binding(0) var<uniform> uniforms : Uniforms;
@group(0) @binding(1) var spectrumTex : texture_2d<f32>;
@group(0) @binding(2) var spectrumSampler : sampler;

struct VertexInput {
@location(0) position : vec3<f32>,
@location(1) uv : vec2<f32>,
};

struct VertexOutput {
@builtin(position) position : vec4<f32>,
@location(0) uv : vec2<f32>,
@location(1) height : f32,
@location(2) worldPos : vec3<f32>,
};

@vertex
fn vs_main(in : VertexInput) -> VertexOutput {
var out : VertexOutput;
out.uv = in.uv;

// 采样 1D 音频纹理中的能量值
let audioAmp = textureSampleLevel(spectrumTex, spectrumSampler, vec2<f32>(in.uv.x, 0.5), 0.0).r;

// 结合时钟与音频能量进行三维波形调制
let wave = sin(in.uv.y * 20.0 – uniforms.time * 2.0) * 0.2;
let displacedHeight = (audioAmp * 2.5 + wave * audioAmp);

var pos = in.position;
pos.y += displacedHeight;

out.height = displacedHeight;
out.worldPos = pos;
out.position = uniforms.viewProjMatrix * vec4<f32>(pos, 1.0);
return out;
}

@fragment
fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
// 色彩动力学映射:根据高度与能量从深蓝墨色过渡至明亮橙金
let baseColor = vec3<f32>(0.05, 0.12, 0.25); // 深海冷调
let crestColor = vec3<f32>(1.0, 0.45, 0.1); // 能量金光

let factor = clamp(in.height / 2.0, 0.0, 1.0);
var finalRgb = mix(baseColor, crestColor, factor);

// 为波峰增加菲涅尔式高光边缘
let edgeGlow = pow(factor, 3.0) * 1.5;
finalRgb += vec3<f32>(edgeGlow);

return vec4<f32>(finalRgb, 1.0);
}

将音频 FFT 解析的数学精准性与 WebGPU 的强大并行渲染能力结合,使音符从无形的声波转化为具备物理厚度与光影起伏的流动波澜,为现代 Web 多媒体应用注入了沉浸式的视听生命力。

赞(0)
未经允许不得转载:171主机测评 » WebGPU 与 Web Audio API 结合的可视化音频频谱
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址