HSV変換


~ HSV Transform ~






■はじめに

BBXで、「フォトショップの色相変化のように、テクスチャの模様を壊さずに色を変更させたいのです。」という質問があったので検証してみました。
一度RGB成分をHSVに変換して、Hをずらした後、RGBに戻します。
今回のアプリケーションでは、時間に応じて色相の変化量を変えています。 「t-pot」の文字を虹のグラデーションで書いているのですが、この部分はスクロールします。

で、いつものようにプログラムです。

カーソルキーで、カメラが回ります。

ソースには、いつものように適当にファイルが入っています。 大事なファイルは次のものです。

main.hアプリケーションのヘッダ
main.cppアプリケーションのソース
hlsl.fxシェーダプログラム

■HSV色座標系

HSV色座標系とは、比較的理解しやすい、色の表現方法の1つです。
HSV色座標系では、色はH(Hue/色相)、S(Saturation/彩度)、V(Value/明度)で表現されます。

色相とは、赤、黄、緑、シアン、青、マゼンタを周期的につなげた輪のような構造(色相環)をしており、色合いを表現します。

彩度とは、色の鮮やかさのことです。 例えば、彩度を上げればより派手な色になり、彩度を下げれば鮮やかでない白色に近くなります。

明度とは、色のもつ明るさの度合のことです。明度が高いほど明るい色になり、低ければ暗い色になります。

■プログラム

RGB色座標系からHSV色座標系への変換は色々なところに落ちているので、C言語のものをぱくって改良して使いましょう。

まず、元になる色ですが、デカールテクスチャの色を使います。 変数「rgb」に変換する色を格納しておきます。

hlsl.fx
0073: float4 PS(VS_OUTPUT In) : COLOR
0074: {   
0076:     float4 ret = (float4)0;
0077:     float4 decale = tex2D( DecaleSamp, In.Tex );      // デカール
0078:     float3 rgb = decale.xyz;    // 元になる色

RGBからHSVへの変換は、一番強い色をV値に、色の差を正規化したものをS値にします。
H値は、一番強い色から別の色への距離として求められます。
本来は、S値が0のときは、色味が無いので、H値には色実が無いことを表す「NO_HUE」を割り当てるのが普通ですが、今回はどうでもいいのでその処理は省きます。
なお、今回は、H、S、V各色共に0~1の間に正規化しました。

hlsl.fx
0079:     float3 hsv;
0080:     
0081:     // RGB 2 HSV
0082:     float max = max(rgb.r, max(rgb.g, rgb.b));
0083:     float min = min(rgb.r, min(rgb.g, rgb.b));
0084:     float delta = max - min;
0085: 
0086:     hsv.z = max; // v
0087:     if (max != 0.0){
0088:         hsv.y = delta / max;//s
0089:     }else{
0090:         hsv.y = 0.0;//s
0091:     }
0092:     
0075: //    const float NO_HUE = -1;
0093: //  if (hsv.y == 0.0) {
0094: //      hsv.x = NO_HUE; // h
0095: //  } else {
0096:       if ( rgb.r == max ){
0097:           hsv.x =     (rgb.g - rgb.b) / delta;// h
0098:       }else if (rgb.g == max){
0099:           hsv.x = 2 + (rgb.b - rgb.r) / delta;// h
0100:       }else{
0101:           hsv.x = 4 + (rgb.r - rgb.g) / delta;// h
0102:       }
0103:       hsv.x /= 6.0;
0104:       if (hsv.x < 0) hsv.x += 1.0;
0105: //  }

次に、まぁそのままではつまらないので、加工してみましょう。
H値を適当に動かして、一様に色合いを変えて見ます。

hlsl.fx
0107:     hsv.x += fShift;                                // 色相補正
0108:     if (1.0 <= hsv.x) hsv.x -= 1.0;

HSVからRGBへの逆変換もよく知られていて、そのまま実装してみます。

hlsl.fx
0110:     // HSV 2 RGB
0111:     if ( hsv.y == 0 ){ /* Grayscale */
0112:         ret.r = ret.g = ret.b = hsv.z;// v
0113:     } else {
0114:         if (1.0 <= hsv.x) hsv.x -= 1.0;
0115:         hsv.x *= 6.0;
0116:         float i = floor (hsv.x);
0117:         float f = hsv.x - i;
0118:         float aa = hsv.z * (1 - hsv.y);
0119:         float bb = hsv.z * (1 - (hsv.y * f));
0120:         float cc = hsv.z * (1 - (hsv.y * (1 - f)));
0121:         if( i < 1 ){
0122:             ret.r = hsv.z; ret.g = cc;    ret.b = aa;
0123:         }else if( i < 2 ){
0124:             ret.r = bb;    ret.g = hsv.z; ret.b = aa;
0125:         }else if( i < 3 ){
0126:             ret.r = aa;    ret.g = hsv.z; ret.b = cc;
0127:         }else if( i < 4 ){
0128:             ret.r = aa;    ret.g = bb;    ret.b = hsv.z;
0129:         }else if( i < 5 ){
0130:             ret.r = cc;    ret.g = aa;    ret.b = hsv.z;
0131:         }else{
0132:             ret.r = hsv.z; ret.g = aa;    ret.b = bb;
0133:         }
0134:     }

今回は、最終的に求められた色をシェーディング色とみなしてライティングして出力します。

hlsl.fx
0136:     return (0.5f*In.LN + 0.5f) * ret;
0137: }

■最後に

やる前は命令数とかたらないのかなぁと思っていたのですが、意外と実現できますね。
まぁ、プログラムの長さの割りにできることはたいしたことが無いので、あまり使わないと思いますが…





もどる

imagire@gmail.com