BBXで、「フォトショップの色相変化のように、テクスチャの模様を壊さずに色を変更させたいのです。」という質問があったので検証してみました。
一度RGB成分をHSVに変換して、Hをずらした後、RGBに戻します。
今回のアプリケーションでは、時間に応じて色相の変化量を変えています。
「t-pot」の文字を虹のグラデーションで書いているのですが、この部分はスクロールします。
で、いつものようにプログラムです。
カーソルキーで、カメラが回ります。
ソースには、いつものように適当にファイルが入っています。 大事なファイルは次のものです。
main.h | アプリケーションのヘッダ |
main.cpp | アプリケーションのソース |
hlsl.fx | シェーダプログラム |
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: }
やる前は命令数とかたらないのかなぁと思っていたのですが、意外と実現できますね。
まぁ、プログラムの長さの割りにできることはたいしたことが無いので、あまり使わないと思いますが…