TwitterにアップしたHSV調整イメージエフェクトのシェーダコードを貼っておきます。
先日RTした「ニーアらしさ」を出せるように、彩度を調整できるImageEffectシェーダ書いた。
— MIYAKE (@ScreenPocket) August 2, 2020
ついでにHSVの微調整も出来るようにしました。
HSVtoRGB変換は基礎的なやつだけど、そういえばQiitaにまだ記事を書いてなかったので今日の夜にでも書きます。 pic.twitter.com/a1lC3mvFTx
きっかけはyokotaro氏のツイート
「ニーアらしさとは「Photoshopで彩度を半分にすること」です」
というツイートを見た事。
10個近くImageEffectを投稿してきましたが、そういえばHSVを調整できるイメージエフェクトを作ってなかったですね。。
HSV調整ができれば、ImageEffect上で「ニーアらしさ」を実装できるはず。
手順としては、RGBカラーをHSV(色相、彩度、明度)空間に変換して、調整し再びRGBカラーに戻します。
RGB→HSV、HSV→RGBの変換式についてはググれば山の様に出てきますが、
せっかくなのでUnityEngine.Color.RGBtoHSV()、UnityEngine.Color.HSVtoRGB()のコードを参考にして実装します。
※下記コードで言うとRGBToHSVHelper(),RGBtoHSV(), HSVtoRGB()が参考&修正部分です。
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を入れれば「ニーアらしさ」をリスペクト出来るかと。
他にも色相調整でガラッと雰囲気を変えることも出来るので、色々試してみたいですね。