文章目录

  • 什么时 Dithering
  • 色阶纹理图案 - Texture Dither Pattern
  • 程序化 动态 Dithering - 让 RGBA8888 压缩到 RGBA4444 而没有明显色阶
    • Floyd-Steinberg
      • Unity 自带的 RGBA32->RGBA4444
      • 使用 keijiro dither 4444 优化
      • RGBA32->RGB565
  • Bayer-Matrix-Dithering - 运行时的Dithering
    • Unity 中的 Bayer-Matrix-Dithering
      • Original Shader
      • Optimzied Shader
      • Optimized Shader Version2
      • Optimzied Shader Version 3
      • 简单理解 Bayer-Matrix 的生产过程
      • 效果
  • 引用大神统计的一张 dither 算法图
  • Project
  • References

什么时 Dithering

Introduction——Dithering 是什么

Dither is an intentionally applied form of noise used to randomize quantization error, preventing large-scale patterns such as color banding in images.

从Wiki的解释可以看出,Dithering所要处理的问题是信号量化误差引起的大尺度pattern,使用的手段是噪声。

总之我觉得,Dithering 的算法真的很神奇

这种算法也能前人发现,我是真的佩服

下面主要演示一些:

  • Texture Dither Pattern
  • Floyd-Steinberg Dithering
  • Bayer-Matrix-Dithering

色阶纹理图案 - Texture Dither Pattern

下面的 dither 纹理图 3x3 像素,参考与:数字半色调技术/RIP/dithering algorithm


程序化 动态 Dithering - 让 RGBA8888 压缩到 RGBA4444 而没有明显色阶

参考:keijiro/unity-dither4444 中的,将 RGBA32 优化为 RGBA4444,而且没有明显的色阶块


Floyd-Steinberg


Unity 自带的 RGBA32->RGBA4444

如,下面四张 图,左边的是 RGBA32,右边的是 RGBA4444,可以看到有大块的明显的色阶块



使用 keijiro dither 4444 优化

思路是:Floyd–Steinberg 的算法

如,下面四张 图,左边的是 RGBA32,右边的是 RGBA4444,可以看到有大块的明显的色阶块

下面是主要的核心算法代码:TextureModifier.cs

using UnityEngine;
using UnityEditor;
using System.Collections;class TextureModifier : AssetPostprocessor
{void OnPreprocessTexture(){var importer = (assetImporter as TextureImporter);importer.textureType = TextureImporterType.GUI;if (assetPath.EndsWith ("Dither.png")) {importer.textureFormat = TextureImporterFormat.RGBA32;}}void OnPostprocessTexture (Texture2D texture){if (!assetPath.EndsWith ("Dither.png")) {return;}var texw = texture.width;var texh = texture.height;var pixels = texture.GetPixels ();var offs = 0;var k1Per15 = 1.0f / 15.0f;var k1Per16 = 1.0f / 16.0f;var k3Per16 = 3.0f / 16.0f;var k5Per16 = 5.0f / 16.0f;var k7Per16 = 7.0f / 16.0f;for (var y = 0; y < texh; y++) {for (var x = 0; x < texw; x++) {float a = pixels [offs].a;float r = pixels [offs].r;float g = pixels [offs].g;float b = pixels [offs].b;var a2 = Mathf.Clamp01 (Mathf.Floor (a * 16) * k1Per15);var r2 = Mathf.Clamp01 (Mathf.Floor (r * 16) * k1Per15);var g2 = Mathf.Clamp01 (Mathf.Floor (g * 16) * k1Per15);var b2 = Mathf.Clamp01 (Mathf.Floor (b * 16) * k1Per15);var ae = a - a2;var re = r - r2;var ge = g - g2;var be = b - b2;pixels [offs].a = a2;pixels [offs].r = r2;pixels [offs].g = g2;pixels [offs].b = b2;var n1 = offs + 1;var n2 = offs + texw - 1;var n3 = offs + texw;var n4 = offs + texw + 1;if (x < texw - 1) {pixels [n1].a += ae * k7Per16;pixels [n1].r += re * k7Per16;pixels [n1].g += ge * k7Per16;pixels [n1].b += be * k7Per16;}if (y < texh - 1) {pixels [n3].a += ae * k5Per16;pixels [n3].r += re * k5Per16;pixels [n3].g += ge * k5Per16;pixels [n3].b += be * k5Per16;if (x > 0) {pixels [n2].a += ae * k3Per16;pixels [n2].r += re * k3Per16;pixels [n2].g += ge * k3Per16;pixels [n2].b += be * k3Per16;}if (x < texw - 1) {pixels [n4].a += ae * k1Per16;pixels [n4].r += re * k1Per16;pixels [n4].g += ge * k1Per16;pixels [n4].b += be * k1Per16;}}offs++;}}texture.SetPixels (pixels);EditorUtility.CompressTexture (texture, TextureFormat.RGBA4444, TextureCompressionQuality.Best);}
}

RGBA32->RGB565

