【unity】頂点カラーで色相をいじる

※注意※

この記事には最適化されていない情報が含まれています!
本来は記事を削除すべきですが、備忘録として残しているので、
これは "よくない実装方法なんだ" という認識で読んで下さい。
良くない例の箇所に再度注記を入れます。





軽量化を図る時、こんなことを思ったことはないだろうか。


「街の看板、ただの色違いなのに別テクスチャなのもったいないな...」

「フォトショで色相いじってるだけなんだけどな...」


そう思ったアナタ。
それ、このシェーダーで解決できます!


例えばこんな感じ。

画像1

これを、頂点カラーで色相をいじって、テクスチャは1パターンで済ませようというのが今回のシェーダーの趣旨。

紹介する前にあらかじめダメな点を挙げておこう。


・ダメポイント

1. 細かい色指定はできない。
2. 頂点カラーでの色指定がちょっと面倒。

1について。
これは当然だが色相をいじっているだけなので、例えばサンプル画像の黄色部分の文字が見づらいので文字だけ色を変えたい、などということはできない。
強行策として、色を変えたい部分をポリゴンで分割し別の頂点カラーを当てれば不可能ではないが、見栄えはあまりよくならない。

2について。
こちらは下記に続く使い方を見ていただければわかるだろう。
簡単に言うと、頂点カラーを色相に変換しなくてはいけないのである。



それでは紹介に移ろう。


紹介

まずはコードから。

Shader "Custom/HueChanger"
{
	Properties
	{
		_MainTex("Texture", 2D) = "white"{}
		_Cutoff("Alpha Cutoff", Range(0,1)) = 0.5
	}

		SubShader
	{
		Tags{ "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" }
		LOD 100
		Blend SrcAlpha OneMinusSrcAlpha
		Alphatest Greater[_Cutoff]
		AlphaToMask True

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			sampler2D _MainTex;
			float4 _MainTex_ST;

			struct appdata
			{
				float4 vertex : POSITION;
				fixed3 color : COLOR0;
				float2 uv:TEXCOORD0;
			};


			struct v2f
			{
				float4 vertex : SV_POSITION;
				fixed3 color : COLOR0;
				float2 uv:TEXCOORD0;
			};


			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.color = v.color;
				return o;
			}


			fixed4 frag(v2f i) : SV_Target
			{
				fixed4 texCol = tex2D(_MainTex, i.uv);
				float valMax = 0;
				float valmin = 0;
				float Hue = 0;
				float addHue = i.color.r * 360;

				if (texCol.r >= texCol.g && texCol.r >= texCol.b)
				{
					valMax = texCol.r;
					if (texCol.g > texCol.b)
					{
						valmin = texCol.b;
						Hue = 60 * (texCol.g - texCol.b) / (texCol.r - texCol.b);
					} else {
						valmin = texCol.g;
						Hue = 60 * (texCol.g - texCol.b) / (texCol.r - texCol.g);
					}
				};
				if (texCol.g >= texCol.b && texCol.g >= texCol.r)
				{
					valMax = texCol.g;
					if (texCol.b > texCol.r)
					{
						valmin = texCol.r;
						Hue = 60 * (texCol.b - texCol.r) / (texCol.g - texCol.r) + 120;
					} else {
						valmin = texCol.b;
						Hue = 60 * (texCol.b - texCol.r) / (texCol.g - texCol.b) + 120;
					}
				};
				if (texCol.b >= texCol.r && texCol.b >= texCol.g)
				{
					valMax = texCol.b;
					if (texCol.r > texCol.g)
					{
						valmin = texCol.g;
						Hue = 60 * (texCol.r - texCol.g) / (texCol.b - texCol.g) + 240;
					} else {
						valmin = texCol.r;
						Hue = 60 * (texCol.r - texCol.g) / (texCol.b - texCol.r) + 240;
					}
				};

				float newHue = Hue + addHue;
				float ColR;
				float ColG;
				float ColB;
				
				if (newHue > 360)
				{
					newHue -= 360;
				};
				
				if (newHue >= 0 && newHue <= 60)
				{
					ColR = valMax;
					ColB = valmin;
					ColG = valmin + ((valMax - valmin) * smoothstep(0, 59, newHue));
				};
				if (newHue >= 60 && newHue <= 120)

				{
					ColG = valMax;
					ColB = valmin;
					ColR = valMax + ((valmin - valMax) * smoothstep(60, 119, newHue));
				};
				if (newHue >= 120 && newHue <= 180)
				{
					ColG = valMax;
					ColR = valmin;
					ColB = valmin + ((valMax - valmin) * smoothstep(120, 179, newHue));
				};
				if (newHue >= 180 && newHue <= 240)
				{
					ColB = valMax;
					ColR = valmin;
					ColG = valMax + ((valmin - valMax) * smoothstep(180, 239, newHue));
				};
				if (newHue >= 240 && newHue <= 300)
				{
					ColB = valMax;
					ColG = valmin;
					ColR = valmin + ((valMax - valmin) * smoothstep(240, 299, newHue));
				};
				if (newHue >= 300 && newHue <= 360)
				{
					ColR = valMax;
					ColG = valmin;
					ColB = valMax + ((valmin - valMax) * smoothstep(300, 359, newHue));
				};

				fixed4 o = fixed4(ColR,ColG,ColB,texCol.a);
				return o;
			}
			ENDCG
		}
	}

	Fallback "Diffuse"
}

