引言:什么是抖动技术?
在计算机图形学中,抖动技术(Dithering)是一种通过人为引入噪声来模拟更多颜色或透明度级别的算法。当输出设备(如显示器)的色彩精度低于渲染精度时,抖动技术能有效减少色带效应(Banding Artifacts),创造出更平滑的渐变效果。
其中Bayer抖动(Bayer Dithering)作为一种有序抖动(Ordered Dithering)算法,因其规则性强、实现高效,成为游戏开发和实时渲染中最常用的抖动技术。
一、Bayer 矩阵的核心原理
1.1 基础生成规则
所有Bayer矩阵都源于2×2基础模式:
[ 0, 2 ]
[ 3, 1 ]
这个看似简单的矩阵是经过数学验证的最优分布,具有:
● 相邻元素最大平均差(2.0)
● 完美的旋转对称性
● 递归扩展兼容性
1.2 递归扩展算法
更大尺寸矩阵通过递归规则生成:
using System;
using System.Collections.Generic;
public class BayerMatrixGenerator
{
// 基础2x2 Bayer矩阵
private static readonly int[,] BaseBayer2x2 =
{
{ 0, 2 },
{ 3, 1 }
};
/// <summary>
/// 生成指定尺寸的Bayer抖动矩阵
/// </summary>
/// <param name="sizeLog2">矩阵尺寸的2的幂次(如2表示4x4,3表示8x8)</param>
/// <returns>归一化的抖动矩阵(0-1范围)</returns>
public static float[,] GenerateBayerMatrix(int sizeLog2)
{
// 计算实际尺寸
int size = (int)Mathf.Pow(2, sizeLog2);
// 从基础2x2矩阵开始递归扩展
int[,] matrix = BaseBayer2x2;
for (int i = 1; i < sizeLog2; i++)
{
matrix = ExpandMatrix(matrix);
}
// 转换为Unity常用的归一化值(0-1范围)
return NormalizeMatrix(matrix);
}
/// <summary>
/// 扩展Bayer矩阵(递归核心)
/// </summary>
private static int[,] ExpandMatrix(int[,] m)
{
int n = m.GetLength(0);
int newSize = n * 2;
int[,] newM = new int[newSize, newSize];
for (int y = 0; y < n; y++)
{
for (int x = 0; x < n; x++)
{
int v = m[y, x];
// 四象限规则
newM[y, x] = 4 * v; // 左上
newM[y, x + n] = 4 * v + 2; // 右上
newM[y + n, x] = 4 * v + 3; // 左下
newM[y + n, x + n] = 4 * v + 1; // 右下
}
}
return newM;
}
/// <summary>
/// 矩阵归一化(将整数值转换到0-1范围)
/// </summary>
private static float[,] NormalizeMatrix(int[,] matrix)
{
int size = matrix.GetLength(0);
float maxValue = size * size - 1;
float[,] result = new float[size, size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
// 两种归一化方式可选:
// 1. 标准方式:value / (size²-1)
// result[y, x] = matrix[y, x] / maxValue;
// 2. 优化方式(行反序+值域映射):(size² - value) / size²
result[y, x] = (maxValue + 1 - matrix[size - 1 - y, x]) / (maxValue + 1);
}
}
return result;
}
/// <summary>
/// 打印矩阵(调试用)
/// </summary>
public static void PrintMatrix(float[,] matrix)
{
int size = matrix.GetLength(0);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine($"Bayer {size}x{size} Matrix:");
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
sb.Append(matrix[y, x].ToString("F4").PadLeft(7) + " ");
}
sb.AppendLine();
}
Debug.Log(sb.ToString());
}
}
1.3 值域映射优化
原始生成值范围为 [0, N²-1],但实践中会进行优化处理:
optimized = (N² - original) # 行反序 + 值域映射
例如4×4矩阵优化前后对比:
原始: 优化后:
[ 0, 8, 2,10] [16, 8,14, 6]
[12, 4,14, 6] [ 4,12, 2,10]
[ 3,11, 1, 9] [13, 5,15, 7]
[15, 7,13, 5] [ 1, 9, 3,11]
这种优化使阈值分布更均匀,能有效减少可见图案重复。
二、标准矩阵参考手册
2.1 2×2 Bayer矩阵(4级)
整数值: 归一化值:
[ 0, 2 ] [ 0.00, 0.50 ]
[ 3, 1 ] [ 0.75, 0.25 ]
适用场景:移动设备、复古像素风渲染
2.2 4×4 Bayer矩阵(16级)
优化后整数值: 归一化值:
[16, 8,14, 6] [1.00,0.50,0.88,0.38]
[ 4,12, 2,10] [0.25,0.75,0.13,0.63]
[13, 5,15, 7] [0.81,0.31,0.94,0.44]
[ 1, 9, 3,11] [0.06,0.56,0.19,0.69]
适用场景:全高清分辨率平衡方案
2.3 8×8 Bayer矩阵(64级)
优化后整数值:
[ 1,49,13,61, 4,52,16,64]
[33,17,45,29,36,20,48,32]
[ 9,57, 5,53,12,60, 8,56]
[41,25,37,21,44,28,40,24]
[ 3,51,15,63, 2,50,14,62]
[35,19,47,31,34,18,46,30]
[11,59, 7,55,10,58, 6,54]
[43,27,39,23,42,26,38,22]
适用场景:PC/主机游戏、影视级渲染
三、实现方案
3.1 硬编码实现(推荐≤8×8)
**** hlsl 代码****
float Dither4x4(int x, int y)
{
x %= 4; y %= 4;
const float dither[16] = {
1.00,0.50,0.88,0.38,
0.25,0.75,0.13,0.63,
0.81,0.31,0.94,0.44,
0.06,0.56,0.19,0.69
};
return dither[y*4 + x];
}
**** CG ****
float dither = 1.0;
float2 ditherUV = screenUV * _ScreenParams.xy;
dither = Dither8x8Bayer(fmod(ditherUV.x, 8), fmod(ditherUV.y, 8));
clip(dither - 0.5);
float Dither_8x8Bayer( int x, int y )
{
const float dither[ 64 ] = {
1, 49, 13, 61, 4, 52, 16, 64,
33, 17, 45, 29, 36, 20, 48, 32,
9, 57, 5, 53, 12, 60, 8, 56,
41, 25, 37, 21, 44, 28, 40, 24,
3, 51, 15, 63, 2, 50, 14, 62,
35, 19, 47, 31, 34, 18, 46, 30,
11, 59, 7, 55, 10, 58, 6, 54,
43, 27, 39, 23, 42, 26, 38, 22};
int r = y * 8 + x;
return dither[r] / 64;
}
3.2 纹理采样实现(推荐≥16×16)
**** hlsl 代码****
Texture2D<float> _BayerTex;
SamplerState sampler_BayerTex;
float Dither128x128(float2 uv)
{
// 使用重复寻址模式
return _BayerTex.Sample(sampler_BayerTex, uv * _ScreenParams.xy/128);
}
3.3 实时位运算实现
**** hlsl 代码****
float BayerDither(uint2 pos, uint sizeLog2)
{
uint d = 0;
for(uint i=0; i<sizeLog2; i++) {
d |= ((pos.x >> i) & 1) << (2*i) |
((pos.y >> i) & 1) << (2*i+1);
}
return float(ReverseBits(d)) / exp2(2*sizeLog2);
}
四、应用场景与性能优化
4.1 典型应用场景
1、透明度裁剪(Alpha Clipping)
**** hlsl 代码****
clip(alpha - GetDither(screenPos));
2、色深模拟(Color Depth Reduction)
float3 quantized = floor(color * 16 + GetDither(screenPos)) / 16;
3、屏幕空间效果
● 半透明材质(纱窗、烟雾)
● 水折射边缘
● 毛发抗锯齿
4.2 性能优化策略
技术 | 适应场景 | VRAM占用 | 计算开销 |
---|---|---|---|
硬编码 | ≤8×8矩阵 | 0 | 极低 |
纹理采样 | 任意尺寸 | 4-64KB | 中等 |
程序生成 | 动态LOD | 0 | 高 |
动态LOD方案:
int GetDitherSize(float distance)
{
float t = saturate(distance / _MaxDitherDist);
return lerp(32, 4, t*t); // 近距离用大矩阵
}
结语:为什么Bayer抖动历久弥新?
Bayer抖动自1973年由柯达科学家Bryce Bayer发明以来,因其完美的数学结构和硬件友好性,在图形学领域持续发光发热。随着4K/8K显示设备的普及,大尺寸Bayer矩阵(128×128)重新获得关注,成为平衡画质与性能的利器。
"好的抖动算法让有限的色彩绽放无限可能。"
—— 图形学格言
知识要点备忘:
- 始终从 [0,2; 3,1] 基础矩阵开始
- 优化时进行行反序+值域映射
- 8×8是桌面游戏的黄金尺寸
- 大尺寸矩阵(>32)建议用纹理采样
- 动态LOD可节省50%+性能