Unity图片优化神器 - dither算法究极进化方案 - 将 Floyd–Steinberg dithering 系数修改了一些,并将 RGBA32 dither 后再压缩到 RGB565

void OnPostprocessTexture (Texture2D texture)
{if(assetPath.Contains ("_dither565")){var texw = texture.width;var texh = texture.height;var pixels = texture.GetPixels ();var offs = 0;var k1Per31 = 1.0f / 31.0f;var k1Per32 = 1.0f / 32.0f;var k5Per32 = 5.0f / 32.0f;var k11Per32 = 11.0f / 32.0f;var k15Per32 = 15.0f / 32.0f;var k1Per63 = 1.0f / 63.0f;var k3Per64 = 3.0f / 64.0f;var k11Per64 = 11.0f / 64.0f;var k21Per64 = 21.0f / 64.0f;var k29Per64 = 29.0f / 64.0f;var k_r = 32; //R&B压缩到5位,所以取2的5次方var k_g = 64; //G压缩到6位,所以取2的6次方for(var y = 0; y < texh; y++){for(var x = 0; x < texw; x++){float r = pixels [offs].r;float g = pixels [offs].g;float b = pixels [offs].b;var r2 = Mathf.Clamp01 (Mathf.Floor (r * k_r) * k1Per31);var g2 = Mathf.Clamp01 (Mathf.Floor (g * k_g) * k1Per63);var b2 = Mathf.Clamp01 (Mathf.Floor (b * k_r) * k1Per31);var re = r - r2;var ge = g - g2;var be = b - b2;var n1 = offs + 1;var n2 = offs + texw - 1;var n3 = offs + texw;var n4 = offs + texw + 1;if(x < texw - 1){pixels [n1].r += re * k15Per32;pixels [n1].g += ge * k29Per64;pixels [n1].b += be * k15Per32;}if(y < texh - 1){pixels [n3].r += re * k11Per32;pixels [n3].g += ge * k21Per64;pixels [n3].b += be * k11Per32;if(x > 0){pixels [n2].r += re * k5Per32;pixels [n2].g += ge * k11Per64;pixels [n2].b += be * k5Per32;}if(x < texw - 1){pixels [n4].r += re * k1Per32;pixels [n4].g += ge * k3Per64;pixels [n4].b += be * k1Per32;}}pixels [offs].r = r2;pixels [offs].g = g2;pixels [offs].b = b2;offs++;}}texture.SetPixels (pixels);EditorUtility.CompressTexture (texture, TextureFormat.RGB565, TextureCompressionQuality.Best);}
}

Bayer-Matrix-Dithering - 运行时的Dithering


Unity 中的 Bayer-Matrix-Dithering


Original Shader

// jave.lin 2021/12/21
// 测试再 shader 上 bayer-matrix-dithering 的效果(未优化)
// references : https://github.com/libretro/glsl-shaders/blob/master/dithering/shaders/bayer-matrix-dithering.glslShader "Test/TestingDithering_UnOptimize"
{Properties{_MainTex ("Texture", 2D) = "white" {}FrameCount ("FrameCount", Int) = 2OutputSize ("OutputSize", Vector) = (1, 1, 1, 1)animate("animate", Range(0, 1)) = 1dither_size("dither_size", Range(0, 100)) = 0.05}
CGINCLUDE#include "UnityCG.cginc"uniform int FrameCount;
uniform float2 OutputSize;
uniform float animate;
uniform float dither_size;
sampler2D _MainTex;
float4 _MainTex_ST;struct appdata
{float4 positionOS : POSITION;float2 uv : TEXCOORD0;
};
struct v2f
{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD0;
};fixed find_closest(int x, int y, fixed c0)
{int dither[8][8] = {{ 0, 32, 8, 40, 2, 34, 10, 42}, /* 8x8 Bayer ordered dithering */{48, 16, 56, 24, 50, 18, 58, 26}, /* pattern. Each input pixel */{12, 44, 4, 36, 14, 46, 6, 38}, /* is scaled to the 0..63 range */{60, 28, 52, 20, 62, 30, 54, 22}, /* before looking in this table */{ 3, 35, 11, 43, 1, 33, 9, 41}, /* to determine the action. */{51, 19, 59, 27, 49, 17, 57, 25},{15, 47, 7, 39, 13, 45, 5, 37},{63, 31, 55, 23, 61, 29, 53, 21} };fixed limit = 0.0;if (x < 8){limit = (dither[x][y] + 1) / 64.0;}if (c0 < limit)return 0.0;return 1.0;
}v2f vert(appdata v)
{v2f o;o.positionCS = UnityObjectToClipPos(v.positionOS);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;
}
fixed4 frag(v2f i) : SV_Target
{half Scale = 3.0 + fmod(2.0 * FrameCount, 32.0) * animate + dither_size;fixed4 lum = fixed4(0.299, 0.587, 0.114, 0);fixed4 mainCol = tex2D(_MainTex, i.uv);fixed3 rgb = mainCol.rgb;float2 xy = (i.uv * OutputSize.xy) * Scale;int x = int(fmod(xy.x, 8));int y = int(fmod(xy.y, 8));fixed3 finalRGB;finalRGB.r = find_closest(x, y, rgb.r);finalRGB.g = find_closest(x, y, rgb.g);finalRGB.b = find_closest(x, y, rgb.b);fixed grayscale = dot(mainCol, lum);fixed final = find_closest(x, y, grayscale);return final;//return fixed4(finalRGB, 1.0);
}
ENDCGSubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment fragENDCG}}
}

