WEBKT

WebGPU法线贴图压缩:BC5与ASTC的RG通道精确保留与WGSL重建实践

9 0 0 0

在 WebGPU 时代,现代图形管线对显存带宽和渲染品质的要求达到了新的高度。法线贴图(Normal Map)作为决定表面细节的核心贴图,其压缩质量直接影响光影表现。

传统的 BC1/BC3(DXT1/DXT5)压缩格式由于其通道间的关联性(Co-relation)以及对绿色通道的非对称偏好,在压缩法线贴图时极易引入明显的块状伪影和交叉干扰(Crosstalk)。为了获得无损般的法线细节,行业标准的做法是丢弃 Z 通道,仅保留 R、G 通道,并采用针对单双通道优化的压缩格式:在桌面端/主机端采用 BC5,在移动端/跨平台则采用 ASTC

本文将深入探讨在 WebGPU 中,如何通过 BC5 和 ASTC 格式精确保留法线的 RG 通道,以及如何在 WGSL 着色器中高效、精确地重建 Z 通道。


一、 核心数学原理:切线空间法线的 2 通道重建

在切线空间(Tangent Space)中,法线向量 $\vec{N} = (x, y, z)$ 是一个单位向量,满足恒等式:

$$x^2 + y^2 + z^2 = 1$$

由于切线空间的法线方向总是朝向表面外侧(即 $z \ge 0$),我们可以忽略负半轴。因此,只要确保 $x$ 和 $y$(即法线贴图的 R 和 G 通道)的精度,就可以在着色器中安全地推导出 $z$:

$$z = \sqrt{\max(0.0, 1.0 - x^2 - y^2)}$$

通过在压缩阶段丢弃 Z 通道,我们可以把原本分配给 Z 的宝贵码率(Bitrate)全部倾斜给 R 和 G 通道,从而实现画质的倍增。


二、 BC5 压缩方案(桌面端/WebGPU 主机端)

BC5(在 Direct3D 中称为 BC5,在 OpenGL/Vulkan 中常称为 RGTC2)是专门为双通道数据设计的块压缩格式。

1. 存储机制

  • 块大小:8x8 像素的图像块被压缩为 16 字节(128 bit),每个通道分配 64 bit。
  • 高精度独立编码:BC5 的 R 和 G 通道是完全独立编码的,每个通道使用类似于 BC4(或 DXT5 Alpha 块)的渐变区间插值算法。它在块内定义两个 8 位的基准值,然后生成 6 或 8 个渐变插值点,每个像素用 3 个 bit 来索引这些插值。
  • 无通道干扰:由于 R 和 G 物理上完全分立,彻底杜绝了 BC3 中 RGB 共享终点色导致的色彩交叉干扰,法线梯度的平滑度极高。

2. WebGPU 格式选择

在 WebGPU 中使用 BC5 需要开启 "texture-compression-bc" 扩展。推荐使用 bc5-snorm 格式,而不是 bc5-unorm

// WebGPU 设备初始化时需请求扩展
const device = await adapter.requestDevice({
  requiredFeatures: ["texture-compression-bc"]
});

// 创建纹理时使用 bc5-snorm
const normalTexture = device.createTexture({
  size: [width, height, 1],
  format: "bc5-snorm", // 自动将 [-1.0, 1.0] 的有符号数据映射到内部
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
});

为什么优先选择 bc5-snorm

  • bc5-unorm:采样器返回的值域在 $[0.0, 1.0]$。在着色器中,你需要手动进行重映射:$x = x_{raw} \times 2.0 - 1.0$。
  • bc5-snorm:采样器返回的值域直接是 $[-1.0, 1.0]$。WebGPU 驱动层和硬件会自动在采样时完成解包,消除了着色器中的算术开销,性能更优。

三、 ASTC 压缩方案(移动端/跨平台)

在移动端设备(如 iOS、Android)上,BC5 并不被硬件原生支持。我们需要使用 ASTC(Adaptive Scalable Texture Compression)格式(WebGPU 扩展为 "texture-compression-astc")。

ASTC 极其灵活,但若直接将普通 RGB 格式的法线丢给 ASTC 进行块压缩,由于其内部色度/亮度终点分配机制,仍可能在边缘产生伪影。

1. 精确存储:ASTC_RA(双平面模式)

为了在 ASTC 中精确保留 RG 通道,图形学界通用的做法是双平面编码(Dual-Plane Mode),通常称为 ASTC_RA 布局:

  • 将法线的 $X$ 写入 Red 通道。
  • 将法线的 $Y$ 写入 Alpha 通道(而非 Green)。
  • 在 ASTC 编码器中,启用双平面压缩(Dual-Plane),并将第二个平面指定为 Alpha 通道。

