ShaderでSpriteの色相をシフトする

はじめに

前回、Sprite、Texture の 色相をシフトするで、SpriteからTexture2Dを取得して、SetPixelを使って色相をシフトすることをした。
今回はシェーダーを使って色相をシフトして、下図のように画像の色を変える。

色相をアニメーション

用意

Unity のダウンロードから ビルトインシェーダーをダウンロードする。インストーラーじゃなくて、右端にあるビルトインシェーダ。この記事は現在最新の4.6.1をダウンロードして記述する。

解凍したら、DefaultResources にある Sprites-Default.shaderを元に作成する。これはSpriteを生成するとデフォルトで使われるシェーダーだ。
適当に名前を Sprites-HSV-Default.shader などにしてUnityのProjectに突っ込んで開始。

シェーダー名の変更

Shader “Sprites/Default” の箇所を Shader “Sprites/HSV Default” に変更。これで、MaterialのShaderのドロップダウンから Sprites -> HSV Default を選択できるようになる。

Propertiesの追加

まずはインスペクター上から設定できる項目を増やす。 色相(H)、彩度(S)、明度(V) の 3つを追加したいので、Propertiesを以下のように修正。

1
2
3
4
5
6
7
8
9
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_Hue ("Hue", Float) = 0 //色相
_Sat ("Saturation", Float) = 1 //彩度
_Val ("Value", Float) = 1 //明度
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
}

このシェーダーをマテリアルに設定すると、下図のように Hue、Saturation、Value の値がインスペクターから設定できるようになる。

Hue、Saturation、Valueのインスペクタ

frag の修正

テクスチャーの各ピクセルの色に対して色相をシフトするので、fragの処理を修正する。
ここで、RGBからHSVに変換して計算するのだが、その処理は以下を参照した。

Problem with getting hue shift shader right.

追記、修正したコードは以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
fixed3 shift_col(fixed3 RGB, half3 shift)
{
fixed3 RESULT = fixed3(RGB);
float VSU = shift.z*shift.y*cos(shift.x*3.14159265/180);
float VSW = shift.z*shift.y*sin(shift.x*3.14159265/180);

RESULT.x = (.299*shift.z+.701*VSU+.168*VSW)*RGB.x
+ (.587*shift.z-.587*VSU+.330*VSW)*RGB.y
+ (.114*shift.z-.114*VSU-.497*VSW)*RGB.z;

RESULT.y = (.299*shift.z-.299*VSU-.328*VSW)*RGB.x
+ (.587*shift.z+.413*VSU+.035*VSW)*RGB.y
+ (.114*shift.z-.114*VSU+.292*VSW)*RGB.z;

RESULT.z = (.299*shift.z-.3*VSU+1.25*VSW)*RGB.x
+ (.587*shift.z-.588*VSU-1.05*VSW)*RGB.y
+ (.114*shift.z+.886*VSU-.203*VSW)*RGB.z;

return (RESULT);
}

sampler2D _MainTex;
half _Hue, _Sat, _Val;

fixed4 frag(v2f IN) : SV_Target
{
fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
c.rgb *= c.a;

half3 shift = half3(_Hue, _Sat, _Val);

return fixed4( shift_col(c, shift), c.a);
}

Spriteに適用

あとは適当なマテリアルに作成したシェーダーをつけて、SpriteRendererに設定すれば良い(下図)。

マテリアルにshaderを設定

スクリプトから色相をシフトして、アニメーションする処理はこうなる。

1
2
3
4
void Update () {
angle += 60f * Time.deltaTime;
renderer.material.SetFloat("_Hue", angle);
}

シェーダーの全ソース

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
Shader "Sprites/HSV Default"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_Hue ("Hue", Float) = 0
_Sat ("Saturation", Float) = 1
_Val ("Value", Float) = 1
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
}

SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}

Cull Off
Lighting Off
ZWrite Off
Fog { Mode Off }
Blend One OneMinusSrcAlpha

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

struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};

struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
half2 texcoord : TEXCOORD0;
};

fixed4 _Color;

v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color;
#ifdef PIXELSNAP_ON
OUT.vertex = UnityPixelSnap (OUT.vertex);
#endif

return OUT;
}


fixed3 shift_col(fixed3 RGB, half3 shift)
{
fixed3 RESULT = fixed3(RGB);
float VSU = shift.z*shift.y*cos(shift.x*3.14159265/180);
float VSW = shift.z*shift.y*sin(shift.x*3.14159265/180);

RESULT.x = (.299*shift.z+.701*VSU+.168*VSW)*RGB.x
+ (.587*shift.z-.587*VSU+.330*VSW)*RGB.y
+ (.114*shift.z-.114*VSU-.497*VSW)*RGB.z;

RESULT.y = (.299*shift.z-.299*VSU-.328*VSW)*RGB.x
+ (.587*shift.z+.413*VSU+.035*VSW)*RGB.y
+ (.114*shift.z-.114*VSU+.292*VSW)*RGB.z;

RESULT.z = (.299*shift.z-.3*VSU+1.25*VSW)*RGB.x
+ (.587*shift.z-.588*VSU-1.05*VSW)*RGB.y
+ (.114*shift.z+.886*VSU-.203*VSW)*RGB.z;

return (RESULT);
}

sampler2D _MainTex;
half _Hue, _Sat, _Val;

fixed4 frag(v2f IN) : SV_Target
{
fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
c.rgb *= c.a;

half3 shift = half3(_Hue, _Sat, _Val);

return fixed4( shift_col(c, shift), c.a);
}
ENDCG
}
}
}


確認バージョン

Unity 4.6

Unityカテゴリの記事
Color SpaceがLinearのときUIの透明度が正しくならない
History Inspectorの紹介
敵AIとビジュアルスクリプティング
Chronosを使った感想
Smart Inspectorの紹介
コンポーネントの順番をスクリプトから並び替える
Kris' Favorite Assets が便利
キー操作でUIのナビゲーションをループさせる
TextMeshProのSprite Assetを更新する
UnityPhysicsDebugDraw2D が便利
色管理を考える
細かいTips
ビルドスクリプトを書く
AnimatorのCulling Modeでハマった話
Vectrosityを使ってUGUI上で線や円のアニメーションをする
スプレッドシートからjsonデータを読み込む
ビジュアルノベルアセットFungusにコマンドを追加してカスタマイズする
Skinned Mesh Renderer の Boundsについて
シーンごとにビルド結果の容量を出す
シーンビューにクオリティ設定のスライダーを出すエディタ拡張
ビルド結果のFile headersが大きい理由
フリーのビジュアルノベルアセットFungusを使ってRPGのイベントを作る
Move To View を改良する
フリーのビジュアルノベルアセットのFungusが便利
Visual Studio で保存時にフォーマットする
スプレッドシートからデータを読み込む
Easy Save2 で シリアライズされたクラスを保存する
Sprite、Texture の 色相をシフトする
uGUIのButtonをクリック時にハイライトのままになる
uGUIのCanvas Groupを使って透過処理をしたり、操作を制限する
自作のコンフィグ画面に必要なもの
uGUIでトグルなボタンを作る
uGUI で動的にボタンを作る
Easy Save2 を使ってみる
csv読み込んで ローカライズ
LoadLevelAdditive で共通シーンを加算
画面全体に色をかける
Any State でどこからでも遷移できるようにする
iTween のStop ではまる
sprite の multiple で 境界がおかしくなる
Renderer の Materials を スクリプトから設定する
2D画面に線を引く Line Renderer
背景をスクロールさせる