Bayer 抖动矩阵

引言:什么是抖动技术?

在计算机图形学中,抖动技术(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)重新获得关注,成为平衡画质与性能的利器。
"好的抖动算法让有限的色彩绽放无限可能。"
—— 图形学格言

知识要点备忘:

  1. 始终从 [0,2; 3,1] 基础矩阵开始
  2. 优化时进行行反序+值域映射
  3. 8×8是桌面游戏的黄金尺寸
  4. 大尺寸矩阵(>32)建议用纹理采样
  5. 动态LOD可节省50%+性能
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容