Unity Shader - 搬砖日志 - Dithering
文章目录
- 什么时 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=[0321]
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=⎣⎢⎢⎡012315841172141131695⎦⎥⎥⎤
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=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡0481260351156332164428351947318564521159755402436204327392325014621491361341846303317452910586549575534226382241253721⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
上面的 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+0Un4Mn+3Un4Mn+2Un4Mn+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=[4Mn4Mn+3Un4Mn+2Un4Mn+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=[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实现
Unity Shader - 搬砖日志 - Dithering相关推荐
- Unity Shader - 搬砖日志 - URP PBR (抄作业篇,持续更新~)
文章目录 目的 环境 PBR 主要渲染方程 D 项 GGB(desmos) D_Term 完整 Shader G 项 GGB G_Term 完整 Shader F 项 GGB F_Term 完整 Sh ...
- Unity Shader - 搬砖日志 - 3D Noise, Noise 3D 相关
文章目录 GLSL noise 3d Project References 备忘,2D 的 noise 有纹理或是现成的比较多 3D noise 在 unity SG 出奇的没有封装 显示再 shad ...
- Unity Shader - 板砖日志 - 简单的树、草 等植物的 随风飘扬 动画
文章目录 目的 思路 Script include cginc appled shader csharp 效果 目的 便于后续自己的 CTRL+C,V的面向复制.粘贴编程 思路 非常简单:可以使用 p ...
- Unity - 搬砖日志 - BRP 管线下的自定义阴影尺寸(脱离ProjectSettings/Quality/ShadowResolution设置)
文章目录 环境 原因 解决 CSharp 脚本 效果预览 - Light.shadowCustomResolution 效果预览 - Using Quality Settings 应用 Control ...
- Unity - 搬砖日志 - 打开项目时崩溃/或是运行不起unity的日志
崩溃日志目录所在:C:/Users/admins/AppData/Local/Unity/Editor/Editor.log 其中 admins 是你的 windows 登录账号名 打开该 Edito ...
- Unity - 搬砖日志 - 获取 LODGroup 当前显示的 LOD 级别
最近 QA 同学需要在 某个 LODGroup 对象,当前显示的 LOD 是哪个级别的功能,所以上网搜索了一下,发现:Current LOD level - LOD Group Unity 也有人需求 ...
- Unity - 搬砖日志 - Android 的 Optimized Frame Pacing - 对应 ProjectSettings 配置字段:androidUseSwappy
备忘用,因为 project settings 动不动,会提交一堆不相关的配置字段修改 Unity PlayerSettings 的 GUI 上 Optimized Frame Pacing 和 实际 ...
- Unity - 搬砖日志 - Occlusion Culling 关闭动态遮挡剔除 Renderer.allowOcclusionWhenDynamic = false
文章目录 CullDynamicObjectsWithUmbra 消耗 如何禁止 动态对象的遮挡剔除的消耗 写工具来遍历 Renderer.allowOcclusionWhenDynamic = fa ...
- Unity Shader - 类似七龙珠的人物气焰效果
文章目录 环境 效果 思路 passes pass - 气焰 优化后的 shader Project 关于效果落地 环境 Unity : 2018.3.11f1 Pipeline : BRP 效果 覆 ...
最新文章
- mysql 创建库 5.7_MySQL数据库之MySQL5.7创建用户时报错
- [羊城杯 2020]RRRRRRRSA
- solr java api_solr java api
- 甘特图 知乎_APS生产排程软件基础知识-计划甘特图
- opencv 叠加文字_opencv 图像上添加文字
- AcWing基础算法课Level-2 第四讲 数学知识
- python学生管理系统gui版好例子网_python界面版学生管理系统 相关实例(示例源码)下载 - 好例子网...
- Industrial Design System v4.5 1CD(工业产品设计软件)
- 捣鼓小米路由器开发版本
- 无线通信设备安装工程概预算编制_建筑安装工程,预算编制中易遗漏总结分享...
- stvd能编译c语言,stvd+stm8s单片机程序编译报错
- word图片撑满_word图片满页 word图片铺满整个页面
- 【代码质量】如何使用Valgrind检测内存泄漏
- OpenCV图像处理(下) 边缘检测+模板匹配+霍夫变换
- 心电图心电轴怎么计算_心电图电轴计算方法
- 信息安全三级易错题总结
- NLPCC-2019 依存句法分析领域移植评测技术分享(封闭情况下双第一)
- 安卓手机阅读软件哪个比较好啊
- Binary Knapsack (BKP) Problem 是什么
- Android Google 账户