Optimzied Shader

这个是优化代码后的写法,去掉部分没用的 uniform 和优化代码写法,并增加了变体:Dither 开关、是否之对灰度 dither

// jave.lin 2021/12/21
// 测试再 shader 上 bayer-matrix-dithering 的效果(已优化)
// references : https://github.com/libretro/glsl-shaders/blob/master/dithering/shaders/bayer-matrix-dithering.glslShader "Test/TestingDithering_Optimized"
{Properties{_MainTex ("Texture", 2D) = "white" {}_DitherSize ("Dihter Size(xy:uv scale, z:u&v scale)", Vector) = (1, 1, 1, 0)[Toggle] _DITHERING ("Dithering", Float) = 1[Toggle] _ONLY_GRAY ("Only Gray", Float) = 0}CGINCLUDE#include "UnityCG.cginc"uniform sampler2D _MainTex;uniform float4 _MainTex_ST;uniform half4 _DitherSize;struct appdata{float4 positionOS : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD0;};fixed find_closest(int x, int y, fixed col){/*jave.lin : 下面的 8x8 矩阵的由来:先以一个2×2的矩阵开始,如下 M1M1 = | 0 2 || 3 1 |再有一个 Un 矩阵所有元素都是1,并且 Un 的方阵 n == Mn 的 n,即:U 的维度等于 M的如下 M1 是 2x2 的,那么 U1 也是 2x2 的,如下U1 = | 1 1 || 1 1 |然后套如下面的公式M_{n+1} = | 4M_{n}     4M_{n}+2U |= | 4M_{n}+3U  4M_{n}+ U |那么 M2 等于如下M2 = | 4M1      4M1+2U1 |= | 4M1+3U1  4M1+ U1 |同理 M3 如下M3 = | 4M2      4M2+2U2 |= | 4M2+3U2  4M2+ U2 |...经过上面的讲解,可以了解到下面到 8x8 的时候,正好就是 M3 的*/const int dither[8][8] = {{ 0,  32, 8,  40, 2,  34, 10, 42 }, /* 8x8 Bayer ordered dithering */{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */{ 12, 44, 4,  36, 14, 46, 6,  38 }, /* is scaled to the 0..63 range */{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */{ 3,  35, 11, 43, 1,  33, 9,  41 }, /* to determine the action. */{ 51, 19, 59, 27, 49, 17, 57, 25 },{ 15, 47, 7,  39, 13, 45, 5,  37 },{ 63, 31, 55, 23, 61, 29, 53, 21 } };fixed limit = (dither[x][y] + 1) / 64.0;//if (col < limit)//    return 0.0;//return 1.0;// 使用 step 优化return step(limit, col);}fixed find_map_t(int x, int y, fixed col){/*jave.lin : 8x8 和 find_closest 中的 8x8 矩阵一致*/const int dither[8][8] = {{ 0,  32, 8,  40, 2,  34, 10, 42 }, /* 8x8 Bayer ordered dithering */{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */{ 12, 44, 4,  36, 14, 46, 6,  38 }, /* is scaled to the 0..63 range */{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */{ 3,  35, 11, 43, 1,  33, 9,  41 }, /* to determine the action. */{ 51, 19, 59, 27, 49, 17, 57, 25 },{ 15, 47, 7,  39, 13, 45, 5,  37 },{ 63, 31, 55, 23, 61, 29, 53, 21 } };fixed limit = (dither[x][y] + 1) / 64.0;if (limit == 0.0)return 0.0;elsereturn col / limit;}fixed3 find_map_t(int x, int y, fixed3 col){/*jave.lin : 8x8 和 find_closest 中的 8x8 矩阵一致*/const int dither[8][8] = {{ 0,  32, 8,  40, 2,  34, 10, 42 }, /* 8x8 Bayer ordered dithering */{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */{ 12, 44, 4,  36, 14, 46, 6,  38 }, /* is scaled to the 0..63 range */{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */{ 3,  35, 11, 43, 1,  33, 9,  41 }, /* to determine the action. */{ 51, 19, 59, 27, 49, 17, 57, 25 },{ 15, 47, 7,  39, 13, 45, 5,  37 },{ 63, 31, 55, 23, 61, 29, 53, 21 } };const float d = 1.0 / 64.0;fixed limit = (dither[x][y] + 1) * d;if (limit == 0.0)    return 0.0;else return col / limit; jave.lin : 优化成 step & lerp//fixed notZero = step(0, limit);//return lerp(0.0, col * (1.0 / limit), notZero);return col * (1.0 / limit);}v2f vert(appdata v){v2f o;o.positionCS = UnityObjectToClipPos(v.positionOS);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}fixed4 frag(v2f i) : SV_Target{fixed4 mainCol = tex2D(_MainTex, i.uv);#ifndef _DITHERING_ONreturn mainCol;#else // #ifndef _DITHERING_ONfloat2 xy = (i.uv * _DitherSize.xy) * _DitherSize.z;int x = int(fmod(xy.x, 8)); // jave.lin : return : 0, 1, 2, 3, 4, 5, 6, 7//return x / 7.0; // jave.lin : 水平 7 灰度色阶int y = int(fmod(xy.y, 8)); // jave.lin : return : 0, 1, 2, 3, 4, 5, 6, 7//return y / 7.0; // jave.lin : 垂直 7 灰度色阶# ifdef _ONLY_GRAY_ONfixed4 lum = fixed4(0.299, 0.587, 0.114, 0);fixed grayscale = dot(mainCol, lum);fixed final = find_closest(x, y, grayscale);return final;#    else // #   ifdef _ONLY_GRAY_ONfixed3 finalRGB = mainCol.rgb;finalRGB.r = find_closest(x, y, finalRGB.r);finalRGB.g = find_closest(x, y, finalRGB.g);finalRGB.b = find_closest(x, y, finalRGB.b);return fixed4(finalRGB, 1.0);# endif // end #  ifdef _ONLY_GRAY_ON#endif // end #ifndef _DITHERING_ON}ENDCGSubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma multi_compile _ _DITHERING_ON#pragma multi_compile _ _ONLY_GRAY_ON#pragma vertex vert#pragma fragment fragENDCG}}
}

