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