Unity URPShader:实现和PS一样的色相/饱和度调整参数效果

目录

前言:

一、色相(Hue)

二、饱和度(Saturation)

三、明度(Lightness)

四、全代码实现

五、效果对比


前言:

PS中的色相/饱和度调节界面可通过快捷键Ctrl+U快速打开,如下

而饱和度和明度调整,PS中的处理并不像平常的单一图像处理,而是做了一些算法改变,接下来就是详细拆分这其中的原理。

一、色相(Hue)

色相的处理就没有那么多的弯弯绕绕了,只需要将RGB转化为HSV,根据值设置H的变化,在转化会RGB即可。原本PS中的色相/饱和度处理面板是对HSL空间的处理,照理说是需要转换到HSL空间的,但对于色相来说,HSV和HSL都是一致的,所以并不需要我们真正的转到HSL。

代码:

复制代码
          // RGB转HSV/HSB
           half3 RGB2HSV(half3 c)
           {
                half4 K = half4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
                half4 P = lerp(half4(c.bg, K.wz), half4(c.gb, K.xy), step(c.b, c.g));
                half4 Q = lerp(half4(P.xyw, c.r), half4(c.r, P.yzx), step(P.x, c.r));
                half D = Q.x - min(Q.w, Q.y);
                half E = 1e-10;
                return half3(abs(Q.z + (Q.w - Q.y) / (6.0 * D + E)), D / (Q.x + E), Q.x);
            }
            
            // HSV/HSB转RGB
            half3 HSV2RGB(half3 c)
            {
                half4 K = half4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
                half3 P = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
                return c.z * lerp(K.xxx, saturate(P - K.xxx), c.y);
            }

                 //Hue
                half3 hsv = RGB2HSV(color.rgb);
                hsv.x = frac(hsv.x + _Hue / 360.0);
                color.rgb = HSV2RGB(hsv);

二、饱和度(Saturation)

饱和度本身在图像处理中就是根据灰度来处理的,但在PS中却设置了许多其它条件,其算法原理如下操作:

(注意,以下原理只使用RGB颜色空间,Lab颜色将不适用此公式)

对每个像素点:

rgbMax=max(R,G,B);

rgbMin=min(R,G,B);

delta=(rgbMax-rgbMin)/255

如果delta=0,则此像素点不再进行处理,而是跳出,进行下一个像素点颜色操作

如果delta>0,则value=(rgbMax+rgbMin)/255

HSL中的L:L=value/2;

HSL中的S:如果L<0.5,S=delta/value;

如果L>=0.5,S=delta/(2-value)

percent = 饱和度调整参数/100【将其归一化到(-1,1)】

如果percent>=0,先求出alpha:

如果percent+S>=1:alpha=S;

否则:alpha=1-percent;

alpha=1/alpha-1;

final_rgb = RGB+(RGB-L*255)*alpha;

如果percent<0,alpha=percent

final_rgb = L*255+(RGB-L*255)*(1+alpha);

解释:其中rgbMax是指传入颜色RGB中最大值

rgbMin是指传入颜色RGB中最小值

percent是指饱和度调整参数归一化后的值(参数在PS的范围是(-100,100))

Shader代码:

复制代码
 half3 Saturation(half3 color, float saturation)
            {
                half rgbMax = max(color.r, max(color.g, color.b));
                half rgbMin = min(color.r, min(color.g, color.b));
                half delta = rgbMax - rgbMin;
                if (delta > 0)
                {
                    half value = rgbMax + rgbMin;
                    half L = value * 0.5;
                    half S = lerp(delta / value, delta / (2.0 - value), step(0.5, delta));
                    half percent = saturation * 0.01;
                    half alpha = 0;
                    half3 final_color = 0;
                    if (percent >= 0)
                    {
                        if (percent + S >= 1)
                            alpha = S;
                        else
                            alpha = 1 - percent;
                        alpha = 1 / alpha - 1;
                        final_color = color + (color - half3(L, L, L)) * alpha;
                    }
                    else
                    {
                        alpha = percent;
                        final_color = L + (color - half3(L, L, L)) * (1 + alpha);
                    }
                
                    return final_color;
                }
                else
                    return color;
            }