Optimized Shader Version2

// jave.lin 2021/12/21
// 测试再 shader 上 bayer-matrix-dithering 的效果(已优化Version2)
// references : https://github.com/libretro/glsl-shaders/blob/master/dithering/shaders/bayer-matrix-dithering.glslShader "Test/TestingDithering_OptimizedV2"
{Properties{_MainTex ("Texture", 2D) = "white" {}_DitherSize ("Dihter Size(xy:uv scale, z:u&v scale)", Vector) = (1, 1, 1, 0)[Toggle] _DITHERING ("Dithering", Float) = 1[Toggle] _ONLY_GRAY ("Only Gray", Float) = 0}CGINCLUDE#include "UnityCG.cginc"uniform sampler2D _MainTex;uniform float4 _MainTex_ST;uniform half4 _DitherSize;struct appdata{float4 positionOS : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD0;};fixed find_closest(int x, int y, inout fixed col){/*jave.lin : 参考:- https://blog.csdn.net/paris_he/article/details/40341233- https://en.wikipedia.org/wiki/Ordered_dithering下面的 8x8 矩阵的由来:先以一个2×2的矩阵开始,如下 M1M1 = | 0 2 || 3 1 |再有一个 Un 矩阵所有元素都是1,并且 Un 的方阵 n == Mn 的 n,即:U 的维度等于 M的如下 M1 是 2x2 的,那么 U1 也是 2x2 的,如下U1 = | 1 1 || 1 1 |然后套如下面的公式M_{n+1} = | 4M_{n}     4M_{n}+2U |= | 4M_{n}+3U  4M_{n}+ U |那么 M2 等于如下M2 = | 4M1      4M1+2U1 |= | 4M1+3U1  4M1+ U1 |同理 M3 如下M3 = | 4M2      4M2+2U2 |= | 4M2+3U2  4M2+ U2 |...经过上面的讲解,可以了解到下面到 8x8 的时候,正好就是 M3 的*/const int dither[8][8] = {{ 0,  32, 8,  40, 2,  34, 10, 42 }, /* 8x8 Bayer ordered dithering */{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */{ 12, 44, 4,  36, 14, 46, 6,  38 }, /* is scaled to the 0..63 range */{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */{ 3,  35, 11, 43, 1,  33, 9,  41 }, /* to determine the action. */{ 51, 19, 59, 27, 49, 17, 57, 25 },{ 15, 47, 7,  39, 13, 45, 5,  37 },{ 63, 31, 55, 23, 61, 29, 53, 21 } };fixed limit = (dither[x][y] + 1) / 64.0;//if (col < limit)//  return 0.0;//return 1.0;// 使用 step 优化// jave.lin : // 如果我们的纹理格式支持 R1G1B1,也就是 RGB111的话// 可以压缩到原来的只有原来的 1/8 大小,但是目前没又看到又这样的格式纹理(因为这个效果也就 30~50 年前的需求能接受)return step(limit, col); // 压缩到 1 or 0}void find_closest(int x, int y, inout fixed r, inout fixed g, inout fixed b){const int dither[8][8] = {{ 0,  32, 8,  40, 2,  34, 10, 42 }, /* 8x8 Bayer ordered dithering */{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */{ 12, 44, 4,  36, 14, 46, 6,  38 }, /* is scaled to the 0..63 range */{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */{ 3,  35, 11, 43, 1,  33, 9,  41 }, /* to determine the action. */{ 51, 19, 59, 27, 49, 17, 57, 25 },{ 15, 47, 7,  39, 13, 45, 5,  37 },{ 63, 31, 55, 23, 61, 29, 53, 21 } };fixed limit = (dither[x][y] + 1) / 64.0;r = step(limit, r); // 压缩到 1 or 0g = step(limit, g); // 压缩到 1 or 0b = step(limit, b); // 压缩到 1 or 0}v2f vert(appdata v){v2f o;o.positionCS = UnityObjectToClipPos(v.positionOS);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}fixed4 frag(v2f i) : SV_Target{fixed4 mainCol = tex2D(_MainTex, i.uv);#ifndef _DITHERING_ONreturn mainCol;#else // #ifndef _DITHERING_ONfloat2 xy = (i.uv * _DitherSize.xy) * _DitherSize.z;int x = int(fmod(xy.x, 8)); // jave.lin : return : 0, 1, 2, 3, 4, 5, 6, 7//return x / 7.0; // jave.lin : 水平 7 灰度色阶int y = int(fmod(xy.y, 8)); // jave.lin : return : 0, 1, 2, 3, 4, 5, 6, 7//return y / 7.0; // jave.lin : 垂直 7 灰度色阶# ifdef _ONLY_GRAY_ONfixed4 lum = fixed4(0.299, 0.587, 0.114, 0);fixed grayscale = dot(mainCol, lum);fixed final = find_closest(x, y, grayscale);return final;#    else // #   ifdef _ONLY_GRAY_ONfind_closest(x, y, mainCol.r, mainCol.g, mainCol.b);return fixed4(mainCol.rgb, 1);#  endif // end #  ifdef _ONLY_GRAY_ON#endif // end #ifndef _DITHERING_ON}ENDCGSubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma multi_compile _ _DITHERING_ON#pragma multi_compile _ _ONLY_GRAY_ON#pragma vertex vert#pragma fragment fragENDCG}}
}

