demoscene.jp Japanese demoscene portal

1912月/120

HSV 表色系を使ってみよう (C++ / GLSL 実装付き)

メガデモアドベントカレンダー 2012、本日は RGB とはちょっと違う方法で色を扱う表色系、「HSV」を紹介します。

―――駆け出しデモシーナーの @Reputeless は、かっこいいデモを作るために、プログラムで虹色のグラデーションを描こうとしました―――

ええっと、赤が RGB(1.0f, 0.0f, 0.0f)、赤とオレンジの中間が RGB(1.0f, 0.25f, 0.0f)
オレンジは RGB(1.0f, 0.5f, 0.0f)、黄色は RGB(1.0f, 1.0f, 0.0f)
黄緑色は RGB(0.0f, 1.0f, 0.0f);
・・・うっ


うわああ、めんどくさい!
レインボーなグラデーションって RGB でどうやって表現するの!?

気を取り直して、今度はカラフルな円をたくさん描くことにしました。
RGB の値を乱数で決めているようです。

RGB( Random(), Random(), Random() )、RGB( Random(), Random(), Random() )・・・
・・・うっ


うわああっ、全体的にくすんだ色ばかり!
どうすれば鮮やかな色になるんだろう!?
色を作るのって難しい!
 
 
―――おやおや、前途多難のようですね。
HSV 表色系を知っていれば、こんな苦労をしなくてすんだのに!

HSV って何?

HSV とは、色相(Hue)、彩度(Saturation)、明度(Value)の 3 つの要素で色を表す方法のことです。

色相は、赤っぽい、青っぽいといった「色あい」を表します。
色相を環状に並べたものを色相環といい、赤は 0°、黄色は 60°といったように円上の角度で色あいを示します。

彩度は「鮮やかさ」を表します。
色相が同じでも、彩度が高ければ鮮やかに見え、低ければグレーに近くなります。彩度がゼロの場合は無彩色(黒、グレー、白)になります。

明度は「明るさ」を表します。明度が高ければ明るい色に、低ければ暗い色になります。

色相 H、彩度 S 、明度 V と、RGB 成分の対応は次の図の通りです。

HSV 表色系 では RGB で作成できるすべての色を表現できます。
RGB に比べて直感的に色を選択することができるので、色づくりがとても楽になると思います。

例えば、RGB では難しかった次のようなプログラムが簡単にできます。

・S と V を固定し H を遷移させて虹色のグラデーションを生み出す

・S を 1.0、 V を 1.0 に固定し H を乱数で決めることで、グレーや黒といった無彩色を避けながらランダムな色を作り出す

これで冒頭の悩みは解決しましたね。
ほかにも面白い使い方がたくさん思いつきそうです。ぜひいろいろ試してみてください。

実装

HSV から RGB への変換を C++ と GLSL で実装しました。

どちらも、色相 h の 0°~360° を 0.0 ~ 1.0 に対応させています。
h < 0.0 や 1.0 < h のときでも正しく動作します。

C++ のコード

# include <cmath>
# include <algorithm>

namespace megademo
{
	float Fraction( float v )
	{
		return v - floor(v);
	}

	struct RGB
	{
		RGB(){}

		RGB( float r_, float g_, float b_ ) : r(r_),g(g_),b(b_){}

		float r,g,b;	// [0.0f, 1.0f]
	};

	struct HSV
	{
		HSV(){}

		HSV( float h_, float s_, float v_ ) : h(h_),s(s_),v(v_){}

		float h;	// ... 0°==0.0f, 360°==1.0f ...
		
		float s,v;	// [0.0f, 1.0f]
	};

	RGB HSVtoRGB( const HSV& hsv )
	{
		const float h = Fraction(hsv.h);
		const float s = hsv.s;
		const float v = hsv.v;
		const float hueF = h * 6.0f;
		const int hueI = static_cast<int>(hueF);
		const float fr = hueF - hueI;
		const float m = v * (1.0f-s);
		const float n = v * (1.0f-s*fr);
		const float p = v * (1.0f-s*(1.0f-fr));

		RGB rgb;

		switch(hueI)
		{
			case 0: rgb.r = v; rgb.g = p; rgb.b = m; break;
			case 1: rgb.r = n; rgb.g = v; rgb.b = m; break;
			case 2: rgb.r = m; rgb.g = v; rgb.b = p; break;
			case 3: rgb.r = m; rgb.g = n; rgb.b = v; break;
			case 4: rgb.r = p; rgb.g = m; rgb.b = v; break;
			default: rgb.r = v; rgb.g = m; rgb.b = n; break;
		}

		return rgb;
	}

	HSV RGBtoHSV( const RGB& rgb )
	{
		const float min = std::min(std::min(rgb.r,rgb.g),rgb.b);
		const float max = std::max(std::max(rgb.r,rgb.g),rgb.b);

		HSV hsv(0.0f,0.0f,max);	

		const float delta = max - min;

		if(delta!=0.0f)
		{
			hsv.s = delta / max;

			if(rgb.r==max)
			{
				hsv.h = (rgb.g-rgb.b) / delta;
			}
			else if(rgb.g==max)
			{
				hsv.h = 2.0f + (rgb.b-rgb.r) / delta;
			}
			else
			{
				hsv.h = 4.0f + (rgb.r-rgb.g) / delta;
			}
		
			hsv.h /= 6.0f;

			if(hsv.h<0.0f)
			{
				hsv.h += 1.0f;
			}
		}

		return hsv;
	}
}

 

GLSL のコード

uniform float time;
uniform vec2 resolution;
uniform vec2 mouse;

vec3 Hue( float hue )
{
	vec3 rgb = fract(hue + vec3(0.0,2.0/3.0,1.0/3.0));

	rgb = abs(rgb*2.0-1.0);
		
	return clamp(rgb*3.0-1.0,0.0,1.0);
}

vec3 HSVtoRGB( vec3 hsv )
{
	return ((Hue(hsv.x)-1.0)*hsv.y+1.0) * hsv.z;
}

void main( void )
{
	float h = gl_FragCoord.x / resolution.x;
	
	float s = gl_FragCoord.y / resolution.y;
	
	float v = mouse.y;
	
	gl_FragColor = vec4(HSVtoRGB(vec3(h,s,v)),1.0);
}

GLSL の実装は少しトリッキーです。
コストの大きい分岐の代わりに abs や saturate 命令を使っているので高速に動作します。

このコードを GLSL sandbox で動かしてみました。
http://glsl.heroku.com/e#5505.0

x 座標が色相に、y 座標が彩度に対応しています。
明度を変化させるには、マウスを上下に移動させてください。
 
 
―――こうして、HSV 表色系という色作りの便利アイテムを手に入れた Reputeless は、
Tokyo Demo Fest 2013 に向け着々とデモを作っていくのでした。
 
 
参考
・グラフ: http://ja.wikipedia.org/wiki/%E8%89%B2%E7%9B%B8
http://chilliant.blogspot.jp/2010/11/rgbhsv-in-hlsl.html
http://glsl.heroku.com/e#5397.3
 
 

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

Trackbacks are disabled.