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?=[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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android.view.Inflate
- 下一篇: 简述核心网