Optimzied Shader Version 3

增加了 dither size x2, x4, x8 的变体

// jave.lin 2021/12/21
// 测试再 shader 上 bayer-matrix-dithering 的效果(已优化Version3)
// references : https://github.com/libretro/glsl-shaders/blob/master/dithering/shaders/bayer-matrix-dithering.glslShader "Test/TestingDithering_OptimizedV3"
{Properties{_MainTex ("Texture", 2D) = "white" {}_DitherSize ("Dihter Size(xy:uv scale, z:u&v scale)", Vector) = (1, 1, 1, 0)[Toggle] _DITHERING ("Dithering", Float) = 1[Toggle] _ONLY_GRAY ("Only Gray", Float) = 0[KeywordEnum(X2, X4, X8)] _DITHER_MAT("Dither Matrix Size",Float) = 2}CGINCLUDE#include "UnityCG.cginc"uniform sampler2D _MainTex;uniform float4 _MainTex_ST;uniform half4 _DitherSize;struct appdata{float4 positionOS : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD0;};#ifdef _DITHER_MAT_X2
#   define DITHER_SIZE 2
#   define DITHER_NUM 4.0
#elif _DITHER_MAT_X4
#   define DITHER_SIZE 4
#   define DITHER_NUM 16.0
#else // _DITHER_MAT_X8
#   define DITHER_SIZE 8
#   define DITHER_NUM 64.0
#endifinline fixed get_limit(int x, int y){/*jave.lin : 参考:- https://blog.csdn.net/paris_he/article/details/40341233- https://en.wikipedia.org/wiki/Ordered_dithering下面的 8x8 矩阵的由来:先以一个2×2的矩阵开始,如下 M1M1 = | 0 2 || 3 1 |再有一个 Un 矩阵所有元素都是1,并且 Un 的方阵 n == Mn 的 n,即:U 的维度等于 M的如下 M1 是 2x2 的,那么 U1 也是 2x2 的,如下U1 = | 1 1 || 1 1 |然后套如下面的公式M_{n+1} = | 4M_{n}     4M_{n}+2U |= | 4M_{n}+3U  4M_{n}+ U |那么 M2 等于如下M2 = | 4M1      4M1+2U1 |= | 4M1+3U1  4M1+ U1 |同理 M3 如下M3 = | 4M2      4M2+2U2 |= | 4M2+3U2  4M2+ U2 |...经过上面的讲解,可以了解到下面到 8x8 的时候,正好就是 M3 的*/
#ifdef _DITHER_MAT_X2const int dither[2][2] = {{ 0,  2 },  /* 4x4 Bayer ordered dithering */{ 3,  1 } };/* pattern. Each input pixel *//* is scaled to the 0..4 range *//* before looking in this table *//* to determine the action. */
#elif _DITHER_MAT_X4const int dither[4][4] = {{ 0,  8,  2,  10 },  /* 4x4 Bayer ordered dithering */{ 12, 4,  14, 6  },  /* pattern. Each input pixel */{ 3,  11, 1,  9  },  /* is scaled to the 0..16 range */{ 15, 7,  13, 5  } };/* before looking in this table *//* to determine the action. */#else // _DITHER_MAT_X8const int dither[8][8] = {{ 0,  32, 8,  40, 2,  34, 10, 42 }, /* 8x8 Bayer ordered dithering */{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */{ 12, 44, 4,  36, 14, 46, 6,  38 }, /* is scaled to the 0..63 range */{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */{ 3,  35, 11, 43, 1,  33, 9,  41 }, /* to determine the action. */{ 51, 19, 59, 27, 49, 17, 57, 25 },{ 15, 47, 7,  39, 13, 45, 5,  37 },{ 63, 31, 55, 23, 61, 29, 53, 21 } };
#endifreturn (dither[x][y] + 1) / DITHER_NUM;}fixed find_closest(int x, int y, inout fixed col){// jave.lin : // 如果我们的纹理格式支持 R1G1B1,也就是 RGB111的话// 可以压缩到原来的只有原来的 1/8 大小,但是目前没又看到又这样的格式纹理(因为这个效果也就 30~50 年前的需求能接受)fixed limit = get_limit(x, y);//if (col < limit)//  return 0.0;//return 1.0;// 使用 step 优化return step(limit, col); // 压缩到 1 or 0}void find_closest(int x, int y, inout fixed r, inout fixed g, inout fixed b){fixed limit = get_limit(x, y);r = step(limit, r); // 压缩到 1 or 0g = step(limit, g); // 压缩到 1 or 0b = step(limit, b); // 压缩到 1 or 0}v2f vert(appdata v){v2f o;o.positionCS = UnityObjectToClipPos(v.positionOS);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;}fixed4 frag(v2f i) : SV_Target{fixed4 mainCol = tex2D(_MainTex, i.uv);#ifndef _DITHERING_ONreturn mainCol;#else // #ifndef _DITHERING_ONfloat2 xy = (i.uv * _DitherSize.xy) * _DitherSize.z;int x = int(fmod(xy.x, DITHER_SIZE)); // jave.lin : return : 0, 1, 2, 3, 4, 5, 6, 7//return x / DITHER_SIZE; // jave.lin : 水平 7 灰度色阶int y = int(fmod(xy.y, DITHER_SIZE)); // jave.lin : return : 0, 1, 2, 3, 4, 5, 6, 7//return y / DITHER_SIZE; // jave.lin : 垂直 7 灰度色阶#   ifdef _ONLY_GRAY_ONfixed4 lum = fixed4(0.299, 0.587, 0.114, 0);fixed grayscale = dot(mainCol, lum);fixed final = find_closest(x, y, grayscale);return final;#    else // #   ifdef _ONLY_GRAY_ONfind_closest(x, y, mainCol.r, mainCol.g, mainCol.b);return fixed4(mainCol.rgb, 1);#  endif // end #  ifdef _ONLY_GRAY_ON#endif // end #ifndef _DITHERING_ON}ENDCGSubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma multi_compile _ _DITHERING_ON#pragma multi_compile _ _ONLY_GRAY_ON#pragma multi_compile _ _DITHER_MAT_X2 _DITHER_MAT_X4 _DITHER_MAT_X8#pragma vertex vert#pragma fragment fragENDCG}}
}

