编写图像抖动修正色带问题

图像色带(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

posted @ 2023-01-18 11:00  HONT  阅读(186)  评论(0编辑  收藏  举报