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"
}

五、效果对比

基础色:

色相:

饱和度:

明度:

相关推荐
cyr___11 分钟前
Unity教程(二十二)技能系统 分身技能
学习·游戏·unity·游戏引擎
妙为1 小时前
unreal engine5 mation warping使用,敌人受击后面向攻击者
ue5·游戏引擎·虚幻
我命由我123453 小时前
游戏引擎 Unity - Unity 顶部菜单栏(文件、编辑、资源、游戏对象、组件、服务、窗口、帮助)
学习·游戏·unity·ue5·游戏引擎·游戏程序·游戏策划
Hello.Reader3 小时前
调试 Rust + WebAssembly 版康威生命游戏
游戏·rust·wasm
YY-nb4 小时前
基于 Quest 摄像头数据开发的原理介绍【Unity Meta Quest MR 开发教程】
unity·游戏引擎·mr
Cindy辛蒂6 小时前
C语言:能够规定次数,处理非法字符的猜数游戏(三重循环)
c语言·算法·游戏
咩咩觉主7 小时前
C# &Unity 唐老狮 No.10 模拟面试题
开发语言·unity·c#
LF男男7 小时前
xLua_001
unity·lua
@Sunset...7 小时前
热更新解决方案5——toLua
开发语言·unity·游戏引擎·lua