简单理解 Bayer-Matrix 的生产过程

 inline fixed get_limit(int x, int y){/*jave.lin : 参考:- https://blog.csdn.net/paris_he/article/details/40341233- https://en.wikipedia.org/wiki/Ordered_dithering下面的 8x8 矩阵的由来:先以一个2×2的矩阵开始,如下 M1M1 = | 0 2 || 3 1 |再有一个 Un 矩阵所有元素都是1,并且 Un 的方阵 n == Mn 的 n,即:U 的维度等于 M的如下 M1 是 2x2 的,那么 U1 也是 2x2 的,如下U1 = | 1 1 || 1 1 |然后套如下面的公式M_{n+1} = | 4M_{n}     4M_{n}+2U |= | 4M_{n}+3U  4M_{n}+ U |那么 M2 等于如下M2 = | 4M1      4M1+2U1 |= | 4M1+3U1  4M1+ U1 |同理 M3 如下M3 = | 4M2      4M2+2U2 |= | 4M2+3U2  4M2+ U2 |...经过上面的讲解,可以了解到下面到 8x8 的时候,正好就是 M3 的*/
#ifdef _DITHER_MAT_X2const int dither[2][2] = {{ 0,  2 },  /* 4x4 Bayer ordered dithering */{ 3,  1 } };/* pattern. Each input pixel *//* is scaled to the 0..4 range *//* before looking in this table *//* to determine the action. */
#elif _DITHER_MAT_X4const int dither[4][4] = {{ 0,  8,  2,  10 },  /* 4x4 Bayer ordered dithering */{ 12, 4,  14, 6  },  /* pattern. Each input pixel */{ 3,  11, 1,  9  },  /* is scaled to the 0..16 range */{ 15, 7,  13, 5  } };/* before looking in this table *//* to determine the action. */#else // _DITHER_MAT_X8const int dither[8][8] = {{ 0,  32, 8,  40, 2,  34, 10, 42 }, /* 8x8 Bayer ordered dithering */{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */{ 12, 44, 4,  36, 14, 46, 6,  38 }, /* is scaled to the 0..63 range */{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */{ 3,  35, 11, 43, 1,  33, 9,  41 }, /* to determine the action. */{ 51, 19, 59, 27, 49, 17, 57, 25 },{ 15, 47, 7,  39, 13, 45, 5,  37 },{ 63, 31, 55, 23, 61, 29, 53, 21 } };
#endifreturn (dither[x][y] + 1) / DITHER_NUM;}

从上面的代码中,可以看到我在注释里写的比较清楚的部分:
将其 LaTeX 化:

M1=[0231]M_1= \begin{bmatrix} 0 & 2 \\ 3 & 1 \end{bmatrix} M1​=[03​21​]

M2=[082112414631119157135]M_2= \begin{bmatrix} 0& 8& 2& 1\\ 12& 4& 14& 6\\ 3& 11& 1& 9\\ 15& 7& 13& 5 \end{bmatrix} M2​=⎣⎢⎢⎡​012315​84117​214113​1695​⎦⎥⎥⎤​

M3=[0328402341042481656245018582612444361446638602852206230542233511431339415119592749175725154773913455376331552361295321]M_3= \begin{bmatrix} 0& 32& 8& 40& 2& 34& 10& 42\\ 48& 16& 56& 24& 50& 18& 58& 26\\ 12& 44& 4& 36& 14& 46& 6& 38\\ 60& 28& 52& 20& 62& 30& 54& 22\\ 3& 35& 11& 43& 1& 33& 9& 41\\ 51& 19& 59& 27& 49& 17& 57& 25\\ 15& 47& 7& 39& 13& 45& 5& 37\\ 63& 31& 55& 23& 61& 29& 53& 21 \end{bmatrix} M3​=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡​04812603511563​3216442835194731​8564521159755​4024362043273923​25014621491361​3418463033174529​1058654957553​4226382241253721​⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤​

上面的 M1M_1M1​,M2M_2M2​,M3M_3M3​ 都是通过下面的公式来的:
Mn+1=[4Mn+0Un4Mn+2Un4Mn+3Un4Mn+1Un]M_{n+1}= \begin{bmatrix} 4M_n+0U_n & 4M_n+2U_n \\ 4M_n+3U_n & 4M_n+1U_n \end{bmatrix} Mn+1​=[4Mn​+0Un​4Mn​+3Un​​4Mn​+2Un​4Mn​+1Un​​]
简化
Mn+1=[4Mn4Mn+2Un4Mn+3Un4Mn+Un]M_{n+1}= \begin{bmatrix} 4M_n & 4M_n+2U_n \\ 4M_n+3U_n & 4M_n+U_n \end{bmatrix} Mn+1​=[4Mn​4Mn​+3Un​​4Mn​+2Un​4Mn​+Un​​]

其中 UnU_nUn​ 都是与 MnM_nMn​ 同纬度的矩阵,只不过 UnU_nUn​ 的每一个元素都是 1

比如:M1M_1M1​ 是 2x2 的矩阵,那么 U1U_1U1​ 如下:
U1=[1111]U_1= \begin{bmatrix} 1 & 1\\ 1 & 1 \end{bmatrix} U1​=[11​11​]


效果

原图如下

然后我们新建一个材质,将上面原图放到 main texture,再将材质放到 cube 上才看效果

可以控制 dithering 的 uv size 信息

可以看到有 Color (RGB) 、 Gray 的 通道输出结果

下面是:x2, x4, x8 的 Bayer Matrix 的切换效果


引用大神统计的一张 dither 算法图

上面 keijiro 使用的是 Floyd–Steinberg 算法,那么主流的 dithering 算法有下面这些:
(参考:10bit 视频是什么?相比起 8bit 视频有什么优势?)


Project

TestingDithering_unity_2019_4_30f1


References

  • 利用Floyd-Steinberg方法(dithering),将灰度图转换为二值图 - 程序化 动态 dithering - 图像信号学处理
  • keijiro/unity-dither4444
    • Unity图片优化神器 - dither算法究极进化方案 - 将 Floyd–Steinberg dithering 系数修改了一些,并将 RGBA32(8888) dither 到 RGB565
  • 10bit 视频是什么?相比起 8bit 视频有什么优势?
  • 数字半色调技术/RIP/dithering algorithm
  • 图像处理之 Dithering
  • DITHER 抖动算法
  • Ordered dithering - wiki 上的说明
  • glsl-shaders/dithering/shaders/bayer-matrix-dithering.glsl - glsl 的 bayer-matrix-dithering 的演示使用
  • Bayer filter
  • Unity3D Dither 抖动Shader实现

Unity Shader - 搬砖日志 - Dithering相关推荐

  1. Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)

    文章目录 目的 环境 PBR 主要渲染方程 D 项 GGB(desmos) D_Term 完整 Shader G 项 GGB G_Term 完整 Shader F 项 GGB F_Term 完整 Sh ...

  2. Unity Shader - 搬砖日志 - 3D Noise, Noise 3D 相关

    文章目录 GLSL noise 3d Project References 备忘,2D 的 noise 有纹理或是现成的比较多 3D noise 在 unity SG 出奇的没有封装 显示再 shad ...

  3. Unity Shader - 板砖日志 - 简单的树、草 等植物的 随风飘扬 动画

    文章目录 目的 思路 Script include cginc appled shader csharp 效果 目的 便于后续自己的 CTRL+C,V的面向复制.粘贴编程 思路 非常简单:可以使用 p ...

  4. Unity - 搬砖日志 - BRP 管线下的自定义阴影尺寸(脱离ProjectSettings/Quality/ShadowResolution设置)

    文章目录 环境 原因 解决 CSharp 脚本 效果预览 - Light.shadowCustomResolution 效果预览 - Using Quality Settings 应用 Control ...

  5. Unity - 搬砖日志 - 打开项目时崩溃/或是运行不起unity的日志

    崩溃日志目录所在:C:/Users/admins/AppData/Local/Unity/Editor/Editor.log 其中 admins 是你的 windows 登录账号名 打开该 Edito ...

  6. Unity - 搬砖日志 - 获取 LODGroup 当前显示的 LOD 级别

    最近 QA 同学需要在 某个 LODGroup 对象,当前显示的 LOD 是哪个级别的功能,所以上网搜索了一下,发现:Current LOD level - LOD Group Unity 也有人需求 ...

  7. Unity - 搬砖日志 - Android 的 Optimized Frame Pacing - 对应 ProjectSettings 配置字段:androidUseSwappy

    备忘用,因为 project settings 动不动,会提交一堆不相关的配置字段修改 Unity PlayerSettings 的 GUI 上 Optimized Frame Pacing 和 实际 ...

  8. Unity - 搬砖日志 - Occlusion Culling 关闭动态遮挡剔除 Renderer.allowOcclusionWhenDynamic = false

    文章目录 CullDynamicObjectsWithUmbra 消耗 如何禁止 动态对象的遮挡剔除的消耗 写工具来遍历 Renderer.allowOcclusionWhenDynamic = fa ...

  9. Unity Shader - 类似七龙珠的人物气焰效果

    文章目录 环境 效果 思路 passes pass - 气焰 优化后的 shader Project 关于效果落地 环境 Unity : 2018.3.11f1 Pipeline : BRP 效果 覆 ...

