编写图像抖动修正色带问题
图像色带(Band)问题一般出现在带有渐变的图像上,在影视上可以换成16bit的色深以解决,
而实时渲染领域通常对图像做色彩抖动处理来解决。但抖动这块一直没有找到很好的插件,PS也一直没有
相关教程。本文就自己动手丰衣足食;编写一个小工具来实现图像抖动,配合蓝噪声效果更佳。
2024/09/10补充:
FYI1:PS的“储存为Web所用格式”然后选择Png也可以抖动,基于调色板Png模式,不过质量较差
FYI2:MagickImage库也可实现抖动,支持算法且有C#版本,推荐使用
色带问题演示(上图为色带,下图为增加过噪声的效果):
c#代码(使用时挂载任意GameObject并在脚本上右键Exec,随后会在Assets根目录生成输出图像):
using System.IO; using System.Collections; using System.Collections.Generic; using UnityEngine; public class ColorDither : MonoBehaviour { public Texture src; public Material mat; public Texture blueNoiseTex; public int iterate = 1; [ContextMenu("Exec")] private void Exec() { RenderTexture rt = new RenderTexture(src.width, src.height, 0); rt.Create(); mat.SetInt("_Iterate", iterate); mat.SetTexture("_BlueNoiseTex", blueNoiseTex); Graphics.Blit(src, rt, mat); RenderTexture.active = rt; Rect rect = new Rect(0f, 0f, rt.width, rt.height); Texture2D tex2D = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); tex2D.ReadPixels(rect, 0, 0); tex2D.Apply(); RenderTexture.active = null; rt.Release(); if (File.Exists("Assets/output.jpg")) File.Delete("Assets/output.jpg"); File.WriteAllBytes("Assets/output.jpg", tex2D.EncodeToJPG()); #if UNITY_EDITOR UnityEditor.AssetDatabase.Refresh(); #endif } }
Shader(还包含了一个8x8矩阵的程序化抖动,但效果没蓝噪声版本好):
Shader "Hidden/ColorDither" { Properties { _MainTex("Texture", 2D) = "white" {} } SubShader { Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o = (v2f)0; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } sampler2D _MainTex; uniform float4 _MainTex_TexelSize; uniform int _Iterate; uniform sampler2D _BlueNoiseTex; half _8x8DitherClip(half value, half2 sceneUVs) { if (value <= 0) return -0.1; half2 ditherUV = half2(fmod(sceneUVs.x, 8), fmod(sceneUVs.y, 8)); const float dither[64] = { 0, 48, 12, 60, 3, 51, 15, 63, 32, 16, 44, 28, 35, 19, 47, 31, 8, 56, 4, 52, 11, 59, 7, 55, 40, 24, 36, 20, 43, 27, 39, 23, 2, 50, 14, 62, 1, 49, 13, 61, 34, 18, 46, 30, 33, 17, 45, 29, 10, 58, 6, 54, 9, 57, 5, 53, 42, 26, 38, 22, 41, 25, 37, 21 }; int index = ditherUV.y * 8 + ditherUV.x; return value - dither[index] / 64; } inline fixed4 ExecuteDither(sampler2D tex, half2 duv, half2 uv, int iterate) { fixed4 dryCol = tex2D(tex, uv); fixed4 minValue = dryCol; fixed4 maxValue = dryCol; for (int x = -iterate; x <= iterate; x++) { for (int y = -iterate; y <= iterate; y++) { fixed4 col = tex2D(tex, uv + duv * half2(x, y)); minValue = min(col, minValue); maxValue = max(col, maxValue); } } fixed4 delta = (maxValue - minValue); half2 ditherUv = uv*512; //8X8 Dither Version: //fixed4 wetCol = lerp(minValue, maxValue, step(0, _8x8DitherClip(delta.x, ditherUv))); //Blue Noise Version: fixed4 wetCol = lerp(minValue, maxValue, tex2D(_BlueNoiseTex, uv*5)); return wetCol; } fixed4 frag(v2f i) : SV_Target { return ExecuteDither(_MainTex, half2(_MainTex_TexelSize.x, _MainTex_TexelSize.y), i.uv, _Iterate); } ENDCG } } }
具体Demo见gitee:https://gitee.com/Hont/color-dither