毎度のことで申し訳ないが、当方はプログラマではないのでコードの汚さには目を瞑っていただきたい...。

一応どういった処理をしているかも書いておく。
ちなみにこちらのシェーダーはUnlitであることをお忘れなく。

fixed4 texCol = tex2D(_MainTex, i.uv);
float valMax = 0;
float valmin = 0;
float Hue = 0;
float addHue = i.color.r * 360;

・MainTexに入れたテクスチャの色情報をtexColに代入
・addHueは頂点カラーの0~10~360に変換した値。
この値をHueに足してRGBに戻すという作業をする。


2023/05/26 追記
※ここがダメポイントです。
シェーダーは if 分岐が苦手。パフォーマンスがとても悪いです。
この下にもif分岐箇所がありますが、めちゃ多用してますね…。この頃はまだ何も知らなかった。

if (texCol.r >= texCol.g && texCol.r >= texCol.b)
{
    valMax = texCol.r;
    if (texCol.g > texCol.b)
    {
        valmin = texCol.b;
        Hue = 60 * (texCol.g - texCol.b) / (texCol.r - texCol.b);
    } else {
        valmin = texCol.g;
        Hue = 60 * (texCol.g - texCol.b) / (texCol.r - texCol.g);
    }
};
if (texCol.g >= texCol.b && texCol.g >= texCol.r)
{
    valMax = texCol.g;
    if (texCol.b > texCol.r)
    {
        valmin = texCol.r;
        Hue = 60 * (texCol.b - texCol.r) / (texCol.g - texCol.r) + 120;
    } else {
        valmin = texCol.b;
        Hue = 60 * (texCol.b - texCol.r) / (texCol.g - texCol.b) + 120;
    }
};
if (texCol.b >= texCol.r && texCol.b >= texCol.g)
{
    valMax = texCol.b;
    if (texCol.r > texCol.g)
    {
        valmin = texCol.g;
        Hue = 60 * (texCol.r - texCol.g) / (texCol.b - texCol.g) + 240;
    } else {
        valmin = texCol.r;
        Hue = 60 * (texCol.r - texCol.g) / (texCol.b - texCol.r) + 240;
    }
};

これはRGB to Hueの変換式。
端的に言うと、各RGBの最大値と最小値を出して、それがR、G、Bのどれなのかによって角度を出す。
ここでは最大値、最小値、色相の三種を場合分けで算出している。
float newHue = Hue + addHue;
float ColR;
float ColG;
float ColB;

ここで、上で得られた色相値に頂点カラーから得られる色相値を足して新しい色相値を指定している。
各色をここで指定した意味は特にない。この各色は最終的に新しい色になるもの。
if (newHue > 360)
{
newHue -= 360;
};
				
if (newHue >= 0 && newHue <= 60)
{
    ColR = valMax;
    ColB = valmin;
    ColG = valmin + ((valMax - valmin) * smoothstep(0, 59, newHue));
};
if (newHue >= 60 && newHue <= 120)
{
    ColG = valMax;
    ColB = valmin;
    ColR = valMax + ((valmin - valMax) * smoothstep(60, 119, newHue));
};
if (newHue >= 120 && newHue <= 180)
{
    ColG = valMax;
    ColR = valmin;
    ColB = valmin + ((valMax - valmin) * smoothstep(120, 179, newHue));
};
if (newHue >= 180 && newHue <= 240)
{
    ColB = valMax;
    ColR = valmin;
    ColG = valMax + ((valmin - valMax) * smoothstep(180, 239, newHue));
};
if (newHue >= 240 && newHue <= 300)
{
    ColB = valMax;
    ColG = valmin;
    ColR = valmin + ((valMax - valmin) * smoothstep(240, 299, newHue));
};
if (newHue >= 300 && newHue <= 360)
{
    ColR = valMax;
    ColG = valmin;
    ColB = valMax + ((valmin - valMax) * smoothstep(300, 359, newHue));
};

上のifは、Hueが360を超えた場合の処置。
下のifは、新しい色相値がどの角度かによって場合分けした、Hue to RGBの変換式。
fixed4 o = fixed4(ColR,ColG,ColB,texCol.a);
return o;

最後にRGB値を代入してfinish


見た目はこちら。

画像2

左がシェーダーを当てた状態、右が頂点カラーのみを表示した状態。
上段と中段はあまり違わないが気にしないでほしい。

画像3

テクスチャは冒頭で載せているこのサンプルテクスチャ。この色しか使用していない。


作り方

まずは画像編集ツールで、使用したいテクスチャの色違いを、どれだけ色相を変更すればいいか確かめよう。

画像4

画像はPhotoshopの色相変更ダイアログ。

次は頂点カラーを付ける。

画像5

この時、RGBは256段階なのに対し色相は360度で表現するため、その変換を行う必要がある。数式は

色相値 / 1.412

例えば上記例の場合は 40 ÷ 1.412 = 28.3286... ≒ 28
となり、明度に28を記入することでRGB全てに28を当てている。
シェーダーに記載している、

float addHue = i.color.r * 360;

ここを見ればわかるが、実際は赤情報しか取っていないので、赤のみの変更でも問題はない。(逆に言えば他の色を彩度の調整なんかに使えそう)



いかがだっただろうか。

個人的には非常に有用ではないだろうかと思っている。

この記事が気に入ったらサポートをしてみませんか?