Unity Shader - 搬砖日志 - Dithering

141 篇文章 31 订阅
50 篇文章 2 订阅


什么时 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

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader是一种用于在Unity引擎中创建和控制图形渲染效果的编程语言。通过使用Unity Shader,开发人员可以自定义游戏中各种物体的外观和行为,从而实现更加逼真和出色的视觉效果。 而热图(Heatmap)是一种用于显示某个区域内物体热度分布的视觉化工具。在游戏开发中,热图通常用于统计和分析玩家在游戏中的行为和偏好,以便开发人员可以根据这些数据进行游戏优化和改进。 为了创建一个热图效果,我们可以使用Unity Shader来实现。首先,我们需要将游戏中各个物体按照玩家与其的互动情况和频率进行区分,不同的行为和频率可以对应不同的颜色或者纹理。接着,我们可以在Shader中根据这些信息来着色和渲染物体,以展示物体的热度分布。 在Shader中,我们可以通过为物体添加一张热图纹理,并使用该纹理来表示物体的热度值。热图纹理可以是一张灰度图,不同的灰度值对应不同的热度。然后,我们可以使用纹理坐标和采样操作来获取每个像素对应的热度值,并根据这些值来着色和渲染物体。 除了使用纹理来表示热度分布,我们还可以使用其他的技术和效果来增强热图的可视化效果。例如,我们可以使用颜色渐变和透明度来形成平滑的过渡效果,以更好地显示物体的热度变化。我们还可以添加动画效果,使热图效果更加生动和有趣。 总结而言,Unity Shader可以用于创建热图效果,通过着色和渲染来展示物体的热度分布。这样的热图可以帮助开发人员分析游戏中玩家的行为和偏好,从而优化和改进游戏的设计和内容。这些热图效果能够增强游戏的可视化效果,并提供有价值的数据供开发人员参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值