文章目录
什么时 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.glsl
Shader "Test/TestingDithering_UnOptimize"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
FrameCount ("FrameCount", Int) = 2
OutputSize ("OutputSize", Vector) = (1, 1, 1, 1)
animate("animate", Range(0, 1)) = 1
dither_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);
}
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
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.glsl
Shader "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的矩阵开始,如下 M1
M1 = | 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;
else
return 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_ON
return mainCol;
#else // #ifndef _DITHERING_ON
float2 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_ON
fixed4 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_ON
fixed3 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
}
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma multi_compile _ _DITHERING_ON
#pragma multi_compile _ _ONLY_GRAY_ON
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
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.glsl
Shader "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的矩阵开始,如下 M1
M1 = | 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 0
g = step(limit, g); // 压缩到 1 or 0
b = 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_ON
return mainCol;
#else // #ifndef _DITHERING_ON
float2 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_ON
fixed4 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_ON
find_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
}
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma multi_compile _ _DITHERING_ON
#pragma multi_compile _ _ONLY_GRAY_ON
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
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.glsl
Shader "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
#endif
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的矩阵开始,如下 M1
M1 = | 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_X2
const 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_X4
const 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_X8
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 } };
#endif
return (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 0
g = step(limit, g); // 压缩到 1 or 0
b = 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_ON
return mainCol;
#else // #ifndef _DITHERING_ON
float2 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_ON
fixed4 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_ON
find_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
}
ENDCG
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
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 frag
ENDCG
}
}
}
简单理解 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的矩阵开始,如下 M1
M1 = | 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_X2
const 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_X4
const 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_X8
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 } };
#endif
return (dither[x][y] + 1) / DITHER_NUM;
}
从上面的代码中,可以看到我在注释里写的比较清楚的部分:
将其 LaTeX 化:
M 1 = [ 0 2 3 1 ] M_1= \begin{bmatrix} 0 & 2 \\ 3 & 1 \end{bmatrix} M1=[0321]
M 2 = [ 0 8 2 1 12 4 14 6 3 11 1 9 15 7 13 5 ] M_2= \begin{bmatrix} 0& 8& 2& 1\\ 12& 4& 14& 6\\ 3& 11& 1& 9\\ 15& 7& 13& 5 \end{bmatrix} M2=⎣⎢⎢⎡012315841172141131695⎦⎥⎥⎤
M 3 = [ 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 ] 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=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡0481260351156332164428351947318564521159755402436204327392325014621491361341846303317452910586549575534226382241253721⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
上面的
M
1
M_1
M1,
M
2
M_2
M2,
M
3
M_3
M3 都是通过下面的公式来的:
M
n
+
1
=
[
4
M
n
+
0
U
n
4
M
n
+
2
U
n
4
M
n
+
3
U
n
4
M
n
+
1
U
n
]
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+0Un4Mn+3Un4Mn+2Un4Mn+1Un]
简化
M
n
+
1
=
[
4
M
n
4
M
n
+
2
U
n
4
M
n
+
3
U
n
4
M
n
+
U
n
]
M_{n+1}= \begin{bmatrix} 4M_n & 4M_n+2U_n \\ 4M_n+3U_n & 4M_n+U_n \end{bmatrix}
Mn+1=[4Mn4Mn+3Un4Mn+2Un4Mn+Un]
其中 U n U_n Un 都是与 M n M_n Mn 同纬度的矩阵,只不过 U n U_n Un 的每一个元素都是 1
比如:
M
1
M_1
M1 是 2x2 的矩阵,那么
U
1
U_1
U1 如下:
U
1
=
[
1
1
1
1
]
U_1= \begin{bmatrix} 1 & 1\\ 1 & 1 \end{bmatrix}
U1=[1111]
效果
原图如下
然后我们新建一个材质,将上面原图放到 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实现