优化算法:

复制代码
  half3 Saturation(half3 color, float saturation)
            {
                half rgbMax = max(color.r, max(color.g, color.b));
                half rgbMin = min(color.r, min(color.g, color.b));
                half delta = rgbMax - rgbMin;
                half value = rgbMax + rgbMin;
                half L = value * 0.5;
                half S = lerp(delta / value, delta / (2.0 - value), step(0.5, delta));
                half percent = saturation * 0.01;
                half3 final_color = lerp(L + (color - L.xxx) * (1 + percent),
                color + (color - L.xxx) * (1.0 / (lerp(1.0 - percent, S, step(1.0, percent + S))) - 1.0),
                step(0.0, percent));

                final_color = lerp(color, final_color, ceil(delta));
                return final_color;
            }

函数调用

复制代码
              //Saturation
                color.rgb = Saturation(color.rgb, _Saturation);

三、明度(Lightness)

PS的明度不是指代HSV中的Value和HSB中的Brightness,它也是不线性的单一乘法。实际上,PS的明度调整采用的是Alpha遮罩模式。明度正调整是以一张白色遮罩层进行操作,负调整则是黑色遮罩层。其公式如下:

(注意,以下原理只使用RGB颜色空间,Lab颜色将不适用此公式)

alpha=_Lightness/100【对alpha进行归一化,范围为(-1,1)】

如果_Lightness>0:

final_color = RGB*(1-alpha)+255*alpha;

如果_Lightness<0:

final_color = RGB+RGB*alpha;

解释:其中255代表白色,255*alpha就代表是白色遮罩层,明度越大,遮罩层alpha越大

当alpha<0时则是已黑色遮罩层进行计算,alpha=-1时则是全黑

当且仅当alpha=0时显示的是原图片

Shader代码:

复制代码
                 //Value
                half value = _Lightness * 0.01;
                if (value > 0)
                    color.rgb = (1 - value) * color.rgb + half3(1.0, 1.0, 1.0) * value;
                else if (value < 0)
                    color.rgb = color.rgb * (1.0 + value);

优化算法:

复制代码
 color.rgb = lerp(color.rgb * (1.0 + value), lerp(color.rgb, half3(1.0, 1.0, 1.0), value), step(0.0, _Lightness));

四、全代码实现

