LoginSignup
1
0

More than 3 years have passed since last update.

シンプルなImageEffect その12。HSV調整

Last updated at Posted at 2020-08-02

TwitterにアップしたHSV調整イメージエフェクトのシェーダコードを貼っておきます。

きっかけはyokotaro氏のツイート
「ニーアらしさとは「Photoshopで彩度を半分にすること」です」
というツイートを見た事。
10個近くImageEffectを投稿してきましたが、そういえばHSVを調整できるイメージエフェクトを作ってなかったですね。。
HSV調整ができれば、ImageEffect上で「ニーアらしさ」を実装できるはず。

手順としては、RGBカラーをHSV(色相、彩度、明度)空間に変換して、調整し再びRGBカラーに戻します。
RGB→HSV、HSV→RGBの変換式についてはググれば山の様に出てきますが、
せっかくなのでUnityEngine.Color.RGBtoHSV()、UnityEngine.Color.HSVtoRGB()のコードを参考にして実装します。
※下記コードで言うとRGBToHSVHelper(),RGBtoHSV(), HSVtoRGB()が参考&修正部分です。

hsv.shader
Shader "ScreenPocket/ImageEffect/HSV"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _HueOffset("Hue Offset", Range(-1,1)) = 0
        _SaturationMultiply("Saturation Multiply", Range(0,2)) = 1
        _SaturationOffset("Saturation Offset", Range(-1,1)) = 0
        _ValueMultiply("Value Multiply", Range(0,2)) = 1
        _ValueOffset("Value Offset", Range(-1,1)) = 0
    }
    SubShader
    {
        // No culling or depth
        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;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            half3 RGBToHSVHelper(
                half offset,
                half dominantColor,
                half colorOne,
                half colorTwo)
            {
                half3 hsv;
                hsv.z = dominantColor;
                if (hsv.z == 0.0)
                {
                    hsv.x = 0;
                    hsv.y = 0;
                    return hsv;
                }

                half num1 = colorOne <= colorTwo ? colorOne : colorTwo;
                half num2 = hsv.z - num1;
                if (num2 != 0.0)
                {
                    hsv.y = num2 / hsv.z;
                    hsv.x = offset + (colorOne - colorTwo) / num2;
                }
                else
                {
                    hsv.y = 0.0f;
                    hsv.x = offset + (colorOne - colorTwo);
                }

                hsv.x /= 6.0;
                if ( hsv.x >= 0.0)
                    return hsv;
                hsv.x += 1.0;
                return hsv;
            }

            half3 RGBtoHSV(half3 rgb)
            {
                if (rgb.b > rgb.g && rgb.b > rgb.r)
                    return RGBToHSVHelper(4.0, rgb.b, rgb.r, rgb.g);
                if (rgb.g > rgb.r)
                    return RGBToHSVHelper(2.0, rgb.g, rgb.b, rgb.r);

                return RGBToHSVHelper(0.0, rgb.r, rgb.g, rgb.b);
            }

            half3 HSVtoRGB(half3 hsv)
            {
                if (hsv.y == 0.0)
                {
                    return hsv.zzz;
                }

                if (hsv.z == 0.0)
                {
                    return half3(0,0,0);
                }

                half num1 = hsv.y;
                half num2 = hsv.z;
                half f = hsv.x * 6.0;
                int num3 = (int)floor(f);
                half num4 = f - (half)num3;
                half num5 = num2 * (1.0 - num1);
                half num6 = num2 * (1.0 - num1 * num4);
                half num7 = num2 * (1.0 - num1 * (1.0 - num4));

                switch (num3)
                {
                case -1:
                    return saturate(half3(num2,num5,num6));
                case 0:
                    return saturate(half3(num2,num7,num5));
                case 1:
                    return saturate(half3(num6,num2,num5));
                case 2:
                    return saturate(half3(num5,num2,num7));
                case 3:
                    return saturate(half3(num5,num6,num2));
                case 4:
                    return saturate(half3(num7,num5,num2));
                case 5:
                    return saturate(half3(num2,num5,num6));
                case 6:
                    return saturate(half3(num2,num7,num5));
                default:
                    return half3(0,0,0);
                }
            }

            half _HueOffset;
            half _SaturationMultiply;
            half _SaturationOffset;
            half _ValueMultiply;
            half _ValueOffset;
            sampler2D _MainTex;

            half4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);
                half3 hsv = RGBtoHSV(col.rgb);

                hsv.x = frac(hsv.x + _HueOffset);
                hsv.y = saturate(hsv.y * _SaturationMultiply + _SaturationOffset );
                hsv.z = saturate(hsv.z * _ValueMultiply + _ValueOffset );

                col.rgb = HSVtoRGB(hsv);
                return col;
            }
            ENDCG
        }
    }
}

ポイントはHSVのH(色相)に関してだけは周期性があるのでsaturate()ではなくfrac()を使っている所でしょうか。
あと、計算式はLDR用に元コードの一部のHDR用if文を端折っていたりします。
※HLSL化した時に面倒くさいので今回からfixedは使わないようにしました。fixed化したい場合は該当箇所を書き換えてください。

_SaturationMultiplyに0.5を入れれば「ニーアらしさ」をリスペクト出来るかと。
他にも色相調整でガラッと雰囲気を変えることも出来るので、色々試してみたいですね。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0