技术细节:ASTC 支持将一个块的比特流分配给两个独立的平面(例如,Plane 1 负责 RGB,Plane 2 负责 Alpha)。通过将 $X$ 和 $Y$ 分别放在 Red 和 Alpha 两个通道,并开启双平面模式,这两个通道就获得了完全独立的端点插值。这与 BC5 的独立编码思想异曲同工,能极大限度降低通道间的串扰。

2. 编码器配置示例

使用 ARM 官方的 astcenc 命令行工具进行法线贴图编码时,应指定 -normal 模式(该模式会自动选择双平面,并将 Y 分量映射至 Alpha 通道):

# 使用 6x6 块大小进行高品质法线压缩
astcenc -cl normal_map.png normal_map_6x6.astc 6x6 -normal -thorough

3. WebGPU 格式映射

由于 ASTC 在采样时会将未指定的通道填充为默认值,在 WebGPU 中,我们通常使用通用格式如 astc-4x4-unormastc-6x6-unorm

const normalTexture = device.createTexture({
  size: [width, height, 1],
  format: "astc-6x6-unorm", // 配合 astc_ra 布局
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
});

四、 WGSL 精确重建 Z 通道实现

在着色器中,我们需要根据采样的 RG 数据(或 RA 数据),以最高效的方式恢复出完整法线。

下面给出了针对 BC5 (Snorm)ASTC (RA 布局) 的 WGSL 核心采样函数实现:

// 绑定资源
@group(0) @binding(0) var normalSampler: sampler;
@group(0) @binding(1) var bc5NormalTex: texture_2d<f32>;   // bc5-snorm
@group(0) @binding(2) var astcNormalTex: texture_2d<f32>;  // astc-unorm (RA layout)

// ==========================================
// 1. 从 BC5-Snorm 纹理重建法线
// ==========================================
fn reconstructNormalBC5(uv: vec2<f32>) -> vec3<f32> {
    // bc5-snorm 采样后,xy 分量已经自动映射在 [-1.0, 1.0] 区间
    let raw = textureSample(bc5NormalTex, normalSampler, uv).xy;
    
    let x = raw.x;
    let y = raw.y;
    
    // 浮点数计算误差可能导致 1.0 - x^2 - y^2 略小于 0,必须使用 max 进行保护
    let z = sqrt(max(0.0, 1.0 - x * x - y * y));
    
    return vec3<f32>(x, y, z);
}

// ==========================================
// 2. 从 ASTC (RA 布局) 纹理重建法线
// ==========================================
fn reconstructNormalASTC_RA(uv: vec2<f32>) -> vec3<f32> {
    // 采样 unorm 纹理,返回 [0.0, 1.0] 区间的值
    let raw = textureSample(astcNormalTex, normalSampler, uv);
    
    // 提取 Red (X) 和 Alpha (Y) 通道,并手动映射到 [-1.0, 1.0]
    let x = raw.r * 2.0 - 1.0;
    let y = raw.a * 2.0 - 1.0;
    
    // 重建 Z 通道
    let z = sqrt(max(0.0, 1.0 - x * x - y * y));
    
    return vec3<f32>(x, y, z);
}

关键细节说明:

  1. 防止 NaN 异常:由于浮点插值与压缩精度损失,若某个像素的法线在横向上被拉扯得非常极限,计算 $1.0 - x^2 - y^2$ 时可能会得到微小的负数(例如 -0.00001)。直接对其求平方根(sqrt)会产生 NaN,导致渲染出黑点。在 WGSL 中,使用 max(0.0, ...) 进行截断是极其必要的一步防错。
  2. 切线空间转换:重建出来的 vec3<f32>(x, y, z) 处于切线空间,后续可通过典型的 TBN(Tangent, Bitangent, Normal)矩阵将其变换至世界空间参与光照计算。

五、 性能与画质对比总结

特性 / 格式 BC5 (snorm) ASTC (RA layout, 6x6)
平台支持 桌面端 (Windows, macOS) 移动端 (iOS, Android), Apple Silicon Mac
显存占用 (BPP) 8 bpp (每个像素 1 字节) 3.56 bpp (对于 6x6 块大小)
着色器算术开销 极低 (无重映射, 仅 1x max, 1x sqrt) 较低 (需执行一次 MAD 操作重映射)
RG通道隔离度 绝对物理隔离(高) 强隔离(双平面独立端点分配)
重建品质 极高,无色差,无宏块效应 高,通过调节 Block Size 可弹性微调画质/带宽

生产环境最佳实践指南:

  • 多平台资产分发:在打包 WebGPU 应用程序时,建议通过检测当前环境支持的 Feature(device.features.has("texture-compression-bc") 还是 "texture-compression-astc"),在运行时分发不同的纹理资产。
  • 对于精度要求严苛的微表面(如雨水冲刷的金属、高光瓷砖):在 PC 端坚定选择 bc5-snorm;在移动端,若显存和带宽允许,ASTC 推荐使用 4x45x5 块大小,并始终采用 RA 双平面模式进行编码。
极客光影 WebGPU纹理压缩图形学

评论点评