最新文章

  1. mysql 创建库 5.7_MySQL数据库之MySQL5.7创建用户时报错
  2. [羊城杯 2020]RRRRRRRSA
  3. solr java api_solr java api
  4. 甘特图 知乎_APS生产排程软件基础知识-计划甘特图
  5. opencv 叠加文字_opencv 图像上添加文字
  6. AcWing基础算法课Level-2 第四讲 数学知识
  7. python学生管理系统gui版好例子网_python界面版学生管理系统 相关实例(示例源码)下载 - 好例子网...
  8. Industrial Design System v4.5 1CD(工业产品设计软件)
  9. 捣鼓小米路由器开发版本
  10. 无线通信设备安装工程概预算编制_建筑安装工程,预算编制中易遗漏总结分享...
  11. stvd能编译c语言,stvd+stm8s单片机程序编译报错
  12. word图片撑满_word图片满页 word图片铺满整个页面
  13. 【代码质量】如何使用Valgrind检测内存泄漏
  14. OpenCV图像处理(下) 边缘检测+模板匹配+霍夫变换
  15. 心电图心电轴怎么计算_心电图电轴计算方法
  16. 信息安全三级易错题总结
  17. NLPCC-2019 依存句法分析领域移植评测技术分享(封闭情况下双第一)
  18. 安卓手机阅读软件哪个比较好啊
  19. Binary Knapsack (BKP) Problem 是什么
  20. Android Google 账户

热门文章

  1. 【vue项目】vue项目创建全流程,创建使用 vue-cli 搭建项目
  2. “数据”首次被中央纳入生产要素,数字政务建设迫在眉睫丨钛度热评
  3. vivo Y66解账户锁刷机包 线刷包救砖教程
  4. 小伙子,还是别把路走窄了
  5. jupyter自学使用教程
  6. 【源码分析设计模式 5】Java I/O系统中的装饰器模式
  7. 数据相关的在职研究生_大数据在职研究生都学什么?
  8. 易观分析:海外业务亮眼,研发+IP运营助力中手游持续增长
  9. jquery改变css样式和vue改变样式的区别
  10. 空间滤波 频率域滤波