复制代码
Shader "Background_Custom_HSV"
{
    Properties
    {
        _PatternTex ("贴图", 2D) = "black" { }
        [Header(BaseColor)]
        [Space]
        [HDR]_Color0 ("颜色", Color) = (1, 1, 1, 1)

        [Header(HSL)]
        [Space]
        _Hue ("Hue", Range(-180, 180)) = 0.0
        _Saturation ("Saturation", Range(-100, 100)) = 0.0
        _Lightness ("Lightness", Range(-100, 100)) = 0.0

    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True" }

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Back
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"


            TEXTURE2D(_PatternTex);
            SAMPLER(sampler_PatternTex);

            CBUFFER_START(UnityPerMaterial)
                half3 _Color0;
                float _Hue;
                float _Saturation;
                float _Lightness;
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 texcoord : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

     
            // RGB转HSV/HSB
            half3 RGB2HSV(half3 c)
            {
                half4 K = half4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
                half4 P = lerp(half4(c.bg, K.wz), half4(c.gb, K.xy), step(c.b, c.g));
                half4 Q = lerp(half4(P.xyw, c.r), half4(c.r, P.yzx), step(P.x, c.r));
                half D = Q.x - min(Q.w, Q.y);
                half E = 1e-10;
                return half3(abs(Q.z + (Q.w - Q.y) / (6.0 * D + E)), D / (Q.x + E), Q.x);
            }
            
            // HSV/HSB转RGB
            half3 HSV2RGB(half3 c)
            {
                half4 K = half4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
                half3 P = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
                return c.z * lerp(K.xxx, saturate(P - K.xxx), c.y);
            }


            half3 Saturation(half3 color, float saturation)
            {
                half rgbMax = max(color.r, max(color.g, color.b));
                half rgbMin = min(color.r, min(color.g, color.b));
                half delta = rgbMax - rgbMin;
                // if (delta > 0)
                // {
                //     half value = rgbMax + rgbMin;
                //     half L = value * 0.5;
                //     half S = lerp(delta / value, delta / (2.0 - value), step(0.5, delta));
                //     half percent = saturation * 0.01;
                //     half alpha = 0;
                //     half3 final_color = 0;
                //     if (percent >= 0)
                //     {
                //         if (percent + S >= 1)
                //             alpha = S;
                //         else
                //             alpha = 1 - percent;
                //         alpha = 1 / alpha - 1;
                //         final_color = color + (color - half3(L, L, L)) * alpha;
                //     }
                //     else
                //     {
                //         alpha = percent;
                //         final_color = L + (color - half3(L, L, L)) * (1 + alpha);
                //     }
                
                //     return final_color;
                // }
                // else
                //     return color;
                half value = rgbMax + rgbMin;
                half L = value * 0.5;
                half S = lerp(delta / value, delta / (2.0 - value), step(0.5, delta));
                half percent = saturation * 0.01;
                half3 final_color = lerp(L + (color - L.xxx) * (1 + percent),
                color + (color - L.xxx) * (1.0 / (lerp(1.0 - percent, S, step(1.0, percent + S))) - 1.0),
                step(0.0, percent));

                final_color = lerp(color, final_color, ceil(delta));
                return final_color;
            }
            
            Varyings vert(Attributes v)
            {
                Varyings o;
                o.positionHS = TransformObjectToHClip(v.positionOS.xyz);
                o.uv = v.texcoord;
                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_PatternTex, sampler_PatternTex, i.uv);
                color.rgb *= _Color0.rgb;
                //Hue
                half3 hsv = RGB2HSV(color.rgb);
                hsv.x = frac(hsv.x + _Hue / 360.0);
                color.rgb = HSV2RGB(hsv);
                //Saturation
                color.rgb = Saturation(color.rgb, _Saturation);
                
                //Value
                half value = _Lightness * 0.01;
                // if (value > 0)
                //     color.rgb = (1 - value) * color.rgb + half3(1.0, 1.0, 1.0) * value;
                // else if (value < 0)
                //     color.rgb = color.rgb * (1.0 + value);
                color.rgb = lerp(color.rgb * (1.0 + value), lerp(color.rgb, half3(1.0, 1.0, 1.0), value), step(0.0, _Lightness));
                color.rgb = saturate(color.rgb);
                return color;
            }
            ENDHLSL
        }
    }

    FallBack "Hidden/Universal Render Pipeline/FallbackError"
}

五、效果对比

基础色:

色相:

饱和度:

明度:

相关推荐
BT-BOX11 小时前
普中开发板基于51单片机贪吃蛇游戏设计
单片机·游戏·51单片机
90后小陈老师13 小时前
Unity教学 项目2 2D闯关游戏
游戏·unity·游戏引擎
噗噗夹的TA之旅13 小时前
Unity Shader 学习20:URP LitForwardPass PBR 解析
学习·unity·游戏引擎·图形渲染·技术美术
nnsix13 小时前
Unity ReferenceFinder插件 多选资源查找bug解决
unity·游戏引擎·bug
gzroy15 小时前
Unity Shader Graph实现全息瞄准器
unity·游戏引擎
90后小陈老师18 小时前
Unity教学 基础介绍
unity·游戏引擎
90后小陈老师18 小时前
Unity教学 项目3 3D坦克大战
3d·unity·游戏引擎
秦奈20 小时前
Unity复习学习随笔(五):Unity基础
学习·unity·游戏引擎
nnsix21 小时前
Unity ReferenceFinder插件 窗口中选择资源时 同步选择Assets下的资源
java·unity·游戏引擎
毕设源码-邱学长21 小时前
【开题答辩全过程】以 基于协同过滤算法的游戏推荐系统的设计与实现为例,包含答辩的问题和答案
游戏